]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add start of fault-injection tests for session module. Fix some bugs related to the...
authordan <dan@noemail.net>
Mon, 21 Mar 2011 16:17:42 +0000 (16:17 +0000)
committerdan <dan@noemail.net>
Mon, 21 Mar 2011 16:17:42 +0000 (16:17 +0000)
FossilOrigin-Name: 32e95164d1192b87b1ab019549fd2394642cd3fe

ext/session/session2.test
ext/session/session_common.tcl [new file with mode: 0644]
ext/session/sessionfault.test [new file with mode: 0644]
ext/session/sqlite3session.c
manifest
manifest.uuid
src/vdbeapi.c

index 357b806043eed734750ed16d1ca5ebf50ed9dd24..9065df99a0743d515a3586013b3e2fb550e7c7ee 100644 (file)
@@ -28,67 +28,6 @@ proc test_reset {} {
   sqlite3 db2 test.db2
 }
 
-proc do_common_sql {sql} {
-  execsql $sql db
-  execsql $sql db2
-}
-proc xConflict args { return "OMIT" }
-
-proc do_then_apply_sql {sql {dbname main}} {
-
-  sqlite3session S db $dbname
-  db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
-    S attach $name
-  }
-
-  db eval $sql
-  sqlite3changeset_apply db2 [S changeset] xConflict
-  S delete
-}
-
-proc do_iterator_test {tn tbl_list sql res} {
-  sqlite3session S db main
-  foreach t $tbl_list {S attach $t}
-  execsql $sql
-
-  set r [list]
-  foreach v $res { lappend r $v }
-
-  set x [list]
-  sqlite3session_foreach c [S changeset] { lappend x $c }
-  uplevel do_test $tn [list [list set {} $x]] [list $r]
-
-  S delete
-}
-
-# Compare the contents of all tables in [db1] and [db2]. Throw an error if 
-# they are not identical, or return an empty string if they are.
-#
-proc compare_db {db1 db2} {
-
-  set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name}
-  set lot1 [$db1 eval $sql]
-  set lot2 [$db2 eval $sql]
-
-  if {$lot1 != $lot2} { error "databases contain different tables" }
-
-  foreach tbl $lot1 {
-    set col1 [list]
-    set col2 [list]
-
-    $db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name }
-    $db2 eval "PRAGMA table_info = $tbl" { lappend col2 $name }
-    if {$col1 != $col2} { error "table $tbl schema mismatch" }
-
-    set sql "SELECT * FROM $tbl ORDER BY [join $col1 ,]"
-    set data1 [$db1 eval $sql]
-    set data2 [$db2 eval $sql]
-    if {$data1 != $data2} { error "table $tbl data mismatch" }
-  }
-
-  return ""
-}
-
 ##########################################################################
 # End of proc definitions. Start of tests.
 ##########################################################################
@@ -105,12 +44,13 @@ do_iterator_test 1.1 t1 {
   {DELETE t1 {t i t one} {}} 
   {INSERT t1 {} {t ii t two}}
 }
