mirror of
https://github.com/sqlite/sqlite.git
synced 2026-10-26 21:59:27 +03:00
"wb" mode. FossilOrigin-Name: fb1c2277912c55cfae30c18b5434bc193748746395fa7df983cd8a29e5741ff9
1489 lines
42 KiB
C
1489 lines
42 KiB
C
/*
|
|
** 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 32
|
|
#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," \
|
|
" ekey BLOB," \
|
|
" header JSON HIDDEN" \
|
|
")"
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
/*
|
|
** A structure to store the parameters for a single zonefile_write()
|
|
** invocation.
|
|
*/
|
|
typedef struct ZonefileWrite ZonefileWrite;
|
|
struct ZonefileWrite {
|
|
int compressionTypeIndexData;
|
|
int compressionTypeContent;
|
|
int encryptionType;
|
|
int maxAutoFrameSize;
|
|
};
|
|
|
|
/*
|
|
** A structure to store a deserialized zonefile header in.
|
|
*/
|
|
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;
|
|
};
|
|
|
|
/*
|
|
** Buffer structure used by the zonefile_write() implementation.
|
|
*/
|
|
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 */
|
|
){
|
|
sqlite3 *db = sqlite3_context_db_handle(pCtx);
|
|
sqlite3_stmt *pStmt = 0;
|
|
char *zErr = 0;
|
|
int rc = SQLITE_OK;
|
|
int rc2;
|
|
|
|
memset(p, 0, sizeof(ZonefileWrite));
|
|
p->maxAutoFrameSize = ZONEFILE_DEFAULT_MAXAUTOFRAMESIZE;
|
|
|
|
rc = zonefilePrepare(db, &pStmt, &zErr,"SELECT key, value FROM json_each(?)");
|
|
if( rc==SQLITE_OK ){
|
|
sqlite3_bind_text(pStmt, 1, zJson, -1, SQLITE_STATIC);
|
|
}
|
|
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
|
const char *zKey = (const char*)sqlite3_column_text(pStmt, 0);
|
|
int iVal = sqlite3_column_int(pStmt, 1);
|
|
if( sqlite3_stricmp("maxAutoFrameSize", zKey)==0 ){
|
|
p->maxAutoFrameSize = iVal;
|
|
}else
|
|
if( sqlite3_stricmp("compressionTypeIndexData", zKey)==0 ){
|
|
p->compressionTypeIndexData = iVal;
|
|
}else
|
|
if( sqlite3_stricmp("compressionTypeContent", zKey)==0 ){
|
|
p->compressionTypeContent = iVal;
|
|
}else
|
|
if( sqlite3_stricmp("encryptionType", zKey)==0 ){
|
|
p->encryptionType = iVal;
|
|
}else{
|
|
rc = SQLITE_ERROR;
|
|
zErr = sqlite3_mprintf("unknown parameter name: \"%s\"", zKey);
|
|
}
|
|
}
|
|
rc2 = sqlite3_finalize(pStmt);
|
|
if( rc==SQLITE_OK ) rc = rc2;
|
|
|
|
if( zErr ){
|
|
sqlite3_result_error(pCtx, zErr, -1);
|
|
sqlite3_free(zErr);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** 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( nNew<nReq ) nNew = nNew*2;
|
|
aNew = sqlite3_realloc(pBuf->a, 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 ? "wb" : "rb");
|
|
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;
|
|
char *zErr = 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 = zonefileFileOpen(zFile, 1, &zErr);
|
|
if( pFd==0 ){
|
|
sqlite3_result_error(pCtx, zErr, -1);
|
|
sqlite3_finalize(pStmt);
|
|
sqlite3_free(zErr);
|
|
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 */
|
|
memset(aHdr, 0, ZONEFILE_SZ_HEADER);
|
|
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 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){
|
|
char *zErr = 0;
|
|
FILE *pFd = zonefileFileOpen(zFile, 0, &zErr);
|
|
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);
|
|
}else{
|
|
sqlite3_result_error(pCtx, zErr, -1);
|
|
sqlite3_free(zErr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** 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: /* ekey */
|
|
break;
|
|
case 2: { /* 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; i<hdr.numKeys && rc==SQLITE_OK; i++){
|
|
u8 *aEntry = &aKey[ZONEFILE_SZ_KEYOFFSETS_ENTRY * i];
|
|
|
|
sqlite3_bind_int64(pTab->pInsertIdx, 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 3 columns: (filename,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,"
|
|
" 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; i<pInfo->nConstraint; 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 && i<sizeof(aFunc)/sizeof(aFunc[0]); i++){
|
|
struct Func *p = &aFunc[i];
|
|
rc = sqlite3_create_function(db, p->z, 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);
|
|
}
|