Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Fix the preupdate hook so that it works when the "old.*" row has a column with a non-NULL default value that was added by ALTER TABLE ADD COLUMN after the current record was created. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
6a8c687904e92f00c1a5f768947545d2 |
User & Date: | dan 2024-09-18 15:52:05.754 |
Context
2024-09-18
| ||
16:33 | Improvements to the scope of valueFromFunction(). (check-in: a0f39419cb user: drh tags: trunk) | |
15:52 | Fix the preupdate hook so that it works when the "old.*" row has a column with a non-NULL default value that was added by ALTER TABLE ADD COLUMN after the current record was created. (check-in: 6a8c687904 user: dan tags: trunk) | |
15:38 | Fix sqlite3-rsync so that it recognizes drive-letters on the front of pathnames in Windows, and does not misinterpret them as hostnames. (check-in: 54a3bbd578 user: drh tags: trunk) | |
15:02 | Fix the preupdate hook so that it works when the "old.*" row has a column with a non-NULL default value that was added by ALTER TABLE ADD COLUMN after the current record was created. (Closed-Leaf check-in: 00a398cf90 user: dan tags: preupdate-hook-fix) | |
Changes
Changes to ext/session/sessionalter.test.
︙ | ︙ | |||
198 199 200 201 202 203 204 205 206 207 208 209 210 211 | set C1 [$cmd $sql1] execsql $at set C2 [$cmd $sql2] sqlite3changegroup grp grp schema db main grp add $C1 grp add $C2 set T1 [grp output] grp delete db_restore_and_reopen execsql $at | > | 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | set C1 [$cmd $sql1] execsql $at set C2 [$cmd $sql2] sqlite3changegroup grp grp schema db main breakpoint grp add $C1 grp add $C2 set T1 [grp output] grp delete db_restore_and_reopen execsql $at |
︙ | ︙ |
Changes to ext/session/sessionfault3.test.
︙ | ︙ | |||
51 52 53 54 55 56 57 58 59 | G add $::C2 G output set {} {} } -test { catch { G delete } faultsim_test_result {0 {}} {1 SQLITE_NOMEM} } finish_test | > > > > > > > > > > > > > > > > > > > > > > > > | 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 | G add $::C2 G output set {} {} } -test { catch { G delete } faultsim_test_result {0 {}} {1 SQLITE_NOMEM} } #------------------------------------------------------------------------- reset_db do_execsql_test 2.0 { CREATE TABLE t1(a INTEGER PRIMARY KEY, b); INSERT INTO t1 VALUES(1, 'one'); INSERT INTO t1 VALUES(2, 'two'); ALTER TABLE t1 ADD COLUMN c DEFAULT 'abcdefghijklmnopqrstuvwxyz'; } faultsim_save_and_close do_faultsim_test 2 -faults oom-t* -prep { faultsim_restore_and_reopen db eval {SELECT * FROM sqlite_schema} } -body { sqlite3session S db main S attach * execsql { DELETE FROM t1 WHERE a = 1; } } -test { faultsim_test_result {0 {}} {1 SQLITE_NOMEM} catch { S delete } } finish_test |
Changes to ext/session/sqlite3session.c.
︙ | ︙ | |||
44 45 46 47 48 49 50 | struct sqlite3_session { sqlite3 *db; /* Database handle session is attached to */ char *zDb; /* Name of database session is attached to */ int bEnableSize; /* True if changeset_size() enabled */ int bEnable; /* True if currently recording */ int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ | | < | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | struct sqlite3_session { sqlite3 *db; /* Database handle session is attached to */ char *zDb; /* Name of database session is attached to */ int bEnableSize; /* True if changeset_size() enabled */ int bEnable; /* True if currently recording */ int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ int bImplicitPK; /* True to handle tables with implicit PK */ int rc; /* Non-zero if an error has occurred */ void *pFilterCtx; /* First argument to pass to xTableFilter */ int (*xTableFilter)(void *pCtx, const char *zTab); i64 nMalloc; /* Number of bytes of data allocated */ i64 nMaxChangesetSize; sqlite3_value *pZeroBlob; /* Value containing X'' */ sqlite3_session *pNext; /* Next session object on same db. */ SessionTable *pTable; /* List of attached tables */ |
︙ | ︙ | |||
1753 1754 1755 1756 1757 1758 1759 | pTab->nEntry++; /* Figure out how large an allocation is required */ nByte = sizeof(SessionChange); for(i=0; i<(pTab->nCol-pTab->bRowid); i++){ sqlite3_value *p = 0; if( op!=SQLITE_INSERT ){ | > > | < > | | | > | 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 | pTab->nEntry++; /* Figure out how large an allocation is required */ nByte = sizeof(SessionChange); for(i=0; i<(pTab->nCol-pTab->bRowid); i++){ sqlite3_value *p = 0; if( op!=SQLITE_INSERT ){ /* This may fail if the column has a non-NULL default and was added ** using ALTER TABLE ADD COLUMN after this record was created. */ rc = pSession->hook.xOld(pSession->hook.pCtx, i, &p); }else if( pTab->abPK[i] ){ TESTONLY(int trc = ) pSession->hook.xNew(pSession->hook.pCtx, i, &p); assert( trc==SQLITE_OK ); } if( rc==SQLITE_OK ){ /* This may fail if SQLite value p contains a utf-16 string that must ** be converted to utf-8 and an OOM error occurs while doing so. */ rc = sessionSerializeValue(0, p, &nByte); } if( rc!=SQLITE_OK ) goto error_out; } if( pTab->bRowid ){ nByte += 9; /* Size of rowid field - an integer */ } /* Allocate the change object */ |
︙ | ︙ | |||
5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 | pOut->nBuf = 0; if( op==SQLITE_INSERT || (op==SQLITE_DELETE && pGrp->bPatch==0) ){ /* Append the missing default column values to the record. */ sessionAppendBlob(pOut, aRec, nRec, &rc); if( rc==SQLITE_OK && pTab->pDfltStmt==0 ){ rc = sessionPrepareDfltStmt(pGrp->db, pTab, &pTab->pDfltStmt); } for(ii=nCol; rc==SQLITE_OK && ii<pTab->nCol; ii++){ int eType = sqlite3_column_type(pTab->pDfltStmt, ii); sessionAppendByte(pOut, eType, &rc); switch( eType ){ case SQLITE_FLOAT: case SQLITE_INTEGER: { i64 iVal; if( eType==SQLITE_INTEGER ){ iVal = sqlite3_column_int64(pTab->pDfltStmt, ii); }else{ double rVal = sqlite3_column_int64(pTab->pDfltStmt, ii); memcpy(&iVal, &rVal, sizeof(i64)); } if( SQLITE_OK==sessionBufferGrow(pOut, 8, &rc) ){ sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal); } break; } case SQLITE_BLOB: case SQLITE_TEXT: { int n = sqlite3_column_bytes(pTab->pDfltStmt, ii); | > > > > | 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 | pOut->nBuf = 0; if( op==SQLITE_INSERT || (op==SQLITE_DELETE && pGrp->bPatch==0) ){ /* Append the missing default column values to the record. */ sessionAppendBlob(pOut, aRec, nRec, &rc); if( rc==SQLITE_OK && pTab->pDfltStmt==0 ){ rc = sessionPrepareDfltStmt(pGrp->db, pTab, &pTab->pDfltStmt); if( rc==SQLITE_OK && SQLITE_ROW!=sqlite3_step(pTab->pDfltStmt) ){ rc = sqlite3_errcode(pGrp->db); } } for(ii=nCol; rc==SQLITE_OK && ii<pTab->nCol; ii++){ int eType = sqlite3_column_type(pTab->pDfltStmt, ii); sessionAppendByte(pOut, eType, &rc); switch( eType ){ case SQLITE_FLOAT: case SQLITE_INTEGER: { i64 iVal; if( eType==SQLITE_INTEGER ){ iVal = sqlite3_column_int64(pTab->pDfltStmt, ii); }else{ double rVal = sqlite3_column_int64(pTab->pDfltStmt, ii); memcpy(&iVal, &rVal, sizeof(i64)); } if( SQLITE_OK==sessionBufferGrow(pOut, 8, &rc) ){ sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal); pOut->nBuf += 8; } break; } case SQLITE_BLOB: case SQLITE_TEXT: { int n = sqlite3_column_bytes(pTab->pDfltStmt, ii); |
︙ | ︙ |
Changes to src/vdbeInt.h.
︙ | ︙ | |||
539 540 541 542 543 544 545 546 547 548 549 550 551 552 | int iNewReg; /* Register for new.* values */ int iBlobWrite; /* Value returned by preupdate_blobwrite() */ i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ Mem *aNew; /* Array of new.* values */ Table *pTab; /* Schema object being updated */ Index *pPk; /* PK index if pTab is WITHOUT ROWID */ }; /* ** An instance of this object is used to pass an vector of values into ** OP_VFilter, the xFilter method of a virtual table. The vector is the ** set of values on the right-hand side of an IN constraint. ** | > | 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 | int iNewReg; /* Register for new.* values */ int iBlobWrite; /* Value returned by preupdate_blobwrite() */ i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ Mem *aNew; /* Array of new.* values */ Table *pTab; /* Schema object being updated */ Index *pPk; /* PK index if pTab is WITHOUT ROWID */ sqlite3_value **apDflt; /* Array of default values, if required */ }; /* ** An instance of this object is used to pass an vector of values into ** OP_VFilter, the xFilter method of a virtual table. The vector is the ** set of values on the right-hand side of an IN constraint. ** |
︙ | ︙ |
Changes to src/vdbeapi.c.
︙ | ︙ | |||
2218 2219 2220 2221 2222 2223 2224 | p->aRecord = aRec; } pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; if( iIdx==p->pTab->iPKey ){ sqlite3VdbeMemSetInt64(pMem, p->iKey1); }else if( iIdx>=p->pUnpacked->nField ){ | > > > > > > > > > > > > > > > > > > > > | > | 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 | p->aRecord = aRec; } pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; if( iIdx==p->pTab->iPKey ){ sqlite3VdbeMemSetInt64(pMem, p->iKey1); }else if( iIdx>=p->pUnpacked->nField ){ /* This occurs when the table has been extended using ALTER TABLE ** ADD COLUMN. The value to return is the default value of the column. */ Column *pCol = &p->pTab->aCol[iIdx]; if( pCol->iDflt>0 ){ if( p->apDflt==0 ){ int nByte = sizeof(sqlite3_value*)*p->pTab->nCol; p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte); if( p->apDflt==0 ) goto preupdate_old_out; } if( p->apDflt[iIdx]==0 ){ Expr *pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; sqlite3_value *pVal = 0; rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal); if( rc==SQLITE_OK && pVal==0 ){ rc = SQLITE_CORRUPT_BKPT; } p->apDflt[iIdx] = pVal; } *ppValue = p->apDflt[iIdx]; }else{ *ppValue = (sqlite3_value *)columnNullValue(); } }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ if( pMem->flags & (MEM_Int|MEM_IntReal) ){ testcase( pMem->flags & MEM_Int ); testcase( pMem->flags & MEM_IntReal ); sqlite3VdbeMemRealify(pMem); } } |
︙ | ︙ |
Changes to src/vdbeaux.c.
︙ | ︙ | |||
5539 5540 5541 5542 5543 5544 5545 5546 5547 | if( preupdate.aNew ){ int i; for(i=0; i<pCsr->nField; i++){ sqlite3VdbeMemRelease(&preupdate.aNew[i]); } sqlite3DbNNFreeNN(db, preupdate.aNew); } } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ | > > > > > > > | 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 | if( preupdate.aNew ){ int i; for(i=0; i<pCsr->nField; i++){ sqlite3VdbeMemRelease(&preupdate.aNew[i]); } sqlite3DbNNFreeNN(db, preupdate.aNew); } if( preupdate.apDflt ){ int i; for(i=0; i<pTab->nCol; i++){ sqlite3ValueFree(preupdate.apDflt[i]); } sqlite3DbFree(db, preupdate.apDflt); } } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ |
Changes to test/hook.test.
︙ | ︙ | |||
702 703 704 705 706 707 708 | CREATE TABLE t8(a, b); INSERT INTO t8 VALUES('one', 'two'); INSERT INTO t8 VALUES('three', 'four'); ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx'; } } | | > > | 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 | CREATE TABLE t8(a, b); INSERT INTO t8 VALUES('one', 'two'); INSERT INTO t8 VALUES('three', 'four'); ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx'; } } if 1 { # At time of writing, these two are broken. They demonstrate that the # sqlite3_preupdate_old() method does not handle the case where ALTER TABLE # has been used to add a column with a default value other than NULL. # # 2024-09-18: These are now fixed. # do_preupdate_test 7.5.2.1 { DELETE FROM t8 WHERE a = 'one' } { DELETE main t8 1 1 one two xxx } do_preupdate_test 7.5.2.2 { |
︙ | ︙ | |||
1017 1018 1019 1020 1021 1022 1023 1024 1025 | execsql VACUUM set ::res } {} do_catchsql_test 12.6 { INSERT INTO t4 VALUES('def', 3); } {1 {UNIQUE constraint failed: t4.a}} finish_test | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 | execsql VACUUM set ::res } {} do_catchsql_test 12.6 { INSERT INTO t4 VALUES('def', 3); } {1 {UNIQUE constraint failed: t4.a}} #------------------------------------------------------------------------- # Test adding non-NULL default values using ALTER TABLE. # reset_db db preupdate hook preupdate_hook do_execsql_test 13.0 { CREATE TABLE t1(a INTEGER PRIMARY KEY); INSERT INTO t1 VALUES(100), (200), (300), (400); } do_execsql_test 13.1 { ALTER TABLE t1 ADD COLUMN b DEFAULT 1234; ALTER TABLE t1 ADD COLUMN c DEFAULT 'abcdef'; ALTER TABLE t1 ADD COLUMN d DEFAULT NULL; } do_preupdate_test 13.2 { DELETE FROM t1 WHERE a=300 } {DELETE main t1 300 300 0 300 1234 abcdef {}} do_preupdate_test 13.3 { UPDATE t1 SET d='hello world' WHERE a=200 } { UPDATE main t1 200 200 0 200 1234 abcdef {} 200 1234 abcdef {hello world} } do_preupdate_test 13.4 { INSERT INTO t1 DEFAULT VALUES; } { INSERT main t1 401 401 0 401 1234 abcdef {} } finish_test |