Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch sessions-stat1 Excluding Merge-Ins
This is equivalent to a diff from 09aed136 to dc7c48cb
2018-01-18
| ||
16:59 | Fix sessions module handling of sqlite_stat1 rows with (idx IS NULL). (check-in: 0e916416 user: dan tags: trunk) | |
16:56 | Clarify the handling of the sqlite_stat1 table by legacy versions of the sessions module. (Closed-Leaf check-in: dc7c48cb user: dan tags: sessions-stat1) | |
16:53 | Add comments describing the special sqlite_stat1 handling to sqlite3session.h. (check-in: 4431a325 user: dan tags: sessions-stat1) | |
2018-01-17
| ||
21:14 | Alternative implementation for the internal sqlite3Pow10() utility for MSVC, which is more accurate on that platform. (check-in: 469b96be user: drh tags: trunk) | |
20:57 | Fix a problem in the sessions module with logging sqlite_stat1 rows for which (idx IS NULL) is true. (check-in: 25bf734b user: dan tags: sessions-stat1) | |
17:38 | Fix a problem causing the sessions module to occasionally lose track of rows with composite primary keys when there are two rows with the same text value in the leftmost column of the PK. (check-in: 09aed136 user: dan tags: trunk) | |
16:11 | Fix main.mk so that testfixture can be built either from the amalgamation or from individual source files. No changes to code. (check-in: a8aea925 user: dan tags: trunk) | |
Changes to ext/session/sessionstat1.test.
︙ | ︙ | |||
116 117 118 119 120 121 122 123 124 125 | do_execsql_test -db db2 2.4 { SELECT * FROM sqlite_stat1 } { } do_execsql_test -db db2 2.5 { SELECT count(*) FROM t1 } 32 finish_test | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 183 184 185 186 187 188 189 190 191 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 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | do_execsql_test -db db2 2.4 { SELECT * FROM sqlite_stat1 } { } do_execsql_test -db db2 2.5 { SELECT count(*) FROM t1 } 32 #------------------------------------------------------------------------- db2 close forcedelete test.db2 reset_db sqlite3 db2 test.db2 do_test 3.0 { do_common_sql { CREATE TABLE t1(a, b, c); ANALYZE; DELETE FROM sqlite_stat1; } execsql { INSERT INTO t1 VALUES(1, 1, 1); INSERT INTO t1 VALUES(2, 2, 2); INSERT INTO t1 VALUES(3, 3, 3); INSERT INTO t1 VALUES(4, 4, 4); } } {} do_iterator_test 3.1 {} { ANALYZE } { {INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 4}} } db null null db2 null null do_execsql_test 3.2 { SELECT * FROM sqlite_stat1; } {t1 null 4} do_test 3.3 { execsql { DELETE FROM sqlite_stat1 } do_then_apply_sql { ANALYZE } execsql { SELECT * FROM sqlite_stat1 } db2 } {t1 null 4} do_test 3.4 { execsql { INSERT INTO t1 VALUES(5,5,5) } do_then_apply_sql { ANALYZE } execsql { SELECT * FROM sqlite_stat1 } db2 } {t1 null 5} do_test 3.5 { do_then_apply_sql { DROP TABLE t1 } execsql { SELECT * FROM sqlite_stat1 } db2 } {} do_test 3.6.1 { execsql { CREATE TABLE t1(a, b, c); CREATE TABLE t2(x, y, z); INSERT INTO t1 VALUES(1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5); INSERT INTO t2 SELECT * FROM t1; DELETE FROM sqlite_stat1; } sqlite3session S db main S attach sqlite_stat1 execsql { ANALYZE } } {} do_changeset_test 3.6.2 S { {INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 5}} {INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}} } do_changeset_invert_test 3.6.3 S { {DELETE sqlite_stat1 0 XX. {t t2 b {} t 5} {}} {DELETE sqlite_stat1 0 XX. {t t1 b {} t 5} {}} } do_test 3.6.4 { S delete } {} proc sql_changeset_concat {args} { foreach sql $args { sqlite3session S db main S attach sqlite_stat1 execsql $sql set change [S changeset] S delete if {[info vars ret]!=""} { set ret [sqlite3changeset_concat $ret $change] } else { set ret $change } } changeset_to_list $ret } proc do_scc_test {tn args} { uplevel [list \ do_test $tn [concat sql_changeset_concat [lrange $args 0 end-1]] \ [list {*}[ lindex $args end ]] ] } do_execsql_test 3.7.0 { DELETE FROM sqlite_stat1; } do_scc_test 3.7.1 { ANALYZE; } { INSERT INTO t2 VALUES(6,6,6); ANALYZE; } { {INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}} {INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 6}} } #------------------------------------------------------------------------- catch { db2 close } reset_db forcedelete test.db2 sqlite3 db2 test.db2 do_test 4.1.0 { do_common_sql { CREATE TABLE t1(a, b); CREATE INDEX i1 ON t1(a); CREATE INDEX i2 ON t1(b); INSERT INTO t1 VALUES(1,1), (2,2); ANALYZE; } execsql { DELETE FROM sqlite_stat1 } } {} do_test 4.1.1 { execsql { INSERT INTO t1 VALUES(3,3); } set C [changeset_from_sql {ANALYZE}] set ::c [list] proc xConflict {args} { lappend ::c $args return "OMIT" } sqlite3changeset_apply db2 $C xConflict set ::c } [list {*}{ {INSERT sqlite_stat1 CONFLICT {t t1 t i1 t {3 1}} {t t1 t i1 t {2 1}}} {INSERT sqlite_stat1 CONFLICT {t t1 t i2 t {3 1}} {t t1 t i2 t {2 1}}} }] do_execsql_test -db db2 4.1.2 { SELECT * FROM sqlite_stat1 ORDER BY 1,2; } {t1 i1 {2 1} t1 i2 {2 1}} do_test 4.1.3 { proc xConflict {args} { return "REPLACE" } sqlite3changeset_apply db2 $C xConflict execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2 } {t1 i1 {3 1} t1 i2 {3 1}} do_test 4.2.0 { do_common_sql { DROP TABLE t1; CREATE TABLE t3(x,y); INSERT INTO t3 VALUES('a','a'); INSERT INTO t3 VALUES('b','b'); ANALYZE; } execsql { DELETE FROM sqlite_stat1 } } {} do_test 4.2.1 { execsql { INSERT INTO t3 VALUES('c','c'); } set C [changeset_from_sql {ANALYZE}] set ::c [list] proc xConflict {args} { lappend ::c $args return "OMIT" } sqlite3changeset_apply db2 $C xConflict set ::c } [list {*}{ {INSERT sqlite_stat1 CONFLICT {t t3 b {} t 3} {t t3 b {} t 2}} }] db2 null null do_execsql_test -db db2 4.2.2 { SELECT * FROM sqlite_stat1 ORDER BY 1,2; } {t3 null 2} do_test 4.2.3 { proc xConflict {args} { return "REPLACE" } sqlite3changeset_apply db2 $C xConflict execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2 } {t3 null 3} finish_test |
Changes to ext/session/sqlite3session.c.
︙ | ︙ | |||
42 43 44 45 46 47 48 49 50 51 52 53 54 55 | char *zDb; /* Name of database session is attached to */ int bEnable; /* True if currently recording */ int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ int rc; /* Non-zero if an error has occurred */ void *pFilterCtx; /* First argument to pass to xTableFilter */ int (*xTableFilter)(void *pCtx, const char *zTab); sqlite3_session *pNext; /* Next session object on same db. */ SessionTable *pTable; /* List of attached tables */ SessionHook hook; /* APIs to grab new and old data with */ }; /* ** Instances of this structure are used to build strings or binary records. | > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | char *zDb; /* Name of database session is attached to */ int bEnable; /* True if currently recording */ int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ int rc; /* Non-zero if an error has occurred */ void *pFilterCtx; /* First argument to pass to xTableFilter */ int (*xTableFilter)(void *pCtx, const char *zTab); sqlite3_value *pZeroBlob; /* Value containing X'' */ sqlite3_session *pNext; /* Next session object on same db. */ SessionTable *pTable; /* List of attached tables */ SessionHook hook; /* APIs to grab new and old data with */ }; /* ** Instances of this structure are used to build strings or binary records. |
︙ | ︙ | |||
109 110 111 112 113 114 115 116 117 118 119 120 121 122 | ** a subset of the initial values that the modified row contained at the ** start of the session. Or no initial values if the row was inserted. */ struct SessionTable { SessionTable *pNext; char *zName; /* Local name of table */ int nCol; /* Number of columns in table zName */ const char **azCol; /* Column names */ u8 *abPK; /* Array of primary key flags */ int nEntry; /* Total number of entries in hash table */ int nChange; /* Size of apChange[] array */ SessionChange **apChange; /* Hash table buckets */ }; | > | 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | ** a subset of the initial values that the modified row contained at the ** start of the session. Or no initial values if the row was inserted. */ struct SessionTable { SessionTable *pNext; char *zName; /* Local name of table */ int nCol; /* Number of columns in table zName */ int bStat1; /* True if this is sqlite_stat1 */ const char **azCol; /* Column names */ u8 *abPK; /* Array of primary key flags */ int nEntry; /* Total number of entries in hash table */ int nChange; /* Size of apChange[] array */ SessionChange **apChange; /* Hash table buckets */ }; |
︙ | ︙ | |||
492 493 494 495 496 497 498 499 500 501 502 503 504 505 | z = (const u8 *)sqlite3_value_blob(pVal); } n = sqlite3_value_bytes(pVal); if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; h = sessionHashAppendBlob(h, n, z); }else{ assert( eType==SQLITE_NULL ); *pbNullPK = 1; } } } *piHash = (h % pTab->nChange); return SQLITE_OK; | > | 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 | z = (const u8 *)sqlite3_value_blob(pVal); } n = sqlite3_value_bytes(pVal); if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; h = sessionHashAppendBlob(h, n, z); }else{ assert( eType==SQLITE_NULL ); assert( pTab->bStat1==0 || i!=1 ); *pbNullPK = 1; } } } *piHash = (h % pTab->nChange); return SQLITE_OK; |
︙ | ︙ | |||
1043 1044 1045 1046 1047 1048 1049 | int i; for(i=0; i<pTab->nCol; i++){ if( abPK[i] ){ pTab->abPK = abPK; break; } } | > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 | int i; for(i=0; i<pTab->nCol; i++){ if( abPK[i] ){ pTab->abPK = abPK; break; } } if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){ pTab->bStat1 = 1; } } } return (pSession->rc || pTab->abPK==0); } /* ** Versions of the four methods in object SessionHook for use with the ** sqlite_stat1 table. The purpose of this is to substitute a zero-length ** blob each time a NULL value is read from the "idx" column of the ** sqlite_stat1 table. */ typedef struct SessionStat1Ctx SessionStat1Ctx; struct SessionStat1Ctx { SessionHook hook; sqlite3_session *pSession; }; static int sessionStat1Old(void *pCtx, int iCol, sqlite3_value **ppVal){ SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; sqlite3_value *pVal = 0; int rc = p->hook.xOld(p->hook.pCtx, iCol, &pVal); if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){ pVal = p->pSession->pZeroBlob; } *ppVal = pVal; return rc; } static int sessionStat1New(void *pCtx, int iCol, sqlite3_value **ppVal){ SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; sqlite3_value *pVal = 0; int rc = p->hook.xNew(p->hook.pCtx, iCol, &pVal); if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){ pVal = p->pSession->pZeroBlob; } *ppVal = pVal; return rc; } static int sessionStat1Count(void *pCtx){ SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; return p->hook.xCount(p->hook.pCtx); } static int sessionStat1Depth(void *pCtx){ SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx; return p->hook.xDepth(p->hook.pCtx); } /* ** This function is only called from with a pre-update-hook reporting a ** change on table pTab (attached to session pSession). The type of change ** (UPDATE, INSERT, DELETE) is specified by the first argument. ** ** Unless one is already present or an error occurs, an entry is added ** to the changed-rows hash table associated with table pTab. */ static void sessionPreupdateOneChange( int op, /* One of SQLITE_UPDATE, INSERT, DELETE */ sqlite3_session *pSession, /* Session object pTab is attached to */ SessionTable *pTab /* Table that change applies to */ ){ int iHash; int bNull = 0; int rc = SQLITE_OK; SessionStat1Ctx stat1; if( pSession->rc ) return; /* Load table details if required */ if( sessionInitTable(pSession, pTab) ) return; /* Check the number of columns in this xPreUpdate call matches the ** number of columns in the table. */ if( pTab->nCol!=pSession->hook.xCount(pSession->hook.pCtx) ){ pSession->rc = SQLITE_SCHEMA; return; } /* Grow the hash table if required */ if( sessionGrowHash(0, pTab) ){ pSession->rc = SQLITE_NOMEM; return; } if( pTab->bStat1 ){ stat1.hook = pSession->hook; stat1.pSession = pSession; pSession->hook.pCtx = (void*)&stat1; pSession->hook.xNew = sessionStat1New; pSession->hook.xOld = sessionStat1Old; pSession->hook.xCount = sessionStat1Count; pSession->hook.xDepth = sessionStat1Depth; if( pSession->pZeroBlob==0 ){ sqlite3_value *p = sqlite3ValueNew(0); if( p==0 ){ rc = SQLITE_NOMEM; goto error_out; } sqlite3ValueSetStr(p, 0, "", 0, SQLITE_STATIC); pSession->pZeroBlob = p; } } /* Calculate the hash-key for this change. If the primary key of the row ** includes a NULL value, exit early. Such changes are ignored by the ** session module. */ rc = sessionPreupdateHash(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull); if( rc!=SQLITE_OK ) goto error_out; |
︙ | ︙ | |||
1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 | pC->bIndirect = 0; } } } /* If an error has occurred, mark the session object as failed. */ error_out: if( rc!=SQLITE_OK ){ pSession->rc = rc; } } static int sessionFindTable( sqlite3_session *pSession, | > > > | 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 | pC->bIndirect = 0; } } } /* If an error has occurred, mark the session object as failed. */ error_out: if( pTab->bStat1 ){ pSession->hook = stat1.hook; } if( rc!=SQLITE_OK ){ pSession->rc = rc; } } static int sessionFindTable( sqlite3_session *pSession, |
︙ | ︙ | |||
1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 | if( (*pp)==pSession ){ *pp = (*pp)->pNext; if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void*)pHead); break; } } sqlite3_mutex_leave(sqlite3_db_mutex(db)); /* Delete all attached table objects. And the contents of their ** associated hash-tables. */ sessionDeleteTable(pSession->pTable); /* Free the session object itself. */ sqlite3_free(pSession); | > | 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 | if( (*pp)==pSession ){ *pp = (*pp)->pNext; if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void*)pHead); break; } } sqlite3_mutex_leave(sqlite3_db_mutex(db)); sqlite3ValueFree(pSession->pZeroBlob); /* Delete all attached table objects. And the contents of their ** associated hash-tables. */ sessionDeleteTable(pSession->pTable); /* Free the session object itself. */ sqlite3_free(pSession); |
︙ | ︙ | |||
2100 2101 2102 2103 2104 2105 2106 | const char *zTab, /* Table name */ int nCol, /* Number of columns in table */ const char **azCol, /* Names of table columns */ u8 *abPK, /* PRIMARY KEY array */ sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */ ){ int rc = SQLITE_OK; | > > > > > > > > > | | | | | | | | | | | | | | | | | > > > > | | | 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 | const char *zTab, /* Table name */ int nCol, /* Number of columns in table */ const char **azCol, /* Names of table columns */ u8 *abPK, /* PRIMARY KEY array */ sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */ ){ int rc = SQLITE_OK; char *zSql = 0; int nSql = -1; if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){ zSql = sqlite3_mprintf( "SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND " "idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb ); }else{ int i; const char *zSep = ""; SessionBuffer buf = {0, 0, 0}; sessionAppendStr(&buf, "SELECT * FROM ", &rc); sessionAppendIdent(&buf, zDb, &rc); sessionAppendStr(&buf, ".", &rc); sessionAppendIdent(&buf, zTab, &rc); sessionAppendStr(&buf, " WHERE ", &rc); for(i=0; i<nCol; i++){ if( abPK[i] ){ sessionAppendStr(&buf, zSep, &rc); sessionAppendIdent(&buf, azCol[i], &rc); sessionAppendStr(&buf, " IS ?", &rc); sessionAppendInteger(&buf, i+1, &rc); zSep = " AND "; } } zSql = (char*)buf.aBuf; nSql = buf.nBuf; } if( rc==SQLITE_OK ){ rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0); } sqlite3_free(zSql); return rc; } /* ** Bind the PRIMARY KEY values from the change passed in argument pChange ** to the SELECT statement passed as the first argument. The SELECT statement ** is as prepared by function sessionSelectStmt(). |
︙ | ︙ | |||
3290 3291 3292 3293 3294 3295 3296 | sqlite3_stmt *pDelete; /* DELETE statement */ sqlite3_stmt *pUpdate; /* UPDATE statement */ sqlite3_stmt *pInsert; /* INSERT statement */ sqlite3_stmt *pSelect; /* SELECT statement */ int nCol; /* Size of azCol[] and abPK[] arrays */ const char **azCol; /* Array of column names */ u8 *abPK; /* Boolean array - true if column is in PK */ | | | 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 | sqlite3_stmt *pDelete; /* DELETE statement */ sqlite3_stmt *pUpdate; /* UPDATE statement */ sqlite3_stmt *pInsert; /* INSERT statement */ sqlite3_stmt *pSelect; /* SELECT statement */ int nCol; /* Size of azCol[] and abPK[] arrays */ const char **azCol; /* Array of column names */ u8 *abPK; /* Boolean array - true if column is in PK */ int bStat1; /* True if table is sqlite_stat1 */ int bDeferConstraints; /* True to defer constraints */ SessionBuffer constraints; /* Deferred constraints are stored here */ }; /* ** Formulate a statement to DELETE a row from database db. Assuming a table ** structure like this: |
︙ | ︙ | |||
3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 | if( rc==SQLITE_OK ){ rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0); } sqlite3_free(buf.aBuf); return rc; } /* ** Formulate and prepare an SQL statement to query table zTab by primary ** key. Assuming the following table structure: ** ** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); ** | > | 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 | if( rc==SQLITE_OK ){ rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0); } sqlite3_free(buf.aBuf); return rc; } /* ** Formulate and prepare an SQL statement to query table zTab by primary ** key. Assuming the following table structure: ** ** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); ** |
︙ | ︙ | |||
3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 | if( rc==SQLITE_OK ){ rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0); } sqlite3_free(buf.aBuf); return rc; } /* ** A wrapper around sqlite3_bind_value() that detects an extra problem. ** See comments in the body of this function for details. */ static int sessionBindValue( sqlite3_stmt *pStmt, /* Statement to bind value to */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 | if( rc==SQLITE_OK ){ rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0); } sqlite3_free(buf.aBuf); return rc; } static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){ return sqlite3_prepare_v2(db, zSql, -1, pp, 0); } /* ** Prepare statements for applying changes to the sqlite_stat1 table. ** These are similar to those created by sessionSelectRow(), ** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for ** other tables. */ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ int rc = sessionSelectRow(db, "sqlite_stat1", p); if( rc==SQLITE_OK ){ rc = sessionPrepare(db, &p->pInsert, "INSERT INTO main.sqlite_stat1 VALUES(?1, " "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, " "?3)" ); } if( rc==SQLITE_OK ){ rc = sessionPrepare(db, &p->pUpdate, "UPDATE main.sqlite_stat1 SET " "tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, " "idx = CASE WHEN ?5 THEN ?6 ELSE idx END, " "stat = CASE WHEN ?8 THEN ?9 ELSE stat END " "WHERE tbl=?1 AND idx IS " "CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END " "AND (?10 OR ?8=0 OR stat IS ?7)" ); } if( rc==SQLITE_OK ){ rc = sessionPrepare(db, &p->pDelete, "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS " "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END " "AND (?4 OR stat IS ?3)" ); } assert( rc==SQLITE_OK ); return rc; } /* ** A wrapper around sqlite3_bind_value() that detects an extra problem. ** See comments in the body of this function for details. */ static int sessionBindValue( sqlite3_stmt *pStmt, /* Statement to bind value to */ |
︙ | ︙ | |||
3851 3852 3853 3854 3855 3856 3857 | rc = sessionConflictHandler( SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0 ); } }else{ assert( op==SQLITE_INSERT ); | > > > > > > > > > > > > | | | | > > | 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 | rc = sessionConflictHandler( SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0 ); } }else{ assert( op==SQLITE_INSERT ); if( p->bStat1 ){ /* Check if there is a conflicting row. For sqlite_stat1, this needs ** to be done using a SELECT, as there is no PRIMARY KEY in the ** database schema to throw an exception if a duplicate is inserted. */ rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect); if( rc==SQLITE_ROW ){ rc = SQLITE_CONSTRAINT; sqlite3_reset(p->pSelect); } } if( rc==SQLITE_OK ){ rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert); if( rc!=SQLITE_OK ) return rc; sqlite3_step(p->pInsert); rc = sqlite3_reset(p->pInsert); } if( (rc&0xff)==SQLITE_CONSTRAINT ){ rc = sessionConflictHandler( SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace ); } } |
︙ | ︙ | |||
4088 4089 4090 4091 4092 4093 4094 | schemaMismatch = 1; sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_apply(): " "primary key mismatch for table %s", zTab ); } else{ sApply.nCol = nCol; | > > > > > > | | | | | | > > | 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 | schemaMismatch = 1; sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_apply(): " "primary key mismatch for table %s", zTab ); } else{ sApply.nCol = nCol; if( 0==sqlite3_stricmp(zTab, "sqlite_stat1") ){ if( (rc = sessionStat1Sql(db, &sApply) ) ){ break; } sApply.bStat1 = 1; }else{ if((rc = sessionSelectRow(db, zTab, &sApply)) || (rc = sessionUpdateRow(db, zTab, &sApply)) || (rc = sessionDeleteRow(db, zTab, &sApply)) || (rc = sessionInsertRow(db, zTab, &sApply)) ){ break; } sApply.bStat1 = 0; } } nTab = sqlite3Strlen30(zTab); } } /* If there is a schema mismatch on the current table, proceed to the |
︙ | ︙ |
Changes to ext/session/sqlite3session.h.
︙ | ︙ | |||
143 144 145 146 147 148 149 150 151 152 153 154 155 156 | ** no changes will be recorded in either of these scenarios. ** ** Changes are not recorded for individual rows that have NULL values stored ** in one or more of their PRIMARY KEY columns. ** ** SQLITE_OK is returned if the call completes without error. Or, if an error ** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. */ int sqlite3session_attach( sqlite3_session *pSession, /* Session object */ const char *zTab /* Table name */ ); /* | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 183 184 185 | ** no changes will be recorded in either of these scenarios. ** ** Changes are not recorded for individual rows that have NULL values stored ** in one or more of their PRIMARY KEY columns. ** ** SQLITE_OK is returned if the call completes without error. Or, if an error ** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. ** ** <h3>Special sqlite_stat1 Handling</h3> ** ** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to ** some of the rules above. In SQLite, the schema of sqlite_stat1 is: ** <pre> ** CREATE TABLE sqlite_stat1(tbl,idx,stat) ** </pre> ** ** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are ** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes ** are recorded for rows for which (idx IS NULL) is true. However, for such ** rows a zero-length blob (SQL value X'') is stored in the changeset or ** patchset instead of a NULL value. This allows such changesets to be ** manipulated by legacy implementations of sqlite3changeset_invert(), ** concat() and similar. ** ** The sqlite3changeset_apply() function automatically converts the ** zero-length blob back to a NULL value when updating the sqlite_stat1 ** table. However, if the application calls sqlite3changeset_new(), ** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset ** iterator directly (including on a changeset iterator passed to a ** conflict-handler callback) then the X'' value is returned. The application ** must translate X'' to NULL itself if required. ** ** Legacy (older than 3.22.0) versions of the sessions module cannot capture ** changes made to the sqlite_stat1 table. Legacy versions of the ** sqlite3changeset_apply() function silently ignore any modifications to the ** sqlite_stat1 table that are part of a changeset or patchset. */ int sqlite3session_attach( sqlite3_session *pSession, /* Session object */ const char *zTab /* Table name */ ); /* |
︙ | ︙ |
Changes to src/analyze.c.
︙ | ︙ | |||
1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 | jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname); assert( "BBB"[0]==SQLITE_AFF_TEXT ); sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3VdbeJumpHere(v, jZeroRows); } } /* ** Generate code that will cause the most recent index analysis to | > > > | 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 | jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname); assert( "BBB"[0]==SQLITE_AFF_TEXT ); sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); #ifdef SQLITE_ENABLE_PREUPDATE_HOOK sqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE); #endif sqlite3VdbeJumpHere(v, jZeroRows); } } /* ** Generate code that will cause the most recent index analysis to |
︙ | ︙ |