diff options
Diffstat (limited to 'common/unqlite/mem_kv.c')
-rw-r--r-- | common/unqlite/mem_kv.c | 678 |
1 files changed, 678 insertions, 0 deletions
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 @@ | |||
1 | /* | ||
2 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
3 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
4 | * Version 1.1.6 | ||
5 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
6 | * please contact Symisc Systems via: | ||
7 | * legal@symisc.net | ||
8 | * licensing@symisc.net | ||
9 | * contact@symisc.net | ||
10 | * or visit: | ||
11 | * http://unqlite.org/licensing.html | ||
12 | */ | ||
13 | /* $SymiscID: mem_kv.c v1.7 Win7 2012-11-28 01:41 stable <chm@symisc.net> $ */ | ||
14 | #ifndef UNQLITE_AMALGAMATION | ||
15 | #include "unqliteInt.h" | ||
16 | #endif | ||
17 | /* | ||
18 | * This file implements an in-memory key value storage engine for unQLite. | ||
19 | * Note that this storage engine does not support transactions. | ||
20 | * | ||
21 | * Normaly, I (chm@symisc.net) planned to implement a red-black tree | ||
22 | * which is suitable for this kind of operation, but due to the lack | ||
23 | * of time, I decided to implement a tunned hashtable which everybody | ||
24 | * know works very well for this kind of operation. | ||
25 | * Again, I insist on a red-black tree implementation for future version | ||
26 | * of Unqlite. | ||
27 | */ | ||
28 | /* Forward declaration */ | ||
29 | typedef struct mem_hash_kv_engine mem_hash_kv_engine; | ||
30 | /* | ||
31 | * Each record is storead in an instance of the following structure. | ||
32 | */ | ||
33 | typedef struct mem_hash_record mem_hash_record; | ||
34 | struct mem_hash_record | ||
35 | { | ||
36 | mem_hash_kv_engine *pEngine; /* Storage engine */ | ||
37 | sxu32 nHash; /* Hash of the key */ | ||
38 | const void *pKey; /* Key */ | ||
39 | sxu32 nKeyLen; /* Key size (Max 1GB) */ | ||
40 | const void *pData; /* Data */ | ||
41 | sxu32 nDataLen; /* Data length (Max 4GB) */ | ||
42 | mem_hash_record *pNext,*pPrev; /* Link to other records */ | ||
43 | mem_hash_record *pNextHash,*pPrevHash; /* Collision link */ | ||
44 | }; | ||
45 | /* | ||
46 | * Each in-memory KV engine is represented by an instance | ||
47 | * of the following structure. | ||
48 | */ | ||
49 | struct mem_hash_kv_engine | ||
50 | { | ||
51 | const unqlite_kv_io *pIo; /* IO methods: MUST be first */ | ||
52 | /* Private data */ | ||
53 | SyMemBackend sAlloc; /* Private memory allocator */ | ||
54 | ProcHash xHash; /* Default hash function */ | ||
55 | ProcCmp xCmp; /* Default comparison function */ | ||
56 | sxu32 nRecord; /* Total number of records */ | ||
57 | sxu32 nBucket; /* Bucket size: Must be a power of two */ | ||
58 | mem_hash_record **apBucket; /* Hash bucket */ | ||
59 | mem_hash_record *pFirst; /* First inserted entry */ | ||
60 | mem_hash_record *pLast; /* Last inserted entry */ | ||
61 | }; | ||
62 | /* | ||
63 | * Allocate a new hash record. | ||
64 | */ | ||
65 | static mem_hash_record * MemHashNewRecord( | ||
66 | mem_hash_kv_engine *pEngine, | ||
67 | const void *pKey,int nKey, | ||
68 | const void *pData,unqlite_int64 nData, | ||
69 | sxu32 nHash | ||
70 | ) | ||
71 | { | ||
72 | SyMemBackend *pAlloc = &pEngine->sAlloc; | ||
73 | mem_hash_record *pRecord; | ||
74 | void *pDupData; | ||
75 | sxu32 nByte; | ||
76 | char *zPtr; | ||
77 | |||
78 | /* Total number of bytes to alloc */ | ||
79 | nByte = sizeof(mem_hash_record) + nKey; | ||
80 | /* Allocate a new instance */ | ||
81 | pRecord = (mem_hash_record *)SyMemBackendAlloc(pAlloc,nByte); | ||
82 | if( pRecord == 0 ){ | ||
83 | return 0; | ||
84 | } | ||
85 | pDupData = (void *)SyMemBackendAlloc(pAlloc,(sxu32)nData); | ||
86 | if( pDupData == 0 ){ | ||
87 | SyMemBackendFree(pAlloc,pRecord); | ||
88 | return 0; | ||
89 | } | ||
90 | zPtr = (char *)pRecord; | ||
91 | zPtr += sizeof(mem_hash_record); | ||
92 | /* Zero the structure */ | ||
93 | SyZero(pRecord,sizeof(mem_hash_record)); | ||
94 | /* Fill in the structure */ | ||
95 | pRecord->pEngine = pEngine; | ||
96 | pRecord->nDataLen = (sxu32)nData; | ||
97 | pRecord->nKeyLen = (sxu32)nKey; | ||
98 | pRecord->nHash = nHash; | ||
99 | SyMemcpy(pKey,zPtr,pRecord->nKeyLen); | ||
100 | pRecord->pKey = (const void *)zPtr; | ||
101 | SyMemcpy(pData,pDupData,pRecord->nDataLen); | ||
102 | pRecord->pData = pDupData; | ||
103 | /* All done */ | ||
104 | return pRecord; | ||
105 | } | ||
106 | /* | ||
107 | * Install a given record in the hashtable. | ||
108 | */ | ||
109 | static void MemHashLinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pRecord) | ||
110 | { | ||
111 | sxu32 nBucket = pRecord->nHash & (pEngine->nBucket - 1); | ||
112 | pRecord->pNextHash = pEngine->apBucket[nBucket]; | ||
113 | if( pEngine->apBucket[nBucket] ){ | ||
114 | pEngine->apBucket[nBucket]->pPrevHash = pRecord; | ||
115 | } | ||
116 | pEngine->apBucket[nBucket] = pRecord; | ||
117 | if( pEngine->pFirst == 0 ){ | ||
118 | pEngine->pFirst = pEngine->pLast = pRecord; | ||
119 | }else{ | ||
120 | MACRO_LD_PUSH(pEngine->pLast,pRecord); | ||
121 | } | ||
122 | pEngine->nRecord++; | ||
123 | } | ||
124 | /* | ||
125 | * Unlink a given record from the hashtable. | ||
126 | */ | ||
127 | static void MemHashUnlinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pEntry) | ||
128 | { | ||
129 | sxu32 nBucket = pEntry->nHash & (pEngine->nBucket - 1); | ||
130 | SyMemBackend *pAlloc = &pEngine->sAlloc; | ||
131 | if( pEntry->pPrevHash == 0 ){ | ||
132 | pEngine->apBucket[nBucket] = pEntry->pNextHash; | ||
133 | }else{ | ||
134 | pEntry->pPrevHash->pNextHash = pEntry->pNextHash; | ||
135 | } | ||
136 | if( pEntry->pNextHash ){ | ||
137 | pEntry->pNextHash->pPrevHash = pEntry->pPrevHash; | ||
138 | } | ||
139 | MACRO_LD_REMOVE(pEngine->pLast,pEntry); | ||
140 | if( pEntry == pEngine->pFirst ){ | ||
141 | pEngine->pFirst = pEntry->pPrev; | ||
142 | } | ||
143 | pEngine->nRecord--; | ||
144 | /* Release the entry */ | ||
145 | SyMemBackendFree(pAlloc,(void *)pEntry->pData); | ||
146 | SyMemBackendFree(pAlloc,pEntry); /* Key is also stored here */ | ||
147 | } | ||
148 | /* | ||
149 | * Perform a lookup for a given entry. | ||
150 | */ | ||
151 | static mem_hash_record * MemHashGetEntry( | ||
152 | mem_hash_kv_engine *pEngine, | ||
153 | const void *pKey,int nKeyLen | ||
154 | ) | ||
155 | { | ||
156 | mem_hash_record *pEntry; | ||
157 | sxu32 nHash,nBucket; | ||
158 | /* Hash the entry */ | ||
159 | nHash = pEngine->xHash(pKey,(sxu32)nKeyLen); | ||
160 | nBucket = nHash & (pEngine->nBucket - 1); | ||
161 | pEntry = pEngine->apBucket[nBucket]; | ||
162 | for(;;){ | ||
163 | if( pEntry == 0 ){ | ||
164 | break; | ||
165 | } | ||
166 | if( pEntry->nHash == nHash && pEntry->nKeyLen == (sxu32)nKeyLen && | ||
167 | pEngine->xCmp(pEntry->pKey,pKey,pEntry->nKeyLen) == 0 ){ | ||
168 | return pEntry; | ||
169 | } | ||
170 | pEntry = pEntry->pNextHash; | ||
171 | } | ||
172 | /* No such entry */ | ||
173 | return 0; | ||
174 | } | ||
175 | /* | ||
176 | * Rehash all the entries in the given table. | ||
177 | */ | ||
178 | static int MemHashGrowTable(mem_hash_kv_engine *pEngine) | ||
179 | { | ||
180 | sxu32 nNewSize = pEngine->nBucket << 1; | ||
181 | mem_hash_record *pEntry; | ||
182 | mem_hash_record **apNew; | ||
183 | sxu32 n,iBucket; | ||
184 | /* Allocate a new larger table */ | ||
185 | apNew = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc, nNewSize * sizeof(mem_hash_record *)); | ||
186 | if( apNew == 0 ){ | ||
187 | /* Not so fatal, simply a performance hit */ | ||
188 | return UNQLITE_OK; | ||
189 | } | ||
190 | /* Zero the new table */ | ||
191 | SyZero((void *)apNew, nNewSize * sizeof(mem_hash_record *)); | ||
192 | /* Rehash all entries */ | ||
193 | n = 0; | ||
194 | pEntry = pEngine->pLast; | ||
195 | for(;;){ | ||
196 | |||
197 | /* Loop one */ | ||
198 | if( n >= pEngine->nRecord ){ | ||
199 | break; | ||
200 | } | ||
201 | pEntry->pNextHash = pEntry->pPrevHash = 0; | ||
202 | /* Install in the new bucket */ | ||
203 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
204 | pEntry->pNextHash = apNew[iBucket]; | ||
205 | if( apNew[iBucket] ){ | ||
206 | apNew[iBucket]->pPrevHash = pEntry; | ||
207 | } | ||
208 | apNew[iBucket] = pEntry; | ||
209 | /* Point to the next entry */ | ||
210 | pEntry = pEntry->pNext; | ||
211 | n++; | ||
212 | |||
213 | /* Loop two */ | ||
214 | if( n >= pEngine->nRecord ){ | ||
215 | break; | ||
216 | } | ||
217 | pEntry->pNextHash = pEntry->pPrevHash = 0; | ||
218 | /* Install in the new bucket */ | ||
219 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
220 | pEntry->pNextHash = apNew[iBucket]; | ||
221 | if( apNew[iBucket] ){ | ||
222 | apNew[iBucket]->pPrevHash = pEntry; | ||
223 | } | ||
224 | apNew[iBucket] = pEntry; | ||
225 | /* Point to the next entry */ | ||
226 | pEntry = pEntry->pNext; | ||
227 | n++; | ||
228 | |||
229 | /* Loop three */ | ||
230 | if( n >= pEngine->nRecord ){ | ||
231 | break; | ||
232 | } | ||
233 | pEntry->pNextHash = pEntry->pPrevHash = 0; | ||
234 | /* Install in the new bucket */ | ||
235 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
236 | pEntry->pNextHash = apNew[iBucket]; | ||
237 | if( apNew[iBucket] ){ | ||
238 | apNew[iBucket]->pPrevHash = pEntry; | ||
239 | } | ||
240 | apNew[iBucket] = pEntry; | ||
241 | /* Point to the next entry */ | ||
242 | pEntry = pEntry->pNext; | ||
243 | n++; | ||
244 | |||
245 | /* Loop four */ | ||
246 | if( n >= pEngine->nRecord ){ | ||
247 | break; | ||
248 | } | ||
249 | pEntry->pNextHash = pEntry->pPrevHash = 0; | ||
250 | /* Install in the new bucket */ | ||
251 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
252 | pEntry->pNextHash = apNew[iBucket]; | ||
253 | if( apNew[iBucket] ){ | ||
254 | apNew[iBucket]->pPrevHash = pEntry; | ||
255 | } | ||
256 | apNew[iBucket] = pEntry; | ||
257 | /* Point to the next entry */ | ||
258 | pEntry = pEntry->pNext; | ||
259 | n++; | ||
260 | } | ||
261 | /* Release the old table and reflect the change */ | ||
262 | SyMemBackendFree(&pEngine->sAlloc,(void *)pEngine->apBucket); | ||
263 | pEngine->apBucket = apNew; | ||
264 | pEngine->nBucket = nNewSize; | ||
265 | return UNQLITE_OK; | ||
266 | } | ||
267 | /* | ||
268 | * Exported Interfaces. | ||
269 | */ | ||
270 | /* | ||
271 | * Each public cursor is identified by an instance of this structure. | ||
272 | */ | ||
273 | typedef struct mem_hash_cursor mem_hash_cursor; | ||
274 | struct mem_hash_cursor | ||
275 | { | ||
276 | unqlite_kv_engine *pStore; /* Must be first */ | ||
277 | /* Private fields */ | ||
278 | mem_hash_record *pCur; /* Current hash record */ | ||
279 | }; | ||
280 | /* | ||
281 | * Initialize the cursor. | ||
282 | */ | ||
283 | static void MemHashInitCursor(unqlite_kv_cursor *pCursor) | ||
284 | { | ||
285 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; | ||
286 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
287 | /* Point to the first inserted entry */ | ||
288 | pMem->pCur = pEngine->pFirst; | ||
289 | } | ||
290 | /* | ||
291 | * Point to the first entry. | ||
292 | */ | ||
293 | static int MemHashCursorFirst(unqlite_kv_cursor *pCursor) | ||
294 | { | ||
295 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; | ||
296 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
297 | pMem->pCur = pEngine->pFirst; | ||
298 | return UNQLITE_OK; | ||
299 | } | ||
300 | /* | ||
301 | * Point to the last entry. | ||
302 | */ | ||
303 | static int MemHashCursorLast(unqlite_kv_cursor *pCursor) | ||
304 | { | ||
305 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; | ||
306 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
307 | pMem->pCur = pEngine->pLast; | ||
308 | return UNQLITE_OK; | ||
309 | } | ||
310 | /* | ||
311 | * is a Valid Cursor. | ||
312 | */ | ||
313 | static int MemHashCursorValid(unqlite_kv_cursor *pCursor) | ||
314 | { | ||
315 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
316 | return pMem->pCur != 0 ? 1 : 0; | ||
317 | } | ||
318 | /* | ||
319 | * Point to the next entry. | ||
320 | */ | ||
321 | static int MemHashCursorNext(unqlite_kv_cursor *pCursor) | ||
322 | { | ||
323 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
324 | if( pMem->pCur == 0){ | ||
325 | return UNQLITE_EOF; | ||
326 | } | ||
327 | pMem->pCur = pMem->pCur->pPrev; /* Reverse link: Not a Bug */ | ||
328 | return UNQLITE_OK; | ||
329 | } | ||
330 | /* | ||
331 | * Point to the previous entry. | ||
332 | */ | ||
333 | static int MemHashCursorPrev(unqlite_kv_cursor *pCursor) | ||
334 | { | ||
335 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
336 | if( pMem->pCur == 0){ | ||
337 | return UNQLITE_EOF; | ||
338 | } | ||
339 | pMem->pCur = pMem->pCur->pNext; /* Reverse link: Not a Bug */ | ||
340 | return UNQLITE_OK; | ||
341 | } | ||
342 | /* | ||
343 | * Return key length. | ||
344 | */ | ||
345 | static int MemHashCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen) | ||
346 | { | ||
347 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
348 | if( pMem->pCur == 0){ | ||
349 | return UNQLITE_EOF; | ||
350 | } | ||
351 | *pLen = (int)pMem->pCur->nKeyLen; | ||
352 | return UNQLITE_OK; | ||
353 | } | ||
354 | /* | ||
355 | * Return data length. | ||
356 | */ | ||
357 | static int MemHashCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen) | ||
358 | { | ||
359 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
360 | if( pMem->pCur == 0 ){ | ||
361 | return UNQLITE_EOF; | ||
362 | } | ||
363 | *pLen = pMem->pCur->nDataLen; | ||
364 | return UNQLITE_OK; | ||
365 | } | ||
366 | /* | ||
367 | * Consume the key. | ||
368 | */ | ||
369 | static int MemHashCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) | ||
370 | { | ||
371 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
372 | int rc; | ||
373 | if( pMem->pCur == 0){ | ||
374 | return UNQLITE_EOF; | ||
375 | } | ||
376 | /* Invoke the callback */ | ||
377 | rc = xConsumer(pMem->pCur->pKey,pMem->pCur->nKeyLen,pUserData); | ||
378 | /* Callback result */ | ||
379 | return rc; | ||
380 | } | ||
381 | /* | ||
382 | * Consume the data. | ||
383 | */ | ||
384 | static int MemHashCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) | ||
385 | { | ||
386 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
387 | int rc; | ||
388 | if( pMem->pCur == 0){ | ||
389 | return UNQLITE_EOF; | ||
390 | } | ||
391 | /* Invoke the callback */ | ||
392 | rc = xConsumer(pMem->pCur->pData,pMem->pCur->nDataLen,pUserData); | ||
393 | /* Callback result */ | ||
394 | return rc; | ||
395 | } | ||
396 | /* | ||
397 | * Reset the cursor. | ||
398 | */ | ||
399 | static void MemHashCursorReset(unqlite_kv_cursor *pCursor) | ||
400 | { | ||
401 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
402 | pMem->pCur = ((mem_hash_kv_engine *)pCursor->pStore)->pFirst; | ||
403 | } | ||
404 | /* | ||
405 | * Remove a particular record. | ||
406 | */ | ||
407 | static int MemHashCursorDelete(unqlite_kv_cursor *pCursor) | ||
408 | { | ||
409 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
410 | mem_hash_record *pNext; | ||
411 | if( pMem->pCur == 0 ){ | ||
412 | /* Cursor does not point to anything */ | ||
413 | return UNQLITE_NOTFOUND; | ||
414 | } | ||
415 | pNext = pMem->pCur->pPrev; | ||
416 | /* Perform the deletion */ | ||
417 | MemHashUnlinkRecord(pMem->pCur->pEngine,pMem->pCur); | ||
418 | /* Point to the next entry */ | ||
419 | pMem->pCur = pNext; | ||
420 | return UNQLITE_OK; | ||
421 | } | ||
422 | /* | ||
423 | * Find a particular record. | ||
424 | */ | ||
425 | static int MemHashCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos) | ||
426 | { | ||
427 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; | ||
428 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
429 | /* Perform the lookup */ | ||
430 | pMem->pCur = MemHashGetEntry(pEngine,pKey,nByte); | ||
431 | if( pMem->pCur == 0 ){ | ||
432 | if( iPos != UNQLITE_CURSOR_MATCH_EXACT ){ | ||
433 | /* noop; */ | ||
434 | } | ||
435 | /* No such record */ | ||
436 | return UNQLITE_NOTFOUND; | ||
437 | } | ||
438 | return UNQLITE_OK; | ||
439 | } | ||
440 | /* | ||
441 | * Builtin hash function. | ||
442 | */ | ||
443 | static sxu32 MemHashFunc(const void *pSrc,sxu32 nLen) | ||
444 | { | ||
445 | register unsigned char *zIn = (unsigned char *)pSrc; | ||
446 | unsigned char *zEnd; | ||
447 | sxu32 nH = 5381; | ||
448 | zEnd = &zIn[nLen]; | ||
449 | for(;;){ | ||
450 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
451 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
452 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
453 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
454 | } | ||
455 | return nH; | ||
456 | } | ||
457 | /* Default bucket size */ | ||
458 | #define MEM_HASH_BUCKET_SIZE 64 | ||
459 | /* Default fill factor */ | ||
460 | #define MEM_HASH_FILL_FACTOR 4 /* or 3 */ | ||
461 | /* | ||
462 | * Initialize the in-memory storage engine. | ||
463 | */ | ||
464 | static int MemHashInit(unqlite_kv_engine *pKvEngine,int iPageSize) | ||
465 | { | ||
466 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; | ||
467 | /* Note that this instance is already zeroed */ | ||
468 | /* Memory backend */ | ||
469 | SyMemBackendInitFromParent(&pEngine->sAlloc,unqliteExportMemBackend()); | ||
470 | #if defined(UNQLITE_ENABLE_THREADS) | ||
471 | /* Already protected by the upper layers */ | ||
472 | SyMemBackendDisbaleMutexing(&pEngine->sAlloc); | ||
473 | #endif | ||
474 | /* Default hash & comparison function */ | ||
475 | pEngine->xHash = MemHashFunc; | ||
476 | pEngine->xCmp = SyMemcmp; | ||
477 | /* Allocate a new bucket */ | ||
478 | pEngine->apBucket = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); | ||
479 | if( pEngine->apBucket == 0 ){ | ||
480 | SXUNUSED(iPageSize); /* cc warning */ | ||
481 | return UNQLITE_NOMEM; | ||
482 | } | ||
483 | /* Zero the bucket */ | ||
484 | SyZero(pEngine->apBucket,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); | ||
485 | pEngine->nRecord = 0; | ||
486 | pEngine->nBucket = MEM_HASH_BUCKET_SIZE; | ||
487 | return UNQLITE_OK; | ||
488 | } | ||
489 | /* | ||
490 | * Release the in-memory storage engine. | ||
491 | */ | ||
492 | static void MemHashRelease(unqlite_kv_engine *pKvEngine) | ||
493 | { | ||
494 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; | ||
495 | /* Release the private memory backend */ | ||
496 | SyMemBackendRelease(&pEngine->sAlloc); | ||
497 | } | ||
498 | /* | ||
499 | * Configure the in-memory storage engine. | ||
500 | */ | ||
501 | static int MemHashConfigure(unqlite_kv_engine *pKvEngine,int iOp,va_list ap) | ||
502 | { | ||
503 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; | ||
504 | int rc = UNQLITE_OK; | ||
505 | switch(iOp){ | ||
506 | case UNQLITE_KV_CONFIG_HASH_FUNC:{ | ||
507 | /* Use a default hash function */ | ||
508 | if( pEngine->nRecord > 0 ){ | ||
509 | rc = UNQLITE_LOCKED; | ||
510 | }else{ | ||
511 | ProcHash xHash = va_arg(ap,ProcHash); | ||
512 | if( xHash ){ | ||
513 | pEngine->xHash = xHash; | ||
514 | } | ||
515 | } | ||
516 | break; | ||
517 | } | ||
518 | case UNQLITE_KV_CONFIG_CMP_FUNC: { | ||
519 | /* Default comparison function */ | ||
520 | ProcCmp xCmp = va_arg(ap,ProcCmp); | ||
521 | if( xCmp ){ | ||
522 | pEngine->xCmp = xCmp; | ||
523 | } | ||
524 | break; | ||
525 | } | ||
526 | default: | ||
527 | /* Unknown configuration option */ | ||
528 | rc = UNQLITE_UNKNOWN; | ||
529 | } | ||
530 | return rc; | ||
531 | } | ||
532 | /* | ||
533 | * Replace method. | ||
534 | */ | ||
535 | static int MemHashReplace( | ||
536 | unqlite_kv_engine *pKv, | ||
537 | const void *pKey,int nKeyLen, | ||
538 | const void *pData,unqlite_int64 nDataLen | ||
539 | ) | ||
540 | { | ||
541 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; | ||
542 | mem_hash_record *pRecord; | ||
543 | if( nDataLen > SXU32_HIGH ){ | ||
544 | /* Database limit */ | ||
545 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); | ||
546 | return UNQLITE_LIMIT; | ||
547 | } | ||
548 | /* Fetch the record first */ | ||
549 | pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); | ||
550 | if( pRecord == 0 ){ | ||
551 | /* Allocate a new record */ | ||
552 | pRecord = MemHashNewRecord(pEngine, | ||
553 | pKey,nKeyLen, | ||
554 | pData,nDataLen, | ||
555 | pEngine->xHash(pKey,nKeyLen) | ||
556 | ); | ||
557 | if( pRecord == 0 ){ | ||
558 | return UNQLITE_NOMEM; | ||
559 | } | ||
560 | /* Link the entry */ | ||
561 | MemHashLinkRecord(pEngine,pRecord); | ||
562 | if( (pEngine->nRecord >= pEngine->nBucket * MEM_HASH_FILL_FACTOR) && pEngine->nRecord < 100000 ){ | ||
563 | /* Rehash the table */ | ||
564 | MemHashGrowTable(pEngine); | ||
565 | } | ||
566 | }else{ | ||
567 | sxu32 nData = (sxu32)nDataLen; | ||
568 | void *pNew; | ||
569 | /* Replace an existing record */ | ||
570 | if( nData == pRecord->nDataLen ){ | ||
571 | /* No need to free the old chunk */ | ||
572 | pNew = (void *)pRecord->pData; | ||
573 | }else{ | ||
574 | pNew = SyMemBackendAlloc(&pEngine->sAlloc,nData); | ||
575 | if( pNew == 0 ){ | ||
576 | return UNQLITE_NOMEM; | ||
577 | } | ||
578 | /* Release the old data */ | ||
579 | SyMemBackendFree(&pEngine->sAlloc,(void *)pRecord->pData); | ||
580 | } | ||
581 | /* Reflect the change */ | ||
582 | pRecord->nDataLen = nData; | ||
583 | SyMemcpy(pData,pNew,nData); | ||
584 | pRecord->pData = pNew; | ||
585 | } | ||
586 | return UNQLITE_OK; | ||
587 | } | ||
588 | /* | ||
589 | * Append method. | ||
590 | */ | ||
591 | static int MemHashAppend( | ||
592 | unqlite_kv_engine *pKv, | ||
593 | const void *pKey,int nKeyLen, | ||
594 | const void *pData,unqlite_int64 nDataLen | ||
595 | ) | ||
596 | { | ||
597 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; | ||
598 | mem_hash_record *pRecord; | ||
599 | if( nDataLen > SXU32_HIGH ){ | ||
600 | /* Database limit */ | ||
601 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); | ||
602 | return UNQLITE_LIMIT; | ||
603 | } | ||
604 | /* Fetch the record first */ | ||
605 | pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); | ||
606 | if( pRecord == 0 ){ | ||
607 | /* Allocate a new record */ | ||
608 | pRecord = MemHashNewRecord(pEngine, | ||
609 | pKey,nKeyLen, | ||
610 | pData,nDataLen, | ||
611 | pEngine->xHash(pKey,nKeyLen) | ||
612 | ); | ||
613 | if( pRecord == 0 ){ | ||
614 | return UNQLITE_NOMEM; | ||
615 | } | ||
616 | /* Link the entry */ | ||
617 | MemHashLinkRecord(pEngine,pRecord); | ||
618 | if( pEngine->nRecord * MEM_HASH_FILL_FACTOR >= pEngine->nBucket && pEngine->nRecord < 100000 ){ | ||
619 | /* Rehash the table */ | ||
620 | MemHashGrowTable(pEngine); | ||
621 | } | ||
622 | }else{ | ||
623 | unqlite_int64 nNew = pRecord->nDataLen + nDataLen; | ||
624 | void *pOld = (void *)pRecord->pData; | ||
625 | sxu32 nData; | ||
626 | char *zNew; | ||
627 | /* Append data to the existing record */ | ||
628 | if( nNew > SXU32_HIGH ){ | ||
629 | /* Overflow */ | ||
630 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow"); | ||
631 | return UNQLITE_LIMIT; | ||
632 | } | ||
633 | nData = (sxu32)nNew; | ||
634 | /* Allocate bigger chunk */ | ||
635 | zNew = (char *)SyMemBackendRealloc(&pEngine->sAlloc,pOld,nData); | ||
636 | if( zNew == 0 ){ | ||
637 | return UNQLITE_NOMEM; | ||
638 | } | ||
639 | /* Reflect the change */ | ||
640 | SyMemcpy(pData,&zNew[pRecord->nDataLen],(sxu32)nDataLen); | ||
641 | pRecord->pData = (const void *)zNew; | ||
642 | pRecord->nDataLen = nData; | ||
643 | } | ||
644 | return UNQLITE_OK; | ||
645 | } | ||
646 | /* | ||
647 | * Export the in-memory storage engine. | ||
648 | */ | ||
649 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void) | ||
650 | { | ||
651 | static const unqlite_kv_methods sMemStore = { | ||
652 | "mem", /* zName */ | ||
653 | sizeof(mem_hash_kv_engine), /* szKv */ | ||
654 | sizeof(mem_hash_cursor), /* szCursor */ | ||
655 | 1, /* iVersion */ | ||
656 | MemHashInit, /* xInit */ | ||
657 | MemHashRelease, /* xRelease */ | ||
658 | MemHashConfigure, /* xConfig */ | ||
659 | 0, /* xOpen */ | ||
660 | MemHashReplace, /* xReplace */ | ||
661 | MemHashAppend, /* xAppend */ | ||
662 | MemHashInitCursor, /* xCursorInit */ | ||
663 | MemHashCursorSeek, /* xSeek */ | ||
664 | MemHashCursorFirst, /* xFirst */ | ||
665 | MemHashCursorLast, /* xLast */ | ||
666 | MemHashCursorValid, /* xValid */ | ||
667 | MemHashCursorNext, /* xNext */ | ||
668 | MemHashCursorPrev, /* xPrev */ | ||
669 | MemHashCursorDelete, /* xDelete */ | ||
670 | MemHashCursorKeyLength, /* xKeyLength */ | ||
671 | MemHashCursorKey, /* xKey */ | ||
672 | MemHashCursorDataLength, /* xDataLength */ | ||
673 | MemHashCursorData, /* xData */ | ||
674 | MemHashCursorReset, /* xReset */ | ||
675 | 0 /* xRelease */ | ||
676 | }; | ||
677 | return &sMemStore; | ||
678 | } | ||