-do_iterator_test 1.1 t1 {
+do_iterator_test 1.2 t1 {
   INSERT INTO t1 VALUES(1.5, 99.9)
 } {
   {INSERT t1 {} {f 1.5 f 99.9}}
 }
 
+
 # Execute each of the following blocks of SQL on database [db1]. Collect
 # changes using a session object. Apply the resulting changeset to
 # database [db2]. Then check that the contents of the two databases are
@@ -178,6 +118,38 @@ set set_of_tests {
   12 {
     INSERT INTO %T2% VALUES(NULL, NULL);
   }
+
+  13 {
+    DELETE FROM %T1% WHERE 1;
+
+    -- Insert many rows with real primary keys. Enough to force the session
+    -- objects hash table to resize. 
+    INSERT INTO %T1% VALUES(0.1, 0.1);
+    INSERT INTO %T1% SELECT a+0.1, b+0.1 FROM %T1%;
+    INSERT INTO %T1% SELECT a+0.2, b+0.2 FROM %T1%;
+    INSERT INTO %T1% SELECT a+0.4, b+0.4 FROM %T1%;
+    INSERT INTO %T1% SELECT a+0.8, b+0.8 FROM %T1%;
+    INSERT INTO %T1% SELECT a+1.6, b+1.6 FROM %T1%;
+    INSERT INTO %T1% SELECT a+3.2, b+3.2 FROM %T1%;
+    INSERT INTO %T1% SELECT a+6.4, b+6.4 FROM %T1%;
+    INSERT INTO %T1% SELECT a+12.8, b+12.8 FROM %T1%;
+    INSERT INTO %T1% SELECT a+25.6, b+25.6 FROM %T1%;
+    INSERT INTO %T1% SELECT a+51.2, b+51.2 FROM %T1%;
+    INSERT INTO %T1% SELECT a+102.4, b+102.4 FROM %T1%;
+    INSERT INTO %T1% SELECT a+204.8, b+204.8 FROM %T1%;
+  }
+
+  14 {
+    DELETE FROM %T1% WHERE 1;
+  }
+
+  15 {
+    INSERT INTO %T1% VALUES(1, 1);
+    INSERT INTO %T1% SELECT a+2, b+2 FROM %T1%;
+    INSERT INTO %T1% SELECT a+4, b+4 FROM %T1%;
+    INSERT INTO %T1% SELECT a+8, b+8 FROM %T1%;
+    INSERT INTO %T1% SELECT a+256, b+256 FROM %T1%;
+  }
 }
 
 test_reset
@@ -189,7 +161,7 @@ do_common_sql {
 
 foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] {
   do_then_apply_sql $sql
-  do_test 1.$tn { compare_db db db2 } {}
+  do_test 2.$tn { compare_db db db2 } {}
 }
 
 # The following block of tests is similar to the last, except that the
@@ -200,7 +172,7 @@ foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] {
 test_reset
 forcedelete test.db3
 sqlite3 db3 test.db3
-do_test 2.0 {
+do_test 3.0 {
   execsql {
     ATTACH 'test.db3' AS 'aux';
     CREATE TABLE t1(a, b PRIMARY KEY);
@@ -224,7 +196,7 @@ foreach {tn sql} [
   string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3} $set_of_tests
 ] {
   do_then_apply_sql $sql aux
-  do_test 2.$tn { compare_db db3 db2 } {}
+  do_test 3.$tn { compare_db db3 db2 } {}
 }
 catch {db3 close}
 
@@ -234,7 +206,7 @@ catch {db3 close}
 # handled correctly by the session module.
 #
 test_reset
-do_execsql_test 3.0 {
+do_execsql_test 4.0 {
   CREATE TABLE t1(a PRIMARY KEY);
   CREATE TABLE t2(a, b, c, PRIMARY KEY(c, b));
   CREATE TABLE t3(a, b INTEGER PRIMARY KEY);
@@ -272,7 +244,7 @@ foreach {tn sql changeset} {
   9 { DELETE FROM t2 WHERE 1 }               { {DELETE t2 {i 1 i 2 i 3} {}} }
 
 } {
-  do_iterator_test 3.$tn {t1 t2 t3} $sql $changeset
+  do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
 }
 
 
diff --git a/ext/session/session_common.tcl b/ext/session/session_common.tcl
new file mode 100644 (file)
index 0000000..1b5ebc5
--- /dev/null
@@ -0,0 +1,106 @@
+
+
+proc do_conflict_test {tn args} {
+  proc xConflict {args} { 
+    lappend ::xConflict $args
+    return "" 
+  }
+  proc bgerror {args} { set ::background_error $args }
+
+
+  set O(-tables)    [list]
+  set O(-sql)       [list]
+  set O(-conflicts) [list]
+
+  array set V $args
+  foreach key [array names V] {
+    if {![info exists O($key)]} {error "no such option: $key"}
+  }
+  array set O $args
+
+  sqlite3session S db main
+  foreach t $O(-tables) { S attach $t }
+  execsql $O(-sql)
+
+  set ::xConflict [list]
+  sqlite3changeset_apply db2 [S changeset] xConflict
+
+  set conflicts [list]
+  foreach c $O(-conflicts) {
+    lappend conflicts $c
+  }
+
+  after 1 {set go 1}
+  vwait go
+
+  uplevel do_test $tn [list { set ::xConflict }] [list $conflicts]
+  S delete
+}
+
+proc do_common_sql {sql} {
+  execsql $sql db
+  execsql $sql db2
+}
+
+proc do_then_apply_sql {sql {dbname main}} {
+  proc xConflict args { return "OMIT" }
+
+  set rc [catch {
+    sqlite3session S db $dbname
+    db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
+      S attach $name
+    }
+    db eval $sql
+    sqlite3changeset_apply db2 [S changeset] xConflict
+  } msg]
+
+  catch { S delete }
+
+  if {$rc} {error $msg}
+}
+
+proc do_iterator_test {tn tbl_list sql res} {
+  sqlite3session S db main
+  foreach t $tbl_list {S attach $t}
+  execsql $sql
+
+  set r [list]
+  foreach v $res { lappend r $v }
+
+  set x [list]
+  sqlite3session_foreach c [S changeset] { lappend x $c }
+  uplevel do_test $tn [list [list set {} $x]] [list $r]
+
+  S delete
+}
+
+# Compare the contents of all tables in [db1] and [db2]. Throw an error if 
+# they are not identical, or return an empty string if they are.
+#
+proc compare_db {db1 db2} {
+
+  set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name}
+  set lot1 [$db1 eval $sql]
+  set lot2 [$db2 eval $sql]
+
+  if {$lot1 != $lot2} { error "databases contain different tables" }
+
+  foreach tbl $lot1 {
+    set col1 [list]
+    set col2 [list]
+
+    $db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name }
+    $db2 eval "PRAGMA table_info = $tbl" { lappend col2 $name }
+    if {$col1 != $col2} { error "table $tbl schema mismatch" }
+
+    set sql "SELECT * FROM $tbl ORDER BY [join $col1 ,]"
+    set data1 [$db1 eval $sql]
+    set data2 [$db2 eval $sql]
+    if {$data1 != $data2} { error "table $tbl data mismatch" }
+  }
+
+  return ""
+}
+
+
+
diff --git a/ext/session/sessionfault.test b/ext/session/sessionfault.test
new file mode 100644 (file)
index 0000000..d6dd361
--- /dev/null
@@ -0,0 +1,85 @@
+# 2011 Mar 21
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the session module.
+#
+
+if {![info exists testdir]} {
+  set testdir [file join [file dirname [info script]] .. .. test]
+} 
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+
+set testprefix sessionfault
+
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_common_sql {
+  CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
+  INSERT INTO t1 VALUES(1, 2, 3);
+  INSERT INTO t1 VALUES(4, 5, 6);
+}
+faultsim_save_and_close
+db2 close
+
+# Test OOM error handling when collecting and applying a simple changeset.
+#
+do_faultsim_test pagerfault-1 -faults oom-* -prep {
+  catch {db2 close}
+  catch {db close}
+  faultsim_restore_and_reopen
+  sqlite3 db2 test.db2
+} -body {
+  do_then_apply_sql {
+    INSERT INTO t1 VALUES(7, 8, 9);
+    UPDATE t1 SET c = 10 WHERE a = 1;
+    DELETE FROM t1 WHERE a = 4;
+  }
+} -test {
+  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+  faultsim_integrity_check
+  if {$testrc==0} { compare_db db db2 }
+}
+
+# This test case is designed so that a malloc() failure occurs while
+# resizing the session object hash-table from 256 to 512 buckets. This
+# is not an error, just a sub-optimal condition.
+#
+do_faultsim_test pagerfault-2 -faults oom-* -prep {
+  catch {db2 close}
+  catch {db close}
+  faultsim_restore_and_reopen
+  sqlite3 db2 test.db2
+
+  sqlite3session S db main
+  S attach t1
+  execsql { BEGIN }
+  for {set i 0} {$i < 125} {incr i} {
+    execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 10+$i)}
+  }
+} -body {
+  for {set i 125} {$i < 133} {incr i} {
+    execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 1-+$i)}
+  }
+  S changeset
+  set {} {}
+} -test {
+  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+  if {$testrc==0} { 
+    sqlite3changeset_apply db2 [S changeset] xConflict
+    compare_db db db2 
+  }
+  catch { S delete }
+  faultsim_integrity_check
+}
+
+finish_test
index 4663938bb2339caaf38a8e7cf911cbb22c7781d5..e6d4675b08b5a95c65de1a1717cc3c0b1b971811 100644 (file)
@@ -305,6 +305,7 @@ static unsigned int sessionPreupdateHash(
       }else{
         rc = sqlite3_preupdate_old(db, i, &pVal);
       }
