Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Add start of fault-injection tests for session module. Fix some bugs related to the same. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | sessions |
Files: | files | file ages | folders |
SHA1: |
32e95164d1192b87b1ab019549fd2394 |
User & Date: | dan 2011-03-21 16:17:42.000 |
Context
2011-03-21
| ||
17:17 | Merge in the sqlite3_db_config() enhancements for enabling and disabling FKs and triggers from trunk. (check-in: 2b3c8b9d9a user: drh tags: sessions) | |
16:17 | Add start of fault-injection tests for session module. Fix some bugs related to the same. (check-in: 32e95164d1 user: dan tags: sessions) | |
11:55 | Clarify handling of NULL values in PK columns in sqlite3session.h. Add tests and fixes for the same. (check-in: aed4273054 user: dan tags: sessions) | |
Changes
Changes to ext/session/session2.test.
︙ | ︙ | |||
24 25 26 27 28 29 30 | catch { db close } catch { db2 close } forcedelete test.db test.db2 sqlite3 db test.db sqlite3 db2 test.db2 } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | > | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | catch { db close } catch { db2 close } forcedelete test.db test.db2 sqlite3 db test.db sqlite3 db2 test.db2 } ########################################################################## # End of proc definitions. Start of tests. ########################################################################## test_reset do_execsql_test 1.0 { CREATE TABLE t1(a PRIMARY KEY, b); INSERT INTO t1 VALUES('i', 'one'); } do_iterator_test 1.1 t1 { DELETE FROM t1 WHERE a = 'i'; INSERT INTO t1 VALUES('ii', 'two'); } { {DELETE t1 {t i t one} {}} {INSERT t1 {} {t ii t two}} } 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 # identical. # |
︙ | ︙ | |||
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | INSERT INTO %T1% VALUES(randomblob(21000), randomblob(0)); INSERT INTO %T1% VALUES(1.5, 1.5); INSERT INTO %T1% VALUES(4.56, -99.999999999999999999999); } 12 { INSERT INTO %T2% VALUES(NULL, NULL); } } test_reset do_common_sql { CREATE TABLE t1(a PRIMARY KEY, b); CREATE TABLE t2(a, b INTEGER PRIMARY KEY); CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b)); } foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] { do_then_apply_sql $sql | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | INSERT INTO %T1% VALUES(randomblob(21000), randomblob(0)); INSERT INTO %T1% VALUES(1.5, 1.5); INSERT INTO %T1% VALUES(4.56, -99.999999999999999999999); } 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 do_common_sql { CREATE TABLE t1(a PRIMARY KEY, b); CREATE TABLE t2(a, b INTEGER PRIMARY KEY); CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b)); } foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] { do_then_apply_sql $sql do_test 2.$tn { compare_db db db2 } {} } # The following block of tests is similar to the last, except that the # session object is recording changes made to an attached database. The # main database contains a table of the same name as the table being # modified within the attached db. # test_reset forcedelete test.db3 sqlite3 db3 test.db3 do_test 3.0 { execsql { ATTACH 'test.db3' AS 'aux'; CREATE TABLE t1(a, b PRIMARY KEY); CREATE TABLE t2(x, y, z); CREATE TABLE t3(a); CREATE TABLE aux.t1(a PRIMARY KEY, b); |
︙ | ︙ | |||
220 221 222 223 224 225 226 | proc xTrace {args} { puts $args } foreach {tn sql} [ string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3} $set_of_tests ] { do_then_apply_sql $sql aux | | | | 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | proc xTrace {args} { puts $args } 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 3.$tn { compare_db db3 db2 } {} } catch {db3 close} #------------------------------------------------------------------------- # The following tests verify that NULL values in primary key columns are # handled correctly by the session module. # test_reset 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); } foreach {tn sql changeset} { 1 { |
︙ | ︙ | |||
268 269 270 271 272 273 274 | 5 { INSERT INTO t2 VALUES(1, 2, NULL) } { } 6 { INSERT INTO t2 VALUES(1, NULL, 3) } { } 7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { } 8 { INSERT INTO t2 VALUES(1, 2, 3) } { {INSERT t2 {} {i 1 i 2 i 3}} } 9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 {i 1 i 2 i 3} {}} } } { | | | 240 241 242 243 244 245 246 247 248 249 250 251 252 | 5 { INSERT INTO t2 VALUES(1, 2, NULL) } { } 6 { INSERT INTO t2 VALUES(1, NULL, 3) } { } 7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { } 8 { INSERT INTO t2 VALUES(1, 2, 3) } { {INSERT t2 {} {i 1 i 2 i 3}} } 9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 {i 1 i 2 i 3} {}} } } { do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset } finish_test |
Added ext/session/session_common.tcl.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 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 "" } |
Added ext/session/sessionfault.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 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 |
Changes to ext/session/sqlite3session.c.
︙ | ︙ | |||
301 302 303 304 305 306 307 308 309 310 311 312 313 314 | sqlite3_value *pVal; if( bNew ){ rc = sqlite3_preupdate_new(db, i, &pVal); }else{ rc = sqlite3_preupdate_old(db, i, &pVal); } eType = sqlite3_value_type(pVal); h = HASH_APPEND(h, eType); switch( eType ){ case SQLITE_INTEGER: case SQLITE_FLOAT: { i64 iVal; | > | 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 | sqlite3_value *pVal; if( bNew ){ rc = sqlite3_preupdate_new(db, i, &pVal); }else{ rc = sqlite3_preupdate_old(db, i, &pVal); } if( rc!=SQLITE_OK ) return rc; eType = sqlite3_value_type(pVal); h = HASH_APPEND(h, eType); switch( eType ){ case SQLITE_INTEGER: case SQLITE_FLOAT: { i64 iVal; |
︙ | ︙ | |||
420 421 422 423 424 425 426 | if( bNew ){ rc = sqlite3_preupdate_new(db, i, &pVal); }else{ rc = sqlite3_preupdate_old(db, i, &pVal); } if( rc!=SQLITE_OK || sqlite3_value_type(pVal)!=eType ) return rc; | > | > > | | | | | | | | | | | | < | < < | | | | | > > | > | | | < | 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 | if( bNew ){ rc = sqlite3_preupdate_new(db, i, &pVal); }else{ rc = sqlite3_preupdate_old(db, i, &pVal); } if( rc!=SQLITE_OK || sqlite3_value_type(pVal)!=eType ) return rc; /* 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; } }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; } } } *pbEqual = 1; return SQLITE_OK; } |
︙ | ︙ | |||
730 731 732 733 734 735 736 | }else if( 1 || pTab->abPK[i] ){ rc = sqlite3_preupdate_new(pSession->db, i, &p); } if( p && rc==SQLITE_OK ){ rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); } } | < < < | < < < > > > > > > > > | 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 | }else if( 1 || pTab->abPK[i] ){ rc = sqlite3_preupdate_new(pSession->db, i, &p); } if( p && rc==SQLITE_OK ){ rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); } } 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; } } /* ** The 'pre-update' hook registered by this module with SQLite databases. */ static void xPreUpdate( void *pCtx, /* Copy of third arg to preupdate_hook() */ |
︙ | ︙ | |||
1341 1342 1343 1344 1345 1346 1347 | } if( rc==SQLITE_OK && nCol!=sqlite3_column_count(pSel) ){ rc = SQLITE_SCHEMA; } nNoop = buf.nBuf; | | > | > < | 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 | } if( rc==SQLITE_OK && nCol!=sqlite3_column_count(pSel) ){ rc = SQLITE_SCHEMA; } nNoop = buf.nBuf; 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){ rc = sessionSelectBind(pSel, nCol, abPK, p->aRecord, p->nRecord); if( rc==SQLITE_OK ){ if( sqlite3_step(pSel)==SQLITE_ROW ){ int iCol; if( p->bInsert ){ sessionAppendByte(&buf, SQLITE_INSERT, &rc); for(iCol=0; iCol<nCol; iCol++){ sessionAppendCol(&buf, pSel, iCol, &rc); } }else{ sessionAppendUpdate(&buf, pSel, p, abPK, &rc); } }else if( !p->bInsert ){ /* A DELETE change */ sessionAppendByte(&buf, SQLITE_DELETE, &rc); sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc); } if( rc==SQLITE_OK ){ rc = sqlite3_reset(pSel); } } } } sqlite3_finalize(pSel); if( buf.nBuf==nNoop ){ buf.nBuf = nRewind; } } } if( rc==SQLITE_OK ){ |
︙ | ︙ | |||
1670 1671 1672 1673 1674 1675 1676 | ** ** This function may not be called on iterators passed to a conflict handler ** callback by changeset_apply(). */ int sqlite3changeset_finalize(sqlite3_changeset_iter *p){ int i; /* Used to iterate through p->apValue[] */ int rc = p->rc; /* Return code */ | > | > | 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 | ** ** This function may not be called on iterators passed to a conflict handler ** callback by changeset_apply(). */ int sqlite3changeset_finalize(sqlite3_changeset_iter *p){ int i; /* Used to iterate through p->apValue[] */ int rc = p->rc; /* Return code */ if( p->apValue ){ for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]); } sqlite3_free(p->apValue); sqlite3_free(p); return rc; } /* ** Invert a changeset object. |
︙ | ︙ | |||
2312 2313 2314 2315 2316 2317 2318 | sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ int rc; /* Return code */ const char *zTab = 0; /* Name of current table */ int nTab = 0; /* Result of strlen(zTab) */ SessionApplyCtx sApply; /* changeset_apply() context object */ memset(&sApply, 0, sizeof(sApply)); | | > | 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 | sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ int rc; /* Return code */ const char *zTab = 0; /* Name of current table */ int nTab = 0; /* Result of strlen(zTab) */ SessionApplyCtx sApply; /* changeset_apply() context object */ memset(&sApply, 0, sizeof(sApply)); 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); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ int nCol; int op; int bReplace = 0; |
︙ | ︙ |
Changes to src/vdbeapi.c.
︙ | ︙ | |||
1344 1345 1346 1347 1348 1349 1350 | if( iIdx>=p->pCsr->nField || iIdx<0 ){ rc = SQLITE_RANGE; goto preupdate_old_out; } /* If the old.* record has not yet been loaded into memory, do so now. */ if( p->pUnpacked==0 ){ | | | | | | | > > > > | < < | | 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 | if( iIdx>=p->pCsr->nField || iIdx<0 ){ rc = SQLITE_RANGE; goto preupdate_old_out; } /* If the old.* record has not yet been loaded into memory, do so now. */ if( p->pUnpacked==0 ){ u32 nRec; u8 *aRec; rc = sqlite3BtreeDataSize(p->pCsr->pCursor, &nRec); if( rc!=SQLITE_OK ) goto preupdate_old_out; 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, aRec); goto preupdate_old_out; } p->aRecord = aRec; } if( iIdx>=p->pUnpacked->nField ){ *ppValue = (sqlite3_value *)columnNullValue(); }else{ *ppValue = &p->pUnpacked->aMem[iIdx]; if( iIdx==p->iPKey ){ |
︙ | ︙ |