mirror of
https://github.com/tursodatabase/libsql.git
synced 2025-01-09 08:49:02 +00:00
980 lines
29 KiB
C
980 lines
29 KiB
C
/*
|
|
** 2022-09-06
|
|
**
|
|
** 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 experimental VFS layer that operates on a
|
|
** Key/Value storage engine where both keys and values must be pure
|
|
** text.
|
|
*/
|
|
#include <sqliteInt.h>
|
|
#if SQLITE_OS_KV || (SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL))
|
|
|
|
/*****************************************************************************
|
|
** Debugging logic
|
|
*/
|
|
|
|
/* SQLITE_KV_TRACE() is used for tracing calls to kvstorage routines. */
|
|
#if 0
|
|
#define SQLITE_KV_TRACE(X) printf X
|
|
#else
|
|
#define SQLITE_KV_TRACE(X)
|
|
#endif
|
|
|
|
/* SQLITE_KV_LOG() is used for tracing calls to the VFS interface */
|
|
#if 0
|
|
#define SQLITE_KV_LOG(X) printf X
|
|
#else
|
|
#define SQLITE_KV_LOG(X)
|
|
#endif
|
|
|
|
|
|
/*
|
|
** Forward declaration of objects used by this VFS implementation
|
|
*/
|
|
typedef struct KVVfsFile KVVfsFile;
|
|
|
|
/* A single open file. There are only two files represented by this
|
|
** VFS - the database and the rollback journal.
|
|
*/
|
|
struct KVVfsFile {
|
|
sqlite3_file base; /* IO methods */
|
|
const char *zClass; /* Storage class */
|
|
int isJournal; /* True if this is a journal file */
|
|
unsigned int nJrnl; /* Space allocated for aJrnl[] */
|
|
char *aJrnl; /* Journal content */
|
|
int szPage; /* Last known page size */
|
|
sqlite3_int64 szDb; /* Database file size. -1 means unknown */
|
|
char *aData; /* Buffer to hold page data */
|
|
};
|
|
#define SQLITE_KVOS_SZ 133073
|
|
|
|
/*
|
|
** Methods for KVVfsFile
|
|
*/
|
|
static int kvvfsClose(sqlite3_file*);
|
|
static int kvvfsReadDb(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
|
static int kvvfsReadJrnl(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
|
static int kvvfsWriteDb(sqlite3_file*,const void*,int iAmt, sqlite3_int64);
|
|
static int kvvfsWriteJrnl(sqlite3_file*,const void*,int iAmt, sqlite3_int64);
|
|
static int kvvfsTruncateDb(sqlite3_file*, sqlite3_int64 size);
|
|
static int kvvfsTruncateJrnl(sqlite3_file*, sqlite3_int64 size);
|
|
static int kvvfsSyncDb(sqlite3_file*, int flags);
|
|
static int kvvfsSyncJrnl(sqlite3_file*, int flags);
|
|
static int kvvfsFileSizeDb(sqlite3_file*, sqlite3_int64 *pSize);
|
|
static int kvvfsFileSizeJrnl(sqlite3_file*, sqlite3_int64 *pSize);
|
|
static int kvvfsLock(sqlite3_file*, int);
|
|
static int kvvfsUnlock(sqlite3_file*, int);
|
|
static int kvvfsCheckReservedLock(sqlite3_file*, int *pResOut);
|
|
static int kvvfsFileControlDb(sqlite3_file*, int op, void *pArg);
|
|
static int kvvfsFileControlJrnl(sqlite3_file*, int op, void *pArg);
|
|
static int kvvfsSectorSize(sqlite3_file*);
|
|
static int kvvfsDeviceCharacteristics(sqlite3_file*);
|
|
|
|
/*
|
|
** Methods for sqlite3_vfs
|
|
*/
|
|
static int kvvfsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
|
|
static int kvvfsDelete(sqlite3_vfs*, const char *zName, int syncDir);
|
|
static int kvvfsAccess(sqlite3_vfs*, const char *zName, int flags, int *);
|
|
static int kvvfsFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
|
|
static void *kvvfsDlOpen(sqlite3_vfs*, const char *zFilename);
|
|
static int kvvfsRandomness(sqlite3_vfs*, int nByte, char *zOut);
|
|
static int kvvfsSleep(sqlite3_vfs*, int microseconds);
|
|
static int kvvfsCurrentTime(sqlite3_vfs*, double*);
|
|
static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
|
|
|
|
static sqlite3_vfs sqlite3OsKvvfsObject = {
|
|
1, /* iVersion */
|
|
sizeof(KVVfsFile), /* szOsFile */
|
|
1024, /* mxPathname */
|
|
0, /* pNext */
|
|
"kvvfs", /* zName */
|
|
0, /* pAppData */
|
|
kvvfsOpen, /* xOpen */
|
|
kvvfsDelete, /* xDelete */
|
|
kvvfsAccess, /* xAccess */
|
|
kvvfsFullPathname, /* xFullPathname */
|
|
kvvfsDlOpen, /* xDlOpen */
|
|
0, /* xDlError */
|
|
0, /* xDlSym */
|
|
0, /* xDlClose */
|
|
kvvfsRandomness, /* xRandomness */
|
|
kvvfsSleep, /* xSleep */
|
|
kvvfsCurrentTime, /* xCurrentTime */
|
|
0, /* xGetLastError */
|
|
kvvfsCurrentTimeInt64 /* xCurrentTimeInt64 */
|
|
};
|
|
|
|
/* Methods for sqlite3_file objects referencing a database file
|
|
*/
|
|
static sqlite3_io_methods kvvfs_db_io_methods = {
|
|
1, /* iVersion */
|
|
kvvfsClose, /* xClose */
|
|
kvvfsReadDb, /* xRead */
|
|
kvvfsWriteDb, /* xWrite */
|
|
kvvfsTruncateDb, /* xTruncate */
|
|
kvvfsSyncDb, /* xSync */
|
|
kvvfsFileSizeDb, /* xFileSize */
|
|
kvvfsLock, /* xLock */
|
|
kvvfsUnlock, /* xUnlock */
|
|
kvvfsCheckReservedLock, /* xCheckReservedLock */
|
|
kvvfsFileControlDb, /* xFileControl */
|
|
kvvfsSectorSize, /* xSectorSize */
|
|
kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */
|
|
0, /* xShmMap */
|
|
0, /* xShmLock */
|
|
0, /* xShmBarrier */
|
|
0, /* xShmUnmap */
|
|
0, /* xFetch */
|
|
0 /* xUnfetch */
|
|
};
|
|
|
|
/* Methods for sqlite3_file objects referencing a rollback journal
|
|
*/
|
|
static sqlite3_io_methods kvvfs_jrnl_io_methods = {
|
|
1, /* iVersion */
|
|
kvvfsClose, /* xClose */
|
|
kvvfsReadJrnl, /* xRead */
|
|
kvvfsWriteJrnl, /* xWrite */
|
|
kvvfsTruncateJrnl, /* xTruncate */
|
|
kvvfsSyncJrnl, /* xSync */
|
|
kvvfsFileSizeJrnl, /* xFileSize */
|
|
kvvfsLock, /* xLock */
|
|
kvvfsUnlock, /* xUnlock */
|
|
kvvfsCheckReservedLock, /* xCheckReservedLock */
|
|
kvvfsFileControlJrnl, /* xFileControl */
|
|
kvvfsSectorSize, /* xSectorSize */
|
|
kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */
|
|
0, /* xShmMap */
|
|
0, /* xShmLock */
|
|
0, /* xShmBarrier */
|
|
0, /* xShmUnmap */
|
|
0, /* xFetch */
|
|
0 /* xUnfetch */
|
|
};
|
|
|
|
/****** Storage subsystem **************************************************/
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
/* Forward declarations for the low-level storage engine
|
|
*/
|
|
static int kvstorageWrite(const char*, const char *zKey, const char *zData);
|
|
static int kvstorageDelete(const char*, const char *zKey);
|
|
static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf);
|
|
#define KVSTORAGE_KEY_SZ 32
|
|
|
|
/* Expand the key name with an appropriate prefix and put the result
|
|
** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least
|
|
** KVSTORAGE_KEY_SZ bytes.
|
|
*/
|
|
static void kvstorageMakeKey(
|
|
const char *zClass,
|
|
const char *zKeyIn,
|
|
char *zKeyOut
|
|
){
|
|
sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn);
|
|
}
|
|
|
|
/* Write content into a key. zClass is the particular namespace of the
|
|
** underlying key/value store to use - either "local" or "session".
|
|
**
|
|
** Both zKey and zData are zero-terminated pure text strings.
|
|
**
|
|
** Return the number of errors.
|
|
*/
|
|
static int kvstorageWrite(
|
|
const char *zClass,
|
|
const char *zKey,
|
|
const char *zData
|
|
){
|
|
FILE *fd;
|
|
char zXKey[KVSTORAGE_KEY_SZ];
|
|
kvstorageMakeKey(zClass, zKey, zXKey);
|
|
fd = fopen(zXKey, "wb");
|
|
if( fd ){
|
|
SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey,
|
|
(int)strlen(zData), zData,
|
|
strlen(zData)>50 ? "..." : ""));
|
|
fputs(zData, fd);
|
|
fclose(fd);
|
|
return 0;
|
|
}else{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Delete a key (with its corresponding data) from the key/value
|
|
** namespace given by zClass. If the key does not previously exist,
|
|
** this routine is a no-op.
|
|
*/
|
|
static int kvstorageDelete(const char *zClass, const char *zKey){
|
|
char zXKey[KVSTORAGE_KEY_SZ];
|
|
kvstorageMakeKey(zClass, zKey, zXKey);
|
|
unlink(zXKey);
|
|
SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey));
|
|
return 0;
|
|
}
|
|
|
|
/* Read the value associated with a zKey from the key/value namespace given
|
|
** by zClass and put the text data associated with that key in the first
|
|
** nBuf bytes of zBuf[]. The value might be truncated if zBuf is not large
|
|
** enough to hold it all. The value put into zBuf must always be zero
|
|
** terminated, even if it gets truncated because nBuf is not large enough.
|
|
**
|
|
** Return the total number of bytes in the data, without truncation, and
|
|
** not counting the final zero terminator. Return -1 if the key does
|
|
** not exist.
|
|
**
|
|
** If nBuf<=0 then this routine simply returns the size of the data without
|
|
** actually reading it.
|
|
*/
|
|
static int kvstorageRead(
|
|
const char *zClass,
|
|
const char *zKey,
|
|
char *zBuf,
|
|
int nBuf
|
|
){
|
|
FILE *fd;
|
|
struct stat buf;
|
|
char zXKey[KVSTORAGE_KEY_SZ];
|
|
kvstorageMakeKey(zClass, zKey, zXKey);
|
|
if( access(zXKey, R_OK)!=0
|
|
|| stat(zXKey, &buf)!=0
|
|
|| !S_ISREG(buf.st_mode)
|
|
){
|
|
SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey));
|
|
return -1;
|
|
}
|
|
if( nBuf<=0 ){
|
|
return (int)buf.st_size;
|
|
}else if( nBuf==1 ){
|
|
zBuf[0] = 0;
|
|
SQLITE_KV_TRACE(("KVVFS-READ %-15s (%d)\n", zXKey,
|
|
(int)buf.st_size));
|
|
return (int)buf.st_size;
|
|
}
|
|
if( nBuf > buf.st_size + 1 ){
|
|
nBuf = buf.st_size + 1;
|
|
}
|
|
fd = fopen(zXKey, "rb");
|
|
if( fd==0 ){
|
|
SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey));
|
|
return -1;
|
|
}else{
|
|
sqlite3_int64 n = fread(zBuf, 1, nBuf-1, fd);
|
|
fclose(fd);
|
|
zBuf[n] = 0;
|
|
SQLITE_KV_TRACE(("KVVFS-READ %-15s (%lld) %.50s%s\n", zXKey,
|
|
n, zBuf, n>50 ? "..." : ""));
|
|
return (int)n;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** An internal level of indirection which enables us to replace the
|
|
** kvvfs i/o methods with JavaScript implementations in WASM builds.
|
|
** Maintenance reminder: if this struct changes in any way, the JSON
|
|
** rendering of its structure must be updated in
|
|
** sqlite3_wasm_enum_json(). There are no binary compatibility
|
|
** concerns, so it does not need an iVersion member. This file is
|
|
** necessarily always compiled together with sqlite3_wasm_enum_json(),
|
|
** and JS code dynamically creates the mapping of members based on
|
|
** that JSON description.
|
|
*/
|
|
typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods;
|
|
struct sqlite3_kvvfs_methods {
|
|
int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf);
|
|
int (*xWrite)(const char *zClass, const char *zKey, const char *zData);
|
|
int (*xDelete)(const char *zClass, const char *zKey);
|
|
const int nKeySize;
|
|
};
|
|
|
|
/*
|
|
** This object holds the kvvfs I/O methods which may be swapped out
|
|
** for JavaScript-side implementations in WASM builds. In such builds
|
|
** it cannot be const, but in native builds it should be so that
|
|
** the compiler can hopefully optimize this level of indirection out.
|
|
** That said, kvvfs is intended primarily for use in WASM builds.
|
|
**
|
|
** Note that this is not explicitly flagged as static because the
|
|
** amalgamation build will tag it with SQLITE_PRIVATE.
|
|
*/
|
|
#ifndef SQLITE_WASM
|
|
const
|
|
#endif
|
|
sqlite3_kvvfs_methods sqlite3KvvfsMethods = {
|
|
kvstorageRead,
|
|
kvstorageWrite,
|
|
kvstorageDelete,
|
|
KVSTORAGE_KEY_SZ
|
|
};
|
|
|
|
/****** Utility subroutines ************************************************/
|
|
|
|
/*
|
|
** Encode binary into the text encoded used to persist on disk.
|
|
** The output text is stored in aOut[], which must be at least
|
|
** nData+1 bytes in length.
|
|
**
|
|
** Return the actual length of the encoded text, not counting the
|
|
** zero terminator at the end.
|
|
**
|
|
** Encoding format
|
|
** ---------------
|
|
**
|
|
** * Non-zero bytes are encoded as upper-case hexadecimal
|
|
**
|
|
** * A sequence of one or more zero-bytes that are not at the
|
|
** beginning of the buffer are encoded as a little-endian
|
|
** base-26 number using a..z. "a" means 0. "b" means 1,
|
|
** "z" means 25. "ab" means 26. "ac" means 52. And so forth.
|
|
**
|
|
** * Because there is no overlap between the encoding characters
|
|
** of hexadecimal and base-26 numbers, it is always clear where
|
|
** one stops and the next begins.
|
|
*/
|
|
static int kvvfsEncode(const char *aData, int nData, char *aOut){
|
|
int i, j;
|
|
const unsigned char *a = (const unsigned char*)aData;
|
|
for(i=j=0; i<nData; i++){
|
|
unsigned char c = a[i];
|
|
if( c!=0 ){
|
|
aOut[j++] = "0123456789ABCDEF"[c>>4];
|
|
aOut[j++] = "0123456789ABCDEF"[c&0xf];
|
|
}else{
|
|
/* A sequence of 1 or more zeros is stored as a little-endian
|
|
** base-26 number using a..z as the digits. So one zero is "b".
|
|
** Two zeros is "c". 25 zeros is "z", 26 zeros is "ab", 27 is "bb",
|
|
** and so forth.
|
|
*/
|
|
int k;
|
|
for(k=1; i+k<nData && a[i+k]==0; k++){}
|
|
i += k-1;
|
|
while( k>0 ){
|
|
aOut[j++] = 'a'+(k%26);
|
|
k /= 26;
|
|
}
|
|
}
|
|
}
|
|
aOut[j] = 0;
|
|
return j;
|
|
}
|
|
|
|
static const signed char kvvfsHexValue[256] = {
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
|
|
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
|
|
};
|
|
|
|
/*
|
|
** Decode the text encoding back to binary. The binary content is
|
|
** written into pOut, which must be at least nOut bytes in length.
|
|
**
|
|
** The return value is the number of bytes actually written into aOut[].
|
|
*/
|
|
static int kvvfsDecode(const char *a, char *aOut, int nOut){
|
|
int i, j;
|
|
int c;
|
|
const unsigned char *aIn = (const unsigned char*)a;
|
|
i = 0;
|
|
j = 0;
|
|
while( 1 ){
|
|
c = kvvfsHexValue[aIn[i]];
|
|
if( c<0 ){
|
|
int n = 0;
|
|
int mult = 1;
|
|
c = aIn[i];
|
|
if( c==0 ) break;
|
|
while( c>='a' && c<='z' ){
|
|
n += (c - 'a')*mult;
|
|
mult *= 26;
|
|
c = aIn[++i];
|
|
}
|
|
if( j+n>nOut ) return -1;
|
|
memset(&aOut[j], 0, n);
|
|
j += n;
|
|
if( c==0 || mult==1 ) break; /* progress stalled if mult==1 */
|
|
}else{
|
|
aOut[j] = c<<4;
|
|
c = kvvfsHexValue[aIn[++i]];
|
|
if( c<0 ) break;
|
|
aOut[j++] += c;
|
|
i++;
|
|
}
|
|
}
|
|
return j;
|
|
}
|
|
|
|
/*
|
|
** Decode a complete journal file. Allocate space in pFile->aJrnl
|
|
** and store the decoding there. Or leave pFile->aJrnl set to NULL
|
|
** if an error is encountered.
|
|
**
|
|
** The first few characters of the text encoding will be a little-endian
|
|
** base-26 number (digits a..z) that is the total number of bytes
|
|
** in the decoded journal file image. This base-26 number is followed
|
|
** by a single space, then the encoding of the journal. The space
|
|
** separator is required to act as a terminator for the base-26 number.
|
|
*/
|
|
static void kvvfsDecodeJournal(
|
|
KVVfsFile *pFile, /* Store decoding in pFile->aJrnl */
|
|
const char *zTxt, /* Text encoding. Zero-terminated */
|
|
int nTxt /* Bytes in zTxt, excluding zero terminator */
|
|
){
|
|
unsigned int n = 0;
|
|
int c, i, mult;
|
|
i = 0;
|
|
mult = 1;
|
|
while( (c = zTxt[i++])>='a' && c<='z' ){
|
|
n += (zTxt[i] - 'a')*mult;
|
|
mult *= 26;
|
|
}
|
|
sqlite3_free(pFile->aJrnl);
|
|
pFile->aJrnl = sqlite3_malloc64( n );
|
|
if( pFile->aJrnl==0 ){
|
|
pFile->nJrnl = 0;
|
|
return;
|
|
}
|
|
pFile->nJrnl = n;
|
|
n = kvvfsDecode(zTxt+i, pFile->aJrnl, pFile->nJrnl);
|
|
if( n<pFile->nJrnl ){
|
|
sqlite3_free(pFile->aJrnl);
|
|
pFile->aJrnl = 0;
|
|
pFile->nJrnl = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Read or write the "sz" element, containing the database file size.
|
|
*/
|
|
static sqlite3_int64 kvvfsReadFileSize(KVVfsFile *pFile){
|
|
char zData[50];
|
|
zData[0] = 0;
|
|
sqlite3KvvfsMethods.xRead(pFile->zClass, "sz", zData, sizeof(zData)-1);
|
|
return strtoll(zData, 0, 0);
|
|
}
|
|
static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){
|
|
char zData[50];
|
|
sqlite3_snprintf(sizeof(zData), zData, "%lld", sz);
|
|
return sqlite3KvvfsMethods.xWrite(pFile->zClass, "sz", zData);
|
|
}
|
|
|
|
/****** sqlite3_io_methods methods ******************************************/
|
|
|
|
/*
|
|
** Close an kvvfs-file.
|
|
*/
|
|
static int kvvfsClose(sqlite3_file *pProtoFile){
|
|
KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
|
|
|
|
SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass,
|
|
pFile->isJournal ? "journal" : "db"));
|
|
sqlite3_free(pFile->aJrnl);
|
|
sqlite3_free(pFile->aData);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Read from the -journal file.
|
|
*/
|
|
static int kvvfsReadJrnl(
|
|
sqlite3_file *pProtoFile,
|
|
void *zBuf,
|
|
int iAmt,
|
|
sqlite_int64 iOfst
|
|
){
|
|
KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
|
|
assert( pFile->isJournal );
|
|
SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
|
|
if( pFile->aJrnl==0 ){
|
|
int szTxt = kvstorageRead(pFile->zClass, "jrnl", 0, 0);
|
|
char *aTxt;
|
|
if( szTxt<=4 ){
|
|
return SQLITE_IOERR;
|
|
}
|
|
aTxt = sqlite3_malloc64( szTxt+1 );
|
|
if( aTxt==0 ) return SQLITE_NOMEM;
|
|
kvstorageRead(pFile->zClass, "jrnl", aTxt, szTxt+1);
|
|
kvvfsDecodeJournal(pFile, aTxt, szTxt);
|
|
sqlite3_free(aTxt);
|
|
if( pFile->aJrnl==0 ) return SQLITE_IOERR;
|
|
}
|
|
if( iOfst+iAmt>pFile->nJrnl ){
|
|
return SQLITE_IOERR_SHORT_READ;
|
|
}
|
|
memcpy(zBuf, pFile->aJrnl+iOfst, iAmt);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Read from the database file.
|
|
*/
|
|
static int kvvfsReadDb(
|
|
sqlite3_file *pProtoFile,
|
|
void *zBuf,
|
|
int iAmt,
|
|
sqlite_int64 iOfst
|
|
){
|
|
KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
|
|
unsigned int pgno;
|
|
int got, n;
|
|
char zKey[30];
|
|
char *aData = pFile->aData;
|
|
assert( iOfst>=0 );
|
|
assert( iAmt>=0 );
|
|
SQLITE_KV_LOG(("xRead('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
|
|
if( iOfst+iAmt>=512 ){
|
|
if( (iOfst % iAmt)!=0 ){
|
|
return SQLITE_IOERR_READ;
|
|
}
|
|
if( (iAmt & (iAmt-1))!=0 || iAmt<512 || iAmt>65536 ){
|
|
return SQLITE_IOERR_READ;
|
|
}
|
|
pFile->szPage = iAmt;
|
|
pgno = 1 + iOfst/iAmt;
|
|
}else{
|
|
pgno = 1;
|
|
}
|
|
sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno);
|
|
got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey,
|
|
aData, SQLITE_KVOS_SZ-1);
|
|
if( got<0 ){
|
|
n = 0;
|
|
}else{
|
|
aData[got] = 0;
|
|
if( iOfst+iAmt<512 ){
|
|
int k = iOfst+iAmt;
|
|
aData[k*2] = 0;
|
|
n = kvvfsDecode(aData, &aData[2000], SQLITE_KVOS_SZ-2000);
|
|
if( n>=iOfst+iAmt ){
|
|
memcpy(zBuf, &aData[2000+iOfst], iAmt);
|
|
n = iAmt;
|
|
}else{
|
|
n = 0;
|
|
}
|
|
}else{
|
|
n = kvvfsDecode(aData, zBuf, iAmt);
|
|
}
|
|
}
|
|
if( n<iAmt ){
|
|
memset(zBuf+n, 0, iAmt-n);
|
|
return SQLITE_IOERR_SHORT_READ;
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
** Write into the -journal file.
|
|
*/
|
|
static int kvvfsWriteJrnl(
|
|
sqlite3_file *pProtoFile,
|
|
const void *zBuf,
|
|
int iAmt,
|
|
sqlite_int64 iOfst
|
|
){
|
|
KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
|
|
sqlite3_int64 iEnd = iOfst+iAmt;
|
|
SQLITE_KV_LOG(("xWrite('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
|
|
if( iEnd>=0x10000000 ) return SQLITE_FULL;
|
|
if( pFile->aJrnl==0 || pFile->nJrnl<iEnd ){
|
|
char *aNew = sqlite3_realloc(pFile->aJrnl, iEnd);
|
|
if( aNew==0 ){
|
|
return SQLITE_IOERR_NOMEM;
|
|
}
|
|
pFile->aJrnl = aNew;
|
|
if( pFile->nJrnl<iOfst ){
|
|
memset(pFile->aJrnl+pFile->nJrnl, 0, iOfst-pFile->nJrnl);
|
|
}
|
|
pFile->nJrnl = iEnd;
|
|
}
|
|
memcpy(pFile->aJrnl+iOfst, zBuf, iAmt);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Write into the database file.
|
|
*/
|
|
static int kvvfsWriteDb(
|
|
sqlite3_file *pProtoFile,
|
|
const void *zBuf,
|
|
int iAmt,
|
|
sqlite_int64 iOfst
|
|
){
|
|
KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
|
|
unsigned int pgno;
|
|
char zKey[30];
|
|
char *aData = pFile->aData;
|
|
SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst));
|
|
assert( iAmt>=512 && iAmt<=65536 );
|
|
assert( (iAmt & (iAmt-1))==0 );
|
|
assert( pFile->szPage<0 || pFile->szPage==iAmt );
|
|
pFile->szPage = iAmt;
|
|
pgno = 1 + iOfst/iAmt;
|
|
sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno);
|
|
kvvfsEncode(zBuf, iAmt, aData);
|
|
if( sqlite3KvvfsMethods.xWrite(pFile->zClass, zKey, aData) ){
|
|
return SQLITE_IOERR;
|
|
}
|
|
if( iOfst+iAmt > pFile->szDb ){
|
|
pFile->szDb = iOfst + iAmt;
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Truncate an kvvfs-file.
|
|
*/
|
|
static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){
|
|
KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
|
|
SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size));
|
|
assert( size==0 );
|
|
sqlite3KvvfsMethods.xDelete(pFile->zClass, "jrnl");
|
|
sqlite3_free(pFile->aJrnl);
|
|
pFile->aJrnl = 0;
|
|
pFile->nJrnl = 0;
|
|
return SQLITE_OK;
|
|
}
|
|
static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){
|
|
KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
|
|
if( pFile->szDb>size
|
|
&& pFile->szPage>0
|
|
&& (size % pFile->szPage)==0
|
|
){
|
|
char zKey[50];
|
|
unsigned int pgno, pgnoMax;
|
|
SQLITE_KV_LOG(("xTruncate('%s-db',%lld)\n", pFile->zClass, size));
|
|
pgno = 1 + size/pFile->szPage;
|
|
pgnoMax = 2 + pFile->szDb/pFile->szPage;
|
|
while( pgno<=pgnoMax ){
|
|
sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno);
|
|
sqlite3KvvfsMethods.xDelete(pFile->zClass, zKey);
|
|
pgno++;
|
|
}
|
|
pFile->szDb = size;
|
|
return kvvfsWriteFileSize(pFile, size) ? SQLITE_IOERR : SQLITE_OK;
|
|
}
|
|
return SQLITE_IOERR;
|
|
}
|
|
|
|
/*
|
|
** Sync an kvvfs-file.
|
|
*/
|
|
static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){
|
|
int i, n;
|
|
KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
|
|
char *zOut;
|
|
SQLITE_KV_LOG(("xSync('%s-journal')\n", pFile->zClass));
|
|
if( pFile->nJrnl<=0 ){
|
|
return kvvfsTruncateJrnl(pProtoFile, 0);
|
|
}
|
|
zOut = sqlite3_malloc64( pFile->nJrnl*2 + 50 );
|
|
if( zOut==0 ){
|
|
return SQLITE_IOERR_NOMEM;
|
|
}
|
|
n = pFile->nJrnl;
|
|
i = 0;
|
|
do{
|
|
zOut[i++] = 'a' + (n%26);
|
|
n /= 26;
|
|
}while( n>0 );
|
|
zOut[i++] = ' ';
|
|
kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]);
|
|
i = sqlite3KvvfsMethods.xWrite(pFile->zClass, "jrnl", zOut);
|
|
sqlite3_free(zOut);
|
|
return i ? SQLITE_IOERR : SQLITE_OK;
|
|
}
|
|
static int kvvfsSyncDb(sqlite3_file *pProtoFile, int flags){
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Return the current file-size of an kvvfs-file.
|
|
*/
|
|
static int kvvfsFileSizeJrnl(sqlite3_file *pProtoFile, sqlite_int64 *pSize){
|
|
KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
|
|
SQLITE_KV_LOG(("xFileSize('%s-journal')\n", pFile->zClass));
|
|
*pSize = pFile->nJrnl;
|
|
return SQLITE_OK;
|
|
}
|
|
static int kvvfsFileSizeDb(sqlite3_file *pProtoFile, sqlite_int64 *pSize){
|
|
KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
|
|
SQLITE_KV_LOG(("xFileSize('%s-db')\n", pFile->zClass));
|
|
if( pFile->szDb>=0 ){
|
|
*pSize = pFile->szDb;
|
|
}else{
|
|
*pSize = kvvfsReadFileSize(pFile);
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Lock an kvvfs-file.
|
|
*/
|
|
static int kvvfsLock(sqlite3_file *pProtoFile, int eLock){
|
|
KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
|
|
assert( !pFile->isJournal );
|
|
SQLITE_KV_LOG(("xLock(%s,%d)\n", pFile->zClass, eLock));
|
|
|
|
if( eLock!=SQLITE_LOCK_NONE ){
|
|
pFile->szDb = kvvfsReadFileSize(pFile);
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Unlock an kvvfs-file.
|
|
*/
|
|
static int kvvfsUnlock(sqlite3_file *pProtoFile, int eLock){
|
|
KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
|
|
assert( !pFile->isJournal );
|
|
SQLITE_KV_LOG(("xUnlock(%s,%d)\n", pFile->zClass, eLock));
|
|
if( eLock==SQLITE_LOCK_NONE ){
|
|
pFile->szDb = -1;
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Check if another file-handle holds a RESERVED lock on an kvvfs-file.
|
|
*/
|
|
static int kvvfsCheckReservedLock(sqlite3_file *pProtoFile, int *pResOut){
|
|
SQLITE_KV_LOG(("xCheckReservedLock\n"));
|
|
*pResOut = 0;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** File control method. For custom operations on an kvvfs-file.
|
|
*/
|
|
static int kvvfsFileControlJrnl(sqlite3_file *pProtoFile, int op, void *pArg){
|
|
SQLITE_KV_LOG(("xFileControl(%d) on journal\n", op));
|
|
return SQLITE_NOTFOUND;
|
|
}
|
|
static int kvvfsFileControlDb(sqlite3_file *pProtoFile, int op, void *pArg){
|
|
SQLITE_KV_LOG(("xFileControl(%d) on database\n", op));
|
|
if( op==SQLITE_FCNTL_SYNC ){
|
|
KVVfsFile *pFile = (KVVfsFile *)pProtoFile;
|
|
int rc = SQLITE_OK;
|
|
SQLITE_KV_LOG(("xSync('%s-db')\n", pFile->zClass));
|
|
if( pFile->szDb>0 && 0!=kvvfsWriteFileSize(pFile, pFile->szDb) ){
|
|
rc = SQLITE_IOERR;
|
|
}
|
|
return rc;
|
|
}
|
|
return SQLITE_NOTFOUND;
|
|
}
|
|
|
|
/*
|
|
** Return the sector-size in bytes for an kvvfs-file.
|
|
*/
|
|
static int kvvfsSectorSize(sqlite3_file *pFile){
|
|
return 512;
|
|
}
|
|
|
|
/*
|
|
** Return the device characteristic flags supported by an kvvfs-file.
|
|
*/
|
|
static int kvvfsDeviceCharacteristics(sqlite3_file *pProtoFile){
|
|
return 0;
|
|
}
|
|
|
|
/****** sqlite3_vfs methods *************************************************/
|
|
|
|
/*
|
|
** Open an kvvfs file handle.
|
|
*/
|
|
static int kvvfsOpen(
|
|
sqlite3_vfs *pProtoVfs,
|
|
const char *zName,
|
|
sqlite3_file *pProtoFile,
|
|
int flags,
|
|
int *pOutFlags
|
|
){
|
|
KVVfsFile *pFile = (KVVfsFile*)pProtoFile;
|
|
if( zName==0 ) zName = "";
|
|
SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName));
|
|
if( strcmp(zName, "local")==0
|
|
|| strcmp(zName, "session")==0
|
|
){
|
|
pFile->isJournal = 0;
|
|
pFile->base.pMethods = &kvvfs_db_io_methods;
|
|
}else
|
|
if( strcmp(zName, "local-journal")==0
|
|
|| strcmp(zName, "session-journal")==0
|
|
){
|
|
pFile->isJournal = 1;
|
|
pFile->base.pMethods = &kvvfs_jrnl_io_methods;
|
|
}else{
|
|
return SQLITE_CANTOPEN;
|
|
}
|
|
if( zName[0]=='s' ){
|
|
pFile->zClass = "session";
|
|
}else{
|
|
pFile->zClass = "local";
|
|
}
|
|
pFile->aData = sqlite3_malloc64(SQLITE_KVOS_SZ);
|
|
if( pFile->aData==0 ){
|
|
return SQLITE_NOMEM;
|
|
}
|
|
pFile->aJrnl = 0;
|
|
pFile->nJrnl = 0;
|
|
pFile->szPage = -1;
|
|
pFile->szDb = -1;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Delete the file located at zPath. If the dirSync argument is true,
|
|
** ensure the file-system modifications are synced to disk before
|
|
** returning.
|
|
*/
|
|
static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
|
|
if( strcmp(zPath, "local-journal")==0 ){
|
|
sqlite3KvvfsMethods.xDelete("local", "jrnl");
|
|
}else
|
|
if( strcmp(zPath, "session-journal")==0 ){
|
|
sqlite3KvvfsMethods.xDelete("session", "jrnl");
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Test for access permissions. Return true if the requested permission
|
|
** is available, or false otherwise.
|
|
*/
|
|
static int kvvfsAccess(
|
|
sqlite3_vfs *pProtoVfs,
|
|
const char *zPath,
|
|
int flags,
|
|
int *pResOut
|
|
){
|
|
SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath));
|
|
if( strcmp(zPath, "local-journal")==0 ){
|
|
*pResOut = sqlite3KvvfsMethods.xRead("local", "jrnl", 0, 0)>0;
|
|
}else
|
|
if( strcmp(zPath, "session-journal")==0 ){
|
|
*pResOut = sqlite3KvvfsMethods.xRead("session", "jrnl", 0, 0)>0;
|
|
}else
|
|
if( strcmp(zPath, "local")==0 ){
|
|
*pResOut = sqlite3KvvfsMethods.xRead("local", "sz", 0, 0)>0;
|
|
}else
|
|
if( strcmp(zPath, "session")==0 ){
|
|
*pResOut = sqlite3KvvfsMethods.xRead("session", "sz", 0, 0)>0;
|
|
}else
|
|
{
|
|
*pResOut = 0;
|
|
}
|
|
SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut));
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Populate buffer zOut with the full canonical pathname corresponding
|
|
** to the pathname in zPath. zOut is guaranteed to point to a buffer
|
|
** of at least (INST_MAX_PATHNAME+1) bytes.
|
|
*/
|
|
static int kvvfsFullPathname(
|
|
sqlite3_vfs *pVfs,
|
|
const char *zPath,
|
|
int nOut,
|
|
char *zOut
|
|
){
|
|
size_t nPath;
|
|
#ifdef SQLITE_OS_KV_ALWAYS_LOCAL
|
|
zPath = "local";
|
|
#endif
|
|
nPath = strlen(zPath);
|
|
SQLITE_KV_LOG(("xFullPathname(\"%s\")\n", zPath));
|
|
if( nOut<nPath+1 ) nPath = nOut - 1;
|
|
memcpy(zOut, zPath, nPath);
|
|
zOut[nPath] = 0;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Open the dynamic library located at zPath and return a handle.
|
|
*/
|
|
static void *kvvfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
** Populate the buffer pointed to by zBufOut with nByte bytes of
|
|
** random data.
|
|
*/
|
|
static int kvvfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
|
|
memset(zBufOut, 0, nByte);
|
|
return nByte;
|
|
}
|
|
|
|
/*
|
|
** Sleep for nMicro microseconds. Return the number of microseconds
|
|
** actually slept.
|
|
*/
|
|
static int kvvfsSleep(sqlite3_vfs *pVfs, int nMicro){
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Return the current time as a Julian Day number in *pTimeOut.
|
|
*/
|
|
static int kvvfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
|
|
sqlite3_int64 i = 0;
|
|
int rc;
|
|
rc = kvvfsCurrentTimeInt64(0, &i);
|
|
*pTimeOut = i/86400000.0;
|
|
return rc;
|
|
}
|
|
#include <sys/time.h>
|
|
static int kvvfsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
|
|
static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000;
|
|
struct timeval sNow;
|
|
(void)gettimeofday(&sNow, 0); /* Cannot fail given valid arguments */
|
|
*pTimeOut = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000;
|
|
return SQLITE_OK;
|
|
}
|
|
#endif /* SQLITE_OS_KV || SQLITE_OS_UNIX */
|
|
|
|
#if SQLITE_OS_KV
|
|
/*
|
|
** This routine is called initialize the KV-vfs as the default VFS.
|
|
*/
|
|
int sqlite3_os_init(void){
|
|
return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 1);
|
|
}
|
|
int sqlite3_os_end(void){
|
|
return SQLITE_OK;
|
|
}
|
|
#endif /* SQLITE_OS_KV */
|
|
|
|
#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL)
|
|
int sqlite3KvvfsInit(void){
|
|
return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 0);
|
|
}
|
|
#endif
|