| /* |
| ** 2013-11-14 |
| ** |
| ** 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 "schema2" virtual table |
| ** for displaying the content of various internal objects associated with |
| ** the parsed schema. |
| */ |
| |
| #ifndef SQLITE_AMALGAMATION |
| # include "sqliteInt.h" |
| #endif |
| |
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
| |
| static const char azSchema2[] = |
| "CREATE TABLE x(" |
| " dbname STRING," /* Name of attached database */ |
| " tblname STRING," /* Name of the table */ |
| " idxname STRING," /* Index name or NULL if not applicable */ |
| " cnum INT," /* Column number or NULL if not applicable */ |
| " attr STRING," /* Attribute name */ |
| " value STRING " /* Attribute value */ |
| ");" |
| ; |
| |
| typedef struct Schema2Table Schema2Table; |
| typedef struct Schema2Cursor Schema2Cursor; |
| typedef struct Schema2Row Schema2Row; |
| |
| /* |
| ** A single row of the result |
| */ |
| struct Schema2Row { |
| const char *zDb; /* Schema name. db->aDb[].zName */ |
| const char *zTbl; /* Table name. Might be NULL */ |
| const char *zIdx; /* Index name. Might be NULL */ |
| int iCol; /* Column number */ |
| const char *zAttr; /* Attribute */ |
| char *zValue; /* Value of the attribute */ |
| Schema2Row *pNext; /* Next row */ |
| }; |
| |
| /* |
| ** A cursor for iterating through internal schema information |
| */ |
| struct Schema2Cursor { |
| sqlite3_vtab_cursor base; /* Base class. Must be first */ |
| int iRowid; /* Current rowid */ |
| Schema2Row *pAll; /* All rows */ |
| Schema2Row *pCurrent; /* Current row */ |
| Schema2Row *pLast; /* Last row */ |
| }; |
| |
| /* |
| ** The complete Schema2 virtual table |
| */ |
| struct Schema2Table { |
| sqlite3_vtab base; /* Base class. Must be first */ |
| sqlite3 *db; /* The database connection that owns this table */ |
| }; |
| |
| /* |
| ** Connect to or create an Schema2 virtual table. |
| */ |
| static int schema2Connect( |
| sqlite3 *db, |
| void *pAux, |
| int argc, const char *const*argv, |
| sqlite3_vtab **ppVtab, |
| char **pzErr |
| ){ |
| Schema2Table *pTab; |
| |
| pTab = (Schema2Table *)sqlite3_malloc(sizeof(Schema2Table)); |
| memset(pTab, 0, sizeof(Schema2Table)); |
| pTab->db = db; |
| |
| sqlite3_declare_vtab(db, azSchema2); |
| *ppVtab = &pTab->base; |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** Disconnect from or destroy an Schema2 virtual table. |
| */ |
| static int schema2Disconnect(sqlite3_vtab *pVtab){ |
| sqlite3_free(pVtab); |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** There is no "best-index". This virtual table always does a complete |
| ** scan. |
| */ |
| static int schema2BestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ |
| pIdxInfo->estimatedCost = 10.0; |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** Open a new Schema2 cursor. |
| */ |
| static int schema2Open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ |
| Schema2Cursor *pCsr; |
| pCsr = (Schema2Cursor *)sqlite3_malloc(sizeof(Schema2Cursor)); |
| memset(pCsr, 0, sizeof(Schema2Cursor)); |
| pCsr->base.pVtab = pVTab; |
| *ppCursor = (sqlite3_vtab_cursor *)pCsr; |
| return SQLITE_OK; |
| } |
| |
| static void schema2ResetCsr(Schema2Cursor *pCsr){ |
| Schema2Row *pRow, *pNextRow; |
| for(pRow=pCsr->pAll; pRow; pRow=pNextRow){ |
| pNextRow = pRow->pNext; |
| sqlite3_free(pRow->zValue); |
| sqlite3_free(pRow); |
| } |
| pCsr->pAll = 0; |
| pCsr->pCurrent = 0; |
| pCsr->pLast = 0; |
| pCsr->iRowid = 0; |
| } |
| |
| /* |
| ** Close a statvfs cursor. |
| */ |
| static int schema2Close(sqlite3_vtab_cursor *pCursor){ |
| Schema2Cursor *pCsr = (Schema2Cursor *)pCursor; |
| schema2ResetCsr(pCsr); |
| sqlite3_free(pCsr); |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** Move a statvfs cursor to the next entry. |
| */ |
| static int schema2Next(sqlite3_vtab_cursor *pCursor){ |
| Schema2Cursor *pCsr = (Schema2Cursor *)pCursor; |
| if( pCsr->pCurrent==0 ){ |
| pCsr->pCurrent = pCsr->pAll; |
| }else{ |
| pCsr->pCurrent = pCsr->pCurrent->pNext; |
| } |
| pCsr->iRowid++; |
| return SQLITE_OK; |
| } |
| |
| static int schema2Eof(sqlite3_vtab_cursor *pCursor){ |
| Schema2Cursor *pCsr = (Schema2Cursor *)pCursor; |
| return pCsr->pCurrent==0 && pCsr->iRowid>0; |
| } |
| |
| /* Append a single to the cursor pCsr */ |
| static void schema2AppendRow( |
| Schema2Cursor *pCsr, |
| const char *zDb, |
| const char *zTbl, |
| const char *zIdx, |
| int iCol, |
| const char *zAttr, |
| const char *zValue, |
| ... |
| ){ |
| Schema2Row *pRow = sqlite3_malloc( sizeof(*pRow) ); |
| va_list ap; |
| if( pRow==0 ); |
| pRow->zDb = zDb; |
| pRow->zTbl = zTbl; |
| pRow->zIdx = zIdx; |
| pRow->iCol = iCol; |
| pRow->zAttr = zAttr; |
| va_start(ap, zValue); |
| pRow->zValue = sqlite3_vmprintf(zValue, ap); |
| va_end(ap); |
| if( pCsr->pLast==0 ){ |
| pCsr->pAll = pRow; |
| }else{ |
| pCsr->pLast->pNext = pRow; |
| } |
| pCsr->pLast = pRow; |
| } |
| |
| /* Append rows for index pIdx of table pTab which is part of the zDb schema */ |
| static void schema2AppendIndex( |
| Schema2Cursor *pCsr, |
| const char *zDb, |
| Table *pTab, |
| Index *pIdx |
| ){ |
| const char *zTbl = pTab->zName; |
| const char *zIdx = pIdx->zName; |
| int i; |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"zColAff","%s",pIdx->zColAff); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"tnum","%d",pIdx->tnum); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"szIdxRow","%d",pIdx->szIdxRow); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"nKeyCol","%d",pIdx->nKeyCol); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"nColumn","%d",pIdx->nColumn); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"onError","%d",pIdx->onError); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"autoIndex","%d",pIdx->autoIndex); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"bUnordered","%d",pIdx->bUnordered); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"uniqNotNull","%d",pIdx->uniqNotNull); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"isResized","%d",pIdx->isResized); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,-1,"isCovering","%d",pIdx->isCovering); |
| for(i=0; i<pIdx->nColumn; i++){ |
| i16 x = pIdx->aiColumn[i]; |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,i,"zName","%s", |
| x>=0?pTab->aCol[x].zName:"rowid"); |
| schema2AppendRow(pCsr,zDb,zTbl,zIdx,i,"aiRowEst","%lld", |
| (sqlite3_int64)pIdx->aiRowEst[i]); |
| } |
| } |
| |
| /* Append rows for table pTab which is part of the zDb schema */ |
| static void schema2AppendTable( |
| Schema2Cursor *pCsr, |
| const char *zDb, |
| Table *pTab |
| ){ |
| const char *zTbl = pTab->zName; |
| int i; |
| Index *pIdx; |
| schema2AppendRow(pCsr,zDb,zTbl,0,-1,"zColAff","%s",pTab->zColAff); |
| schema2AppendRow(pCsr,zDb,zTbl,0,-1,"nRowEst", |
| "%lld",(sqlite3_int64)pTab->nRowEst); |
| schema2AppendRow(pCsr,zDb,zTbl,0,-1,"tnum","%d",pTab->tnum); |
| schema2AppendRow(pCsr,zDb,zTbl,0,-1,"iPKey","%d",pTab->iPKey); |
| schema2AppendRow(pCsr,zDb,zTbl,0,-1,"nCol","%d",pTab->nCol); |
| schema2AppendRow(pCsr,zDb,zTbl,0,-1,"nRef","%d",pTab->nRef); |
| schema2AppendRow(pCsr,zDb,zTbl,0,-1,"szTabRow","%d",pTab->szTabRow); |
| schema2AppendRow(pCsr,zDb,zTbl,0,-1,"tabFlags","%d",pTab->tabFlags); |
| for(i=0; i<pTab->nCol; i++){ |
| const Column *p = pTab->aCol + i; |
| schema2AppendRow(pCsr,zDb,zTbl,0,i,"zName","%s",p->zName); |
| schema2AppendRow(pCsr,zDb,zTbl,0,i,"zDflt","%s",p->zDflt); |
| schema2AppendRow(pCsr,zDb,zTbl,0,i,"zType","%s",p->zType); |
| schema2AppendRow(pCsr,zDb,zTbl,0,i,"zColl","%s",p->zColl); |
| schema2AppendRow(pCsr,zDb,zTbl,0,i,"notNull","%d",p->notNull); |
| schema2AppendRow(pCsr,zDb,zTbl,0,i,"affinity","%c",p->affinity); |
| schema2AppendRow(pCsr,zDb,zTbl,0,i,"szEst","%d",p->szEst); |
| schema2AppendRow(pCsr,zDb,zTbl,0,i,"colFlags","%04x",p->colFlags); |
| } |
| for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ |
| schema2AppendIndex(pCsr, zDb, pTab, pIdx); |
| } |
| } |
| |
| |
| /* Append rows for schema object pSchema */ |
| static void schema2AppendSchema( |
| Schema2Cursor *pCsr, |
| const char *zDb, |
| Schema *pSchema |
| ){ |
| HashElem *i; |
| schema2AppendRow(pCsr,zDb,0,0,-1,"generation","%d", pSchema->iGeneration); |
| schema2AppendRow(pCsr,zDb,0,0,-1,"file_format","%d", pSchema->file_format); |
| schema2AppendRow(pCsr,zDb,0,0,-1,"enc","%d", pSchema->enc); |
| schema2AppendRow(pCsr,zDb,0,0,-1,"flags","%d", pSchema->flags); |
| schema2AppendRow(pCsr,zDb,0,0,-1,"cache_size","%d", pSchema->cache_size); |
| for(i=sqliteHashFirst(&pSchema->tblHash); i; i=sqliteHashNext(i)){ |
| schema2AppendTable(pCsr, zDb, (Table*)sqliteHashData(i)); |
| } |
| } |
| |
| |
| static int schema2Filter( |
| sqlite3_vtab_cursor *pCursor, |
| int idxNum, const char *idxStr, |
| int argc, sqlite3_value **argv |
| ){ |
| int iDb; |
| Schema2Cursor *pCsr = (Schema2Cursor*)pCursor; |
| Schema2Table *pTab = (Schema2Table*)(pCursor->pVtab); |
| sqlite3 *db = pTab->db; |
| schema2ResetCsr(pCsr); |
| for(iDb=0; iDb<db->nDb; iDb++){ |
| const char *zDb = db->aDb[iDb].zName; |
| Schema *pSchema = db->aDb[iDb].pSchema; |
| schema2AppendRow(pCsr,zDb,0,0,-1,"safety_level", |
| "%d", db->aDb[iDb].safety_level); |
| schema2AppendSchema(pCsr, zDb, pSchema); |
| } |
| return schema2Next(pCursor); |
| } |
| |
| static int schema2Column( |
| sqlite3_vtab_cursor *pCursor, |
| sqlite3_context *ctx, |
| int i |
| ){ |
| Schema2Cursor *pCsr = (Schema2Cursor *)pCursor; |
| Schema2Row *pRow = pCsr->pCurrent; |
| if( pRow==0 ) return SQLITE_OK; |
| switch( i ){ |
| case 0: /* dbname */ |
| sqlite3_result_text(ctx, pRow->zDb, -1, SQLITE_STATIC); |
| break; |
| case 1: /* tblname */ |
| if( pRow->zTbl ) sqlite3_result_text(ctx, pRow->zTbl, -1, SQLITE_STATIC); |
| break; |
| case 2: /* idxname */ |
| if( pRow->zIdx ) sqlite3_result_text(ctx, pRow->zIdx, -1, SQLITE_STATIC); |
| break; |
| case 3: /* cnum */ |
| if( pRow->iCol>=0 ) sqlite3_result_int(ctx, pRow->iCol); |
| break; |
| case 4: /* attr */ |
| sqlite3_result_text(ctx, pRow->zAttr, -1, SQLITE_STATIC); |
| break; |
| case 5: /* value */ |
| if( pRow->zValue ) sqlite3_result_text(ctx,pRow->zValue,-1,SQLITE_STATIC); |
| break; |
| } |
| return SQLITE_OK; |
| } |
| |
| static int schema2Rowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ |
| Schema2Cursor *pCsr = (Schema2Cursor *)pCursor; |
| *pRowid = pCsr->iRowid; |
| return SQLITE_OK; |
| } |
| |
| int sqlite3_schema2_register(sqlite3 *db){ |
| static sqlite3_module schema2_module = { |
| 0, /* iVersion */ |
| schema2Connect, /* xCreate */ |
| schema2Connect, /* xConnect */ |
| schema2BestIndex, /* xBestIndex */ |
| schema2Disconnect, /* xDisconnect */ |
| schema2Disconnect, /* xDestroy */ |
| schema2Open, /* xOpen - open a cursor */ |
| schema2Close, /* xClose - close a cursor */ |
| schema2Filter, /* xFilter - configure scan constraints */ |
| schema2Next, /* xNext - advance a cursor */ |
| schema2Eof, /* xEof - check for end of scan */ |
| schema2Column, /* xColumn - read data */ |
| schema2Rowid, /* xRowid - read data */ |
| 0, /* xUpdate */ |
| 0, /* xBegin */ |
| 0, /* xSync */ |
| 0, /* xCommit */ |
| 0, /* xRollback */ |
| 0, /* xFindMethod */ |
| 0, /* xRename */ |
| }; |
| sqlite3_create_module(db, "schema2", &schema2_module, 0); |
| return SQLITE_OK; |
| } |
| |
| #endif |
| |
| #if defined(SQLITE_TEST) || TCLSH==2 |
| #include <tcl.h> |
| |
| static int test_schema2( |
| void *clientData, |
| Tcl_Interp *interp, |
| int objc, |
| Tcl_Obj *CONST objv[] |
| ){ |
| #ifdef SQLITE_OMIT_VIRTUALTABLE |
| Tcl_AppendResult(interp, "schema2 not available because of " |
| "SQLITE_OMIT_VIRTUALTABLE", (void*)0); |
| return TCL_ERROR; |
| #else |
| struct SqliteDb { sqlite3 *db; }; |
| char *zDb; |
| Tcl_CmdInfo cmdInfo; |
| |
| if( objc!=2 ){ |
| Tcl_WrongNumArgs(interp, 1, objv, "DB"); |
| return TCL_ERROR; |
| } |
| |
| zDb = Tcl_GetString(objv[1]); |
| if( Tcl_GetCommandInfo(interp, zDb, &cmdInfo) ){ |
| sqlite3* db = ((struct SqliteDb*)cmdInfo.objClientData)->db; |
| sqlite3_schema2_register(db); |
| } |
| return TCL_OK; |
| #endif |
| } |
| |
| int SqlitetestSchema2_Init(Tcl_Interp *interp){ |
| Tcl_CreateObjCommand(interp, "register_schema2_vtab", test_schema2, 0, 0); |
| return TCL_OK; |
| } |
| #endif /* if defined(SQLITE_TEST) || TCLSH==2 */ |