+      if( rc!=SQLITE_OK ) return rc;
 
       eType = sqlite3_value_type(pVal);
       h = HASH_APPEND(h, eType);
@@ -424,33 +425,35 @@ static int sessionPreupdateEqual(
       }
       if( rc!=SQLITE_OK || sqlite3_value_type(pVal)!=eType ) return rc;
 
-      switch( eType ){
-        case SQLITE_INTEGER:
-        case SQLITE_FLOAT: {
-          i64 iVal = sessionGetI64(a);
-          a += 8;
-          if( eType==SQLITE_INTEGER ){
-            if( sqlite3_value_int64(pVal)!=iVal ) return SQLITE_OK;
-          }else{
-            double rVal;
-            assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
-            memcpy(&rVal, &iVal, 8);
-            if( sqlite3_value_double(pVal)!=rVal ) return SQLITE_OK;
-          }
-          break;
+      /* A SessionChange object never has a NULL value in a PK column */
+      assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
+           || eType==SQLITE_BLOB    || eType==SQLITE_TEXT
+      );
+
+      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
+        i64 iVal = sessionGetI64(a);
+        a += 8;
+        if( eType==SQLITE_INTEGER ){
+          if( sqlite3_value_int64(pVal)!=iVal ) return SQLITE_OK;
+        }else{
+          double rVal;
+          assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
+          memcpy(&rVal, &iVal, 8);
+          if( sqlite3_value_double(pVal)!=rVal ) return SQLITE_OK;
         }
-        case SQLITE_TEXT:
-        case SQLITE_BLOB: {
-          int n;
-          const u8 *z;
-          a += sessionVarintGet(a, &n);
-          if( sqlite3_value_bytes(pVal)!=n ) return SQLITE_OK;
-          z = eType==SQLITE_TEXT ? 
-            sqlite3_value_text(pVal) : sqlite3_value_blob(pVal);
-          if( memcmp(a, z, n) ) return SQLITE_OK;
-          a += n;
-          break;
+      }else{
+        int n;
+        const u8 *z;
+        a += sessionVarintGet(a, &n);
+        if( sqlite3_value_bytes(pVal)!=n ) return SQLITE_OK;
+        if( eType==SQLITE_TEXT ){
+          z = sqlite3_value_text(pVal);
+        }else{
+          z = sqlite3_value_blob(pVal);
         }
+        if( memcmp(a, z, n) ) return SQLITE_OK;
+        a += n;
+        break;
       }
     }
   }
