mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
221817e537
- Now integrated with `make tags` for Emacs IDE features - Delete some old deprecated broken full-text search engines - Rename .h → .inc files that don't meet our definition of header - Make sure every #include line is normal form so tools understand See #162
418 lines
12 KiB
C
418 lines
12 KiB
C
/*
|
|
** 2017-10-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 contains an implementation of the "sqlite_dbpage" virtual table.
|
|
**
|
|
** The sqlite_dbpage virtual table is used to read or write whole raw
|
|
** pages of the database file. The pager interface is used so that
|
|
** uncommitted changes and changes recorded in the WAL file are correctly
|
|
** retrieved.
|
|
**
|
|
** Usage example:
|
|
**
|
|
** SELECT data FROM sqlite_dbpage('aux1') WHERE pgno=123;
|
|
**
|
|
** This is an eponymous virtual table so it does not need to be created before
|
|
** use. The optional argument to the sqlite_dbpage() table name is the
|
|
** schema for the database file that is to be read. The default schema is
|
|
** "main".
|
|
**
|
|
** The data field of sqlite_dbpage table can be updated. The new
|
|
** value must be a BLOB which is the correct page size, otherwise the
|
|
** update fails. Rows may not be deleted or inserted.
|
|
*/
|
|
#include "third_party/sqlite3/sqliteInt.inc" /* Requires access to internal data inc */
|
|
|
|
/* clang-format off */
|
|
|
|
#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) && \
|
|
!defined(SQLITE_OMIT_VIRTUALTABLE)
|
|
|
|
typedef struct DbpageTable DbpageTable;
|
|
typedef struct DbpageCursor DbpageCursor;
|
|
|
|
struct DbpageCursor {
|
|
sqlite3_vtab_cursor base; /* Base class. Must be first */
|
|
int pgno; /* Current page number */
|
|
int mxPgno; /* Last page to visit on this scan */
|
|
Pager *pPager; /* Pager being read/written */
|
|
DbPage *pPage1; /* Page 1 of the database */
|
|
int iDb; /* Index of database to analyze */
|
|
int szPage; /* Size of each page in bytes */
|
|
};
|
|
|
|
struct DbpageTable {
|
|
sqlite3_vtab base; /* Base class. Must be first */
|
|
sqlite3 *db; /* The database */
|
|
};
|
|
|
|
/* Columns */
|
|
#define DBPAGE_COLUMN_PGNO 0
|
|
#define DBPAGE_COLUMN_DATA 1
|
|
#define DBPAGE_COLUMN_SCHEMA 2
|
|
|
|
|
|
|
|
/*
|
|
** Connect to or create a dbpagevfs virtual table.
|
|
*/
|
|
static int dbpageConnect(
|
|
sqlite3 *db,
|
|
void *pAux,
|
|
int argc, const char *const*argv,
|
|
sqlite3_vtab **ppVtab,
|
|
char **pzErr
|
|
){
|
|
DbpageTable *pTab = 0;
|
|
int rc = SQLITE_OK;
|
|
|
|
sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
|
|
rc = sqlite3_declare_vtab(db,
|
|
"CREATE TABLE x(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)");
|
|
if( rc==SQLITE_OK ){
|
|
pTab = (DbpageTable *)sqlite3_malloc64(sizeof(DbpageTable));
|
|
if( pTab==0 ) rc = SQLITE_NOMEM_BKPT;
|
|
}
|
|
|
|
assert( rc==SQLITE_OK || pTab==0 );
|
|
if( rc==SQLITE_OK ){
|
|
memset(pTab, 0, sizeof(DbpageTable));
|
|
pTab->db = db;
|
|
}
|
|
|
|
*ppVtab = (sqlite3_vtab*)pTab;
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Disconnect from or destroy a dbpagevfs virtual table.
|
|
*/
|
|
static int dbpageDisconnect(sqlite3_vtab *pVtab){
|
|
sqlite3_free(pVtab);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** idxNum:
|
|
**
|
|
** 0 schema=main, full table scan
|
|
** 1 schema=main, pgno=?1
|
|
** 2 schema=?1, full table scan
|
|
** 3 schema=?1, pgno=?2
|
|
*/
|
|
static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
|
int i;
|
|
int iPlan = 0;
|
|
|
|
/* If there is a schema= constraint, it must be honored. Report a
|
|
** ridiculously large estimated cost if the schema= constraint is
|
|
** unavailable
|
|
*/
|
|
for(i=0; i<pIdxInfo->nConstraint; i++){
|
|
struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i];
|
|
if( p->iColumn!=DBPAGE_COLUMN_SCHEMA ) continue;
|
|
if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
|
|
if( !p->usable ){
|
|
/* No solution. */
|
|
return SQLITE_CONSTRAINT;
|
|
}
|
|
iPlan = 2;
|
|
pIdxInfo->aConstraintUsage[i].argvIndex = 1;
|
|
pIdxInfo->aConstraintUsage[i].omit = 1;
|
|
break;
|
|
}
|
|
|
|
/* If we reach this point, it means that either there is no schema=
|
|
** constraint (in which case we use the "main" schema) or else the
|
|
** schema constraint was accepted. Lower the estimated cost accordingly
|
|
*/
|
|
pIdxInfo->estimatedCost = 1.0e6;
|
|
|
|
/* Check for constraints against pgno */
|
|
for(i=0; i<pIdxInfo->nConstraint; i++){
|
|
struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i];
|
|
if( p->usable && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){
|
|
pIdxInfo->estimatedRows = 1;
|
|
pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE;
|
|
pIdxInfo->estimatedCost = 1.0;
|
|
pIdxInfo->aConstraintUsage[i].argvIndex = iPlan ? 2 : 1;
|
|
pIdxInfo->aConstraintUsage[i].omit = 1;
|
|
iPlan |= 1;
|
|
break;
|
|
}
|
|
}
|
|
pIdxInfo->idxNum = iPlan;
|
|
|
|
if( pIdxInfo->nOrderBy>=1
|
|
&& pIdxInfo->aOrderBy[0].iColumn<=0
|
|
&& pIdxInfo->aOrderBy[0].desc==0
|
|
){
|
|
pIdxInfo->orderByConsumed = 1;
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Open a new dbpagevfs cursor.
|
|
*/
|
|
static int dbpageOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
|
DbpageCursor *pCsr;
|
|
|
|
pCsr = (DbpageCursor *)sqlite3_malloc64(sizeof(DbpageCursor));
|
|
if( pCsr==0 ){
|
|
return SQLITE_NOMEM_BKPT;
|
|
}else{
|
|
memset(pCsr, 0, sizeof(DbpageCursor));
|
|
pCsr->base.pVtab = pVTab;
|
|
pCsr->pgno = -1;
|
|
}
|
|
|
|
*ppCursor = (sqlite3_vtab_cursor *)pCsr;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Close a dbpagevfs cursor.
|
|
*/
|
|
static int dbpageClose(sqlite3_vtab_cursor *pCursor){
|
|
DbpageCursor *pCsr = (DbpageCursor *)pCursor;
|
|
if( pCsr->pPage1 ) sqlite3PagerUnrefPageOne(pCsr->pPage1);
|
|
sqlite3_free(pCsr);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Move a dbpagevfs cursor to the next entry in the file.
|
|
*/
|
|
static int dbpageNext(sqlite3_vtab_cursor *pCursor){
|
|
int rc = SQLITE_OK;
|
|
DbpageCursor *pCsr = (DbpageCursor *)pCursor;
|
|
pCsr->pgno++;
|
|
return rc;
|
|
}
|
|
|
|
static int dbpageEof(sqlite3_vtab_cursor *pCursor){
|
|
DbpageCursor *pCsr = (DbpageCursor *)pCursor;
|
|
return pCsr->pgno > pCsr->mxPgno;
|
|
}
|
|
|
|
/*
|
|
** idxNum:
|
|
**
|
|
** 0 schema=main, full table scan
|
|
** 1 schema=main, pgno=?1
|
|
** 2 schema=?1, full table scan
|
|
** 3 schema=?1, pgno=?2
|
|
**
|
|
** idxStr is not used
|
|
*/
|
|
static int dbpageFilter(
|
|
sqlite3_vtab_cursor *pCursor,
|
|
int idxNum, const char *idxStr,
|
|
int argc, sqlite3_value **argv
|
|
){
|
|
DbpageCursor *pCsr = (DbpageCursor *)pCursor;
|
|
DbpageTable *pTab = (DbpageTable *)pCursor->pVtab;
|
|
int rc;
|
|
sqlite3 *db = pTab->db;
|
|
Btree *pBt;
|
|
|
|
/* Default setting is no rows of result */
|
|
pCsr->pgno = 1;
|
|
pCsr->mxPgno = 0;
|
|
|
|
if( idxNum & 2 ){
|
|
const char *zSchema;
|
|
assert( argc>=1 );
|
|
zSchema = (const char*)sqlite3_value_text(argv[0]);
|
|
pCsr->iDb = sqlite3FindDbName(db, zSchema);
|
|
if( pCsr->iDb<0 ) return SQLITE_OK;
|
|
}else{
|
|
pCsr->iDb = 0;
|
|
}
|
|
pBt = db->aDb[pCsr->iDb].pBt;
|
|
if( pBt==0 ) return SQLITE_OK;
|
|
pCsr->pPager = sqlite3BtreePager(pBt);
|
|
pCsr->szPage = sqlite3BtreeGetPageSize(pBt);
|
|
pCsr->mxPgno = sqlite3BtreeLastPage(pBt);
|
|
if( idxNum & 1 ){
|
|
assert( argc>(idxNum>>1) );
|
|
pCsr->pgno = sqlite3_value_int(argv[idxNum>>1]);
|
|
if( pCsr->pgno<1 || pCsr->pgno>pCsr->mxPgno ){
|
|
pCsr->pgno = 1;
|
|
pCsr->mxPgno = 0;
|
|
}else{
|
|
pCsr->mxPgno = pCsr->pgno;
|
|
}
|
|
}else{
|
|
assert( pCsr->pgno==1 );
|
|
}
|
|
if( pCsr->pPage1 ) sqlite3PagerUnrefPageOne(pCsr->pPage1);
|
|
rc = sqlite3PagerGet(pCsr->pPager, 1, &pCsr->pPage1, 0);
|
|
return rc;
|
|
}
|
|
|
|
static int dbpageColumn(
|
|
sqlite3_vtab_cursor *pCursor,
|
|
sqlite3_context *ctx,
|
|
int i
|
|
){
|
|
DbpageCursor *pCsr = (DbpageCursor *)pCursor;
|
|
int rc = SQLITE_OK;
|
|
switch( i ){
|
|
case 0: { /* pgno */
|
|
sqlite3_result_int(ctx, pCsr->pgno);
|
|
break;
|
|
}
|
|
case 1: { /* data */
|
|
DbPage *pDbPage = 0;
|
|
rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0);
|
|
if( rc==SQLITE_OK ){
|
|
sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage,
|
|
SQLITE_TRANSIENT);
|
|
}
|
|
sqlite3PagerUnref(pDbPage);
|
|
break;
|
|
}
|
|
default: { /* schema */
|
|
sqlite3 *db = sqlite3_context_db_handle(ctx);
|
|
sqlite3_result_text(ctx, db->aDb[pCsr->iDb].zDbSName, -1, SQLITE_STATIC);
|
|
break;
|
|
}
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
static int dbpageRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
|
|
DbpageCursor *pCsr = (DbpageCursor *)pCursor;
|
|
*pRowid = pCsr->pgno;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
static int dbpageUpdate(
|
|
sqlite3_vtab *pVtab,
|
|
int argc,
|
|
sqlite3_value **argv,
|
|
sqlite_int64 *pRowid
|
|
){
|
|
DbpageTable *pTab = (DbpageTable *)pVtab;
|
|
Pgno pgno;
|
|
DbPage *pDbPage = 0;
|
|
int rc = SQLITE_OK;
|
|
char *zErr = 0;
|
|
const char *zSchema;
|
|
int iDb;
|
|
Btree *pBt;
|
|
Pager *pPager;
|
|
int szPage;
|
|
|
|
if( pTab->db->flags & SQLITE_Defensive ){
|
|
zErr = "read-only";
|
|
goto update_fail;
|
|
}
|
|
if( argc==1 ){
|
|
zErr = "cannot delete";
|
|
goto update_fail;
|
|
}
|
|
pgno = sqlite3_value_int(argv[0]);
|
|
if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){
|
|
zErr = "cannot insert";
|
|
goto update_fail;
|
|
}
|
|
zSchema = (const char*)sqlite3_value_text(argv[4]);
|
|
iDb = zSchema ? sqlite3FindDbName(pTab->db, zSchema) : -1;
|
|
if( iDb<0 ){
|
|
zErr = "no such schema";
|
|
goto update_fail;
|
|
}
|
|
pBt = pTab->db->aDb[iDb].pBt;
|
|
if( pgno<1 || pBt==0 || pgno>(int)sqlite3BtreeLastPage(pBt) ){
|
|
zErr = "bad page number";
|
|
goto update_fail;
|
|
}
|
|
szPage = sqlite3BtreeGetPageSize(pBt);
|
|
if( sqlite3_value_type(argv[3])!=SQLITE_BLOB
|
|
|| sqlite3_value_bytes(argv[3])!=szPage
|
|
){
|
|
zErr = "bad page value";
|
|
goto update_fail;
|
|
}
|
|
pPager = sqlite3BtreePager(pBt);
|
|
rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pDbPage, 0);
|
|
if( rc==SQLITE_OK ){
|
|
rc = sqlite3PagerWrite(pDbPage);
|
|
if( rc==SQLITE_OK ){
|
|
memcpy(sqlite3PagerGetData(pDbPage),
|
|
sqlite3_value_blob(argv[3]),
|
|
szPage);
|
|
}
|
|
}
|
|
sqlite3PagerUnref(pDbPage);
|
|
return rc;
|
|
|
|
update_fail:
|
|
sqlite3_free(pVtab->zErrMsg);
|
|
pVtab->zErrMsg = sqlite3_mprintf("%s", zErr);
|
|
return SQLITE_ERROR;
|
|
}
|
|
|
|
/* Since we do not know in advance which database files will be
|
|
** written by the sqlite_dbpage virtual table, start a write transaction
|
|
** on them all.
|
|
*/
|
|
static int dbpageBegin(sqlite3_vtab *pVtab){
|
|
DbpageTable *pTab = (DbpageTable *)pVtab;
|
|
sqlite3 *db = pTab->db;
|
|
int i;
|
|
for(i=0; i<db->nDb; i++){
|
|
Btree *pBt = db->aDb[i].pBt;
|
|
if( pBt ) sqlite3BtreeBeginTrans(pBt, 1, 0);
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
** Invoke this routine to register the "dbpage" virtual table module
|
|
*/
|
|
int sqlite3DbpageRegister(sqlite3 *db){
|
|
static sqlite3_module dbpage_module = {
|
|
0, /* iVersion */
|
|
dbpageConnect, /* xCreate */
|
|
dbpageConnect, /* xConnect */
|
|
dbpageBestIndex, /* xBestIndex */
|
|
dbpageDisconnect, /* xDisconnect */
|
|
dbpageDisconnect, /* xDestroy */
|
|
dbpageOpen, /* xOpen - open a cursor */
|
|
dbpageClose, /* xClose - close a cursor */
|
|
dbpageFilter, /* xFilter - configure scan constraints */
|
|
dbpageNext, /* xNext - advance a cursor */
|
|
dbpageEof, /* xEof - check for end of scan */
|
|
dbpageColumn, /* xColumn - read data */
|
|
dbpageRowid, /* xRowid - read data */
|
|
dbpageUpdate, /* xUpdate */
|
|
dbpageBegin, /* 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_dbpage", &dbpage_module, 0);
|
|
}
|
|
#elif defined(SQLITE_ENABLE_DBPAGE_VTAB)
|
|
int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; }
|
|
#endif /* SQLITE_ENABLE_DBSTAT_VTAB */
|