OLD | NEW |
(Empty) | |
| 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. |
| 10 ** |
| 11 ************************************************************************* |
| 12 ** |
| 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. |
| 16 ** |
| 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. |
| 20 ** |
| 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. |
| 26 ** |
| 27 ** (2) Database reconfiguration such as page size or auto_vacuum changes |
| 28 ** are not supported by this utility. |
| 29 ** |
| 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. |
| 33 ** |
| 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. |
| 37 ** |
| 38 ** To compile this program: |
| 39 ** |
| 40 ** cc fast_vacuum.c sqlite3.c |
| 41 ** |
| 42 ** Add whatever linker options are required. (Example: "-ldl -lpthread"). |
| 43 ** Then to run the program: |
| 44 ** |
| 45 ** ./a.out file-to-vacuum |
| 46 ** |
| 47 */ |
| 48 #include "sqlite3.h" |
| 49 #include <stdio.h> |
| 50 #include <stdlib.h> |
| 51 |
| 52 /* |
| 53 ** Finalize a prepared statement. If an error has occurred, print the |
| 54 ** error message and exit. |
| 55 */ |
| 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); |
| 62 } |
| 63 } |
| 64 |
| 65 /* |
| 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. |
| 69 */ |
| 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); |
| 75 } |
| 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); |
| 80 } |
| 81 sqlite3_step(pStmt); |
| 82 vacuumFinalize(pStmt); |
| 83 } |
| 84 |
| 85 /* |
| 86 ** Execute zSql on database db. The zSql statement returns exactly |
| 87 ** one column. Execute this return value as SQL on the same database. |
| 88 ** |
| 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. |
| 92 */ |
| 93 static void execExecSql(sqlite3 *db, const char *zSql){ |
| 94 sqlite3_stmt *pStmt; |
| 95 int rc; |
| 96 |
| 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); |
| 102 } |
| 103 while( SQLITE_ROW==sqlite3_step(pStmt) ){ |
| 104 execSql(db, (char*)sqlite3_column_text(pStmt, 0)); |
| 105 } |
| 106 vacuumFinalize(pStmt); |
| 107 } |
| 108 |
| 109 |
| 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 */ |
| 118 |
| 119 if( argc!=2 ){ |
| 120 fprintf(stderr, "Usage: %s DATABASE\n", argv[0]); |
| 121 return 1; |
| 122 } |
| 123 |
| 124 /* Identify the database file to be vacuumed and open it. |
| 125 */ |
| 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; |
| 132 } |
| 133 |
| 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. |
| 137 */ |
| 138 sqlite3_randomness(sizeof(r), &r); |
| 139 zTempDb = sqlite3_mprintf("%s-vacuum-%016llx", zDbToVacuum, r); |
| 140 zBackupDb = sqlite3_mprintf("%s-backup-%016llx", zDbToVacuum, r); |
| 141 |
| 142 /* Attach the zTempDb database to the database connection. |
| 143 */ |
| 144 zSql = sqlite3_mprintf("ATTACH '%q' AS vacuum_db;", zTempDb); |
| 145 execSql(db, zSql); |
| 146 sqlite3_free(zSql); |
| 147 |
| 148 /* TODO: |
| 149 ** Set the page_size and auto_vacuum mode for zTempDb here, if desired. |
| 150 */ |
| 151 |
| 152 /* The vacuum will occur inside of a transaction. Set writable_schema |
| 153 ** to ON so that we can directly update the sqlite_master table in the |
| 154 ** zTempDb database. |
| 155 */ |
| 156 execSql(db, "PRAGMA writable_schema=ON"); |
| 157 execSql(db, "BEGIN"); |
| 158 |
| 159 |
| 160 /* Query the schema of the main database. Create a mirror schema |
| 161 ** in the temporary database. |
| 162 */ |
| 163 execExecSql(db, |
| 164 "SELECT 'CREATE TABLE vacuum_db.' || substr(sql,14) " |
| 165 " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'" |
| 166 " AND rootpage>0" |
| 167 ); |
| 168 execExecSql(db, |
| 169 "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14)" |
| 170 " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %'" |
| 171 ); |
| 172 execExecSql(db, |
| 173 "SELECT 'CREATE UNIQUE INDEX vacuum_db.' || substr(sql,21) " |
| 174 " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %'" |
| 175 ); |
| 176 |
| 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. |
| 180 */ |
| 181 execExecSql(db, |
| 182 "SELECT 'INSERT INTO vacuum_db.' || quote(name) " |
| 183 "|| ' SELECT * FROM main.' || quote(name) " |
| 184 "FROM main.sqlite_master " |
| 185 "WHERE type = 'table' AND name!='sqlite_sequence' " |
| 186 " AND rootpage>0" |
| 187 ); |
| 188 |
| 189 /* Copy over the sequence table |
| 190 */ |
| 191 execExecSql(db, |
| 192 "SELECT 'DELETE FROM vacuum_db.' || quote(name) " |
| 193 "FROM vacuum_db.sqlite_master WHERE name='sqlite_sequence'" |
| 194 ); |
| 195 execExecSql(db, |
| 196 "SELECT 'INSERT INTO vacuum_db.' || quote(name) " |
| 197 "|| ' SELECT * FROM main.' || quote(name) " |
| 198 "FROM vacuum_db.sqlite_master WHERE name=='sqlite_sequence'" |
| 199 ); |
| 200 |
| 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. |
| 205 */ |
| 206 execSql(db, |
| 207 "INSERT INTO vacuum_db.sqlite_master " |
| 208 " SELECT type, name, tbl_name, rootpage, sql" |
| 209 " FROM main.sqlite_master" |
| 210 " WHERE type='view' OR type='trigger'" |
| 211 " OR (type='table' AND rootpage=0)" |
| 212 ); |
| 213 |
| 214 /* Commit the transaction and close the database |
| 215 */ |
| 216 execSql(db, "COMMIT"); |
| 217 printf("-- close database\n"); |
| 218 sqlite3_close(db); |
| 219 |
| 220 |
| 221 /* At this point, zDbToVacuum is unchanged. zTempDb contains a |
| 222 ** vacuumed copy of zDbToVacuum. Rearrange filenames so that |
| 223 ** zTempDb becomes thenew zDbToVacuum. |
| 224 */ |
| 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); |
| 229 |
| 230 /* Release allocated memory */ |
| 231 sqlite3_free(zTempDb); |
| 232 sqlite3_free(zBackupDb); |
| 233 return 0; |
| 234 } |
OLD | NEW |