]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the experimental dbdata extension.
authordan <dan@noemail.net>
Wed, 17 Apr 2019 21:17:22 +0000 (21:17 +0000)
committerdan <dan@noemail.net>
Wed, 17 Apr 2019 21:17:22 +0000 (21:17 +0000)
FossilOrigin-Name: a3ab58832935e1399ecc7e4d8daefa3a6afa6b301792ce7176bc5d7c173510fb

ext/misc/dbdata.c [new file with mode: 0644]
manifest
manifest.uuid
test/dbdata.test [new file with mode: 0644]

diff --git a/ext/misc/dbdata.c b/ext/misc/dbdata.c
new file mode 100644 (file)
index 0000000..c0ad8c1
--- /dev/null
@@ -0,0 +1,620 @@
+/*
+** 2019-04-17
+**
+** 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 implementation of the eponymous "sqlite_dbdata"
+** virtual table. sqlite_dbdata is used to extract data directly from a
+** database b-tree page and its associated overflow pages, bypassing the b-tree
+** layer. The table schema is equivalent to:
+**
+**     CREATE TABLE sqlite_dbdata(
+**       pgno INTEGER,
+**       cell INTEGER,
+**       field INTEGER,
+**       value ANY,
+**       schema TEXT HIDDEN
+**     );
+**
+** Each page of the database is inspected. If it cannot be interpreted as a
+** b-tree page, or if it is a b-tree page containing 0 entries, the
+** sqlite_dbdata table contains no rows for that page.  Otherwise, the table
+** contains one row for each field in the record associated with each
+** cell on the page. For intkey b-trees, the key value is stored in field -1.
+**
+** For example, for the database:
+**
+**     CREATE TABLE t1(a, b);     -- root page is page 2
+**     INSERT INTO t1(rowid, a, b) VALUES(5, 'v', 'five');
+**     INSERT INTO t1(rowid, a, b) VALUES(10, 'x', 'ten');
+**
+** the sqlite_dbdata table contains, as well as from entries related to 
+** page 1, content equivalent to:
+**
+**     INSERT INTO sqlite_dbdata(pgno, cell, field, value) VALUES
+**         (2, 0, -1, 5     ),
+**         (2, 0,  0, 'v'   ),
+**         (2, 0,  1, 'five'),
+**         (2, 1, -1, 10    ),
+**         (2, 1,  0, 'x'   ),
+**         (2, 1,  1, 'ten' );
+**
+** If database corruption is encountered, this module does not report an
+** error. Instead, it attempts to extract as much data as possible and
+** ignores the corruption.
+**
+** This module requires that the "sqlite_dbpage" eponymous virtual table be
+** available.
+*/
+#if !defined(SQLITEINT_H)
+#include "sqlite3ext.h"
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+#endif
+SQLITE_EXTENSION_INIT1
+#include <string.h>
+#include <assert.h>
+
+typedef struct DbdataTable DbdataTable;
+typedef struct DbdataCursor DbdataCursor;
+
+/* A cursor for the sqlite_dbdata table */
+struct DbdataCursor {
+  sqlite3_vtab_cursor base;       /* Base class.  Must be first */
+  sqlite3_stmt *pStmt;            /* For fetching database pages */
+
+  int iPgno;                      /* Current page number */
+  u8 *aPage;                      /* Buffer containing page */
+  int nPage;                      /* Size of aPage[] in bytes */
+  int nCell;                      /* Number of cells on aPage[] */
+  int iCell;                      /* Current cell number */
+  u8 *pRec;                       /* Buffer containing current record */
+  int nRec;                       /* Size of pRec[] in bytes */
+  int nField;                     /* Number of fields in pRec */
+  int iField;                     /* Current field number */
+  sqlite3_int64 iIntkey;          /* Integer key value */
+
+  sqlite3_int64 iRowid;
+};
+
+/* The sqlite_dbdata table */
+struct DbdataTable {
+  sqlite3_vtab base;              /* Base class.  Must be first */
+  sqlite3 *db;                    /* The database connection */
+};
+
+#define DBDATA_COLUMN_PGNO        0
+#define DBDATA_COLUMN_CELL        1
+#define DBDATA_COLUMN_FIELD       2
+#define DBDATA_COLUMN_VALUE       3
+#define DBDATA_COLUMN_SCHEMA      4
+
+#define DBDATA_SCHEMA             \
+      "CREATE TABLE x("           \
+      "  pgno INTEGER,"           \
+      "  cell INTEGER,"           \
+      "  field INTEGER,"          \
+      "  value ANY,"              \
+      "  schema TEXT HIDDEN"      \
+      ")"
+
+/*
+** Connect to the sqlite_dbdata virtual table.
+*/
+static int dbdataConnect(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  DbdataTable *pTab = 0;
+  int rc = sqlite3_declare_vtab(db, DBDATA_SCHEMA);
+
+  if( rc==SQLITE_OK ){
+    pTab = (DbdataTable*)sqlite3_malloc64(sizeof(DbdataTable));
+    if( pTab==0 ){
+      rc = SQLITE_NOMEM;
+    }else{
+      memset(pTab, 0, sizeof(DbdataTable));
+      pTab->db = db;
+    }
+  }
+
+  *ppVtab = (sqlite3_vtab*)pTab;
+  return rc;
+}
+
+/*
+** Disconnect from or destroy a dbdata virtual table.
+*/
+static int dbdataDisconnect(sqlite3_vtab *pVtab){
+  sqlite3_free(pVtab);
+  return SQLITE_OK;
+}
+
+/*
+**
+** This function interprets two types of constraints:
+**
+**       schema=?
+**       pgno=?
+**
+** If neither are present, idxNum is set to 0. If schema=? is present,
+** the 0x01 bit in idxNum is set. If pgno=? is present, the 0x02 bit
+** in idxNum is set.
+**
+** If both parameters are present, schema is in position 0 and pgno in
+** position 1.
+*/
+static int dbdataBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+  int i;
+  int iSchema = -1;
+  int iPgno = -1;
+
+  for(i=0; i<pIdxInfo->nConstraint; i++){
+    struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i];
+    if( p->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+      if( p->iColumn==DBDATA_COLUMN_SCHEMA ){
+        if( p->usable==0 ) return SQLITE_CONSTRAINT;
+        iSchema = i;
+      }
+      if( p->iColumn==DBDATA_COLUMN_PGNO && p->usable ){
+        iPgno = i;
+      }
+    }
+  }
+
+  if( iSchema>=0 ){
+    pIdxInfo->aConstraintUsage[iSchema].argvIndex = 1;
+    pIdxInfo->aConstraintUsage[iSchema].omit = 1;
+  }
+  if( iPgno>=0 ){
+    pIdxInfo->aConstraintUsage[iPgno].argvIndex = 1 + (iSchema>=0);
+    pIdxInfo->aConstraintUsage[iPgno].omit = 1;
+  }
+  pIdxInfo->idxNum = (iSchema>=0 ? 0x01 : 0x00) | (iPgno>=0 ? 0x02 : 0x00);
+  return SQLITE_OK;
+}
+
+/*
+** Open a new dbdata cursor.
+*/
+static int dbdataOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  DbdataCursor *pCsr;
+
+  pCsr = (DbdataCursor*)sqlite3_malloc64(sizeof(DbdataCursor));
+  if( pCsr==0 ){
+    return SQLITE_NOMEM;
+  }else{
+    memset(pCsr, 0, sizeof(DbdataCursor));
+    pCsr->base.pVtab = pVTab;
+  }
+
+  *ppCursor = (sqlite3_vtab_cursor *)pCsr;
+  return SQLITE_OK;
+}
+
+static void dbdataResetCursor(DbdataCursor *pCsr){
+  sqlite3_finalize(pCsr->pStmt);
+  pCsr->pStmt = 0;
+  pCsr->iPgno = 1;
+  pCsr->iCell = 0;
+  pCsr->iField = 0;
+}
+
+/*
+** Close a dbdata cursor.
+*/
+static int dbdataClose(sqlite3_vtab_cursor *pCursor){
+  DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+  dbdataResetCursor(pCsr);
+  sqlite3_free(pCsr);
+  return SQLITE_OK;
+}
+
+
+/* Decode big-endian integers */
+static unsigned int get_uint16(unsigned char *a){
+  return (a[0]<<8)|a[1];
+}
+static unsigned int get_uint32(unsigned char *a){
+  return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|a[3];
+}
+
+static int dbdataLoadPage(
+  DbdataCursor *pCsr, 
+  u32 pgno,
+  u8 **ppPage,
+  int *pnPage
+){
+  int rc2;
+  int rc = SQLITE_OK;
+  sqlite3_stmt *pStmt = pCsr->pStmt;
+
+  *ppPage = 0;
+  *pnPage = 0;
+  sqlite3_bind_int64(pStmt, 2, pgno);
+  if( SQLITE_ROW==sqlite3_step(pStmt) ){
+    int nCopy = sqlite3_column_bytes(pStmt, 0);
+    u8 *pPage = (u8*)sqlite3_malloc64(nCopy);
+    if( pPage==0 ){
+      rc = SQLITE_NOMEM;
+    }else{
+      const u8 *pCopy = sqlite3_column_blob(pStmt, 0);
+      memcpy(pPage, pCopy, nCopy);
+      *ppPage = pPage;
+      *pnPage = nCopy;
+    }
+  }
+  rc2 = sqlite3_reset(pStmt);
+  if( *ppPage==0 ) rc = rc2;
+
+  return rc;
+}
+
+/*
+** Read a varint.  Put the value in *pVal and return the number of bytes.
+*/
+static int dbdataGetVarint(const u8 *z, sqlite3_int64 *pVal){
+  sqlite3_int64 v = 0;
+  int i;
+  for(i=0; i<8; i++){
+    v = (v<<7) + (z[i]&0x7f);
+    if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; }
+  }
+  v = (v<<8) + (z[i]&0xff);
+  *pVal = v;
+  return 9;
+}
+
+/*
+** Move a dbdata cursor to the next entry in the file.
+*/
+static int dbdataNext(sqlite3_vtab_cursor *pCursor){
+  DbdataCursor *pCsr = (DbdataCursor *)pCursor;
+
+  pCsr->iRowid++;
+  while( 1 ){
+    int rc;
+
+    if( pCsr->aPage==0 ){
+      rc = dbdataLoadPage(pCsr, pCsr->iPgno, &pCsr->aPage, &pCsr->nPage);
+      if( rc!=SQLITE_OK ) return rc;
+      pCsr->iCell = 0;
+      pCsr->nCell = get_uint16(&pCsr->aPage[pCsr->iPgno==1 ? 103 : 3]);
+    }
+
+    /* If there is no record loaded, load it now. */
+    if( pCsr->pRec==0 ){
+      int iOff = (pCsr->iPgno==1 ? 100 : 0);
+      int bHasRowid = 0;
+      int nPointer = 0;
+      sqlite3_int64 nPayload = 0;
+      sqlite3_int64 nHdr = 0;
+      int iHdr;
+      int U, X;
+      int nLocal;
+
+      switch( pCsr->aPage[iOff] ){
+        case 0x02:
+          nPointer = 4;
+          break;
+        case 0x0a:
+          break;
+        case 0x0d:
+          bHasRowid = 1;
+          break;
+        default:
+          pCsr->iCell = pCsr->nCell;
+          break;
+      }
+      if( pCsr->iCell>=pCsr->nCell ){
+        sqlite3_free(pCsr->aPage);
+        pCsr->aPage = 0;
+        return SQLITE_OK;
+      }
+
+      iOff += 8 + nPointer + pCsr->iCell*2;
+      iOff = get_uint16(&pCsr->aPage[iOff]);
+
+      /* For an interior node cell, skip past the child-page number */
+      iOff += nPointer;
+
+      /* Load the "byte of payload including overflow" field */
+      iOff += dbdataGetVarint(&pCsr->aPage[iOff], &nPayload);
+
+      /* If this is a leaf intkey cell, load the rowid */
+      if( bHasRowid ){
+        iOff += dbdataGetVarint(&pCsr->aPage[iOff], &pCsr->iIntkey);
+      }
+
+      /* Allocate space for payload */
+      pCsr->pRec = (u8*)sqlite3_malloc64(nPayload);
+      if( pCsr->pRec==0 ) return SQLITE_NOMEM;
+      pCsr->nRec = nPayload;
+
+      U = pCsr->nPage;
+      if( bHasRowid ){
+        X = U-35;
+      }else{
+        X = ((U-12)*64/255)-23;
+      }
+      if( nPayload<=X ){
+        nLocal = nPayload;
+      }else{
+        int M, K;
+        M = ((U-12)*32/255)-23;
+        K = M+((nPayload-M)%(U-4));
+        if( K<=X ){
+          nLocal = K;
+        }else{
+          nLocal = M;
+        }
+      }
+
+      /* Load the nLocal bytes of payload */
+      memcpy(pCsr->pRec, &pCsr->aPage[iOff], nLocal);
+      iOff += nLocal;
+
+      /* Load content from overflow pages */
+      if( nPayload>nLocal ){
+        sqlite3_int64 nRem = nPayload - nLocal;
+        u32 pgnoOvfl = get_uint32(&pCsr->aPage[iOff]);
+        while( nRem>0 ){
+          u8 *aOvfl = 0;
+          int nOvfl = 0;
+          int nCopy;
+          rc = dbdataLoadPage(pCsr, pgnoOvfl, &aOvfl, &nOvfl);
+          assert( rc!=SQLITE_OK || nOvfl==pCsr->nPage );
+          if( rc!=SQLITE_OK ) return rc;
+
+          nCopy = U-4;
+          if( nCopy>nRem ) nCopy = nRem;
+          memcpy(&pCsr->pRec[nPayload-nRem], &aOvfl[4], nCopy);
+          nRem -= nCopy;
+
+          sqlite3_free(aOvfl);
+        }
+      }
+
+      /* Figure out how many fields in the record */
+      pCsr->nField = 0;
+      iHdr = dbdataGetVarint(pCsr->pRec, &nHdr);
+      while( iHdr<nHdr ){
+        sqlite3_int64 iDummy;
+        iHdr += dbdataGetVarint(&pCsr->pRec[iHdr], &iDummy);
+        pCsr->nField++;
+      }
+
+      pCsr->iField = (bHasRowid ? -2 : -1);
+    }
+
+    pCsr->iField++;
+    if( pCsr->iField<pCsr->nField ) return SQLITE_OK;
+
+    /* Advance to the next cell. The next iteration of the loop will load
+    ** the record and so on. */
+    sqlite3_free(pCsr->pRec);
+    pCsr->pRec = 0;
+    pCsr->iCell++;
+  }
+
+  assert( !"can't get here" );
+  return SQLITE_OK;
+}
+
+/* We have reached EOF if previous sqlite3_step() returned
+** anything other than SQLITE_ROW;
+*/
+static int dbdataEof(sqlite3_vtab_cursor *pCursor){
+  DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+  return pCsr->aPage==0;
+}
+
+/* Position a cursor back to the beginning.
+*/
+static int dbdataFilter(
+  sqlite3_vtab_cursor *pCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+  DbdataTable *pTab = (DbdataTable*)pCursor->pVtab;
+  int rc;
+  const char *zSchema = "main";
+
+  dbdataResetCursor(pCsr);
+  assert( pCsr->iPgno==1 );
+  if( idxNum & 0x01 ){
+    zSchema = sqlite3_value_text(argv[0]);
+  }
+  if( idxNum & 0x02 ){
+    pCsr->iPgno = sqlite3_value_int(argv[(idxNum & 0x01)]);
+  }
+
+  rc = sqlite3_prepare_v2(pTab->db, 
+      "SELECT data FROM sqlite_dbpage(?) WHERE pgno=?", -1,
+      &pCsr->pStmt, 0
+  );
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_bind_text(pCsr->pStmt, 1, zSchema, -1, SQLITE_TRANSIENT);
+  }
+  if( rc==SQLITE_OK ){
+    rc = dbdataNext(pCursor);
+  }
+  return rc;
+}
+
+static int dbdataValueBytes(int eType){
+  switch( eType ){
+    case 0: case 8: case 9:
+    case 10: case 11:
+      return 0;
+    case 1:
+      return 1;
+    case 2:
+      return 2;
+    case 3:
+      return 3;
+    case 4:
+      return 4;
+    case 5:
+      return 6;
+    case 6:
+    case 7:
+      return 8;
+    default:
+      return ((eType-12) / 2);
+  }
+}
+
+static void dbdataValue(sqlite3_context *pCtx, int eType, u8 *pData){
+  switch( eType ){
+    case 0: 
+    case 10: 
+    case 11: 
+      sqlite3_result_null(pCtx);
+      break;
+    
+    case 8: 
+      sqlite3_result_int(pCtx, 0);
+      break;
+    case 9:
+      sqlite3_result_int(pCtx, 1);
+      break;
+
+    case 1: case 2: case 3: case 4: case 5: case 6: case 7: {
+      sqlite3_uint64 v = (signed char)pData[0];
+      pData++;
+      switch( eType ){
+        case 7:
+        case 6:  v = (v<<16) + (pData[0]<<8) + pData[1];  pData += 2;
+        case 5:  v = (v<<16) + (pData[0]<<8) + pData[1];  pData += 2;
+        case 4:  v = (v<<8) + pData[0];  pData++;
+        case 3:  v = (v<<8) + pData[0];  pData++;
+        case 2:  v = (v<<8) + pData[0];  pData++;
+      }
+
+      if( eType==7 ){
+        double r;
+        memcpy(&r, &v, sizeof(r));
+        sqlite3_result_double(pCtx, r);
+      }else{
+        sqlite3_result_int64(pCtx, (sqlite3_int64)v);
+      }
+      break;
+    }
+
+    default: {
+      int n = ((eType-12) / 2);
+      if( eType % 2 ){
+        sqlite3_result_text(pCtx, pData, n, SQLITE_TRANSIENT);
+      }else{
+        sqlite3_result_blob(pCtx, pData, n, SQLITE_TRANSIENT);
+      }
+    }
+  }
+}
+
+/* Return a column for the sqlite_dbdata table */
+static int dbdataColumn(
+  sqlite3_vtab_cursor *pCursor, 
+  sqlite3_context *ctx, 
+  int i
+){
+  DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+  switch( i ){
+    case DBDATA_COLUMN_PGNO:
+      sqlite3_result_int64(ctx, pCsr->iPgno);
+      break;
+    case DBDATA_COLUMN_CELL:
+      sqlite3_result_int(ctx, pCsr->iCell);
+      break;
+    case DBDATA_COLUMN_FIELD:
+      sqlite3_result_int(ctx, pCsr->iField);
+      break;
+    case DBDATA_COLUMN_VALUE: {
+      if( pCsr->iField<0 ){
+        sqlite3_result_int64(ctx, pCsr->iIntkey);
+      }else{
+        int iHdr;
+        sqlite3_int64 iType;
+        sqlite3_int64 iOff;
+        int i;
+        iHdr = dbdataGetVarint(pCsr->pRec, &iOff);
+        for(i=0; i<pCsr->iField; i++){
+          iHdr += dbdataGetVarint(&pCsr->pRec[iHdr], &iType);
+          iOff += dbdataValueBytes(iType);
+        }
+        dbdataGetVarint(&pCsr->pRec[iHdr], &iType);
+
+        dbdataValue(ctx, iType, &pCsr->pRec[iOff]);
+      }
+      break;
+    }
+  }
+  return SQLITE_OK;
+}
+
+/* Return the ROWID for the sqlite_dbdata table */
+static int dbdataRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+  DbdataCursor *pCsr = (DbdataCursor*)pCursor;
+  *pRowid = pCsr->iRowid;
+  return SQLITE_OK;
+}
+
+
+/*
+** Invoke this routine to register the "sqlite_dbdata" virtual table module
+*/
+static int sqlite3DbdataRegister(sqlite3 *db){
+  static sqlite3_module dbdata_module = {
+    0,                            /* iVersion */
+    0,                            /* xCreate */
+    dbdataConnect,                /* xConnect */
+    dbdataBestIndex,              /* xBestIndex */
+    dbdataDisconnect,             /* xDisconnect */
+    0,                            /* xDestroy */
+    dbdataOpen,                   /* xOpen - open a cursor */
+    dbdataClose,                  /* xClose - close a cursor */
+    dbdataFilter,                 /* xFilter - configure scan constraints */
+    dbdataNext,                   /* xNext - advance a cursor */
+    dbdataEof,                    /* xEof - check for end of scan */
+    dbdataColumn,                 /* xColumn - read data */
+    dbdataRowid,                  /* xRowid - read data */
+    0,                            /* xUpdate */
+    0,                            /* xBegin */
+    0,                            /* xSync */
+    0,                            /* xCommit */
+    0,                            /* xRollback */
+    0,                            /* xFindMethod */
+    0,                            /* xRename */
+    0,                            /* xSavepoint */
+    0,                            /* xRelease */
+    0,                            /* xRollbackTo */
+    0                             /* xShadowName */
+  };
+  return sqlite3_create_module(db, "sqlite_dbdata", &dbdata_module, 0);
+}
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_dbdata_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  SQLITE_EXTENSION_INIT2(pApi);
+  return sqlite3DbdataRegister(db);
+}
index b6c54ac76e8e1126da6062e936af4e6fca8e2098..59bdc64972bacf35729a9e3116215f632ede165b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Faster\sand\ssmaller\simplementation\sof\ssqlite3StrICmp().
-D 2019-04-17T11:34:44.568
+C Add\sthe\sexperimental\sdbdata\sextension.
+D 2019-04-17T21:17:22.795
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -284,6 +284,7 @@ F ext/misc/closure.c dbfd8543b2a017ae6b1a5843986b22ddf99ff126ec9634a2f4047cd14c8
 F ext/misc/completion.c cec672d40604075bb341a7f11ac48393efdcd90a979269b8fe7977ea62d0547f
 F ext/misc/compress.c dd4f8a6d0baccff3c694757db5b430f3bbd821d8686d1fc24df55cf9f035b189
 F ext/misc/csv.c 7f047aeb68f5802e7ce6639292095d622a488bb43526ed04810e0649faa71ceb
