1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-08 14:02:16 +03:00

Add an experimental extension for applying bulk updates to databases.

FossilOrigin-Name: 2954ab501049968430011b63d046eb42ff37a56c
This commit is contained in:
dan
2014-09-02 19:59:40 +00:00
parent f8ede57a61
commit b0083756f2
22 changed files with 1878 additions and 102 deletions

181
ext/ota/ota1.test Normal file
View File

@@ -0,0 +1,181 @@
# 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

62
ext/ota/ota2.test Normal file
View File

@@ -0,0 +1,62 @@
# 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

811
ext/ota/sqlite3ota.c Normal file
View File

@@ -0,0 +1,811 @@
/*
** 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 */

212
ext/ota/sqlite3ota.h Normal file
View File

@@ -0,0 +1,212 @@
/*
** 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 */

View File

@@ -331,7 +331,8 @@ TESTSRC2 = \
$(TOP)/ext/fts3/fts3_expr.c \ $(TOP)/ext/fts3/fts3_expr.c \
$(TOP)/ext/fts3/fts3_tokenizer.c \ $(TOP)/ext/fts3/fts3_tokenizer.c \
$(TOP)/ext/fts3/fts3_write.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. # Header files used by all library source files.
# #

View File

@@ -1,5 +1,5 @@
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. C Add\san\sexperimental\sextension\sfor\sapplying\sbulk\supdates\sto\sdatabases.
D 2014-09-01T23:06:44.401 D 2014-09-02T19:59:40.729
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in cf57f673d77606ab0f2d9627ca52a9ba1464146a F Makefile.in cf57f673d77606ab0f2d9627ca52a9ba1464146a
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -121,6 +121,10 @@ F ext/misc/totype.c 4a167594e791abeed95e0a8db028822b5e8fe512
F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95 F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95
F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e
F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212 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/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
F ext/rtree/rtree.c 57bec53e1a677ab74217fe1f20a58c3a47261d6b F ext/rtree/rtree.c 57bec53e1a677ab74217fe1f20a58c3a47261d6b
F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e
@@ -147,7 +151,7 @@ F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
F main.mk 9b5ccf1097050b1f16681f7d4beeea4f7f7ac2c3 F main.mk 566c36f247d19525264e584466b5f07c0a48302e
F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5 F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
@@ -168,7 +172,7 @@ F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34
F src/backup.c a31809c65623cc41849b94d368917f8bb66e6a7e F src/backup.c a31809c65623cc41849b94d368917f8bb66e6a7e
F src/bitvec.c 19a4ba637bd85f8f63fc8c9bae5ade9fb05ec1cb F src/bitvec.c 19a4ba637bd85f8f63fc8c9bae5ade9fb05ec1cb
F src/btmutex.c ec9d3f1295dafeb278c3830211cc5584132468f4 F src/btmutex.c ec9d3f1295dafeb278c3830211cc5584132468f4
F src/btree.c 2a483a8045118faa99867a8679da42754b532318 F src/btree.c c46043fbb09c18a19bdb96eadde6e724901d6fcf
F src/btree.h a79aa6a71e7f1055f01052b7f821bd1c2dce95c8 F src/btree.h a79aa6a71e7f1055f01052b7f821bd1c2dce95c8
F src/btreeInt.h cf180d86b2e9e418f638d65baa425c4c69c0e0e3 F src/btreeInt.h cf180d86b2e9e418f638d65baa425c4c69c0e0e3
F src/build.c c26b233dcdb1e2c8f468d49236c266f9f3de96d8 F src/build.c c26b233dcdb1e2c8f468d49236c266f9f3de96d8
@@ -185,12 +189,12 @@ F src/global.c 1e4bd956dc2f608f87d2a929abc4a20db65f30e4
F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5 F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5
F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094 F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08 F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
F src/insert.c d1a104e67b33314d4cc5c1356147446086ab9fc8 F src/insert.c 62b0ceab1720dc74ed1fbcf953224132245704d8
F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
F src/legacy.c 87c92f4a08e2f70220e3b22a9c3b2482d36a134a F src/legacy.c 87c92f4a08e2f70220e3b22a9c3b2482d36a134a
F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b
F src/loadext.c 31c2122b7dd05a179049bbf163fd4839f181cbab F src/loadext.c 31c2122b7dd05a179049bbf163fd4839f181cbab
F src/main.c d2ef03a45552e11813c68326d5edfda992e319d4 F src/main.c c9802dc99c019fbba516202300d56be2c478fa93
F src/malloc.c 954de5f998c23237e04474a3f2159bf483bba65a F src/malloc.c 954de5f998c23237e04474a3f2159bf483bba65a
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c c0c990fcaddff810ea277b4fb5d9138603dd5d4b F src/mem1.c c0c990fcaddff810ea277b4fb5d9138603dd5d4b
@@ -211,30 +215,30 @@ F src/os_setup.h c9d4553b5aaa6f73391448b265b89bed0b890faa
F src/os_unix.c 8525ca79457c5b4673a5fda2774ee39fe155f40f F src/os_unix.c 8525ca79457c5b4673a5fda2774ee39fe155f40f
F src/os_win.c 2aa8aa7780d7cf03e912d2088ab2ec5c32f33dc5 F src/os_win.c 2aa8aa7780d7cf03e912d2088ab2ec5c32f33dc5
F src/os_win.h 09e751b20bbc107ffbd46e13555dc73576d88e21 F src/os_win.h 09e751b20bbc107ffbd46e13555dc73576d88e21
F src/pager.c 3e732d2bbdd8d8d95fed0c5ae7e718d73153c4c5 F src/pager.c a3caa08db8227c5a32f388be67f33d8cb44d5e35
F src/pager.h ffd5607f7b3e4590b415b007a4382f693334d428 F src/pager.h 1acd367a0ffb63026b0461ea5eaeeb8046414a71
F src/parse.y 22d6a074e5f5a7258947a1dc55a9bf946b765dd0 F src/parse.y 22d6a074e5f5a7258947a1dc55a9bf946b765dd0
F src/pcache.c 3b3791297e8977002e56b4a9b8916f2039abad9b F src/pcache.c 3b3791297e8977002e56b4a9b8916f2039abad9b
F src/pcache.h 9b559127b83f84ff76d735c8262f04853be0c59a F src/pcache.h 9b559127b83f84ff76d735c8262f04853be0c59a
F src/pcache1.c c5af6403a55178c9d1c09e4f77b0f9c88822762c F src/pcache1.c c5af6403a55178c9d1c09e4f77b0f9c88822762c
F src/pragma.c 14bcdb504128a476cce5bbc086d5226c5e46c225 F src/pragma.c d252459fb3ce19448d1a2f41000c780fac4c0c26
F src/prepare.c 3842c1dfc0b053458e3adcf9f6efc48e03e3fe3d F src/prepare.c 314961aa6650cc860394cb2f31931cf2de93baa8
F src/printf.c 00986c86ddfffefc2fd3c73667ff51b3b9709c74 F src/printf.c 00986c86ddfffefc2fd3c73667ff51b3b9709c74
F src/random.c d10c1f85b6709ca97278428fd5db5bbb9c74eece F src/random.c d10c1f85b6709ca97278428fd5db5bbb9c74eece
F src/resolve.c 0ea356d32a5e884add23d1b9b4e8736681dd5697 F src/resolve.c 0ea356d32a5e884add23d1b9b4e8736681dd5697
F src/rowset.c a9c9aae3234b44a6d7c6f5a3cadf90dce1e627be F src/rowset.c a9c9aae3234b44a6d7c6f5a3cadf90dce1e627be
F src/select.c 89e569b263535662f54b537eb9118b2c554ae7aa F src/select.c 89e569b263535662f54b537eb9118b2c554ae7aa
F src/shell.c 713cef4d73c05fc8e12f4960072329d767a05d50 F src/shell.c 713cef4d73c05fc8e12f4960072329d767a05d50
F src/sqlite.h.in 43852c8b68b4c579948cb37182918078836c5c06 F src/sqlite.h.in 706b420dc3390532435a3bd196360a940805f02f
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc
F src/sqliteInt.h 6244ee9052752e26d1275ab20c9b774385aa57d2 F src/sqliteInt.h 7c090825333d91ca392c2479a9e835e7b6a5eb12
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158 F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
F src/tclsqlite.c 7d100e2e7aad614bb3d7026a41a0e3827dbaaebc F src/tclsqlite.c 29357f2be7b0d00e8ea900eaf727e0c5ffeaa660
F src/test1.c 363a5089230a92cf0aaa7a2945da7f2bf3b0a8d3 F src/test1.c e9a0e5804b078532e69e69ec14c8326bf2cfc318
F src/test2.c 98049e51a17dc62606a99a9eb95ee477f9996712 F src/test2.c 84f6a786aa7ffa12fff83acb52660e337ffe642a
F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c
F src/test4.c 9b32d22f5f150abe23c1830e2057c4037c45b3df F src/test4.c 9b32d22f5f150abe23c1830e2057c4037c45b3df
F src/test5.c 5a34feec76d9b3a86aab30fd4f6cc9c48cbab4c1 F src/test5.c 5a34feec76d9b3a86aab30fd4f6cc9c48cbab4c1
@@ -290,13 +294,13 @@ F src/vdbe.h c63fad052c9e7388d551e556e119c0bcf6bebdf8
F src/vdbeInt.h cdc8e421f85beb1ac9b4669ec5beadab6faa15e0 F src/vdbeInt.h cdc8e421f85beb1ac9b4669ec5beadab6faa15e0
F src/vdbeapi.c 09677a53dd8c71bcd670b0bd073bb9aefa02b441 F src/vdbeapi.c 09677a53dd8c71bcd670b0bd073bb9aefa02b441
F src/vdbeaux.c cef5d34a64ae3a65b56d96d3fd663246ec8e1c36 F src/vdbeaux.c cef5d34a64ae3a65b56d96d3fd663246ec8e1c36
F src/vdbeblob.c 848238dc73e93e48432991bb5651bf87d865eca4 F src/vdbeblob.c 0bc9d22578d87ad9ff1c16e20a36863326f34fd7
F src/vdbemem.c 921d5468a68ac06f369810992e84ca22cc730a62 F src/vdbemem.c 921d5468a68ac06f369810992e84ca22cc730a62
F src/vdbesort.c 02646a9f86421776ae5d7594f620f9ed669d3698 F src/vdbesort.c 02646a9f86421776ae5d7594f620f9ed669d3698
F src/vdbetrace.c 6f52bc0c51e144b7efdcfb2a8f771167a8816767 F src/vdbetrace.c 6f52bc0c51e144b7efdcfb2a8f771167a8816767
F src/vtab.c 019dbfd0406a7447c990e1f7bd1dfcdb8895697f F src/vtab.c 019dbfd0406a7447c990e1f7bd1dfcdb8895697f
F src/wal.c 264df50a1b33124130b23180ded2e2c5663c652a F src/wal.c 93b4fcb56a98f435a2cb66024bb2b12d66d1ff53
F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4 F src/wal.h 237bc4484f7c289f094ecb6efb2b6c02005484e1
F src/walker.c 11edb74d587bc87b33ca96a5173e3ec1b8389e45 F src/walker.c 11edb74d587bc87b33ca96a5173e3ec1b8389e45
F src/where.c d9eae96b2cbbe4842eac3ee156ccd1b933d802c4 F src/where.c d9eae96b2cbbe4842eac3ee156ccd1b933d802c4
F src/whereInt.h 923820bee9726033a501a08d2fc69b9c1ee4feb3 F src/whereInt.h 923820bee9726033a501a08d2fc69b9c1ee4feb3
@@ -1193,7 +1197,10 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P 4cae93f8ae8fb3fe38fd5dc7d3a5ea0d11552841 P 9779c7a9eb1e2bd36e9286331a9314f064014d80
R fe37d23fb1f96547febf2ff7c0ba49b9 R 5df4197c767caad19feca7391f16a42d
U drh T *branch * experimental-bulk-update
Z 29e2a2847ff584a00093c5b89ffca704 T *sym-experimental-bulk-update *
T -sym-trunk *
U dan
Z 195de077202fec58ba90fc725d7305e7

