--- /dev/null
+# 2014 August 30
+#
+# 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.
+#
+#***********************************************************************
+#
+
+set testdir [file join [file dirname $argv0] .. .. test]
+source $testdir/tester.tcl
+set ::testprefix ota1
+
+
+# Create a simple OTA database. That expects to write to a table:
+#
+# CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+#
+proc create_ota1 {filename} {
+ forcedelete $filename
+ 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, 'two', 'three', 0);
+ INSERT INTO data_t1 VALUES(3, NULL, 8.2, 0);
+ }
+ ota1 close
+ return $filename
+}
+
+# Create an empty target database suitable for the OTA created by
+# [create_ota1].
+#
+# CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+#
+proc create_target1 {filename} {
+ forcedelete $filename
+ sqlite3 target1 $filename
+ target1 eval { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c) }
+ target1 close
+ return $filename
+}
+
+# Run the OTA in file $ota on target database $target until completion.
+#
+proc run_ota {target ota} {
+ sqlite3ota ota $target $ota
+ while { [ota step]=="SQLITE_OK" } {}
+ ota close
+}
+
+proc step_ota {target ota} {
+ while 1 {
+ sqlite3ota ota $target $ota
+ set rc [ota step]
+ ota close
+ if {$rc != "SQLITE_OK"} break
+ }
+ set rc
+}
+
+foreach {tn2 cmd} {1 step_ota 2 run_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 PRIMARY KEY, b, c) WITHOUT ROWID;
+ }
+ 4 {
+ CREATE TABLE t1(a PRIMARY KEY, b, c) WITHOUT ROWID;
+ CREATE INDEX i1 ON t1(b);
+ }
+ 5 {
+ CREATE TABLE t1(a, b, c, PRIMARY KEY(a, c)) WITHOUT ROWID;
+ CREATE INDEX i1 ON t1(b);
+ }
+ 6 {
+ CREATE TABLE t1(a, b, c, PRIMARY KEY(c)) WITHOUT ROWID;
+ CREATE INDEX i1 ON t1(b, a);
+ }
+ 7 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+ CREATE INDEX i1 ON t1(b, c);
+ CREATE INDEX i2 ON t1(c, b);
+ CREATE INDEX i3 ON t1(a, b, c, a, b, c);
+ }
+ } {
+ reset_db
+ execsql $schema
+
+ do_test 1.$tn2.$tn.1 {
+ create_ota1 ota.db
+ $cmd test.db ota.db
+ } {SQLITE_DONE}
+
+ do_execsql_test 1.$tn2.$tn.2 {
+ SELECT * FROM t1 ORDER BY a ASC;
+ } {
+ 1 2 3
+ 2 two three
+ 3 {} 8.2
+ }
+
+ do_execsql_test 1.$tn2.$tn.3 { PRAGMA integrity_check } ok
+ }
+}
+
+#-------------------------------------------------------------------------
+# Check that an OTA cannot be applied to a table that has no PK.
+#
+reset_db
+create_ota1 ota.db
+do_execsql_test 2.1 { CREATE TABLE t1(a, b, c) }
+do_test 2.2 {
+ sqlite3ota ota test.db ota.db
+ ota step
+} {SQLITE_ERROR}
+do_test 2.3 {
+ list [catch { ota close } msg] $msg
+} {1 {SQLITE_ERROR - table t1 has no PRIMARY KEY}}
+
+reset_db
+do_execsql_test 2.4 { CREATE TABLE t1(a PRIMARY KEY, b, c) }
+do_test 2.5 {
+ sqlite3ota ota test.db ota.db
+ ota step
+} {SQLITE_ERROR}
+do_test 2.6 {
+ list [catch { ota close } msg] $msg
+} {1 {SQLITE_ERROR - table t1 has no PRIMARY KEY}}
+
+#-------------------------------------------------------------------------
+# Check that if a UNIQUE constraint is violated the current and all
+# subsequent [ota step] calls return SQLITE_CONSTRAINT. And that the OTA
+# transaction is rolled back by the [ota close] that deletes the ota
+# handle.
+#
+foreach {tn errcode errmsg schema} {
+ 1 SQLITE_CONSTRAINT "UNIQUE constraint failed: t1.a" {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+ INSERT INTO t1 VALUES(3, 2, 1);
+ }
+
+ 2 SQLITE_CONSTRAINT "UNIQUE constraint failed: t1.c" {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c UNIQUE);
+ INSERT INTO t1 VALUES(4, 2, 'three');
+ }
+
+} {
+ reset_db
+ execsql $schema
+ set cksum [dbcksum db main]
+
+ do_test 3.$tn.1 {
+ create_ota1 ota.db
+ sqlite3ota ota test.db ota.db
+ while {[set res [ota step]]=="SQLITE_OK"} {}
+ set res
+ } $errcode
+
+ do_test 3.$tn.2 { ota step } $errcode
+
+ do_test 3.$tn.3 {
+ list [catch { ota close } msg] $msg
+ } [list 1 "$errcode - $errmsg"]
+
+ do_test 3.$tn.4 { dbcksum db main } $cksum
+}
+
+
+finish_test
+
--- /dev/null
+# 2014 August 30
+#
+# 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.
+#
+#***********************************************************************
+#
+
+set testdir [file join [file dirname $argv0] .. .. test]
+source $testdir/tester.tcl
+set ::testprefix ota2
+
+
+do_execsql_test 1.0 {
+ PRAGMA ota_mode = 1;
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(a, b);
+ BEGIN;
+ INSERT INTO t1 VALUES(1, 2);
+} {wal}
+
+do_test 1.1 {
+ set state [sqlite3_transaction_save db]
+ db close
+ file exists test.db-wal
+} {1}
+
+do_test 1.2 {
+ sqlite3 db test.db
+ db eval {SELECT * FROM t1}
+} {}
+
+do_test 1.3 {
+ execsql {BEGIN IMMEDIATE}
+ sqlite3_transaction_restore db $::state
+ db eval {SELECT * FROM t1}
+} {1 2}
+
+do_test 1.4 {
+ execsql {
+ INSERT INTO t1 VALUES(3, 4);
+ COMMIT;
+ SELECT * FROM t1;
+ }
+} {1 2 3 4}
+
+do_test 1.5 {
+ db close
+ file exists test.db-wal
+} {0}
+
+do_test 1.5 {
+ sqlite3 db test.db
+ db eval {SELECT * FROM t1}
+} {1 2 3 4}
+
+finish_test
+
--- /dev/null
+/*
+** 2014 August 30
+**
+** 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.
+**
+*************************************************************************
+*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "sqlite3.h"
+#include "sqlite3ota.h"
+
+
+/*
+** The ota_state table is used to save the state of a partially applied
+** update so that it can be resumed later. The table contains at most a
+** single row:
+**
+** "wal_state" -> Blob to use with sqlite3_transaction_restore().
+**
+** "tbl" -> Table currently being written (target database names).
+**
+** "idx" -> Index currently being written (target database names).
+** Or, if the main table is being written, a NULL value.
+**
+** "row" -> Last rowid processed from ota database table (i.e. data_%).
+**
+** "progress" -> total number of key/value b-tree operations performed
+** so far as part of this ota update.
+*/
+#define OTA_CREATE_STATE "CREATE TABLE IF NOT EXISTS ota_state" \
+ "(wal_state, tbl, idx, row, progress)"
+
+typedef struct OtaTblIter OtaTblIter;
+typedef struct OtaIdxIter OtaIdxIter;
+
+/*
+** Iterator used to iterate through all data tables in the OTA. As follows:
+**
+** OtaTblIter iter;
+** for(rc=tblIterFirst(db, &iter);
+** rc==SQLITE_OK && iter.zTarget;
+** rc=tblIterNext(&iter)
+** ){
+** }
+*/
+struct OtaTblIter {
+ sqlite3_stmt *pTblIter; /* Iterate through tables */
+ int iEntry; /* Index of current entry (from 1) */
+
+ /* Output varibles. zTarget==0 implies EOF. */
+ const char *zTarget; /* Name of target table */
+ const char *zSource; /* Name of source table */
+
+ /* Useful things populated by a call to tblIterPrepareAll() */
+ int nCol; /* Number of columns in this table */
+ char **azCol; /* Array of quoted column names */
+ sqlite3_stmt *pSelect; /* PK b-tree SELECT statement */
+ sqlite3_stmt *pInsert; /* PK b-tree INSERT statement */
+};
+
+/*
+** API is:
+**
+** idxIterFirst()
+** idxIterNext()
+** idxIterFinalize()
+** idxIterPrepareAll()
+*/
+struct OtaIdxIter {
+ sqlite3_stmt *pIdxIter; /* Iterate through indexes */
+ int iEntry; /* Index of current entry (from 1) */
+
+ /* Output varibles. zTarget==0 implies EOF. */
+ const char *zIndex; /* Name of index */
+
+ int nCol; /* Number of columns in index */
+ int *aiCol; /* Array of column indexes */
+ sqlite3_stmt *pWriter; /* Index writer */
+ sqlite3_stmt *pSelect; /* Select to read values in index order */
+};
+
+
+struct sqlite3ota {
+ sqlite3 *dbDest; /* Target db */
+ sqlite3 *dbOta; /* Ota db */
+
+ int rc; /* Value returned by last ota_step() call */
+ char *zErrmsg; /* Error message if rc!=SQLITE_OK */
+
+ OtaTblIter tbliter; /* Used to iterate through tables */
+ OtaIdxIter idxiter; /* Used to iterate through indexes */
+};
+
+static int prepareAndCollectError(
+ sqlite3 *db,
+ const char *zSql,
+ sqlite3_stmt **ppStmt,
+ char **pzErrmsg
+){
+ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
+ if( rc!=SQLITE_OK ){
+ *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ *ppStmt = 0;
+ }
+ return rc;
+}
+
+/*
+** Unless it is NULL, argument zSql points to a buffer allocated using
+** sqlite3_malloc containing an SQL statement. This function prepares the SQL
+** statement against database db and frees the buffer. If statement
+** compilation is successful, *ppStmt is set to point to the new statement
+** handle and SQLITE_OK is returned.
+**
+** Otherwise, if an error occurs, *ppStmt is set to NULL and an error code
+** returned. In this case, *pzErrmsg may also be set to point to an error
+** message. It is the responsibility of the caller to free this error message
+** buffer using sqlite3_free().
+**
+** If argument zSql is NULL, this function assumes that an OOM has occurred.
+** In this case SQLITE_NOMEM is returned and *ppStmt set to NULL.
+*/
+static int prepareFreeAndCollectError(
+ sqlite3 *db,
+ char *zSql,
+ sqlite3_stmt **ppStmt,
+ char **pzErrmsg
+){
+ int rc;
+ assert( *pzErrmsg==0 );
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ *ppStmt = 0;
+ }else{
+ rc = prepareAndCollectError(db, zSql, ppStmt, pzErrmsg);
+ sqlite3_free(zSql);
+ }
+ return rc;
+}
+
+static char *quoteSqlName(const char *zName){
+ int nName = strlen(zName);
+ char *zRet = sqlite3_malloc(nName * 2 + 2 + 1);
+ if( zRet ){
+ int i;
+ char *p = zRet;
+ *p++ = '"';
+ for(i=0; i<nName; i++){
+ if( zName[i]=='"' ) *p++ = '"';
+ *p++ = zName[i];
+ }
+ *p++ = '"';
+ *p++ = '\0';
+ }
+ return zRet;
+}
+
+static int tblIterPrepareAll(sqlite3ota *p){
+ OtaTblIter *pIter = &p->tbliter;
+ int rc = SQLITE_OK;
+ char *zCol = 0;
+ char *zBindings = 0;
+ char *zSql;
+ sqlite3_stmt *pPragma = 0;
+ int i;
+ int bSeenPk = 0; /* Set to true once PK column seen */
+
+ /* Allocate and populate the azCol[] array */
+ zSql = sqlite3_mprintf("PRAGMA main.table_info(%Q)", pIter->zTarget);
+ rc = prepareFreeAndCollectError(p->dbDest, zSql, &pPragma, &p->zErrmsg);
+ pIter->nCol = 0;
+ if( rc==SQLITE_OK ){
+ while( SQLITE_ROW==sqlite3_step(pPragma) ){
+ const char *zName = (const char*)sqlite3_column_text(pPragma, 1);
+ if( (pIter->nCol % 4)==0 ){
+ int nByte = sizeof(char*) * (pIter->nCol+4);
+ char **azNew = (char**)sqlite3_realloc(pIter->azCol, nByte);
+ if( azNew==0 ){
+ rc = SQLITE_NOMEM;
+ break;
+ }
+ pIter->azCol = azNew;
+ }
+ pIter->azCol[pIter->nCol] = quoteSqlName(zName);
+ if( pIter->azCol[pIter->nCol]==0 ){
+ rc = SQLITE_NOMEM;
+ break;
+ }
+ pIter->nCol++;
+ if( sqlite3_column_int(pPragma, 5) ) bSeenPk = 1;
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_finalize(pPragma);
+ }else{
+ sqlite3_finalize(pPragma);
+ }
+ }
+
+ /* If the table has no PRIMARY KEY, throw an exception. */
+ if( bSeenPk==0 ){
+ p->zErrmsg = sqlite3_mprintf("table %s has no PRIMARY KEY", pIter->zTarget);
+ rc = SQLITE_ERROR;
+ }
+
+ /* Populate the zCol variable */
+ for(i=0; rc==SQLITE_OK && i<pIter->nCol; i++){
+ zCol = sqlite3_mprintf("%z%s%s", zCol, (i==0?"":", "), pIter->azCol[i]);
+ if( zCol==0 ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+
+ /* Allocate and populate zBindings */
+ if( rc==SQLITE_OK ){
+ zBindings = (char*)sqlite3_malloc(pIter->nCol * 2);
+ if( zBindings==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int i;
+ for(i=0; i<pIter->nCol; i++){
+ zBindings[i*2] = '?';
+ zBindings[i*2+1] = ',';
+ }
+ zBindings[pIter->nCol*2-1] = '\0';
+ }
+ }
+
+ /* Create OtaTblIter.pSelect */
+ if( rc==SQLITE_OK ){
+ zSql = sqlite3_mprintf("SELECT rowid, %s FROM %Q", zCol, pIter->zSource);
+ rc = prepareFreeAndCollectError(p->dbOta,zSql,&pIter->pSelect, &p->zErrmsg);
+ }
+
+ /* Create OtaTblIter.pInsert */
+ if( rc==SQLITE_OK ){
+ zSql = sqlite3_mprintf("INSERT INTO %Q(%s) VALUES(%s)",
+ pIter->zTarget, zCol, zBindings
+ );
+ rc = prepareFreeAndCollectError(p->dbDest,zSql,&pIter->pInsert,&p->zErrmsg);
+ }
+
+ sqlite3_free(zCol);
+ sqlite3_free(zBindings);
+ return rc;
+}
+
+static void tblIterFreeAll(OtaTblIter *pIter){
+ int i;
+
+ sqlite3_finalize(pIter->pSelect);
+ sqlite3_finalize(pIter->pInsert);
+ for(i=0; i<pIter->nCol; i++) sqlite3_free(pIter->azCol[i]);
+ sqlite3_free(pIter->azCol);
+ pIter->azCol = 0;
+ pIter->pSelect = 0;
+ pIter->pInsert = 0;
+ pIter->nCol = 0;
+}
+
+static int tblIterNext(OtaTblIter *pIter){
+ int rc;
+
+ tblIterFreeAll(pIter);
+ assert( pIter->pTblIter );
+ rc = sqlite3_step(pIter->pTblIter);
+ if( rc==SQLITE_ROW ){
+ pIter->zSource = (const char*)sqlite3_column_text(pIter->pTblIter, 0);
+ pIter->zTarget = &pIter->zSource[5]; assert( 5==strlen("data_") );
+ pIter->iEntry++;
+ }else{
+ pIter->zSource = 0;
+ pIter->zTarget = 0;
+ }
+
+ if( rc==SQLITE_ROW || rc==SQLITE_DONE ) rc = SQLITE_OK;
+ return rc;
+}
+
+static int tblIterFirst(sqlite3 *db, OtaTblIter *pIter){
+ int rc; /* return code */
+ memset(pIter, 0, sizeof(OtaTblIter));
+ rc = sqlite3_prepare_v2(db,
+ "SELECT name FROM sqlite_master "
+ "WHERE type='table' AND name LIKE 'data_%'", -1, &pIter->pTblIter, 0
+ );
+ if( rc==SQLITE_OK ){
+ rc = tblIterNext(pIter);
+ }
+ return rc;
+}
+
+
+static void tblIterFinalize(OtaTblIter *pIter){
+ tblIterFreeAll(pIter);
+ sqlite3_finalize(pIter->pTblIter);
+ memset(pIter, 0, sizeof(OtaTblIter));
+}
+
+static void idxIterFreeAll(OtaIdxIter *pIter){
+ sqlite3_finalize(pIter->pWriter);
+ sqlite3_finalize(pIter->pSelect);
+ pIter->pWriter = 0;
+ pIter->pSelect = 0;
+ pIter->aiCol = 0;
+ pIter->nCol = 0;
+}
+
+static int idxIterPrepareAll(sqlite3ota *p){
+ int rc;
+ int i; /* Iterator variable */
+ char *zSql = 0;
+ char *zCols = 0; /* Columns list */
+ OtaIdxIter *pIter = &p->idxiter;
+
+ /* Prepare the writer statement to write (insert) entries into the index. */
+ rc = sqlite3_index_writer(
+ p->dbDest, 0, pIter->zIndex, &pIter->pWriter, &pIter->aiCol, &pIter->nCol
+ );
+
+ /* Prepare a SELECT statement to read values from the source table in
+ ** the same order as they are stored in the current index. The statement
+ ** is:
+ **
+ ** SELECT rowid, <cols> FROM data_<tbl> ORDER BY <cols>
+ */
+ for(i=0; rc==SQLITE_OK && i<pIter->nCol; i++){
+ const char *zQuoted = p->tbliter.azCol[ pIter->aiCol[i] ];
+ zCols = sqlite3_mprintf("%z%s%s", zCols, zCols?", ":"", zQuoted);
+ if( !zCols ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+ if( rc==SQLITE_OK ){
+ const char *zFmt = "SELECT rowid, %s FROM %Q ORDER BY %s";
+ zSql = sqlite3_mprintf(zFmt, zCols, p->tbliter.zSource, zCols);
+ if( zSql ){
+ sqlite3_stmt **pp = &p->idxiter.pSelect;
+ rc = prepareFreeAndCollectError(p->dbOta, zSql, pp, &p->zErrmsg);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+
+ sqlite3_free(zCols);
+ return rc;
+}
+
+static int idxIterNext(OtaIdxIter *pIter){
+ int rc;
+
+ idxIterFreeAll(pIter);
+ assert( pIter->pIdxIter );
+ rc = sqlite3_step(pIter->pIdxIter);
+ if( rc==SQLITE_ROW ){
+ pIter->zIndex = (const char*)sqlite3_column_text(pIter->pIdxIter, 0);
+ pIter->iEntry++;
+ }else{
+ pIter->zIndex = 0;
+ rc = sqlite3_finalize(pIter->pIdxIter);
+ pIter->pIdxIter = 0;
+ }
+
+ if( rc==SQLITE_ROW ) rc = SQLITE_OK;
+ return rc;
+}
+
+static int idxIterFirst(sqlite3 *db, const char *zTable, OtaIdxIter *pIter){
+ int rc; /* return code */
+ memset(pIter, 0, sizeof(OtaIdxIter));
+ rc = sqlite3_prepare_v2(db,
+ "SELECT name FROM sqlite_master "
+ "WHERE type='index' AND tbl_name = ?", -1, &pIter->pIdxIter, 0
+ );
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_bind_text(pIter->pIdxIter, 1, zTable, -1, SQLITE_TRANSIENT);
+ }
+ if( rc==SQLITE_OK ){
+ rc = idxIterNext(pIter);
+ }
+ return rc;
+}
+
+static void idxIterFinalize(OtaIdxIter *pIter){
+ idxIterFreeAll(pIter);
+ sqlite3_finalize(pIter->pIdxIter);
+ memset(pIter, 0, sizeof(OtaIdxIter));
+}
+
+/*
+** Call sqlite3_reset() on the SQL statement passed as the second argument.
+** If it returns anything other than SQLITE_OK, store the error code and
+** error message in the OTA handle.
+*/
+static void otaResetStatement(sqlite3ota *p, sqlite3_stmt *pStmt){
+ assert( p->rc==SQLITE_OK );
+ assert( p->zErrmsg==0 );
+ p->rc = sqlite3_reset(pStmt);
+ if( p->rc!=SQLITE_OK ){
+ sqlite3 *db = sqlite3_db_handle(pStmt);
+ p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ }
+}
+
+/*
+** Check that all SQL statements required to process the current
+** table and index have been prepared. If not, prepare them. If
+** an error occurs, store the error code and message in the OTA
+** handle before returning.
+*/
+static int otaPrepareAll(sqlite3ota *p){
+ assert( p->rc==SQLITE_OK );
+ assert( p->zErrmsg==0 );
+ assert( p->tbliter.zTarget );
+
+ if( p->tbliter.pSelect==0 ){
+ p->rc = tblIterPrepareAll(p);
+ }
+ if( p->rc==SQLITE_OK && p->idxiter.zIndex && 0==p->idxiter.pSelect ){
+ p->rc = idxIterPrepareAll(p);
+ }
+ return p->rc;
+}
+
+int sqlite3ota_step(sqlite3ota *p){
+ if( p ){
+ while( p && p->rc==SQLITE_OK && p->tbliter.zTarget ){
+ sqlite3_stmt *pSelect;
+ int i;
+
+ otaPrepareAll(p);
+ pSelect = (p->idxiter.zIndex ? p->idxiter.pSelect : p->tbliter.pSelect);
+
+ /* Advance to the next input row. */
+ if( p->rc==SQLITE_OK ){
+ int rc = sqlite3_step(pSelect);
+ if( rc!=SQLITE_ROW ){
+ otaResetStatement(p, pSelect);
+
+ /* Go to the next index. */
+ if( p->rc==SQLITE_OK ){
+ if( p->idxiter.zIndex ){
+ p->rc = idxIterNext(&p->idxiter);
+ }else{
+ p->rc = idxIterFirst(p->dbDest, p->tbliter.zTarget, &p->idxiter);
+ }
+ }
+
+ /* If there is no next index, go to the next table. */
+ if( p->rc==SQLITE_OK && p->idxiter.zIndex==0 ){
+ p->rc = tblIterNext(&p->tbliter);
+ }
+ continue;
+ }
+ }
+
+ /* Update the target database PK table according to the row that
+ ** tbliter.pSelect currently points to.
+ **
+ ** todo: For now, we assume all rows are INSERT commands - this will
+ ** change. */
+ if( p->rc==SQLITE_OK ){
+ sqlite3_stmt *pInsert;
+ int nCol;
+ if( p->idxiter.zIndex ){
+ pInsert = p->idxiter.pWriter;
+ nCol = p->idxiter.nCol;
+ }else{
+ pInsert = p->tbliter.pInsert;
+ nCol = p->tbliter.nCol;
+ }
+
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal = sqlite3_column_value(pSelect, i+1);
+ sqlite3_bind_value(pInsert, i+1, pVal);
+ }
+
+ sqlite3_step(pInsert);
+ otaResetStatement(p, pInsert);
+ }
+
+ break;
+ }
+
+ if( p->rc==SQLITE_OK && p->tbliter.zTarget==0 ) p->rc = SQLITE_DONE;
+ }
+
+ return (p ? p->rc : SQLITE_NOMEM);
+}
+
+static void otaOpenDatabase(sqlite3ota *p, sqlite3 **pDb, const char *zFile){
+ if( p->rc==SQLITE_OK ){
+ p->rc = sqlite3_open(zFile, pDb);
+ if( p->rc ){
+ const char *zErr = sqlite3_errmsg(*pDb);
+ p->zErrmsg = sqlite3_mprintf("sqlite3_open(): %s", zErr);
+ }
+ }
+}
+
+static void otaSaveTransactionState(sqlite3ota *p){
+ sqlite3_stmt *pStmt = 0;
+ void *pWalState = 0;
+ int nWalState = 0;
+ int rc;
+
+ const char *zInsert =
+ "INSERT INTO ota_state(wal_state, tbl, idx, row, progress)"
+ "VALUES(:wal_state, :tbl, :idx, :row, :progress)";
+
+ rc = sqlite3_transaction_save(p->dbDest, &pWalState, &nWalState);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(p->dbOta, "DELETE FROM ota_state", 0, 0, 0);
+ }
+ if( rc==SQLITE_OK ){
+ rc = prepareAndCollectError(p->dbOta, zInsert, &pStmt, &p->zErrmsg);
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_stmt *pSelect;
+ pSelect = (p->idxiter.zIndex ? p->idxiter.pSelect : p->tbliter.pSelect);
+ sqlite3_bind_blob(pStmt, 1, pWalState, nWalState, SQLITE_STATIC);
+ sqlite3_bind_text(pStmt, 2, p->tbliter.zTarget, -1, SQLITE_STATIC);
+ if( p->idxiter.zIndex ){
+ sqlite3_bind_text(pStmt, 3, p->idxiter.zIndex, -1, SQLITE_STATIC);
+ }
+ sqlite3_bind_int64(pStmt, 4, sqlite3_column_int64(pSelect, 0));
+ sqlite3_step(pStmt);
+ rc = sqlite3_finalize(pStmt);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(p->dbOta, "COMMIT", 0, 0, 0);
+ }
+ if( rc!=SQLITE_OK ){
+ p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(p->dbOta));
+ }
+ }
+ sqlite3_free(pWalState);
+ assert( p->rc==SQLITE_OK );
+ p->rc = rc;
+}
+
+static void otaLoadTransactionState(sqlite3ota *p){
+ sqlite3_stmt *pStmt = 0;
+ int rc;
+
+ const char *zSelect =
+ "SELECT wal_state, tbl, idx, row, progress FROM ota_state";
+
+ rc = prepareAndCollectError(p->dbOta, zSelect, &pStmt, &p->zErrmsg);
+ if( rc==SQLITE_OK ){
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ const void *pWalState = 0;
+ int nWalState = 0;
+ const char *zTbl;
+ const char *zIdx;
+ sqlite3_int64 iRowid;
+
+ pWalState = sqlite3_column_blob(pStmt, 0);
+ nWalState = sqlite3_column_bytes(pStmt, 0);
+ zTbl = (const char*)sqlite3_column_text(pStmt, 1);
+ zIdx = (const char*)sqlite3_column_text(pStmt, 2);
+ iRowid = sqlite3_column_int64(pStmt, 3);
+ rc = sqlite3_transaction_restore(p->dbDest, pWalState, nWalState);
+
+ while( rc==SQLITE_OK
+ && p->tbliter.zTarget
+ && sqlite3_stricmp(p->tbliter.zTarget, zTbl)
+ ){
+ rc = tblIterNext(&p->tbliter);
+ }
+ if( rc==SQLITE_OK && !p->tbliter.zTarget ){
+ rc = SQLITE_ERROR;
+ p->zErrmsg = sqlite3_mprintf("ota_state mismatch error");
+ }
+
+ if( rc==SQLITE_OK && zIdx ){
+ rc = idxIterFirst(p->dbDest, p->tbliter.zTarget, &p->idxiter);
+ while( rc==SQLITE_OK
+ && p->idxiter.zIndex
+ && sqlite3_stricmp(p->idxiter.zIndex, zIdx)
+ ){
+ rc = idxIterNext(&p->idxiter);
+ }
+ if( rc==SQLITE_OK && !p->idxiter.zIndex ){
+ rc = SQLITE_ERROR;
+ p->zErrmsg = sqlite3_mprintf("ota_state mismatch error");
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = otaPrepareAll(p);
+ }
+
+ if( rc==SQLITE_OK ){
+ sqlite3_stmt *pSelect;
+ pSelect = (p->idxiter.zIndex ? p->idxiter.pSelect : p->tbliter.pSelect);
+ while( sqlite3_column_int64(pSelect, 0)!=iRowid ){
+ rc = sqlite3_step(pSelect);
+ if( rc!=SQLITE_ROW ) break;
+ }
+ if( rc==SQLITE_ROW ){
+ rc = SQLITE_OK;
+ }else{
+ rc = SQLITE_ERROR;
+ p->zErrmsg = sqlite3_mprintf("ota_state mismatch error");
+ }
+ }
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_finalize(pStmt);
+ }else{
+ sqlite3_finalize(pStmt);
+ }
+ }
+ p->rc = rc;
+}
+
+
+/*
+** Open and return a new OTA handle.
+*/
+sqlite3ota *sqlite3ota_open(const char *zTarget, const char *zOta){
+ sqlite3ota *p;
+
+ p = (sqlite3ota*)sqlite3_malloc(sizeof(sqlite3ota));
+ if( p ){
+
+ /* Open the target database */
+ memset(p, 0, sizeof(sqlite3ota));
+ otaOpenDatabase(p, &p->dbDest, zTarget);
+ otaOpenDatabase(p, &p->dbOta, zOta);
+
+ /* If it has not already been created, create the ota_state table */
+ if( p->rc==SQLITE_OK ){
+ p->rc = sqlite3_exec(p->dbOta, OTA_CREATE_STATE, 0, 0, &p->zErrmsg);
+ }
+
+ if( p->rc==SQLITE_OK ){
+ const char *zScript =
+ "PRAGMA ota_mode=1;"
+ "PRAGMA journal_mode=wal;"
+ "BEGIN IMMEDIATE;"
+ ;
+ p->rc = sqlite3_exec(p->dbDest, zScript, 0, 0, &p->zErrmsg);
+ }
+
+ if( p->rc==SQLITE_OK ){
+ const char *zScript = "BEGIN IMMEDIATE";
+ p->rc = sqlite3_exec(p->dbOta, zScript, 0, 0, &p->zErrmsg);
+ }
+
+ /* Point the table iterator at the first table */
+ if( p->rc==SQLITE_OK ){
+ p->rc = tblIterFirst(p->dbOta, &p->tbliter);
+ }
+
+ if( p->rc==SQLITE_OK ){
+ otaLoadTransactionState(p);
+ }
+ }
+
+ return p;
+}
+
+static void otaCloseHandle(sqlite3 *db){
+ int rc = sqlite3_close(db);
+ assert( rc==SQLITE_OK );
+}
+
+int sqlite3ota_close(sqlite3ota *p, char **pzErrmsg){
+ int rc;
+ if( p ){
+
+ /* If the update has not been fully applied, save the state in
+ ** the ota db. If successful, this call also commits the open
+ ** transaction on the ota db. */
+ assert( p->rc!=SQLITE_ROW );
+ if( p->rc==SQLITE_OK ){
+ assert( p->zErrmsg==0 );
+ otaSaveTransactionState(p);
+ }
+
+ /* Close all open statement handles. */
+ tblIterFinalize(&p->tbliter);
+ idxIterFinalize(&p->idxiter);
+
+ /* If the ota update has been fully applied, commit the transaction
+ ** on the target database. */
+ if( p->rc==SQLITE_DONE ){
+ rc = sqlite3_exec(p->dbDest, "COMMIT", 0, 0, &p->zErrmsg);
+ if( rc!=SQLITE_OK ) p->rc = rc;
+ }
+
+ rc = p->rc;
+ *pzErrmsg = p->zErrmsg;
+ otaCloseHandle(p->dbDest);
+ otaCloseHandle(p->dbOta);
+ sqlite3_free(p);
+ }else{
+ rc = SQLITE_NOMEM;
+ *pzErrmsg = 0;
+ }
+ return rc;
+}
+
+
+/**************************************************************************/
+
+#ifdef SQLITE_TEST
+
+#include <tcl.h>
+
+/* From main.c (apparently...) */
+extern const char *sqlite3ErrName(int);
+
+static int test_sqlite3ota_cmd(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int ret = TCL_OK;
+ sqlite3ota *pOta = (sqlite3ota*)clientData;
+ const char *azMethod[] = { "step", "close", 0 };
+ int iMethod;
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "METHOD");
+ return TCL_ERROR;
+ }
+ if( Tcl_GetIndexFromObj(interp, objv[1], azMethod, "method", 0, &iMethod) ){
+ return TCL_ERROR;
+ }
+
+ switch( iMethod ){
+ case 0: /* step */ {
+ int rc = sqlite3ota_step(pOta);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ break;
+ }
+
+ case 1: /* close */ {
+ char *zErrmsg = 0;
+ int rc;
+ Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+ rc = sqlite3ota_close(pOta, &zErrmsg);
+ if( rc==SQLITE_OK || rc==SQLITE_DONE ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ assert( zErrmsg==0 );
+ }else{
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ if( zErrmsg ){
+ Tcl_AppendResult(interp, " - ", zErrmsg, 0);
+ sqlite3_free(zErrmsg);
+ }
+ ret = TCL_ERROR;
+ }
+ break;
+ }
+
+ default: /* seems unlikely */
+ assert( !"cannot happen" );
+ break;
+ }
+
+ return ret;
+}
+
+/*
+** Tclcmd: sqlite3ota CMD <target-db> <ota-db>
+*/
+static int test_sqlite3ota(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3ota *pOta = 0;
+ const char *zCmd;
+ const char *zTarget;
+ const char *zOta;
+
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "NAME TARGET-DB OTA-DB");
+ return TCL_ERROR;
+ }
+ zCmd = Tcl_GetString(objv[1]);
+ zTarget = Tcl_GetString(objv[2]);
+ zOta = Tcl_GetString(objv[3]);
+
+ pOta = sqlite3ota_open(zTarget, zOta);
+ Tcl_CreateObjCommand(interp, zCmd, test_sqlite3ota_cmd, (ClientData)pOta, 0);
+ Tcl_SetObjResult(interp, objv[1]);
+ return TCL_OK;
+}
+
+int SqliteOta_Init(Tcl_Interp *interp){
+ Tcl_CreateObjCommand(interp, "sqlite3ota", test_sqlite3ota, 0, 0);
+ return TCL_OK;
+}
+
+#endif /* ifdef SQLITE_TEST */
+
+
+
--- /dev/null
+/*
+** 2014 August 30
+**
+** 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.
+**
+*************************************************************************
+**
+** This file contains the public interface for the OTA extension.
+*/
+
+/*
+** SUMMARY
+**
+** Writing a transaction containing a large number of operations on
+** b-tree indexes that are collectively larger than the available cache
+** memory can be very inefficient.
+**
+** The problem is that in order to update a b-tree, the leaf page (at least)
+** containing the entry being inserted or deleted must be modified. If the
+** working set of leaves is larger than the available cache memory, then a
+** single leaf that is modified more than once as part of the transaction
+** may be loaded from or written to the persistent media more than once.
+** Additionally, because the index updates are likely to be applied in
+** random order, access to pages within the databse is also likely to be in
+** random order, which is itself quite inefficient.
+**
+** One way to improve the situation is to sort the operations on each index
+** by index key before applying them to the b-tree. This leads to an IO
+** pattern that resembles a single linear scan through the index b-tree,
+** and all but guarantees each modified leaf page is loaded and stored
+** exactly once. SQLite uses this trick to improve the performance of
+** CREATE INDEX commands. This extension allows it to be used to improve
+** the performance of large transactions on existing databases.
+**
+** Additionally, this extension allows the work involved in writing the
+** large transaction to be broken down into sub-transactions performed
+** sequentially by separate processes. This is useful if the system cannot
+** guarantee that a single update process may run for long enough to apply
+** the entire update, for example because the update is running on a mobile
+** device that is frequently rebooted. Even after the writer process has
+** committed one or more sub-transactions, other database clients continue
+** to read from the original database snapshot. In other words, partially
+** applied transactions are not visible to other clients.
+**
+** "OTA" stands for "Over The Air" update. As in a large database update
+** transmitted via a wireless network to a mobile device. A transaction
+** applied using this extension is hence refered to as an "OTA update".
+**
+**
+** LIMITATIONS
+**
+** An "OTA update" transaction is subject to the following limitations:
+**
+** * The transaction must consist of INSERT, UPDATE and DELETE operations
+** only.
+**
+** * INSERT statements may not use any default values.
+**
+** * UPDATE and DELETE statements must identify their target rows by
+** real PRIMARY KEY values - i.e. INTEGER PRIMARY KEY columns or
+** by the PRIMARY KEY columns of WITHOUT ROWID tables.
+**
+** * UPDATE statements may not modify real PRIMARY KEY columns.
+**
+** * No triggers will be fired.
+**
+** * No foreign key violations are detected or reported.
+**
+** * No constraint handling mode except for "OR ROLLBACK" is supported.
+**
+**
+** PREPARATION
+**
+** An "OTA update" is stored as a separate SQLite database. A database
+** containing an OTA update is an "OTA database". For each table in the
+** target database to be updated, the OTA database should contain a table
+** named "data_<target name>" containing the same set of columns as the
+** target table, and one more - "ota_control". The data_% table should
+** have no PRIMARY KEY or UNIQUE constraints, but each column should have
+** the same type as the corresponding column in the target database.
+** The "ota_control" column should have no type at all. For example, if
+** the target database contains:
+**
+** CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c UNIQUE);
+**
+** Then the OTA database should contain:
+**
+** CREATE TABLE data_t1(a INTEGER, b TEXT, c, ota_control);
+**
+** The order of the columns in the data_% table does not matter.
+**
+** For each row to INSERT into the target database as part of the OTA
+** update, the corresponding data_% table should contain a single record
+** with the "ota_control" column set to contain integer value 0. The
+** other columns should be set to the values that make up the new record
+** to insert.
+**
+** For each row to DELETE from the target database as part of the OTA
+** update, the corresponding data_% table should contain a single record
+** with the "ota_control" column set to contain integer value 1. The
+** real primary key values of the row to delete should be stored in the
+** corresponding columns of the data_% table. The values stored in the
+** other columns are not used.
+**
+** For each row to DELETE from the target database as part of the OTA
+** update, the corresponding data_% table should contain a single record
+** with the "ota_control" column set to contain a value of type text.
+** The real primary key values identifying the row to update should be
+** stored in the corresponding columns of the data_% table row, as should
+** the new values of all columns being update. The text value in the
+** "ota_control" column must contain the same number of characters as
+** there are column in the target database table, and must consist entirely
+** of "x" and "." characters. For each column that is being updated,
+** the corresponding character is set to "x". For those that remain as
+** they are, the corresponding character of the ota_control value should
+** be set to ".". For example, given the tables above, the update
+** statement:
+**
+** UPDATE t1 SET c = 'usa' WHERE a = 4;
+**
+** is represented by the data_t1 row created by:
+**
+** INSERT INTO data_t1(a, b, c, ota_control) VALUES(4, NULL, 'usa', '..x');
+**
+**
+** USAGE
+**
+** The API declared below allows an application to apply an OTA update
+** stored on disk to an existing target database. Essentially, the
+** application:
+**
+** 1) Opens an OTA handle using the sqlite3ota_open() function.
+**
+** 2) Calls the sqlite3ota_step() function one or more times on
+** the new handle. Each call to sqlite3ota_step() performs a single
+** b-tree operation, so thousands of calls may be required to apply
+** a complete update.
+**
+** 3) Calls sqlite3ota_close() to close the OTA update handle. If
+** sqlite3ota_step() has been called enough times to completely
+** apply the update to the target database, then it is committed
+** and made visible to other database clients at this point.
+** Otherwise, the state of the OTA update application is saved
+** in the OTA database for later resumption.
+**
+** See comments below for more detail on APIs.
+**
+** If an update is only partially applied to the target database by the
+** time sqlite3ota_close() is called, various state information is saved
+** within the OTA database. This allows subsequent processes to automatically
+** resume the OTA update from where it left off.
+**
+** To remove all OTA extension state information, returning an OTA database
+** to its original contents, it is sufficient to drop all tables that begin
+** with the prefix "ota_"
+*/
+
+#ifndef _SQLITE3OTA_H
+#define _SQLITE3OTA_H
+
+typedef struct sqlite3ota sqlite3ota;
+
+/*
+** Open an OTA handle.
+**
+** Argument zTarget is the path to the target database. Argument zOta is
+** the path to the OTA database. Each call to this function must be matched
+** by a call to sqlite3ota_close().
+*/
+sqlite3ota *sqlite3ota_open(const char *zTarget, const char *zOta);
+
+/*
+** Do some work towards applying the OTA update to the target db.
+**
+** Return SQLITE_DONE if the update has been completely applied, or
+** SQLITE_OK if no error occurs but there remains work to do to apply
+** the OTA update. If an error does occur, some other error code is
+** returned.
+**
+** Once a call to sqlite3ota_step() has returned a value other than
+** SQLITE_OK, all subsequent calls on the same OTA handle are no-ops
+** that immediately return the same value.
+*/
+int sqlite3ota_step(sqlite3ota *pOta);
+
+/*
+** Close an OTA handle.
+**
+** If the OTA update has been completely applied, commit it to the target
+** database. Otherwise, assuming no error has occurred, save the current
+** state of the OTA update appliation to the OTA database.
+**
+** If an error has already occurred as part of an sqlite3ota_step()
+** or sqlite3ota_open() call, or if one occurs within this function, an
+** SQLite error code is returned. Additionally, *pzErrmsg may be set to
+** point to a buffer containing a utf-8 formatted English language error
+** message. It is the responsibility of the caller to eventually free any
+** such buffer using sqlite3_free().
+**
+** Otherwise, if no error occurs, this function returns SQLITE_OK if the
+** update has been partially applied, or SQLITE_DONE if it has been
+** completely applied.
+*/
+int sqlite3ota_close(sqlite3ota *pOta, char **pzErrmsg);
+
+#endif /* _SQLITE3OTA_H */
+
$(TOP)/ext/fts3/fts3_expr.c \
$(TOP)/ext/fts3/fts3_tokenizer.c \
$(TOP)/ext/fts3/fts3_write.c \
- $(TOP)/ext/async/sqlite3async.c
+ $(TOP)/ext/async/sqlite3async.c \
+ $(TOP)/ext/ota/sqlite3ota.c
# Header files used by all library source files.
#
-C Update\scomments\sin\sthe\sANALYZE\scommand\sthat\sdescribe\show\sthe\sStat4Accum\nobjecct\sis\spassed\saround\swithin\sthe\sVDBE.\s\sNo\schanges\sto\sfunctional\scode.
-D 2014-09-01T23:06:44.401
+C Add\san\sexperimental\sextension\sfor\sapplying\sbulk\supdates\sto\sdatabases.
+D 2014-09-02T19:59:40.729
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in cf57f673d77606ab0f2d9627ca52a9ba1464146a
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95
F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e
F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212
+F ext/ota/ota1.test ea2865997ce573fadaf12eb0a0f80ef22d9dd77f
+F ext/ota/ota2.test 4f7abfe1dfb7c3709bf45e94f3e65f3839b4f115
+F ext/ota/sqlite3ota.c ad55821883e4110367a30ffca282032d2bf36e45
+F ext/ota/sqlite3ota.h d3187a98fe1e3445c58f7a27d96ac385b78486a1
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
F ext/rtree/rtree.c 57bec53e1a677ab74217fe1f20a58c3a47261d6b
F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 9b5ccf1097050b1f16681f7d4beeea4f7f7ac2c3
+F main.mk 566c36f247d19525264e584466b5f07c0a48302e
F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F src/backup.c a31809c65623cc41849b94d368917f8bb66e6a7e
F src/bitvec.c 19a4ba637bd85f8f63fc8c9bae5ade9fb05ec1cb
F src/btmutex.c ec9d3f1295dafeb278c3830211cc5584132468f4
-F src/btree.c 2a483a8045118faa99867a8679da42754b532318
+F src/btree.c c46043fbb09c18a19bdb96eadde6e724901d6fcf
F src/btree.h a79aa6a71e7f1055f01052b7f821bd1c2dce95c8
F src/btreeInt.h cf180d86b2e9e418f638d65baa425c4c69c0e0e3
F src/build.c c26b233dcdb1e2c8f468d49236c266f9f3de96d8
F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5
F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
-F src/insert.c d1a104e67b33314d4cc5c1356147446086ab9fc8
+F src/insert.c 62b0ceab1720dc74ed1fbcf953224132245704d8
F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
F src/legacy.c 87c92f4a08e2f70220e3b22a9c3b2482d36a134a
F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b
F src/loadext.c 31c2122b7dd05a179049bbf163fd4839f181cbab
-F src/main.c d2ef03a45552e11813c68326d5edfda992e319d4
+F src/main.c c9802dc99c019fbba516202300d56be2c478fa93
F src/malloc.c 954de5f998c23237e04474a3f2159bf483bba65a
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c c0c990fcaddff810ea277b4fb5d9138603dd5d4b
F src/os_unix.c 8525ca79457c5b4673a5fda2774ee39fe155f40f
F src/os_win.c 2aa8aa7780d7cf03e912d2088ab2ec5c32f33dc5
F src/os_win.h 09e751b20bbc107ffbd46e13555dc73576d88e21
-F src/pager.c 3e732d2bbdd8d8d95fed0c5ae7e718d73153c4c5
-F src/pager.h ffd5607f7b3e4590b415b007a4382f693334d428
+F src/pager.c a3caa08db8227c5a32f388be67f33d8cb44d5e35
+F src/pager.h 1acd367a0ffb63026b0461ea5eaeeb8046414a71
F src/parse.y 22d6a074e5f5a7258947a1dc55a9bf946b765dd0
F src/pcache.c 3b3791297e8977002e56b4a9b8916f2039abad9b
F src/pcache.h 9b559127b83f84ff76d735c8262f04853be0c59a
F src/pcache1.c c5af6403a55178c9d1c09e4f77b0f9c88822762c
-F src/pragma.c 14bcdb504128a476cce5bbc086d5226c5e46c225
-F src/prepare.c 3842c1dfc0b053458e3adcf9f6efc48e03e3fe3d
+F src/pragma.c d252459fb3ce19448d1a2f41000c780fac4c0c26
+F src/prepare.c 314961aa6650cc860394cb2f31931cf2de93baa8
F src/printf.c 00986c86ddfffefc2fd3c73667ff51b3b9709c74
F src/random.c d10c1f85b6709ca97278428fd5db5bbb9c74eece
F src/resolve.c 0ea356d32a5e884add23d1b9b4e8736681dd5697
F src/rowset.c a9c9aae3234b44a6d7c6f5a3cadf90dce1e627be
F src/select.c 89e569b263535662f54b537eb9118b2c554ae7aa
F src/shell.c 713cef4d73c05fc8e12f4960072329d767a05d50
-F src/sqlite.h.in 43852c8b68b4c579948cb37182918078836c5c06
+F src/sqlite.h.in 706b420dc3390532435a3bd196360a940805f02f
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc
-F src/sqliteInt.h 6244ee9052752e26d1275ab20c9b774385aa57d2
+F src/sqliteInt.h 7c090825333d91ca392c2479a9e835e7b6a5eb12
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
-F src/tclsqlite.c 7d100e2e7aad614bb3d7026a41a0e3827dbaaebc
-F src/test1.c 363a5089230a92cf0aaa7a2945da7f2bf3b0a8d3
-F src/test2.c 98049e51a17dc62606a99a9eb95ee477f9996712
+F src/tclsqlite.c 29357f2be7b0d00e8ea900eaf727e0c5ffeaa660
+F src/test1.c e9a0e5804b078532e69e69ec14c8326bf2cfc318
+F src/test2.c 84f6a786aa7ffa12fff83acb52660e337ffe642a
F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c
F src/test4.c 9b32d22f5f150abe23c1830e2057c4037c45b3df
F src/test5.c 5a34feec76d9b3a86aab30fd4f6cc9c48cbab4c1
F src/vdbeInt.h cdc8e421f85beb1ac9b4669ec5beadab6faa15e0
F src/vdbeapi.c 09677a53dd8c71bcd670b0bd073bb9aefa02b441
F src/vdbeaux.c cef5d34a64ae3a65b56d96d3fd663246ec8e1c36
-F src/vdbeblob.c 848238dc73e93e48432991bb5651bf87d865eca4
+F src/vdbeblob.c 0bc9d22578d87ad9ff1c16e20a36863326f34fd7
F src/vdbemem.c 921d5468a68ac06f369810992e84ca22cc730a62
F src/vdbesort.c 02646a9f86421776ae5d7594f620f9ed669d3698
F src/vdbetrace.c 6f52bc0c51e144b7efdcfb2a8f771167a8816767
F src/vtab.c 019dbfd0406a7447c990e1f7bd1dfcdb8895697f
-F src/wal.c 264df50a1b33124130b23180ded2e2c5663c652a
-F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
+F src/wal.c 93b4fcb56a98f435a2cb66024bb2b12d66d1ff53
+F src/wal.h 237bc4484f7c289f094ecb6efb2b6c02005484e1
F src/walker.c 11edb74d587bc87b33ca96a5173e3ec1b8389e45
F src/where.c d9eae96b2cbbe4842eac3ee156ccd1b933d802c4
F src/whereInt.h 923820bee9726033a501a08d2fc69b9c1ee4feb3
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 4cae93f8ae8fb3fe38fd5dc7d3a5ea0d11552841
-R fe37d23fb1f96547febf2ff7c0ba49b9
-U drh
-Z 29e2a2847ff584a00093c5b89ffca704
+P 9779c7a9eb1e2bd36e9286331a9314f064014d80
+R 5df4197c767caad19feca7391f16a42d
+T *branch * experimental-bulk-update
+T *sym-experimental-bulk-update *
+T -sym-trunk *
+U dan
+Z 195de077202fec58ba90fc725d7305e7
-9779c7a9eb1e2bd36e9286331a9314f064014d80
\ No newline at end of file
+2954ab501049968430011b63d046eb42ff37a56c
\ No newline at end of file
** and has the read-uncommitted flag set, then no lock is required.
** Return true immediately.
*/
- if( (pBtree->sharable==0)
+ if( (pBtree->db->flags & SQLITE_OtaMode)
+ || (pBtree->sharable==0)
|| (eLockType==READ_LOCK && (pBtree->db->flags & SQLITE_ReadUncommitted))
){
return 1;
btree_open_out:
if( rc!=SQLITE_OK ){
if( pBt && pBt->pPager ){
- sqlite3PagerClose(pBt->pPager);
+ sqlite3PagerClose(pBt->pPager, 0);
}
sqlite3_free(pBt);
sqlite3_free(p);
** Clean out and delete the BtShared object.
*/
assert( !pBt->pCursor );
- sqlite3PagerClose(pBt->pPager);
+ sqlite3PagerClose(pBt->pPager, (p->db->flags & SQLITE_OtaMode)!=0);
if( pBt->xFreeSchema && pBt->pSchema ){
pBt->xFreeSchema(pBt->pSchema);
}
int iThisCur; /* Cursor for this UNIQUE index */
int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */
+ /* If the "ota_mode" flag is set, ignore all indexes except the PK
+ ** index of WITHOUT ROWID tables. */
+ if( (db->flags & SQLITE_OtaMode) && pIdx!=pPk) continue;
+
if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */
if( bAffinityDone==0 ){
sqlite3TableAffinity(v, pTab, regNewData+1);
assert( pTab->pSelect==0 ); /* This table is not a VIEW */
for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
if( aRegIdx[i]==0 ) continue;
+
+ /* If the "ota_mode" flag is set, ignore all indexes except the PK
+ ** index of WITHOUT ROWID tables. */
+ if( (pParse->db->flags & SQLITE_OtaMode)
+ && (pTab->iPKey>=0 || pIdx->idxType!=SQLITE_IDXTYPE_PRIMARYKEY)
+ ){
+ continue;
+ }
+
bAffinityDone = 1;
if( pIdx->pPartIdxWhere ){
sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2);
Btree *pBt = sqlite3DbNameToBtree(db, zDbName);
return pBt ? sqlite3BtreeIsReadonly(pBt) : -1;
}
+
+int sqlite3_transaction_save(sqlite3 *db, void **ppState, int *pnState){
+ Pager *pPager = sqlite3BtreePager(db->aDb[0].pBt);
+ return sqlite3PagerSaveState(pPager, ppState, pnState);
+}
+
+int sqlite3_transaction_restore(sqlite3 *db, const void *pState, int nState){
+ Pager *pPager = sqlite3BtreePager(db->aDb[0].pBt);
+ return sqlite3PagerRestoreState(pPager, pState, nState);
+}
+
+
** a hot journal may be left in the filesystem but no error is returned
** to the caller.
*/
-int sqlite3PagerClose(Pager *pPager){
+int sqlite3PagerClose(Pager *pPager, int bOtaMode){
u8 *pTmp = (u8 *)pPager->pTmpSpace;
assert( assert_pager_state(pPager) );
/* pPager->errCode = 0; */
pPager->exclusiveMode = 0;
#ifndef SQLITE_OMIT_WAL
- sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, pPager->pageSize, pTmp);
+ sqlite3WalClose(
+ pPager->pWal, pPager->ckptSyncFlags, pPager->pageSize, (bOtaMode?0:pTmp)
+ );
pPager->pWal = 0;
#endif
pager_reset(pPager);
return rc;
}
+int sqlite3PagerSaveState(Pager *pPager, void **ppState, int *pnState){
+ int rc = SQLITE_OK;
+ *ppState = 0;
+ *pnState = 0;
+ if( pPager->pWal==0 || pPager->eState<PAGER_WRITER_LOCKED ){
+ rc = SQLITE_ERROR;
+ }else{
+ /* Flush all dirty pages to the wal. */
+ PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
+ rc = sqlite3WalFrames(pPager->pWal,
+ pPager->pageSize, pList, 0, 0, pPager->walSyncFlags
+ );
+ if( rc==SQLITE_OK ){
+ rc = sqlite3WalSaveState(pPager->pWal, ppState, pnState);
+ }
+ }
+ return rc;
+}
+
+int sqlite3PagerRestoreState(Pager *pPager, const void *pState, int nState){
+ int rc = SQLITE_OK;
+ if( pPager->pWal==0
+ || pPager->eState<PAGER_WRITER_LOCKED
+ || sqlite3PcacheDirtyList(pPager->pPCache)
+ ){
+ rc = SQLITE_ERROR;
+ }else{
+ sqlite3PcacheTruncate(pPager->pPCache, 1);
+ rc = sqlite3WalRestoreState(pPager->pWal, pState, nState);
+ pPager->eState = PAGER_WRITER_CACHEMOD;
+ }
+
+ return rc;
+}
+
#endif /* !SQLITE_OMIT_WAL */
#ifdef SQLITE_ENABLE_ZIPVFS
int,
void(*)(DbPage*)
);
-int sqlite3PagerClose(Pager *pPager);
+int sqlite3PagerClose(Pager *pPager, int);
int sqlite3PagerReadFileheader(Pager*, int, unsigned char*);
/* Functions used to configure a Pager object. */
# define enable_simulated_io_errors()
#endif
+int sqlite3PagerSaveState(Pager *pPager, void **ppState, int *pnState);
+int sqlite3PagerRestoreState(Pager *pPager, const void *pState, int nState);
+
#endif /* _PAGER_H_ */
/* ePragTyp: */ PragTyp_MMAP_SIZE,
/* ePragFlag: */ 0,
/* iArg: */ 0 },
+#endif
+ { /* zName: */ "ota_mode",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlag: */ 0,
+ /* iArg: */ SQLITE_OtaMode },
+#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
{ /* zName: */ "page_count",
/* ePragTyp: */ PragTyp_PAGE_COUNT,
/* ePragFlag: */ PragFlag_NeedSchema,
}else if( pPk==0 ){
k = 1;
}else{
- for(k=1; ALWAYS(k<=pTab->nCol) && pPk->aiColumn[k-1]!=i; k++){}
+ if( (db->flags & SQLITE_OtaMode) && HasRowid(pTab) ){
+ k = 0;
+ }else{
+ for(k=1; ALWAYS(k<=pTab->nCol) && pPk->aiColumn[k-1]!=i; k++){}
+ }
}
sqlite3VdbeAddOp2(v, OP_Integer, k, 6);
sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6);
return rc;
}
-
#ifndef SQLITE_OMIT_UTF16
/*
** Compile the UTF-16 encoded SQL statement zSql into a statement handle.
/* #define SQLITE_ABORT 4 // Also an error code */
#define SQLITE_REPLACE 5
+/*
+** Allocate a statement handle that may be used to write directly to an
+** index b-tree. This allows the user to create a corrupt database. Once
+** the statement handle is allocated, it may be used with the same APIs
+** as any statement handle created with sqlite3_prepare().
+**
+** The statement writes to the index specified by parameter zIndex, which
+** must be in the "main" database. If argument bDelete is false, then each
+** time the statement is sqlite3_step()ed, an entry is inserted into the
+** b-tree index. If it is true, then an entry may be deleted (or may not, if
+** the specified key is not found) each time the statement is
+** sqlite3_step()ed.
+**
+** If statement compilation is successful, *ppStmt is set to point to the
+** new statement handle and SQLITE_OK is returned. Otherwise, if an error
+** occurs, *ppStmt is set to NULL and an error code returned. An error
+** message may be left in the database handle in this case.
+**
+** If statement compilation succeeds, output variable *pnCol is set to the
+** total number of columns in the index, including the primary key columns
+** at the end. Variable *paiCol is set to point to an array *pnCol entries
+** in size. Each entry is the table column index, numbered from zero from left
+** to right, of the corresponding index column. For example, if:
+**
+** CREATE TABLE t1(a, b, c, d);
+** CREATE INDEX i1 ON t1(b, c);
+**
+** then *pnCol is 3 and *paiCol points to an array containing {1, 2, -1}.
+** If table t1 had an explicit INTEGER PRIMARY KEY, then the "-1" in the
+** *paiCol array would be replaced by its column index. Or if:
+**
+** CREATE TABLE t2(a, b, c, d, PRIMARY KEY(d, c)) WITHOUT ROWID;
+** CREATE INDEX i2 ON t2(a);
+**
+** then (*pnCol) is 3 and *paiCol points to an array containing {0, 3, 2}.
+**
+** The lifetime of the array is the same as that of the statement handle -
+** it is automatically freed when the statement handle is passed to
+** sqlite3_finalize().
+**
+** The statement has (*pnCol) SQL variables that values may be bound to.
+** They correspond to the values used to create the index key that is
+** inserted or deleted when the statement is stepped.
+**
+** If the index is a UNIQUE index, the usual checking and error codes apply
+** to insert operations.
+*/
+int sqlite3_index_writer(
+ sqlite3 *db,
+ int bDelete, /* Zero for insert, non-zero for delete */
+ const char *zIndex, /* Index to write to */
+ sqlite3_stmt**, /* OUT: New statement handle */
+ int **paiCol, int *pnCol /* OUT: See above */
+);
+
+/*
+** This function is used to save the state of an ongoing WAL mode write
+** transaction on the "main" database of the supplied database handle.
+**
+** If successful, SQLITE_OK is returned and output variable (*ppState)
+** is set to point to a buffer containing the transaction state data.
+** (*pnState) is set to the size of that buffer in bytes. Otherwise, if
+** an error occurs, an SQLite error code is returned and both output
+** variables are zeroed.
+**
+** A transaction state may be saved if:
+**
+** * the transaction does not contain any schema modifications.
+** * there are no open sub-transactions.
+*/
+int sqlite3_transaction_save(sqlite3 *db, void **ppState, int *pnState);
+int sqlite3_transaction_restore(sqlite3 *db, const void *pState, int nState);
/*
** Undo the hack that converts floating point types to integer for
#define SQLITE_QueryOnly 0x02000000 /* Disable database changes */
#define SQLITE_VdbeEQP 0x04000000 /* Debug EXPLAIN QUERY PLAN */
+#define SQLITE_OtaMode 0x08000000 /* True in "ota mode" */
+
/*
** Bits of the sqlite3.dbOptFlags field that are used by the
#define MEMTYPE_PCACHE 0x08 /* Page cache allocations */
#define MEMTYPE_DB 0x10 /* Uses sqlite3DbMalloc, not sqlite_malloc */
+
/*
** Threading interface
*/
extern int SqliteSuperlock_Init(Tcl_Interp*);
extern int SqlitetestSyscall_Init(Tcl_Interp*);
+ extern int SqliteOta_Init(Tcl_Interp*);
+
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
extern int Sqlitetestfts3_Init(Tcl_Interp *interp);
#endif
Sqlitemultiplex_Init(interp);
SqliteSuperlock_Init(interp);
SqlitetestSyscall_Init(interp);
+ SqliteOta_Init(interp);
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
Sqlitetestfts3_Init(interp);
}
+/*
+** tclcmd: sqlite3_transaction_save DB
+*/
+static int testTransactionSave(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ void *pState;
+ int nState;
+ sqlite3 *db;
+ int rc;
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+ rc = sqlite3_transaction_save(db, &pState, &nState);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pState, nState));
+ }else{
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ return TCL_ERROR;
+ }
+
+ sqlite3_free(pState);
+ return TCL_OK;
+}
+
+/*
+** tclcmd: sqlite3_transaction_restore DB BLOB
+*/
+static int testTransactionRestore(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ void *pState;
+ int nState;
+ sqlite3 *db;
+ int rc;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB BLOB");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+ pState = (void*)Tcl_GetByteArrayFromObj(objv[2], &nState);
+
+ rc = sqlite3_transaction_restore(db, pState, nState);
+ if( rc==SQLITE_OK ){
+ Tcl_ResetResult(interp);
+ }else{
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
/*
** Register commands with the TCL interpreter.
*/
{ "load_static_extension", tclLoadStaticExtensionCmd },
{ "sorter_test_fakeheap", sorter_test_fakeheap },
{ "sorter_test_sort4_helper", sorter_test_sort4_helper },
+ { "sqlite3_transaction_save", testTransactionSave },
+ { "sqlite3_transaction_restore", testTransactionRestore },
};
static int bitmask_size = sizeof(Bitmask)*8;
int i;
return TCL_ERROR;
}
pPager = sqlite3TestTextToPtr(argv[1]);
- rc = sqlite3PagerClose(pPager);
+ rc = sqlite3PagerClose(pPager, 0);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
return rc;
}
+int sqlite3_index_writer(
+ sqlite3 *db,
+ int bDelete,
+ const char *zIndex,
+ sqlite3_stmt **ppStmt,
+ int **paiCol, int *pnCol
+){
+ int rc = SQLITE_OK;
+ Parse *pParse = 0;
+ Index *pIdx = 0; /* The index to write to */
+ Table *pTab;
+ int i; /* Used to iterate through index columns */
+ Vdbe *v = 0;
+ int regRec; /* Register to assemble record in */
+ int *aiCol = 0;
+
+ sqlite3_mutex_enter(db->mutex);
+ sqlite3BtreeEnterAll(db);
+
+ /* Allocate the parse context */
+ pParse = sqlite3StackAllocRaw(db, sizeof(*pParse));
+ if( !pParse ) goto index_writer_out;
+ memset(pParse, 0, sizeof(Parse));
+ pParse->db = db;
+
+ /* Allocate the Vdbe */
+ v = sqlite3GetVdbe(pParse);
+ if( v==0 ) goto index_writer_out;
+
+ /* Find the index to write to */
+ pIdx = sqlite3FindIndex(db, zIndex, "main");
+ if( pIdx==0 ){
+ sqlite3ErrorMsg(pParse, "no such index: %s", zIndex);
+ goto index_writer_out;
+ }
+ pTab = pIdx->pTable;
+
+ /* Populate the two output variables, *pnCol and *pnAiCol. */
+ *pnCol = pIdx->nColumn;
+ *paiCol = aiCol = sqlite3DbMallocZero(db, sizeof(int) * pIdx->nColumn);
+ if( aiCol==0 ){
+ rc = SQLITE_NOMEM;
+ goto index_writer_out;
+ }
+ for(i=0; i<pIdx->nKeyCol; i++){
+ aiCol[i] = pIdx->aiColumn[i];
+ }
+ if( !HasRowid(pTab) ){
+ Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
+ assert( pIdx->nColumn==pIdx->nKeyCol+pPk->nKeyCol );
+ if( pPk==pIdx ){
+ rc = SQLITE_ERROR;
+ goto index_writer_out;
+ }
+ for(i=0; i<pPk->nKeyCol; i++){
+ aiCol[pIdx->nKeyCol+i] = pPk->aiColumn[i];
+ }
+ }else{
+ assert( pIdx->nColumn==pIdx->nKeyCol+1 );
+ aiCol[i] = pTab->iPKey;
+ }
+
+ /* Add an OP_Noop to the VDBE program. Then store a pointer to the
+ ** output array *paiCol as its P4 value. This is so that the array
+ ** is automatically deleted when the user finalizes the statement. The
+ ** OP_Noop serves no other purpose. */
+ sqlite3VdbeAddOp0(v, OP_Noop);
+ sqlite3VdbeChangeP4(v, -1, (const char*)aiCol, P4_INTARRAY);
+
+ sqlite3BeginWriteOperation(pParse, 0, 0);
+
+ /* Open a write cursor on the index */
+ pParse->nTab = 1;
+ sqlite3VdbeAddOp3(v, OP_OpenWrite, 0, pIdx->tnum, 0);
+ sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+
+ /* Create the record to insert into the index. Store it in register regRec. */
+ pParse->nVar = pIdx->nColumn;
+ pParse->nMem = pIdx->nColumn;
+ for(i=1; i<=pIdx->nColumn; i++){
+ sqlite3VdbeAddOp2(v, OP_Variable, i, i);
+ }
+ regRec = ++pParse->nMem;
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, 1, pIdx->nColumn, regRec);
+
+ /* If this is a UNIQUE index, check the constraint. */
+ if( pIdx->onError ){
+ int addr = sqlite3VdbeAddOp4Int(v, OP_NoConflict, 0, 0, 1, pIdx->nKeyCol);
+ sqlite3UniqueConstraint(pParse, SQLITE_ABORT, pIdx);
+ sqlite3VdbeJumpHere(v, addr);
+ }
+
+ /* Code the IdxInsert to write to the b-tree index. */
+ sqlite3VdbeAddOp2(v, OP_IdxInsert, 0, regRec);
+ sqlite3FinishCoding(pParse);
+
+index_writer_out:
+ if( rc==SQLITE_OK && db->mallocFailed==0 ){
+ *ppStmt = (sqlite3_stmt*)v;
+ }else{
+ *ppStmt = 0;
+ if( v ) sqlite3VdbeFinalize(v);
+ }
+
+ sqlite3ParserReset(pParse);
+ sqlite3StackFree(db, pParse);
+ sqlite3BtreeLeaveAll(db);
+ rc = sqlite3ApiExit(db, rc);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
#endif /* #ifndef SQLITE_OMIT_INCRBLOB */
return rc;
}
+static int walFileReadHdr(Wal *pWal, int *pbValid){
+ u8 aBuf[WAL_HDRSIZE]; /* Buffer to load WAL header into */
+ int rc; /* Return code */
+ u32 magic; /* Magic value read from WAL header */
+ int szPage; /* Page size according to the log */
+ u32 version; /* Magic value read from WAL header */
+
+ *pbValid = 0;
+
+ /* Read in the WAL header. */
+ rc = sqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ /* If the database page size is not a power of two, or is greater than
+ ** SQLITE_MAX_PAGE_SIZE, conclude that the WAL file contains no valid
+ ** data. Similarly, if the 'magic' value is invalid, ignore the whole
+ ** WAL file.
+ */
+ magic = sqlite3Get4byte(&aBuf[0]);
+ szPage = sqlite3Get4byte(&aBuf[8]);
+ if( (magic&0xFFFFFFFE)!=WAL_MAGIC
+ || szPage&(szPage-1)
+ || szPage>SQLITE_MAX_PAGE_SIZE
+ || szPage<512
+ ){
+ return SQLITE_OK;
+ }
+
+ pWal->hdr.bigEndCksum = (u8)(magic&0x00000001);
+ pWal->szPage = szPage;
+ pWal->nCkpt = sqlite3Get4byte(&aBuf[12]);
+ memcpy(&pWal->hdr.aSalt, &aBuf[16], 8);
+
+ /* Verify that the WAL header checksum is correct */
+ walChecksumBytes(pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN,
+ aBuf, WAL_HDRSIZE-2*4, 0, pWal->hdr.aFrameCksum
+ );
+ if( pWal->hdr.aFrameCksum[0]!=sqlite3Get4byte(&aBuf[24])
+ || pWal->hdr.aFrameCksum[1]!=sqlite3Get4byte(&aBuf[28])
+ ){
+ return SQLITE_OK;
+ }
+
+ /* Verify that the version number on the WAL format is one that
+ ** are able to understand */
+ version = sqlite3Get4byte(&aBuf[4]);
+ if( version!=WAL_MAX_VERSION ){
+ return SQLITE_CANTOPEN_BKPT;
+ }
+
+ *pbValid = 1;
+ return SQLITE_OK;
+}
+
/*
** Recover the wal-index by reading the write-ahead log file.
}
if( nSize>WAL_HDRSIZE ){
- u8 aBuf[WAL_HDRSIZE]; /* Buffer to load WAL header into */
u8 *aFrame = 0; /* Malloc'd buffer to load entire frame */
int szFrame; /* Number of bytes in buffer aFrame[] */
u8 *aData; /* Pointer to data part of aFrame buffer */
int iFrame; /* Index of last frame read */
i64 iOffset; /* Next offset to read from log file */
int szPage; /* Page size according to the log */
- u32 magic; /* Magic value read from WAL header */
- u32 version; /* Magic value read from WAL header */
int isValid; /* True if this frame is valid */
- /* Read in the WAL header. */
- rc = sqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0);
- if( rc!=SQLITE_OK ){
- goto recovery_error;
- }
-
- /* If the database page size is not a power of two, or is greater than
- ** SQLITE_MAX_PAGE_SIZE, conclude that the WAL file contains no valid
- ** data. Similarly, if the 'magic' value is invalid, ignore the whole
- ** WAL file.
- */
- magic = sqlite3Get4byte(&aBuf[0]);
- szPage = sqlite3Get4byte(&aBuf[8]);
- if( (magic&0xFFFFFFFE)!=WAL_MAGIC
- || szPage&(szPage-1)
- || szPage>SQLITE_MAX_PAGE_SIZE
- || szPage<512
- ){
- goto finished;
- }
- pWal->hdr.bigEndCksum = (u8)(magic&0x00000001);
- pWal->szPage = szPage;
- pWal->nCkpt = sqlite3Get4byte(&aBuf[12]);
- memcpy(&pWal->hdr.aSalt, &aBuf[16], 8);
-
- /* Verify that the WAL header checksum is correct */
- walChecksumBytes(pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN,
- aBuf, WAL_HDRSIZE-2*4, 0, pWal->hdr.aFrameCksum
- );
- if( pWal->hdr.aFrameCksum[0]!=sqlite3Get4byte(&aBuf[24])
- || pWal->hdr.aFrameCksum[1]!=sqlite3Get4byte(&aBuf[28])
- ){
- goto finished;
- }
-
- /* Verify that the version number on the WAL format is one that
- ** are able to understand */
- version = sqlite3Get4byte(&aBuf[4]);
- if( version!=WAL_MAX_VERSION ){
- rc = SQLITE_CANTOPEN_BKPT;
- goto finished;
- }
+ rc = walFileReadHdr(pWal, &isValid);
+ if( rc!=SQLITE_OK ) goto recovery_error;
+ if( isValid==0 ) goto finished;
+ szPage = pWal->szPage;
/* Malloc a buffer to read frames into. */
szFrame = szPage + WAL_FRAME_HDRSIZE;
**
** The EXCLUSIVE lock is not released before returning.
*/
- rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE);
- if( rc==SQLITE_OK ){
- if( pWal->exclusiveMode==WAL_NORMAL_MODE ){
- pWal->exclusiveMode = WAL_EXCLUSIVE_MODE;
- }
- rc = sqlite3WalCheckpoint(
- pWal, SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0
- );
+ if( zBuf ){
+ rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE);
if( rc==SQLITE_OK ){
- int bPersist = -1;
- sqlite3OsFileControlHint(
- pWal->pDbFd, SQLITE_FCNTL_PERSIST_WAL, &bPersist
+ if( pWal->exclusiveMode==WAL_NORMAL_MODE ){
+ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE;
+ }
+ rc = sqlite3WalCheckpoint(
+ pWal, SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0
);
- if( bPersist!=1 ){
- /* Try to delete the WAL file if the checkpoint completed and
- ** fsyned (rc==SQLITE_OK) and if we are not in persistent-wal
- ** mode (!bPersist) */
- isDelete = 1;
- }else if( pWal->mxWalSize>=0 ){
- /* Try to truncate the WAL file to zero bytes if the checkpoint
- ** completed and fsynced (rc==SQLITE_OK) and we are in persistent
- ** WAL mode (bPersist) and if the PRAGMA journal_size_limit is a
- ** non-negative value (pWal->mxWalSize>=0). Note that we truncate
- ** to zero bytes as truncating to the journal_size_limit might
- ** leave a corrupt WAL file on disk. */
- walLimitSize(pWal, 0);
+ if( rc==SQLITE_OK ){
+ int bPersist = -1;
+ sqlite3OsFileControlHint(
+ pWal->pDbFd, SQLITE_FCNTL_PERSIST_WAL, &bPersist
+ );
+ if( bPersist!=1 ){
+ /* Try to delete the WAL file if the checkpoint completed and
+ ** fsyned (rc==SQLITE_OK) and if we are not in persistent-wal
+ ** mode (!bPersist) */
+ isDelete = 1;
+ }else if( pWal->mxWalSize>=0 ){
+ /* Try to truncate the WAL file to zero bytes if the checkpoint
+ ** completed and fsynced (rc==SQLITE_OK) and we are in persistent
+ ** WAL mode (bPersist) and if the PRAGMA journal_size_limit is a
+ ** non-negative value (pWal->mxWalSize>=0). Note that we truncate
+ ** to zero bytes as truncating to the journal_size_limit might
+ ** leave a corrupt WAL file on disk. */
+ walLimitSize(pWal, 0);
+ }
}
}
}
return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE );
}
+/*
+** Save current transaction state.
+**
+** The transaction state consists of a series of 32-bit big-endian integers:
+**
+** * initial number of frames in WAL file.
+** * initial checksum values (2 integers).
+** * current number of frames.
+** * current checksum values (2 integers).
+*/
+int sqlite3WalSaveState(Wal *pWal, void **ppState, int *pnState){
+ int rc = SQLITE_OK;
+
+ *ppState = 0;
+ *pnState = 0;
+ if( pWal->writeLock==0 ){
+ /* Must be in a write transaction to call this function. */
+ rc = SQLITE_ERROR;
+ }else{
+ WalIndexHdr *pOrig = (WalIndexHdr*)walIndexHdr(pWal);
+ int nBuf = 6 * 4; /* Bytes of space to allocate */
+ u8 *aBuf;
+
+ aBuf = sqlite3_malloc(nBuf);
+ if( aBuf==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ sqlite3Put4byte(&aBuf[0], pOrig->mxFrame);
+ sqlite3Put4byte(&aBuf[4], pOrig->aFrameCksum[0]);
+ sqlite3Put4byte(&aBuf[8], pOrig->aFrameCksum[1]);
+ sqlite3Put4byte(&aBuf[12], pWal->hdr.mxFrame);
+ sqlite3Put4byte(&aBuf[16], pWal->hdr.aFrameCksum[0]);
+ sqlite3Put4byte(&aBuf[20], pWal->hdr.aFrameCksum[1]);
+ *ppState = (void*)aBuf;
+ *pnState = nBuf;
+ }
+ }
+
+ return rc;
+}
+
+static int walUndoNoop(void *pUndoCtx, Pgno pgno){
+ UNUSED_PARAMETER(pUndoCtx);
+ UNUSED_PARAMETER(pgno);
+ return SQLITE_OK;
+}
+
+/*
+** If possible, restore the state of the curent transaction to that
+** described by the second and third arguments.
+*/
+int sqlite3WalRestoreState(Wal *pWal, const void *pState, int nState){
+ int rc = SQLITE_OK;
+
+ if( pWal->writeLock==0 ){
+ /* Must have opened a write transaction to call this */
+ rc = SQLITE_ERROR;
+ }else{
+ u8 *aBuf = (u8*)pState;
+ int szFrame; /* Size of each frame in WAL file */
+ u8 *aFrame = 0; /* Buffer to read data into */
+ u8 *aData; /* Data part of aFrame[] buffer */
+ u32 mxFrame; /* Maximum frame following restoration */
+ int i; /* Iterator variable */
+
+ WalIndexHdr *pOrig = (WalIndexHdr*)walIndexHdr(pWal);
+
+ /* Check that no dirty pages have been written to the WAL file since
+ ** the current transaction was opened. */
+ if( pOrig->mxFrame!=pWal->hdr.mxFrame
+ || pOrig->aFrameCksum[0]!=pWal->hdr.aFrameCksum[0]
+ || pOrig->aFrameCksum[1]!=pWal->hdr.aFrameCksum[1]
+ ){
+ rc = SQLITE_ERROR;
+ }
+
+ /* Check that the WAL file is in the same state that it was when the
+ ** transaction was saved. If not, return SQLITE_MISMATCH - cannot
+ ** resume this transaction */
+ if( rc==SQLITE_OK && (
+ pWal->hdr.mxFrame!=sqlite3Get4byte(&aBuf[0])
+ || pWal->hdr.aFrameCksum[0]!=sqlite3Get4byte(&aBuf[4])
+ || pWal->hdr.aFrameCksum[1]!=sqlite3Get4byte(&aBuf[8])
+ )){
+ rc = SQLITE_MISMATCH;
+ }
+
+ if( rc==SQLITE_OK && pWal->readLock==0 ){
+ int cnt = 0;
+ walUnlockShared(pWal, WAL_READ_LOCK(0));
+ pWal->readLock = -1;
+ do{
+ int notUsed;
+ rc = walTryBeginRead(pWal, ¬Used, 1, ++cnt);
+ }while( rc==WAL_RETRY );
+
+ if( rc==SQLITE_OK ){
+ int bValid;
+ rc = walFileReadHdr(pWal, &bValid);
+ if( rc==SQLITE_OK && bValid==0 ) rc = SQLITE_MISMATCH;
+ pWal->hdr.szPage = (u16)((pWal->szPage&0xff00) | (pWal->szPage>>16));
+ }
+ }
+
+ /* Malloc a buffer to read frames into. */
+ if( rc==SQLITE_OK ){
+ szFrame = pWal->szPage + WAL_FRAME_HDRSIZE;
+ aFrame = (u8*)sqlite3_malloc(szFrame);
+ if( !aFrame ){
+ rc = SQLITE_NOMEM;
+ }else{
+ aData = &aFrame[WAL_FRAME_HDRSIZE];
+ }
+ }
+
+ mxFrame = sqlite3Get4byte(&aBuf[12]);
+ for(i=pWal->hdr.mxFrame+1; rc==SQLITE_OK && i<=mxFrame; i++){
+ sqlite3_int64 iOff = walFrameOffset(i, pWal->szPage);
+ rc = sqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOff);
+ if( rc==SQLITE_OK ){
+ u32 iPg;
+ u32 dummy;
+ if( 0==walDecodeFrame(pWal, &iPg, &dummy, aData, aFrame) ){
+ rc = SQLITE_MISMATCH;
+ }else{
+ rc = walIndexAppend(pWal, i, iPg);
+ if( iPg>pWal->hdr.nPage ) pWal->hdr.nPage = iPg;
+ }
+ pWal->hdr.mxFrame = i;
+ }
+ }
+ sqlite3_free(aFrame);
+
+ if( rc==SQLITE_OK ){
+ assert( pWal->hdr.mxFrame==mxFrame );
+ if( pWal->hdr.aFrameCksum[0]!=sqlite3Get4byte(&aBuf[16])
+ || pWal->hdr.aFrameCksum[1]!=sqlite3Get4byte(&aBuf[20])
+ ){
+ rc = SQLITE_MISMATCH;
+ }
+ }
+
+
+ if( rc!=SQLITE_OK ){
+ sqlite3WalUndo(pWal, walUndoNoop, 0);
+ }
+ }
+
+ return rc;
+}
+
#ifdef SQLITE_ENABLE_ZIPVFS
/*
** If the argument is not NULL, it points to a Wal object that holds a
*/
int sqlite3WalHeapMemory(Wal *pWal);
+int sqlite3WalSaveState(Wal *pWal, void **ppState, int *pnState);
+int sqlite3WalRestoreState(Wal *pWal, const void *pState, int nState);
+
#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).