@@ -734,20 +737,22 @@ static void sessionPreupdateOneChange(
           rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
         }
       }
-      pChange->nRecord = nByte;
-  
-      /* If an error has occurred, mark the session object as failed. */
-      if( rc!=SQLITE_OK ){
-        sqlite3_free(pChange);
-        pSession->rc = rc;
-      }else{
+      if( rc==SQLITE_OK ){
         /* Add the change back to the hash-table */
+        pChange->nRecord = nByte;
         pChange->bInsert = (op==SQLITE_INSERT);
         pChange->pNext = pTab->apChange[iHash];
         pTab->apChange[iHash] = pChange;
+      }else{
+        sqlite3_free(pChange);
       }
     }
   }
+
+  /* If an error has occurred, mark the session object as failed. */
+  if( rc!=SQLITE_OK ){
+    pSession->rc = rc;
+  }
 }
 
 /*
@@ -1345,7 +1350,7 @@ int sqlite3session_changeset(
       }
 
       nNoop = buf.nBuf;
-      for(i=0; i<pTab->nChange; i++){
+      for(i=0; i<pTab->nChange && rc==SQLITE_OK; i++){
         SessionChange *p;         /* Used to iterate through changes */
 
         for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
@@ -1366,13 +1371,14 @@ int sqlite3session_changeset(
               sessionAppendByte(&buf, SQLITE_DELETE, &rc);
               sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
             }
-            rc = sqlite3_reset(pSel);
+            if( rc==SQLITE_OK ){
+              rc = sqlite3_reset(pSel);
+            }
           }
         }
       }
 
       sqlite3_finalize(pSel);
-
       if( buf.nBuf==nNoop ){
         buf.nBuf = nRewind;
       }
@@ -1674,7 +1680,9 @@ int sqlite3changeset_conflict(
 int sqlite3changeset_finalize(sqlite3_changeset_iter *p){
   int i;                          /* Used to iterate through p->apValue[] */
   int rc = p->rc;                 /* Return code */
-  for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]);
+  if( p->apValue ){
+    for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]);
+  }
   sqlite3_free(p->apValue);
   sqlite3_free(p);
   return rc;
