Add tests for the new code on this branch.
[sqlite.git] / tool / fast_vacuum.c
blob5ca0271dc950b2a91bdf978646bf6a30e3ce8a26
1 /*
2 ** 2013-10-01
3 **
4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
6 **
7 ** May you do good and not evil.
8 ** May you find forgiveness for yourself and forgive others.
9 ** May you share freely, never taking more than you give.
11 *************************************************************************
13 ** This program implements a high-speed version of the VACUUM command.
14 ** It repacks an SQLite database to remove as much unused space as
15 ** possible and to relocate content sequentially in the file.
17 ** This program runs faster and uses less temporary disk space than the
18 ** built-in VACUUM command. On the other hand, this program has a number
19 ** of important restrictions relative to the built-in VACUUM command.
21 ** (1) The caller must ensure that no other processes are accessing the
22 ** database file while the vacuum is taking place. The usual SQLite
23 ** file locking is insufficient for this. The caller must use
24 ** external means to make sure only this one routine is reading and
25 ** writing the database.
27 ** (2) Database reconfiguration such as page size or auto_vacuum changes
28 ** are not supported by this utility.
30 ** (3) The database file might be renamed if a power loss or crash
31 ** occurs at just the wrong moment. Recovery must be prepared to
32 ** to deal with the possibly changed filename.
34 ** This program is intended as a *Demonstration Only*. The intent of this
35 ** program is to provide example code that application developers can use
36 ** when creating similar functionality in their applications.
38 ** To compile this program:
40 ** cc fast_vacuum.c sqlite3.c
42 ** Add whatever linker options are required. (Example: "-ldl -lpthread").
43 ** Then to run the program:
45 ** ./a.out file-to-vacuum
48 #include "sqlite3.h"
49 #include <stdio.h>
50 #include <stdlib.h>
53 ** Finalize a prepared statement. If an error has occurred, print the
54 ** error message and exit.
56 static void vacuumFinalize(sqlite3_stmt *pStmt){
57 sqlite3 *db = sqlite3_db_handle(pStmt);
58 int rc = sqlite3_finalize(pStmt);
59 if( rc ){
60 fprintf(stderr, "finalize error: %s\n", sqlite3_errmsg(db));
61 exit(1);
66 ** Execute zSql on database db. The SQL text is printed to standard
67 ** output. If an error occurs, print an error message and exit the
68 ** process.
70 static void execSql(sqlite3 *db, const char *zSql){
71 sqlite3_stmt *pStmt;
72 if( !zSql ){
73 fprintf(stderr, "out of memory!\n");
74 exit(1);
76 printf("%s;\n", zSql);
77 if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){
78 fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
79 exit(1);
81 sqlite3_step(pStmt);
82 vacuumFinalize(pStmt);
86 ** Execute zSql on database db. The zSql statement returns exactly
87 ** one column. Execute this return value as SQL on the same database.
89 ** The zSql statement is printed on standard output prior to being
90 ** run. If any errors occur, an error is printed and the process
91 ** exits.
93 static void execExecSql(sqlite3 *db, const char *zSql){
94 sqlite3_stmt *pStmt;
95 int rc;
97 printf("%s;\n", zSql);
98 rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
99 if( rc!=SQLITE_OK ){
100 fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
101 exit(1);
103 while( SQLITE_ROW==sqlite3_step(pStmt) ){
104 execSql(db, (char*)sqlite3_column_text(pStmt, 0));
106 vacuumFinalize(pStmt);
110 int main(int argc, char **argv){
111 sqlite3 *db; /* Connection to the database file */
112 int rc; /* Return code from SQLite interface calls */
113 sqlite3_uint64 r; /* A random number */
114 const char *zDbToVacuum; /* Database to be vacuumed */
115 char *zBackupDb; /* Backup copy of the original database */
116 char *zTempDb; /* Temporary database */
117 char *zSql; /* An SQL statement */
119 if( argc!=2 ){
120 fprintf(stderr, "Usage: %s DATABASE\n", argv[0]);
121 return 1;
124 /* Identify the database file to be vacuumed and open it.
126 zDbToVacuum = argv[1];
127 printf("-- open database file \"%s\"\n", zDbToVacuum);
128 rc = sqlite3_open(zDbToVacuum, &db);
129 if( rc ){
130 fprintf(stderr, "%s: %s\n", zDbToVacuum, sqlite3_errstr(rc));
131 return 1;
134 /* Create names for two other files. zTempDb will be a new database
135 ** into which we construct a vacuumed copy of zDbToVacuum. zBackupDb
136 ** will be a new name for zDbToVacuum after it is vacuumed.
138 sqlite3_randomness(sizeof(r), &r);
139 zTempDb = sqlite3_mprintf("%s-vacuum-%016llx", zDbToVacuum, r);
140 zBackupDb = sqlite3_mprintf("%s-backup-%016llx", zDbToVacuum, r);
142 /* Attach the zTempDb database to the database connection.
144 zSql = sqlite3_mprintf("ATTACH '%q' AS vacuum_db;", zTempDb);
145 execSql(db, zSql);
146 sqlite3_free(zSql);
148 /* TODO:
149 ** Set the page_size and auto_vacuum mode for zTempDb here, if desired.
152 /* The vacuum will occur inside of a transaction. Set writable_schema
153 ** to ON so that we can directly update the sqlite_schema table in the
154 ** zTempDb database.
156 execSql(db, "PRAGMA writable_schema=ON");
157 execSql(db, "BEGIN");
160 /* Query the schema of the main database. Create a mirror schema
161 ** in the temporary database.
163 execExecSql(db,
164 "SELECT 'CREATE TABLE vacuum_db.' || substr(sql,14) "
165 " FROM sqlite_schema WHERE type='table' AND name!='sqlite_sequence'"
166 " AND rootpage>0"
168 execExecSql(db,
169 "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14)"
170 " FROM sqlite_schema WHERE sql LIKE 'CREATE INDEX %'"
172 execExecSql(db,
173 "SELECT 'CREATE UNIQUE INDEX vacuum_db.' || substr(sql,21) "
174 " FROM sqlite_schema WHERE sql LIKE 'CREATE UNIQUE INDEX %'"
177 /* Loop through the tables in the main database. For each, do
178 ** an "INSERT INTO vacuum_db.xxx SELECT * FROM main.xxx;" to copy
179 ** the contents to the temporary database.
181 execExecSql(db,
182 "SELECT 'INSERT INTO vacuum_db.' || quote(name) "
183 "|| ' SELECT * FROM main.' || quote(name) "
184 "FROM main.sqlite_schema "
185 "WHERE type = 'table' AND name!='sqlite_sequence' "
186 " AND rootpage>0"
189 /* Copy over the sequence table
191 execExecSql(db,
192 "SELECT 'DELETE FROM vacuum_db.' || quote(name) "
193 "FROM vacuum_db.sqlite_schema WHERE name='sqlite_sequence'"
195 execExecSql(db,
196 "SELECT 'INSERT INTO vacuum_db.' || quote(name) "
197 "|| ' SELECT * FROM main.' || quote(name) "
198 "FROM vacuum_db.sqlite_schema WHERE name=='sqlite_sequence'"
201 /* Copy the triggers, views, and virtual tables from the main database
202 ** over to the temporary database. None of these objects has any
203 ** associated storage, so all we have to do is copy their entries
204 ** from the SQLITE_MASTER table.
206 execSql(db,
207 "INSERT INTO vacuum_db.sqlite_schema "
208 " SELECT type, name, tbl_name, rootpage, sql"
209 " FROM main.sqlite_schema"
210 " WHERE type='view' OR type='trigger'"
211 " OR (type='table' AND rootpage=0)"
214 /* Commit the transaction and close the database
216 execSql(db, "COMMIT");
217 printf("-- close database\n");
218 sqlite3_close(db);
221 /* At this point, zDbToVacuum is unchanged. zTempDb contains a
222 ** vacuumed copy of zDbToVacuum. Rearrange filenames so that
223 ** zTempDb becomes thenew zDbToVacuum.
225 printf("-- rename \"%s\" to \"%s\"\n", zDbToVacuum, zBackupDb);
226 rename(zDbToVacuum, zBackupDb);
227 printf("-- rename \"%s\" to \"%s\"\n", zTempDb, zDbToVacuum);
228 rename(zTempDb, zDbToVacuum);
230 /* Release allocated memory */
231 sqlite3_free(zTempDb);
232 sqlite3_free(zBackupDb);
233 return 0;