aboutsummaryrefslogtreecommitdiffstats
path: root/Plugins/DbSqliteWx/rekeyvacuum.c
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@ubuntu.com>2018-07-27 23:51:12 -0400
committerLibravatarUnit 193 <unit193@ubuntu.com>2018-07-27 23:51:12 -0400
commitfeda8a7db8d1d7c5439aa8f8feef7cc0dd2b59a0 (patch)
tree1e50f5f666f419143f510d5ded00fe2006b7bd85 /Plugins/DbSqliteWx/rekeyvacuum.c
parentd9aa870e5d509cc7309ab82dd102a937ab58613a (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.c273
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;
+}
+