+F ext/misc/dbdata.c 436a7883a7f1455c5d2853bd927c5ac31826b21c6e16ca11a21d7262d25ff838
 F ext/misc/dbdump.c baf6e37447c9d6968417b1cd34cbedb0b0ab3f91b5329501d8a8d5be3287c336
 F ext/misc/eval.c 4b4757592d00fd32e44c7a067e6a0e4839c81a4d57abc4131ee7806d1be3104e
 F ext/misc/explain.c d5c12962d79913ef774b297006872af1fccda388f61a11d37758f9179a09551f
@@ -786,6 +787,7 @@ F test/cursorhint2.test 6f3aa9cb19e7418967a10ec6905209bcbb5968054da855fc36c8beee
 F test/dataversion1.test 6e5e86ac681f0782e766ebcb56c019ae001522d114e0e111e5ebf68ccf2a7bb8
 F test/date.test 9b73bbeb1b82d9c1f44dec5cf563bf7da58d2373
 F test/date2.test 74c234bece1b016e94dd4ef9c8cc7a199a8806c0e2291cab7ba64bace6350b10
+F test/dbdata.test 6e791619d18e0cff2c79392de980ca0594368cdaa05326f043f866beb0c82614
 F test/dbfuzz.c 73047c920d6210e5912c87cdffd9a1c281d4252e
 F test/dbfuzz001.test e32d14465f1c77712896fda6a1ccc0f037b481c191c1696a9c44f6c9e4964faf
 F test/dbfuzz2-seed1.db e6225c6f3d7b63f9c5b6867146a5f329d997ab105bee64644dc2b3a2f2aebaee
