#
# CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
#
+# This OTA includes both insert and delete operations.
+#
proc create_ota4 {filename} {
forcedelete $filename
sqlite3 ota1 $filename
return $filename
}
+# Create a simple OTA database. That expects to write to a table:
+#
+# CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d);
+#
+# This OTA includes update statements.
+#
+proc create_ota5 {filename} {
+ forcedelete $filename
+ sqlite3 ota5 $filename
+ ota5 eval {
+ CREATE TABLE data_t1(a, b, c, d, ota_control);
+ INSERT INTO data_t1 VALUES(1, NULL, NULL, 5, '...x'); -- SET d = 5
+ INSERT INTO data_t1 VALUES(2, NULL, 10, 5, '..xx'); -- SET c=10, d = 5
+ INSERT INTO data_t1 VALUES(3, 11, NULL, NULL, '.x..'); -- SET b=11
+ }
+ ota5 close
+ return $filename
+}
+
# Run the OTA in file $ota on target database $target until completion.
#
proc run_ota {target ota} {
}
}
+#-------------------------------------------------------------------------
+#
+foreach {tn2 cmd} {1 run_ota 2 step_ota} {
+ foreach {tn schema} {
+ 1 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d);
+ }
+ 2 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d);
+ CREATE INDEX i1 ON t1(d);
+ CREATE INDEX i2 ON t1(d, c);
+ CREATE INDEX i3 ON t1(d, c, b);
+ CREATE INDEX i4 ON t1(b);
+ CREATE INDEX i5 ON t1(c);
+ CREATE INDEX i6 ON t1(c, b);
+ }
+ } {
+ reset_db
+ execsql $schema
+ execsql {
+ INSERT INTO t1 VALUES(1, 2, 3, 4);
+ INSERT INTO t1 VALUES(2, 5, 6, 7);
+ INSERT INTO t1 VALUES(3, 8, 9, 10);
+ }
+
+ do_test 5.$tn2.$tn.1 {
+ create_ota5 ota.db
+ $cmd test.db ota.db
+ } {SQLITE_DONE}
+
+ do_execsql_test 5.$tn2.$tn.2 {
+ SELECT * FROM t1 ORDER BY a ASC;
+ } {
+ 1 2 3 5
+ 2 5 10 5
+ 3 11 9 10
+ }
+
+ do_execsql_test 5.$tn2.$tn.3 { PRAGMA integrity_check } ok
+ }
+}
finish_test
sqlite3_stmt *pSelect; /* Source data */
sqlite3_stmt *pInsert; /* Statement for INSERT operations */
sqlite3_stmt *pDelete; /* Statement for DELETE ops */
+
+ /* Last UPDATE used (for PK b-tree updates only), or NULL. */
+ char *zMask; /* Copy of update mask used with pUpdate */
+ sqlite3_stmt *pUpdate; /* Last update statement (or NULL) */
};
/*
pIter->azTblCol = 0;
pIter->abTblPk = 0;
pIter->nTblCol = 0;
+ sqlite3_free(pIter->zMask);
+ pIter->zMask = 0;
+}
+
+/*
+** Finalize all statements and free all allocations that are specific to
+** the current object (table/index pair).
+*/
+static void otaObjIterClearStatements(OtaObjIter *pIter){
+ sqlite3_finalize(pIter->pSelect);
+ sqlite3_finalize(pIter->pInsert);
+ sqlite3_finalize(pIter->pDelete);
+ sqlite3_finalize(pIter->pUpdate);
+ pIter->pSelect = 0;
+ pIter->pInsert = 0;
+ pIter->pDelete = 0;
+ pIter->pUpdate = 0;
+ pIter->nCol = 0;
}
/*
** as the only argument.
*/
static void otaObjIterFinalize(OtaObjIter *pIter){
+ otaObjIterClearStatements(pIter);
sqlite3_finalize(pIter->pTblIter);
sqlite3_finalize(pIter->pIdxIter);
- sqlite3_finalize(pIter->pSelect);
- sqlite3_finalize(pIter->pInsert);
- sqlite3_finalize(pIter->pDelete);
otaObjIterFreeCols(pIter);
memset(pIter, 0, sizeof(OtaObjIter));
}
if( rc==SQLITE_OK ){
/* Free any SQLite statements used while processing the previous object */
- sqlite3_finalize(pIter->pSelect);
- sqlite3_finalize(pIter->pInsert);
- sqlite3_finalize(pIter->pDelete);
- pIter->pSelect = 0;
- pIter->pInsert = 0;
- pIter->pDelete = 0;
- pIter->nCol = 0;
+ otaObjIterClearStatements(pIter);
if( pIter->bCleanup ){
otaObjIterFreeCols(pIter);
static char *otaObjIterGetOldlist(
sqlite3ota *p,
- OtaObjIter *pIter
+ OtaObjIter *pIter,
+ const char *zObj
){
char *zList = 0;
if( p->rc==SQLITE_OK ){
- const char *zSep = "";
+ const char *zS = "";
int i;
for(i=0; i<pIter->nTblCol; i++){
- zList = sqlite3_mprintf("%z%sold.%s", zList, zSep, pIter->azTblCol[i]);
- zSep = ", ";
+ zList = sqlite3_mprintf("%z%s%s.%s", zList, zS, zObj, pIter->azTblCol[i]);
+ zS = ", ";
if( zList==0 ){
p->rc = SQLITE_NOMEM;
break;
int i;
for(i=0; i<pIter->nTblCol; i++){
if( pIter->abTblPk[i] ){
- zList = sqlite3_mprintf("%z%s%s=?", zList, zSep, pIter->azTblCol[i]);
+ const char *zCol = pIter->azTblCol[i];
+ zList = sqlite3_mprintf("%z%s%s=?%d", zList, zSep, zCol, i+1);
zSep = " AND ";
if( zList==0 ){
p->rc = SQLITE_NOMEM;
return zList;
}
+/*
+** The SELECT statement iterating through the keys for the current object
+** (p->objiter.pSelect) currently points to a valid row. However, there
+** is something wrong with the ota_control value in the ota_control value
+** stored in the (p->nCol+1)'th column. Set the error code and error message
+** of the OTA handle to something reflecting this.
+*/
+static void otaBadControlError(sqlite3ota *p){
+ p->rc = SQLITE_ERROR;
+ p->zErrmsg = sqlite3_mprintf("Invalid ota_control value");
+}
+
+static char *otaObjIterGetSetlist(
+ sqlite3ota *p,
+ OtaObjIter *pIter,
+ const char *zMask
+){
+ char *zList = 0;
+ if( p->rc==SQLITE_OK ){
+ int i;
+
+ if( strlen(zMask)!=pIter->nTblCol ){
+ otaBadControlError(p);
+ }else{
+ const char *zSep = "";
+ for(i=0; i<pIter->nTblCol; i++){
+ if( zMask[i]=='x' ){
+ zList = sqlite3_mprintf("%z%s%s=?%d",
+ zList, zSep, pIter->azTblCol[i], i+1
+ );
+ zSep = ", ";
+ }
+ }
+ }
+ }
+ return zList;
+}
+
static char *otaObjIterGetBindlist(sqlite3ota *p, int nBind){
char *zRet = 0;
if( p->rc==SQLITE_OK ){
p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz,
sqlite3_mprintf(
"SELECT %s, ota_control FROM ota.'data_%q' "
+ "WHERE typeof(ota_control)='integer' AND ota_control!=1 "
"UNION ALL "
"SELECT %s, ota_control FROM ota.'ota_tmp_%q' "
"ORDER BY %s%s",
}else{
char *zBindings = otaObjIterGetBindlist(p, pIter->nTblCol);
char *zWhere = otaObjIterGetWhere(p, pIter);
- char *zOldlist = otaObjIterGetOldlist(p, pIter);
+ char *zOldlist = otaObjIterGetOldlist(p, pIter, "old");
+ char *zNewlist = otaObjIterGetOldlist(p, pIter, "new");
zCollist = otaObjIterGetCollist(p, pIter, pIter->nTblCol, 0);
pIter->nCol = pIter->nTblCol;
otaMPrintfExec(p,
"CREATE TABLE IF NOT EXISTS ota.'ota_tmp_%q' AS "
"SELECT * FROM ota.'data_%q' WHERE 0;"
+
"CREATE TEMP TRIGGER ota_delete_%q BEFORE DELETE ON main.%Q "
"BEGIN "
" INSERT INTO 'ota_tmp_%q'(ota_control, %s) VALUES(2, %s);"
"END;"
- , pIter->zTbl, pIter->zTbl, pIter->zTbl, pIter->zTbl, pIter->zTbl,
- zCollist, zOldlist
+
+ "CREATE TEMP TRIGGER ota_update1_%q BEFORE UPDATE ON main.%Q "
+ "BEGIN "
+ " INSERT INTO 'ota_tmp_%q'(ota_control, %s) VALUES(2, %s);"
+ "END;"
+
+ "CREATE TEMP TRIGGER ota_update2_%q AFTER UPDATE ON main.%Q "
+ "BEGIN "
+ " INSERT INTO 'ota_tmp_%q'(ota_control, %s) VALUES(3, %s);"
+ "END;"
+
+ , pIter->zTbl, pIter->zTbl,
+ pIter->zTbl, pIter->zTbl, pIter->zTbl, zCollist, zOldlist,
+ pIter->zTbl, pIter->zTbl, pIter->zTbl, zCollist, zOldlist,
+ pIter->zTbl, pIter->zTbl, pIter->zTbl, zCollist, zNewlist
);
}
+ /* Allocate space required for the zMask field. */
+ if( p->rc==SQLITE_OK ){
+ int nMask = pIter->nTblCol+1;
+ pIter->zMask = (char*)sqlite3_malloc(nMask);
+ if( pIter->zMask==0 ){
+ p->rc = SQLITE_NOMEM;
+ }else{
+ memset(pIter->zMask, 0, nMask);
+ }
+ }
+
sqlite3_free(zWhere);
sqlite3_free(zOldlist);
+ sqlite3_free(zNewlist);
sqlite3_free(zBindings);
}
sqlite3_free(zCollist);
#define OTA_INSERT 1
#define OTA_DELETE 2
#define OTA_IDX_DELETE 3
-#define OTA_UPDATE 4
+#define OTA_IDX_INSERT 4
+#define OTA_UPDATE 5
-/*
-** The SELECT statement iterating through the keys for the current object
-** (p->objiter.pSelect) currently points to a valid row. However, there
-** is something wrong with the ota_control value in the ota_control value
-** stored in the (p->nCol+1)'th column. Set the error code and error message
-** of the OTA handle to something reflecting this.
-*/
-static void otaBadControlError(sqlite3ota *p){
- p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("Invalid ota_control value");
+static int otaGetUpdateStmt(
+ sqlite3ota *p,
+ OtaObjIter *pIter,
+ const char *zMask,
+ sqlite3_stmt **ppStmt
+){
+ if( pIter->pUpdate && strcmp(zMask, pIter->zMask)==0 ){
+ *ppStmt = pIter->pUpdate;
+ }else{
+ char *zWhere = otaObjIterGetWhere(p, pIter);
+ char *zSet = otaObjIterGetSetlist(p, pIter, zMask);
+ char *zUpdate = 0;
+ sqlite3_finalize(pIter->pUpdate);
+ pIter->pUpdate = 0;
+ if( p->rc==SQLITE_OK ){
+ zUpdate = sqlite3_mprintf("UPDATE %Q SET %s WHERE %s",
+ pIter->zTbl, zSet, zWhere
+ );
+ p->rc = prepareFreeAndCollectError(
+ p->db, &pIter->pUpdate, &p->zErrmsg, zUpdate
+ );
+ *ppStmt = pIter->pUpdate;
+ }
+ if( p->rc==SQLITE_OK ){
+ memcpy(pIter->zMask, zMask, pIter->nTblCol);
+ }
+ sqlite3_free(zWhere);
+ sqlite3_free(zSet);
+ }
+ return p->rc;
}
/*
res = OTA_DELETE;
}else if( iVal==2 ){
res = OTA_IDX_DELETE;
+ }else if( iVal==3 ){
+ res = OTA_IDX_INSERT;
}
break;
}
if( pIter->zIdx==0 && eType==OTA_IDX_DELETE ){
otaBadControlError(p);
}
- else if( eType==OTA_INSERT || eType==OTA_IDX_DELETE ){
+ else if(
+ eType==OTA_INSERT
+ || eType==OTA_DELETE
+ || eType==OTA_IDX_DELETE
+ || eType==OTA_IDX_INSERT
+ ){
sqlite3_stmt *pWriter;
+
assert( eType!=OTA_UPDATE );
+ assert( eType!=OTA_DELETE || pIter->zIdx==0 );
+
+ if( eType==OTA_IDX_DELETE || eType==OTA_DELETE ){
+ pWriter = pIter->pDelete;
+ }else{
+ pWriter = pIter->pInsert;
+ }
- pWriter = (eType==OTA_INSERT)?pIter->pInsert:pIter->pDelete;
for(i=0; i<pIter->nCol; i++){
+ if( eType==SQLITE_DELETE && pIter->zIdx==0 && pIter->abTblPk[i]==0 ){
+ continue;
+ }
sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i);
sqlite3_bind_value(pWriter, i+1, pVal);
}
sqlite3_step(pWriter);
p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
- }
- else if( eType==OTA_DELETE && pIter->zIdx==0 ){
- int iVar = 1;
- assert( pIter->zIdx==0 );
- assert( pIter->nCol==pIter->nTblCol );
- for(i=0; i<pIter->nCol; i++){
- if( pIter->abTblPk[i] ){
+ }else if( eType==OTA_UPDATE ){
+ sqlite3_stmt *pUpdate = 0;
+ otaGetUpdateStmt(p, pIter, zMask, &pUpdate);
+ if( pUpdate ){
+ for(i=0; i<pIter->nCol; i++){
sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i);
- sqlite3_bind_value(pIter->pDelete, iVar++, pVal);
+ sqlite3_bind_value(pUpdate, i+1, pVal);
}
+ sqlite3_step(pUpdate);
+ p->rc = resetAndCollectError(pUpdate, &p->zErrmsg);
}
- sqlite3_step(pIter->pDelete);
- p->rc = resetAndCollectError(pIter->pDelete, &p->zErrmsg);
- }else if( eType==OTA_UPDATE ){
- p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("not yet");
}else{
/* no-op */
assert( eType==OTA_DELETE && pIter->zIdx );
/* Clean up the ota_tmp_xxx table for the previous table. It
** cannot be dropped as there are currently active SQL statements.
** But the contents can be deleted. */
- otaMPrintfExec(p, "DELETE FROM ota.'ota_tmp_%q'", pIter->zTbl);
+ // otaMPrintfExec(p, "DELETE FROM ota.'ota_tmp_%q'", pIter->zTbl);
}else{
otaObjIterPrepareAll(p, pIter, 0);