cosmetix
[iv.d.git] / sdbm.d
blob14d450a3dea0905f271e075a1d8632534e7ffae6
1 /* ********************************************************************************************* *
2 * Simple Database Manager, based on QDBM code
3 * QDBM Copyright (C) 2000-2007 Mikio Hirabayashi
4 * SDBM Copyright (C) 2015 Ketmar Dark <ketmar@ketmar.no-ip.org>
6 * SDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
7 * Lesser General Public License as published by the Free Software Foundation; either version
8 * 2.1 of the License or any later version. SDBM is distributed in the hope that it will be
9 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
11 * details.
13 * You should have received a copy of the GNU Lesser General Public License along with SDBM; if
14 * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
15 * 02111-1307 USA.
16 * ********************************************************************************************* */
17 // key/value database based on QDBM
18 module iv.sdbm /*is aliced*/;
20 import std.traits : isArray, isDynamicArray;
21 import iv.alice;
22 import iv.hash.fasthash;
25 /// database errors
26 class SDBMException : Exception {
27 this (SDBM.Error ecode, string file=__FILE__, usize line=__LINE__, Throwable next=null) pure nothrow @safe @nogc {
28 super(errorMessage(ecode), file, line, next);
31 /** Get a message string corresponding to an error code.
33 * Params:
34 * ecode = an error code
36 * Returns:
37 * The message string of the error code.
39 static string errorMessage (SDBM.Error ecode) @safe pure nothrow @nogc {
40 switch (ecode) with (SDBM.Error) {
41 case NOERR: return "no error";
42 case FATAL: return "with fatal error";
43 case CLOSED: return "database not opened error";
44 case OPENED: return "already opened database error";
45 case MODE: return "invalid mode";
46 case BROKEN: return "broken database file";
47 case KEEP: return "existing record";
48 case NOITEM: return "no item found";
49 case ALLOC: return "memory allocation error";
50 case MAP: return "memory mapping error";
51 case OPEN: return "open error";
52 case CLOSE: return "close error";
53 case TRUNC: return "trunc error";
54 case SYNC: return "sync error";
55 case STAT: return "stat error";
56 case SEEK: return "seek error";
57 case READ: return "read error";
58 case WRITE: return "write error";
59 case LOCK: return "lock error";
60 case UNLINK: return "unlink error";
61 case MKDIR: return "mkdir error";
62 case RMDIR: return "rmdir error";
63 case MISC: return "miscellaneous error";
64 case NOTFOUND: return "not found error";
65 default: return "(invalid ecode)";
67 assert(0);
72 /// database
73 public final class SDBM {
74 public import core.sys.posix.sys.types : time_t;
75 private import std.typecons : Flag, Yes, No;
77 enum SDBM_LIBVER = "0666";
78 static assert(SDBM_LIBVER.length == SDBMHeader.versionstr.length);
80 this (const(char)[] name, int omode=READER, int bnum=-1) { open(name, omode, bnum); }
81 ~this () { close(); }
83 private:
84 string m_name; // name of the database file; terminated with '\0', but '\0' is not a string part
85 bool m_wmode; // whether to be writable
86 ulong m_inode; // inode of the database file
87 time_t m_mtime; // last modified time of the database
88 int m_fd = -1; // file descriptor of the database file
89 long m_fsiz; // size of the database file
90 char* m_map; // pointer to the mapped memory
91 int m_msiz; // size of the mapped memory
92 int* m_buckets; // pointer to the bucket array
93 int m_bnum; // number of the bucket array
94 int m_rnum; // number of records
95 bool m_fatal; // whether a fatal error occured
96 int m_ioff; // offset of the iterator
97 int* m_fbpool; // free block pool
98 int m_fbpsiz; // size of the free block pool
99 int m_fbpinc; // incrementor of update of the free block pool
100 int m_alignment; // basic size of alignment (can be negative; why?)
102 private:
103 public static ushort DP_FILEMODE = 384; // 0o600: permission of a creating file
104 version(BigEndian) {
105 enum DP_MAGIC = "[SDBMFILE]\n\f"; // magic on environments of big endian
106 } else {
107 enum DP_MAGIC = "[sdbmfile]\n\f"; // magic on environments of little endian
109 static assert(DP_MAGIC.length > 0 && DP_MAGIC.length <= SDBMHeader.signature.length);
110 enum DP_DEFBNUM = 8191; // default bucket number
111 enum DP_FBPOOLSIZ = 16; // size of free block pool
112 enum DP_ENTBUFSIZ = 128; // size of the entity buffer
113 enum DP_STKBUFSIZ = 256; // size of the stack key buffer
114 enum DP_WRTBUFSIZ = 8192; // size of the writing buffer
115 enum DP_FSBLKSIZ = 4096; // size of a block of the file system
116 enum DP_OPTBLOAD = 0.25; // ratio of bucket loading at optimization
117 enum DP_OPTRUNIT = 256; // number of records in a process of optimization
118 enum DP_NUMBUFSIZ = 32; // size of a buffer for a number
119 enum DP_IOBUFSIZ = 8192; // size of an I/O buffer
120 enum DP_TMPFSUF = ".~sdbmtemp"; // suffix of a temporary file
122 // enumeration for the flag of a record
123 enum {
124 DP_RECFDEL = 0x01, // deleted
125 DP_RECFREUSE = 0x02, // reusable
128 static align(1) struct SDBMHeader {
129 align(1):
130 char[12] signature; // DP_MAGICNUMB or DP_MAGICNUML, padded with '\0'
131 char[4] versionstr; // string, padded with '\0'
132 int flags;
133 uint unused0;
134 int filesize;
135 uint unused1;
136 int nbuckets; // number of buckets
137 uint unused2;
138 int nrecords; // number of records
139 uint unused3;
141 static assert(SDBMHeader.sizeof == 48);
142 static assert(SDBMHeader.signature.offsetof == 0);
143 static assert(SDBMHeader.versionstr.offsetof == 12);
144 static assert(SDBMHeader.flags.offsetof == 16);
145 static assert(SDBMHeader.filesize.offsetof == 24);
146 static assert(SDBMHeader.nbuckets.offsetof == 32);
147 static assert(SDBMHeader.nrecords.offsetof == 40);
149 static align(1) struct RecordHeader {
150 align(1):
151 int flags; // flags
152 int hash2; // value of the second hash function
153 int ksiz; // the size of the key
154 int vsiz; // the size of the value
155 int psiz; // the size of the padding bytes
156 int left; // the offset of the left child
157 int right; // the offset of the right child
159 /* Get the size of a record in a database file.
161 * Returns:
162 * The return value is the size of a record in a database file
164 @property int recsize () const @safe pure nothrow @nogc {
165 return cast(int)(RecordHeader.sizeof+ksiz+vsiz+psiz);
168 static assert(RecordHeader.sizeof == 7*int.sizeof);
169 static assert(RecordHeader.flags.offsetof == 0*int.sizeof);
170 static assert(RecordHeader.hash2.offsetof == 1*int.sizeof);
171 static assert(RecordHeader.ksiz.offsetof == 2*int.sizeof);
172 static assert(RecordHeader.vsiz.offsetof == 3*int.sizeof);
173 static assert(RecordHeader.psiz.offsetof == 4*int.sizeof);
174 static assert(RecordHeader.left.offsetof == 5*int.sizeof);
175 static assert(RecordHeader.right.offsetof == 6*int.sizeof);
177 public:
178 /// enumeration for error codes
179 enum Error {
180 NOERR, /// no error
181 FATAL, /// with fatal error
182 CLOSED, /// trying to operate on closed db
183 OPENED, /// trying to opend an already opened db
184 MODE, /// invalid mode
185 BROKEN, /// broken database file
186 KEEP, /// existing record
187 NOITEM, /// no item found
188 ALLOC, /// memory allocation error
189 MAP, /// memory mapping error
190 OPEN, /// open error
191 CLOSE, /// close error
192 TRUNC, /// trunc error
193 SYNC, /// sync error
194 STAT, /// stat error
195 SEEK, /// seek error
196 READ, /// read error
197 WRITE, /// write error
198 LOCK, /// lock error
199 UNLINK, /// unlink error
200 MKDIR, /// mkdir error
201 RMDIR, /// rmdir error
202 MISC, /// miscellaneous error
203 NOTFOUND, /// not found error
206 /// enumeration for open modes
207 enum {
208 READER = 1<<0, /// open as a reader
209 WRITER = 1<<1, /// open as a writer
210 CREAT = 1<<2, /// a writer creating
211 TRUNC = 1<<3, /// a writer truncating
212 NOLCK = 1<<4, /// open without locking
213 LCKNB = 1<<5, /// lock without blocking
216 /// enumeration for write modes
217 enum WMode {
218 OVER, /// overwrite an existing value
219 KEEP, /// keep an existing value
220 CAT, /// concatenate values
223 final:
224 public:
225 @property bool opened () const @safe pure nothrow @nogc { return (m_fd >= 0); }
227 void checkOpened (string file=__FILE__, usize line=__LINE__) {
228 if (m_fatal) raise(Error.FATAL, file, line);
229 if (!opened) raise(Error.CLOSED, file, line);
232 void checkWriting (string file=__FILE__, usize line=__LINE__) {
233 checkOpened(file, line);
234 if (!m_wmode) raise(Error.MODE, file, line);
237 /** Get a database handle.
239 * While connecting as a writer, an exclusive lock is invoked to the database file.
240 * While connecting as a reader, a shared lock is invoked to the database file. The thread
241 * blocks until the lock is achieved. If `NOLCK` is used, the application is responsible
242 * for exclusion control.
244 * Params:
245 * name = the name of a database file
246 * omode = specifies the connection mode: `WRITER` as a writer, `READER` as a reader.
247 * If the mode is `WRITER`, the following may be added by bitwise or:
248 * `CREAT`, which means it creates a new database if not exist,
249 * `TRUNC`, which means it creates a new database regardless if one exists.
250 * Both of `READER` and `WRITER` can be added to by bitwise or:
251 * `NOLCK`, which means it opens a database file without file locking, or
252 * `LCKNB`, which means locking is performed without blocking.
253 * bnum = the number of elements of the bucket array.
254 * If it is not more than 0, the default value is specified. The size of a bucket array is
255 * determined on creating, and can not be changed except for by optimization of the database.
256 * Suggested size of a bucket array is about from 0.5 to 4 times of the number of all records
257 * to store.
258 * errcode = the error code (can be `null`)
260 * Throws:
261 * SDBMException on various errors
263 void open (const(char)[] name, int omode=READER, int bnum=-1) {
264 import core.sys.posix.fcntl : open, O_CREAT, O_RDONLY, O_RDWR;
265 import core.sys.posix.sys.mman : mmap, munmap, MAP_FAILED, PROT_READ, PROT_WRITE, MAP_SHARED;
266 import core.sys.posix.sys.stat : fstat, lstat, S_ISREG, stat_t;
267 import core.sys.posix.unistd : close, ftruncate;
268 SDBMHeader hbuf;
269 char* map;
270 int mode, fd;
271 usize msiz;
272 ulong inode;
273 long fsiz;
274 int* fbpool;
275 stat_t sbuf;
276 time_t mtime;
277 if (opened) raise(Error.OPENED);
278 assert(name.length);
279 char[] namez; // unique
280 // add '\0' after string
282 usize len = 0;
283 while (len < name.length && name[len]) ++len;
284 namez = new char[](len+1);
285 namez[0..$-1] = name[0..len];
286 namez[$-1] = 0;
288 mode = O_RDONLY;
289 if (omode&WRITER) {
290 mode = O_RDWR;
291 if (omode&CREAT) mode |= O_CREAT;
293 if ((fd = open(namez.ptr, mode, DP_FILEMODE)) == -1) raise(Error.OPEN);
294 scope(failure) close(fd);
295 if ((omode&NOLCK) == 0) fdlock(fd, (omode&WRITER) != 0, (omode&LCKNB) != 0);
296 if ((omode&(WRITER|TRUNC)) == (WRITER|TRUNC)) {
297 if (ftruncate(fd, 0) == -1) raise(Error.TRUNC);
299 if (fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode) || (sbuf.st_ino == 0 && lstat(namez.ptr, &sbuf) == -1)) raise(Error.STAT);
300 inode = sbuf.st_ino;
301 mtime = sbuf.st_mtime;
302 fsiz = sbuf.st_size;
303 if ((omode&WRITER) && fsiz == 0) {
304 hbuf.signature[] = 0;
305 hbuf.versionstr[] = 0;
306 hbuf.signature[0..DP_MAGIC.length] = DP_MAGIC[];
307 hbuf.versionstr[] = SDBM_LIBVER[];
308 bnum = (bnum < 1 ? DP_DEFBNUM : bnum);
309 bnum = primenum(bnum);
310 hbuf.nbuckets = bnum;
311 hbuf.nrecords = 0;
312 fsiz = hbuf.sizeof+bnum*int.sizeof;
313 hbuf.filesize = cast(int)fsiz;
314 fdseekwrite(fd, 0, (&hbuf)[0..1]);
316 ubyte[DP_IOBUFSIZ] ebuf = 0; // totally empty buffer initialized with 0 %-)
317 usize pos = hbuf.sizeof;
318 while (pos < fsiz) {
319 usize left = cast(usize)fsiz-pos;
320 usize wr = (left > ebuf.length ? ebuf.length : left);
321 fdseekwrite(fd, pos, ebuf[0..wr]);
322 pos += wr;
326 try {
327 fdseekread(fd, 0, (&hbuf)[0..1]);
328 } catch (Exception) {
329 raise(Error.BROKEN);
331 if (hbuf.signature[0..DP_MAGIC.length] != DP_MAGIC) raise(Error.BROKEN);
332 if (hbuf.versionstr[] != SDBM_LIBVER) raise(Error.BROKEN);
333 if (hbuf.filesize != fsiz) raise(Error.BROKEN);
334 bnum = hbuf.nbuckets;
335 if (bnum < 1 || hbuf.nrecords < 0 || fsiz < SDBMHeader.sizeof+bnum*int.sizeof) raise(Error.BROKEN);
336 msiz = SDBMHeader.sizeof+bnum*int.sizeof;
337 map = cast(char*)mmap(null, msiz, PROT_READ|(mode&WRITER ? PROT_WRITE : 0), MAP_SHARED, fd, 0);
338 if (map == MAP_FAILED || map is null) raise(Error.MAP);
339 scope(failure) munmap(map, msiz);
340 fbpool = alloc!int(DP_FBPOOLSIZ*2);
342 import std.exception : assumeUnique;
343 m_name = namez[0..$-1].assumeUnique;
345 m_wmode = (mode&WRITER) != 0;
346 m_inode = inode;
347 m_mtime = mtime;
348 m_fd = fd;
349 m_fsiz = fsiz;
350 m_map = map;
351 m_msiz = cast(int)msiz;
352 m_buckets = cast(int*)(map+SDBMHeader.sizeof);
353 m_bnum = bnum;
354 m_rnum = hbuf.nrecords;
355 m_fatal = false;
356 m_ioff = 0;
357 m_fbpool = fbpool;
358 m_fbpool[0..DP_FBPOOLSIZ*2] = -1;
359 m_fbpsiz = DP_FBPOOLSIZ*2;
360 m_fbpinc = 0;
361 m_alignment = 0;
364 /** Close a database handle.
366 * Returns:
367 * If successful, the return value is true, else, it is false.
368 * Because the region of a closed handle is released, it becomes impossible to use the handle.
369 * Updating a database is assured to be written when the handle is closed. If a writer opens
370 * a database but does not close it appropriately, the database will be broken.
372 * Throws:
373 * SDBMException on various errors
375 void close () {
376 import core.sys.posix.sys.mman : munmap, MAP_FAILED;
377 import core.sys.posix.unistd : close;
378 if (!opened) return;
379 bool fatal = m_fatal;
380 Error err = Error.NOERR;
381 if (m_wmode) updateHeader();
382 if (m_map != MAP_FAILED && m_map !is null) {
383 if (munmap(m_map, m_msiz) == -1) err = Error.MAP;
385 m_map = null;
386 if (close(m_fd) == -1) err = Error.CLOSE;
387 freeptr(m_fbpool);
388 m_name = null;
389 m_fd = -1;
390 m_wmode = false;
391 if (fatal) err = Error.FATAL;
392 if (err != Error.NOERR) raise(err);
395 /** Retrieve a record.
397 * Params:
398 * kbuf = the pointer to the region of a key
399 * start = the offset address of the beginning of the region of the value to be read
400 * max = specifies the max size to be read; if it is `uint.max`, the size to read is unlimited
401 * sp = the pointer to a variable to which the size of the region of the return
402 * value is assigned; if it is `null`, it is not used
404 * Returns:
405 * If successful, the return value is the GC-allocated slice of the region of the value of the
406 * corresponding record, else, it is `null`. `null` is returned when no record corresponds to
407 * the specified key or the size of the value of the corresponding record is less than `start`.
408 * No additional zero code is appended at the end of the region of the return value.
410 * Throws:
411 * SDBMException on various errors
413 T[] getA(T=char) (const(void)[] kbuf, uint start=0, uint max=uint.max, usize* sp=null) if (!isArray!T) {
414 RecordHeader head;
415 int bi, off, entoff;
416 bool ee;
417 usize vsiz;
418 char[DP_ENTBUFSIZ] ebuf;
419 ubyte[] vbuf = null;
420 if (sp !is null) *sp = 0;
421 checkOpened();
422 if (!recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee)) return null; //raise(Error.NOITEM);
423 if (start >= head.vsiz) return null; //raise(Error.NOITEM);
424 scope(failure) m_fatal = true; // any failure beyond this point is fatal
425 if (ee && RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
426 import core.stdc.string : memcpy;
427 head.vsiz -= start;
428 if (max == uint.max) {
429 vsiz = head.vsiz;
430 } else {
431 vsiz = (max < head.vsiz ? max : head.vsiz);
433 if (vsiz != 0) {
434 vbuf.length = vsiz;
435 memcpy(vbuf.ptr, ebuf.ptr+(RecordHeader.sizeof+head.ksiz+start), vsiz);
436 } else {
437 vbuf = new ubyte[](1);
438 vbuf.length = 0;
440 } else {
441 vbuf = recReadValue!ubyte(off, head, start, max);
443 if (sp !is null) {
444 if (max == uint.max) {
445 *sp = head.vsiz;
446 } else {
447 *sp = (max < head.vsiz ? max : head.vsiz);
450 immutable len = vbuf.length/T.sizeof;
451 return cast(T[])(vbuf[0..len*T.sizeof]);
452 //return cast(void[])vbuf;
455 /** Retrieve a record and write the value into a buffer.
457 * Params:
458 * vbuf = the pointer to a buffer into which the value of the corresponding record is written
459 * kbuf = the pointer to the region of a key
460 * start = the offset address of the beginning of the region of the value to be read
462 * Returns:
463 * If successful, the return value is the read data (slice of vbuf), else, it is `null`.
464 * `null` returned when no record corresponds to the specified key or the size of the value
465 * of the corresponding record is less than `start`.
466 * Note that no additional zero code is appended at the end of the region of the writing buffer.
468 * Throws:
469 * SDBMException on various errors
471 T[] getToBuf(T=char) (T[] vbuff, const(void)[] kbuf, uint start=0) if (!isArray!T) {
472 RecordHeader head;
473 int bi, off, entoff;
474 bool ee;
475 usize vsiz;
476 char[DP_ENTBUFSIZ] ebuf;
477 checkOpened();
478 if (!recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee)) return null; //raise(Error.NOITEM);
479 if (start > head.vsiz) return null; //raise(Error.NOITEM);
480 scope(failure) m_fatal = true; // any failure beyond this point is fatal
481 auto vbuf = cast(ubyte[])vbuff;
482 if (ee && RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
483 import core.stdc.string : memcpy;
484 head.vsiz -= start;
485 vsiz = (vbuf.length < head.vsiz ? vbuf.length : head.vsiz);
486 memcpy(vbuf.ptr, ebuf.ptr+(RecordHeader.sizeof+head.ksiz+start), vsiz);
487 } else {
488 vbuf = recReadValueToBuf(vbuf, off, head, start);
489 vsiz = vbuf.length;
491 immutable len = vsiz/T.sizeof;
492 return cast(T[])(vbuf[0..len*T.sizeof]);
493 //return vbuf[0..vsiz];
496 // `mode`: "throw", "nothrow"
497 T get(T, string mode="throw") (const(void)[] kbuf) if (!isArray!T) {
498 static assert(mode == "throw" || mode == "nothrow", "invalid mode");
499 T[1] data;
500 auto res = getToBuf(cast(ubyte[])(data[]), kbuf);
501 static if (mode == "throw") {
502 if (res.length != T.sizeof) raise(Error.NOTFOUND);
503 } else {
504 if (res.length != T.sizeof) data[0] = T.init;
506 return data[0];
509 // `mode`: "throw", "nothrow"
510 bool get(T, string mode="nothrow") (const(void)[] kbuf, ref T dval) if (!isArray!T) {
511 static assert(mode == "throw" || mode == "nothrow", "invalid mode");
512 auto res = getToBuf(cast(ubyte[])((&dval)[0..1]), kbuf);
513 static if (mode == "throw") {
514 if (res.length != T.sizeof) raise(Error.NOTFOUND);
515 } else {
516 if (res.length != T.sizeof) dval = T.init;
518 return (res.length == T.sizeof);
521 /** Get the size of the value of a record.
523 * Params:
524 * kbuf = the pointer to the region of a key
526 * Returns:
527 * If successful, the return value is the size of the value of the corresponding record, else, it is -1.
528 * Because this function does not read the entity of a record, it is faster than `get`.
530 * Throws:
531 * SDBMException on various errors
533 int vsize (const(void)[] kbuf) {
534 RecordHeader head;
535 int bi, off, entoff;
536 bool ee;
537 char[DP_ENTBUFSIZ] ebuf;
538 checkOpened();
539 if (!recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee)) return -1; //raise(Error.NOITEM);
540 return head.vsiz;
543 /** Check if a record is existing.
545 * Params:
546 * kbuf = the pointer to the region of a key
548 * Returns:
549 * `true` if a record is in database, `false` otherwise.
551 * Throws:
552 * SDBMException on various errors
554 bool exists (const(void)[] kbuf) {
555 RecordHeader head;
556 int bi, off, entoff;
557 bool ee;
558 char[DP_ENTBUFSIZ] ebuf;
559 checkOpened();
560 if (!recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee)) return false;
561 return true;
564 /** Store a record.
566 * Params:
567 * kbuf = the pointer to the region of a key
568 * vbuf = the pointer to the region of a value
569 * dmode = behavior when the key overlaps, by the following values:
570 * `WMode.OVER`, which means the specified value overwrites the existing one,
571 * `WMode.KEEP`, which means the existing value is kept,
572 * `WMode.CAT`, which means the specified value is concatenated at the end of the existing value.
574 * Throws:
575 * SDBMException on various errors
577 void put (const(void)[] kbuf, const(void)[] vbuf, WMode dmode=WMode.OVER) {
578 RecordHeader head, next;
579 int hash, bi, off, entoff, newoff, fdel, mroff, mrsiz, mi, min;
580 usize rsiz, nsiz;
581 bool ee;
582 char[DP_ENTBUFSIZ] ebuf;
583 checkWriting();
584 newoff = -1;
585 if (recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee, Yes.delhit, &hash)) {
586 // record found
587 fdel = head.flags&DP_RECFDEL;
588 if (dmode == WMode.KEEP && !fdel) raise(Error.KEEP);
589 if (fdel) {
590 head.psiz += head.vsiz;
591 head.vsiz = 0;
593 rsiz = head.recsize;
594 nsiz = RecordHeader.sizeof+kbuf.length+vbuf.length;
595 if (dmode == WMode.CAT) nsiz += head.vsiz;
596 if (off+rsiz >= m_fsiz) {
597 if (rsiz < nsiz) {
598 head.psiz += nsiz-rsiz;
599 rsiz = nsiz;
600 m_fsiz = off+rsiz;
602 } else {
603 while (nsiz > rsiz && off+rsiz < m_fsiz) {
604 rechead(off+rsiz, next, null, null);
605 if ((next.flags&DP_RECFREUSE) == 0) break;
606 head.psiz += next.recsize;
607 rsiz += next.recsize;
609 for (uint i = 0; i < m_fbpsiz; i += 2) {
610 if (m_fbpool[i] >= off && m_fbpool[i] < off+rsiz) {
611 m_fbpool[i] = m_fbpool[i+1] = -1;
615 if (nsiz <= rsiz) {
616 recover(off, head, vbuf, (dmode == WMode.CAT ? Yes.catmode : No.catmode));
617 } else {
618 char[] tval = null;
619 scope(failure) m_fatal = true;
620 if (dmode == WMode.CAT) {
621 import core.stdc.string : memcpy;
622 if (ee && RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
623 tval.length = head.vsiz+vbuf.length;
624 if (head.vsiz > 0) memcpy(tval.ptr, ebuf.ptr+(RecordHeader.sizeof+head.ksiz), head.vsiz);
625 } else {
626 tval = recReadValue(off, head);
627 tval.length = head.vsiz+vbuf.length;
629 if (vbuf.length) memcpy(tval.ptr+head.vsiz, vbuf.ptr, vbuf.length);
630 immutable newsize = head.vsiz+vbuf.length;
631 vbuf = tval[0..newsize];
633 mi = -1;
634 min = -1;
635 for (uint i = 0; i < m_fbpsiz; i += 2) {
636 if (m_fbpool[i+1] < nsiz) continue;
637 if (mi == -1 || m_fbpool[i+1] < min) {
638 mi = i;
639 min = m_fbpool[i+1];
642 if (mi >= 0) {
643 mroff = m_fbpool[mi];
644 mrsiz = m_fbpool[mi+1];
645 m_fbpool[mi] = -1;
646 m_fbpool[mi+1] = -1;
647 } else {
648 mroff = -1;
649 mrsiz = -1;
651 recdelete(off, head, Yes.reusable);
652 if (mroff > 0 && nsiz <= mrsiz) {
653 recrewrite(mroff, mrsiz, kbuf, vbuf, hash, head.left, head.right);
654 newoff = mroff;
655 } else {
656 newoff = recappend(kbuf, vbuf, hash, head.left, head.right);
659 if (fdel) ++m_rnum;
660 } else {
661 // no such record
662 scope(failure) m_fatal = true;
663 newoff = recappend(kbuf, vbuf, hash, 0, 0);
664 ++m_rnum;
666 if (newoff > 0) {
667 if (entoff > 0) {
668 fdseekwritenum(m_fd, entoff, newoff);
669 } else {
670 m_buckets[bi] = newoff;
675 // `mode`: "throw", "nothrow"
676 void put(T) (const(void)[] kbuf, in T val, WMode dmode=WMode.OVER) if (!isArray!T) {
677 put(kbuf, (&val)[0..1], dmode);
680 /** Delete a record.
682 * Params:
683 * kbuf = the pointer to the region of a key
685 * Returns:
686 * If successful, the return value is true, else, it is false.
687 * False is returned when no record corresponds to the specified key.
689 * Throws:
690 * SDBMException on various errors
692 bool del (const(void)[] kbuf) {
693 RecordHeader head;
694 int bi, off, entoff;
695 bool ee;
696 char[DP_ENTBUFSIZ] ebuf;
697 checkWriting();
698 if (!recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee)) return false; //raise(Error.NOITEM);
699 recdelete(off, head, No.reusable);
700 --m_rnum;
701 return true;
704 /** Initialize the iterator of a database handle.
706 * Returns:
707 * If successful, the return value is true, else, it is false.
708 * The iterator is used in order to access the key of every record stored in a database.
710 * Throws:
711 * SDBMException on various errors
713 void itInit () {
714 checkOpened();
715 m_ioff = 0;
718 /** Get the next key of the iterator.
720 * Params:
721 * sp = the pointer to a variable to which the size of the region of the return value is assigned.
722 * If it is `null`, it is not used.
724 * Returns:
725 * If successful, the return value is the pointer to the GC-allocated region of the next key,
726 * else, it is `null`. `null` is returned when no record is to be get out of the iterator.
727 * No additional zero code is appended at the end of the region of the return value.
728 * It is possible to access every record by iteration of calling this function. However,
729 * it is not assured if updating the database is occurred while the iteration. Besides,
730 * the order of this traversal access method is arbitrary, so it is not assured that the
731 * order of storing matches the one of the traversal access.
733 * Throws:
734 * SDBMException on various errors
736 T[] itNext(T=char) (usize* sp=null) if (T.sizeof == 1) {
737 RecordHeader head;
738 usize off;
739 bool ee;
740 char[DP_ENTBUFSIZ] ebuf;
741 char[] kbuf;
742 if (sp !is null) *sp = 0;
743 checkOpened();
744 off = SDBMHeader.sizeof+m_bnum*int.sizeof;
745 off = (off > m_ioff ? off : m_ioff);
746 scope(failure) m_fatal = true; // any failure is fatal here
747 while (off < m_fsiz) {
748 rechead(off, head, ebuf[], &ee);
749 if (head.flags&DP_RECFDEL) {
750 off += head.recsize;
751 } else {
752 if (ee && RecordHeader.sizeof+head.ksiz <= DP_ENTBUFSIZ) {
753 import core.stdc.string : memcpy;
754 if (head.ksiz > 0) {
755 kbuf.length = head.ksiz;
756 memcpy(kbuf.ptr, ebuf.ptr+RecordHeader.sizeof, head.ksiz);
758 } else {
759 kbuf = recReadKey(off, head);
761 m_ioff = cast(int)(off+head.recsize);
762 if (sp !is null) *sp = head.ksiz;
763 return kbuf;
766 //raise(Error.NOITEM);
767 return null;
770 /** Set alignment of a database handle.
772 * If alignment is set to a database, the efficiency of overwriting values is improved.
773 * The size of alignment is suggested to be average size of the values of the records to be
774 * stored. If alignment is positive, padding whose size is multiple number of the alignment
775 * is placed. If alignment is negative, as `vsiz` is the size of a value, the size of padding
776 * is calculated with `(vsiz/pow(2, abs(alignment)-1))'. Because alignment setting is not
777 * saved in a database, you should specify alignment every opening a database.
779 * Params:
780 * alignment = the size of alignment
782 * Throws:
783 * SDBMException on various errors
785 @property void alignment (int alignment) {
786 checkWriting();
787 m_alignment = alignment;
790 /** Get alignment of a database handle.
792 * Returns:
793 * The size of alignment
795 * Throws:
796 * SDBMException on various errors
798 @property int alignment () {
799 checkOpened();
800 return m_alignment;
803 /** Set the size of the free block pool of a database handle.
805 * The default size of the free block pool is 16. If the size is greater, the space efficiency
806 * of overwriting values is improved with the time efficiency sacrificed.
808 * Params:
809 * size = the size of the free block pool of a database
811 * Throws:
812 * SDBMException on various errors
814 @property void freeBlockPoolSize (uint size) {
815 import core.stdc.stdlib : realloc;
816 checkWriting();
817 if (size > 0x3fff_ffffu) raise(Error.ALLOC);
818 size *= 2;
819 auto fbpool = cast(int*)realloc(m_fbpool, size*int.sizeof+(size ? 0 : 1));
820 if (fbpool is null) raise(Error.ALLOC);
821 fbpool[0..size] = -1;
822 m_fbpool = fbpool;
823 m_fbpsiz = size;
826 /** Get the size of the free block pool of a database handle.
828 * Returns:
829 * The size of the free block pool of a database
831 * Throws:
832 * SDBMException on various errors
834 @property uint freeBlockPoolSize () {
835 checkOpened();
836 return m_fbpsiz/2;
839 /** Synchronize updating contents with the file and the device.
841 * This function is useful when another process uses the connected database file.
843 * Throws:
844 * SDBMException on various errors
846 void sync () {
847 import core.sys.posix.sys.mman : msync, MS_SYNC;
848 import core.sys.posix.unistd : fsync;
849 checkWriting();
850 updateHeader();
851 if (msync(m_map, m_msiz, MS_SYNC) == -1) {
852 m_fatal = true;
853 raise(Error.MAP);
855 if (fsync(m_fd) == -1) {
856 m_fatal = true;
857 raise(Error.SYNC);
861 /** Optimize a database.
863 * In an alternating succession of deleting and storing with overwrite or concatenate,
864 * dispensable regions accumulate. This function is useful to do away with them.
866 * Params:
867 * bnum = the number of the elements of the bucket array. If it is not more than 0,
868 * the default value is specified
870 * Throws:
871 * SDBMException on various errors
873 void optimize (int bnum=-1) {
874 import core.sys.posix.sys.mman : mmap, munmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE;
875 import core.sys.posix.unistd : ftruncate, unlink;
876 SDBM tdepot;
877 RecordHeader head;
878 usize off;
879 int unum;
880 bool ee;
881 int[DP_OPTRUNIT] ksizs, vsizs;
882 char[DP_ENTBUFSIZ] ebuf;
883 char[][DP_OPTRUNIT] kbufs, vbufs;
884 checkWriting();
885 if (bnum < 0) {
886 bnum = cast(int)(m_rnum*(1.0/DP_OPTBLOAD))+1;
887 if (bnum < DP_DEFBNUM/2) bnum = DP_DEFBNUM/2;
889 tdepot = new SDBM(m_name~DP_TMPFSUF, WRITER|CREAT|TRUNC, bnum);
890 scope(failure) {
891 import std.exception : collectException;
892 m_fatal = true;
893 unlink(tdepot.m_name.ptr);
894 collectException(tdepot.close());
896 scope(exit) delete tdepot;
897 tdepot.flags = flags;
898 tdepot.m_alignment = m_alignment;
899 off = SDBMHeader.sizeof+m_bnum*int.sizeof;
900 unum = 0;
901 while (off < m_fsiz) {
902 rechead(off, head, ebuf[], &ee);
903 if ((head.flags&DP_RECFDEL) == 0) {
904 if (ee && RecordHeader.sizeof+head.ksiz <= DP_ENTBUFSIZ) {
905 import core.stdc.string : memcpy;
906 kbufs[unum].length = head.ksiz;
907 if (head.ksiz > 0) memcpy(kbufs[unum].ptr, ebuf.ptr+RecordHeader.sizeof, head.ksiz);
908 if (RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
909 vbufs[unum].length = head.vsiz;
910 if (head.vsiz > 0) memcpy(vbufs[unum].ptr, ebuf.ptr+(RecordHeader.sizeof+head.ksiz), head.vsiz);
911 } else {
912 vbufs[unum] = recReadValue(off, head);
914 } else {
915 kbufs[unum] = recReadKey(off, head);
916 vbufs[unum] = recReadValue(off, head);
918 ksizs[unum] = head.ksiz;
919 vsizs[unum] = head.vsiz;
920 ++unum;
921 if (unum >= DP_OPTRUNIT) {
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 kbufs[i].length = 0;
926 vbufs[i].length = 0;
928 unum = 0;
931 off += head.recsize;
933 for (uint i = 0; i < unum; ++i) {
934 assert(kbufs[i] !is null && vbufs[i] !is null);
935 tdepot.put(kbufs[i][0..ksizs[i]], vbufs[i][0..vsizs[i]], WMode.KEEP);
936 kbufs[i].length = 0;
937 vbufs[i].length = 0;
939 tdepot.sync();
940 if (munmap(m_map, m_msiz) == -1) raise(Error.MAP);
941 m_map = cast(char*)MAP_FAILED;
942 if (ftruncate(m_fd, 0) == -1) raise(Error.TRUNC);
943 fcopy(m_fd, 0, tdepot.m_fd, 0);
944 m_fsiz = tdepot.m_fsiz;
945 m_bnum = tdepot.m_bnum;
946 m_ioff = 0;
947 for (uint i = 0; i < m_fbpsiz; i += 2) {
948 m_fbpool[i] = m_fbpool[i+1] = -1;
950 m_msiz = tdepot.m_msiz;
951 m_map = cast(char*)mmap(null, m_msiz, PROT_READ|PROT_WRITE, MAP_SHARED, m_fd, 0);
952 if (m_map == MAP_FAILED) raise(Error.MAP);
953 m_buckets = cast(int*)(m_map+SDBMHeader.sizeof);
954 string tempname = tdepot.m_name; // with trailing zero
955 tdepot.close();
956 if (unlink(tempname.ptr) == -1) raise(Error.UNLINK);
959 /** Get the name of a database.
961 * Returns:
962 * If successful, the return value is the pointer to the region of the name of the database,
963 * else, it is `null`.
965 * Throws:
966 * nothing
968 @property string name () const @safe pure nothrow @nogc {
969 return m_name;
972 /** Get the size of a database file.
974 * Returns:
975 * If successful, the return value is the size of the database file, else, it is -1.
977 * Throws:
978 * SDBMException on various errors
980 @property long fileSize () {
981 checkOpened();
982 return m_fsiz;
986 /** Get the number of the elements of the bucket array.
988 * Returns:
989 * If successful, the return value is the number of the elements of the bucket array, else, it is -1.
991 * Throws:
992 * SDBMException on various errors
994 @property int bucketCount () {
995 checkOpened();
996 return m_bnum;
999 /** Get the number of the used elements of the bucket array.
1001 * This function is inefficient because it accesses all elements of the bucket array.
1003 * Returns:
1004 * If successful, the return value is the number of the used elements of the bucket array, else, it is -1.
1006 * Throws:
1007 * SDBMException on various errors
1009 int bucketUsed () {
1010 checkOpened();
1011 int hits = 0;
1012 foreach (immutable idx; 0..m_bnum) if (m_buckets[idx]) ++hits;
1013 return hits;
1016 /** Get the number of the records stored in a database.
1018 * Returns:
1019 * If successful, the return value is the number of the records stored in the database, else, it is -1.
1021 * Throws:
1022 * SDBMException on various errors
1024 @property int recordCount () {
1025 checkOpened();
1026 return m_rnum;
1029 /** Check whether a database handle is a writer or not.
1031 * Returns:
1032 * The return value is true if the handle is a writer, false if not.
1034 @property bool writable () const @safe pure nothrow @nogc {
1035 return (opened && m_wmode);
1038 /** Check whether a database has a fatal error or not.
1040 * Returns:
1041 * The return value is true if the database has a fatal error, false if not.
1043 @property bool fatalError () const @safe pure nothrow @nogc {
1044 return m_fatal;
1047 /** Get the inode number of a database file.
1049 * Returns:
1050 * The return value is the inode number of the database file.
1052 * Throws:
1053 * SDBMException on various errors
1055 @property long inode () {
1056 checkOpened();
1057 return m_inode;
1060 /** Get the last modified time of a database.
1062 * Returns:
1063 * The return value is the last modified time of the database.
1065 * Throws:
1066 * SDBMException on various errors
1068 @property time_t mtime () {
1069 checkOpened();
1070 return m_mtime;
1073 /** Get the file descriptor of a database file.
1075 * Returns:
1076 * The return value is the file descriptor of the database file.
1077 * Handling the file descriptor of a database file directly is not suggested.
1079 * Throws:
1080 * SDBMException on various errors
1082 @property int fdesc () {
1083 checkOpened();
1084 return m_fd;
1087 /* ********************************************************************************************* *
1088 * features for experts
1089 * ********************************************************************************************* */
1091 /** Synchronize updating contents on memory.
1093 * Throws:
1094 * SDBMException on various errors
1096 void memsync () {
1097 import core.sys.posix.sys.mman : msync, MS_SYNC;
1098 checkWriting();
1099 updateHeader();
1100 if (msync(m_map, m_msiz, MS_SYNC) == -1) {
1101 m_fatal = true;
1102 raise(Error.MAP);
1106 /** Synchronize updating contents on memory, not physically.
1108 * Throws:
1109 * SDBMException on various errors
1111 void memflush () {
1112 checkWriting();
1113 updateHeader();
1114 // there is no mflush() call
1115 version(none) {
1116 if (mflush(m_map, m_msiz, MS_SYNC) == -1) {
1117 m_fatal = true;
1118 raise(Error.MAP);
1123 /** Get flags of a database.
1125 * Returns:
1126 * The return value is the flags of a database.
1128 * Throws:
1129 * SDBMException on various errors
1131 @property int flags () {
1132 checkOpened();
1133 auto hdr = cast(SDBMHeader*)m_map;
1134 return hdr.flags;
1137 /** Set flags of a database.
1139 * Params:
1140 * flags = flags to set. Least ten bits are reserved for internal use.
1142 * Returns:
1143 * If successful, the return value is true, else, it is false.
1145 * Throws:
1146 * SDBMException on various errors
1148 @property void flags (int v) {
1149 checkWriting();
1150 auto hdr = cast(SDBMHeader*)m_map;
1151 hdr.flags = v;
1154 private:
1155 /* ********************************************************************************************* *
1156 * private objects
1157 * ********************************************************************************************* */
1159 void raise (Error errcode, string file=__FILE__, usize line=__LINE__) {
1160 assert(errcode >= Error.NOERR);
1161 if (errcode == Error.FATAL) m_fatal = true;
1162 throw new SDBMException(errcode, file, line);
1165 static void straise (Error errcode, string file=__FILE__, usize line=__LINE__) {
1166 assert(errcode >= Error.NOERR);
1167 throw new SDBMException(errcode, file, line);
1170 void updateHeader () @trusted nothrow @nogc {
1171 auto hdr = cast(SDBMHeader*)m_map;
1172 hdr.filesize = cast(int)m_fsiz;
1173 hdr.nrecords = m_rnum;
1176 /* Lock a file descriptor.
1178 * Params:
1179 * fd = a file descriptor
1180 * ex = whether an exclusive lock or a shared lock is performed
1181 * nb = whether to request with non-blocking
1182 * errcode = the error code (can be `null`)
1184 * Throws:
1185 * SDBMException on various errors
1187 static void fdlock (int fd, bool ex, bool nb) {
1188 import core.stdc.stdio : SEEK_SET;
1189 import core.stdc.string : memset;
1190 import core.sys.posix.fcntl : flock, fcntl, F_RDLCK, F_SETLK, F_SETLKW, F_WRLCK;
1191 flock lock;
1192 assert(fd >= 0);
1193 memset(&lock, 0, lock.sizeof);
1194 lock.l_type = (ex ? F_WRLCK : F_RDLCK);
1195 lock.l_whence = SEEK_SET;
1196 lock.l_start = 0;
1197 lock.l_len = 0;
1198 lock.l_pid = 0;
1199 while (fcntl(fd, (nb ? F_SETLK : F_SETLKW), &lock) == -1) {
1200 import core.stdc.errno : errno, EINTR;
1201 if (errno != EINTR) straise(Error.LOCK);
1205 /* Write into a file.
1207 * Params:
1208 * fd = a file descriptor
1209 * buf = a buffer to write
1211 * Returns:
1212 * The return value is the size of the written buffer, or -1 on failure
1214 * Throws:
1215 * Nothing
1217 static int fdwrite (int fd, const(void)[] buf) @trusted nothrow @nogc {
1218 auto lbuf = cast(const(ubyte)[])buf;
1219 int rv = 0;
1220 assert(fd >= 0);
1221 while (lbuf.length > 0) {
1222 import core.sys.posix.unistd : write;
1223 auto wb = write(fd, lbuf.ptr, lbuf.length);
1224 if (wb == -1) {
1225 import core.stdc.errno : errno, EINTR;
1226 if (errno != EINTR) return -1;
1227 continue;
1229 if (wb == 0) break;
1230 lbuf = lbuf[wb..$];
1231 rv += cast(int)wb;
1233 return rv;
1236 /* Write into a file at an offset.
1238 * Params:
1239 * fd = a file descriptor
1240 * off = an offset of the file
1241 * buf = a buffer to write
1243 * Throws:
1244 * SDBMException on various errors
1246 static void fdseekwrite (int fd, long off, const(void)[] buf) {
1247 import core.stdc.stdio : SEEK_END, SEEK_SET;
1248 import core.sys.posix.unistd : lseek;
1249 assert(fd >= 0);
1250 if (buf.length < 1) return;
1251 if (lseek(fd, (off < 0 ? 0 : off), (off < 0 ? SEEK_END : SEEK_SET)) == -1) straise(Error.SEEK);
1252 if (fdwrite(fd, buf) != buf.length) straise(Error.WRITE);
1255 /* Write an integer into a file at an offset.
1257 * Params:
1258 * fd = a file descriptor
1259 * off = an offset of the file
1260 * num = an integer
1262 * Throws:
1263 * SDBMException on various errors
1265 void fdseekwritenum (int fd, long off, int num) {
1266 assert(fd >= 0);
1267 scope(failure) m_fatal = true;
1268 fdseekwrite(fd, off, (&num)[0..1]);
1271 /* Read from a file and store the data into a buffer.
1273 * Params:
1274 * fd = a file descriptor
1275 * buf = a buffer to store into.
1277 * Returns:
1278 * The return value is the size read with, or -1 on failure
1280 * Throws:
1281 * Nothing
1283 static int fdread (int fd, void[] buf) @trusted nothrow @nogc {
1284 import core.sys.posix.unistd : read;
1285 auto lbuf = cast(ubyte[])buf;
1286 int total = 0;
1287 assert(fd >= 0);
1288 while (lbuf.length > 0) {
1289 auto bs = read(fd, lbuf.ptr, lbuf.length);
1290 if (bs < 0) {
1291 import core.stdc.errno : errno, EINTR;
1292 if (errno != EINTR) return -1;
1293 continue;
1295 if (bs == 0) break;
1296 lbuf = lbuf[bs..$];
1297 total += cast(int)bs;
1299 return total;
1302 /* Read from a file at an offset and store the data into a buffer.
1304 * Params:
1305 * fd = a file descriptor
1306 * off = an offset of the file
1307 * buf = a buffer to store into
1309 * Throws:
1310 * SDBMException on various errors
1312 static void fdseekread (int fd, long off, void[] buf) {
1313 import core.stdc.stdio : SEEK_SET;
1314 import core.sys.posix.unistd : lseek;
1315 assert(fd >= 0 && off >= 0);
1316 if (lseek(fd, off, SEEK_SET) != off) straise(Error.SEEK);
1317 if (fdread(fd, buf) != buf.length) straise(Error.READ);
1320 /* Copy data between files.
1322 * Params:
1323 * destfd = a file descriptor of a destination file
1324 * destoff = an offset of the destination file
1325 * srcfd = a file descriptor of a source file
1326 * srcoff = an offset of the source file
1328 * Returns:
1329 * The return value is the size copied with
1331 * Throws:
1332 * SDBMException on various errors
1334 static int fcopy (int destfd, long destoff, int srcfd, long srcoff) {
1335 import core.stdc.stdio : SEEK_SET;
1336 import core.sys.posix.unistd : lseek;
1337 char[DP_IOBUFSIZ] iobuf;
1338 int sum, iosiz;
1339 if (lseek(srcfd, srcoff, SEEK_SET) == -1 || lseek(destfd, destoff, SEEK_SET) == -1) straise(Error.SEEK);
1340 sum = 0;
1341 while ((iosiz = fdread(srcfd, iobuf[])) > 0) {
1342 if (fdwrite(destfd, iobuf[0..iosiz]) != iosiz) straise(Error.WRITE);
1343 sum += iosiz;
1345 if (iosiz < 0) straise(Error.READ);
1346 return sum;
1349 /* Get the padding size of a record.
1351 * Params:
1352 * ksiz = the size of the key of a record
1353 * vsiz = the size of the value of a record
1355 * Returns:
1356 * The return value is the padding size of a record
1358 usize padsize (usize ksiz, usize vsiz) const @safe pure nothrow @nogc {
1359 if (m_alignment > 0) {
1360 return cast(usize)(m_alignment-(m_fsiz+RecordHeader.sizeof+ksiz+vsiz)%m_alignment);
1361 } else if (m_alignment < 0) {
1362 usize pad = cast(usize)(vsiz*(2.0/(1<<(-m_alignment))));
1363 if (vsiz+pad >= DP_FSBLKSIZ) {
1364 if (vsiz <= DP_FSBLKSIZ) pad = 0;
1365 if (m_fsiz%DP_FSBLKSIZ == 0) {
1366 return cast(usize)((pad/DP_FSBLKSIZ)*DP_FSBLKSIZ+DP_FSBLKSIZ-(m_fsiz+RecordHeader.sizeof+ksiz+vsiz)%DP_FSBLKSIZ);
1367 } else {
1368 return cast(usize)((pad/(DP_FSBLKSIZ/2))*(DP_FSBLKSIZ/2)+(DP_FSBLKSIZ/2)-
1369 (m_fsiz+RecordHeader.sizeof+ksiz+vsiz)%(DP_FSBLKSIZ/2));
1371 } else {
1372 return (pad >= RecordHeader.sizeof ? pad : RecordHeader.sizeof);
1375 return 0;
1378 /* Read the header of a record.
1380 * Params:
1381 * off = an offset of the database file
1382 * head = specifies a buffer for the header
1383 * ebuf = specifies the pointer to the entity buffer
1384 * eep = the pointer to a variable to which whether ebuf was used is assigned
1386 * Throws:
1387 * SDBMException on various errors
1389 void rechead (long off, ref RecordHeader head, void[] ebuf, bool* eep) {
1390 assert(off >= 0);
1391 if (eep !is null) *eep = false;
1392 if (off < 0 || off > m_fsiz) raise(Error.BROKEN);
1393 scope(failure) m_fatal = true; // any failure is fatal here
1394 if (ebuf.length >= DP_ENTBUFSIZ && off < m_fsiz-DP_ENTBUFSIZ) {
1395 import core.stdc.string : memcpy;
1396 if (eep !is null) *eep = true;
1397 fdseekread(m_fd, off, ebuf[0..DP_ENTBUFSIZ]);
1398 memcpy(&head, ebuf.ptr, RecordHeader.sizeof);
1399 } else {
1400 fdseekread(m_fd, off, (&head)[0..1]);
1402 if (head.ksiz < 0 || head.vsiz < 0 || head.psiz < 0 || head.left < 0 || head.right < 0) raise(Error.BROKEN);
1405 /* Read the entitiy of the key of a record.
1407 * Params:
1408 * off = an offset of the database file
1409 * head = the header of a record
1411 * Returns:
1412 * The return value is a key data whose region is allocated by GC
1414 * Throws:
1415 * SDBMException on various errors
1417 T[] recReadKey(T=char) (long off, ref in RecordHeader head) if (T.sizeof == 1) {
1418 T[] kbuf;
1419 assert(off >= 0);
1420 int ksiz = head.ksiz;
1421 if (ksiz > 0) {
1422 kbuf.length = ksiz;
1423 fdseekread(m_fd, off+RecordHeader.sizeof, kbuf[0..ksiz]);
1424 } else {
1425 kbuf = new T[](1);
1426 kbuf.length = 0;
1428 return kbuf;
1431 /* Read the entitiy of the value of a record.
1433 * Params:
1434 * off = an offset of the database file
1435 * head = the header of a record
1436 * start = the offset address of the beginning of the region of the value to be read
1437 * max = the max size to be read; if it is `uint.max`, the size to read is unlimited
1439 * Returns:
1440 * The return value is a value data whose region is allocated with GC.
1442 * Throws:
1443 * SDBMException on various errors
1445 T[] recReadValue(T=char) (long off, ref RecordHeader head, uint start=0, uint max=uint.max) if (T.sizeof == 1) {
1446 T[] vbuf;
1447 uint vsiz;
1448 assert(off >= 0);
1449 head.vsiz -= start;
1450 if (max == uint.max) {
1451 vsiz = head.vsiz;
1452 } else {
1453 vsiz = (max < head.vsiz ? max : head.vsiz);
1455 scope(failure) m_fatal = true;
1456 if (vsiz > 0) {
1457 vbuf.length = vsiz;
1458 fdseekread(m_fd, off+RecordHeader.sizeof+head.ksiz+start, vbuf[0..vsiz]);
1459 } else {
1460 vbuf = new T[](1);
1461 vbuf.length = 0;
1463 return vbuf;
1466 /* Read the entitiy of the value of a record and write it into a given buffer.
1468 * Params:
1469 * off = an offset of the database file
1470 * head = the header of a record
1471 * start = the offset address of the beginning of the region of the value to be read
1472 * vbuf = the pointer to a buffer into which the value of the corresponding record is written
1474 * Returns:
1475 * Read data (can be less then vbuf)
1477 * Throws:
1478 * SDBMException on various errors
1480 T[] recReadValueToBuf(T=char) (T[] vbuf, long off, ref RecordHeader head, uint start=0) if (T.sizeof == 1) {
1481 assert(off >= 0);
1482 head.vsiz -= start;
1483 usize vsiz = (vbuf.length < head.vsiz ? vbuf.length : head.vsiz);
1484 fdseekread(m_fd, off+RecordHeader.sizeof+head.ksiz+start, vbuf[0..vsiz]);
1485 return vbuf[0..vsiz];
1488 /* Compare two keys.
1490 * Params:
1491 * abuf = the pointer to the region of the former
1492 * asiz = the size of the region
1493 * bbuf = the pointer to the region of the latter
1494 * bsiz = the size of the region
1496 * Returns:
1497 * The return value is 0 if two equals, positive if the formar is big, else, negative.
1499 static int keycmp (const(void)[] abuf, const(void)[] bbuf) @trusted nothrow @nogc {
1500 import core.stdc.string : memcmp;
1501 //assert(abuf && asiz >= 0 && bbuf && bsiz >= 0);
1502 if (abuf.length > bbuf.length) return 1;
1503 if (abuf.length < bbuf.length) return -1;
1504 return memcmp(abuf.ptr, bbuf.ptr, abuf.length);
1507 /* Search for a record.
1509 * Params:
1510 * kbuf = the pointer to the region of a key
1511 * hash = the second hash value of the key
1512 * bip = the pointer to the region to assign the index of the corresponding record
1513 * offp = the pointer to the region to assign the last visited node in the hash chain,
1514 * or, -1 if the hash chain is empty
1515 * entp = the offset of the last used joint, or, -1 if the hash chain is empty
1516 * head = the pointer to the region to store the header of the last visited record in
1517 * ebuf = the pointer to the entity buffer
1518 * eep = the pointer to a variable to which whether ebuf was used is assigned
1519 * delhit = whether a deleted record corresponds or not
1520 * hout = calculated "second hash" of a key
1522 * Returns:
1523 * The return value is true if record was found, false if there is no corresponding record.
1525 * Throws:
1526 * SDBMException on various errors
1528 bool recsearch (const(void)[] kbuf, int* bip, int* offp, int* entp,
1529 ref RecordHeader head, void[] ebuf, bool* eep, Flag!"delhit" delhit=No.delhit, int* hout=null)
1531 usize off;
1532 int entoff;
1533 int kcmp;
1534 char[DP_STKBUFSIZ] stkey;
1535 assert(ebuf.length >= DP_ENTBUFSIZ);
1536 assert(bip !is null && offp !is null && entp !is null && eep !is null);
1537 int hash = fastHash32(kbuf, 0xdeadf00dU)&0x7fff_ffff;
1538 if (hout !is null) *hout = hash;
1539 int thash = fastHash32(kbuf, 0xf00ddeadU)&0x7fff_ffff;
1540 *bip = thash%m_bnum;
1541 off = m_buckets[*bip];
1542 *offp = -1;
1543 *entp = -1;
1544 entoff = -1;
1545 *eep = false;
1546 while (off != 0) {
1547 rechead(off, head, ebuf, eep);
1548 thash = head.hash2;
1549 if (hash > thash) {
1550 entoff = cast(int)(off+RecordHeader.left.offsetof);
1551 off = head.left;
1552 } else if (hash < thash) {
1553 entoff = cast(int)(off+RecordHeader.right.offsetof);
1554 off = head.right;
1555 } else {
1556 if (*eep && RecordHeader.sizeof+head.ksiz <= DP_ENTBUFSIZ) {
1557 immutable ebstart = RecordHeader.sizeof;
1558 kcmp = keycmp(kbuf, ebuf[ebstart..ebstart+head.ksiz]);
1559 } else if (head.ksiz > DP_STKBUFSIZ) {
1560 auto tkey = recReadKey(off, head);
1561 kcmp = keycmp(kbuf, tkey[0..head.ksiz]);
1562 tkey.length = 0;
1563 } else {
1564 try {
1565 fdseekread(m_fd, off+RecordHeader.sizeof, stkey[0..head.ksiz]);
1566 } catch (Exception) {
1567 raise(Error.FATAL);
1569 kcmp = keycmp(kbuf, stkey[0..head.ksiz]);
1571 if (kcmp > 0) {
1572 entoff = cast(int)(off+RecordHeader.left.offsetof);
1573 off = head.left;
1574 } else if (kcmp < 0) {
1575 entoff = cast(int)(off+RecordHeader.right.offsetof);
1576 off = head.right;
1577 } else {
1578 if (!delhit && (head.flags&DP_RECFDEL)) {
1579 entoff = cast(int)(off+RecordHeader.left.offsetof);
1580 off = head.left;
1581 } else {
1582 *offp = cast(int)off;
1583 *entp = entoff;
1584 return true;
1589 *offp = cast(int)off;
1590 *entp = entoff;
1591 return false;
1594 /* Overwrite a record.
1596 * Params:
1597 * off = the offset of the database file
1598 * rsiz = the size of the existing record
1599 * kbuf = the pointer to the region of a key
1600 * vbuf = the pointer to the region of a value
1601 * hash = the second hash value of the key
1602 * left = the offset of the left child
1603 * right = the offset of the right child
1605 * Throws:
1606 * SDBMException on various errors
1608 void recrewrite (long off, int rsiz, const(void)[] kbuf, const(void)[] vbuf, int hash, int left, int right) {
1609 char[DP_WRTBUFSIZ] ebuf;
1610 RecordHeader head;
1611 int hoff, koff, voff, mi, min, size;
1612 usize asiz;
1613 assert(off >= 1 && rsiz > 0);
1614 head.flags = 0;
1615 head.hash2 = hash;
1616 head.ksiz = cast(int)kbuf.length;
1617 head.vsiz = cast(int)vbuf.length;
1618 head.psiz = cast(int)(rsiz-head.sizeof-kbuf.length-vbuf.length);
1619 head.left = left;
1620 head.right = right;
1621 asiz = head.sizeof+kbuf.length+vbuf.length;
1622 if (m_fbpsiz > DP_FBPOOLSIZ*4 && head.psiz > asiz) {
1623 rsiz = cast(int)((head.psiz-asiz)/2+asiz);
1624 head.psiz -= rsiz;
1625 } else {
1626 rsiz = 0;
1628 if (asiz <= DP_WRTBUFSIZ) {
1629 import core.stdc.string : memcpy;
1630 memcpy(ebuf.ptr, &head, head.sizeof);
1631 memcpy(ebuf.ptr+head.sizeof, kbuf.ptr, kbuf.length);
1632 memcpy(ebuf.ptr+head.sizeof+kbuf.length, vbuf.ptr, vbuf.length);
1633 fdseekwrite(m_fd, off, ebuf[0..asiz]);
1634 } else {
1635 hoff = cast(int)off;
1636 koff = cast(int)(hoff+head.sizeof);
1637 voff = cast(int)(koff+kbuf.length);
1638 fdseekwrite(m_fd, hoff, (&head)[0..1]);
1639 fdseekwrite(m_fd, koff, kbuf[]);
1640 fdseekwrite(m_fd, voff, vbuf[]);
1642 if (rsiz > 0) {
1643 off += head.sizeof+kbuf.length+vbuf.length+head.psiz;
1644 head.flags = DP_RECFDEL|DP_RECFREUSE;
1645 head.hash2 = hash;
1646 head.ksiz = cast(int)kbuf.length;
1647 head.vsiz = cast(int)vbuf.length;
1648 head.psiz = cast(int)(rsiz-head.sizeof-kbuf.length-vbuf.length);
1649 head.left = 0;
1650 head.right = 0;
1651 fdseekwrite(m_fd, off, (&head)[0..1]);
1652 size = head.recsize;
1653 mi = -1;
1654 min = -1;
1655 for (uint i = 0; i < m_fbpsiz; i += 2) {
1656 if (m_fbpool[i] == -1) {
1657 m_fbpool[i] = cast(int)off;
1658 m_fbpool[i+1] = size;
1659 fbpoolcoal();
1660 mi = -1;
1661 break;
1663 if (mi == -1 || m_fbpool[i+1] < min) {
1664 mi = i;
1665 min = m_fbpool[i+1];
1668 if (mi >= 0 && size > min) {
1669 m_fbpool[mi] = cast(int)off;
1670 m_fbpool[mi+1] = size;
1671 fbpoolcoal();
1676 /* Write a record at the end of a database file.
1678 * Params:
1679 * kbuf = the pointer to the region of a key
1680 * vbuf = the pointer to the region of a value
1681 * hash = the second hash value of the key
1682 * left = the offset of the left child
1683 * right = the offset of the right child
1685 * Returns:
1686 * The return value is the offset of the record
1688 * Throws:
1689 * SDBMException on various errors
1691 int recappend (const(void)[] kbuf, const(void)[] vbuf, int hash, int left, int right) {
1692 char[DP_WRTBUFSIZ] ebuf;
1693 RecordHeader head;
1694 usize asiz, psiz;
1695 long off;
1696 psiz = padsize(kbuf.length, vbuf.length);
1697 head.flags = 0;
1698 head.hash2 = hash;
1699 head.ksiz = cast(int)kbuf.length;
1700 head.vsiz = cast(int)vbuf.length;
1701 head.psiz = cast(int)psiz;
1702 head.left = left;
1703 head.right = right;
1704 asiz = head.sizeof+kbuf.length+vbuf.length+psiz;
1705 off = m_fsiz;
1706 if (asiz <= DP_WRTBUFSIZ) {
1707 import core.stdc.string : memcpy, memset;
1708 memcpy(ebuf.ptr, &head, head.sizeof);
1709 memcpy(ebuf.ptr+head.sizeof, kbuf.ptr, kbuf.length);
1710 memcpy(ebuf.ptr+head.sizeof+kbuf.length, vbuf.ptr, vbuf.length);
1711 memset(ebuf.ptr+head.sizeof+kbuf.length+vbuf.length, 0, psiz);
1712 fdseekwrite(m_fd, off, ebuf[0..asiz]);
1713 } else {
1714 import core.stdc.string : memcpy, memset;
1715 ubyte[8192] tbuf;
1716 ubyte[] hbuf;
1717 if (asiz <= tbuf.length) hbuf = tbuf[0..asiz]; else hbuf.length = asiz;
1718 memcpy(hbuf.ptr, &head, head.sizeof);
1719 memcpy(hbuf.ptr+head.sizeof, kbuf.ptr, kbuf.length);
1720 memcpy(hbuf.ptr+head.sizeof+kbuf.length, vbuf.ptr, vbuf.length);
1721 memset(hbuf.ptr+head.sizeof+kbuf.length+vbuf.length, 0, psiz);
1722 fdseekwrite(m_fd, off, hbuf[0..asiz]);
1724 m_fsiz += asiz;
1725 return cast(int)off;
1728 /* Overwrite the value of a record.
1730 * Params:
1731 * off = the offset of the database file
1732 * head = the header of the record
1733 * vbuf = the pointer to the region of a value
1734 * cat = whether it is concatenate mode or not
1736 * Throws:
1737 * SDBMException on various errors
1739 void recover (long off, ref RecordHeader head, const(void)[] vbuf, Flag!"catmode" catmode) {
1740 assert(off >= 0);
1741 for (uint i = 0; i < m_fbpsiz; i += 2) {
1742 if (m_fbpool[i] == off) {
1743 m_fbpool[i] = m_fbpool[i+1] = -1;
1744 break;
1747 head.flags = 0;
1748 long voff = off+RecordHeader.sizeof+head.ksiz;
1749 if (catmode) {
1750 head.psiz -= vbuf.length;
1751 head.vsiz += vbuf.length;
1752 voff += head.vsiz-vbuf.length;
1753 } else {
1754 head.psiz += head.vsiz-vbuf.length;
1755 head.vsiz = cast(int)vbuf.length;
1757 scope(failure) m_fatal = true; // any failure is fatal here
1758 fdseekwrite(m_fd, off, (&head)[0..1]);
1759 fdseekwrite(m_fd, voff, vbuf[]);
1762 /* Delete a record.
1764 * Params:
1765 * off = the offset of the database file
1766 * head = the header of the record
1767 * reusable = whether the region is reusable or not
1769 * Throws:
1770 * SDBMException on various errors
1772 void recdelete (long off, ref in RecordHeader head, Flag!"reusable" reusable) {
1773 assert(off >= 0);
1774 if (reusable) {
1775 auto size = head.recsize;
1776 int mi = -1;
1777 int min = -1;
1778 for (uint i = 0; i < m_fbpsiz; i += 2) {
1779 if (m_fbpool[i] == -1) {
1780 m_fbpool[i] = cast(int)off;
1781 m_fbpool[i+1] = size;
1782 fbpoolcoal();
1783 mi = -1;
1784 break;
1786 if (mi == -1 || m_fbpool[i+1] < min) {
1787 mi = i;
1788 min = m_fbpool[i+1];
1791 if (mi >= 0 && size > min) {
1792 m_fbpool[mi] = cast(int)off;
1793 m_fbpool[mi+1] = size;
1794 fbpoolcoal();
1797 fdseekwritenum(m_fd, off+RecordHeader.flags.offsetof, DP_RECFDEL|(reusable ? DP_RECFREUSE : 0));
1800 /* Make contiguous records of the free block pool coalesce. */
1801 void fbpoolcoal () @trusted {
1802 import core.stdc.stdlib : qsort;
1803 if (m_fbpinc++ <= m_fbpsiz/4) return;
1804 m_fbpinc = 0;
1805 qsort(m_fbpool, m_fbpsiz/2, int.sizeof*2, &fbpoolcmp);
1806 for (uint i = 2; i < m_fbpsiz; i += 2) {
1807 if (m_fbpool[i-2] > 0 && m_fbpool[i-2]+m_fbpool[i-1]-m_fbpool[i] == 0) {
1808 m_fbpool[i] = m_fbpool[i-2];
1809 m_fbpool[i+1] += m_fbpool[i-1];
1810 m_fbpool[i-2] = m_fbpool[i-1] = -1;
1815 /* Compare two records of the free block pool.
1816 a = the pointer to one record.
1817 b = the pointer to the other record.
1818 The return value is 0 if two equals, positive if the formar is big, else, negative. */
1819 static extern(C) int fbpoolcmp (in void* a, in void* b) @trusted nothrow @nogc {
1820 assert(a && b);
1821 return *cast(const int*)a - *cast(const int*)b;
1824 /* ********************************************************************************************* *
1825 * static utilities
1826 * ********************************************************************************************* */
1827 public:
1828 static:
1829 /** Remove a database file.
1831 * Params:
1832 * name = the name of a database file
1834 * Throws:
1835 * SDBMException on various errors
1837 void remove (const(char)[] name) {
1838 import core.stdc.errno : errno, ENOENT;
1839 import core.sys.posix.sys.stat : lstat, stat_t;
1840 import core.sys.posix.unistd : unlink;
1841 import std.string : toStringz;
1842 stat_t sbuf;
1843 assert(name.length);
1844 auto namez = name.toStringz;
1845 if (lstat(namez, &sbuf) == -1) {
1846 if (errno != ENOENT) straise(Error.STAT);
1847 // no file
1848 return;
1850 //k8:??? try to open the file to check if it's not locked or something
1851 auto depot = new SDBM(name, WRITER|TRUNC);
1852 delete depot;
1853 // remove file
1854 if (unlink(namez) == -1) {
1855 if (errno != ENOENT) straise(Error.UNLINK);
1856 // no file
1860 /** Repair a broken database file.
1862 * There is no guarantee that all records in a repaired database file correspond to the original
1863 * or expected state.
1865 * Params:
1866 * name = the name of a database file
1868 * Returns:
1869 * true if ok, false is there were some errors
1871 * Throws:
1872 * SDBMException on various errors
1874 bool repair (const(char)[] name) {
1875 import core.sys.posix.fcntl : open, O_RDWR;
1876 import core.sys.posix.sys.stat : lstat, stat_t;
1877 import core.sys.posix.unistd : close, ftruncate, unlink;
1878 SDBM tdepot;
1879 SDBMHeader dbhead;
1880 char* kbuf, vbuf;
1881 RecordHeader head;
1882 int fd, flags, bnum, tbnum, rsiz, ksiz, vsiz;
1883 usize off;
1884 long fsiz;
1885 stat_t sbuf;
1886 assert(name.length);
1888 import std.string : toStringz;
1889 auto namez = name.toStringz;
1890 if (lstat(namez, &sbuf) == -1) throw new SDBMException(Error.STAT); //raise(Error.STAT);
1891 fsiz = sbuf.st_size;
1892 if ((fd = open(namez, O_RDWR, DP_FILEMODE)) == -1) throw new SDBMException(Error.OPEN); //raise(Error.OPEN);
1894 scope(exit) if (fd >= 0) close(fd);
1895 fdseekread(fd, 0, (&dbhead)[0..1]);
1896 flags = dbhead.flags;
1897 bnum = dbhead.nbuckets;
1898 tbnum = dbhead.nrecords*2;
1899 if (tbnum < DP_DEFBNUM) tbnum = DP_DEFBNUM;
1900 tdepot = new SDBM(name~DP_TMPFSUF, WRITER|CREAT|TRUNC, tbnum);
1901 off = SDBMHeader.sizeof+bnum*int.sizeof;
1902 bool err = false;
1903 while (off < fsiz) {
1904 try {
1905 fdseekread(fd, off, (&head)[0..1]);
1906 } catch (Exception) {
1907 break;
1909 if (head.flags&DP_RECFDEL) {
1910 rsiz = head.recsize;
1911 if (rsiz < 0) break;
1912 off += rsiz;
1913 continue;
1915 ksiz = head.ksiz;
1916 vsiz = head.vsiz;
1917 if (ksiz >= 0 && vsiz >= 0) {
1918 kbuf = alloc!char(ksiz);
1919 vbuf = alloc!char(vsiz);
1920 if (kbuf !is null && vbuf !is null) {
1921 try {
1922 fdseekread(fd, off+RecordHeader.sizeof, kbuf[0..ksiz]);
1923 fdseekread(fd, off+RecordHeader.sizeof+ksiz, vbuf[0..vsiz]);
1924 tdepot.put(kbuf[0..ksiz], vbuf[0..vsiz], WMode.KEEP);
1925 } catch (Exception) {
1926 err = true;
1928 } else {
1929 //if (!err) raise(Error.ALLOC);
1930 err = true;
1932 if (vbuf !is null) freeptr(vbuf);
1933 if (kbuf !is null) freeptr(kbuf);
1934 } else {
1935 //if (!err) raise(Error.BROKEN);
1936 err = true;
1938 rsiz = head.recsize;
1939 if (rsiz < 0) break;
1940 off += rsiz;
1942 tdepot.flags = flags; // err = true;
1943 try {
1944 tdepot.sync();
1945 } catch (Exception) {
1946 err = true;
1948 if (ftruncate(fd, 0) == -1) {
1949 //if (!err) raise(Error.TRUNC);
1950 err = true;
1952 auto tempname = tdepot.m_name; // with trailing zero
1953 try {
1954 fcopy(fd, 0, tdepot.m_fd, 0);
1955 tdepot.close();
1956 } catch (Exception) {
1957 err = true;
1959 if (close(fd) == -1) {
1960 //if (!err) raise(Error.CLOSE);
1961 err = true;
1963 fd = -1;
1964 if (unlink(tempname.ptr) == -1) {
1965 //if (!err) raise(Error.UNLINK);
1966 err = true;
1968 return !err;
1971 /** Get a natural prime number not less than a number.
1973 * This function is useful when an application determines the size of a bucket array of its
1974 * own hash algorithm.
1976 * Params:
1977 * num = a natural number
1979 * Returns:
1980 * The return value is a natural prime number not less than the specified number
1982 int primenum (int num) @safe pure nothrow @nogc {
1983 static immutable int[217] primes = [
1984 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 43, 47, 53, 59, 61, 71, 79, 83,
1985 89, 103, 109, 113, 127, 139, 157, 173, 191, 199, 223, 239, 251, 283, 317, 349,
1986 383, 409, 443, 479, 509, 571, 631, 701, 761, 829, 887, 953, 1021, 1151, 1279,
1987 1399, 1531, 1663, 1789, 1913, 2039, 2297, 2557, 2803, 3067, 3323, 3583, 3833,
1988 4093, 4603, 5119, 5623, 6143, 6653, 7159, 7673, 8191, 9209, 10223, 11261,
1989 12281, 13309, 14327, 15359, 16381, 18427, 20479, 22511, 24571, 26597, 28669,
1990 30713, 32749, 36857, 40949, 45053, 49139, 53239, 57331, 61417, 65521, 73727,
1991 81919, 90107, 98299, 106487, 114679, 122869, 131071, 147451, 163819, 180221,
1992 196597, 212987, 229373, 245759, 262139, 294911, 327673, 360439, 393209, 425977,
1993 458747, 491503, 524287, 589811, 655357, 720887, 786431, 851957, 917503, 982981,
1994 1048573, 1179641, 1310719, 1441771, 1572853, 1703903, 1835003, 1966079,
1995 2097143, 2359267, 2621431, 2883577, 3145721, 3407857, 3670013, 3932153,
1996 4194301, 4718579, 5242877, 5767129, 6291449, 6815741, 7340009, 7864301,
1997 8388593, 9437179, 10485751, 11534329, 12582893, 13631477, 14680063, 15728611,
1998 16777213, 18874367, 20971507, 23068667, 25165813, 27262931, 29360087, 31457269,
1999 33554393, 37748717, 41943023, 46137319, 50331599, 54525917, 58720253, 62914549,
2000 67108859, 75497467, 83886053, 92274671, 100663291, 109051903, 117440509,
2001 125829103, 134217689, 150994939, 167772107, 184549373, 201326557, 218103799,
2002 234881011, 251658227, 268435399, 301989881, 335544301, 369098707, 402653171,
2003 436207613, 469762043, 503316469, 536870909, 603979769, 671088637, 738197503,
2004 805306357, 872415211, 939524087, 1006632947, 1073741789, 1207959503,
2005 1342177237, 1476394991, 1610612711, 1744830457, 1879048183, 2013265907,
2007 assert(num > 0);
2008 foreach (immutable pr; primes) if (num <= pr) return pr;
2009 return primes[$-1];
2012 private:
2013 /** Free `malloc()`ed pointer and set variable to `null`.
2015 * Params:
2016 * ptr = the pointer to variable holding a pointer
2018 static freeptr(T) (ref T* ptr) {
2019 import core.stdc.stdlib : free;
2020 if (ptr !is null) {
2021 free(cast(void*)ptr);
2022 ptr = null;
2026 // `noerr`: "nothrow" --> return `null` on error
2027 static T* alloc(T, string noerr=null) (usize count=1) {
2028 import core.stdc.stdlib : malloc;
2029 import core.stdc.string : memset;
2030 //TODO: overflow checking
2031 usize asz = T.sizeof*count;
2032 if (asz == 0) asz = 1;
2033 void* res = malloc(asz);
2034 if (res is null) {
2035 static if (noerr == "nothrow") throw new SDBMException(Error.ALLOC);
2036 } else {
2037 memset(res, 0, asz);
2039 return cast(T*)res;