View File

@@ -1 +1 @@
9779c7a9eb1e2bd36e9286331a9314f064014d80 2954ab501049968430011b63d046eb42ff37a56c

View File

@@ -151,7 +151,8 @@ static int hasSharedCacheTableLock(
** and has the read-uncommitted flag set, then no lock is required. ** and has the read-uncommitted flag set, then no lock is required.
** Return true immediately. ** Return true immediately.
*/ */
if( (pBtree->sharable==0) if( (pBtree->db->flags & SQLITE_OtaMode)
|| (pBtree->sharable==0)
|| (eLockType==READ_LOCK && (pBtree->db->flags & SQLITE_ReadUncommitted)) || (eLockType==READ_LOCK && (pBtree->db->flags & SQLITE_ReadUncommitted))
){ ){
return 1; return 1;
@@ -2045,7 +2046,7 @@ int sqlite3BtreeOpen(
btree_open_out: btree_open_out:
if( rc!=SQLITE_OK ){ if( rc!=SQLITE_OK ){
if( pBt && pBt->pPager ){ if( pBt && pBt->pPager ){
sqlite3PagerClose(pBt->pPager); sqlite3PagerClose(pBt->pPager, 0);
} }
sqlite3_free(pBt); sqlite3_free(pBt);
sqlite3_free(p); sqlite3_free(p);
@@ -2174,7 +2175,7 @@ int sqlite3BtreeClose(Btree *p){
** Clean out and delete the BtShared object. ** Clean out and delete the BtShared object.
*/ */
assert( !pBt->pCursor ); assert( !pBt->pCursor );
sqlite3PagerClose(pBt->pPager); sqlite3PagerClose(pBt->pPager, (p->db->flags & SQLITE_OtaMode)!=0);
if( pBt->xFreeSchema && pBt->pSchema ){ if( pBt->xFreeSchema && pBt->pSchema ){
pBt->xFreeSchema(pBt->pSchema); pBt->xFreeSchema(pBt->pSchema);
} }

View File

@@ -1365,6 +1365,10 @@ void sqlite3GenerateConstraintChecks(
int iThisCur; /* Cursor for this UNIQUE index */ int iThisCur; /* Cursor for this UNIQUE index */
int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */ 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( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */
if( bAffinityDone==0 ){ if( bAffinityDone==0 ){
sqlite3TableAffinity(v, pTab, regNewData+1); sqlite3TableAffinity(v, pTab, regNewData+1);
@@ -1556,6 +1560,15 @@ void sqlite3CompleteInsertion(
assert( pTab->pSelect==0 ); /* This table is not a VIEW */ assert( pTab->pSelect==0 ); /* This table is not a VIEW */
for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
if( aRegIdx[i]==0 ) continue; 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; bAffinityDone = 1;
if( pIdx->pPartIdxWhere ){ if( pIdx->pPartIdxWhere ){
sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2); sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2);

View File

@@ -3470,3 +3470,15 @@ int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){
Btree *pBt = sqlite3DbNameToBtree(db, zDbName); Btree *pBt = sqlite3DbNameToBtree(db, zDbName);
return pBt ? sqlite3BtreeIsReadonly(pBt) : -1; 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);
}

View File

@@ -3947,7 +3947,7 @@ static void pagerFreeMapHdrs(Pager *pPager){
** a hot journal may be left in the filesystem but no error is returned ** a hot journal may be left in the filesystem but no error is returned
** to the caller. ** to the caller.
*/ */
int sqlite3PagerClose(Pager *pPager){ int sqlite3PagerClose(Pager *pPager, int bOtaMode){
u8 *pTmp = (u8 *)pPager->pTmpSpace; u8 *pTmp = (u8 *)pPager->pTmpSpace;
assert( assert_pager_state(pPager) ); assert( assert_pager_state(pPager) );
@@ -3957,7 +3957,9 @@ int sqlite3PagerClose(Pager *pPager){
/* pPager->errCode = 0; */ /* pPager->errCode = 0; */
pPager->exclusiveMode = 0; pPager->exclusiveMode = 0;
#ifndef SQLITE_OMIT_WAL #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; pPager->pWal = 0;
#endif #endif
pager_reset(pPager); pager_reset(pPager);
@@ -7217,6 +7219,41 @@ int sqlite3PagerCloseWal(Pager *pPager){
return rc; 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 */ #endif /* !SQLITE_OMIT_WAL */
#ifdef SQLITE_ENABLE_ZIPVFS #ifdef SQLITE_ENABLE_ZIPVFS

View File

@@ -112,7 +112,7 @@ int sqlite3PagerOpen(
int, int,
void(*)(DbPage*) void(*)(DbPage*)
); );
int sqlite3PagerClose(Pager *pPager); int sqlite3PagerClose(Pager *pPager, int);
int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); int sqlite3PagerReadFileheader(Pager*, int, unsigned char*);
/* Functions used to configure a Pager object. */ /* Functions used to configure a Pager object. */
@@ -207,4 +207,7 @@ void *sqlite3PagerCodec(DbPage *);
# define enable_simulated_io_errors() # define enable_simulated_io_errors()
#endif #endif
int sqlite3PagerSaveState(Pager *pPager, void **ppState, int *pnState);
int sqlite3PagerRestoreState(Pager *pPager, const void *pState, int nState);
#endif /* _PAGER_H_ */ #endif /* _PAGER_H_ */

View File

@@ -308,6 +308,12 @@ static const struct sPragmaNames {
/* ePragTyp: */ PragTyp_MMAP_SIZE, /* ePragTyp: */ PragTyp_MMAP_SIZE,
/* ePragFlag: */ 0, /* ePragFlag: */ 0,
/* iArg: */ 0 }, /* iArg: */ 0 },
#endif
{ /* zName: */ "ota_mode",
/* ePragTyp: */ PragTyp_FLAG,
/* ePragFlag: */ 0,
/* iArg: */ SQLITE_OtaMode },
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
{ /* zName: */ "page_count", { /* zName: */ "page_count",
/* ePragTyp: */ PragTyp_PAGE_COUNT, /* ePragTyp: */ PragTyp_PAGE_COUNT,
/* ePragFlag: */ PragFlag_NeedSchema, /* ePragFlag: */ PragFlag_NeedSchema,
@@ -1466,9 +1472,13 @@ void sqlite3Pragma(
k = 0; k = 0;
}else if( pPk==0 ){ }else if( pPk==0 ){
k = 1; k = 1;
}else{
if( (db->flags & SQLITE_OtaMode) && HasRowid(pTab) ){
k = 0;
}else{ }else{
for(k=1; ALWAYS(k<=pTab->nCol) && pPk->aiColumn[k-1]!=i; k++){} for(k=1; ALWAYS(k<=pTab->nCol) && pPk->aiColumn[k-1]!=i; k++){}
} }
}
sqlite3VdbeAddOp2(v, OP_Integer, k, 6); sqlite3VdbeAddOp2(v, OP_Integer, k, 6);
sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6);
} }

View File

@@ -796,7 +796,6 @@ int sqlite3_prepare_v2(
return rc; return rc;
} }
#ifndef SQLITE_OMIT_UTF16 #ifndef SQLITE_OMIT_UTF16
/* /*
** Compile the UTF-16 encoded SQL statement zSql into a statement handle. ** Compile the UTF-16 encoded SQL statement zSql into a statement handle.

View File

@@ -7363,7 +7363,79 @@ int sqlite3_vtab_on_conflict(sqlite3 *);
/* #define SQLITE_ABORT 4 // Also an error code */ /* #define SQLITE_ABORT 4 // Also an error code */
#define SQLITE_REPLACE 5 #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 ** Undo the hack that converts floating point types to integer for

View File

@@ -1141,6 +1141,8 @@ struct sqlite3 {
#define SQLITE_QueryOnly 0x02000000 /* Disable database changes */ #define SQLITE_QueryOnly 0x02000000 /* Disable database changes */
#define SQLITE_VdbeEQP 0x04000000 /* Debug EXPLAIN QUERY PLAN */ #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 ** Bits of the sqlite3.dbOptFlags field that are used by the
@@ -3726,6 +3728,7 @@ SQLITE_EXTERN void (*sqlite3IoTrace)(const char*,...);
#define MEMTYPE_PCACHE 0x08 /* Page cache allocations */ #define MEMTYPE_PCACHE 0x08 /* Page cache allocations */
#define MEMTYPE_DB 0x10 /* Uses sqlite3DbMalloc, not sqlite_malloc */ #define MEMTYPE_DB 0x10 /* Uses sqlite3DbMalloc, not sqlite_malloc */
/* /*
** Threading interface ** Threading interface
*/ */

View File

@@ -3699,6 +3699,8 @@ static void init_all(Tcl_Interp *interp){
extern int SqliteSuperlock_Init(Tcl_Interp*); extern int SqliteSuperlock_Init(Tcl_Interp*);
extern int SqlitetestSyscall_Init(Tcl_Interp*); extern int SqlitetestSyscall_Init(Tcl_Interp*);
extern int SqliteOta_Init(Tcl_Interp*);
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
extern int Sqlitetestfts3_Init(Tcl_Interp *interp); extern int Sqlitetestfts3_Init(Tcl_Interp *interp);
#endif #endif
@@ -3740,6 +3742,7 @@ static void init_all(Tcl_Interp *interp){
Sqlitemultiplex_Init(interp); Sqlitemultiplex_Init(interp);
SqliteSuperlock_Init(interp); SqliteSuperlock_Init(interp);
SqlitetestSyscall_Init(interp); SqlitetestSyscall_Init(interp);
SqliteOta_Init(interp);
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
Sqlitetestfts3_Init(interp); Sqlitetestfts3_Init(interp);

View File

@@ -6497,6 +6497,70 @@ static int sorter_test_sort4_helper(
} }
/*
** 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. ** Register commands with the TCL interpreter.
*/ */
@@ -6734,6 +6798,8 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "load_static_extension", tclLoadStaticExtensionCmd }, { "load_static_extension", tclLoadStaticExtensionCmd },
{ "sorter_test_fakeheap", sorter_test_fakeheap }, { "sorter_test_fakeheap", sorter_test_fakeheap },
{ "sorter_test_sort4_helper", sorter_test_sort4_helper }, { "sorter_test_sort4_helper", sorter_test_sort4_helper },
{ "sqlite3_transaction_save", testTransactionSave },
{ "sqlite3_transaction_restore", testTransactionRestore },
}; };
static int bitmask_size = sizeof(Bitmask)*8; static int bitmask_size = sizeof(Bitmask)*8;
int i; int i;

View File

@@ -89,7 +89,7 @@ static int pager_close(
return TCL_ERROR; return TCL_ERROR;
} }
pPager = sqlite3TestTextToPtr(argv[1]); pPager = sqlite3TestTextToPtr(argv[1]);
rc = sqlite3PagerClose(pPager); rc = sqlite3PagerClose(pPager, 0);
if( rc!=SQLITE_OK ){ if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0); Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR; return TCL_ERROR;

View File

@@ -463,4 +463,116 @@ int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
return rc; 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 */ #endif /* #ifndef SQLITE_OMIT_INCRBLOB */

258
src/wal.c
View File

@@ -1046,6 +1046,62 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){
return rc; 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. ** Recover the wal-index by reading the write-ahead log file.
@@ -1090,59 +1146,18 @@ static int walIndexRecover(Wal *pWal){
} }
if( nSize>WAL_HDRSIZE ){ if( nSize>WAL_HDRSIZE ){
u8 aBuf[WAL_HDRSIZE]; /* Buffer to load WAL header into */
u8 *aFrame = 0; /* Malloc'd buffer to load entire frame */ u8 *aFrame = 0; /* Malloc'd buffer to load entire frame */
int szFrame; /* Number of bytes in buffer aFrame[] */ int szFrame; /* Number of bytes in buffer aFrame[] */
u8 *aData; /* Pointer to data part of aFrame buffer */ u8 *aData; /* Pointer to data part of aFrame buffer */
int iFrame; /* Index of last frame read */ int iFrame; /* Index of last frame read */
i64 iOffset; /* Next offset to read from log file */ i64 iOffset; /* Next offset to read from log file */
int szPage; /* Page size according to the log */ 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 */ int isValid; /* True if this frame is valid */
/* Read in the WAL header. */ rc = walFileReadHdr(pWal, &isValid);
rc = sqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0); if( rc!=SQLITE_OK ) goto recovery_error;
if( rc!=SQLITE_OK ){ if( isValid==0 ) goto finished;
goto recovery_error; szPage = pWal->szPage;
}
/* 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;
}
/* Malloc a buffer to read frames into. */ /* Malloc a buffer to read frames into. */
szFrame = szPage + WAL_FRAME_HDRSIZE; szFrame = szPage + WAL_FRAME_HDRSIZE;
@@ -1837,6 +1852,7 @@ int sqlite3WalClose(
** **
** The EXCLUSIVE lock is not released before returning. ** The EXCLUSIVE lock is not released before returning.
*/ */
if( zBuf ){
rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE); rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE);
if( rc==SQLITE_OK ){ if( rc==SQLITE_OK ){
if( pWal->exclusiveMode==WAL_NORMAL_MODE ){ if( pWal->exclusiveMode==WAL_NORMAL_MODE ){
@@ -1866,6 +1882,7 @@ int sqlite3WalClose(
} }
} }
} }
}
walIndexClose(pWal, isDelete); walIndexClose(pWal, isDelete);
sqlite3OsClose(pWal->pWalFd); sqlite3OsClose(pWal->pWalFd);
@@ -3079,6 +3096,157 @@ int sqlite3WalHeapMemory(Wal *pWal){
return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ); 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, &notUsed, 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 #ifdef SQLITE_ENABLE_ZIPVFS
/* /*
** If the argument is not NULL, it points to a Wal object that holds a ** If the argument is not NULL, it points to a Wal object that holds a

View File

@@ -126,6 +126,9 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op);
*/ */
int sqlite3WalHeapMemory(Wal *pWal); 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 #ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content /* 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). ** stored in each frame (i.e. the db page-size when the WAL was created).