/* ** 2018-02-10 ** ** 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. ** ****************************************************************************** */ #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 #ifndef SQLITE_OMIT_VIRTUALTABLE #ifndef SQLITE_AMALGAMATION typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned long u32; #define MIN(a,b) ((a)<(b) ? (a) : (b)) #if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) # define ALWAYS(X) (1) # define NEVER(X) (0) #elif !defined(NDEBUG) # define ALWAYS(X) ((X)?1:(assert(0),0)) # define NEVER(X) ((X)?(assert(0),1):0) #else # define ALWAYS(X) (X) # define NEVER(X) (X) #endif #endif /* SQLITE_AMALGAMATION */ #define ZONEFILE_MAGIC_NUMBER 0x464B3138 #define ZONEFILE_SZ_HEADER 26 #define ZONEFILE_SZ_KEYOFFSETS_ENTRY 20 #define ZONEFILE_DEFAULT_MAXAUTOFRAMESIZE (64*1024) #define ZONEFILE_DEFAULT_ENCRYPTION 0 #define ZONEFILE_DEFAULT_COMPRESSION 0 #define ZONEFILE_SCHEMA \ "CREATE TABLE z1(" \ " k INTEGER PRIMARY KEY," \ " v BLOB," \ " fileid INTEGER," \ " frame INTEGER," \ " ofst INTEGER," \ " sz INTEGER" \ ")" #define ZONEFILE_FILES_SCHEMA \ "CREATE TABLE z2(" \ " filename TEXT," \ " priority INTEGER," \ " ekey BLOB," \ " header JSON HIDDEN" \ ")" #include #include #include typedef struct ZonefileWrite ZonefileWrite; struct ZonefileWrite { int compressionTypeIndexData; int compressionTypeContent; int encryptionType; int maxAutoFrameSize; }; typedef struct ZonefileHeader ZonefileHeader; struct ZonefileHeader { u32 magicNumber; u8 compressionTypeIndexData; u8 compressionTypeContent; u32 byteOffsetDictionary; u32 byteOffsetFrames; u32 numFrames; u32 numKeys; u8 encryptionType; u8 encryptionKeyIdx; u8 extendedHeaderVersion; u8 extendedHeaderSize; }; typedef struct ZonefileBuffer ZonefileBuffer; struct ZonefileBuffer { u8 *a; int n; int nAlloc; }; /* ** Set the error message contained in context ctx to the results of ** vprintf(zFmt, ...). */ static void zonefileCtxError(sqlite3_context *ctx, const char *zFmt, ...){ char *zMsg = 0; va_list ap; va_start(ap, zFmt); zMsg = sqlite3_vmprintf(zFmt, ap); sqlite3_result_error(ctx, zMsg, -1); sqlite3_free(zMsg); va_end(ap); } static void zonefileTransferError(sqlite3_context *pCtx){ sqlite3 *db = sqlite3_context_db_handle(pCtx); const char *zErr = sqlite3_errmsg(db); sqlite3_result_error(pCtx, zErr, -1); } static int zonefilePrepare( sqlite3 *db, sqlite3_stmt **ppStmt, char **pzErr, const char *zFmt, ... ){ int rc; va_list ap; char *zSql; va_start(ap, zFmt); zSql = sqlite3_vmprintf(zFmt, ap); *ppStmt = 0; if( zSql ){ rc = sqlite3_prepare(db, zSql, -1, ppStmt, 0); if( rc!=SQLITE_OK ){ *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); } sqlite3_free(zSql); }else{ rc = SQLITE_NOMEM; } return rc; } static sqlite3_stmt *zonefileCtxPrepare( sqlite3_context *pCtx, const char *zFmt, ... ){ sqlite3_stmt *pRet = 0; va_list ap; char *zSql; va_start(ap, zFmt); zSql = sqlite3_vmprintf(zFmt, ap); if( zSql ){ sqlite3 *db = sqlite3_context_db_handle(pCtx); int rc = sqlite3_prepare(db, zSql, -1, &pRet, 0); if( rc!=SQLITE_OK ){ zonefileTransferError(pCtx); } sqlite3_free(zSql); }else{ sqlite3_result_error_nomem(pCtx); } return pRet; } /* ** Return zero if the two SQL values passed as arguments are equal, or ** non-zero otherwise. Values with different types are considered unequal, ** even if they both contain the same numeric value (e.g. 2 and 2.0). */ static int zonefileCompareValue(sqlite3_value *p1, sqlite3_value *p2){ int eType; assert( p1 ); if( p2==0 ) return 1; eType = sqlite3_value_type(p1); if( sqlite3_value_type(p2)!=eType ) return 1; switch( eType ){ case SQLITE_INTEGER: return sqlite3_value_int64(p1)!=sqlite3_value_int64(p2); case SQLITE_FLOAT: return sqlite3_value_double(p1)!=sqlite3_value_double(p2); case SQLITE_TEXT: case SQLITE_BLOB: { int n1 = sqlite3_value_bytes(p1); int n2 = sqlite3_value_bytes(p2); if( n1!=n2 ) return 1; return memcmp(sqlite3_value_blob(p1), sqlite3_value_blob(p2), n1); } default: assert( eType==SQLITE_NULL); } return 0; } int zonefileIsAutoFrame(sqlite3_value *pFrame){ return ( sqlite3_value_type(pFrame)==SQLITE_INTEGER && sqlite3_value_int64(pFrame)==-1 ); } static int zonefileGetParams( sqlite3_context *pCtx, /* Leave any error message here */ const char *zJson, /* JSON configuration parameter (or NULL) */ ZonefileWrite *p /* Populate this object before returning */ ){ memset(p, 0, sizeof(ZonefileWrite)); p->maxAutoFrameSize = ZONEFILE_DEFAULT_MAXAUTOFRAMESIZE; return SQLITE_OK; } /* ** Check that there is room in buffer pBuf for at least nByte bytes more ** data. If not, attempt to allocate more space. If the allocation attempt ** fails, leave an error message in context pCtx and return SQLITE_ERROR. ** ** If no error occurs, SQLITE_OK is returned. */ static int zonefileBufferGrow( sqlite3_context *pCtx, ZonefileBuffer *pBuf, int nByte ){ int nReq = pBuf->n + nByte; if( nReq>pBuf->nAlloc ){ u8 *aNew; int nNew = pBuf->nAlloc ? pBuf->nAlloc*2 : 128; while( nNewa, nNew); if( aNew==0 ){ sqlite3_result_error_nomem(pCtx); return SQLITE_ERROR; } pBuf->a = aNew; pBuf->nAlloc = nNew; } return SQLITE_OK; } static void zonefileBufferFree(ZonefileBuffer *pBuf){ sqlite3_free(pBuf->a); memset(pBuf, 0, sizeof(ZonefileBuffer)); } static void zonefilePut32(u8 *aBuf, u32 v){ aBuf[0] = (v >> 24) & 0xFF; aBuf[1] = (v >> 16) & 0xFF; aBuf[2] = (v >> 8) & 0xFF; aBuf[3] = v & 0xFF; } static u32 zonefileGet32(u8 *aBuf){ return (((u32)aBuf[0]) << 24) + (((u32)aBuf[1]) << 16) + (((u32)aBuf[2]) << 8) + (((u32)aBuf[3]) << 0); } static u64 zonefileGet64(u8 *aBuf){ return (((u64)aBuf[0]) << 56) + (((u64)aBuf[1]) << 48) + (((u64)aBuf[2]) << 40) + (((u64)aBuf[3]) << 32) + (((u64)aBuf[4]) << 24) + (((u64)aBuf[5]) << 16) + (((u64)aBuf[6]) << 8) + (((u64)aBuf[7]) << 0); } static void zonefileAppend32(ZonefileBuffer *pBuf, u32 v){ zonefilePut32(&pBuf->a[pBuf->n], v); pBuf->n += 4; } static void zonefileAppend64(ZonefileBuffer *pBuf, u64 v){ zonefileAppend32(pBuf, v>>32); zonefileAppend32(pBuf, v & 0xFFFFFFFF); } static void zonefileAppendBlob(ZonefileBuffer *pBuf, const u8 *p, int n){ memcpy(&pBuf->a[pBuf->n], p, n); pBuf->n += n; } static int zonefileFileWrite(FILE *pFd, const u8 *aBuf, int nBuf){ size_t res = fwrite(aBuf, 1, nBuf, pFd); return res!=nBuf ? SQLITE_ERROR : SQLITE_OK; } static int zonefileFileRead(FILE *pFd, u8 *aBuf, int nBuf, i64 iOff){ int rc = fseek(pFd, iOff, SEEK_SET); if( rc==0 ){ rc = fread(aBuf, 1, nBuf, pFd); rc = (rc==nBuf) ? SQLITE_OK : SQLITE_ERROR; } return rc; } static FILE *zonefileFileOpen(const char *zFile, int bWrite, char **pzErr){ FILE *pFd = fopen(zFile, bWrite ? "w" : "r"); if( pFd==0 ){ *pzErr = sqlite3_mprintf("failed to open file \"%s\" for %s", zFile, bWrite ? "writing" : "reading" ); } return pFd; } static void zonefileFileClose(FILE *pFd){ if( pFd ) fclose(pFd); } /* ** Function: zonefile_write(F,T[,J]) */ static void zonefileWriteFunc( sqlite3_context *pCtx, /* Context object */ int objc, /* Number of SQL arguments */ sqlite3_value **objv /* Array of SQL arguments */ ){ const char *zFile = 0; /* File to write to */ const char *zTbl = 0; /* Database object to read from */ const char *zJson = 0; /* JSON configuration parameters */ ZonefileWrite sWrite; /* Decoded JSON parameters */ int nKey = 0; /* Number of keys in new zonefile */ int nFrame = 0; /* Number of frames in new zonefile */ int szFrame = 0; /* Size of current frame */ sqlite3_stmt *pStmt = 0; /* SQL used to read data from source table */ FILE *pFd = 0; int rc; sqlite3_value *pPrev = 0; ZonefileBuffer sFrameIdx = {0, 0, 0}; ZonefileBuffer sKeyIdx = {0, 0, 0}; ZonefileBuffer sFrames = {0, 0, 0}; u8 aHdr[ZONEFILE_SZ_HEADER]; /* Space to assemble zonefile header */ assert( objc==2 || objc==3 ); zFile = (const char*)sqlite3_value_text(objv[0]); zTbl = (const char*)sqlite3_value_text(objv[1]); if( objc==3 ){ zJson = (const char*)sqlite3_value_text(objv[2]); } if( zonefileGetParams(pCtx, zJson, &sWrite) ) return; /* Prepare the SQL statement used to read data from the source table. This ** also serves to verify the suitability of the source table schema. */ pStmt = zonefileCtxPrepare(pCtx, "SELECT k, frame, v FROM %Q ORDER BY frame, idx, k", zTbl ); if( pStmt==0 ) return; /* Open a file-handle used to write out the zonefile */ pFd = fopen(zFile, "w"); if( pFd==0 ){ zonefileCtxError(pCtx, "error opening file \"%s\" (fopen())", zFile); sqlite3_finalize(pStmt); return; } while( SQLITE_ROW==sqlite3_step(pStmt) ){ sqlite3_int64 k = sqlite3_column_int64(pStmt, 0); sqlite3_value *pFrame = sqlite3_column_value(pStmt, 1); int nBlob = sqlite3_column_bytes(pStmt, 2); const u8 *pBlob = (const u8*)sqlite3_column_blob(pStmt, 2); int bAuto = zonefileIsAutoFrame(pFrame); if( zonefileCompareValue(pFrame, pPrev) || (bAuto && szFrame && (szFrame+nBlob)>sWrite.maxAutoFrameSize) ){ /* Add new entry to sFrameIdx */ szFrame = 0; if( zonefileBufferGrow(pCtx, &sFrameIdx, 4) ) goto zone_write_out; zonefileAppend32(&sFrameIdx, sFrames.n); sqlite3_value_free(pPrev); pPrev = sqlite3_value_dup(pFrame); if( pPrev==0 ){ sqlite3_result_error_nomem(pCtx); goto zone_write_out; } nFrame++; } /* Add new entry to sKeyIdx */ if( zonefileBufferGrow(pCtx, &sKeyIdx, ZONEFILE_SZ_KEYOFFSETS_ENTRY) ){ goto zone_write_out; } zonefileAppend64(&sKeyIdx, k); zonefileAppend32(&sKeyIdx, nFrame-1); zonefileAppend32(&sKeyIdx, szFrame); zonefileAppend32(&sKeyIdx, nBlob); /* Add data for new entry to sFrames */ if( zonefileBufferGrow(pCtx, &sFrames, nBlob) ) goto zone_write_out; zonefileAppendBlob(&sFrames, pBlob, nBlob); szFrame += nBlob; nKey++; } sqlite3_value_free(pPrev); pPrev = 0; /* Create the zonefile header in the in-memory buffer */ zonefilePut32(&aHdr[0], ZONEFILE_MAGIC_NUMBER); aHdr[4] = sWrite.compressionTypeIndexData; aHdr[5] = sWrite.compressionTypeContent; zonefilePut32(&aHdr[6], 0); /* Compression dictionary byte offset */ zonefilePut32(&aHdr[10], ZONEFILE_SZ_HEADER + sFrameIdx.n + sKeyIdx.n); zonefilePut32(&aHdr[14], nFrame); zonefilePut32(&aHdr[18], nKey); aHdr[22] = sWrite.encryptionType; aHdr[23] = 0; /* Encryption key index */ aHdr[24] = 0; /* extended header version */ aHdr[25] = 0; /* extended header size */ assert( ZONEFILE_SZ_HEADER==26 ); rc = zonefileFileWrite(pFd, aHdr, ZONEFILE_SZ_HEADER); if( rc==SQLITE_OK ) rc = zonefileFileWrite(pFd, sFrameIdx.a, sFrameIdx.n); if( rc==SQLITE_OK ) rc = zonefileFileWrite(pFd, sKeyIdx.a, sKeyIdx.n); if( rc==SQLITE_OK ) rc = zonefileFileWrite(pFd, sFrames.a, sFrames.n); if( rc ){ zonefileCtxError(pCtx, "error writing file \"%s\" (fwrite())", zFile); goto zone_write_out; } if( fclose(pFd) ){ zonefileCtxError(pCtx, "error writing file \"%s\" (fclose())", zFile); } pFd = 0; zone_write_out: if( pFd ) fclose(pFd); sqlite3_finalize(pStmt); zonefileBufferFree(&sFrameIdx); zonefileBufferFree(&sKeyIdx); zonefileBufferFree(&sFrames); } typedef struct ZonefileFilesTab ZonefileFilesTab; struct ZonefileFilesTab { sqlite3_vtab base; /* Base class - must be first */ sqlite3 *db; char *zBase; /* Name of this table */ char *zDb; /* Database containing this table */ sqlite3_stmt *pInsert; /* Insert into the %_shadow_file table */ sqlite3_stmt *pInsertIdx; /* Insert into the %_shadow_idx table */ sqlite3_stmt *pDeleteIdx; /* Delete by fileid from %_shadow_idx table */ sqlite3_stmt *pDelete; /* Delete by rowid from %_shadow_file table */ }; typedef struct ZonefileFilesCsr ZonefileFilesCsr; struct ZonefileFilesCsr { sqlite3_vtab_cursor base; /* Base class - must be first */ sqlite3_stmt *pSelect; }; /* ** This function does the work of xCreate (if bCreate!=0) or xConnect ** (if bCreate==0) for the zonefile_files module. */ static int zffCreateConnect( int bCreate, sqlite3 *db, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ ZonefileFilesTab *p; const char *zName = argv[2]; const char *zDb = argv[1]; int nName = strlen(zName); int nDb = strlen(zDb); int rc = SQLITE_OK; if( nName<6 || memcmp(&zName[nName-6], "_files", 6)!=0 ){ *pzErr = sqlite3_mprintf("do not create zonefile_files tables directly!"); *ppVtab = 0; return SQLITE_ERROR; } p = (ZonefileFilesTab*)sqlite3_malloc(sizeof(ZonefileFilesTab)+nName+1+nDb+1); if( !p ){ rc = SQLITE_NOMEM; }else{ memset(p, 0, sizeof(ZonefileFilesTab)); p->zBase = (char*)&p[1]; memcpy(p->zBase, zName, nName-6); p->zBase[nName-6] = '\0'; p->zDb = &p->zBase[nName+1]; memcpy(p->zDb, zDb, nDb+1); p->db = db; rc = sqlite3_declare_vtab(db, ZONEFILE_FILES_SCHEMA); } if( rc!=SQLITE_OK ){ sqlite3_free(p); p = 0; } *ppVtab = (sqlite3_vtab*)p; return rc; } /* ** zonefile_files virtual table module xCreate method. */ static int zffCreate( sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ return zffCreateConnect(1, db, argc, argv, ppVtab, pzErr); } /* ** zonefile_files virtual table module xConnect method. */ static int zffConnect( sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ return zffCreateConnect(0, db, argc, argv, ppVtab, pzErr); } /* ** zonefile_files virtual table module xDisconnect method. */ static int zffDisconnect(sqlite3_vtab *pVtab){ ZonefileFilesTab *pTab = (ZonefileFilesTab*)pVtab; sqlite3_finalize(pTab->pInsert); sqlite3_finalize(pTab->pDelete); sqlite3_finalize(pTab->pInsertIdx); sqlite3_finalize(pTab->pDeleteIdx); sqlite3_free(pTab); return SQLITE_OK; } /* ** zonefile_files virtual table module xBestIndex method. */ static int zffBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pInfo){ return SQLITE_OK; } /* ** zonefile_files virtual table module xOpen method. */ static int zffOpen(sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppCursor){ ZonefileFilesCsr *pCsr; pCsr = (ZonefileFilesCsr*)sqlite3_malloc(sizeof(ZonefileFilesCsr)); if( pCsr==0 ){ return SQLITE_NOMEM; } memset(pCsr, 0, sizeof(ZonefileFilesCsr)); *ppCursor = (sqlite3_vtab_cursor*)pCsr; return SQLITE_OK; } /* ** Reset a ZonefileFilesCsr object to the state it is in immediately after ** it is allocated by zffOpen(). */ static void zffCursorReset(ZonefileFilesCsr *pCsr){ sqlite3_finalize(pCsr->pSelect); pCsr->pSelect = 0; } /* ** zonefile_files virtual table module xClose method. */ static int zffClose(sqlite3_vtab_cursor *cur){ ZonefileFilesCsr *pCsr = (ZonefileFilesCsr*)cur; zffCursorReset(pCsr); sqlite3_free(pCsr); return SQLITE_OK; } /* ** zonefile_files virtual table module xNext method. */ static int zffNext(sqlite3_vtab_cursor *cur){ ZonefileFilesCsr *pCsr = (ZonefileFilesCsr*)cur; int rc; if( SQLITE_ROW==sqlite3_step(pCsr->pSelect) ){ rc = SQLITE_OK; }else{ rc = sqlite3_finalize(pCsr->pSelect); pCsr->pSelect = 0; } return rc; } /* ** zonefile_files virtual table module xFilter method. */ static int zffFilter( sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ ZonefileFilesCsr *pCsr = (ZonefileFilesCsr*)cur; ZonefileFilesTab *pTab = (ZonefileFilesTab*)(pCsr->base.pVtab); int rc; zffCursorReset(pCsr); rc = zonefilePrepare(pTab->db, &pCsr->pSelect, &pTab->base.zErrMsg, "SELECT filename, fileid FROM %Q.'%q_shadow_file'", pTab->zDb, pTab->zBase ); if( rc==SQLITE_OK ){ rc = zffNext(cur); } return rc; } /* ** zonefile_files virtual table module xEof method. */ static int zffEof(sqlite3_vtab_cursor *cur){ ZonefileFilesCsr *pCsr = (ZonefileFilesCsr*)cur; return pCsr->pSelect==0; } static FILE *zonefileOpenFile(sqlite3_context *pCtx, const char *zFile){ FILE *pFd = fopen(zFile, "r"); if( pFd==0 ){ zonefileCtxError(pCtx, "failed to open file for reading: \"%s\"", zFile); } return pFd; } static void zonefileHeaderDeserialize(u8 *aBuf, ZonefileHeader *pHdr){ pHdr->magicNumber = zonefileGet32(&aBuf[0]); pHdr->compressionTypeIndexData = aBuf[4]; pHdr->compressionTypeContent = aBuf[5]; pHdr->byteOffsetDictionary = zonefileGet32(&aBuf[6]); pHdr->byteOffsetFrames = zonefileGet32(&aBuf[10]); pHdr->numFrames = zonefileGet32(&aBuf[14]); pHdr->numKeys = zonefileGet32(&aBuf[18]); pHdr->encryptionType = aBuf[22]; pHdr->encryptionKeyIdx = aBuf[23]; pHdr->extendedHeaderVersion = aBuf[24]; pHdr->extendedHeaderSize = aBuf[25]; } static void zonefileJsonHeader(sqlite3_context *pCtx, const char *zFile){ FILE *pFd = zonefileOpenFile(pCtx, zFile); if( pFd ){ int rc; ZonefileHeader hdr; u8 aBuf[ZONEFILE_SZ_HEADER]; rc = zonefileFileRead(pFd, aBuf, ZONEFILE_SZ_HEADER, 0); if( rc==SQLITE_OK ){ zonefileHeaderDeserialize(aBuf, &hdr); } if( rc!=SQLITE_OK ){ zonefileCtxError(pCtx, "failed to read header from file: \"%s\"", zFile); }else{ char *zJson = sqlite3_mprintf("{" "\"magicNumber\":%u," "\"compressionTypeIndexData\":%u," "\"compressionTypeContent\":%u," "\"byteOffsetDictionary\":%u," "\"byteOffsetFrames\":%u," "\"numFrames\":%u," "\"numKeys\":%u," "\"encryptionType\":%u," "\"encryptionKeyIdx\":%u," "\"extendedHeaderVersion\":%u," "\"extendedHeaderSize\":%u}", (u32)hdr.magicNumber, (u32)hdr.compressionTypeIndexData, (u32)hdr.compressionTypeContent, (u32)hdr.byteOffsetDictionary, (u32)hdr.byteOffsetFrames, (u32)hdr.numFrames, (u32)hdr.numKeys, (u32)hdr.encryptionType, (u32)hdr.encryptionKeyIdx, (u32)hdr.extendedHeaderVersion, (u32)hdr.extendedHeaderSize ); if( zJson ){ sqlite3_result_text(pCtx, zJson, -1, SQLITE_TRANSIENT); sqlite3_free(zJson); }else{ sqlite3_result_error_nomem(pCtx); } } fclose(pFd); } } /* ** zonefile_files virtual table module xColumn method. */ static int zffColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ ZonefileFilesCsr *pCsr = (ZonefileFilesCsr*)cur; switch( i ){ case 0: /* filename */ sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pSelect, 0)); break; case 1: /* priority */ break; case 2: /* ekey */ break; case 3: { /* header */ const char *zFile = (const char*)sqlite3_column_text(pCsr->pSelect, 0); zonefileJsonHeader(ctx, zFile); break; } } return SQLITE_OK; } /* ** zonefile_files virtual table module xRowid method. */ static int zffRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ ZonefileFilesCsr *pCsr = (ZonefileFilesCsr*)cur; *pRowid = sqlite3_column_int64(pCsr->pSelect, 1); return SQLITE_OK; } /* ** Read and decode a Zonefile header from the start of the file opened ** by file-handle pFd. If successful, populate object (*pHdr) before ** returning SQLITE_OK. Otherwise, if an error occurs, set output ** parameter (*pzErr) to point to an English language error message and ** return an SQLite error code. ** ** It is the responsibility of the caller to eventually free any error ** message returned via (*pzErr) using sqlite3_free(). */ static int zonefileReadHeader( FILE *pFd, /* File to read from */ const char *zFile, /* Name of file opened by pFd */ ZonefileHeader *pHdr, /* Populate this object before returning */ char **pzErr /* OUT: Error message */ ){ u8 aBuf[ZONEFILE_SZ_HEADER]; int rc = zonefileFileRead(pFd, aBuf, ZONEFILE_SZ_HEADER, 0); if( rc==SQLITE_OK ){ zonefileHeaderDeserialize(aBuf, pHdr); if( pHdr->magicNumber!=ZONEFILE_MAGIC_NUMBER ){ rc = SQLITE_ERROR; } } if( rc!=SQLITE_OK ){ *pzErr = sqlite3_mprintf( "failed to read zonefile header from file \"%s\"", zFile ); } return rc; } static int zonefilePopulateIndex( ZonefileFilesTab *pTab, const char *zFile, i64 iFileid ){ ZonefileHeader hdr; int rc; FILE *pFd = zonefileFileOpen(zFile, 0, &pTab->base.zErrMsg); if( pFd==0 ){ rc = SQLITE_ERROR; }else{ rc = zonefileReadHeader(pFd, zFile, &hdr, &pTab->base.zErrMsg); } if( rc==SQLITE_OK && hdr.numKeys>0 ){ /* TODO: Deal with encrypted and compressed ZonefileIndex objects */ i64 iOff; /* Offset of keyOffsets array */ u8 *aKey; /* Entire KeyOffsets array */ int nKey; /* Size of buffer aKey[] in bytes */ int i; assert( hdr.encryptionType==0 && hdr.compressionTypeIndexData==0 ); iOff = ZONEFILE_SZ_HEADER + (hdr.numFrames * 4); nKey = ZONEFILE_SZ_KEYOFFSETS_ENTRY * hdr.numKeys; aKey = (u8*)sqlite3_malloc(nKey); if( aKey==0 ){ rc = SQLITE_NOMEM; }else{ rc = zonefileFileRead(pFd, aKey, nKey, iOff); } if( rc==SQLITE_OK && pTab->pInsertIdx==0 ){ rc = zonefilePrepare(pTab->db, &pTab->pInsertIdx, &pTab->base.zErrMsg, "INSERT INTO %Q.'%q_shadow_idx'(k, fileid, frame, ofst, sz)" "VALUES(?,?,?,?,?)", pTab->zDb, pTab->zBase ); } for(i=0; ipInsertIdx, 1, (i64)zonefileGet64(&aEntry[0])); sqlite3_bind_int64(pTab->pInsertIdx, 2, iFileid); sqlite3_bind_int64(pTab->pInsertIdx, 3, (i64)zonefileGet32(&aEntry[8])); sqlite3_bind_int64(pTab->pInsertIdx, 4, (i64)zonefileGet32(&aEntry[12])); sqlite3_bind_int64(pTab->pInsertIdx, 5, (i64)zonefileGet32(&aEntry[16])); sqlite3_step(pTab->pInsertIdx); rc = sqlite3_reset(pTab->pInsertIdx); if( rc!=SQLITE_OK ){ pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); } } sqlite3_free(aKey); } zonefileFileClose(pFd); return rc; } /* ** zonefile_files virtual table module xUpdate method. ** ** A delete specifies a single argument - the rowid of the row to remove. ** ** Update and insert operations pass: ** ** 1. The "old" rowid, or NULL. ** 2. The "new" rowid. ** 3. Values for each of the 4 columns: (filename,priority,ekey,header) */ static int zffUpdate( sqlite3_vtab *pVtab, int nVal, sqlite3_value **apVal, sqlite_int64 *pRowid ){ int rc = SQLITE_OK; ZonefileFilesTab *pTab = (ZonefileFilesTab*)pVtab; if( sqlite3_value_type(apVal[0])==SQLITE_INTEGER ){ if( pTab->pDelete==0 ){ rc = zonefilePrepare(pTab->db, &pTab->pDelete, &pVtab->zErrMsg, "DELETE FROM %Q.'%q_shadow_file' WHERE fileid=?", pTab->zDb, pTab->zBase ); } if( rc==SQLITE_OK && pTab->pDeleteIdx==0 ){ rc = zonefilePrepare(pTab->db, &pTab->pDeleteIdx, &pVtab->zErrMsg, "DELETE FROM %Q.'%q_shadow_idx' WHERE fileid=?", pTab->zDb, pTab->zBase ); } if( rc==SQLITE_OK ){ sqlite3_bind_value(pTab->pDelete, 1, apVal[0]); sqlite3_step(pTab->pDelete); rc = sqlite3_reset(pTab->pDelete); } if( rc==SQLITE_OK ){ sqlite3_bind_value(pTab->pDeleteIdx, 1, apVal[0]); sqlite3_step(pTab->pDeleteIdx); rc = sqlite3_reset(pTab->pDeleteIdx); } } if( nVal>1 ){ const char *zFile = (const char*)sqlite3_value_text(apVal[2]); if( pTab->pInsert==0 ){ rc = zonefilePrepare(pTab->db, &pTab->pInsert, &pVtab->zErrMsg, "INSERT INTO %Q.'%q_shadow_file'(filename) VALUES(?)", pTab->zDb, pTab->zBase ); } /* Add the new entry to the %_shadow_file table. */ if( rc==SQLITE_OK ){ sqlite3_bind_text(pTab->pInsert, 1, zFile, -1, SQLITE_TRANSIENT); sqlite3_step(pTab->pInsert); rc = sqlite3_reset(pTab->pInsert); } /* Populate the %_shadow_idx table with entries for all keys in ** the zonefile just added to %_shadow_file. */ if( rc==SQLITE_OK ){ i64 iFileid = sqlite3_last_insert_rowid(pTab->db); rc = zonefilePopulateIndex(pTab, zFile, iFileid); } } return rc; } typedef struct ZonefileTab ZonefileTab; struct ZonefileTab { sqlite3_vtab base; /* Base class - must be first */ sqlite3 *db; sqlite3_stmt *pIdToName; /* Translate fileid to filename */ char *zName; /* Name of this table */ char *zDb; /* Name of db containing this table */ }; typedef struct ZonefileCsr ZonefileCsr; struct ZonefileCsr { sqlite3_vtab_cursor base; /* Base class - must be first */ sqlite3_stmt *pSelect; /* SELECT on %_shadow_idx table */ }; /* ** This function does the work of xCreate (if bCreate!=0) or xConnect ** (if bCreate==0) for the zonefile module. ** ** argv[0] -> module name ("zonefile") ** argv[1] -> database name ** argv[2] -> table name */ static int zonefileCreateConnect( int bCreate, sqlite3 *db, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ ZonefileTab *p; const char *zName = argv[2]; const char *zDb = argv[1]; int nName = strlen(zName); int nDb = strlen(zDb); int rc = SQLITE_OK; p = (ZonefileTab*)sqlite3_malloc(sizeof(ZonefileTab) + nName+1 + nDb+1); if( !p ){ rc = SQLITE_NOMEM; }else{ memset(p, 0, sizeof(ZonefileTab)); p->zName = (char*)&p[1]; memcpy(p->zName, zName, nName+1); p->zDb = &p->zName[nName+1]; memcpy(p->zDb, zDb, nDb+1); p->db = db; if( bCreate ){ char *zSql = sqlite3_mprintf( "CREATE TABLE %Q.'%q_shadow_idx'(" " k INTEGER PRIMARY KEY," " fileid INTEGER," " frame INTEGER," " ofst INTEGER," " sz INTEGER" ");" "CREATE TABLE %Q.'%q_shadow_file'(" " filename TEXT," " priority INTEGER," " fileid INTEGER PRIMARY KEY" ");" "CREATE VIRTUAL TABLE %Q.'%q_files' USING zonefile_files;", zDb, zName, zDb, zName, zDb, zName ); if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_exec(db, zSql, 0, 0, pzErr); sqlite3_free(zSql); } } if( rc==SQLITE_OK ){ rc = sqlite3_declare_vtab(db, ZONEFILE_SCHEMA); } } if( rc!=SQLITE_OK ){ sqlite3_free(p); p = 0; } *ppVtab = (sqlite3_vtab*)p; return rc; } /* ** zonefile virtual table module xCreate method. */ static int zonefileCreate( sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ return zonefileCreateConnect(1, db, argc, argv, ppVtab, pzErr); } /* ** zonefile virtual table module xConnect method. */ static int zonefileConnect( sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ return zonefileCreateConnect(0, db, argc, argv, ppVtab, pzErr); } /* ** zonefile virtual table module xDisconnect method. */ static int zonefileDisconnect(sqlite3_vtab *pVtab){ ZonefileTab *pTab = (ZonefileTab*)pVtab; sqlite3_finalize(pTab->pIdToName); sqlite3_free(pTab); return SQLITE_OK; } /* ** Zonefile virtual table module xBestIndex method. ** ** Equality and range constraints on either the rowid or column "k" (which ** are the same thing) are processed. Bits in the idxNum parameter are ** set to indicate the constraints present: ** ** 0x01: k == ? ** 0x02: k < ? ** 0x04: k <= ? ** 0x08: k > ? ** 0x10: k >= ? ** ** Only some combinations are valid: ** ** * If an == constraint is found, no other bits are set. ** * If a < constraint is present, any <= is ignored. ** * If a > constraint is present, any >= is ignored. */ static int zonefileBestIndex(sqlite3_vtab *pVtab, sqlite3_index_info *pInfo){ int iEq = -1; int iLt = -1; int iLe = -1; int iGt = -1; int iGe = -1; int i; int idxNum = 0; double cost = 1000000000.0; for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; if( p->usable && p->iColumn<=0 ){ switch( p->op ){ case SQLITE_INDEX_CONSTRAINT_EQ: iEq = i; break; case SQLITE_INDEX_CONSTRAINT_LT: iLt = i; break; case SQLITE_INDEX_CONSTRAINT_LE: iLe = i; break; case SQLITE_INDEX_CONSTRAINT_GT: iGt = i; break; case SQLITE_INDEX_CONSTRAINT_GE: iGe = i; break; } } } if( iEq>=0 ){ cost = 10.0; idxNum = 0x01; pInfo->aConstraintUsage[iEq].argvIndex = 1; }else{ int iIdx = 1; if( iLt>=0 ){ pInfo->aConstraintUsage[iLt].argvIndex = iIdx++; idxNum |= 0x02; }else if( iLe>=0 ){ pInfo->aConstraintUsage[iLe].argvIndex = iIdx++; idxNum |= 0x04; } if( iGt>=0 ){ pInfo->aConstraintUsage[iGt].argvIndex = iIdx++; idxNum |= 0x08; }else if( iGe>=0 ){ pInfo->aConstraintUsage[iGe].argvIndex = iIdx++; idxNum |= 0x10; } if( iIdx==2 ) cost = 10000.0; if( iIdx==3 ) cost = 100.0; } pInfo->idxNum = idxNum; pInfo->estimatedCost = cost; return SQLITE_OK; } /* ** zonefile virtual table module xDestroy method. */ static int zonefileDestroy(sqlite3_vtab *pVtab){ ZonefileTab *pTab = (ZonefileTab*)pVtab; int rc = SQLITE_OK; char *zSql = sqlite3_mprintf( "DROP TABLE IF EXISTS %Q.'%q_shadow_idx';" "DROP TABLE IF EXISTS %Q.'%q_shadow_file';" "DROP TABLE IF EXISTS %Q.'%q_files';", pTab->zDb, pTab->zName, pTab->zDb, pTab->zName, pTab->zDb, pTab->zName ); if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_exec(pTab->db, zSql, 0, 0, 0); sqlite3_free(zSql); } if( rc==SQLITE_OK ){ zonefileDisconnect(pVtab); } return rc; } /* ** zonefile virtual table module xOpen method. */ static int zonefileOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ ZonefileCsr *pCsr; pCsr = (ZonefileCsr*)sqlite3_malloc(sizeof(ZonefileCsr)); if( pCsr==0 ){ return SQLITE_NOMEM; } memset(pCsr, 0, sizeof(ZonefileCsr)); *ppCursor = (sqlite3_vtab_cursor*)pCsr; return SQLITE_OK; } /* ** Reset a ZonefileCsr object to the state it is in immediately after ** it is allocated by zffOpen(). */ static void zonefileCursorReset(ZonefileCsr *pCsr){ sqlite3_finalize(pCsr->pSelect); pCsr->pSelect = 0; } /* ** zonefile virtual table module xClose method. */ static int zonefileClose(sqlite3_vtab_cursor *cur){ ZonefileCsr *pCsr = (ZonefileCsr*)cur; zonefileCursorReset(pCsr); sqlite3_free(pCsr); return SQLITE_OK; } /* ** zonefile virtual table module xNext method. */ static int zonefileNext(sqlite3_vtab_cursor *cur){ ZonefileCsr *pCsr = (ZonefileCsr*)cur; int rc = SQLITE_OK; if( SQLITE_ROW!=sqlite3_step(pCsr->pSelect) ){ rc = sqlite3_finalize(pCsr->pSelect); if( rc!=SQLITE_OK ){ ZonefileTab *pTab = (ZonefileTab*)pCsr->base.pVtab; pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); } pCsr->pSelect = 0; } return rc; } /* ** zonefile virtual table module xFilter method. */ static int zonefileFilter( sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ ZonefileCsr *pCsr = (ZonefileCsr*)cur; ZonefileTab *pTab = (ZonefileTab*)pCsr->base.pVtab; int rc; const char *z1 = 0; const char *z2 = 0; if( idxNum & 0x01 ){ z1 = "k = ?"; }else{ if( idxNum & 0x02 ) { z1 = "k < ?"; } if( idxNum & 0x04 ) { z1 = "k <= ?"; } if( idxNum & 0x08 ) { if( z1 ) z2 = "k > ?"; else z1 = "k > ?"; } if( idxNum & 0x10 ) { if( z1 ) z2 = "k <= ?"; else z1 = "k <= ?"; } } rc = zonefilePrepare(pTab->db, &pCsr->pSelect, &pTab->base.zErrMsg, "SELECT k, fileid, frame, ofst, sz FROM %Q.'%q_shadow_idx'%s%s%s%s", pTab->zDb, pTab->zName, z1 ? " WHERE " : "", z1, z2 ? " AND " : "", z2 ); if( z1 ) sqlite3_bind_value(pCsr->pSelect, 1, argv[0]); if( z2 ) sqlite3_bind_value(pCsr->pSelect, 2, argv[1]); if( rc==SQLITE_OK ){ rc = zonefileNext(cur); } return rc; } /* ** zonefile virtual table module xEof method. */ static int zonefileEof(sqlite3_vtab_cursor *cur){ ZonefileCsr *pCsr = (ZonefileCsr*)cur; return (pCsr->pSelect==0); } static void zonefileFree(void *p){ sqlite3_free(p); } static int zonefileGetValue(sqlite3_context *pCtx, ZonefileCsr *pCsr){ ZonefileTab *pTab = (ZonefileTab*)pCsr->base.pVtab; const char *zFile = 0; char *zErr = 0; FILE *pFd = 0; int rc = SQLITE_OK; u32 iOff = 0; /* Offset of frame in file */ ZonefileHeader hdr; if( pTab->pIdToName==0 ){ rc = zonefilePrepare(pTab->db, &pTab->pIdToName, &pTab->base.zErrMsg, "SELECT filename FROM %Q.'%q_shadow_file' WHERE fileid=?", pTab->zDb, pTab->zName ); if( rc!=SQLITE_OK ){ zonefileTransferError(pCtx); return SQLITE_ERROR; } } /* Open the file to read the blob from */ sqlite3_bind_int64(pTab->pIdToName, 1, sqlite3_column_int64(pCsr->pSelect,1)); if( SQLITE_ROW==sqlite3_step(pTab->pIdToName) ){ zFile = (const char*)sqlite3_column_text(pTab->pIdToName, 0); pFd = zonefileFileOpen(zFile, 0, &zErr); } if( zFile==0 ){ rc = sqlite3_reset(pTab->pIdToName); if( rc!=SQLITE_OK ){ zonefileTransferError(pCtx); }else{ rc = SQLITE_CORRUPT_VTAB; } }else if( pFd==0 ){ rc = SQLITE_ERROR; } /* Read the zonefile header */ if( rc==SQLITE_OK ){ rc = zonefileReadHeader(pFd, zFile, &hdr, &zErr); } /* Calculate the offset of the frame to read the blob from */ if( rc==SQLITE_OK ){ u8 aOff[4] = {0,0,0,0}; int iFrame = sqlite3_column_int(pCsr->pSelect, 2); rc = zonefileFileRead(pFd, aOff, 4, ZONEFILE_SZ_HEADER + 4 * iFrame); iOff = zonefileGet32(aOff); } /* Read the blob */ if( rc==SQLITE_OK ){ int sz = sqlite3_column_int(pCsr->pSelect, 4); int ofst = sqlite3_column_int(pCsr->pSelect, 3); u8 *aBuf = sqlite3_malloc(sz); if( aBuf==0 ){ rc = SQLITE_NOMEM; }else{ rc = zonefileFileRead(pFd, aBuf, sz, hdr.byteOffsetFrames + iOff + ofst); if( rc==SQLITE_OK ){ sqlite3_result_blob(pCtx, aBuf, sz, zonefileFree); }else{ zErr = sqlite3_mprintf( "failed to read %d bytes from file \"%s\"", sz, zFile ); } } } sqlite3_reset(pTab->pIdToName); if( zErr ){ assert( rc!=SQLITE_OK ); sqlite3_result_error(pCtx, zErr, -1); sqlite3_free(zErr); } zonefileFileClose(pFd); return rc; } /* ** zonefile virtual table module xColumn method. */ static int zonefileColumn( sqlite3_vtab_cursor *cur, sqlite3_context *pCtx, int i ){ ZonefileCsr *pCsr = (ZonefileCsr*)cur; int rc = SQLITE_OK; switch( i ){ case 0: /* k */ sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pSelect, 0)); break; case 1: /* v */ rc = zonefileGetValue(pCtx, pCsr); break; case 2: /* fileid */ sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pSelect, 1)); break; case 3: /* frame */ sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pSelect, 2)); break; case 4: /* ofst */ sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pSelect, 3)); break; default: /* sz */ sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pSelect, 4)); break; } return rc; } /* ** zonefile virtual table module xRowid method. */ static int zonefileRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ ZonefileCsr *pCsr = (ZonefileCsr*)cur; *pRowid = sqlite3_column_int64(pCsr->pSelect, 0); return SQLITE_OK; } /* ** Register the "zonefile" extensions. */ static int zonefileRegister(sqlite3 *db){ static sqlite3_module filesModule = { 0, /* iVersion */ zffCreate, /* xCreate - create a table */ zffConnect, /* xConnect - connect to an existing table */ zffBestIndex, /* xBestIndex - Determine search strategy */ zffDisconnect, /* xDisconnect - Disconnect from a table */ zffDisconnect, /* xDestroy - Drop a table */ zffOpen, /* xOpen - open a cursor */ zffClose, /* xClose - close a cursor */ zffFilter, /* xFilter - configure scan constraints */ zffNext, /* xNext - advance a cursor */ zffEof, /* xEof */ zffColumn, /* xColumn - read data */ zffRowid, /* xRowid - read data */ zffUpdate, /* 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 */ 0, /* xSavepoint */ 0, /* xRelease */ 0 /* xRollbackTo */ }; static sqlite3_module zonefileModule = { 0, /* iVersion */ zonefileCreate, /* xCreate - create a table */ zonefileConnect, /* xConnect - connect to an existing table */ zonefileBestIndex, /* xBestIndex - Determine search strategy */ zonefileDisconnect, /* xDisconnect - Disconnect from a table */ zonefileDestroy, /* xDestroy - Drop a table */ zonefileOpen, /* xOpen - open a cursor */ zonefileClose, /* xClose - close a cursor */ zonefileFilter, /* xFilter - configure scan constraints */ zonefileNext, /* xNext - advance a cursor */ zonefileEof, /* xEof */ zonefileColumn, /* xColumn - read data */ zonefileRowid, /* 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 */ 0, /* xSavepoint */ 0, /* xRelease */ 0 /* xRollbackTo */ }; struct Func { const char *z; int n; void (*x)(sqlite3_context*,int,sqlite3_value**); } aFunc[] = { { "zonefile_write", 2, zonefileWriteFunc }, { "zonefile_write", 3, zonefileWriteFunc }, }; int rc = SQLITE_OK; int i; for(i=0; rc==SQLITE_OK && iz, p->n, SQLITE_ANY, 0, p->x, 0, 0); } if( rc==SQLITE_OK ){ rc = sqlite3_create_module(db, "zonefile_files", &filesModule, 0); } if( rc==SQLITE_OK ){ rc = sqlite3_create_module(db, "zonefile", &zonefileModule, 0); } return rc; } #else /* SQLITE_OMIT_VIRTUALTABLE */ # define zonefileRegister(x) SQLITE_OK #endif #ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_zonefile_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi); (void)pzErrMsg; /* Unused parameter */ return zonefileRegister(db); }