diff options
author | shess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-12 01:59:27 +0000 |
---|---|---|
committer | shess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-12 01:59:27 +0000 |
commit | d9d6b3a6a671b606e9f6fceb6368ec522fa52be0 (patch) | |
tree | 8499fd671f27f89accbc802c1c747480baef2879 /third_party/sqlite | |
parent | e0a905b3bcb24474198d755b824d0d467ebeb68b (diff) | |
download | chromium_src-d9d6b3a6a671b606e9f6fceb6368ec522fa52be0.zip chromium_src-d9d6b3a6a671b606e9f6fceb6368ec522fa52be0.tar.gz chromium_src-d9d6b3a6a671b606e9f6fceb6368ec522fa52be0.tar.bz2 |
Skeleton of SQLite virtual-table module to recover data from corrupt databases.
"recover" implements a virtual table which uses the SQLite pager layer
to read table pages and pull out the data which is structurally sound
(at least at the storage layer).
This CL implements the virtual-table interface, including schema, with
some mock data for purposes of landing some initial tests. This CL
should cause no changes to Chromium, as it does not get compiled.
BUG=109482
TEST=none
Review URL: http://codereview.chromium.org/9125018
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@117359 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'third_party/sqlite')
-rw-r--r-- | third_party/sqlite/README.chromium | 2 | ||||
-rw-r--r-- | third_party/sqlite/src/src/recover.c | 803 | ||||
-rw-r--r-- | third_party/sqlite/src/test/recover0.test | 532 | ||||
-rw-r--r-- | third_party/sqlite/src/test/recover1.test | 282 |
4 files changed, 1619 insertions, 0 deletions
diff --git a/third_party/sqlite/README.chromium b/third_party/sqlite/README.chromium index d53d8ba..6220727 100644 --- a/third_party/sqlite/README.chromium +++ b/third_party/sqlite/README.chromium @@ -189,3 +189,5 @@ Changes from Chrome: conflict with an Apple library after amalgamation it was also necessary to rename fts3_porter.c's 'cType' to 'vOrCType'. - fts3_85522.patch allows fts3 to work if PRAGMA is not authorized. + - src/recover.c file implements a virtual table which can read + through corruption. diff --git a/third_party/sqlite/src/src/recover.c b/third_party/sqlite/src/src/recover.c new file mode 100644 index 0000000..6678fea --- /dev/null +++ b/third_party/sqlite/src/src/recover.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; +} diff --git a/third_party/sqlite/src/test/recover0.test b/third_party/sqlite/src/test/recover0.test new file mode 100644 index 0000000..aac2ed9 --- /dev/null +++ b/third_party/sqlite/src/test/recover0.test @@ -0,0 +1,532 @@ +# 2012 January 4 {} +# +# 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 implements regression tests for SQLite library. +# +# Test recover module syntax. +# +# $Id$ + +# TODO(shess): Test with attached databases. + +# TODO(shess): Handle column mismatches? As things stand, the code +# only needs to pull the root page, so that may not be completely +# feasible. + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +db eval { + DROP TABLE IF EXISTS backing; + CREATE TABLE backing (t TEXT); + + DROP TABLE IF EXISTS backing2; + CREATE TABLE backing2 (id INTEGER PRIMARY KEY, t TEXT); +} + +# Baseline create works. +do_test recover-syntax-0.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + t TEXT + ); + } +} {0 {}} + +# Can specify database. +do_test recover-syntax-0.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + main.backing, + t TEXT + ); + } +} {0 {}} + +# Can specify sqlite_master. +do_test recover-syntax-0.2 { + db eval {DROP TABLE IF EXISTS temp.syntax} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + sqlite_master, + type TEXT, + name TEXT, + tbl_name TEXT, + rootpage INTEGER, + sql TEXT + ); + } +} {0 {}} + +# Fails if virtual table is not in the temp database. +do_test recover-syntax-1.0 { + db eval {DROP TABLE IF EXISTS temp.syntax;} + catchsql { + CREATE VIRTUAL TABLE syntax USING recover( + backing, + t TEXT + ); + } +} {1 {recover table must be in temp database}} + +# Fails if mentions missing table. +do_test recover-syntax-2.0 { + db eval {DROP TABLE IF EXISTS temp.syntax;} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + snacking, + t TEXT + ); + } +} {1 {unable to find backing table}} + +# Fails if mentions missing database. +do_test recover-syntax-2.1 { + db eval {DROP TABLE IF EXISTS temp.syntax;} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + db.backing, + t TEXT + ); + } +} {1 {unable to find backing table}} + +# Fails if mentions garbage backing. +do_test recover-syntax-2.2 { + db eval {DROP TABLE IF EXISTS temp.syntax;} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + main.backing excess, + t TEXT + ); + } +} {1 {unable to find backing table}} + +# Database only fails. +do_test recover-syntax-2.3 { + db eval {DROP TABLE IF EXISTS temp.syntax;} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + main., + t TEXT + ); + } +} {1 {ill-formed table specifier}} + +# Table only fails. +do_test recover-syntax-2.4 { + db eval {DROP TABLE IF EXISTS temp.syntax;} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + .backing, + t TEXT + ); + } +} {1 {ill-formed table specifier}} + +# Manifest typing. +do_test recover-syntax-3.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + t + ); + PRAGMA table_info(syntax); + } +} {0 t {} 0 {} 0} + +# ANY as an alternative for manifest typing. +do_test recover-syntax-3.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + t ANY + ); + PRAGMA table_info(syntax); + } +} {0 t {} 0 {} 0} + +# ANY NOT NULL +do_test recover-syntax-3.2 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + t ANY NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 t {} 1 {} 0} + +# ANY STRICT is not sensible. +do_test recover-syntax-3.3 { + db eval {DROP TABLE IF EXISTS temp.syntax} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + v ANY STRICT + ); + PRAGMA table_info(syntax); + } +} {1 {unable to parse column 0}} + +# TEXT column by type works. +do_test recover-syntax-4.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + t TEXT + ); + PRAGMA table_info(syntax); + } +} {0 t TEXT 0 {} 0} + +# TEXT NOT NULL +do_test recover-syntax-4.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + t TEXT NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 t TEXT 1 {} 0} + +# TEXT STRICT +do_test recover-syntax-4.2 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + t TEXT STRICT + ); + PRAGMA table_info(syntax); + } +} {0 t TEXT 0 {} 0} + +# TEXT STRICT NOT NULL +do_test recover-syntax-4.3 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + t TEXT STRICT NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 t TEXT 1 {} 0} + +# INTEGER +do_test recover-syntax-5.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + i INTEGER + ); + PRAGMA table_info(syntax); + } +} {0 i INTEGER 0 {} 0} + +# INTEGER NOT NULL +do_test recover-syntax-5.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + i INTEGER NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 i INTEGER 1 {} 0} + +# INTEGER STRICT +do_test recover-syntax-5.2 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + i INTEGER STRICT + ); + PRAGMA table_info(syntax); + } +} {0 i INTEGER 0 {} 0} + +# INTEGER STRICT NOT NULL +do_test recover-syntax-5.3 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + i INTEGER STRICT NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 i INTEGER 1 {} 0} + +# BLOB +do_test recover-syntax-6.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + b BLOB + ); + PRAGMA table_info(syntax); + } +} {0 b BLOB 0 {} 0} + +# BLOB NOT NULL +do_test recover-syntax-6.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + b BLOB NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 b BLOB 1 {} 0} + +# BLOB STRICT +do_test recover-syntax-6.2 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + b BLOB STRICT + ); + PRAGMA table_info(syntax); + } +} {0 b BLOB 0 {} 0} + +# BLOB STRICT NOT NULL +do_test recover-syntax-6.3 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + b BLOB STRICT NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 b BLOB 1 {} 0} + +# FLOAT +do_test recover-syntax-7.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + f FLOAT + ); + PRAGMA table_info(syntax); + } +} {0 f FLOAT 0 {} 0} + +# FLOAT NOT NULL +do_test recover-syntax-7.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + f FLOAT NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 f FLOAT 1 {} 0} + +# FLOAT STRICT +do_test recover-syntax-7.2 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + f FLOAT STRICT + ); + PRAGMA table_info(syntax); + } +} {0 f FLOAT 0 {} 0} + +# FLOAT STRICT NOT NULL +do_test recover-syntax-7.3 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + f FLOAT STRICT NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 f FLOAT 1 {} 0} + +# NUMERIC +do_test recover-syntax-8.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + f NUMERIC + ); + PRAGMA table_info(syntax); + } +} {0 f NUMERIC 0 {} 0} + +# NUMERIC NOT NULL +do_test recover-syntax-8.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + f NUMERIC NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 f NUMERIC 1 {} 0} + +# NUMERIC STRICT +do_test recover-syntax-8.2 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + f NUMERIC STRICT + ); + PRAGMA table_info(syntax); + } +} {0 f NUMERIC 0 {} 0} + +# NUMERIC STRICT NOT NULL +do_test recover-syntax-8.3 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + f NUMERIC STRICT NOT NULL + ); + PRAGMA table_info(syntax); + } +} {0 f NUMERIC 1 {} 0} + +# ROWID +do_test recover-syntax-9.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing2, + id ROWID, + v + ); + PRAGMA table_info(syntax); + } +} {0 id INTEGER 1 {} 0 1 v {} 0 {} 0} + +# ROWID NOT NULL (is default) +do_test recover-syntax-9.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing2, + id ROWID NOT NULL, + v + ); + PRAGMA table_info(syntax); + } +} {0 id INTEGER 1 {} 0 1 v {} 0 {} 0} + +# ROWID STRICT +do_test recover-syntax-9.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing2, + id ROWID STRICT, + v + ); + PRAGMA table_info(syntax); + } +} {0 id INTEGER 1 {} 0 1 v {} 0 {} 0} + +# ROWID STRICT NOT NULL (is default) +do_test recover-syntax-9.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + execsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing2, + id ROWID STRICT NOT NULL, + v + ); + PRAGMA table_info(syntax); + } +} {0 id INTEGER 1 {} 0 1 v {} 0 {} 0} + +# Invalid type info is not ignored. +do_test recover-syntax-10.0 { + db eval {DROP TABLE IF EXISTS temp.syntax} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + v GARBAGE + ); + } +} {1 {unable to parse column 0}} + +# Extraneous type info is not ignored. +do_test recover-syntax-10.1 { + db eval {DROP TABLE IF EXISTS temp.syntax} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + v INTEGER GARBAGE + ); + } +} {1 {unable to parse column 0}} + +# Extraneous type info is not ignored. +do_test recover-syntax-10.2 { + db eval {DROP TABLE IF EXISTS temp.syntax} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + v INTEGER NOT NULL GARBAGE + ); + } +} {1 {unable to parse column 0}} + +# Multiple types don't work. +do_test recover-syntax-10.3 { + db eval {DROP TABLE IF EXISTS temp.syntax} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + v INTEGER FLOAT BLOB + ); + } +} {1 {unable to parse column 0}} + +# Multiple types don't work. +do_test recover-syntax-10.4 { + db eval {DROP TABLE IF EXISTS temp.syntax} + catchsql { + CREATE VIRTUAL TABLE temp.syntax USING recover( + backing, + v INTEGER NOT NULL TEXT + ); + } +} {1 {unable to parse column 0}} + +finish_test diff --git a/third_party/sqlite/src/test/recover1.test b/third_party/sqlite/src/test/recover1.test new file mode 100644 index 0000000..e38531b --- /dev/null +++ b/third_party/sqlite/src/test/recover1.test @@ -0,0 +1,282 @@ +# 2012 January 4 {} +# +# 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 implements regression tests for SQLite library. +# +# Use tables to test leaf-node reading, and also type checking. +# +# $Id$ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# A really basic table with manifest typing and a row of each type. +db close +sqlite3 db test.db +db eval { + DROP TABLE IF EXISTS types; + CREATE TABLE types (rowtype TEXT, value); + INSERT INTO types VALUES ("NULL", NULL); + INSERT INTO types VALUES ("INTEGER", 17); + INSERT INTO types VALUES ("FLOAT", 3.1415927); + INSERT INTO types VALUES ("TEXT", "This is text"); + INSERT INTO types VALUES ("BLOB", CAST("This is a blob" AS BLOB)); + + -- Same contents, with an alias for rowid. Testing separately + -- because it changes the structure of the data (the alias column is + -- serialized as NULL). + DROP TABLE IF EXISTS types2; + CREATE TABLE types2 (id INTEGER PRIMARY KEY, rowtype TEXT, value); + INSERT INTO types2 (id, rowtype, value) + SELECT rowid, rowtype, value FROM types; +} + +# Baseline results. +do_test recover-types-0.0 { + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types} +} {1 NULL {} null 2 INTEGER 17 integer 3 FLOAT 3.1415927 real 4 TEXT {This is text} text 5 BLOB {This is a blob} blob} + +# With no restrictions, recover table shows identical results. +do_test recover-types-0.1 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {1 NULL {} null 2 INTEGER 17 integer 3 FLOAT 3.1415927 real 4 TEXT {This is text} text 5 BLOB {This is a blob} blob} + +# Restrict by INTEGER +do_test recover-types-1.0 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value INTEGER + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {1 NULL {} null 2 INTEGER 17 integer} + +# Restrict by INTEGER NOT NULL +do_test recover-types-1.1 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value INTEGER NOT NULL + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {2 INTEGER 17 integer} + +# Restrict by FLOAT +do_test recover-types-2.0 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value FLOAT + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {1 NULL {} null 2 INTEGER 17.0 real 3 FLOAT 3.1415927 real} + +# Restrict by FLOAT NOT NULL +do_test recover-types-2.1 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value FLOAT NOT NULL + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {2 INTEGER 17.0 real 3 FLOAT 3.1415927 real} + +# Restrict by FLOAT STRICT +do_test recover-types-2.2 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value FLOAT STRICT + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {1 NULL {} null 3 FLOAT 3.1415927 real} + +# Restrict by FLOAT STRICT NOT NULL +do_test recover-types-2.3 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value FLOAT STRICT NOT NULL + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {3 FLOAT 3.1415927 real} + +# Restrict by TEXT +do_test recover-types-3.0 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value TEXT + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {1 NULL {} null 4 TEXT {This is text} text 5 BLOB {This is a blob} blob} + +# Restrict by TEXT NOT NULL +do_test recover-types-3.1 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value TEXT NOT NULL + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {4 TEXT {This is text} text 5 BLOB {This is a blob} blob} + +# Restrict by TEXT STRICT +do_test recover-types-3.2 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value TEXT STRICT + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {1 NULL {} null 4 TEXT {This is text} text} + +# Restrict by TEXT STRICT NOT NULL +do_test recover-types-3.3 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value TEXT STRICT NOT NULL + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {4 TEXT {This is text} text} + +# Restrict by BLOB +do_test recover-types-4.0 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value BLOB + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {1 NULL {} null 5 BLOB {This is a blob} blob} + +# Restrict by BLOB NOT NULL +do_test recover-types-4.1 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value BLOB NOT NULL + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {5 BLOB {This is a blob} blob} + +# Manifest typing. +do_test recover-types-5.0 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {1 NULL {} null 2 INTEGER 17 integer 3 FLOAT 3.1415927 real 4 TEXT {This is text} text 5 BLOB {This is a blob} blob} + +# Should get same results specifying manifest typing explicitly. +do_test recover-types-5.1 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value ANY + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {1 NULL {} null 2 INTEGER 17 integer 3 FLOAT 3.1415927 real 4 TEXT {This is text} text 5 BLOB {This is a blob} blob} + +# Same results, skipping the NULL row. +do_test recover-types-5.2 { + db eval { + DROP TABLE IF EXISTS temp.types_recover; + CREATE VIRTUAL TABLE temp.types_recover USING recover( + types, + rowtype TEXT, + value ANY NOT NULL + ); + } + execsql {SELECT rowid, rowtype, value, TYPEOF(value) FROM types_recover} +} {2 INTEGER 17 integer 3 FLOAT 3.1415927 real 4 TEXT {This is text} text 5 BLOB {This is a blob} blob} + +# Test ROWID values. +do_test recover-types-6.0 { + db eval { + DROP TABLE IF EXISTS temp.types2_recover; + CREATE VIRTUAL TABLE temp.types2_recover USING recover( + types2, + id ROWID, + rowtype TEXT, + value + ); + } + execsql {SELECT rowid, id, rowtype, value, TYPEOF(value) FROM types2_recover} +} {1 1 NULL {} null 2 2 INTEGER 17 integer 3 3 FLOAT 3.1415927 real 4 4 TEXT {This is text} text 5 5 BLOB {This is a blob} blob} + +# ROWID NOT NULL is identical. +do_test recover-types-6.1 { + db eval { + DROP TABLE IF EXISTS temp.types2_recover; + CREATE VIRTUAL TABLE temp.types2_recover USING recover( + types2, + id ROWID NOT NULL, + rowtype TEXT, + value + ); + } + execsql {SELECT rowid, id, rowtype, value, TYPEOF(value) FROM types2_recover} +} {1 1 NULL {} null 2 2 INTEGER 17 integer 3 3 FLOAT 3.1415927 real 4 4 TEXT {This is text} text 5 5 BLOB {This is a blob} blob} + +finish_test |