@@ -2316,7 +2324,8 @@ int sqlite3changeset_apply(
   SessionApplyCtx sApply;         /* changeset_apply() context object */
 
   memset(&sApply, 0, sizeof(sApply));
-  sqlite3changeset_start(&pIter, nChangeset, pChangeset);
+  rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
+  if( rc!=SQLITE_OK ) return rc;
 
   sqlite3_mutex_enter(sqlite3_db_mutex(db));
   rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
index 63945fc2412ab3c01c9743f4c735ed13111f1c96..3c63f1b8cce4d552c9b240791efcc09157b65b13 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Clarify\shandling\sof\sNULL\svalues\sin\sPK\scolumns\sin\ssqlite3session.h.\sAdd\stests\sand\sfixes\sfor\sthe\ssame.
-D 2011-03-21T11:55:07
+C Add\sstart\sof\sfault-injection\stests\sfor\ssession\smodule.\sFix\ssome\sbugs\srelated\sto\sthe\ssame.
+D 2011-03-21T16:17:42
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -100,8 +100,10 @@ F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0
 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
 F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
 F ext/session/session1.test 3f982c74ee4ba97069917cc35aae25b4ed858e6a
-F ext/session/session2.test 3ef304f660b2a929e6bfec2df125c1809f5501ff
-F ext/session/sqlite3session.c 70b19f80eadf7060836eaa90928f08a58aa3b35f
+F ext/session/session2.test 6462c21d3795d9e48ffea2e7550d1b2d6da66dfb
+F ext/session/session_common.tcl 880b554b0bcadcabe1331afb87d58ad1ed2510c4
+F ext/session/sessionfault.test 4190de237b2c76ca7529ef415778a862d7d0fa30
+F ext/session/sqlite3session.c c5a60c2cf21f8892f9ae4850fad2d7859c2c3692
 F ext/session/sqlite3session.h 2c071ee5925e82c21c7c9c296a0422c039607106
 F ext/session/test_session.c 2559ef68e421c7fb83e2c19ef08a17343b70d535
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
@@ -239,7 +241,7 @@ F src/vacuum.c 924bd1bcee2dfb05376f79845bd3b4cec7b54b2f
 F src/vdbe.c c90edafd941481506f001b17cd8523683fdac853
 F src/vdbe.h edef9c4f0be83e1f1dccd049da37b40e021b63d9
 F src/vdbeInt.h 2cf77c1d151a4a54facd596d52be6d60c1cb26e8
-F src/vdbeapi.c c4692c74b31f4abe638fbcbae3944c52b54e2c36
+F src/vdbeapi.c 3d620e00cb74b6034343009af42d5ff5eb7c19dc
 F src/vdbeaux.c 0216b2c37509a44c3833b297765bee7bdd04fa2f
 F src/vdbeblob.c c3ccb7c8732858c680f442932e66ad06bb036562
 F src/vdbemem.c 0498796b6ffbe45e32960d6a1f5adfb6e419883b
@@ -921,7 +923,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P a192d04f4e3a9e4960a4d96d1d3ee8635bc1034d
-R 0282a54a03134becd2cdef1929ff705c
+P aed4273054cbd150c86b36ea951d17c981633ba0
+R 3ee0056f48f594a086fa203e62ad6366
 U dan
-Z 9cc92283568709d19359195efd9bde16
+Z 9a232200bb7ff725d52774ff9ca627f1
index 754c4bfa3cf881543c8fbc3da21c91e3588d46b8..7adc729880d95051fcb66ae8698198b90ffb42ca 100644 (file)
@@ -1 +1 @@
-aed4273054cbd150c86b36ea951d17c981633ba0
\ No newline at end of file
+32e95164d1192b87b1ab019549fd2394642cd3fe
\ No newline at end of file
index 284e565e13363dac91327f42e31ff7fbe134167c..829d4678b64dd8661d2374d67db0c28419ee9f4c 100644 (file)
@@ -1348,21 +1348,23 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
 
   /* If the old.* record has not yet been loaded into memory, do so now. */
   if( p->pUnpacked==0 ){
-    u32 nRecord;
-    u8 *aRecord;
+    u32 nRec;
+    u8 *aRec;
 
-    rc = sqlite3BtreeDataSize(p->pCsr->pCursor, &nRecord);
+    rc = sqlite3BtreeDataSize(p->pCsr->pCursor, &nRec);
     if( rc!=SQLITE_OK ) goto preupdate_old_out;
-    aRecord = sqlite3DbMallocRaw(db, nRecord);
-    if( !aRecord ) goto preupdate_old_out;
-    rc = sqlite3BtreeData(p->pCsr->pCursor, 0, nRecord, aRecord);
+    aRec = sqlite3DbMallocRaw(db, nRec);
+    if( !aRec ) goto preupdate_old_out;
+    rc = sqlite3BtreeData(p->pCsr->pCursor, 0, nRec, aRec);
+    if( rc==SQLITE_OK ){
+      p->pUnpacked = sqlite3VdbeRecordUnpack(&p->keyinfo, nRec, aRec, 0, 0);
+      if( !p->pUnpacked ) rc = SQLITE_NOMEM;
+    }
     if( rc!=SQLITE_OK ){
-      sqlite3DbFree(db, aRecord);
+      sqlite3DbFree(db, aRec);
       goto preupdate_old_out;
     }
-
-    p->pUnpacked = sqlite3VdbeRecordUnpack(&p->keyinfo, nRecord, aRecord, 0, 0);
-    p->aRecord = aRecord;
+    p->aRecord = aRec;
   }
 
   if( iIdx>=p->pUnpacked->nField ){