@@ -1818,7 +1820,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f83156b50
-R dcc66ec8a55d4cba7e12163fe5fbfbcf
-U drh
-Z 40a9f6f7fce76c8f72a2cb9229b22090
+P 7ac500fb5abfe1ad60f2ffdcc8fbe5ccc1c641bbeed53f00940e9ff78788e53d
+R 9b65ccecf461800383c04a268416aaa8
+T *branch * dbdata
+T *sym-dbdata *
+T -sym-trunk *
+U dan
+Z f99e8e6e33bab5d12ccf1d6f1a7cae6f
index c8e27168f574fe9db1328192aa698863de18b3cd..dc49ef98d80564c23e273dcd2234fa20c0d70bda 100644 (file)
@@ -1 +1 @@
-7ac500fb5abfe1ad60f2ffdcc8fbe5ccc1c641bbeed53f00940e9ff78788e53d
\ No newline at end of file
+a3ab58832935e1399ecc7e4d8daefa3a6afa6b301792ce7176bc5d7c173510fb
\ No newline at end of file
diff --git a/test/dbdata.test b/test/dbdata.test
new file mode 100644 (file)
index 0000000..c4412a5
--- /dev/null
@@ -0,0 +1,54 @@
+# 2019-04-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.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this file is testing the sqlite_dbpage virtual table.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix dbdata
+
+ifcapable !vtab||!compound {
+  finish_test
+  return
+}
+db enable_load_extension 1
+if { [catch { db eval { SELECT load_extension('../dbdata') } }] } {
+  finish_test
+  return
+}
+
+do_execsql_test 1.0 {
+  CREATE TABLE T1(a, b);
+  INSERT INTO t1(rowid, a ,b) VALUES(5, 'v', 'five');
+  INSERT INTO t1(rowid, a, b) VALUES(10, 'x', 'ten');
+}
+
+do_execsql_test 1.1 {
+  SELECT pgno, cell, field, quote(value) FROM sqlite_dbdata WHERE pgno=2;
+} {
+  2 0 -1 5 
+  2 0  0 'v' 
+  2 0  1 'five' 
+  2 1 -1 10 
+  2 1  0 'x' 
+  2 1  1 'ten'
+}
+
+set big [string repeat big 2000]
+do_execsql_test 1.2 {
+  INSERT INTO t1 VALUES(NULL, $big);
+  SELECT value FROM sqlite_dbdata WHERE pgno=2 AND cell=2 AND field=1;
+} $big
+
+
+
+finish_test