From 9ee8378d393778ac67314be7ea8d5bcbaeee9ee0 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Sun, 7 Dec 2014 10:08:07 +0100 Subject: try out unqlite --- common/unqlite/mem_kv.c | 678 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 678 insertions(+) create mode 100644 common/unqlite/mem_kv.c (limited to 'common/unqlite/mem_kv.c') diff --git a/common/unqlite/mem_kv.c b/common/unqlite/mem_kv.c new file mode 100644 index 0000000..bdd3776 --- /dev/null +++ b/common/unqlite/mem_kv.c @@ -0,0 +1,678 @@ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: mem_kv.c v1.7 Win7 2012-11-28 01:41 stable $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* + * This file implements an in-memory key value storage engine for unQLite. + * Note that this storage engine does not support transactions. + * + * Normaly, I (chm@symisc.net) planned to implement a red-black tree + * which is suitable for this kind of operation, but due to the lack + * of time, I decided to implement a tunned hashtable which everybody + * know works very well for this kind of operation. + * Again, I insist on a red-black tree implementation for future version + * of Unqlite. + */ +/* Forward declaration */ +typedef struct mem_hash_kv_engine mem_hash_kv_engine; +/* + * Each record is storead in an instance of the following structure. + */ +typedef struct mem_hash_record mem_hash_record; +struct mem_hash_record +{ + mem_hash_kv_engine *pEngine; /* Storage engine */ + sxu32 nHash; /* Hash of the key */ + const void *pKey; /* Key */ + sxu32 nKeyLen; /* Key size (Max 1GB) */ + const void *pData; /* Data */ + sxu32 nDataLen; /* Data length (Max 4GB) */ + mem_hash_record *pNext,*pPrev; /* Link to other records */ + mem_hash_record *pNextHash,*pPrevHash; /* Collision link */ +}; +/* + * Each in-memory KV engine is represented by an instance + * of the following structure. + */ +struct mem_hash_kv_engine +{ + const unqlite_kv_io *pIo; /* IO methods: MUST be first */ + /* Private data */ + SyMemBackend sAlloc; /* Private memory allocator */ + ProcHash xHash; /* Default hash function */ + ProcCmp xCmp; /* Default comparison function */ + sxu32 nRecord; /* Total number of records */ + sxu32 nBucket; /* Bucket size: Must be a power of two */ + mem_hash_record **apBucket; /* Hash bucket */ + mem_hash_record *pFirst; /* First inserted entry */ + mem_hash_record *pLast; /* Last inserted entry */ +}; +/* + * Allocate a new hash record. + */ +static mem_hash_record * MemHashNewRecord( + mem_hash_kv_engine *pEngine, + const void *pKey,int nKey, + const void *pData,unqlite_int64 nData, + sxu32 nHash + ) +{ + SyMemBackend *pAlloc = &pEngine->sAlloc; + mem_hash_record *pRecord; + void *pDupData; + sxu32 nByte; + char *zPtr; + + /* Total number of bytes to alloc */ + nByte = sizeof(mem_hash_record) + nKey; + /* Allocate a new instance */ + pRecord = (mem_hash_record *)SyMemBackendAlloc(pAlloc,nByte); + if( pRecord == 0 ){ + return 0; + } + pDupData = (void *)SyMemBackendAlloc(pAlloc,(sxu32)nData); + if( pDupData == 0 ){ + SyMemBackendFree(pAlloc,pRecord); + return 0; + } + zPtr = (char *)pRecord; + zPtr += sizeof(mem_hash_record); + /* Zero the structure */ + SyZero(pRecord,sizeof(mem_hash_record)); + /* Fill in the structure */ + pRecord->pEngine = pEngine; + pRecord->nDataLen = (sxu32)nData; + pRecord->nKeyLen = (sxu32)nKey; + pRecord->nHash = nHash; + SyMemcpy(pKey,zPtr,pRecord->nKeyLen); + pRecord->pKey = (const void *)zPtr; + SyMemcpy(pData,pDupData,pRecord->nDataLen); + pRecord->pData = pDupData; + /* All done */ + return pRecord; +} +/* + * Install a given record in the hashtable. + */ +static void MemHashLinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pRecord) +{ + sxu32 nBucket = pRecord->nHash & (pEngine->nBucket - 1); + pRecord->pNextHash = pEngine->apBucket[nBucket]; + if( pEngine->apBucket[nBucket] ){ + pEngine->apBucket[nBucket]->pPrevHash = pRecord; + } + pEngine->apBucket[nBucket] = pRecord; + if( pEngine->pFirst == 0 ){ + pEngine->pFirst = pEngine->pLast = pRecord; + }else{ + MACRO_LD_PUSH(pEngine->pLast,pRecord); + } + pEngine->nRecord++; +} +/* + * Unlink a given record from the hashtable. + */ +static void MemHashUnlinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pEntry) +{ + sxu32 nBucket = pEntry->nHash & (pEngine->nBucket - 1); + SyMemBackend *pAlloc = &pEngine->sAlloc; + if( pEntry->pPrevHash == 0 ){ + pEngine->apBucket[nBucket] = pEntry->pNextHash; + }else{ + pEntry->pPrevHash->pNextHash = pEntry->pNextHash; + } + if( pEntry->pNextHash ){ + pEntry->pNextHash->pPrevHash = pEntry->pPrevHash; + } + MACRO_LD_REMOVE(pEngine->pLast,pEntry); + if( pEntry == pEngine->pFirst ){ + pEngine->pFirst = pEntry->pPrev; + } + pEngine->nRecord--; + /* Release the entry */ + SyMemBackendFree(pAlloc,(void *)pEntry->pData); + SyMemBackendFree(pAlloc,pEntry); /* Key is also stored here */ +} +/* + * Perform a lookup for a given entry. + */ +static mem_hash_record * MemHashGetEntry( + mem_hash_kv_engine *pEngine, + const void *pKey,int nKeyLen + ) +{ + mem_hash_record *pEntry; + sxu32 nHash,nBucket; + /* Hash the entry */ + nHash = pEngine->xHash(pKey,(sxu32)nKeyLen); + nBucket = nHash & (pEngine->nBucket - 1); + pEntry = pEngine->apBucket[nBucket]; + for(;;){ + if( pEntry == 0 ){ + break; + } + if( pEntry->nHash == nHash && pEntry->nKeyLen == (sxu32)nKeyLen && + pEngine->xCmp(pEntry->pKey,pKey,pEntry->nKeyLen) == 0 ){ + return pEntry; + } + pEntry = pEntry->pNextHash; + } + /* No such entry */ + return 0; +} +/* + * Rehash all the entries in the given table. + */ +static int MemHashGrowTable(mem_hash_kv_engine *pEngine) +{ + sxu32 nNewSize = pEngine->nBucket << 1; + mem_hash_record *pEntry; + mem_hash_record **apNew; + sxu32 n,iBucket; + /* Allocate a new larger table */ + apNew = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc, nNewSize * sizeof(mem_hash_record *)); + if( apNew == 0 ){ + /* Not so fatal, simply a performance hit */ + return UNQLITE_OK; + } + /* Zero the new table */ + SyZero((void *)apNew, nNewSize * sizeof(mem_hash_record *)); + /* Rehash all entries */ + n = 0; + pEntry = pEngine->pLast; + for(;;){ + + /* Loop one */ + if( n >= pEngine->nRecord ){ + break; + } + pEntry->pNextHash = pEntry->pPrevHash = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextHash = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevHash = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + + /* Loop two */ + if( n >= pEngine->nRecord ){ + break; + } + pEntry->pNextHash = pEntry->pPrevHash = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextHash = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevHash = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + + /* Loop three */ + if( n >= pEngine->nRecord ){ + break; + } + pEntry->pNextHash = pEntry->pPrevHash = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextHash = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevHash = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + + /* Loop four */ + if( n >= pEngine->nRecord ){ + break; + } + pEntry->pNextHash = pEntry->pPrevHash = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextHash = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevHash = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(&pEngine->sAlloc,(void *)pEngine->apBucket); + pEngine->apBucket = apNew; + pEngine->nBucket = nNewSize; + return UNQLITE_OK; +} +/* + * Exported Interfaces. + */ +/* + * Each public cursor is identified by an instance of this structure. + */ +typedef struct mem_hash_cursor mem_hash_cursor; +struct mem_hash_cursor +{ + unqlite_kv_engine *pStore; /* Must be first */ + /* Private fields */ + mem_hash_record *pCur; /* Current hash record */ +}; +/* + * Initialize the cursor. + */ +static void MemHashInitCursor(unqlite_kv_cursor *pCursor) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + /* Point to the first inserted entry */ + pMem->pCur = pEngine->pFirst; +} +/* + * Point to the first entry. + */ +static int MemHashCursorFirst(unqlite_kv_cursor *pCursor) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + pMem->pCur = pEngine->pFirst; + return UNQLITE_OK; +} +/* + * Point to the last entry. + */ +static int MemHashCursorLast(unqlite_kv_cursor *pCursor) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + pMem->pCur = pEngine->pLast; + return UNQLITE_OK; +} +/* + * is a Valid Cursor. + */ +static int MemHashCursorValid(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + return pMem->pCur != 0 ? 1 : 0; +} +/* + * Point to the next entry. + */ +static int MemHashCursorNext(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + pMem->pCur = pMem->pCur->pPrev; /* Reverse link: Not a Bug */ + return UNQLITE_OK; +} +/* + * Point to the previous entry. + */ +static int MemHashCursorPrev(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + pMem->pCur = pMem->pCur->pNext; /* Reverse link: Not a Bug */ + return UNQLITE_OK; +} +/* + * Return key length. + */ +static int MemHashCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + *pLen = (int)pMem->pCur->nKeyLen; + return UNQLITE_OK; +} +/* + * Return data length. + */ +static int MemHashCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + if( pMem->pCur == 0 ){ + return UNQLITE_EOF; + } + *pLen = pMem->pCur->nDataLen; + return UNQLITE_OK; +} +/* + * Consume the key. + */ +static int MemHashCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + int rc; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + /* Invoke the callback */ + rc = xConsumer(pMem->pCur->pKey,pMem->pCur->nKeyLen,pUserData); + /* Callback result */ + return rc; +} +/* + * Consume the data. + */ +static int MemHashCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + int rc; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + /* Invoke the callback */ + rc = xConsumer(pMem->pCur->pData,pMem->pCur->nDataLen,pUserData); + /* Callback result */ + return rc; +} +/* + * Reset the cursor. + */ +static void MemHashCursorReset(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + pMem->pCur = ((mem_hash_kv_engine *)pCursor->pStore)->pFirst; +} +/* + * Remove a particular record. + */ +static int MemHashCursorDelete(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + mem_hash_record *pNext; + if( pMem->pCur == 0 ){ + /* Cursor does not point to anything */ + return UNQLITE_NOTFOUND; + } + pNext = pMem->pCur->pPrev; + /* Perform the deletion */ + MemHashUnlinkRecord(pMem->pCur->pEngine,pMem->pCur); + /* Point to the next entry */ + pMem->pCur = pNext; + return UNQLITE_OK; +} +/* + * Find a particular record. + */ +static int MemHashCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + /* Perform the lookup */ + pMem->pCur = MemHashGetEntry(pEngine,pKey,nByte); + if( pMem->pCur == 0 ){ + if( iPos != UNQLITE_CURSOR_MATCH_EXACT ){ + /* noop; */ + } + /* No such record */ + return UNQLITE_NOTFOUND; + } + return UNQLITE_OK; +} +/* + * Builtin hash function. + */ +static sxu32 MemHashFunc(const void *pSrc,sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + sxu32 nH = 5381; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + } + return nH; +} +/* Default bucket size */ +#define MEM_HASH_BUCKET_SIZE 64 +/* Default fill factor */ +#define MEM_HASH_FILL_FACTOR 4 /* or 3 */ +/* + * Initialize the in-memory storage engine. + */ +static int MemHashInit(unqlite_kv_engine *pKvEngine,int iPageSize) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; + /* Note that this instance is already zeroed */ + /* Memory backend */ + SyMemBackendInitFromParent(&pEngine->sAlloc,unqliteExportMemBackend()); +#if defined(UNQLITE_ENABLE_THREADS) + /* Already protected by the upper layers */ + SyMemBackendDisbaleMutexing(&pEngine->sAlloc); +#endif + /* Default hash & comparison function */ + pEngine->xHash = MemHashFunc; + pEngine->xCmp = SyMemcmp; + /* Allocate a new bucket */ + pEngine->apBucket = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); + if( pEngine->apBucket == 0 ){ + SXUNUSED(iPageSize); /* cc warning */ + return UNQLITE_NOMEM; + } + /* Zero the bucket */ + SyZero(pEngine->apBucket,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); + pEngine->nRecord = 0; + pEngine->nBucket = MEM_HASH_BUCKET_SIZE; + return UNQLITE_OK; +} +/* + * Release the in-memory storage engine. + */ +static void MemHashRelease(unqlite_kv_engine *pKvEngine) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; + /* Release the private memory backend */ + SyMemBackendRelease(&pEngine->sAlloc); +} +/* + * Configure the in-memory storage engine. + */ +static int MemHashConfigure(unqlite_kv_engine *pKvEngine,int iOp,va_list ap) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; + int rc = UNQLITE_OK; + switch(iOp){ + case UNQLITE_KV_CONFIG_HASH_FUNC:{ + /* Use a default hash function */ + if( pEngine->nRecord > 0 ){ + rc = UNQLITE_LOCKED; + }else{ + ProcHash xHash = va_arg(ap,ProcHash); + if( xHash ){ + pEngine->xHash = xHash; + } + } + break; + } + case UNQLITE_KV_CONFIG_CMP_FUNC: { + /* Default comparison function */ + ProcCmp xCmp = va_arg(ap,ProcCmp); + if( xCmp ){ + pEngine->xCmp = xCmp; + } + break; + } + default: + /* Unknown configuration option */ + rc = UNQLITE_UNKNOWN; + } + return rc; +} +/* + * Replace method. + */ +static int MemHashReplace( + unqlite_kv_engine *pKv, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; + mem_hash_record *pRecord; + if( nDataLen > SXU32_HIGH ){ + /* Database limit */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); + return UNQLITE_LIMIT; + } + /* Fetch the record first */ + pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); + if( pRecord == 0 ){ + /* Allocate a new record */ + pRecord = MemHashNewRecord(pEngine, + pKey,nKeyLen, + pData,nDataLen, + pEngine->xHash(pKey,nKeyLen) + ); + if( pRecord == 0 ){ + return UNQLITE_NOMEM; + } + /* Link the entry */ + MemHashLinkRecord(pEngine,pRecord); + if( (pEngine->nRecord >= pEngine->nBucket * MEM_HASH_FILL_FACTOR) && pEngine->nRecord < 100000 ){ + /* Rehash the table */ + MemHashGrowTable(pEngine); + } + }else{ + sxu32 nData = (sxu32)nDataLen; + void *pNew; + /* Replace an existing record */ + if( nData == pRecord->nDataLen ){ + /* No need to free the old chunk */ + pNew = (void *)pRecord->pData; + }else{ + pNew = SyMemBackendAlloc(&pEngine->sAlloc,nData); + if( pNew == 0 ){ + return UNQLITE_NOMEM; + } + /* Release the old data */ + SyMemBackendFree(&pEngine->sAlloc,(void *)pRecord->pData); + } + /* Reflect the change */ + pRecord->nDataLen = nData; + SyMemcpy(pData,pNew,nData); + pRecord->pData = pNew; + } + return UNQLITE_OK; +} +/* + * Append method. + */ +static int MemHashAppend( + unqlite_kv_engine *pKv, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; + mem_hash_record *pRecord; + if( nDataLen > SXU32_HIGH ){ + /* Database limit */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); + return UNQLITE_LIMIT; + } + /* Fetch the record first */ + pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); + if( pRecord == 0 ){ + /* Allocate a new record */ + pRecord = MemHashNewRecord(pEngine, + pKey,nKeyLen, + pData,nDataLen, + pEngine->xHash(pKey,nKeyLen) + ); + if( pRecord == 0 ){ + return UNQLITE_NOMEM; + } + /* Link the entry */ + MemHashLinkRecord(pEngine,pRecord); + if( pEngine->nRecord * MEM_HASH_FILL_FACTOR >= pEngine->nBucket && pEngine->nRecord < 100000 ){ + /* Rehash the table */ + MemHashGrowTable(pEngine); + } + }else{ + unqlite_int64 nNew = pRecord->nDataLen + nDataLen; + void *pOld = (void *)pRecord->pData; + sxu32 nData; + char *zNew; + /* Append data to the existing record */ + if( nNew > SXU32_HIGH ){ + /* Overflow */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow"); + return UNQLITE_LIMIT; + } + nData = (sxu32)nNew; + /* Allocate bigger chunk */ + zNew = (char *)SyMemBackendRealloc(&pEngine->sAlloc,pOld,nData); + if( zNew == 0 ){ + return UNQLITE_NOMEM; + } + /* Reflect the change */ + SyMemcpy(pData,&zNew[pRecord->nDataLen],(sxu32)nDataLen); + pRecord->pData = (const void *)zNew; + pRecord->nDataLen = nData; + } + return UNQLITE_OK; +} +/* + * Export the in-memory storage engine. + */ +UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void) +{ + static const unqlite_kv_methods sMemStore = { + "mem", /* zName */ + sizeof(mem_hash_kv_engine), /* szKv */ + sizeof(mem_hash_cursor), /* szCursor */ + 1, /* iVersion */ + MemHashInit, /* xInit */ + MemHashRelease, /* xRelease */ + MemHashConfigure, /* xConfig */ + 0, /* xOpen */ + MemHashReplace, /* xReplace */ + MemHashAppend, /* xAppend */ + MemHashInitCursor, /* xCursorInit */ + MemHashCursorSeek, /* xSeek */ + MemHashCursorFirst, /* xFirst */ + MemHashCursorLast, /* xLast */ + MemHashCursorValid, /* xValid */ + MemHashCursorNext, /* xNext */ + MemHashCursorPrev, /* xPrev */ + MemHashCursorDelete, /* xDelete */ + MemHashCursorKeyLength, /* xKeyLength */ + MemHashCursorKey, /* xKey */ + MemHashCursorDataLength, /* xDataLength */ + MemHashCursorData, /* xData */ + MemHashCursorReset, /* xReset */ + 0 /* xRelease */ + }; + return &sMemStore; +} -- cgit v1.2.3