diff options
author | shess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-29 21:02:17 +0000 |
---|---|---|
committer | shess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-29 21:02:17 +0000 |
commit | 62d4d61ec933f2290e2b54f674afed88555ecfa5 (patch) | |
tree | bce9db6f7e668207387146c244347ad29165f009 /third_party/sqlite | |
parent | 3e419d6725611bb0309ba5ef5792fe81d71b7c1b (diff) | |
download | chromium_src-62d4d61ec933f2290e2b54f674afed88555ecfa5.zip chromium_src-62d4d61ec933f2290e2b54f674afed88555ecfa5.tar.gz chromium_src-62d4d61ec933f2290e2b54f674afed88555ecfa5.tar.bz2 |
Dup recover.c to recover-alt.c
I am trying to land additional changes to recover.c, but something
about it is giving the code-review system fits. I think it's the
formfeeds. Making a copy of the file without formfeeds so that I can
continue the review.
NOTE: Neither file is compiled on any platform. Not even referenced
from any gyp file.
BUG=none
TBR=cmp@chromium.org
Review URL: https://codereview.chromium.org/13245004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@191444 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'third_party/sqlite')
-rw-r--r-- | third_party/sqlite/src/src/recover-alt.c | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/third_party/sqlite/src/src/recover-alt.c b/third_party/sqlite/src/src/recover-alt.c new file mode 100644 index 0000000..46da9ce --- /dev/null +++ b/third_party/sqlite/src/src/recover-alt.c @@ -0,0 +1,803 @@ +/* +** 2012 Jan 11 +** +** 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. +*/ +/* TODO(shess): THIS MODULE IS STILL EXPERIMENTAL. DO NOT USE IT. */ +/* Implements a virtual table "recover" which can be used to recover + * data from a corrupt table. The table is walked manually, with + * corrupt items skipped. Additionally, any errors while reading will + * be skipped. + * + * Given a table with this definition: + * + * CREATE TABLE Stuff ( + * name TEXT PRIMARY KEY, + * value TEXT NOT NULL + * ); + * + * to recover the data from teh table, you could do something like: + * + * -- Attach another database, the original is not trustworthy. + * ATTACH DATABASE '/tmp/db.db' AS rdb; + * -- Create a new version of the table. + * CREATE TABLE rdb.Stuff ( + * name TEXT PRIMARY KEY, + * value TEXT NOT NULL + * ); + * -- This will read the original table's data. + * CREATE VIRTUAL TABLE temp.recover_Stuff using recover( + * main.Stuff, + * name TEXT STRICT NOT NULL, -- only real TEXT data allowed + * value TEXT STRICT NOT NULL + * ); + * -- Corruption means the UNIQUE constraint may no longer hold for + * -- Stuff, so either OR REPLACE or OR IGNORE must be used. + * INSERT OR REPLACE INTO rdb.Stuff (rowid, name, value ) + * SELECT rowid, name, value FROM temp.recover_Stuff; + * DROP TABLE temp.recover_Stuff; + * DETACH DATABASE rdb; + * -- Move db.db to replace original db in filesystem. + * + * + * Usage + * + * Given the goal of dealing with corruption, it would not be safe to + * create a recovery table in the database being recovered. So + * recovery tables must be created in the temp database. They are not + * appropriate to persist, in any case. [As a bonus, sqlite_master + * tables can be recovered. Perhaps more cute than useful, though.] + * + * The parameters are a specifier for the table to read, and a column + * definition for each bit of data stored in that table. The named + * table must be convertable to a root page number by reading the + * sqlite_master table. Bare table names are assumed to be in + * database 0 ("main"), other databases can be specified in db.table + * fashion. + * + * Column definitions are similar to BUT NOT THE SAME AS those + * provided to CREATE statements: + * column-def: column-name [type-name [STRICT] [NOT NULL]] + * type-name: (ANY|ROWID|INTEGER|FLOAT|NUMERIC|TEXT|BLOB) + * + * Only those exact type names are accepted, there is no type + * intuition. The only constraints accepted are STRICT (see below) + * and NOT NULL. Anything unexpected will cause the create to fail. + * + * ANY is a convenience to indicate that manifest typing is desired. + * It is equivalent to not specifying a type at all. The results for + * such columns will have the type of the data's storage. The exposed + * schema will contain no type for that column. + * + * ROWID is used for columns representing aliases to the rowid + * (INTEGER PRIMARY KEY, with or without AUTOINCREMENT), to make the + * concept explicit. Such columns are actually stored as NULL, so + * they cannot be simply ignored. The exposed schema will be INTEGER + * for that column. + * + * NOT NULL causes rows with a NULL in that column to be skipped. It + * also adds NOT NULL to the column in the exposed schema. If the + * table has ever had columns added using ALTER TABLE, then those + * columns implicitly contain NULL for rows which have not been + * updated. [Workaround using COALESCE() in your SELECT statement.] + * + * The created table is read-only, with no indices. Any SELECT will + * be a full-table scan, returning each valid row read from the + * storage of the backing table. The rowid will be the rowid of the + * row from the backing table. "Valid" means: + * - The cell metadata for the row is well-formed. Mainly this means that + * the cell header info describes a payload of the size indicated by + * the cell's payload size. + * - The cell does not run off the page. + * - The cell does not overlap any other cell on the page. + * - The cell contains doesn't contain too many columns. + * - The types of the serialized data match the indicated types (see below). + * + * + * Type affinity versus type storage. + * + * http://www.sqlite.org/datatype3.html describes SQLite's type + * affinity system. The system provides for automated coercion of + * types in certain cases, transparently enough that many developers + * do not realize that it is happening. Importantly, it implies that + * the raw data stored in the database may not have the obvious type. + * + * Differences between the stored data types and the expected data + * types may be a signal of corruption. This module makes some + * allowances for automatic coercion. It is important to be concious + * of the difference between the schema exposed by the module, and the + * data types read from storage. The following table describes how + * the module interprets things: + * + * type schema data STRICT + * ---- ------ ---- ------ + * ANY <none> any any + * ROWID INTEGER n/a n/a + * INTEGER INTEGER integer integer + * FLOAT FLOAT integer or float float + * NUMERIC NUMERIC integer, float, or text integer or float + * TEXT TEXT text or blob text + * BLOB BLOB blob blob + * + * type is the type provided to the recover module, schema is the + * schema exposed by the module, data is the acceptable types of data + * decoded from storage, and STRICT is a modification of that. + * + * A very loose recovery system might use ANY for all columns, then + * use the appropriate sqlite3_column_*() calls to coerce to expected + * types. This doesn't provide much protection if a page from a + * different table with the same column count is linked into an + * inappropriate btree. + * + * A very tight recovery system might use STRICT to enforce typing on + * all columns, preferring to skip rows which are valid at the storage + * level but don't contain the right types. Note that FLOAT STRICT is + * almost certainly not appropriate, since integral values are + * transparently stored as integers, when that is more efficient. + * + * Another option is to use ANY for all columns and inspect each + * result manually (using sqlite3_column_*). This should only be + * necessary in cases where developers have used manifest typing (test + * to make sure before you decide that you aren't using manifest + * typing!). + * + * + * Caveats + * + * Leaf pages not referenced by interior nodes will not be found. + * + * Leaf pages referenced from interior nodes of other tables will not + * be resolved. + * + * Rows referencing invalid overflow pages will be skipped. + * + * SQlite rows have a header which describes how to interpret the rest + * of the payload. The header can be valid in cases where the rest of + * the record is actually corrupt (in the sense that the data is not + * the intended data). This can especially happen WRT overflow pages, + * as lack of atomic updates between pages is the primary form of + * corruption I have seen in the wild. + */ +/* TODO(shess): It might be useful to allow DEFAULT in types to + * specify what to do for NULL when an ALTER TABLE case comes up. + * Unfortunately, simply adding it to the exposed schema and using + * sqlite3_result_null() does not cause the default to be generate. + * Handling it ourselves seems hard, unfortunately. + */ + +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <string.h> + +/* Internal things that are used: + * u32, u64, i64 types. + * Btree, Pager, and DbPage structs. + * DbPage.pData, .pPager, and .pgno + * sqlite3 struct. + * sqlite3BtreePager() and sqlite3BtreeGetPageSize() + * sqlite3PagerAcquire() and sqlite3PagerUnref() + * getVarint32() and getVarint(). + */ +#include "sqliteInt.h" + +/* For debugging. */ +#if 0 +#define FNENTRY() fprintf(stderr, "In %s\n", __FUNCTION__) +#else +#define FNENTRY() +#endif + +/* Generic constants and helper functions. */ + +/* Accepted types are specified by a mask. */ +#define MASK_ROWID (1<<0) +#define MASK_INTEGER (1<<1) +#define MASK_FLOAT (1<<2) +#define MASK_TEXT (1<<3) +#define MASK_BLOB (1<<4) +#define MASK_NULL (1<<5) + +/* TODO(shess): In the future, these will be used more often. For + * now, just pretend they're useful. + */ + +/* True if iSerialType refers to a blob. */ +static int SerialTypeIsBlob(u64 iSerialType){ + assert( iSerialType>=12 ); + return (iSerialType%2)==0; +} + +/* Returns true if the serialized type represented by iSerialType is + * compatible with the given type mask. + */ +static int SerialTypeIsCompatible(u64 iSerialType, unsigned char mask){ + switch( iSerialType ){ + case 0 : return (mask&MASK_NULL)!=0; + case 1 : return (mask&MASK_INTEGER)!=0; + case 2 : return (mask&MASK_INTEGER)!=0; + case 3 : return (mask&MASK_INTEGER)!=0; + case 4 : return (mask&MASK_INTEGER)!=0; + case 5 : return (mask&MASK_INTEGER)!=0; + case 6 : return (mask&MASK_INTEGER)!=0; + case 7 : return (mask&MASK_FLOAT)!=0; + case 8 : return (mask&MASK_INTEGER)!=0; + case 9 : return (mask&MASK_INTEGER)!=0; + case 10 : assert( !"RESERVED TYPE"); return 0; + case 11 : assert( !"RESERVED TYPE"); return 0; + } + return (mask&(SerialTypeIsBlob(iSerialType) ? MASK_BLOB : MASK_TEXT)); +} + +/* Versions of strdup() with return values appropriate for + * sqlite3_free(). malloc.c has sqlite3DbStrDup()/NDup(), but those + * need sqlite3DbFree(), which seems intrusive. + */ +static char *sqlite3_strndup(const char *z, unsigned n){ + if( z==NULL ){ + return NULL; + } + + char *zNew = sqlite3_malloc(n+1); + if( zNew!=NULL ){ + memcpy(zNew, z, n); + zNew[n] = '\0'; + } + return zNew; +} +static char *sqlite3_strdup(const char *z){ + if( z==NULL ){ + return NULL; + } + return sqlite3_strndup(z, strlen(z)); +} + +/* Fetch the page number of zTable in zDb from sqlite_master in zDb, + * and put it in *piRootPage. + */ +static int getRootPage(sqlite3 *db, const char *zDb, const char *zTable, + unsigned *piRootPage){ + if( strcmp(zTable, "sqlite_master")==0 ){ + *piRootPage = 1; + return SQLITE_OK; + } + + char *zSql = sqlite3_mprintf("SELECT rootpage FROM %s.sqlite_master " + "WHERE type = 'table' AND tbl_name = %Q", + zDb, zTable); + if( !zSql ){ + return SQLITE_NOMEM; + } + + sqlite3_stmt *pStmt = 0; + int rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc!=SQLITE_OK ){ + return rc; + } + + /* Require a result. */ + rc = sqlite3_step(pStmt); + if( rc==SQLITE_DONE ){ + rc = SQLITE_CORRUPT; + }else if( rc==SQLITE_ROW ){ + *piRootPage = sqlite3_column_int(pStmt, 0); + + /* Require only one result. */ + rc = sqlite3_step(pStmt); + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + }else if( rc==SQLITE_ROW ){ + rc = SQLITE_CORRUPT; + } + } + sqlite3_finalize(pStmt); + return rc; +} + +typedef struct Recover Recover; +struct Recover { + sqlite3_vtab base; + sqlite3 *db; /* Host database connection */ + char *zDb; /* Database containing target table */ + char *zTable; /* Target table */ + int nCols; /* Number of columns in target table */ + unsigned char *pTypes; /* Types of columns in target table */ +}; + +/* Internal helper for deleting the module. */ +static void recoverRelease(Recover *pRecover){ + sqlite3_free(pRecover->zDb); + sqlite3_free(pRecover->zTable); + sqlite3_free(pRecover->pTypes); + memset(pRecover, 0xA5, sizeof(*pRecover)); + sqlite3_free(pRecover); +} + +/* Helper function for initializing the module. Forward-declared so + * recoverCreate() and recoverConnect() can see it. + */ +static int recoverInit( + sqlite3 *, void *, int, const char *const*, sqlite3_vtab **, char ** +); + +static int recoverCreate( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + FNENTRY(); + return recoverInit(db, pAux, argc, argv, ppVtab, pzErr); +} + +/* This should never be called. */ +static int recoverConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + FNENTRY(); + return recoverInit(db, pAux, argc, argv, ppVtab, pzErr); +} + +/* No indices supported. */ +static int recoverBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + FNENTRY(); + return SQLITE_OK; +} + +/* Logically, this should never be called. */ +static int recoverDisconnect(sqlite3_vtab *pVtab){ + FNENTRY(); + recoverRelease((Recover*)pVtab); + return SQLITE_OK; +} + +static int recoverDestroy(sqlite3_vtab *pVtab){ + FNENTRY(); + recoverRelease((Recover*)pVtab); + return SQLITE_OK; +} + +typedef struct RecoverCursor RecoverCursor; +struct RecoverCursor { + sqlite3_vtab_cursor base; + i64 iRowid; /* TODO(shess): Implement for real. */ + int bEOF; +}; + +static int recoverOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + FNENTRY(); + + Recover *pRecover = (Recover*)pVTab; + + unsigned iRootPage = 0; + int rc = getRootPage(pRecover->db, pRecover->zDb, pRecover->zTable, + &iRootPage); + if( rc!=SQLITE_OK ){ + return rc; + } + + /* TODO(shess): Implement some real stuff in here. */ + + RecoverCursor *pCursor = sqlite3_malloc(sizeof(RecoverCursor)); + if( !pCursor ){ + return SQLITE_NOMEM; + } + memset(pCursor, 0, sizeof(*pCursor)); + pCursor->base.pVtab = pVTab; + pCursor->iRowid = 0; + + *ppCursor = (sqlite3_vtab_cursor*)pCursor; + return SQLITE_OK; +} + +static int recoverClose(sqlite3_vtab_cursor *cur){ + FNENTRY(); + RecoverCursor *pCursor = (RecoverCursor*)cur; + memset(pCursor, 0xA5, sizeof(*pCursor)); + sqlite3_free(cur); + return SQLITE_OK; +} + +/* TODO(shess): Some data for purposes of mocking things. Will go + * away. + */ +static struct { + u64 iColType; + const char *zTypeName; +} gMockData[] = { + { 0, "NULL"}, + { 1, "INTEGER"}, + { 7, "FLOAT"}, + { 13, "TEXT"}, + { 12, "BLOB"}, +}; + +static int recoverNext(sqlite3_vtab_cursor *pVtabCursor){ + FNENTRY(); + RecoverCursor *pCursor = (RecoverCursor*)pVtabCursor; + Recover *pRecover = (Recover*)pCursor->base.pVtab; + + /* iRowid is 1-base, gMockData is 0-based. */ + while( pCursor->iRowid<ArraySize(gMockData) ){ + pCursor->iRowid++; + if( SerialTypeIsCompatible(gMockData[pCursor->iRowid-1].iColType, + pRecover->pTypes[pRecover->nCols-1]) ){ + return SQLITE_OK; + } + } + pCursor->bEOF = 1; + return SQLITE_OK; +} + +static int recoverFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + FNENTRY(); + return recoverNext(pVtabCursor); +} + +static int recoverEof(sqlite3_vtab_cursor *pVtabCursor){ + FNENTRY(); + RecoverCursor *pCursor = (RecoverCursor*)pVtabCursor; + return pCursor->bEOF; +} + +static int recoverColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ + FNENTRY(); + RecoverCursor *pCursor = (RecoverCursor*)cur; + Recover *pRecover = (Recover*)pCursor->base.pVtab; + + if( i>=pRecover->nCols ){ + return SQLITE_ERROR; + } + + /* ROWID alias. */ + if( (pRecover->pTypes[i]&MASK_ROWID) ){ + sqlite3_result_int64(ctx, pCursor->iRowid); + return SQLITE_OK; + } + + /* TODO(shess): Replace this with real code. */ + if( pCursor->iRowid<1 || pCursor->iRowid>ArraySize(gMockData)+1 ){ + return SQLITE_ERROR; + } + if( i==pRecover->nCols-2 ){ + sqlite3_result_text(ctx, gMockData[pCursor->iRowid-1].zTypeName, -1, + SQLITE_STATIC); + }else if( i==pRecover->nCols-1 ){ + switch( gMockData[pCursor->iRowid-1].iColType ){ + case 0 : sqlite3_result_null(ctx); break; + case 1 : sqlite3_result_int(ctx, 17); break; + case 7 : sqlite3_result_double(ctx, 3.1415927); break; + case 13 : + sqlite3_result_text(ctx, "This is text", -1, SQLITE_STATIC); + break; + case 12 : + sqlite3_result_blob(ctx, "This is a blob", 14, SQLITE_STATIC); + break; + } + } + return SQLITE_OK; +} + +static int recoverRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ + FNENTRY(); + RecoverCursor *pCursor = (RecoverCursor*)pVtabCursor; + *pRowid = pCursor->iRowid; + return SQLITE_OK; +} + +static sqlite3_module recoverModule = { + 0, /* iVersion */ + recoverCreate, /* xCreate - create a table */ + recoverConnect, /* xConnect - connect to an existing table */ + recoverBestIndex, /* xBestIndex - Determine search strategy */ + recoverDisconnect, /* xDisconnect - Disconnect from a table */ + recoverDestroy, /* xDestroy - Drop a table */ + recoverOpen, /* xOpen - open a cursor */ + recoverClose, /* xClose - close a cursor */ + recoverFilter, /* xFilter - configure scan constraints */ + recoverNext, /* xNext - advance a cursor */ + recoverEof, /* xEof */ + recoverColumn, /* xColumn - read data */ + recoverRowid, /* xRowid - read data */ + 0, /* xUpdate - write data */ + 0, /* xBegin - begin transaction */ + 0, /* xSync - sync transaction */ + 0, /* xCommit - commit transaction */ + 0, /* xRollback - rollback transaction */ + 0, /* xFindFunction - function overloading */ + 0, /* xRename - rename the table */ +}; + +int recoverVtableInit(sqlite3 *db){ + return sqlite3_create_module_v2(db, "recover", &recoverModule, NULL, 0); +} + +/* This section of code is for parsing the create input and + * initializing the module. + */ + +/* Find the next word in zText and place the endpoints in pzWord*. + * Returns true if the word is non-empty. "Word" is defined as + * alphanumeric plus '_' at this time. + */ +static int findWord(const char *zText, + const char **pzWordStart, const char **pzWordEnd){ + while( isspace(*zText) ){ + zText++; + } + *pzWordStart = zText; + while( isalnum(*zText) || *zText=='_' ){ + zText++; + } + int r = zText>*pzWordStart; /* In case pzWordStart==pzWordEnd */ + *pzWordEnd = zText; + return r; +} + +/* Return true if the next word in zText is zWord, also setting + * *pzContinue to the character after the word. + */ +static int expectWord(const char *zText, const char *zWord, + const char **pzContinue){ + const char *zWordStart, *zWordEnd; + if( findWord(zText, &zWordStart, &zWordEnd) && + strncasecmp(zWord, zWordStart, zWordEnd - zWordStart)==0 ){ + *pzContinue = zWordEnd; + return 1; + } + return 0; +} + +/* Parse the name and type information out of parameter. In case of + * success, *pzNameStart/End contain the name of the column, + * *pzTypeStart/End contain the top-level type, and *pTypeMask has the + * type mask to use for the column. + */ +static int findNameAndType(const char *parameter, + const char **pzNameStart, const char **pzNameEnd, + const char **pzTypeStart, const char **pzTypeEnd, + unsigned char *pTypeMask){ + if( !findWord(parameter, pzNameStart, pzNameEnd) ){ + return SQLITE_MISUSE; + } + + /* Manifest typing, accept any storage type. */ + if( !findWord(*pzNameEnd, pzTypeStart, pzTypeEnd) ){ + *pzTypeEnd = *pzTypeStart = ""; + *pTypeMask = MASK_INTEGER | MASK_FLOAT | MASK_BLOB | MASK_TEXT | MASK_NULL; + return SQLITE_OK; + } + + /* strictMask is used for STRICT, strictMask|otherMask if STRICT is + * not supplied. zReplace provides an alternate type to expose to + * the caller. + */ + static struct { + const char *zName; + unsigned char strictMask; + unsigned char otherMask; + const char *zReplace; + } kTypeInfo[] = { + { "ANY", + MASK_INTEGER | MASK_FLOAT | MASK_BLOB | MASK_TEXT | MASK_NULL, + 0, "", + }, + { "ROWID", MASK_INTEGER | MASK_ROWID, 0, "INTEGER", }, + { "INTEGER", MASK_INTEGER | MASK_NULL, 0, NULL, }, + { "FLOAT", MASK_FLOAT | MASK_NULL, MASK_INTEGER, NULL, }, + { "NUMERIC", MASK_INTEGER | MASK_FLOAT | MASK_NULL, MASK_TEXT, NULL, }, + { "TEXT", MASK_TEXT | MASK_NULL, MASK_BLOB, NULL, }, + { "BLOB", MASK_BLOB | MASK_NULL, 0, NULL, }, + }; + + unsigned i, nNameLen = *pzTypeEnd - *pzTypeStart; + for( i=0; i<ArraySize(kTypeInfo); ++i ){ + if( strncasecmp(kTypeInfo[i].zName, *pzTypeStart, nNameLen)==0 ){ + break; + } + } + if( i==ArraySize(kTypeInfo) ){ + return SQLITE_MISUSE; + } + + const char *zEnd = *pzTypeEnd; + int bStrict = 0; + if( expectWord(zEnd, "STRICT", &zEnd) ){ + /* TODO(shess): Ick. But I don't want another single-purpose + * flag, either. + */ + if( kTypeInfo[i].zReplace && !kTypeInfo[i].zReplace[0] ){ + return SQLITE_MISUSE; + } + bStrict = 1; + } + + int bNotNull = 0; + if( expectWord(zEnd, "NOT", &zEnd) ){ + if( expectWord(zEnd, "NULL", &zEnd) ){ + bNotNull = 1; + }else{ + /* Anything other than NULL after NOT is an error. */ + return SQLITE_MISUSE; + } + } + + /* Anything else is an error. */ + const char *zDummy; + if( findWord(zEnd, &zDummy, &zDummy) ){ + return SQLITE_MISUSE; + } + + *pTypeMask = kTypeInfo[i].strictMask; + if( !bStrict ){ + *pTypeMask |= kTypeInfo[i].otherMask; + } + if( bNotNull ){ + *pTypeMask &= ~MASK_NULL; + } + if( kTypeInfo[i].zReplace ){ + *pzTypeStart = kTypeInfo[i].zReplace; + *pzTypeEnd = *pzTypeStart + strlen(*pzTypeStart); + } + return SQLITE_OK; +} + +/* Parse the arguments, placing type masks in *pTypes and the exposed + * schema in *pzCreateSql (for sqlite3_declare_vtab). + */ +static int ParseColumnsAndGenerateCreate(int nCols, const char *const *pCols, + char **pzCreateSql, + unsigned char *pTypes, + char **pzErr){ + char *zCreateSql = sqlite3_mprintf("CREATE TABLE x("); + if( !zCreateSql ){ + return SQLITE_NOMEM; + } + + unsigned i; + for( i=0; i<nCols; i++ ){ + const char *zNameStart, *zNameEnd; + const char *zTypeStart, *zTypeEnd; + int rc = findNameAndType(pCols[i], + &zNameStart, &zNameEnd, + &zTypeStart, &zTypeEnd, + &pTypes[i]); + if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("unable to parse column %d", i); + sqlite3_free(zCreateSql); + return rc; + } + + const char *zNotNull = ""; + if( !(pTypes[i]&MASK_NULL) ){ + zNotNull = " NOT NULL"; + } + + /* Add name and type to the create statement. */ + const char *zSep = (i < nCols - 1 ? ", " : ")"); + zCreateSql = sqlite3_mprintf("%z%.*s %.*s%s%s", + zCreateSql, + zNameEnd - zNameStart, zNameStart, + zTypeEnd - zTypeStart, zTypeStart, + zNotNull, zSep); + if( !zCreateSql ){ + return SQLITE_NOMEM; + } + } + + *pzCreateSql = zCreateSql; + return SQLITE_OK; +} + +/* Helper function for initializing the module. */ +/* TODO(shess): Since connect isn't supported, could inline into + * recoverCreate(). + */ +/* TODO(shess): Explore cases where it would make sense to set *pzErr. */ +static int recoverInit( + sqlite3 *db, /* Database connection */ + void *pAux, /* unused */ + int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */ + sqlite3_vtab **ppVtab, /* OUT: New virtual table */ + char **pzErr /* OUT: Error message, if any */ +){ + /* argv[0] module name + * argv[1] db name for virtual table + * argv[2] virtual table name + * argv[3] backing table name + * argv[4] columns + */ + const int kTypeCol = 4; + int nCols = argc - kTypeCol; + + /* Require to be in the temp database. */ + if( strcasecmp(argv[1], "temp")!=0 ){ + *pzErr = sqlite3_mprintf("recover table must be in temp database"); + return SQLITE_MISUSE; + } + + /* Need the backing table and at least one column. */ + if( nCols<1 ){ + *pzErr = sqlite3_mprintf("no columns specified"); + return SQLITE_MISUSE; + } + + Recover *pRecover = sqlite3_malloc(sizeof(Recover)); + if( !pRecover ){ + return SQLITE_NOMEM; + } + memset(pRecover, 0, sizeof(*pRecover)); + pRecover->base.pModule = &recoverModule; + pRecover->db = db; + + /* Parse out db.table, assuming main if no dot. */ + char *zDot = strchr(argv[3], '.'); + if( !zDot ){ + pRecover->zDb = sqlite3_strdup(db->aDb[0].zName); + pRecover->zTable = sqlite3_strdup(argv[3]); + }else if( zDot>argv[3] && zDot[1]!='\0' ){ + pRecover->zDb = sqlite3_strndup(argv[3], zDot - argv[3]); + pRecover->zTable = sqlite3_strdup(zDot + 1); + }else{ + /* ".table" or "db." not allowed. */ + *pzErr = sqlite3_mprintf("ill-formed table specifier"); + recoverRelease(pRecover); + return SQLITE_ERROR; + } + + pRecover->nCols = nCols; + pRecover->pTypes = sqlite3_malloc(pRecover->nCols); + if( !pRecover->zDb || !pRecover->zTable || !pRecover->pTypes ){ + recoverRelease(pRecover); + return SQLITE_NOMEM; + } + + /* Require the backing table to exist. */ + /* TODO(shess): Be more pedantic about the form of the descriptor + * string. This already fails for poorly-formed strings, simply + * because there won't be a root page, but it would make more sense + * to be explicit. + */ + unsigned iRootPage; + int rc = getRootPage(pRecover->db, pRecover->zDb, pRecover->zTable, + &iRootPage); + if( rc!=SQLITE_OK ){ + *pzErr = sqlite3_mprintf("unable to find backing table"); + recoverRelease(pRecover); + return rc; + } + + /* Parse the column definitions. */ + char *zCreateSql; + rc = ParseColumnsAndGenerateCreate(nCols, argv + kTypeCol, + &zCreateSql, pRecover->pTypes, pzErr); + if( rc!=SQLITE_OK ){ + recoverRelease(pRecover); + return rc; + } + + rc = sqlite3_declare_vtab(db, zCreateSql); + sqlite3_free(zCreateSql); + if( rc!=SQLITE_OK ){ + recoverRelease(pRecover); + return rc; + } + + *ppVtab = (sqlite3_vtab *)pRecover; + return SQLITE_OK; +} |