return $filename
}
-# Create an empty target database suitable for the OTA created by
-# [create_ota1].
+# Create a simple OTA database. That expects to write to a table:
#
# CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
#
-proc create_target1 {filename} {
+proc create_ota4 {filename} {
forcedelete $filename
- sqlite3 target1 $filename
- target1 eval { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c) }
- target1 close
+ sqlite3 ota1 $filename
+ ota1 eval {
+ CREATE TABLE data_t1(a, b, c, ota_control);
+ INSERT INTO data_t1 VALUES(1, 2, 3, 0);
+ INSERT INTO data_t1 VALUES(2, NULL, 5, 1);
+ INSERT INTO data_t1 VALUES(3, 8, 9, 0);
+ INSERT INTO data_t1 VALUES(4, NULL, 11, 1);
+ }
+ ota1 close
return $filename
}
do_test 3.$tn.4 { dbcksum db main } $cksum
}
+#-------------------------------------------------------------------------
+#
+foreach {tn2 cmd} {1 run_ota 2 step_ota} {
+ foreach {tn schema} {
+ 1 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+ }
+ 2 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+ CREATE INDEX i1 ON t1(b);
+ }
+ 3 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+ CREATE INDEX i1 ON t1(b);
+ CREATE INDEX i2 ON t1(c, b);
+ CREATE INDEX i3 ON t1(c, b, c);
+ }
+ } {
+ reset_db
+ execsql $schema
+ execsql {
+ INSERT INTO t1 VALUES(2, 'hello', 'world');
+ INSERT INTO t1 VALUES(4, 'hello', 'planet');
+ INSERT INTO t1 VALUES(6, 'hello', 'xyz');
+ }
+
+ do_test 4.$tn2.$tn.1 {
+ create_ota4 ota.db
+ $cmd test.db ota.db
+ } {SQLITE_DONE}
+
+ do_execsql_test 4.$tn2.$tn.2 {
+ SELECT * FROM t1 ORDER BY a ASC;
+ } {
+ 1 2 3
+ 3 8 9
+ 6 hello xyz
+ }
+
+ do_execsql_test 4.$tn2.$tn.3 { PRAGMA integrity_check } ok
+ }
+}
+
finish_test
int nCol; /* Number of columns in current object */
sqlite3_stmt *pSelect; /* Source data */
sqlite3_stmt *pInsert; /* Statement for INSERT operations */
+ sqlite3_stmt *pDelete; /* Statement for DELETE ops */
};
/*
sqlite3_finalize(pIter->pIdxIter);
sqlite3_finalize(pIter->pSelect);
sqlite3_finalize(pIter->pInsert);
+ sqlite3_finalize(pIter->pDelete);
otaObjIterFreeCols(pIter);
memset(pIter, 0, sizeof(OtaObjIter));
}
/* 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;
if( pIter->bCleanup ){
return zRet;
}
+/*
+** Argument zFmt is a sqlite3_mprintf() style format string. The trailing
+** arguments are the usual subsitution values. This function performs
+** the printf() style substitutions and executes the result as an SQL
+** statement on the OTA handles database.
+**
+** If an error occurs, an error code and error message is stored in the
+** OTA handle. If an error has already occurred when this function is
+** called, it is a no-op.
+*/
+static int otaMPrintfExec(sqlite3ota *p, const char *zFmt, ...){
+ va_list ap;
+ va_start(ap, zFmt);
+ if( p->rc==SQLITE_OK ){
+ char *zSql = sqlite3_vmprintf(zFmt, ap);
+ if( zSql==0 ){
+ p->rc = SQLITE_NOMEM;
+ }else{
+ p->rc = sqlite3_exec(p->db, zSql, 0, 0, &p->zErrmsg);
+ sqlite3_free(zSql);
+ }
+ }
+ va_end(ap);
+ return p->rc;
+}
+
/*
** If they are not already populated, populate the pIter->azTblCol[],
** pIter->abTblPk[] and pIter->nTblCol variables according to the table
return zList;
}
+static char *otaObjIterGetOldlist(
+ sqlite3ota *p,
+ OtaObjIter *pIter
+){
+ char *zList = 0;
+ if( p->rc==SQLITE_OK ){
+ const char *zSep = "";
+ int i;
+ for(i=0; i<pIter->nTblCol; i++){
+ zList = sqlite3_mprintf("%z%sold.%s", zList, zSep, pIter->azTblCol[i]);
+ zSep = ", ";
+ if( zList==0 ){
+ p->rc = SQLITE_NOMEM;
+ break;
+ }
+ }
+ }
+ return zList;
+}
+
+static char *otaObjIterGetWhere(
+ sqlite3ota *p,
+ OtaObjIter *pIter
+){
+ char *zList = 0;
+ if( p->rc==SQLITE_OK ){
+ const char *zSep = "";
+ int i;
+ for(i=0; i<pIter->nTblCol; i++){
+ if( pIter->abTblPk[i] ){
+ zList = sqlite3_mprintf("%z%s%s=?", zList, zSep, pIter->azTblCol[i]);
+ zSep = " AND ";
+ if( zList==0 ){
+ p->rc = SQLITE_NOMEM;
+ break;
+ }
+ }
+ }
+ }
+ return zList;
+}
+
static char *otaObjIterGetBindlist(sqlite3ota *p, int nBind){
char *zRet = 0;
if( p->rc==SQLITE_OK ){
if( zIdx ){
int *aiCol; /* Column map */
- /* Create the index writer */
+ /* Create the index writers */
if( p->rc==SQLITE_OK ){
p->rc = sqlite3_index_writer(
p->db, 0, zIdx, &pIter->pInsert, &aiCol, &pIter->nCol
);
}
+ if( p->rc==SQLITE_OK ){
+ p->rc = sqlite3_index_writer(
+ p->db, 1, zIdx, &pIter->pDelete, &aiCol, &pIter->nCol
+ );
+ }
/* Create the SELECT statement to read keys in sorted order */
zCollist = otaObjIterGetCollist(p, pIter, pIter->nCol, aiCol);
if( p->rc==SQLITE_OK ){
p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz,
sqlite3_mprintf(
- "SELECT %s FROM ota.'data_%q' ORDER BY %s%s",
- zCollist, pIter->zTbl, zCollist, zLimit
+ "SELECT %s, ota_control FROM ota.'data_%q' "
+ "UNION ALL "
+ "SELECT %s, ota_control FROM ota.'ota_tmp_%q' "
+ "ORDER BY %s%s",
+ zCollist, pIter->zTbl,
+ zCollist, pIter->zTbl,
+ zCollist, zLimit
)
);
}
}else{
char *zBindings = otaObjIterGetBindlist(p, pIter->nTblCol);
+ char *zWhere = otaObjIterGetWhere(p, pIter);
+ char *zOldlist = otaObjIterGetOldlist(p, pIter);
zCollist = otaObjIterGetCollist(p, pIter, pIter->nTblCol, 0);
pIter->nCol = pIter->nTblCol;
if( p->rc==SQLITE_OK ){
p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz,
sqlite3_mprintf(
- "SELECT %s FROM ota.'data_%q'%s",
+ "SELECT %s, ota_control FROM ota.'data_%q'%s",
zCollist, pIter->zTbl, zLimit)
);
}
)
);
}
+
+ /* Create the DELETE statement to write to the target PK b-tree */
+ if( p->rc==SQLITE_OK ){
+ p->rc = prepareFreeAndCollectError(p->db, &pIter->pDelete, pz,
+ sqlite3_mprintf(
+ "DELETE FROM main.%Q WHERE %s", pIter->zTbl, zWhere
+ )
+ );
+ }
+
+ if( p->rc==SQLITE_OK ){
+ 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
+ );
+ }
+
+ sqlite3_free(zWhere);
+ sqlite3_free(zOldlist);
sqlite3_free(zBindings);
}
sqlite3_free(zCollist);
return p->rc;
}
+#define OTA_INSERT 1
+#define OTA_DELETE 2
+#define OTA_IDX_DELETE 3
+#define OTA_UPDATE 4
+
+/*
+** 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");
+}
+
+/*
+** The SELECT statement iterating through the keys for the current object
+** (p->objiter.pSelect) currently points to a valid row. This function
+** determines the type of operation requested by this row and returns
+** one of the following values to indicate the result:
+**
+** * OTA_INSERT
+** * OTA_DELETE
+** * OTA_IDX_DELETE
+** * OTA_UPDATE
+**
+** If OTA_UPDATE is returned, then output variable *pzMask is set to
+** point to the text value indicating the columns to update.
+**
+** If the ota_control field contains an invalid value, an error code and
+** message are left in the OTA handle and zero returned.
+*/
+static int otaStepType(sqlite3ota *p, const char **pzMask){
+ int iCol = p->objiter.nCol; /* Index of ota_control column */
+ int res = 0; /* Return value */
+
+ switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){
+ case SQLITE_INTEGER: {
+ int iVal = sqlite3_column_int(p->objiter.pSelect, iCol);
+ if( iVal==0 ){
+ res = OTA_INSERT;
+ }else if( iVal==1 ){
+ res = OTA_DELETE;
+ }else if( iVal==2 ){
+ res = OTA_IDX_DELETE;
+ }
+ break;
+ }
+
+ case SQLITE_TEXT:
+ *pzMask = (const char*)sqlite3_column_text(p->objiter.pSelect, iCol);
+ res = OTA_UPDATE;
+ break;
+
+ default:
+ break;
+ }
+
+ if( res==0 ){
+ otaBadControlError(p);
+ }
+ return res;
+}
+
/*
** This function does the work for an sqlite3ota_step() call.
**
*/
static int otaStep(sqlite3ota *p){
OtaObjIter *pIter = &p->objiter;
+ const char *zMask = 0;
int i;
+ int eType = otaStepType(p, &zMask);
- for(i=0; i<pIter->nCol; i++){
- sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i);
- sqlite3_bind_value(pIter->pInsert, i+1, pVal);
+ if( eType ){
+ assert( eType!=OTA_UPDATE || pIter->zIdx==0 );
+
+ if( pIter->zIdx==0 && eType==OTA_IDX_DELETE ){
+ otaBadControlError(p);
+ }
+ else if( eType==OTA_INSERT || eType==OTA_IDX_DELETE ){
+ sqlite3_stmt *pWriter;
+ assert( eType!=OTA_UPDATE );
+
+ pWriter = (eType==OTA_INSERT)?pIter->pInsert:pIter->pDelete;
+ for(i=0; i<pIter->nCol; i++){
+ 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] ){
+ sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i);
+ sqlite3_bind_value(pIter->pDelete, iVar++, pVal);
+ }
+ }
+ 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 );
+ }
}
- sqlite3_step(pIter->pInsert);
- p->rc = resetAndCollectError(pIter->pInsert, &p->zErrmsg);
return p->rc;
}
while( p && p->rc==SQLITE_OK && pIter->zTbl ){
if( pIter->bCleanup ){
- /* this is where cleanup of the ota_xxx table will happen... */
+ /* 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);
}else{
otaObjIterPrepareAll(p, pIter, 0);
return p->rc;
}
-/*
-** Argument zFmt is a sqlite3_mprintf() style format string. The trailing
-** arguments are the usual subsitution values. This function performs
-** the printf() style substitutions and executes the result as an SQL
-** statement on the OTA handles database.
-**
-** If an error occurs, an error code and error message is stored in the
-** OTA handle. If an error has already occurred when this function is
-** called, it is a no-op.
-*/
-static int otaMPrintfExec(sqlite3ota *p, const char *zFmt, ...){
- va_list ap;
- va_start(ap, zFmt);
- if( p->rc==SQLITE_OK ){
- char *zSql = sqlite3_vmprintf(zFmt, ap);
- if( zSql==0 ){
- p->rc = SQLITE_NOMEM;
- }else{
- p->rc = sqlite3_exec(p->db, zSql, 0, 0, &p->zErrmsg);
- sqlite3_free(zSql);
- }
- }
- va_end(ap);
- return p->rc;
-}
-
static void otaSaveTransactionState(sqlite3ota *p){
otaMPrintfExec(p,
"INSERT OR REPLACE INTO ota.ota_state(rowid, tbl, idx, row, progress)"
-C Switch\sback\sto\susing\sa\ssingle\sdatabase\sconnection\sin\ssqlite3ota.c.
-D 2014-09-05T19:52:42.359
+C Add\ssupport\sfor\sdelete\soperations\sto\sthe\sota\sextension.
+D 2014-09-06T20:19:38.006
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in cf57f673d77606ab0f2d9627ca52a9ba1464146a
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e
F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212
F ext/ota/ota.c d37097e92a005d3915883adefbb93019ea6f8841
-F ext/ota/ota1.test 0bbdffa5cb4c4bc26be5dae55c834830c7e8e5e3
+F ext/ota/ota1.test 47317179125b5e65289a9f59753c9f895186e6d5
F ext/ota/ota2.test 13f76922446c62ed96192e938b8e625ebf0142fa
-F ext/ota/sqlite3ota.c 3ddf5f8122f9ab3270541f61bde5d95ef7b631d5
+F ext/ota/sqlite3ota.c ceb0f77dc6a958d299f532319f6477e5599dc59d
F ext/ota/sqlite3ota.h 545f0008b5f02f2595899cb9841caddada5c17c0
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
F ext/rtree/rtree.c 57bec53e1a677ab74217fe1f20a58c3a47261d6b
F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac
F src/ctime.c 0231df905e2c4abba4483ee18ffc05adc321df2a
F src/date.c 593c744b2623971e45affd0bde347631bdfa4625
-F src/delete.c 5adcd322c6b08fc25d215d780ca62cebce66304d
+F src/delete.c 3c2a375c0329247d01222170ae19ad8a52ecbf9a
F src/expr.c e1691ab0fe6be7247ef073b0038fb8ecd9944fad
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
F src/fkey.c 8d81a780ad78d16ec9082585758a8f1d6bf02ca3
F src/vdbeInt.h cdc8e421f85beb1ac9b4669ec5beadab6faa15e0
F src/vdbeapi.c 09677a53dd8c71bcd670b0bd073bb9aefa02b441
F src/vdbeaux.c cef5d34a64ae3a65b56d96d3fd663246ec8e1c36
-F src/vdbeblob.c 0bc9d22578d87ad9ff1c16e20a36863326f34fd7
+F src/vdbeblob.c b1b8b2cd86617db009f027f116b335f86e95b617
F src/vdbemem.c 921d5468a68ac06f369810992e84ca22cc730a62
F src/vdbesort.c 7c45bfcd823f30d172bbbc1b9f51ef4402fbfe8d
F src/vdbetrace.c 6f52bc0c51e144b7efdcfb2a8f771167a8816767
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 98387f05697526c7740e91d8a846a31f77639406
-R cd4982d2f447b7a01026d09e8721c98f
+P 3c2f4a078132992e33cc675173c84f8385af9cb5
+R eeedfe1c92df64758f3e97de2d55d43b
U dan
-Z 303c253ac0315bace60d825e70dc4fdd
+Z 3317be7135040205f21e9375db1283ea