diff options
| author | 2018-07-27 23:51:12 -0400 | |
|---|---|---|
| committer | 2018-07-27 23:51:12 -0400 | |
| commit | feda8a7db8d1d7c5439aa8f8feef7cc0dd2b59a0 (patch) | |
| tree | 1e50f5f666f419143f510d5ded00fe2006b7bd85 /Plugins/DbSqliteWx/rekeyvacuum.c | |
| parent | d9aa870e5d509cc7309ab82dd102a937ab58613a (diff) | |
New upstream version 3.2.1+dfsg1upstream/3.2.1+dfsg1
Diffstat (limited to 'Plugins/DbSqliteWx/rekeyvacuum.c')
| -rw-r--r-- | Plugins/DbSqliteWx/rekeyvacuum.c | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/Plugins/DbSqliteWx/rekeyvacuum.c b/Plugins/DbSqliteWx/rekeyvacuum.c new file mode 100644 index 0000000..1fd9aed --- /dev/null +++ b/Plugins/DbSqliteWx/rekeyvacuum.c @@ -0,0 +1,273 @@ +/* +** 2018-02-24 +** +** 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 an adjusted version of function wx_sqlite3RunVacuum +** to allow reducing or removing reserved page space. +** For this purpose the number of reserved bytes per page for the target +** database is passed as a parameter to the adjusted function. +** +** NOTE: When upgrading to a new version of SQLite3 it is strongly +** recommended to check the original function wx_sqlite3RunVacuum of the +** new version for relevant changes, and to incorporate them in the +** adjusted function below. +** +** The code below is based on SQLite version 3.24.0 - check when updating SQLite. +*/ + +/* +** This routine implements the OP_Vacuum opcode of the VDBE. +*/ +/* CHANGE 1 of 3: Add function parameter nRes */ +SQLITE_PRIVATE int wx_sqlite3RunVacuumForRekey(char **pzErrMsg, wx_sqlite3 *db, int iDb, int nRes){ + int rc = SQLITE_OK; /* Return code from service routines */ + Btree *pMain; /* The database being vacuumed */ + Btree *pTemp; /* The temporary database we vacuum into */ + u16 saved_mDbFlags; /* Saved value of db->mDbFlags */ + u32 saved_flags; /* Saved value of db->flags */ + int saved_nChange; /* Saved value of db->nChange */ + int saved_nTotalChange; /* Saved value of db->nTotalChange */ + u8 saved_mTrace; /* Saved trace settings */ + Db *pDb = 0; /* Database to detach at end of vacuum */ + int isMemDb; /* True if vacuuming a :memory: database */ + /* CHANGE 2 of 3: Do not define local variable nRes */ + /*int nRes;*/ /* Bytes of reserved space at the end of each page */ + int nDb; /* Number of attached databases */ + const char *zDbMain; /* Schema name of database to vacuum */ + + if (!db->autoCommit) { + wx_sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction"); + return SQLITE_ERROR; + } + if (db->nVdbeActive>1) { + wx_sqlite3SetString(pzErrMsg, db, "cannot VACUUM - SQL statements in progress"); + return SQLITE_ERROR; + } + + /* Save the current value of the database flags so that it can be + ** restored before returning. Then set the writable-schema flag, and + ** disable CHECK and foreign key constraints. */ + saved_flags = db->flags; + saved_mDbFlags = db->mDbFlags; + saved_nChange = db->nChange; + saved_nTotalChange = db->nTotalChange; + saved_mTrace = db->mTrace; + db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks; + db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum; + db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_CountRows); + db->mTrace = 0; + + zDbMain = db->aDb[iDb].zDbSName; + pMain = db->aDb[iDb].pBt; + isMemDb = wx_sqlite3PagerIsMemdb(wx_sqlite3BtreePager(pMain)); + + /* Attach the temporary database as 'vacuum_db'. The synchronous pragma + ** can be set to 'off' for this file, as it is not recovered if a crash + ** occurs anyway. The integrity of the database is maintained by a + ** (possibly synchronous) transaction opened on the main database before + ** wx_sqlite3BtreeCopyFile() is called. + ** + ** An optimisation would be to use a non-journaled pager. + ** (Later:) I tried setting "PRAGMA vacuum_db.journal_mode=OFF" but + ** that actually made the VACUUM run slower. Very little journalling + ** actually occurs when doing a vacuum since the vacuum_db is initially + ** empty. Only the journal header is written. Apparently it takes more + ** time to parse and run the PRAGMA to turn journalling off than it does + ** to write the journal header file. + */ + nDb = db->nDb; + rc = execSql(db, pzErrMsg, "ATTACH''AS vacuum_db"); + if (rc != SQLITE_OK) goto end_of_vacuum; + assert((db->nDb - 1) == nDb); + pDb = &db->aDb[nDb]; + assert(strcmp(pDb->zDbSName, "vacuum_db") == 0); + pTemp = pDb->pBt; + + /* The call to execSql() to attach the temp database has left the file + ** locked (as there was more than one active statement when the transaction + ** to read the schema was concluded. Unlock it here so that this doesn't + ** cause problems for the call to BtreeSetPageSize() below. */ + wx_sqlite3BtreeCommit(pTemp); + + /* CHANGE 3 of 3: Do not call wx_sqlite3BtreeGetOptimalReserve */ + /* nRes = wx_sqlite3BtreeGetOptimalReserve(pMain); */ + + /* A VACUUM cannot change the pagesize of an encrypted database. */ +#ifdef SQLITE_HAS_CODEC + if (db->nextPagesize) { + extern void wx_sqlite3CodecGetKey(wx_sqlite3*, int, void**, int*); + int nKey; + char *zKey; + wx_sqlite3CodecGetKey(db, iDb, (void**)&zKey, &nKey); + if (nKey) db->nextPagesize = 0; + } +#endif + + wx_sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size); + wx_sqlite3BtreeSetSpillSize(pTemp, wx_sqlite3BtreeSetSpillSize(pMain, 0)); + wx_sqlite3BtreeSetPagerFlags(pTemp, PAGER_SYNCHRONOUS_OFF | PAGER_CACHESPILL); + + /* Begin a transaction and take an exclusive lock on the main database + ** file. This is done before the wx_sqlite3BtreeGetPageSize(pMain) call below, + ** to ensure that we do not try to change the page-size on a WAL database. + */ + rc = execSql(db, pzErrMsg, "BEGIN"); + if (rc != SQLITE_OK) goto end_of_vacuum; + rc = wx_sqlite3BtreeBeginTrans(pMain, 2); + if (rc != SQLITE_OK) goto end_of_vacuum; + + /* Do not attempt to change the page size for a WAL database */ + if (wx_sqlite3PagerGetJournalMode(wx_sqlite3BtreePager(pMain)) + == PAGER_JOURNALMODE_WAL) { + db->nextPagesize = 0; + } + + if (wx_sqlite3BtreeSetPageSize(pTemp, wx_sqlite3BtreeGetPageSize(pMain), nRes, 0) + || (!isMemDb && wx_sqlite3BtreeSetPageSize(pTemp, db->nextPagesize, nRes, 0)) + || NEVER(db->mallocFailed) + ) { + rc = SQLITE_NOMEM_BKPT; + goto end_of_vacuum; + } + +#ifndef SQLITE_OMIT_AUTOVACUUM + wx_sqlite3BtreeSetAutoVacuum(pTemp, db->nextAutovac >= 0 ? db->nextAutovac : + wx_sqlite3BtreeGetAutoVacuum(pMain)); +#endif + + /* Query the schema of the main database. Create a mirror schema + ** in the temporary database. + */ + db->init.iDb = nDb; /* force new CREATE statements into vacuum_db */ + rc = execSqlF(db, pzErrMsg, + "SELECT sql FROM \"%w\".sqlite_master" + " WHERE type='table'AND name<>'sqlite_sequence'" + " AND coalesce(rootpage,1)>0", + zDbMain + ); + if (rc != SQLITE_OK) goto end_of_vacuum; + rc = execSqlF(db, pzErrMsg, + "SELECT sql FROM \"%w\".sqlite_master" + " WHERE type='index'", + zDbMain + ); + if (rc != SQLITE_OK) goto end_of_vacuum; + db->init.iDb = 0; + + /* Loop through the tables in the main database. For each, do + ** an "INSERT INTO vacuum_db.xxx SELECT * FROM main.xxx;" to copy + ** the contents to the temporary database. + */ + rc = execSqlF(db, pzErrMsg, + "SELECT'INSERT INTO vacuum_db.'||quote(name)" + "||' SELECT*FROM\"%w\".'||quote(name)" + "FROM vacuum_db.sqlite_master " + "WHERE type='table'AND coalesce(rootpage,1)>0", + zDbMain + ); + assert((db->mDbFlags & DBFLAG_Vacuum) != 0); + db->mDbFlags &= ~DBFLAG_Vacuum; + if (rc != SQLITE_OK) goto end_of_vacuum; + + /* Copy the triggers, views, and virtual tables from the main database + ** over to the temporary database. None of these objects has any + ** associated storage, so all we have to do is copy their entries + ** from the SQLITE_MASTER table. + */ + rc = execSqlF(db, pzErrMsg, + "INSERT INTO vacuum_db.sqlite_master" + " SELECT*FROM \"%w\".sqlite_master" + " WHERE type IN('view','trigger')" + " OR(type='table'AND rootpage=0)", + zDbMain + ); + if (rc) goto end_of_vacuum; + + /* At this point, there is a write transaction open on both the + ** vacuum database and the main database. Assuming no error occurs, + ** both transactions are closed by this block - the main database + ** transaction by wx_sqlite3BtreeCopyFile() and the other by an explicit + ** call to wx_sqlite3BtreeCommit(). + */ + { + u32 meta; + int i; + + /* This array determines which meta meta values are preserved in the + ** vacuum. Even entries are the meta value number and odd entries + ** are an increment to apply to the meta value after the vacuum. + ** The increment is used to increase the schema cookie so that other + ** connections to the same database will know to reread the schema. + */ + static const unsigned char aCopy[] = { + BTREE_SCHEMA_VERSION, 1, /* Add one to the old schema cookie */ + BTREE_DEFAULT_CACHE_SIZE, 0, /* Preserve the default page cache size */ + BTREE_TEXT_ENCODING, 0, /* Preserve the text encoding */ + BTREE_USER_VERSION, 0, /* Preserve the user version */ + BTREE_APPLICATION_ID, 0, /* Preserve the application id */ + }; + + assert(1 == wx_sqlite3BtreeIsInTrans(pTemp)); + assert(1 == wx_sqlite3BtreeIsInTrans(pMain)); + + /* Copy Btree meta values */ + for (i = 0; i<ArraySize(aCopy); i += 2) { + /* GetMeta() and UpdateMeta() cannot fail in this context because + ** we already have page 1 loaded into cache and marked dirty. */ + wx_sqlite3BtreeGetMeta(pMain, aCopy[i], &meta); + rc = wx_sqlite3BtreeUpdateMeta(pTemp, aCopy[i], meta + aCopy[i + 1]); + if (NEVER(rc != SQLITE_OK)) goto end_of_vacuum; + } + + rc = wx_sqlite3BtreeCopyFile(pMain, pTemp); + if (rc != SQLITE_OK) goto end_of_vacuum; + rc = wx_sqlite3BtreeCommit(pTemp); + if (rc != SQLITE_OK) goto end_of_vacuum; +#ifndef SQLITE_OMIT_AUTOVACUUM + wx_sqlite3BtreeSetAutoVacuum(pMain, wx_sqlite3BtreeGetAutoVacuum(pTemp)); +#endif + } + + assert(rc == SQLITE_OK); + rc = wx_sqlite3BtreeSetPageSize(pMain, wx_sqlite3BtreeGetPageSize(pTemp), nRes, 1); + +end_of_vacuum: + /* Restore the original value of db->flags */ + db->init.iDb = 0; + db->mDbFlags = saved_mDbFlags; + db->flags = saved_flags; + db->nChange = saved_nChange; + db->nTotalChange = saved_nTotalChange; + db->mTrace = saved_mTrace; + wx_sqlite3BtreeSetPageSize(pMain, -1, -1, 1); + + /* Currently there is an SQL level transaction open on the vacuum + ** database. No locks are held on any other files (since the main file + ** was committed at the btree level). So it safe to end the transaction + ** by manually setting the autoCommit flag to true and detaching the + ** vacuum database. The vacuum_db journal file is deleted when the pager + ** is closed by the DETACH. + */ + db->autoCommit = 1; + + if (pDb) { + wx_sqlite3BtreeClose(pDb->pBt); + pDb->pBt = 0; + pDb->pSchema = 0; + } + + /* This both clears the schemas and reduces the size of the db->aDb[] + ** array. */ + wx_sqlite3ResetAllSchemasOfConnection(db); + + return rc; +} + |
