strex: added `detectUrl()`
[iv.d.git] / depot.d
blobb3afa2026daf2af634f243e4388dbbf73decf13a
1 /* ********************************************************************************************* *
2 * The basic API of QDBM Copyright (C) 2000-2007 Mikio Hirabayashi
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 * D translation and "d-fication" by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
17 * ********************************************************************************************* */
18 // key/value database based on tokyo kabinet
19 module iv.depot /*is aliced*/;
20 import iv.alice;
23 /// database errors
24 class DepotException : Exception {
25 this (Depot.Error ecode, string file=__FILE__, usize line=__LINE__, Throwable next=null) pure nothrow @safe @nogc {
26 super(errorMessage(ecode), file, line, next);
29 /** Get a message string corresponding to an error code.
31 * Params:
32 * ecode = an error code
34 * Returns:
35 * The message string of the error code.
37 static string errorMessage (Depot.Error ecode) @safe pure nothrow @nogc {
38 switch (ecode) with (Depot.Error) {
39 case NOERR: return "no error";
40 case FATAL: return "with fatal error";
41 case CLOSED: return "database not opened error";
42 case OPENED: return "already opened database error";
43 case MODE: return "invalid mode";
44 case BROKEN: return "broken database file";
45 case KEEP: return "existing record";
46 case NOITEM: return "no item found";
47 case ALLOC: return "memory allocation error";
48 case MAP: return "memory mapping error";
49 case OPEN: return "open error";
50 case CLOSE: return "close error";
51 case TRUNC: return "trunc error";
52 case SYNC: return "sync error";
53 case STAT: return "stat error";
54 case SEEK: return "seek error";
55 case READ: return "read error";
56 case WRITE: return "write error";
57 case LOCK: return "lock error";
58 case UNLINK: return "unlink error";
59 case MKDIR: return "mkdir error";
60 case RMDIR: return "rmdir error";
61 case MISC: return "miscellaneous error";
62 default: return "(invalid ecode)";
64 assert(0);
69 /// database
70 public final class Depot {
71 public import core.sys.posix.sys.types : time_t;
72 private import std.typecons : Flag, Yes, No;
74 enum QDBM_VERSION = "1.8.78"; /// library version
75 enum QDBM_LIBVER = 1414;
77 this (const(char)[] name, int omode=READER, int bnum=-1) { open(name, omode, bnum); }
78 ~this () { close(); }
80 private:
81 string m_name; // name of the database file; terminated with '\0', but '\0' is not a string part
82 bool m_wmode; // whether to be writable
83 ulong m_inode; // inode of the database file
84 time_t m_mtime; // last modified time of the database
85 int m_fd = -1; // file descriptor of the database file
86 long m_fsiz; // size of the database file
87 char* m_map; // pointer to the mapped memory
88 int m_msiz; // size of the mapped memory
89 int* m_buckets; // pointer to the bucket array
90 int m_bnum; // number of the bucket array
91 int m_rnum; // number of records
92 bool m_fatal; // whether a fatal error occured
93 int m_ioff; // offset of the iterator
94 int* m_fbpool; // free block pool
95 int m_fbpsiz; // size of the free block pool
96 int m_fbpinc; // incrementor of update of the free block pool
97 int m_alignment; // basic size of alignment (can be negative; why?)
99 private:
100 enum DP_FILEMODE = 384; // 0o600: permission of a creating file
101 version(BigEndian) {
102 enum DP_MAGIC = "[DEPOT]\n\f"; // magic on environments of big endian
103 } else {
104 enum DP_MAGIC = "[depot]\n\f"; // magic on environments of little endian
106 enum DP_DEFBNUM = 8191; // default bucket number
107 enum DP_FBPOOLSIZ = 16; // size of free block pool
108 enum DP_ENTBUFSIZ = 128; // size of the entity buffer
109 enum DP_STKBUFSIZ = 256; // size of the stack key buffer
110 enum DP_WRTBUFSIZ = 8192; // size of the writing buffer
111 enum DP_FSBLKSIZ = 4096; // size of a block of the file system
112 enum DP_OPTBLOAD = 0.25; // ratio of bucket loading at optimization
113 enum DP_OPTRUNIT = 256; // number of records in a process of optimization
114 enum DP_NUMBUFSIZ = 32; // size of a buffer for a number
115 enum DP_IOBUFSIZ = 8192; // size of an I/O buffer
116 enum DP_TMPFSUF = ".dptmp"; // suffix of a temporary file
119 // enumeration for the flag of a record
120 enum {
121 DP_RECFDEL = 0x01, // deleted
122 DP_RECFREUSE = 0x02, // reusable
125 static align(1) struct QDBMHeader {
126 align(1):
127 char[12] signature; // DP_MAGICNUMB or DP_MAGICNUML, padded with '\0'
128 char[4] versionstr; // string, padded with '\0'
129 int flags;
130 uint unused0;
131 int filesize;
132 uint unused1;
133 int nbuckets; // number of buckets
134 uint unused2;
135 int nrecords; // number of records
136 uint unused3;
138 static assert(QDBMHeader.sizeof == 48);
139 static assert(QDBMHeader.signature.offsetof == 0);
140 static assert(QDBMHeader.versionstr.offsetof == 12);
141 static assert(QDBMHeader.flags.offsetof == 16);
142 static assert(QDBMHeader.filesize.offsetof == 24);
143 static assert(QDBMHeader.nbuckets.offsetof == 32);
144 static assert(QDBMHeader.nrecords.offsetof == 40);
146 static align(1) struct RecordHeader {
147 align(1):
148 int flags; // flags
149 int hash2; // value of the second hash function
150 int ksiz; // the size of the key
151 int vsiz; // the size of the value
152 int psiz; // the size of the padding bytes
153 int left; // the offset of the left child
154 int right; // the offset of the right child
156 /* Get the size of a record in a database file.
158 * Returns:
159 * The return value is the size of a record in a database file
161 @property int recsize () const @safe pure nothrow @nogc {
162 return cast(int)(RecordHeader.sizeof+ksiz+vsiz+psiz);
165 static assert(RecordHeader.sizeof == 7*int.sizeof);
166 static assert(RecordHeader.flags.offsetof == 0*int.sizeof);
167 static assert(RecordHeader.hash2.offsetof == 1*int.sizeof);
168 static assert(RecordHeader.ksiz.offsetof == 2*int.sizeof);
169 static assert(RecordHeader.vsiz.offsetof == 3*int.sizeof);
170 static assert(RecordHeader.psiz.offsetof == 4*int.sizeof);
171 static assert(RecordHeader.left.offsetof == 5*int.sizeof);
172 static assert(RecordHeader.right.offsetof == 6*int.sizeof);
174 public:
175 /// enumeration for error codes
176 enum Error {
177 NOERR, /// no error
178 FATAL, /// with fatal error
179 CLOSED, /// trying to operate on closed db
180 OPENED, /// trying to opend an already opened db
181 MODE, /// invalid mode
182 BROKEN, /// broken database file
183 KEEP, /// existing record
184 NOITEM, /// no item found
185 ALLOC, /// memory allocation error
186 MAP, /// memory mapping error
187 OPEN, /// open error
188 CLOSE, /// close error
189 TRUNC, /// trunc error
190 SYNC, /// sync error
191 STAT, /// stat error
192 SEEK, /// seek error
193 READ, /// read error
194 WRITE, /// write error
195 LOCK, /// lock error
196 UNLINK, /// unlink error
197 MKDIR, /// mkdir error
198 RMDIR, /// rmdir error
199 MISC, /// miscellaneous error
202 /// enumeration for open modes
203 enum {
204 READER = 1<<0, /// open as a reader
205 WRITER = 1<<1, /// open as a writer
206 CREAT = 1<<2, /// a writer creating
207 TRUNC = 1<<3, /// a writer truncating
208 NOLCK = 1<<4, /// open without locking
209 LCKNB = 1<<5, /// lock without blocking
210 SPARSE = 1<<6, /// create as a sparse file
213 /// enumeration for write modes
214 enum WMode {
215 OVER, /// overwrite an existing value
216 KEEP, /// keep an existing value
217 CAT, /// concatenate values
220 final:
221 public:
222 @property bool opened () const @safe pure nothrow @nogc { return (m_fd >= 0); }
224 void checkOpened (string file=__FILE__, usize line=__LINE__) {
225 if (m_fatal) raise(Error.FATAL, file, line);
226 if (!opened) raise(Error.CLOSED, file, line);
229 void checkWriting (string file=__FILE__, usize line=__LINE__) {
230 checkOpened(file, line);
231 if (!m_wmode) raise(Error.MODE, file, line);
234 /** Free `malloc()`ed pointer and set variable to `null`.
236 * Params:
237 * ptr = the pointer to variable holding a pointer
239 static freeptr(T) (ref T* ptr) {
240 import core.stdc.stdlib : free;
241 if (ptr !is null) {
242 free(cast(void*)ptr);
243 ptr = null;
247 /** Get a database handle.
249 * While connecting as a writer, an exclusive lock is invoked to the database file.
250 * While connecting as a reader, a shared lock is invoked to the database file. The thread
251 * blocks until the lock is achieved. If `NOLCK` is used, the application is responsible
252 * for exclusion control.
254 * Params:
255 * name = the name of a database file
256 * omode = specifies the connection mode: `WRITER` as a writer, `READER` as a reader.
257 * If the mode is `WRITER`, the following may be added by bitwise or:
258 * `CREAT`, which means it creates a new database if not exist,
259 * `TRUNC`, which means it creates a new database regardless if one exists.
260 * Both of `READER` and `WRITER` can be added to by bitwise or:
261 * `NOLCK`, which means it opens a database file without file locking, or
262 * `LCKNB`, which means locking is performed without blocking.
263 * `CREAT` can be added to by bitwise or:
264 * `SPARSE`, which means it creates a database file as a sparse file.
265 * bnum = the number of elements of the bucket array.
266 * If it is not more than 0, the default value is specified. The size of a bucket array is
267 * determined on creating, and can not be changed except for by optimization of the database.
268 * Suggested size of a bucket array is about from 0.5 to 4 times of the number of all records
269 * to store.
270 * errcode = the error code (can be `null`)
272 * Throws:
273 * DepotException on various errors
275 void open (const(char)[] name, int omode=READER, int bnum=-1) {
276 import core.sys.posix.fcntl : open, O_CREAT, O_RDONLY, O_RDWR;
277 import core.sys.posix.sys.mman : mmap, munmap, MAP_FAILED, PROT_READ, PROT_WRITE, MAP_SHARED;
278 import core.sys.posix.sys.stat : fstat, lstat, S_ISREG, stat_t;
279 import core.sys.posix.unistd : close, ftruncate;
280 QDBMHeader hbuf;
281 char* map;
282 int mode, fd;
283 usize msiz;
284 ulong inode;
285 long fsiz;
286 int* fbpool;
287 stat_t sbuf;
288 time_t mtime;
289 if (opened) raise(Error.OPENED);
290 assert(name.length);
291 char[] namez; // unique
292 // add '\0' after string
294 usize len = 0;
295 while (len < name.length && name[len]) ++len;
296 namez = new char[](len+1);
297 namez[0..$-1] = name[0..len];
298 namez[$-1] = 0;
300 mode = O_RDONLY;
301 if (omode&WRITER) {
302 mode = O_RDWR;
303 if (omode&CREAT) mode |= O_CREAT;
305 if ((fd = open(namez.ptr, mode, DP_FILEMODE)) == -1) raise(Error.OPEN);
306 scope(failure) close(fd);
307 if ((omode&NOLCK) == 0) fdlock(fd, omode&WRITER, omode&LCKNB);
308 if ((omode&WRITER) && (omode&TRUNC)) {
309 if (ftruncate(fd, 0) == -1) raise(Error.TRUNC);
311 if (fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode) || (sbuf.st_ino == 0 && lstat(namez.ptr, &sbuf) == -1)) raise(Error.STAT);
312 inode = sbuf.st_ino;
313 mtime = sbuf.st_mtime;
314 fsiz = sbuf.st_size;
315 if ((omode&WRITER) && fsiz == 0) {
316 hbuf.signature[] = 0;
317 hbuf.versionstr[] = 0;
318 hbuf.signature[0..DP_MAGIC.length] = DP_MAGIC[];
320 import core.stdc.stdio : snprintf;
321 snprintf(hbuf.versionstr.ptr, hbuf.versionstr.length, "%d", QDBM_LIBVER/100);
323 bnum = (bnum < 1 ? DP_DEFBNUM : bnum);
324 bnum = primenum(bnum);
325 hbuf.nbuckets = bnum;
326 hbuf.nrecords = 0;
327 fsiz = hbuf.sizeof+bnum*int.sizeof;
328 hbuf.filesize = cast(int)fsiz;
329 fdseekwrite(fd, 0, (&hbuf)[0..1]);
330 if (omode&SPARSE) {
331 ubyte c = 0;
332 fdseekwrite(fd, fsiz-1, (&c)[0..1]);
333 } else {
334 ubyte[DP_IOBUFSIZ] ebuf = 0; // totally empty buffer initialized with 0 %-)
335 usize pos = hbuf.sizeof;
336 while (pos < fsiz) {
337 usize left = cast(usize)fsiz-pos;
338 usize wr = (left > ebuf.length ? ebuf.length : left);
339 fdseekwrite(fd, pos, ebuf[0..wr]);
340 pos += wr;
344 try {
345 fdseekread(fd, 0, (&hbuf)[0..1]);
346 } catch (Exception) {
347 raise(Error.BROKEN);
349 //k8: the original code checks header only if ((omode&NOLCK) == 0); why?
350 if (hbuf.signature[0..DP_MAGIC.length] != DP_MAGIC) raise(Error.BROKEN);
351 if (hbuf.filesize != fsiz) raise(Error.BROKEN);
352 bnum = hbuf.nbuckets;
353 if (bnum < 1 || hbuf.nrecords < 0 || fsiz < QDBMHeader.sizeof+bnum*int.sizeof) raise(Error.BROKEN);
354 msiz = QDBMHeader.sizeof+bnum*int.sizeof;
355 map = cast(char*)mmap(null, msiz, PROT_READ|(mode&WRITER ? PROT_WRITE : 0), MAP_SHARED, fd, 0);
356 if (map == MAP_FAILED) raise(Error.MAP);
357 scope(failure) munmap(map, msiz);
358 fbpool = null;
360 import core.stdc.stdlib : malloc;
361 fbpool = cast(int*)malloc(DP_FBPOOLSIZ*2*int.sizeof);
363 if (fbpool is null) raise(Error.ALLOC);
365 import std.exception : assumeUnique;
366 m_name = namez[0..$-1].assumeUnique;
368 m_wmode = (mode&WRITER) != 0;
369 m_inode = inode;
370 m_mtime = mtime;
371 m_fd = fd;
372 m_fsiz = fsiz;
373 m_map = map;
374 m_msiz = cast(int)msiz;
375 m_buckets = cast(int*)(map+QDBMHeader.sizeof);
376 m_bnum = bnum;
377 m_rnum = hbuf.nrecords;
378 m_fatal = false;
379 m_ioff = 0;
380 m_fbpool = fbpool;
381 m_fbpool[0..DP_FBPOOLSIZ*2] = -1;
382 m_fbpsiz = DP_FBPOOLSIZ*2;
383 m_fbpinc = 0;
384 m_alignment = 0;
387 /** Close a database handle.
389 * Returns:
390 * If successful, the return value is true, else, it is false.
391 * Because the region of a closed handle is released, it becomes impossible to use the handle.
392 * Updating a database is assured to be written when the handle is closed. If a writer opens
393 * a database but does not close it appropriately, the database will be broken.
395 * Throws:
396 * DepotException on various errors
398 void close () {
399 import core.sys.posix.sys.mman : munmap, MAP_FAILED;
400 import core.sys.posix.unistd : close;
401 if (!opened) return;
402 bool fatal = m_fatal;
403 Error err = Error.NOERR;
404 if (m_wmode) updateHeader();
405 if (m_map != null) {
406 if (munmap(m_map, m_msiz) == -1) err = Error.MAP;
408 m_map = null;
409 if (close(m_fd) == -1) err = Error.CLOSE;
410 freeptr(m_fbpool);
411 m_name = null;
412 m_fd = -1;
413 m_wmode = false;
414 if (fatal) err = Error.FATAL;
415 if (err != Error.NOERR) raise(err);
418 /** Store a record.
420 * Params:
421 * kbuf = the pointer to the region of a key
422 * vbuf = the pointer to the region of a value
423 * dmode = behavior when the key overlaps, by the following values:
424 * `WMode.OVER`, which means the specified value overwrites the existing one,
425 * `WMode.KEEP`, which means the existing value is kept,
426 * `WMode.CAT`, which means the specified value is concatenated at the end of the existing value.
428 * Throws:
429 * DepotException on various errors
431 void put (const(void)[] kbuf, const(void)[] vbuf, WMode dmode=WMode.OVER) {
432 RecordHeader head, next;
433 int hash, bi, off, entoff, newoff, fdel, mroff, mrsiz, mi, min;
434 usize rsiz, nsiz;
435 bool ee;
436 char[DP_ENTBUFSIZ] ebuf;
437 char* tval, swap;
438 checkWriting();
439 newoff = -1;
440 hash = secondhash(kbuf);
441 if (recsearch(kbuf, hash, &bi, &off, &entoff, head, ebuf[], &ee, Yes.delhit)) {
442 // record found
443 fdel = head.flags&DP_RECFDEL;
444 if (dmode == WMode.KEEP && !fdel) raise(Error.KEEP);
445 if (fdel) {
446 head.psiz += head.vsiz;
447 head.vsiz = 0;
449 rsiz = head.recsize;
450 nsiz = RecordHeader.sizeof+kbuf.length+vbuf.length;
451 if (dmode == WMode.CAT) nsiz += head.vsiz;
452 if (off+rsiz >= m_fsiz) {
453 if (rsiz < nsiz) {
454 head.psiz += nsiz-rsiz;
455 rsiz = nsiz;
456 m_fsiz = off+rsiz;
458 } else {
459 while (nsiz > rsiz && off+rsiz < m_fsiz) {
460 rechead(off+rsiz, next, null, null);
461 if ((next.flags&DP_RECFREUSE) == 0) break;
462 head.psiz += next.recsize;
463 rsiz += next.recsize;
465 for (uint i = 0; i < m_fbpsiz; i += 2) {
466 if (m_fbpool[i] >= off && m_fbpool[i] < off+rsiz) {
467 m_fbpool[i] = m_fbpool[i+1] = -1;
471 if (nsiz <= rsiz) {
472 recover(off, head, vbuf, (dmode == WMode.CAT ? Yes.catmode : No.catmode));
473 } else {
474 tval = null;
475 scope(failure) { m_fatal = true; freeptr(tval); }
476 if (dmode == WMode.CAT) {
477 import core.stdc.string : memcpy;
478 if (ee && RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
479 import core.stdc.stdlib : malloc;
480 tval = cast(char*)malloc(head.vsiz+vbuf.length+1);
481 if (tval is null) { m_fatal = true; raise(Error.ALLOC); }
482 memcpy(tval, ebuf.ptr+(RecordHeader.sizeof+head.ksiz), head.vsiz);
483 } else {
484 import core.stdc.stdlib : realloc;
485 tval = recval(off, head);
486 swap = cast(char*)realloc(tval, head.vsiz+vbuf.length+1);
487 if (swap is null) raise(Error.ALLOC);
488 tval = swap;
490 memcpy(tval+head.vsiz, vbuf.ptr, vbuf.length);
491 immutable newsize = head.vsiz+vbuf.length;
492 vbuf = tval[0..newsize];
494 mi = -1;
495 min = -1;
496 for (uint i = 0; i < m_fbpsiz; i += 2) {
497 if (m_fbpool[i+1] < nsiz) continue;
498 if (mi == -1 || m_fbpool[i+1] < min) {
499 mi = i;
500 min = m_fbpool[i+1];
503 if (mi >= 0) {
504 mroff = m_fbpool[mi];
505 mrsiz = m_fbpool[mi+1];
506 m_fbpool[mi] = -1;
507 m_fbpool[mi+1] = -1;
508 } else {
509 mroff = -1;
510 mrsiz = -1;
512 recdelete(off, head, Yes.reusable);
513 if (mroff > 0 && nsiz <= mrsiz) {
514 recrewrite(mroff, mrsiz, kbuf, vbuf, hash, head.left, head.right);
515 newoff = mroff;
516 } else {
517 newoff = recappend(kbuf, vbuf, hash, head.left, head.right);
519 freeptr(tval);
521 if (fdel) ++m_rnum;
522 } else {
523 // no such record
524 scope(failure) m_fatal = true;
525 newoff = recappend(kbuf, vbuf, hash, 0, 0);
526 ++m_rnum;
528 if (newoff > 0) {
529 if (entoff > 0) {
530 fdseekwritenum(m_fd, entoff, newoff);
531 } else {
532 m_buckets[bi] = newoff;
537 /** Delete a record.
539 * Params:
540 * kbuf = the pointer to the region of a key
542 * Returns:
543 * If successful, the return value is true, else, it is false.
544 * False is returned when no record corresponds to the specified key.
546 * Throws:
547 * DepotException on various errors
549 bool del (const(void)[] kbuf) {
550 RecordHeader head;
551 int hash, bi, off, entoff;
552 bool ee;
553 char[DP_ENTBUFSIZ] ebuf;
554 checkWriting();
555 hash = secondhash(kbuf);
556 if (!recsearch(kbuf, hash, &bi, &off, &entoff, head, ebuf[], &ee)) return false; //raise(Error.NOITEM);
557 recdelete(off, head, No.reusable);
558 --m_rnum;
559 return true;
562 /** Retrieve a record.
564 * Params:
565 * kbuf = the pointer to the region of a key
566 * start = the offset address of the beginning of the region of the value to be read
567 * max = specifies the max size to be read; if it is `uint.max`, the size to read is unlimited
568 * sp = the pointer to a variable to which the size of the region of the return
569 * value is assigned; if it is `null`, it is not used
571 * Returns:
572 * If successful, the return value is the pointer to the region of the value of the
573 * corresponding record, else, it is `null`. `null` is returned when no record corresponds to
574 * the specified key or the size of the value of the corresponding record is less than `start`.
575 * Because an additional zero code is appended at the end of the region of the return value,
576 * the return value can be treated as a character string. Because the region of the return
577 * value is allocated with the `malloc` call, it should be released with the `freeptr` call if it
578 * is no longer in use.
580 * Throws:
581 * DepotException on various errors
583 char* get (const(void)[] kbuf, uint start=0, uint max=uint.max, usize* sp=null) {
584 RecordHeader head;
585 int hash, bi, off, entoff;
586 bool ee;
587 usize vsiz;
588 char[DP_ENTBUFSIZ] ebuf;
589 char* vbuf;
590 if (sp !is null) *sp = 0;
591 checkOpened();
592 hash = secondhash(kbuf);
593 if (!recsearch(kbuf, hash, &bi, &off, &entoff, head, ebuf[], &ee)) return null; //raise(Error.NOITEM);
594 if (start > head.vsiz) return null; //raise(Error.NOITEM);
595 if (start == head.vsiz) {
596 import core.stdc.stdlib : malloc;
597 vbuf = cast(char*)malloc(1);
598 vbuf[0] = 0;
599 return vbuf;
601 scope(failure) m_fatal = true; // any failure beyond this point is fatal
602 if (ee && RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
603 import core.stdc.stdlib : malloc;
604 import core.stdc.string : memcpy;
605 head.vsiz -= start;
606 if (max == uint.max) {
607 vsiz = head.vsiz;
608 } else {
609 vsiz = (max < head.vsiz ? max : head.vsiz);
611 vbuf = cast(char*)malloc(vsiz+1);
612 if (vbuf is null) raise(Error.ALLOC);
613 memcpy(vbuf, ebuf.ptr+(RecordHeader.sizeof+head.ksiz+start), vsiz);
614 vbuf[vsiz] = '\0';
615 } else {
616 vbuf = recval(off, head, start, max);
618 if (sp !is null) {
619 if (max == uint.max) {
620 *sp = head.vsiz;
621 } else {
622 *sp = (max < head.vsiz ? max : head.vsiz);
625 return vbuf;
628 /** Retrieve a record and write the value into a buffer.
630 * Params:
631 * vbuf = the pointer to a buffer into which the value of the corresponding record is written
632 * kbuf = the pointer to the region of a key
633 * start = the offset address of the beginning of the region of the value to be read
635 * Returns:
636 * If successful, the return value is the read data (slice of vbuf), else, it is `null`.
637 * `null` returned when no record corresponds to the specified key or the size of the value
638 * of the corresponding record is less than `start`.
639 * Note that no additional zero code is appended at the end of the region of the writing buffer.
641 * Throws:
642 * DepotException on various errors
644 char[] getwb (void[] vbuf, const(void)[] kbuf, uint start=0) {
645 RecordHeader head;
646 int hash, bi, off, entoff;
647 bool ee;
648 usize vsiz;
649 char[DP_ENTBUFSIZ] ebuf;
650 checkOpened();
651 hash = secondhash(kbuf);
652 if (!recsearch(kbuf, hash, &bi, &off, &entoff, head, ebuf[], &ee)) return null; //raise(Error.NOITEM);
653 if (start > head.vsiz) return null; //raise(Error.NOITEM);
654 scope(failure) m_fatal = true; // any failure beyond this point is fatal
655 if (ee && RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
656 import core.stdc.string : memcpy;
657 head.vsiz -= start;
658 vsiz = (vbuf.length < head.vsiz ? vbuf.length : head.vsiz);
659 memcpy(vbuf.ptr, ebuf.ptr+(RecordHeader.sizeof+head.ksiz+start), vsiz);
660 } else {
661 vsiz = recvalwb(vbuf, off, head, start);
663 return cast(char[])(vbuf[0..vsiz]);
666 /** Get the size of the value of a record.
668 * Params:
669 * kbuf = the pointer to the region of a key
671 * Returns:
672 * If successful, the return value is the size of the value of the corresponding record, else, it is -1.
673 * Because this function does not read the entity of a record, it is faster than `get`.
675 * Throws:
676 * DepotException on various errors
678 usize vsize (const(void)[] kbuf) {
679 RecordHeader head;
680 int hash, bi, off, entoff;
681 bool ee;
682 char[DP_ENTBUFSIZ] ebuf;
683 checkOpened();
684 hash = secondhash(kbuf);
685 if (!recsearch(kbuf, hash, &bi, &off, &entoff, head, ebuf[], &ee)) return -1; //raise(Error.NOITEM);
686 return head.vsiz;
689 /** Initialize the iterator of a database handle.
691 * Returns:
692 * If successful, the return value is true, else, it is false.
693 * The iterator is used in order to access the key of every record stored in a database.
695 * Throws:
696 * DepotException on various errors
698 void itInit () {
699 checkOpened();
700 m_ioff = 0;
703 /** Get the next key of the iterator.
705 * Params:
706 * sp = the pointer to a variable to which the size of the region of the return value is assigned.
707 * If it is `null`, it is not used.
709 * Returns:
710 * If successful, the return value is the pointer to the region of the next key, else, it is
711 * `null`. `null` is returned when no record is to be get out of the iterator.
712 * Because an additional zero code is appended at the end of the region of the return value,
713 * the return value can be treated as a character string. Because the region of the return
714 * value is allocated with the `malloc` call, it should be released with the `freeptr` call if
715 * it is no longer in use. It is possible to access every record by iteration of calling
716 * this function. However, it is not assured if updating the database is occurred while the
717 * iteration. Besides, the order of this traversal access method is arbitrary, so it is not
718 * assured that the order of storing matches the one of the traversal access.
720 * Throws:
721 * DepotException on various errors
723 char* itNext (usize* sp=null) {
724 RecordHeader head;
725 usize off;
726 bool ee;
727 char[DP_ENTBUFSIZ] ebuf;
728 char* kbuf;
729 if (sp !is null) *sp = 0;
730 checkOpened();
731 off = QDBMHeader.sizeof+m_bnum*int.sizeof;
732 off = (off > m_ioff ? off : m_ioff);
733 scope(failure) m_fatal = true; // any failure is fatal here
734 while (off < m_fsiz) {
735 rechead(off, head, ebuf[], &ee);
736 if (head.flags&DP_RECFDEL) {
737 off += head.recsize;
738 } else {
739 if (ee && RecordHeader.sizeof+head.ksiz <= DP_ENTBUFSIZ) {
740 import core.stdc.stdlib : malloc;
741 import core.stdc.string : memcpy;
742 kbuf = cast(char*)malloc(head.ksiz+1);
743 if (kbuf is null) raise(Error.ALLOC);
744 memcpy(kbuf, ebuf.ptr+RecordHeader.sizeof, head.ksiz);
745 kbuf[head.ksiz] = '\0';
746 } else {
747 kbuf = reckey(off, head);
749 m_ioff = cast(int)(off+head.recsize);
750 if (sp !is null) *sp = head.ksiz;
751 return kbuf;
754 //raise(Error.NOITEM);
755 return null;
758 /** Set alignment of a database handle.
760 * If alignment is set to a database, the efficiency of overwriting values is improved.
761 * The size of alignment is suggested to be average size of the values of the records to be
762 * stored. If alignment is positive, padding whose size is multiple number of the alignment
763 * is placed. If alignment is negative, as `vsiz` is the size of a value, the size of padding
764 * is calculated with `(vsiz/pow(2, abs(alignment)-1))'. Because alignment setting is not
765 * saved in a database, you should specify alignment every opening a database.
767 * Params:
768 * alignment = the size of alignment
770 * Throws:
771 * DepotException on various errors
773 @property void alignment (int alignment) {
774 checkWriting();
775 m_alignment = alignment;
778 /** Get alignment of a database handle.
780 * Returns:
781 * The size of alignment
783 * Throws:
784 * DepotException on various errors
786 @property int alignment () {
787 checkOpened();
788 return m_alignment;
791 /** Set the size of the free block pool of a database handle.
793 * The default size of the free block pool is 16. If the size is greater, the space efficiency
794 * of overwriting values is improved with the time efficiency sacrificed.
796 * Params:
797 * size = the size of the free block pool of a database
799 * Throws:
800 * DepotException on various errors
802 @property void freeBlockPoolSize (uint size) {
803 import core.stdc.stdlib : realloc;
804 int* fbpool;
805 checkWriting();
806 size *= 2;
807 fbpool = cast(int*)realloc(m_fbpool, size*int.sizeof+1);
808 if (fbpool is null) raise(Error.ALLOC);
809 fbpool[0..size] = -1;
810 m_fbpool = fbpool;
811 m_fbpsiz = size;
814 /** Get the size of the free block pool of a database handle.
816 * Returns:
817 * The size of the free block pool of a database
819 * Throws:
820 * DepotException on various errors
822 @property uint freeBlockPoolSize () {
823 checkOpened();
824 return m_fbpsiz/2;
827 /** Synchronize updating contents with the file and the device.
829 * This function is useful when another process uses the connected database file.
831 * Throws:
832 * DepotException on various errors
834 void sync () {
835 import core.sys.posix.sys.mman : msync, MS_SYNC;
836 import core.sys.posix.unistd : fsync;
837 checkWriting();
838 updateHeader();
839 if (msync(m_map, m_msiz, MS_SYNC) == -1) {
840 m_fatal = true;
841 raise(Error.MAP);
843 if (fsync(m_fd) == -1) {
844 m_fatal = true;
845 raise(Error.SYNC);
849 /** Optimize a database.
851 * In an alternating succession of deleting and storing with overwrite or concatenate,
852 * dispensable regions accumulate. This function is useful to do away with them.
854 * Params:
855 * bnum = the number of the elements of the bucket array. If it is not more than 0,
856 * the default value is specified
858 * Throws:
859 * DepotException on various errors
861 void optimize (int bnum=-1) {
862 import core.sys.posix.sys.mman : mmap, munmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE;
863 import core.sys.posix.unistd : ftruncate, unlink;
864 Depot tdepot;
865 RecordHeader head;
866 usize off;
867 int unum;
868 bool ee;
869 int[DP_OPTRUNIT] ksizs, vsizs;
870 char[DP_ENTBUFSIZ] ebuf;
871 char*[DP_OPTRUNIT] kbufs, vbufs;
872 checkWriting();
873 if (bnum < 0) {
874 bnum = cast(int)(m_rnum*(1.0/DP_OPTBLOAD))+1;
875 if (bnum < DP_DEFBNUM/2) bnum = DP_DEFBNUM/2;
877 tdepot = new Depot(m_name~DP_TMPFSUF, WRITER|CREAT|TRUNC, bnum);
878 scope(failure) {
879 import std.exception : collectException;
880 m_fatal = true;
881 unlink(tdepot.m_name.ptr);
882 collectException(tdepot.close());
884 scope(exit) delete tdepot;
885 tdepot.flags = flags;
886 tdepot.m_alignment = m_alignment;
887 off = QDBMHeader.sizeof+m_bnum*int.sizeof;
888 unum = 0;
889 while (off < m_fsiz) {
890 rechead(off, head, ebuf[], &ee);
891 if ((head.flags&DP_RECFDEL) == 0) {
892 if (ee && RecordHeader.sizeof+head.ksiz <= DP_ENTBUFSIZ) {
893 import core.stdc.stdlib : malloc;
894 import core.stdc.string : memcpy;
895 if ((kbufs[unum] = cast(char*)malloc(head.ksiz+1)) is null) raise(Error.ALLOC);
896 memcpy(kbufs[unum], ebuf.ptr+RecordHeader.sizeof, head.ksiz);
897 if (RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
898 if ((vbufs[unum] = cast(char*)malloc(head.vsiz+1)) is null) raise(Error.ALLOC);
899 memcpy(vbufs[unum], ebuf.ptr+(RecordHeader.sizeof+head.ksiz), head.vsiz);
900 } else {
901 vbufs[unum] = recval(off, head);
903 } else {
904 kbufs[unum] = reckey(off, head);
905 vbufs[unum] = recval(off, head);
907 ksizs[unum] = head.ksiz;
908 vsizs[unum] = head.vsiz;
909 ++unum;
910 if (unum >= DP_OPTRUNIT) {
911 for (uint i = 0; i < unum; ++i) {
912 assert(kbufs[i] !is null && vbufs[i] !is null);
913 tdepot.put(kbufs[i][0..ksizs[i]], vbufs[i][0..vsizs[i]], WMode.KEEP);
914 freeptr(kbufs[i]);
915 freeptr(vbufs[i]);
917 unum = 0;
920 off += head.recsize;
922 for (uint i = 0; i < unum; ++i) {
923 assert(kbufs[i] !is null && vbufs[i] !is null);
924 tdepot.put(kbufs[i][0..ksizs[i]], vbufs[i][0..vsizs[i]], WMode.KEEP);
925 freeptr(kbufs[i]);
926 freeptr(vbufs[i]);
928 tdepot.sync();
929 if (munmap(m_map, m_msiz) == -1) raise(Error.MAP);
930 m_map = cast(char*)MAP_FAILED;
931 if (ftruncate(m_fd, 0) == -1) raise(Error.TRUNC);
932 fcopy(m_fd, 0, tdepot.m_fd, 0);
933 m_fsiz = tdepot.m_fsiz;
934 m_bnum = tdepot.m_bnum;
935 m_ioff = 0;
936 for (uint i = 0; i < m_fbpsiz; i += 2) {
937 m_fbpool[i] = m_fbpool[i+1] = -1;
939 m_msiz = tdepot.m_msiz;
940 m_map = cast(char*)mmap(null, m_msiz, PROT_READ|PROT_WRITE, MAP_SHARED, m_fd, 0);
941 if (m_map == MAP_FAILED) raise(Error.MAP);
942 m_buckets = cast(int*)(m_map+QDBMHeader.sizeof);
943 string tempname = tdepot.m_name; // with trailing zero
944 tdepot.close();
945 if (unlink(tempname.ptr) == -1) raise(Error.UNLINK);
948 /** Get the name of a database.
950 * Returns:
951 * If successful, the return value is the pointer to the region of the name of the database,
952 * else, it is `null`.
953 * Because the region of the return value is allocated with the `malloc` call, it should be
954 * released with the `freeptr` call if it is no longer in use.
956 * Throws:
957 * DepotException on various errors
959 @property string name () const @safe pure nothrow @nogc {
960 return m_name;
963 /** Get the size of a database file.
965 * Returns:
966 * If successful, the return value is the size of the database file, else, it is -1.
968 * Throws:
969 * DepotException on various errors
971 @property long fileSize () {
972 checkOpened();
973 return m_fsiz;
977 /** Get the number of the elements of the bucket array.
979 * Returns:
980 * If successful, the return value is the number of the elements of the bucket array, else, it is -1.
982 * Throws:
983 * DepotException on various errors
985 @property int bucketCount () {
986 checkOpened();
987 return m_bnum;
990 /** Get the number of the used elements of the bucket array.
992 * This function is inefficient because it accesses all elements of the bucket array.
994 * Returns:
995 * If successful, the return value is the number of the used elements of the bucket array, else, it is -1.
997 * Throws:
998 * DepotException on various errors
1000 int bucketUsed () {
1001 checkOpened();
1002 int hits = 0;
1003 for (uint i = 0; i < m_bnum; ++i) if (m_buckets[i]) ++hits;
1004 return hits;
1007 /** Get the number of the records stored in a database.
1009 * Returns:
1010 * If successful, the return value is the number of the records stored in the database, else, it is -1.
1012 * Throws:
1013 * DepotException on various errors
1015 @property int recordCount () {
1016 checkOpened();
1017 return m_rnum;
1020 /** Check whether a database handle is a writer or not.
1022 * Returns:
1023 * The return value is true if the handle is a writer, false if not.
1025 @property bool writable () const @safe pure nothrow @nogc {
1026 return (opened && m_wmode);
1029 /** Check whether a database has a fatal error or not.
1031 * Returns:
1032 * The return value is true if the database has a fatal error, false if not.
1034 @property bool fatalError () const @safe pure nothrow @nogc {
1035 return m_fatal;
1038 /** Get the inode number of a database file.
1040 * Returns:
1041 * The return value is the inode number of the database file.
1043 * Throws:
1044 * DepotException on various errors
1046 @property long inode () {
1047 checkOpened();
1048 return m_inode;
1051 /** Get the last modified time of a database.
1053 * Returns:
1054 * The return value is the last modified time of the database.
1056 * Throws:
1057 * DepotException on various errors
1059 @property time_t mtime () {
1060 checkOpened();
1061 return m_mtime;
1064 /** Get the file descriptor of a database file.
1066 * Returns:
1067 * The return value is the file descriptor of the database file.
1068 * Handling the file descriptor of a database file directly is not suggested.
1070 * Throws:
1071 * DepotException on various errors
1073 @property int fdesc () {
1074 checkOpened();
1075 return m_fd;
1078 /** Remove a database file.
1080 * Params:
1081 * name = the name of a database file
1083 * Throws:
1084 * DepotException on various errors
1086 static void remove (const(char)[] name) {
1087 import core.stdc.errno : errno, ENOENT;
1088 import core.sys.posix.sys.stat : lstat, stat_t;
1089 import core.sys.posix.unistd : unlink;
1090 import std.string : toStringz;
1091 stat_t sbuf;
1092 assert(name.length);
1093 auto namez = name.toStringz;
1094 if (lstat(namez, &sbuf) == -1) {
1095 if (errno != ENOENT) straise(Error.STAT);
1096 // no file
1097 return;
1099 //k8:??? try to open the file to check if it's not locked or something
1100 auto depot = new Depot(name, WRITER|TRUNC);
1101 delete depot;
1102 // remove file
1103 if (unlink(namez) == -1) {
1104 if (errno != ENOENT) straise(Error.UNLINK);
1105 // no file
1109 /** Repair a broken database file.
1111 * There is no guarantee that all records in a repaired database file correspond to the original
1112 * or expected state.
1114 * Params:
1115 * name = the name of a database file
1117 * Returns:
1118 * true if ok, false is there were some errors
1120 * Throws:
1121 * DepotException on various errors
1123 bool repair (const(char)[] name) {
1124 import core.sys.posix.fcntl : open, O_RDWR;
1125 import core.sys.posix.sys.stat : lstat, stat_t;
1126 import core.sys.posix.unistd : close, ftruncate, unlink;
1127 Depot tdepot;
1128 QDBMHeader dbhead;
1129 char* kbuf, vbuf;
1130 RecordHeader head;
1131 int fd, flags, bnum, tbnum, rsiz, ksiz, vsiz;
1132 usize off;
1133 long fsiz;
1134 stat_t sbuf;
1135 assert(name.length);
1137 import std.string : toStringz;
1138 auto namez = name.toStringz;
1139 if (lstat(namez, &sbuf) == -1) raise(Error.STAT);
1140 fsiz = sbuf.st_size;
1141 if ((fd = open(namez, O_RDWR, DP_FILEMODE)) == -1) raise(Error.OPEN);
1143 scope(exit) if (fd >= 0) close(fd);
1144 fdseekread(fd, 0, (&dbhead)[0..1]);
1145 flags = dbhead.flags;
1146 bnum = dbhead.nbuckets;
1147 tbnum = dbhead.nrecords*2;
1148 if (tbnum < DP_DEFBNUM) tbnum = DP_DEFBNUM;
1149 tdepot = new Depot(name~DP_TMPFSUF, WRITER|CREAT|TRUNC, tbnum);
1150 off = QDBMHeader.sizeof+bnum*int.sizeof;
1151 bool err = false;
1152 while (off < fsiz) {
1153 try {
1154 fdseekread(fd, off, (&head)[0..1]);
1155 } catch (Exception) {
1156 break;
1158 if (head.flags&DP_RECFDEL) {
1159 rsiz = head.recsize;
1160 if (rsiz < 0) break;
1161 off += rsiz;
1162 continue;
1164 ksiz = head.ksiz;
1165 vsiz = head.vsiz;
1166 if (ksiz >= 0 && vsiz >= 0) {
1167 import core.stdc.stdlib : malloc;
1168 kbuf = cast(char*)malloc(ksiz+1);
1169 vbuf = cast(char*)malloc(vsiz+1);
1170 if (kbuf !is null && vbuf !is null) {
1171 try {
1172 fdseekread(fd, off+RecordHeader.sizeof, kbuf[0..ksiz]);
1173 fdseekread(fd, off+RecordHeader.sizeof+ksiz, vbuf[0..vsiz]);
1174 tdepot.put(kbuf[0..ksiz], vbuf[0..vsiz], WMode.KEEP);
1175 } catch (Exception) {
1176 err = true;
1178 } else {
1179 //if (!err) raise(Error.ALLOC);
1180 err = true;
1182 if (vbuf !is null) freeptr(vbuf);
1183 if (kbuf !is null) freeptr(kbuf);
1184 } else {
1185 //if (!err) raise(Error.BROKEN);
1186 err = true;
1188 rsiz = head.recsize;
1189 if (rsiz < 0) break;
1190 off += rsiz;
1192 tdepot.flags = flags; // err = true;
1193 try {
1194 tdepot.sync();
1195 } catch (Exception) {
1196 err = true;
1198 if (ftruncate(fd, 0) == -1) {
1199 //if (!err) raise(Error.TRUNC);
1200 err = true;
1202 auto tempname = tdepot.m_name; // with trailing zero
1203 try {
1204 fcopy(fd, 0, tdepot.m_fd, 0);
1205 tdepot.close();
1206 } catch (Exception) {
1207 err = true;
1209 if (close(fd) == -1) {
1210 //if (!err) raise(Error.CLOSE);
1211 err = true;
1213 fd = -1;
1214 if (unlink(tempname.ptr) == -1) {
1215 //if (!err) raise(Error.UNLINK);
1216 err = true;
1218 return !err;
1221 /** Hash function used inside Depot.
1223 * This function is useful when an application calculates the state of the inside bucket array.
1225 * Params:
1226 * kbuf = the pointer to the region of a key
1228 * Returns:
1229 * The return value is the hash value of 31 bits length computed from the key.
1231 static int innerhash (const(void)[] kbuf) @trusted nothrow @nogc {
1232 int res;
1233 if (kbuf.length == int.sizeof) {
1234 import core.stdc.string : memcpy;
1235 memcpy(&res, kbuf.ptr, res.sizeof);
1236 } else {
1237 res = 751;
1239 foreach (immutable bt; cast(const(ubyte)[])kbuf) res = res*31+bt;
1240 return (res*87767623)&int.max;
1243 /** Hash function which is independent from the hash functions used inside Depot.
1245 * This function is useful when an application uses its own hash algorithm outside Depot.
1247 * Params:
1248 * kbuf = the pointer to the region of a key
1250 * Returns:
1251 * The return value is the hash value of 31 bits length computed from the key.
1253 static int outerhash (const(void)[] kbuf) @trusted nothrow @nogc {
1254 int res = 774831917;
1255 foreach_reverse (immutable bt; cast(const(ubyte)[])kbuf) res = res*29+bt;
1256 return (res*5157883)&int.max;
1259 /** Get a natural prime number not less than a number.
1261 * This function is useful when an application determines the size of a bucket array of its
1262 * own hash algorithm.
1264 * Params:
1265 * num = a natural number
1267 * Returns:
1268 * The return value is a natural prime number not less than the specified number
1270 static int primenum (int num) @safe pure nothrow @nogc {
1271 static immutable int[217] primes = [
1272 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 43, 47, 53, 59, 61, 71, 79, 83,
1273 89, 103, 109, 113, 127, 139, 157, 173, 191, 199, 223, 239, 251, 283, 317, 349,
1274 383, 409, 443, 479, 509, 571, 631, 701, 761, 829, 887, 953, 1021, 1151, 1279,
1275 1399, 1531, 1663, 1789, 1913, 2039, 2297, 2557, 2803, 3067, 3323, 3583, 3833,
1276 4093, 4603, 5119, 5623, 6143, 6653, 7159, 7673, 8191, 9209, 10223, 11261,
1277 12281, 13309, 14327, 15359, 16381, 18427, 20479, 22511, 24571, 26597, 28669,
1278 30713, 32749, 36857, 40949, 45053, 49139, 53239, 57331, 61417, 65521, 73727,
1279 81919, 90107, 98299, 106487, 114679, 122869, 131071, 147451, 163819, 180221,
1280 196597, 212987, 229373, 245759, 262139, 294911, 327673, 360439, 393209, 425977,
1281 458747, 491503, 524287, 589811, 655357, 720887, 786431, 851957, 917503, 982981,
1282 1048573, 1179641, 1310719, 1441771, 1572853, 1703903, 1835003, 1966079,
1283 2097143, 2359267, 2621431, 2883577, 3145721, 3407857, 3670013, 3932153,
1284 4194301, 4718579, 5242877, 5767129, 6291449, 6815741, 7340009, 7864301,
1285 8388593, 9437179, 10485751, 11534329, 12582893, 13631477, 14680063, 15728611,
1286 16777213, 18874367, 20971507, 23068667, 25165813, 27262931, 29360087, 31457269,
1287 33554393, 37748717, 41943023, 46137319, 50331599, 54525917, 58720253, 62914549,
1288 67108859, 75497467, 83886053, 92274671, 100663291, 109051903, 117440509,
1289 125829103, 134217689, 150994939, 167772107, 184549373, 201326557, 218103799,
1290 234881011, 251658227, 268435399, 301989881, 335544301, 369098707, 402653171,
1291 436207613, 469762043, 503316469, 536870909, 603979769, 671088637, 738197503,
1292 805306357, 872415211, 939524087, 1006632947, 1073741789, 1207959503,
1293 1342177237, 1476394991, 1610612711, 1744830457, 1879048183, 2013265907,
1295 assert(num > 0);
1296 foreach (immutable pr; primes) if (num <= pr) return pr;
1297 return primes[$-1];
1300 /* ********************************************************************************************* *
1301 * features for experts
1302 * ********************************************************************************************* */
1304 /** Synchronize updating contents on memory.
1306 * Throws:
1307 * DepotException on various errors
1309 void memsync () {
1310 import core.sys.posix.sys.mman : msync, MS_SYNC;
1311 checkWriting();
1312 updateHeader();
1313 if (msync(m_map, m_msiz, MS_SYNC) == -1) {
1314 m_fatal = true;
1315 raise(Error.MAP);
1319 /** Synchronize updating contents on memory, not physically.
1321 * Throws:
1322 * DepotException on various errors
1324 void memflush () {
1325 checkWriting();
1326 updateHeader();
1327 // there is no mflush() call
1328 version(none) {
1329 if (mflush(m_map, m_msiz, MS_SYNC) == -1) {
1330 m_fatal = true;
1331 raise(Error.MAP);
1336 /** Get flags of a database.
1338 * Returns:
1339 * The return value is the flags of a database.
1341 * Throws:
1342 * DepotException on various errors
1344 @property int flags () {
1345 checkOpened();
1346 auto hdr = cast(QDBMHeader*)m_map;
1347 return hdr.flags;
1350 /** Set flags of a database.
1352 * Params:
1353 * flags = flags to set. Least ten bits are reserved for internal use.
1355 * Returns:
1356 * If successful, the return value is true, else, it is false.
1358 * Throws:
1359 * DepotException on various errors
1361 @property void flags (int v) {
1362 checkWriting();
1363 auto hdr = cast(QDBMHeader*)m_map;
1364 hdr.flags = v;
1367 private:
1368 /* ********************************************************************************************* *
1369 * private objects
1370 * ********************************************************************************************* */
1372 void raise (Error errcode, string file=__FILE__, usize line=__LINE__) {
1373 assert(errcode >= Error.NOERR);
1374 if (errcode == Error.FATAL) m_fatal = true;
1375 throw new DepotException(errcode, file, line);
1378 static void straise (Error errcode, string file=__FILE__, usize line=__LINE__) {
1379 assert(errcode >= Error.NOERR);
1380 throw new DepotException(errcode, file, line);
1383 // get the second hash value
1384 static int secondhash (const(void)[] kbuf) @trusted nothrow @nogc {
1385 int res = 19780211;
1386 foreach_reverse (immutable bt; cast(const(ubyte)[])kbuf) res = res*37+bt;
1387 return (res*43321879)&int.max;
1390 void updateHeader () @trusted nothrow @nogc {
1391 auto hdr = cast(QDBMHeader*)m_map;
1392 hdr.filesize = cast(int)m_fsiz;
1393 hdr.nrecords = m_rnum;
1396 /* Lock a file descriptor.
1398 * Params:
1399 * fd = a file descriptor
1400 * ex = whether an exclusive lock or a shared lock is performed
1401 * nb = whether to request with non-blocking
1402 * errcode = the error code (can be `null`)
1404 * Throws:
1405 * DepotException on various errors
1407 static void fdlock (int fd, int ex, int nb) {
1408 import core.stdc.stdio : SEEK_SET;
1409 import core.stdc.string : memset;
1410 import core.sys.posix.fcntl : flock, fcntl, F_RDLCK, F_SETLK, F_SETLKW, F_WRLCK;
1411 flock lock;
1412 assert(fd >= 0);
1413 memset(&lock, 0, lock.sizeof);
1414 lock.l_type = (ex ? F_WRLCK : F_RDLCK);
1415 lock.l_whence = SEEK_SET;
1416 lock.l_start = 0;
1417 lock.l_len = 0;
1418 lock.l_pid = 0;
1419 while (fcntl(fd, nb ? F_SETLK : F_SETLKW, &lock) == -1) {
1420 import core.stdc.errno : errno, EINTR;
1421 if (errno != EINTR) straise(Error.LOCK);
1425 /* Write into a file.
1427 * Params:
1428 * fd = a file descriptor
1429 * buf = a buffer to write
1431 * Returns:
1432 * The return value is the size of the written buffer, or -1 on failure
1434 * Throws:
1435 * Nothing
1437 static int fdwrite (int fd, const(void)[] buf) @trusted nothrow @nogc {
1438 auto lbuf = cast(const(ubyte)[])buf;
1439 int rv = 0;
1440 assert(fd >= 0);
1441 while (lbuf.length > 0) {
1442 import core.sys.posix.unistd : write;
1443 auto wb = write(fd, lbuf.ptr, lbuf.length);
1444 if (wb == -1) {
1445 import core.stdc.errno : errno, EINTR;
1446 if (errno != EINTR) return -1;
1447 continue;
1449 if (wb == 0) break;
1450 lbuf = lbuf[wb..$];
1451 rv += cast(int)wb;
1453 return rv;
1456 /* Write into a file at an offset.
1458 * Params:
1459 * fd = a file descriptor
1460 * off = an offset of the file
1461 * buf = a buffer to write
1463 * Throws:
1464 * DepotException on various errors
1466 static void fdseekwrite (int fd, long off, const(void)[] buf) {
1467 import core.stdc.stdio : SEEK_END, SEEK_SET;
1468 import core.sys.posix.unistd : lseek;
1469 assert(fd >= 0);
1470 if (buf.length < 1) return;
1471 if (lseek(fd, (off < 0 ? 0 : off), (off < 0 ? SEEK_END : SEEK_SET)) == -1) straise(Error.SEEK);
1472 if (fdwrite(fd, buf) != buf.length) straise(Error.WRITE);
1475 /* Write an integer into a file at an offset.
1477 * Params:
1478 * fd = a file descriptor
1479 * off = an offset of the file
1480 * num = an integer
1482 * Throws:
1483 * DepotException on various errors
1485 void fdseekwritenum (int fd, long off, int num) {
1486 assert(fd >= 0);
1487 scope(failure) m_fatal = true;
1488 fdseekwrite(fd, off, (&num)[0..1]);
1491 /* Read from a file and store the data into a buffer.
1493 * Params:
1494 * fd = a file descriptor
1495 * buf = a buffer to store into.
1497 * Returns:
1498 * The return value is the size read with, or -1 on failure
1500 * Throws:
1501 * Nothing
1503 static int fdread (int fd, void[] buf) @trusted nothrow @nogc {
1504 import core.sys.posix.unistd : read;
1505 auto lbuf = cast(ubyte[])buf;
1506 int total = 0;
1507 assert(fd >= 0);
1508 while (lbuf.length > 0) {
1509 auto bs = read(fd, lbuf.ptr, lbuf.length);
1510 if (bs < 0) {
1511 import core.stdc.errno : errno, EINTR;
1512 if (errno != EINTR) return -1;
1513 continue;
1515 if (bs == 0) break;
1516 lbuf = lbuf[bs..$];
1517 total += cast(int)bs;
1519 return total;
1522 /* Read from a file at an offset and store the data into a buffer.
1524 * Params:
1525 * fd = a file descriptor
1526 * off = an offset of the file
1527 * buf = a buffer to store into
1529 * Throws:
1530 * DepotException on various errors
1532 static void fdseekread (int fd, long off, void[] buf) {
1533 import core.stdc.stdio : SEEK_SET;
1534 import core.sys.posix.unistd : lseek;
1535 assert(fd >= 0 && off >= 0);
1536 if (lseek(fd, off, SEEK_SET) != off) straise(Error.SEEK);
1537 if (fdread(fd, buf) != buf.length) straise(Error.READ);
1540 /* Copy data between files.
1542 * Params:
1543 * destfd = a file descriptor of a destination file
1544 * destoff = an offset of the destination file
1545 * srcfd = a file descriptor of a source file
1546 * srcoff = an offset of the source file
1548 * Returns:
1549 * The return value is the size copied with
1551 * Throws:
1552 * DepotException on various errors
1554 static int fcopy (int destfd, long destoff, int srcfd, long srcoff) {
1555 import core.stdc.stdio : SEEK_SET;
1556 import core.sys.posix.unistd : lseek;
1557 char[DP_IOBUFSIZ] iobuf;
1558 int sum, iosiz;
1559 if (lseek(srcfd, srcoff, SEEK_SET) == -1 || lseek(destfd, destoff, SEEK_SET) == -1) straise(Error.SEEK);
1560 sum = 0;
1561 while ((iosiz = fdread(srcfd, iobuf[])) > 0) {
1562 if (fdwrite(destfd, iobuf[0..iosiz]) != iosiz) straise(Error.WRITE);
1563 sum += iosiz;
1565 if (iosiz < 0) straise(Error.READ);
1566 return sum;
1569 /* Get the padding size of a record.
1571 * Params:
1572 * ksiz = the size of the key of a record
1573 * vsiz = the size of the value of a record
1575 * Returns:
1576 * The return value is the padding size of a record
1578 usize padsize (usize ksiz, usize vsiz) const @safe pure nothrow @nogc {
1579 if (m_alignment > 0) {
1580 return cast(usize)(m_alignment-(m_fsiz+RecordHeader.sizeof+ksiz+vsiz)%m_alignment);
1581 } else if (m_alignment < 0) {
1582 usize pad = cast(usize)(vsiz*(2.0/(1<<(-m_alignment))));
1583 if (vsiz+pad >= DP_FSBLKSIZ) {
1584 if (vsiz <= DP_FSBLKSIZ) pad = 0;
1585 if (m_fsiz%DP_FSBLKSIZ == 0) {
1586 return cast(usize)((pad/DP_FSBLKSIZ)*DP_FSBLKSIZ+DP_FSBLKSIZ-(m_fsiz+RecordHeader.sizeof+ksiz+vsiz)%DP_FSBLKSIZ);
1587 } else {
1588 return cast(usize)((pad/(DP_FSBLKSIZ/2))*(DP_FSBLKSIZ/2)+(DP_FSBLKSIZ/2)-
1589 (m_fsiz+RecordHeader.sizeof+ksiz+vsiz)%(DP_FSBLKSIZ/2));
1591 } else {
1592 return (pad >= RecordHeader.sizeof ? pad : RecordHeader.sizeof);
1595 return 0;
1598 /* Read the header of a record.
1600 * Params:
1601 * off = an offset of the database file
1602 * head = specifies a buffer for the header
1603 * ebuf = specifies the pointer to the entity buffer
1604 * eep = the pointer to a variable to which whether ebuf was used is assigned
1606 * Throws:
1607 * DepotException on various errors
1609 void rechead (long off, ref RecordHeader head, void[] ebuf, bool* eep) {
1610 assert(off >= 0);
1611 if (eep !is null) *eep = false;
1612 if (off < 0 || off > m_fsiz) raise(Error.BROKEN);
1613 scope(failure) m_fatal = true; // any failure is fatal here
1614 if (ebuf.length >= DP_ENTBUFSIZ && off < m_fsiz-DP_ENTBUFSIZ) {
1615 import core.stdc.string : memcpy;
1616 if (eep !is null) *eep = true;
1617 fdseekread(m_fd, off, ebuf[0..DP_ENTBUFSIZ]);
1618 memcpy(&head, ebuf.ptr, RecordHeader.sizeof);
1619 } else {
1620 fdseekread(m_fd, off, (&head)[0..1]);
1622 if (head.ksiz < 0 || head.vsiz < 0 || head.psiz < 0 || head.left < 0 || head.right < 0) raise(Error.BROKEN);
1625 /* Read the entitiy of the key of a record.
1627 * Params:
1628 * off = an offset of the database file
1629 * head = the header of a record
1631 * Returns:
1632 * The return value is a key data whose region is allocated by `malloc`
1634 * Throws:
1635 * DepotException on various errors
1637 char* reckey (long off, ref in RecordHeader head) {
1638 //TODO: return slice instead of pointer?
1639 import core.stdc.stdlib : malloc;
1640 char* kbuf;
1641 assert(off >= 0);
1642 int ksiz = head.ksiz;
1643 kbuf = cast(char*)malloc(ksiz+1);
1644 if (kbuf is null) raise(Error.ALLOC);
1645 scope(failure) freeptr(kbuf);
1646 fdseekread(m_fd, off+RecordHeader.sizeof, kbuf[0..ksiz]);
1647 kbuf[ksiz] = '\0';
1648 return kbuf;
1651 /* Read the entitiy of the value of a record.
1653 * Params:
1654 * off = an offset of the database file
1655 * head = the header of a record
1656 * start = the offset address of the beginning of the region of the value to be read
1657 * max = the max size to be read; if it is `uint.max`, the size to read is unlimited
1659 * Returns:
1660 * The return value is a value data whose region is allocated by `malloc`
1662 * Throws:
1663 * DepotException on various errors
1665 char* recval (long off, ref RecordHeader head, uint start=0, uint max=uint.max) {
1666 //TODO: return slice instead of pointer?
1667 import core.stdc.stdlib : malloc;
1668 char* vbuf;
1669 uint vsiz;
1670 assert(off >= 0);
1671 head.vsiz -= start;
1672 if (max == uint.max) {
1673 vsiz = head.vsiz;
1674 } else {
1675 vsiz = (max < head.vsiz ? max : head.vsiz);
1677 vbuf = cast(char*)malloc(vsiz+1);
1678 if (vbuf is null) { m_fatal = true; raise(Error.ALLOC); }
1679 scope(failure) { m_fatal = true; freeptr(vbuf); }
1680 fdseekread(m_fd, off+RecordHeader.sizeof+head.ksiz+start, vbuf[0..vsiz]);
1681 vbuf[vsiz] = '\0';
1682 return vbuf;
1685 /* Read the entitiy of the value of a record and write it into a given buffer.
1687 * Params:
1688 * off = an offset of the database file
1689 * head = the header of a record
1690 * start = the offset address of the beginning of the region of the value to be read
1691 * vbuf = the pointer to a buffer into which the value of the corresponding record is written
1693 * Returns:
1694 * The return value is the size of the written data
1696 * Throws:
1697 * DepotException on various errors
1699 usize recvalwb (void[] vbuf, long off, ref RecordHeader head, uint start=0) {
1700 assert(off >= 0);
1701 head.vsiz -= start;
1702 usize vsiz = (vbuf.length < head.vsiz ? vbuf.length : head.vsiz);
1703 fdseekread(m_fd, off+RecordHeader.sizeof+head.ksiz+start, vbuf[0..vsiz]);
1704 return vsiz;
1707 /* Compare two keys.
1709 * Params:
1710 * abuf = the pointer to the region of the former
1711 * asiz = the size of the region
1712 * bbuf = the pointer to the region of the latter
1713 * bsiz = the size of the region
1715 * Returns:
1716 * The return value is 0 if two equals, positive if the formar is big, else, negative.
1718 static int keycmp (const(void)[] abuf, const(void)[] bbuf) @trusted nothrow @nogc {
1719 import core.stdc.string : memcmp;
1720 //assert(abuf && asiz >= 0 && bbuf && bsiz >= 0);
1721 if (abuf.length > bbuf.length) return 1;
1722 if (abuf.length < bbuf.length) return -1;
1723 return memcmp(abuf.ptr, bbuf.ptr, abuf.length);
1726 /* Search for a record.
1728 * Params:
1729 * kbuf = the pointer to the region of a key
1730 * hash = the second hash value of the key
1731 * bip = the pointer to the region to assign the index of the corresponding record
1732 * offp = the pointer to the region to assign the last visited node in the hash chain,
1733 * or, -1 if the hash chain is empty
1734 * entp = the offset of the last used joint, or, -1 if the hash chain is empty
1735 * head = the pointer to the region to store the header of the last visited record in
1736 * ebuf = the pointer to the entity buffer
1737 * eep = the pointer to a variable to which whether ebuf was used is assigned
1738 * delhit = whether a deleted record corresponds or not
1740 * Returns:
1741 * The return value is true if record was found, false if there is no corresponding record.
1743 * Throws:
1744 * DepotException on various errors
1746 bool recsearch (const(void)[] kbuf, int hash, int* bip, int* offp, int* entp,
1747 ref RecordHeader head, void[] ebuf, bool* eep, Flag!"delhit" delhit=No.delhit)
1749 usize off;
1750 int entoff;
1751 int thash, kcmp;
1752 char[DP_STKBUFSIZ] stkey;
1753 char* tkey;
1754 assert(ebuf.length >= DP_ENTBUFSIZ);
1755 assert(hash >= 0 && bip !is null && offp !is null && entp !is null && eep !is null);
1756 thash = innerhash(kbuf);
1757 *bip = thash%m_bnum;
1758 off = m_buckets[*bip];
1759 *offp = -1;
1760 *entp = -1;
1761 entoff = -1;
1762 *eep = false;
1763 while (off != 0) {
1764 rechead(off, head, ebuf, eep);
1765 thash = head.hash2;
1766 if (hash > thash) {
1767 entoff = cast(int)(off+RecordHeader.left.offsetof);
1768 off = head.left;
1769 } else if (hash < thash) {
1770 entoff = cast(int)(off+RecordHeader.right.offsetof);
1771 off = head.right;
1772 } else {
1773 if (*eep && RecordHeader.sizeof+head.ksiz <= DP_ENTBUFSIZ) {
1774 immutable ebstart = RecordHeader.sizeof;
1775 kcmp = keycmp(kbuf, ebuf[ebstart..ebstart+head.ksiz]);
1776 } else if (head.ksiz > DP_STKBUFSIZ) {
1777 if ((tkey = reckey(off, head)) is null) raise(Error.FATAL);
1778 kcmp = keycmp(kbuf, tkey[0..head.ksiz]);
1779 freeptr(tkey);
1780 } else {
1781 try {
1782 fdseekread(m_fd, off+RecordHeader.sizeof, stkey[0..head.ksiz]);
1783 } catch (Exception) {
1784 raise(Error.FATAL);
1786 kcmp = keycmp(kbuf, stkey[0..head.ksiz]);
1788 if (kcmp > 0) {
1789 entoff = cast(int)(off+RecordHeader.left.offsetof);
1790 off = head.left;
1791 } else if (kcmp < 0) {
1792 entoff = cast(int)(off+RecordHeader.right.offsetof);
1793 off = head.right;
1794 } else {
1795 if (!delhit && (head.flags&DP_RECFDEL)) {
1796 entoff = cast(int)(off+RecordHeader.left.offsetof);
1797 off = head.left;
1798 } else {
1799 *offp = cast(int)off;
1800 *entp = entoff;
1801 return true;
1806 *offp = cast(int)off;
1807 *entp = entoff;
1808 return false;
1811 /* Overwrite a record.
1813 * Params:
1814 * off = the offset of the database file
1815 * rsiz = the size of the existing record
1816 * kbuf = the pointer to the region of a key
1817 * vbuf = the pointer to the region of a value
1818 * hash = the second hash value of the key
1819 * left = the offset of the left child
1820 * right = the offset of the right child
1822 * Throws:
1823 * DepotException on various errors
1825 void recrewrite (long off, int rsiz, const(void)[] kbuf, const(void)[] vbuf, int hash, int left, int right) {
1826 char[DP_WRTBUFSIZ] ebuf;
1827 RecordHeader head;
1828 int hoff, koff, voff, mi, min, size;
1829 usize asiz;
1830 assert(off >= 1 && rsiz > 0);
1831 head.flags = 0;
1832 head.hash2 = hash;
1833 head.ksiz = cast(int)kbuf.length;
1834 head.vsiz = cast(int)vbuf.length;
1835 head.psiz = cast(int)(rsiz-head.sizeof-kbuf.length-vbuf.length);
1836 head.left = left;
1837 head.right = right;
1838 asiz = head.sizeof+kbuf.length+vbuf.length;
1839 if (m_fbpsiz > DP_FBPOOLSIZ*4 && head.psiz > asiz) {
1840 rsiz = cast(int)((head.psiz-asiz)/2+asiz);
1841 head.psiz -= rsiz;
1842 } else {
1843 rsiz = 0;
1845 if (asiz <= DP_WRTBUFSIZ) {
1846 import core.stdc.string : memcpy;
1847 memcpy(ebuf.ptr, &head, head.sizeof);
1848 memcpy(ebuf.ptr+head.sizeof, kbuf.ptr, kbuf.length);
1849 memcpy(ebuf.ptr+head.sizeof+kbuf.length, vbuf.ptr, vbuf.length);
1850 fdseekwrite(m_fd, off, ebuf[0..asiz]);
1851 } else {
1852 hoff = cast(int)off;
1853 koff = cast(int)(hoff+head.sizeof);
1854 voff = cast(int)(koff+kbuf.length);
1855 fdseekwrite(m_fd, hoff, (&head)[0..1]);
1856 fdseekwrite(m_fd, koff, kbuf[]);
1857 fdseekwrite(m_fd, voff, vbuf[]);
1859 if (rsiz > 0) {
1860 off += head.sizeof+kbuf.length+vbuf.length+head.psiz;
1861 head.flags = DP_RECFDEL|DP_RECFREUSE;
1862 head.hash2 = hash;
1863 head.ksiz = cast(int)kbuf.length;
1864 head.vsiz = cast(int)vbuf.length;
1865 head.psiz = cast(int)(rsiz-head.sizeof-kbuf.length-vbuf.length);
1866 head.left = 0;
1867 head.right = 0;
1868 fdseekwrite(m_fd, off, (&head)[0..1]);
1869 size = head.recsize;
1870 mi = -1;
1871 min = -1;
1872 for (uint i = 0; i < m_fbpsiz; i += 2) {
1873 if (m_fbpool[i] == -1) {
1874 m_fbpool[i] = cast(int)off;
1875 m_fbpool[i+1] = size;
1876 fbpoolcoal();
1877 mi = -1;
1878 break;
1880 if (mi == -1 || m_fbpool[i+1] < min) {
1881 mi = i;
1882 min = m_fbpool[i+1];
1885 if (mi >= 0 && size > min) {
1886 m_fbpool[mi] = cast(int)off;
1887 m_fbpool[mi+1] = size;
1888 fbpoolcoal();
1893 /* Write a record at the end of a database file.
1895 * Params:
1896 * kbuf = the pointer to the region of a key
1897 * vbuf = the pointer to the region of a value
1898 * hash = the second hash value of the key
1899 * left = the offset of the left child
1900 * right = the offset of the right child
1902 * Returns:
1903 * The return value is the offset of the record
1905 * Throws:
1906 * DepotException on various errors
1908 int recappend (const(void)[] kbuf, const(void)[] vbuf, int hash, int left, int right) {
1909 char[DP_WRTBUFSIZ] ebuf;
1910 RecordHeader head;
1911 usize asiz, psiz;
1912 long off;
1913 psiz = padsize(kbuf.length, vbuf.length);
1914 head.flags = 0;
1915 head.hash2 = hash;
1916 head.ksiz = cast(int)kbuf.length;
1917 head.vsiz = cast(int)vbuf.length;
1918 head.psiz = cast(int)psiz;
1919 head.left = left;
1920 head.right = right;
1921 asiz = head.sizeof+kbuf.length+vbuf.length+psiz;
1922 off = m_fsiz;
1923 if (asiz <= DP_WRTBUFSIZ) {
1924 import core.stdc.string : memcpy, memset;
1925 memcpy(ebuf.ptr, &head, head.sizeof);
1926 memcpy(ebuf.ptr+head.sizeof, kbuf.ptr, kbuf.length);
1927 memcpy(ebuf.ptr+head.sizeof+kbuf.length, vbuf.ptr, vbuf.length);
1928 memset(ebuf.ptr+head.sizeof+kbuf.length+vbuf.length, 0, psiz);
1929 fdseekwrite(m_fd, off, ebuf[0..asiz]);
1930 } else {
1931 import core.stdc.stdlib : malloc;
1932 import core.stdc.string : memcpy, memset;
1933 auto hbuf = cast(char*)malloc(asiz);
1934 if (hbuf is null) raise(Error.ALLOC);
1935 scope(exit) freeptr(hbuf);
1936 memcpy(hbuf, &head, head.sizeof);
1937 memcpy(hbuf+head.sizeof, kbuf.ptr, kbuf.length);
1938 memcpy(hbuf+head.sizeof+kbuf.length, vbuf.ptr, vbuf.length);
1939 memset(hbuf+head.sizeof+kbuf.length+vbuf.length, 0, psiz);
1940 fdseekwrite(m_fd, off, hbuf[0..asiz]);
1942 m_fsiz += asiz;
1943 return cast(int)off;
1946 /* Overwrite the value of a record.
1948 * Params:
1949 * off = the offset of the database file
1950 * head = the header of the record
1951 * vbuf = the pointer to the region of a value
1952 * cat = whether it is concatenate mode or not
1954 * Throws:
1955 * DepotException on various errors
1957 void recover (long off, ref RecordHeader head, const(void)[] vbuf, Flag!"catmode" catmode) {
1958 assert(off >= 0);
1959 for (uint i = 0; i < m_fbpsiz; i += 2) {
1960 if (m_fbpool[i] == off) {
1961 m_fbpool[i] = m_fbpool[i+1] = -1;
1962 break;
1965 head.flags = 0;
1966 long voff = off+RecordHeader.sizeof+head.ksiz;
1967 if (catmode) {
1968 head.psiz -= vbuf.length;
1969 head.vsiz += vbuf.length;
1970 voff += head.vsiz-vbuf.length;
1971 } else {
1972 head.psiz += head.vsiz-vbuf.length;
1973 head.vsiz = cast(int)vbuf.length;
1975 scope(failure) m_fatal = true; // any failure is fatal here
1976 fdseekwrite(m_fd, off, (&head)[0..1]);
1977 fdseekwrite(m_fd, voff, vbuf[]);
1980 /* Delete a record.
1982 * Params:
1983 * off = the offset of the database file
1984 * head = the header of the record
1985 * reusable = whether the region is reusable or not
1987 * Throws:
1988 * DepotException on various errors
1990 void recdelete (long off, ref in RecordHeader head, Flag!"reusable" reusable) {
1991 assert(off >= 0);
1992 if (reusable) {
1993 auto size = head.recsize;
1994 int mi = -1;
1995 int min = -1;
1996 for (uint i = 0; i < m_fbpsiz; i += 2) {
1997 if (m_fbpool[i] == -1) {
1998 m_fbpool[i] = cast(int)off;
1999 m_fbpool[i+1] = size;
2000 fbpoolcoal();
2001 mi = -1;
2002 break;
2004 if (mi == -1 || m_fbpool[i+1] < min) {
2005 mi = i;
2006 min = m_fbpool[i+1];
2009 if (mi >= 0 && size > min) {
2010 m_fbpool[mi] = cast(int)off;
2011 m_fbpool[mi+1] = size;
2012 fbpoolcoal();
2015 fdseekwritenum(m_fd, off+RecordHeader.flags.offsetof, DP_RECFDEL|(reusable ? DP_RECFREUSE : 0));
2018 /* Make contiguous records of the free block pool coalesce. */
2019 void fbpoolcoal () @trusted {
2020 import core.stdc.stdlib : qsort;
2021 if (m_fbpinc++ <= m_fbpsiz/4) return;
2022 m_fbpinc = 0;
2023 qsort(m_fbpool, m_fbpsiz/2, int.sizeof*2, &fbpoolcmp);
2024 for (uint i = 2; i < m_fbpsiz; i += 2) {
2025 if (m_fbpool[i-2] > 0 && m_fbpool[i-2]+m_fbpool[i-1]-m_fbpool[i] == 0) {
2026 m_fbpool[i] = m_fbpool[i-2];
2027 m_fbpool[i+1] += m_fbpool[i-1];
2028 m_fbpool[i-2] = m_fbpool[i-1] = -1;
2033 /* Compare two records of the free block pool.
2034 a = the pointer to one record.
2035 b = the pointer to the other record.
2036 The return value is 0 if two equals, positive if the formar is big, else, negative. */
2037 static extern(C) int fbpoolcmp (in void* a, in void* b) @trusted nothrow @nogc {
2038 assert(a && b);
2039 return *cast(const int*)a - *cast(const int*)b;