encoding: avoid doing any work if we don't have to
[iv.d.git] / sdbm.d
blob1e84d4167796c08f66209ef831c108b9fda5540c
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.
259 * Throws:
260 * SDBMException on various errors
262 void open (const(char)[] name, int omode=READER, int bnum=-1) {
263 import core.sys.posix.fcntl : open, O_CREAT, O_RDONLY, O_RDWR;
264 import core.sys.posix.sys.mman : mmap, munmap, MAP_FAILED, PROT_READ, PROT_WRITE, MAP_SHARED;
265 import core.sys.posix.sys.stat : fstat, lstat, S_ISREG, stat_t;
266 import core.sys.posix.unistd : close, ftruncate;
267 SDBMHeader hbuf;
268 char* map;
269 int mode, fd;
270 usize msiz;
271 ulong inode;
272 long fsiz;
273 int* fbpool;
274 stat_t sbuf;
275 time_t mtime;
276 if (opened) raise(Error.OPENED);
277 assert(name.length);
278 char[] namez; // unique
279 // add '\0' after string
281 usize len = 0;
282 while (len < name.length && name[len]) ++len;
283 namez = new char[](len+1);
284 namez[0..$-1] = name[0..len];
285 namez[$-1] = 0;
287 mode = O_RDONLY;
288 if (omode&WRITER) {
289 mode = O_RDWR;
290 if (omode&CREAT) mode |= O_CREAT;
292 if ((fd = open(namez.ptr, mode, DP_FILEMODE)) == -1) raise(Error.OPEN);
293 scope(failure) close(fd);
294 if ((omode&NOLCK) == 0) fdlock(fd, (omode&WRITER) != 0, (omode&LCKNB) != 0);
295 if ((omode&(WRITER|TRUNC)) == (WRITER|TRUNC)) {
296 if (ftruncate(fd, 0) == -1) raise(Error.TRUNC);
298 if (fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode) || (sbuf.st_ino == 0 && lstat(namez.ptr, &sbuf) == -1)) raise(Error.STAT);
299 inode = sbuf.st_ino;
300 mtime = sbuf.st_mtime;
301 fsiz = sbuf.st_size;
302 if ((omode&WRITER) && fsiz == 0) {
303 hbuf.signature[] = 0;
304 hbuf.versionstr[] = 0;
305 hbuf.signature[0..DP_MAGIC.length] = DP_MAGIC[];
306 hbuf.versionstr[] = SDBM_LIBVER[];
307 bnum = (bnum < 1 ? DP_DEFBNUM : bnum);
308 bnum = primenum(bnum);
309 hbuf.nbuckets = bnum;
310 hbuf.nrecords = 0;
311 fsiz = hbuf.sizeof+bnum*int.sizeof;
312 hbuf.filesize = cast(int)fsiz;
313 fdseekwrite(fd, 0, (&hbuf)[0..1]);
315 ubyte[DP_IOBUFSIZ] ebuf = 0; // totally empty buffer initialized with 0 %-)
316 usize pos = hbuf.sizeof;
317 while (pos < fsiz) {
318 usize left = cast(usize)fsiz-pos;
319 usize wr = (left > ebuf.length ? ebuf.length : left);
320 fdseekwrite(fd, pos, ebuf[0..wr]);
321 pos += wr;
325 try {
326 fdseekread(fd, 0, (&hbuf)[0..1]);
327 } catch (Exception) {
328 raise(Error.BROKEN);
330 if (hbuf.signature[0..DP_MAGIC.length] != DP_MAGIC) raise(Error.BROKEN);
331 if (hbuf.versionstr[] != SDBM_LIBVER) raise(Error.BROKEN);
332 if (hbuf.filesize != fsiz) raise(Error.BROKEN);
333 bnum = hbuf.nbuckets;
334 if (bnum < 1 || hbuf.nrecords < 0 || fsiz < SDBMHeader.sizeof+bnum*int.sizeof) raise(Error.BROKEN);
335 msiz = SDBMHeader.sizeof+bnum*int.sizeof;
336 map = cast(char*)mmap(null, msiz, PROT_READ|(mode&WRITER ? PROT_WRITE : 0), MAP_SHARED, fd, 0);
337 if (map == MAP_FAILED || map is null) raise(Error.MAP);
338 scope(failure) munmap(map, msiz);
339 fbpool = alloc!int(DP_FBPOOLSIZ*2);
341 import std.exception : assumeUnique;
342 m_name = namez[0..$-1].assumeUnique;
344 m_wmode = (mode&WRITER) != 0;
345 m_inode = inode;
346 m_mtime = mtime;
347 m_fd = fd;
348 m_fsiz = fsiz;
349 m_map = map;
350 m_msiz = cast(int)msiz;
351 m_buckets = cast(int*)(map+SDBMHeader.sizeof);
352 m_bnum = bnum;
353 m_rnum = hbuf.nrecords;
354 m_fatal = false;
355 m_ioff = 0;
356 m_fbpool = fbpool;
357 m_fbpool[0..DP_FBPOOLSIZ*2] = -1;
358 m_fbpsiz = DP_FBPOOLSIZ*2;
359 m_fbpinc = 0;
360 m_alignment = 0;
363 /** Close a database handle.
365 * Returns:
366 * If successful, the return value is true, else, it is false.
367 * Because the region of a closed handle is released, it becomes impossible to use the handle.
368 * Updating a database is assured to be written when the handle is closed. If a writer opens
369 * a database but does not close it appropriately, the database will be broken.
371 * Throws:
372 * SDBMException on various errors
374 void close () {
375 import core.sys.posix.sys.mman : munmap, MAP_FAILED;
376 import core.sys.posix.unistd : close;
377 if (!opened) return;
378 bool fatal = m_fatal;
379 Error err = Error.NOERR;
380 if (m_wmode) updateHeader();
381 if (m_map != MAP_FAILED && m_map !is null) {
382 if (munmap(m_map, m_msiz) == -1) err = Error.MAP;
384 m_map = null;
385 if (close(m_fd) == -1) err = Error.CLOSE;
386 freeptr(m_fbpool);
387 m_name = null;
388 m_fd = -1;
389 m_wmode = false;
390 if (fatal) err = Error.FATAL;
391 if (err != Error.NOERR) raise(err);
394 /** Retrieve a record.
396 * Params:
397 * kbuf = the pointer to the region of a key
398 * start = the offset address of the beginning of the region of the value to be read
399 * max = specifies the max size to be read; if it is `uint.max`, the size to read is unlimited
400 * sp = the pointer to a variable to which the size of the region of the return
401 * value is assigned; if it is `null`, it is not used
403 * Returns:
404 * If successful, the return value is the GC-allocated slice of the region of the value of the
405 * corresponding record, else, it is `null`. `null` is returned when no record corresponds to
406 * the specified key or the size of the value of the corresponding record is less than `start`.
407 * No additional zero code is appended at the end of the region of the return value.
409 * Throws:
410 * SDBMException on various errors
412 T get(T=string) (const(void)[] kbuf, uint start=0, uint max=uint.max, usize* sp=null) if (isDynamicArray!T && !isArray!(typeof(T.init[0]))) {
413 return getA!(typeof(T.init[0]))(kbuf, start, max, sp);
416 /** Retrieve a record.
418 * Params:
419 * kbuf = the pointer to the region of a key
420 * start = the offset address of the beginning of the region of the value to be read
421 * max = specifies the max size to be read; if it is `uint.max`, the size to read is unlimited
422 * sp = the pointer to a variable to which the size of the region of the return
423 * value is assigned; if it is `null`, it is not used
425 * Returns:
426 * If successful, the return value is the GC-allocated slice of the region of the value of the
427 * corresponding record, else, it is `null`. `null` is returned when no record corresponds to
428 * the specified key or the size of the value of the corresponding record is less than `start`.
429 * No additional zero code is appended at the end of the region of the return value.
431 * Throws:
432 * SDBMException on various errors
434 T[] getA(T=char) (const(void)[] kbuf, uint start=0, uint max=uint.max, usize* sp=null) if (!isArray!T) {
435 RecordHeader head;
436 int bi, off, entoff;
437 bool ee;
438 usize vsiz;
439 char[DP_ENTBUFSIZ] ebuf;
440 ubyte[] vbuf = null;
441 if (sp !is null) *sp = 0;
442 checkOpened();
443 if (!recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee)) return null; //raise(Error.NOITEM);
444 if (start >= head.vsiz) return null; //raise(Error.NOITEM);
445 scope(failure) m_fatal = true; // any failure beyond this point is fatal
446 if (ee && RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
447 import core.stdc.string : memcpy;
448 head.vsiz -= start;
449 if (max == uint.max) {
450 vsiz = head.vsiz;
451 } else {
452 vsiz = (max < head.vsiz ? max : head.vsiz);
454 if (vsiz != 0) {
455 vbuf.length = vsiz;
456 memcpy(vbuf.ptr, ebuf.ptr+(RecordHeader.sizeof+head.ksiz+start), vsiz);
457 } else {
458 vbuf = new ubyte[](1);
459 vbuf.length = 0;
461 } else {
462 vbuf = recReadValue!ubyte(off, head, start, max);
464 if (sp !is null) {
465 if (max == uint.max) {
466 *sp = head.vsiz;
467 } else {
468 *sp = (max < head.vsiz ? max : head.vsiz);
471 immutable len = vbuf.length/T.sizeof;
472 return cast(T[])(vbuf[0..len*T.sizeof]);
473 //return cast(void[])vbuf;
476 /** Retrieve a record and write the value into a buffer.
478 * Params:
479 * vbuff = the pointer to a buffer into which the value of the corresponding record is written
480 * kbuf = the pointer to the region of a key
481 * start = the offset address of the beginning of the region of the value to be read
483 * Returns:
484 * If successful, the return value is the read data (slice of vbuf), else, it is `null`.
485 * `null` returned when no record corresponds to the specified key or the size of the value
486 * of the corresponding record is less than `start`.
487 * Note that no additional zero code is appended at the end of the region of the writing buffer.
489 * Throws:
490 * SDBMException on various errors
492 T[] getToBuf(T=char) (T[] vbuff, const(void)[] kbuf, uint start=0) if (!isArray!T) {
493 RecordHeader head;
494 int bi, off, entoff;
495 bool ee;
496 usize vsiz;
497 char[DP_ENTBUFSIZ] ebuf;
498 checkOpened();
499 if (!recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee)) return null; //raise(Error.NOITEM);
500 if (start > head.vsiz) return null; //raise(Error.NOITEM);
501 scope(failure) m_fatal = true; // any failure beyond this point is fatal
502 auto vbuf = cast(ubyte[])vbuff;
503 if (ee && RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
504 import core.stdc.string : memcpy;
505 head.vsiz -= start;
506 vsiz = (vbuf.length < head.vsiz ? vbuf.length : head.vsiz);
507 memcpy(vbuf.ptr, ebuf.ptr+(RecordHeader.sizeof+head.ksiz+start), vsiz);
508 } else {
509 vbuf = recReadValueToBuf(vbuf, off, head, start);
510 vsiz = vbuf.length;
512 immutable len = vsiz/T.sizeof;
513 return cast(T[])(vbuf[0..len*T.sizeof]);
514 //return vbuf[0..vsiz];
517 // `mode`: "throw", "nothrow"
518 T get(T, string mode="throw") (const(void)[] kbuf) if (!isArray!T) {
519 static assert(mode == "throw" || mode == "nothrow", "invalid mode");
520 T[1] data;
521 auto res = getToBuf(cast(ubyte[])(data[]), kbuf);
522 static if (mode == "throw") {
523 if (res.length != T.sizeof) raise(Error.NOTFOUND);
524 } else {
525 if (res.length != T.sizeof) data[0] = T.init;
527 return data[0];
530 // `mode`: "throw", "nothrow"
531 bool get(T, string mode="nothrow") (const(void)[] kbuf, ref T dval) if (!isArray!T) {
532 static assert(mode == "throw" || mode == "nothrow", "invalid mode");
533 auto res = getToBuf(cast(ubyte[])((&dval)[0..1]), kbuf);
534 static if (mode == "throw") {
535 if (res.length != T.sizeof) raise(Error.NOTFOUND);
536 } else {
537 if (res.length != T.sizeof) dval = T.init;
539 return (res.length == T.sizeof);
542 /** Get the size of the value of a record.
544 * Params:
545 * kbuf = the pointer to the region of a key
547 * Returns:
548 * If successful, the return value is the size of the value of the corresponding record, else, it is -1.
549 * Because this function does not read the entity of a record, it is faster than `get`.
551 * Throws:
552 * SDBMException on various errors
554 int vsize (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 -1; //raise(Error.NOITEM);
561 return head.vsiz;
564 /** Check if a record is existing.
566 * Params:
567 * kbuf = the pointer to the region of a key
569 * Returns:
570 * `true` if a record is in database, `false` otherwise.
572 * Throws:
573 * SDBMException on various errors
575 bool exists (const(void)[] kbuf) {
576 RecordHeader head;
577 int bi, off, entoff;
578 bool ee;
579 char[DP_ENTBUFSIZ] ebuf;
580 checkOpened();
581 if (!recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee)) return false;
582 return true;
585 /** Store a record.
587 * Params:
588 * kbuf = the pointer to the region of a key
589 * vbuf = the pointer to the region of a value
590 * dmode = behavior when the key overlaps, by the following values:
591 * `WMode.OVER`, which means the specified value overwrites the existing one,
592 * `WMode.KEEP`, which means the existing value is kept,
593 * `WMode.CAT`, which means the specified value is concatenated at the end of the existing value.
595 * Throws:
596 * SDBMException on various errors
598 void put (const(void)[] kbuf, const(void)[] vbuf, WMode dmode=WMode.OVER) {
599 RecordHeader head, next;
600 int hash, bi, off, entoff, newoff, fdel, mroff, mrsiz, mi, min;
601 usize rsiz, nsiz;
602 bool ee;
603 char[DP_ENTBUFSIZ] ebuf;
604 checkWriting();
605 newoff = -1;
606 if (recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee, Yes.delhit, &hash)) {
607 // record found
608 fdel = head.flags&DP_RECFDEL;
609 if (dmode == WMode.KEEP && !fdel) raise(Error.KEEP);
610 if (fdel) {
611 head.psiz += head.vsiz;
612 head.vsiz = 0;
614 rsiz = head.recsize;
615 nsiz = RecordHeader.sizeof+kbuf.length+vbuf.length;
616 if (dmode == WMode.CAT) nsiz += head.vsiz;
617 if (off+rsiz >= m_fsiz) {
618 if (rsiz < nsiz) {
619 head.psiz += nsiz-rsiz;
620 rsiz = nsiz;
621 m_fsiz = off+rsiz;
623 } else {
624 while (nsiz > rsiz && off+rsiz < m_fsiz) {
625 rechead(off+rsiz, next, null, null);
626 if ((next.flags&DP_RECFREUSE) == 0) break;
627 head.psiz += next.recsize;
628 rsiz += next.recsize;
630 for (uint i = 0; i < m_fbpsiz; i += 2) {
631 if (m_fbpool[i] >= off && m_fbpool[i] < off+rsiz) {
632 m_fbpool[i] = m_fbpool[i+1] = -1;
636 if (nsiz <= rsiz) {
637 recover(off, head, vbuf, (dmode == WMode.CAT ? Yes.catmode : No.catmode));
638 } else {
639 char[] tval = null;
640 scope(failure) m_fatal = true;
641 if (dmode == WMode.CAT) {
642 import core.stdc.string : memcpy;
643 if (ee && RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
644 tval.length = head.vsiz+vbuf.length;
645 if (head.vsiz > 0) memcpy(tval.ptr, ebuf.ptr+(RecordHeader.sizeof+head.ksiz), head.vsiz);
646 } else {
647 tval = recReadValue(off, head);
648 tval.length = head.vsiz+vbuf.length;
650 if (vbuf.length) memcpy(tval.ptr+head.vsiz, vbuf.ptr, vbuf.length);
651 immutable newsize = head.vsiz+vbuf.length;
652 vbuf = tval[0..newsize];
654 mi = -1;
655 min = -1;
656 for (uint i = 0; i < m_fbpsiz; i += 2) {
657 if (m_fbpool[i+1] < nsiz) continue;
658 if (mi == -1 || m_fbpool[i+1] < min) {
659 mi = i;
660 min = m_fbpool[i+1];
663 if (mi >= 0) {
664 mroff = m_fbpool[mi];
665 mrsiz = m_fbpool[mi+1];
666 m_fbpool[mi] = -1;
667 m_fbpool[mi+1] = -1;
668 } else {
669 mroff = -1;
670 mrsiz = -1;
672 recdelete(off, head, Yes.reusable);
673 if (mroff > 0 && nsiz <= mrsiz) {
674 recrewrite(mroff, mrsiz, kbuf, vbuf, hash, head.left, head.right);
675 newoff = mroff;
676 } else {
677 newoff = recappend(kbuf, vbuf, hash, head.left, head.right);
680 if (fdel) ++m_rnum;
681 } else {
682 // no such record
683 scope(failure) m_fatal = true;
684 newoff = recappend(kbuf, vbuf, hash, 0, 0);
685 ++m_rnum;
687 if (newoff > 0) {
688 if (entoff > 0) {
689 fdseekwritenum(m_fd, entoff, newoff);
690 } else {
691 m_buckets[bi] = newoff;
696 // `mode`: "throw", "nothrow"
697 void put(T) (const(void)[] kbuf, in T val, WMode dmode=WMode.OVER) if (!isArray!T) {
698 put(kbuf, (&val)[0..1], dmode);
701 /** Delete a record.
703 * Params:
704 * kbuf = the pointer to the region of a key
706 * Returns:
707 * If successful, the return value is true, else, it is false.
708 * False is returned when no record corresponds to the specified key.
710 * Throws:
711 * SDBMException on various errors
713 bool del (const(void)[] kbuf) {
714 RecordHeader head;
715 int bi, off, entoff;
716 bool ee;
717 char[DP_ENTBUFSIZ] ebuf;
718 checkWriting();
719 if (!recsearch(kbuf, &bi, &off, &entoff, head, ebuf[], &ee)) return false; //raise(Error.NOITEM);
720 recdelete(off, head, No.reusable);
721 --m_rnum;
722 return true;
725 /** Initialize the iterator of a database handle.
727 * Returns:
728 * If successful, the return value is true, else, it is false.
729 * The iterator is used in order to access the key of every record stored in a database.
731 * Throws:
732 * SDBMException on various errors
734 void itInit () {
735 checkOpened();
736 m_ioff = 0;
739 /** Get the next key of the iterator.
741 * Params:
742 * sp = the pointer to a variable to which the size of the region of the return value is assigned.
743 * If it is `null`, it is not used.
745 * Returns:
746 * If successful, the return value is the pointer to the GC-allocated region of the next key,
747 * else, it is `null`. `null` is returned when no record is to be get out of the iterator.
748 * No additional zero code is appended at the end of the region of the return value.
749 * It is possible to access every record by iteration of calling this function. However,
750 * it is not assured if updating the database is occurred while the iteration. Besides,
751 * the order of this traversal access method is arbitrary, so it is not assured that the
752 * order of storing matches the one of the traversal access.
754 * Throws:
755 * SDBMException on various errors
757 T[] itNext(T=char) (usize* sp=null) if (T.sizeof == 1) {
758 RecordHeader head;
759 usize off;
760 bool ee;
761 char[DP_ENTBUFSIZ] ebuf;
762 char[] kbuf;
763 if (sp !is null) *sp = 0;
764 checkOpened();
765 off = SDBMHeader.sizeof+m_bnum*int.sizeof;
766 off = (off > m_ioff ? off : m_ioff);
767 scope(failure) m_fatal = true; // any failure is fatal here
768 while (off < m_fsiz) {
769 rechead(off, head, ebuf[], &ee);
770 if (head.flags&DP_RECFDEL) {
771 off += head.recsize;
772 } else {
773 if (ee && RecordHeader.sizeof+head.ksiz <= DP_ENTBUFSIZ) {
774 import core.stdc.string : memcpy;
775 if (head.ksiz > 0) {
776 kbuf.length = head.ksiz;
777 memcpy(kbuf.ptr, ebuf.ptr+RecordHeader.sizeof, head.ksiz);
779 } else {
780 kbuf = recReadKey(off, head);
782 m_ioff = cast(int)(off+head.recsize);
783 if (sp !is null) *sp = head.ksiz;
784 return kbuf;
787 //raise(Error.NOITEM);
788 return null;
791 /** Set alignment of a database handle.
793 * If alignment is set to a database, the efficiency of overwriting values is improved.
794 * The size of alignment is suggested to be average size of the values of the records to be
795 * stored. If alignment is positive, padding whose size is multiple number of the alignment
796 * is placed. If alignment is negative, as `vsiz` is the size of a value, the size of padding
797 * is calculated with `(vsiz/pow(2, abs(alignment)-1))'. Because alignment setting is not
798 * saved in a database, you should specify alignment every opening a database.
800 * Params:
801 * alignment = the size of alignment
803 * Throws:
804 * SDBMException on various errors
806 @property void alignment (int alignment) {
807 checkWriting();
808 m_alignment = alignment;
811 /** Get alignment of a database handle.
813 * Returns:
814 * The size of alignment
816 * Throws:
817 * SDBMException on various errors
819 @property int alignment () {
820 checkOpened();
821 return m_alignment;
824 /** Set the size of the free block pool of a database handle.
826 * The default size of the free block pool is 16. If the size is greater, the space efficiency
827 * of overwriting values is improved with the time efficiency sacrificed.
829 * Params:
830 * size = the size of the free block pool of a database
832 * Throws:
833 * SDBMException on various errors
835 @property void freeBlockPoolSize (uint size) {
836 import core.stdc.stdlib : realloc;
837 checkWriting();
838 if (size > 0x3fff_ffffu) raise(Error.ALLOC);
839 size *= 2;
840 auto fbpool = cast(int*)realloc(m_fbpool, size*int.sizeof+(size ? 0 : 1));
841 if (fbpool is null) raise(Error.ALLOC);
842 fbpool[0..size] = -1;
843 m_fbpool = fbpool;
844 m_fbpsiz = size;
847 /** Get the size of the free block pool of a database handle.
849 * Returns:
850 * The size of the free block pool of a database
852 * Throws:
853 * SDBMException on various errors
855 @property uint freeBlockPoolSize () {
856 checkOpened();
857 return m_fbpsiz/2;
860 /** Synchronize updating contents with the file and the device.
862 * This function is useful when another process uses the connected database file.
864 * Throws:
865 * SDBMException on various errors
867 void sync () {
868 import core.sys.posix.sys.mman : msync, MS_SYNC;
869 import core.sys.posix.unistd : fsync;
870 checkWriting();
871 updateHeader();
872 if (msync(m_map, m_msiz, MS_SYNC) == -1) {
873 m_fatal = true;
874 raise(Error.MAP);
876 if (fsync(m_fd) == -1) {
877 m_fatal = true;
878 raise(Error.SYNC);
882 /** Optimize a database.
884 * In an alternating succession of deleting and storing with overwrite or concatenate,
885 * dispensable regions accumulate. This function is useful to do away with them.
887 * Params:
888 * bnum = the number of the elements of the bucket array. If it is not more than 0,
889 * the default value is specified
891 * Throws:
892 * SDBMException on various errors
894 void optimize (int bnum=-1) {
895 import core.sys.posix.sys.mman : mmap, munmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE;
896 import core.sys.posix.unistd : ftruncate, unlink;
897 SDBM tdepot;
898 RecordHeader head;
899 usize off;
900 int unum;
901 bool ee;
902 int[DP_OPTRUNIT] ksizs, vsizs;
903 char[DP_ENTBUFSIZ] ebuf;
904 char[][DP_OPTRUNIT] kbufs, vbufs;
905 checkWriting();
906 if (bnum < 0) {
907 bnum = cast(int)(m_rnum*(1.0/DP_OPTBLOAD))+1;
908 if (bnum < DP_DEFBNUM/2) bnum = DP_DEFBNUM/2;
910 tdepot = new SDBM(m_name~DP_TMPFSUF, WRITER|CREAT|TRUNC, bnum);
911 scope(failure) {
912 import std.exception : collectException;
913 m_fatal = true;
914 unlink(tdepot.m_name.ptr);
915 collectException(tdepot.close());
917 scope(exit) delete tdepot;
918 tdepot.flags = flags;
919 tdepot.m_alignment = m_alignment;
920 off = SDBMHeader.sizeof+m_bnum*int.sizeof;
921 unum = 0;
922 while (off < m_fsiz) {
923 rechead(off, head, ebuf[], &ee);
924 if ((head.flags&DP_RECFDEL) == 0) {
925 if (ee && RecordHeader.sizeof+head.ksiz <= DP_ENTBUFSIZ) {
926 import core.stdc.string : memcpy;
927 kbufs[unum].length = head.ksiz;
928 if (head.ksiz > 0) memcpy(kbufs[unum].ptr, ebuf.ptr+RecordHeader.sizeof, head.ksiz);
929 if (RecordHeader.sizeof+head.ksiz+head.vsiz <= DP_ENTBUFSIZ) {
930 vbufs[unum].length = head.vsiz;
931 if (head.vsiz > 0) memcpy(vbufs[unum].ptr, ebuf.ptr+(RecordHeader.sizeof+head.ksiz), head.vsiz);
932 } else {
933 vbufs[unum] = recReadValue(off, head);
935 } else {
936 kbufs[unum] = recReadKey(off, head);
937 vbufs[unum] = recReadValue(off, head);
939 ksizs[unum] = head.ksiz;
940 vsizs[unum] = head.vsiz;
941 ++unum;
942 if (unum >= DP_OPTRUNIT) {
943 for (uint i = 0; i < unum; ++i) {
944 assert(kbufs[i] !is null && vbufs[i] !is null);
945 tdepot.put(kbufs[i][0..ksizs[i]], vbufs[i][0..vsizs[i]], WMode.KEEP);
946 kbufs[i].length = 0;
947 vbufs[i].length = 0;
949 unum = 0;
952 off += head.recsize;
954 for (uint i = 0; i < unum; ++i) {
955 assert(kbufs[i] !is null && vbufs[i] !is null);
956 tdepot.put(kbufs[i][0..ksizs[i]], vbufs[i][0..vsizs[i]], WMode.KEEP);
957 kbufs[i].length = 0;
958 vbufs[i].length = 0;
960 tdepot.sync();
961 if (munmap(m_map, m_msiz) == -1) raise(Error.MAP);
962 m_map = cast(char*)MAP_FAILED;
963 if (ftruncate(m_fd, 0) == -1) raise(Error.TRUNC);
964 fcopy(m_fd, 0, tdepot.m_fd, 0);
965 m_fsiz = tdepot.m_fsiz;
966 m_bnum = tdepot.m_bnum;
967 m_ioff = 0;
968 for (uint i = 0; i < m_fbpsiz; i += 2) {
969 m_fbpool[i] = m_fbpool[i+1] = -1;
971 m_msiz = tdepot.m_msiz;
972 m_map = cast(char*)mmap(null, m_msiz, PROT_READ|PROT_WRITE, MAP_SHARED, m_fd, 0);
973 if (m_map == MAP_FAILED) raise(Error.MAP);
974 m_buckets = cast(int*)(m_map+SDBMHeader.sizeof);
975 string tempname = tdepot.m_name; // with trailing zero
976 tdepot.close();
977 if (unlink(tempname.ptr) == -1) raise(Error.UNLINK);
980 /** Get the name of a database.
982 * Returns:
983 * If successful, the return value is the pointer to the region of the name of the database,
984 * else, it is `null`.
986 * Throws:
987 * nothing
989 @property string name () const @safe pure nothrow @nogc {
990 return m_name;
993 /** Get the size of a database file.
995 * Returns:
996 * If successful, the return value is the size of the database file, else, it is -1.
998 * Throws:
999 * SDBMException on various errors
1001 @property long fileSize () {
1002 checkOpened();
1003 return m_fsiz;
1007 /** Get the number of the elements of the bucket array.
1009 * Returns:
1010 * If successful, the return value is the number of the elements of the bucket array, else, it is -1.
1012 * Throws:
1013 * SDBMException on various errors
1015 @property int bucketCount () {
1016 checkOpened();
1017 return m_bnum;
1020 /** Get the number of the used elements of the bucket array.
1022 * This function is inefficient because it accesses all elements of the bucket array.
1024 * Returns:
1025 * If successful, the return value is the number of the used elements of the bucket array, else, it is -1.
1027 * Throws:
1028 * SDBMException on various errors
1030 int bucketUsed () {
1031 checkOpened();
1032 int hits = 0;
1033 foreach (immutable idx; 0..m_bnum) if (m_buckets[idx]) ++hits;
1034 return hits;
1037 /** Get the number of the records stored in a database.
1039 * Returns:
1040 * If successful, the return value is the number of the records stored in the database, else, it is -1.
1042 * Throws:
1043 * SDBMException on various errors
1045 @property int recordCount () {
1046 checkOpened();
1047 return m_rnum;
1050 /** Check whether a database handle is a writer or not.
1052 * Returns:
1053 * The return value is true if the handle is a writer, false if not.
1055 @property bool writable () const @safe pure nothrow @nogc {
1056 return (opened && m_wmode);
1059 /** Check whether a database has a fatal error or not.
1061 * Returns:
1062 * The return value is true if the database has a fatal error, false if not.
1064 @property bool fatalError () const @safe pure nothrow @nogc {
1065 return m_fatal;
1068 /** Get the inode number of a database file.
1070 * Returns:
1071 * The return value is the inode number of the database file.
1073 * Throws:
1074 * SDBMException on various errors
1076 @property long inode () {
1077 checkOpened();
1078 return m_inode;
1081 /** Get the last modified time of a database.
1083 * Returns:
1084 * The return value is the last modified time of the database.
1086 * Throws:
1087 * SDBMException on various errors
1089 @property time_t mtime () {
1090 checkOpened();
1091 return m_mtime;
1094 /** Get the file descriptor of a database file.
1096 * Returns:
1097 * The return value is the file descriptor of the database file.
1098 * Handling the file descriptor of a database file directly is not suggested.
1100 * Throws:
1101 * SDBMException on various errors
1103 @property int fdesc () {
1104 checkOpened();
1105 return m_fd;
1108 /* ********************************************************************************************* *
1109 * features for experts
1110 * ********************************************************************************************* */
1112 /** Synchronize updating contents on memory.
1114 * Throws:
1115 * SDBMException on various errors
1117 void memsync () {
1118 import core.sys.posix.sys.mman : msync, MS_SYNC;
1119 checkWriting();
1120 updateHeader();
1121 if (msync(m_map, m_msiz, MS_SYNC) == -1) {
1122 m_fatal = true;
1123 raise(Error.MAP);
1127 /** Synchronize updating contents on memory, not physically.
1129 * Throws:
1130 * SDBMException on various errors
1132 void memflush () {
1133 checkWriting();
1134 updateHeader();
1135 // there is no mflush() call
1136 version(none) {
1137 if (mflush(m_map, m_msiz, MS_SYNC) == -1) {
1138 m_fatal = true;
1139 raise(Error.MAP);
1144 /** Get flags of a database.
1146 * Engine doesn't use database flags in any way.
1148 * Returns:
1149 * The return value is the flags of a database.
1151 * Throws:
1152 * SDBMException on various errors
1154 @property int flags () {
1155 checkOpened();
1156 auto hdr = cast(SDBMHeader*)m_map;
1157 return hdr.flags;
1160 /** Set flags of a database.
1162 * Engine doesn't use database flags in any way.
1164 * Params:
1165 * v = flags to set.
1167 * Returns:
1168 * If successful, the return value is true, else, it is false.
1170 * Throws:
1171 * SDBMException on various errors
1173 @property void flags (int v) {
1174 checkWriting();
1175 auto hdr = cast(SDBMHeader*)m_map;
1176 hdr.flags = v;
1179 private:
1180 /* ********************************************************************************************* *
1181 * private objects
1182 * ********************************************************************************************* */
1184 void raise (Error errcode, string file=__FILE__, usize line=__LINE__) {
1185 assert(errcode >= Error.NOERR);
1186 if (errcode == Error.FATAL) m_fatal = true;
1187 throw new SDBMException(errcode, file, line);
1190 static void straise (Error errcode, string file=__FILE__, usize line=__LINE__) {
1191 assert(errcode >= Error.NOERR);
1192 throw new SDBMException(errcode, file, line);
1195 void updateHeader () @trusted nothrow @nogc {
1196 auto hdr = cast(SDBMHeader*)m_map;
1197 hdr.filesize = cast(int)m_fsiz;
1198 hdr.nrecords = m_rnum;
1201 /* Lock a file descriptor.
1203 * Params:
1204 * fd = a file descriptor
1205 * ex = whether an exclusive lock or a shared lock is performed
1206 * nb = whether to request with non-blocking
1207 * errcode = the error code (can be `null`)
1209 * Throws:
1210 * SDBMException on various errors
1212 static void fdlock (int fd, bool ex, bool nb) {
1213 import core.stdc.stdio : SEEK_SET;
1214 import core.stdc.string : memset;
1215 import core.sys.posix.fcntl : flock, fcntl, F_RDLCK, F_SETLK, F_SETLKW, F_WRLCK;
1216 flock lock;
1217 assert(fd >= 0);
1218 memset(&lock, 0, lock.sizeof);
1219 lock.l_type = (ex ? F_WRLCK : F_RDLCK);
1220 lock.l_whence = SEEK_SET;
1221 lock.l_start = 0;
1222 lock.l_len = 0;
1223 lock.l_pid = 0;
1224 while (fcntl(fd, (nb ? F_SETLK : F_SETLKW), &lock) == -1) {
1225 import core.stdc.errno : errno, EINTR;
1226 if (errno != EINTR) straise(Error.LOCK);
1230 /* Write into a file.
1232 * Params:
1233 * fd = a file descriptor
1234 * buf = a buffer to write
1236 * Returns:
1237 * The return value is the size of the written buffer, or -1 on failure
1239 * Throws:
1240 * Nothing
1242 static int fdwrite (int fd, const(void)[] buf) @trusted nothrow @nogc {
1243 auto lbuf = cast(const(ubyte)[])buf;
1244 int rv = 0;
1245 assert(fd >= 0);
1246 while (lbuf.length > 0) {
1247 import core.sys.posix.unistd : write;
1248 auto wb = write(fd, lbuf.ptr, lbuf.length);
1249 if (wb == -1) {
1250 import core.stdc.errno : errno, EINTR;
1251 if (errno != EINTR) return -1;
1252 continue;
1254 if (wb == 0) break;
1255 lbuf = lbuf[wb..$];
1256 rv += cast(int)wb;
1258 return rv;
1261 /* Write into a file at an offset.
1263 * Params:
1264 * fd = a file descriptor
1265 * off = an offset of the file
1266 * buf = a buffer to write
1268 * Throws:
1269 * SDBMException on various errors
1271 static void fdseekwrite (int fd, long off, const(void)[] buf) {
1272 import core.stdc.stdio : SEEK_END, SEEK_SET;
1273 import core.sys.posix.unistd : lseek;
1274 assert(fd >= 0);
1275 if (buf.length < 1) return;
1276 if (lseek(fd, (off < 0 ? 0 : off), (off < 0 ? SEEK_END : SEEK_SET)) == -1) straise(Error.SEEK);
1277 if (fdwrite(fd, buf) != buf.length) straise(Error.WRITE);
1280 /* Write an integer into a file at an offset.
1282 * Params:
1283 * fd = a file descriptor
1284 * off = an offset of the file
1285 * num = an integer
1287 * Throws:
1288 * SDBMException on various errors
1290 void fdseekwritenum (int fd, long off, int num) {
1291 assert(fd >= 0);
1292 scope(failure) m_fatal = true;
1293 fdseekwrite(fd, off, (&num)[0..1]);
1296 /* Read from a file and store the data into a buffer.
1298 * Params:
1299 * fd = a file descriptor
1300 * buf = a buffer to store into.
1302 * Returns:
1303 * The return value is the size read with, or -1 on failure
1305 * Throws:
1306 * Nothing
1308 static int fdread (int fd, void[] buf) @trusted nothrow @nogc {
1309 import core.sys.posix.unistd : read;
1310 auto lbuf = cast(ubyte[])buf;
1311 int total = 0;
1312 assert(fd >= 0);
1313 while (lbuf.length > 0) {
1314 auto bs = read(fd, lbuf.ptr, lbuf.length);
1315 if (bs < 0) {
1316 import core.stdc.errno : errno, EINTR;
1317 if (errno != EINTR) return -1;
1318 continue;
1320 if (bs == 0) break;
1321 lbuf = lbuf[bs..$];
1322 total += cast(int)bs;
1324 return total;
1327 /* Read from a file at an offset and store the data into a buffer.
1329 * Params:
1330 * fd = a file descriptor
1331 * off = an offset of the file
1332 * buf = a buffer to store into
1334 * Throws:
1335 * SDBMException on various errors
1337 static void fdseekread (int fd, long off, void[] buf) {
1338 import core.stdc.stdio : SEEK_SET;
1339 import core.sys.posix.unistd : lseek;
1340 assert(fd >= 0 && off >= 0);
1341 if (lseek(fd, off, SEEK_SET) != off) straise(Error.SEEK);
1342 if (fdread(fd, buf) != buf.length) straise(Error.READ);
1345 /* Copy data between files.
1347 * Params:
1348 * destfd = a file descriptor of a destination file
1349 * destoff = an offset of the destination file
1350 * srcfd = a file descriptor of a source file
1351 * srcoff = an offset of the source file
1353 * Returns:
1354 * The return value is the size copied with
1356 * Throws:
1357 * SDBMException on various errors
1359 static int fcopy (int destfd, long destoff, int srcfd, long srcoff) {
1360 import core.stdc.stdio : SEEK_SET;
1361 import core.sys.posix.unistd : lseek;
1362 char[DP_IOBUFSIZ] iobuf;
1363 int sum, iosiz;
1364 if (lseek(srcfd, srcoff, SEEK_SET) == -1 || lseek(destfd, destoff, SEEK_SET) == -1) straise(Error.SEEK);
1365 sum = 0;
1366 while ((iosiz = fdread(srcfd, iobuf[])) > 0) {
1367 if (fdwrite(destfd, iobuf[0..iosiz]) != iosiz) straise(Error.WRITE);
1368 sum += iosiz;
1370 if (iosiz < 0) straise(Error.READ);
1371 return sum;
1374 /* Get the padding size of a record.
1376 * Params:
1377 * ksiz = the size of the key of a record
1378 * vsiz = the size of the value of a record
1380 * Returns:
1381 * The return value is the padding size of a record
1383 usize padsize (usize ksiz, usize vsiz) const @safe pure nothrow @nogc {
1384 if (m_alignment > 0) {
1385 return cast(usize)(m_alignment-(m_fsiz+RecordHeader.sizeof+ksiz+vsiz)%m_alignment);
1386 } else if (m_alignment < 0) {
1387 usize pad = cast(usize)(vsiz*(2.0/(1<<(-m_alignment))));
1388 if (vsiz+pad >= DP_FSBLKSIZ) {
1389 if (vsiz <= DP_FSBLKSIZ) pad = 0;
1390 if (m_fsiz%DP_FSBLKSIZ == 0) {
1391 return cast(usize)((pad/DP_FSBLKSIZ)*DP_FSBLKSIZ+DP_FSBLKSIZ-(m_fsiz+RecordHeader.sizeof+ksiz+vsiz)%DP_FSBLKSIZ);
1392 } else {
1393 return cast(usize)((pad/(DP_FSBLKSIZ/2))*(DP_FSBLKSIZ/2)+(DP_FSBLKSIZ/2)-
1394 (m_fsiz+RecordHeader.sizeof+ksiz+vsiz)%(DP_FSBLKSIZ/2));
1396 } else {
1397 return (pad >= RecordHeader.sizeof ? pad : RecordHeader.sizeof);
1400 return 0;
1403 /* Read the header of a record.
1405 * Params:
1406 * off = an offset of the database file
1407 * head = specifies a buffer for the header
1408 * ebuf = specifies the pointer to the entity buffer
1409 * eep = the pointer to a variable to which whether ebuf was used is assigned
1411 * Throws:
1412 * SDBMException on various errors
1414 void rechead (long off, ref RecordHeader head, void[] ebuf, bool* eep) {
1415 assert(off >= 0);
1416 if (eep !is null) *eep = false;
1417 if (off < 0 || off > m_fsiz) raise(Error.BROKEN);
1418 scope(failure) m_fatal = true; // any failure is fatal here
1419 if (ebuf.length >= DP_ENTBUFSIZ && off < m_fsiz-DP_ENTBUFSIZ) {
1420 import core.stdc.string : memcpy;
1421 if (eep !is null) *eep = true;
1422 fdseekread(m_fd, off, ebuf[0..DP_ENTBUFSIZ]);
1423 memcpy(&head, ebuf.ptr, RecordHeader.sizeof);
1424 } else {
1425 fdseekread(m_fd, off, (&head)[0..1]);
1427 if (head.ksiz < 0 || head.vsiz < 0 || head.psiz < 0 || head.left < 0 || head.right < 0) raise(Error.BROKEN);
1430 /* Read the entitiy of the key of a record.
1432 * Params:
1433 * off = an offset of the database file
1434 * head = the header of a record
1436 * Returns:
1437 * The return value is a key data whose region is allocated by GC
1439 * Throws:
1440 * SDBMException on various errors
1442 T[] recReadKey(T=char) (long off, ref in RecordHeader head) if (T.sizeof == 1) {
1443 T[] kbuf;
1444 assert(off >= 0);
1445 int ksiz = head.ksiz;
1446 if (ksiz > 0) {
1447 kbuf.length = ksiz;
1448 fdseekread(m_fd, off+RecordHeader.sizeof, kbuf[0..ksiz]);
1449 } else {
1450 kbuf = new T[](1);
1451 kbuf.length = 0;
1453 return kbuf;
1456 /* Read the entitiy of the value of a record.
1458 * Params:
1459 * off = an offset of the database file
1460 * head = the header of a record
1461 * start = the offset address of the beginning of the region of the value to be read
1462 * max = the max size to be read; if it is `uint.max`, the size to read is unlimited
1464 * Returns:
1465 * The return value is a value data whose region is allocated with GC.
1467 * Throws:
1468 * SDBMException on various errors
1470 T[] recReadValue(T=char) (long off, ref RecordHeader head, uint start=0, uint max=uint.max) if (T.sizeof == 1) {
1471 T[] vbuf;
1472 uint vsiz;
1473 assert(off >= 0);
1474 head.vsiz -= start;
1475 if (max == uint.max) {
1476 vsiz = head.vsiz;
1477 } else {
1478 vsiz = (max < head.vsiz ? max : head.vsiz);
1480 scope(failure) m_fatal = true;
1481 if (vsiz > 0) {
1482 vbuf.length = vsiz;
1483 fdseekread(m_fd, off+RecordHeader.sizeof+head.ksiz+start, vbuf[0..vsiz]);
1484 } else {
1485 vbuf = new T[](1);
1486 vbuf.length = 0;
1488 return vbuf;
1491 /* Read the entitiy of the value of a record and write it into a given buffer.
1493 * Params:
1494 * off = an offset of the database file
1495 * head = the header of a record
1496 * start = the offset address of the beginning of the region of the value to be read
1497 * vbuf = the pointer to a buffer into which the value of the corresponding record is written
1499 * Returns:
1500 * Read data (can be less then vbuf)
1502 * Throws:
1503 * SDBMException on various errors
1505 T[] recReadValueToBuf(T=char) (T[] vbuf, long off, ref RecordHeader head, uint start=0) if (T.sizeof == 1) {
1506 assert(off >= 0);
1507 head.vsiz -= start;
1508 usize vsiz = (vbuf.length < head.vsiz ? vbuf.length : head.vsiz);
1509 fdseekread(m_fd, off+RecordHeader.sizeof+head.ksiz+start, vbuf[0..vsiz]);
1510 return vbuf[0..vsiz];
1513 /* Compare two keys.
1515 * Params:
1516 * abuf = the pointer to the region of the former
1517 * asiz = the size of the region
1518 * bbuf = the pointer to the region of the latter
1519 * bsiz = the size of the region
1521 * Returns:
1522 * The return value is 0 if two equals, positive if the formar is big, else, negative.
1524 static int keycmp (const(void)[] abuf, const(void)[] bbuf) @trusted nothrow @nogc {
1525 import core.stdc.string : memcmp;
1526 //assert(abuf && asiz >= 0 && bbuf && bsiz >= 0);
1527 if (abuf.length > bbuf.length) return 1;
1528 if (abuf.length < bbuf.length) return -1;
1529 return memcmp(abuf.ptr, bbuf.ptr, abuf.length);
1532 /* Search for a record.
1534 * Params:
1535 * kbuf = the pointer to the region of a key
1536 * hash = the second hash value of the key
1537 * bip = the pointer to the region to assign the index of the corresponding record
1538 * offp = the pointer to the region to assign the last visited node in the hash chain,
1539 * or, -1 if the hash chain is empty
1540 * entp = the offset of the last used joint, or, -1 if the hash chain is empty
1541 * head = the pointer to the region to store the header of the last visited record in
1542 * ebuf = the pointer to the entity buffer
1543 * eep = the pointer to a variable to which whether ebuf was used is assigned
1544 * delhit = whether a deleted record corresponds or not
1545 * hout = calculated "second hash" of a key
1547 * Returns:
1548 * The return value is true if record was found, false if there is no corresponding record.
1550 * Throws:
1551 * SDBMException on various errors
1553 bool recsearch (const(void)[] kbuf, int* bip, int* offp, int* entp,
1554 ref RecordHeader head, void[] ebuf, bool* eep, Flag!"delhit" delhit=No.delhit, int* hout=null)
1556 usize off;
1557 int entoff;
1558 int kcmp;
1559 char[DP_STKBUFSIZ] stkey;
1560 assert(ebuf.length >= DP_ENTBUFSIZ);
1561 assert(bip !is null && offp !is null && entp !is null && eep !is null);
1562 int hash = fastHash32(kbuf, 0xdeadf00dU)&0x7fff_ffff;
1563 if (hout !is null) *hout = hash;
1564 int thash = fastHash32(kbuf, 0xf00ddeadU)&0x7fff_ffff;
1565 *bip = thash%m_bnum;
1566 off = m_buckets[*bip];
1567 *offp = -1;
1568 *entp = -1;
1569 entoff = -1;
1570 *eep = false;
1571 while (off != 0) {
1572 rechead(off, head, ebuf, eep);
1573 thash = head.hash2;
1574 if (hash > thash) {
1575 entoff = cast(int)(off+RecordHeader.left.offsetof);
1576 off = head.left;
1577 } else if (hash < thash) {
1578 entoff = cast(int)(off+RecordHeader.right.offsetof);
1579 off = head.right;
1580 } else {
1581 if (*eep && RecordHeader.sizeof+head.ksiz <= DP_ENTBUFSIZ) {
1582 immutable ebstart = RecordHeader.sizeof;
1583 kcmp = keycmp(kbuf, ebuf[ebstart..ebstart+head.ksiz]);
1584 } else if (head.ksiz > DP_STKBUFSIZ) {
1585 auto tkey = recReadKey(off, head);
1586 kcmp = keycmp(kbuf, tkey[0..head.ksiz]);
1587 tkey.length = 0;
1588 } else {
1589 try {
1590 fdseekread(m_fd, off+RecordHeader.sizeof, stkey[0..head.ksiz]);
1591 } catch (Exception) {
1592 raise(Error.FATAL);
1594 kcmp = keycmp(kbuf, stkey[0..head.ksiz]);
1596 if (kcmp > 0) {
1597 entoff = cast(int)(off+RecordHeader.left.offsetof);
1598 off = head.left;
1599 } else if (kcmp < 0) {
1600 entoff = cast(int)(off+RecordHeader.right.offsetof);
1601 off = head.right;
1602 } else {
1603 if (!delhit && (head.flags&DP_RECFDEL)) {
1604 entoff = cast(int)(off+RecordHeader.left.offsetof);
1605 off = head.left;
1606 } else {
1607 *offp = cast(int)off;
1608 *entp = entoff;
1609 return true;
1614 *offp = cast(int)off;
1615 *entp = entoff;
1616 return false;
1619 /* Overwrite a record.
1621 * Params:
1622 * off = the offset of the database file
1623 * rsiz = the size of the existing record
1624 * kbuf = the pointer to the region of a key
1625 * vbuf = the pointer to the region of a value
1626 * hash = the second hash value of the key
1627 * left = the offset of the left child
1628 * right = the offset of the right child
1630 * Throws:
1631 * SDBMException on various errors
1633 void recrewrite (long off, int rsiz, const(void)[] kbuf, const(void)[] vbuf, int hash, int left, int right) {
1634 char[DP_WRTBUFSIZ] ebuf;
1635 RecordHeader head;
1636 int hoff, koff, voff, mi, min, size;
1637 usize asiz;
1638 assert(off >= 1 && rsiz > 0);
1639 head.flags = 0;
1640 head.hash2 = hash;
1641 head.ksiz = cast(int)kbuf.length;
1642 head.vsiz = cast(int)vbuf.length;
1643 head.psiz = cast(int)(rsiz-head.sizeof-kbuf.length-vbuf.length);
1644 head.left = left;
1645 head.right = right;
1646 asiz = head.sizeof+kbuf.length+vbuf.length;
1647 if (m_fbpsiz > DP_FBPOOLSIZ*4 && head.psiz > asiz) {
1648 rsiz = cast(int)((head.psiz-asiz)/2+asiz);
1649 head.psiz -= rsiz;
1650 } else {
1651 rsiz = 0;
1653 if (asiz <= DP_WRTBUFSIZ) {
1654 import core.stdc.string : memcpy;
1655 memcpy(ebuf.ptr, &head, head.sizeof);
1656 memcpy(ebuf.ptr+head.sizeof, kbuf.ptr, kbuf.length);
1657 memcpy(ebuf.ptr+head.sizeof+kbuf.length, vbuf.ptr, vbuf.length);
1658 fdseekwrite(m_fd, off, ebuf[0..asiz]);
1659 } else {
1660 hoff = cast(int)off;
1661 koff = cast(int)(hoff+head.sizeof);
1662 voff = cast(int)(koff+kbuf.length);
1663 fdseekwrite(m_fd, hoff, (&head)[0..1]);
1664 fdseekwrite(m_fd, koff, kbuf[]);
1665 fdseekwrite(m_fd, voff, vbuf[]);
1667 if (rsiz > 0) {
1668 off += head.sizeof+kbuf.length+vbuf.length+head.psiz;
1669 head.flags = DP_RECFDEL|DP_RECFREUSE;
1670 head.hash2 = hash;
1671 head.ksiz = cast(int)kbuf.length;
1672 head.vsiz = cast(int)vbuf.length;
1673 head.psiz = cast(int)(rsiz-head.sizeof-kbuf.length-vbuf.length);
1674 head.left = 0;
1675 head.right = 0;
1676 fdseekwrite(m_fd, off, (&head)[0..1]);
1677 size = head.recsize;
1678 mi = -1;
1679 min = -1;
1680 for (uint i = 0; i < m_fbpsiz; i += 2) {
1681 if (m_fbpool[i] == -1) {
1682 m_fbpool[i] = cast(int)off;
1683 m_fbpool[i+1] = size;
1684 fbpoolcoal();
1685 mi = -1;
1686 break;
1688 if (mi == -1 || m_fbpool[i+1] < min) {
1689 mi = i;
1690 min = m_fbpool[i+1];
1693 if (mi >= 0 && size > min) {
1694 m_fbpool[mi] = cast(int)off;
1695 m_fbpool[mi+1] = size;
1696 fbpoolcoal();
1701 /* Write a record at the end of a database file.
1703 * Params:
1704 * kbuf = the pointer to the region of a key
1705 * vbuf = the pointer to the region of a value
1706 * hash = the second hash value of the key
1707 * left = the offset of the left child
1708 * right = the offset of the right child
1710 * Returns:
1711 * The return value is the offset of the record
1713 * Throws:
1714 * SDBMException on various errors
1716 int recappend (const(void)[] kbuf, const(void)[] vbuf, int hash, int left, int right) {
1717 char[DP_WRTBUFSIZ] ebuf;
1718 RecordHeader head;
1719 usize asiz, psiz;
1720 long off;
1721 psiz = padsize(kbuf.length, vbuf.length);
1722 head.flags = 0;
1723 head.hash2 = hash;
1724 head.ksiz = cast(int)kbuf.length;
1725 head.vsiz = cast(int)vbuf.length;
1726 head.psiz = cast(int)psiz;
1727 head.left = left;
1728 head.right = right;
1729 asiz = head.sizeof+kbuf.length+vbuf.length+psiz;
1730 off = m_fsiz;
1731 if (asiz <= DP_WRTBUFSIZ) {
1732 import core.stdc.string : memcpy, memset;
1733 memcpy(ebuf.ptr, &head, head.sizeof);
1734 memcpy(ebuf.ptr+head.sizeof, kbuf.ptr, kbuf.length);
1735 memcpy(ebuf.ptr+head.sizeof+kbuf.length, vbuf.ptr, vbuf.length);
1736 memset(ebuf.ptr+head.sizeof+kbuf.length+vbuf.length, 0, psiz);
1737 fdseekwrite(m_fd, off, ebuf[0..asiz]);
1738 } else {
1739 import core.stdc.string : memcpy, memset;
1740 ubyte[8192] tbuf;
1741 ubyte[] hbuf;
1742 if (asiz <= tbuf.length) hbuf = tbuf[0..asiz]; else hbuf.length = asiz;
1743 memcpy(hbuf.ptr, &head, head.sizeof);
1744 memcpy(hbuf.ptr+head.sizeof, kbuf.ptr, kbuf.length);
1745 memcpy(hbuf.ptr+head.sizeof+kbuf.length, vbuf.ptr, vbuf.length);
1746 memset(hbuf.ptr+head.sizeof+kbuf.length+vbuf.length, 0, psiz);
1747 fdseekwrite(m_fd, off, hbuf[0..asiz]);
1749 m_fsiz += asiz;
1750 return cast(int)off;
1753 /* Overwrite the value of a record.
1755 * Params:
1756 * off = the offset of the database file
1757 * head = the header of the record
1758 * vbuf = the pointer to the region of a value
1759 * cat = whether it is concatenate mode or not
1761 * Throws:
1762 * SDBMException on various errors
1764 void recover (long off, ref RecordHeader head, const(void)[] vbuf, Flag!"catmode" catmode) {
1765 assert(off >= 0);
1766 for (uint i = 0; i < m_fbpsiz; i += 2) {
1767 if (m_fbpool[i] == off) {
1768 m_fbpool[i] = m_fbpool[i+1] = -1;
1769 break;
1772 head.flags = 0;
1773 long voff = off+RecordHeader.sizeof+head.ksiz;
1774 if (catmode) {
1775 head.psiz -= vbuf.length;
1776 head.vsiz += vbuf.length;
1777 voff += head.vsiz-vbuf.length;
1778 } else {
1779 head.psiz += head.vsiz-vbuf.length;
1780 head.vsiz = cast(int)vbuf.length;
1782 scope(failure) m_fatal = true; // any failure is fatal here
1783 fdseekwrite(m_fd, off, (&head)[0..1]);
1784 fdseekwrite(m_fd, voff, vbuf[]);
1787 /* Delete a record.
1789 * Params:
1790 * off = the offset of the database file
1791 * head = the header of the record
1792 * reusable = whether the region is reusable or not
1794 * Throws:
1795 * SDBMException on various errors
1797 void recdelete (long off, ref in RecordHeader head, Flag!"reusable" reusable) {
1798 assert(off >= 0);
1799 if (reusable) {
1800 auto size = head.recsize;
1801 int mi = -1;
1802 int min = -1;
1803 for (uint i = 0; i < m_fbpsiz; i += 2) {
1804 if (m_fbpool[i] == -1) {
1805 m_fbpool[i] = cast(int)off;
1806 m_fbpool[i+1] = size;
1807 fbpoolcoal();
1808 mi = -1;
1809 break;
1811 if (mi == -1 || m_fbpool[i+1] < min) {
1812 mi = i;
1813 min = m_fbpool[i+1];
1816 if (mi >= 0 && size > min) {
1817 m_fbpool[mi] = cast(int)off;
1818 m_fbpool[mi+1] = size;
1819 fbpoolcoal();
1822 fdseekwritenum(m_fd, off+RecordHeader.flags.offsetof, DP_RECFDEL|(reusable ? DP_RECFREUSE : 0));
1825 /* Make contiguous records of the free block pool coalesce. */
1826 void fbpoolcoal () @trusted {
1827 import core.stdc.stdlib : qsort;
1828 if (m_fbpinc++ <= m_fbpsiz/4) return;
1829 m_fbpinc = 0;
1830 qsort(m_fbpool, m_fbpsiz/2, int.sizeof*2, &fbpoolcmp);
1831 for (uint i = 2; i < m_fbpsiz; i += 2) {
1832 if (m_fbpool[i-2] > 0 && m_fbpool[i-2]+m_fbpool[i-1]-m_fbpool[i] == 0) {
1833 m_fbpool[i] = m_fbpool[i-2];
1834 m_fbpool[i+1] += m_fbpool[i-1];
1835 m_fbpool[i-2] = m_fbpool[i-1] = -1;
1840 /* Compare two records of the free block pool.
1841 a = the pointer to one record.
1842 b = the pointer to the other record.
1843 The return value is 0 if two equals, positive if the formar is big, else, negative. */
1844 static extern(C) int fbpoolcmp (in void* a, in void* b) @trusted nothrow @nogc {
1845 assert(a && b);
1846 return *cast(const int*)a - *cast(const int*)b;
1849 /* ********************************************************************************************* *
1850 * static utilities
1851 * ********************************************************************************************* */
1852 public:
1853 static:
1854 /** Remove a database file.
1856 * Params:
1857 * name = the name of a database file
1859 * Throws:
1860 * SDBMException on various errors
1862 void remove (const(char)[] name) {
1863 import core.stdc.errno : errno, ENOENT;
1864 import core.sys.posix.sys.stat : lstat, stat_t;
1865 import core.sys.posix.unistd : unlink;
1866 import std.string : toStringz;
1867 stat_t sbuf;
1868 assert(name.length);
1869 auto namez = name.toStringz;
1870 if (lstat(namez, &sbuf) == -1) {
1871 if (errno != ENOENT) straise(Error.STAT);
1872 // no file
1873 return;
1875 //k8:??? try to open the file to check if it's not locked or something
1876 auto depot = new SDBM(name, WRITER|TRUNC);
1877 delete depot;
1878 // remove file
1879 if (unlink(namez) == -1) {
1880 if (errno != ENOENT) straise(Error.UNLINK);
1881 // no file
1885 /** Repair a broken database file.
1887 * There is no guarantee that all records in a repaired database file correspond to the original
1888 * or expected state.
1890 * Params:
1891 * name = the name of a database file
1893 * Returns:
1894 * true if ok, false is there were some errors
1896 * Throws:
1897 * SDBMException on various errors
1899 bool repair (const(char)[] name) {
1900 import core.sys.posix.fcntl : open, O_RDWR;
1901 import core.sys.posix.sys.stat : lstat, stat_t;
1902 import core.sys.posix.unistd : close, ftruncate, unlink;
1903 SDBM tdepot;
1904 SDBMHeader dbhead;
1905 char* kbuf, vbuf;
1906 RecordHeader head;
1907 int fd, flags, bnum, tbnum, rsiz, ksiz, vsiz;
1908 usize off;
1909 long fsiz;
1910 stat_t sbuf;
1911 assert(name.length);
1913 import std.string : toStringz;
1914 auto namez = name.toStringz;
1915 if (lstat(namez, &sbuf) == -1) throw new SDBMException(Error.STAT); //raise(Error.STAT);
1916 fsiz = sbuf.st_size;
1917 if ((fd = open(namez, O_RDWR, DP_FILEMODE)) == -1) throw new SDBMException(Error.OPEN); //raise(Error.OPEN);
1919 scope(exit) if (fd >= 0) close(fd);
1920 fdseekread(fd, 0, (&dbhead)[0..1]);
1921 flags = dbhead.flags;
1922 bnum = dbhead.nbuckets;
1923 tbnum = dbhead.nrecords*2;
1924 if (tbnum < DP_DEFBNUM) tbnum = DP_DEFBNUM;
1925 tdepot = new SDBM(name~DP_TMPFSUF, WRITER|CREAT|TRUNC, tbnum);
1926 off = SDBMHeader.sizeof+bnum*int.sizeof;
1927 bool err = false;
1928 while (off < fsiz) {
1929 try {
1930 fdseekread(fd, off, (&head)[0..1]);
1931 } catch (Exception) {
1932 break;
1934 if (head.flags&DP_RECFDEL) {
1935 rsiz = head.recsize;
1936 if (rsiz < 0) break;
1937 off += rsiz;
1938 continue;
1940 ksiz = head.ksiz;
1941 vsiz = head.vsiz;
1942 if (ksiz >= 0 && vsiz >= 0) {
1943 kbuf = alloc!char(ksiz);
1944 vbuf = alloc!char(vsiz);
1945 if (kbuf !is null && vbuf !is null) {
1946 try {
1947 fdseekread(fd, off+RecordHeader.sizeof, kbuf[0..ksiz]);
1948 fdseekread(fd, off+RecordHeader.sizeof+ksiz, vbuf[0..vsiz]);
1949 tdepot.put(kbuf[0..ksiz], vbuf[0..vsiz], WMode.KEEP);
1950 } catch (Exception) {
1951 err = true;
1953 } else {
1954 //if (!err) raise(Error.ALLOC);
1955 err = true;
1957 if (vbuf !is null) freeptr(vbuf);
1958 if (kbuf !is null) freeptr(kbuf);
1959 } else {
1960 //if (!err) raise(Error.BROKEN);
1961 err = true;
1963 rsiz = head.recsize;
1964 if (rsiz < 0) break;
1965 off += rsiz;
1967 tdepot.flags = flags; // err = true;
1968 try {
1969 tdepot.sync();
1970 } catch (Exception) {
1971 err = true;
1973 if (ftruncate(fd, 0) == -1) {
1974 //if (!err) raise(Error.TRUNC);
1975 err = true;
1977 auto tempname = tdepot.m_name; // with trailing zero
1978 try {
1979 fcopy(fd, 0, tdepot.m_fd, 0);
1980 tdepot.close();
1981 } catch (Exception) {
1982 err = true;
1984 if (close(fd) == -1) {
1985 //if (!err) raise(Error.CLOSE);
1986 err = true;
1988 fd = -1;
1989 if (unlink(tempname.ptr) == -1) {
1990 //if (!err) raise(Error.UNLINK);
1991 err = true;
1993 return !err;
1996 /** Get a natural prime number not less than a number.
1998 * This function is useful when an application determines the size of a bucket array of its
1999 * own hash algorithm.
2001 * Params:
2002 * num = a natural number
2004 * Returns:
2005 * The return value is a natural prime number not less than the specified number
2007 int primenum (int num) @safe pure nothrow @nogc {
2008 static immutable int[217] primes = [
2009 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 43, 47, 53, 59, 61, 71, 79, 83,
2010 89, 103, 109, 113, 127, 139, 157, 173, 191, 199, 223, 239, 251, 283, 317, 349,
2011 383, 409, 443, 479, 509, 571, 631, 701, 761, 829, 887, 953, 1021, 1151, 1279,
2012 1399, 1531, 1663, 1789, 1913, 2039, 2297, 2557, 2803, 3067, 3323, 3583, 3833,
2013 4093, 4603, 5119, 5623, 6143, 6653, 7159, 7673, 8191, 9209, 10223, 11261,
2014 12281, 13309, 14327, 15359, 16381, 18427, 20479, 22511, 24571, 26597, 28669,
2015 30713, 32749, 36857, 40949, 45053, 49139, 53239, 57331, 61417, 65521, 73727,
2016 81919, 90107, 98299, 106487, 114679, 122869, 131071, 147451, 163819, 180221,
2017 196597, 212987, 229373, 245759, 262139, 294911, 327673, 360439, 393209, 425977,
2018 458747, 491503, 524287, 589811, 655357, 720887, 786431, 851957, 917503, 982981,
2019 1048573, 1179641, 1310719, 1441771, 1572853, 1703903, 1835003, 1966079,
2020 2097143, 2359267, 2621431, 2883577, 3145721, 3407857, 3670013, 3932153,
2021 4194301, 4718579, 5242877, 5767129, 6291449, 6815741, 7340009, 7864301,
2022 8388593, 9437179, 10485751, 11534329, 12582893, 13631477, 14680063, 15728611,
2023 16777213, 18874367, 20971507, 23068667, 25165813, 27262931, 29360087, 31457269,
2024 33554393, 37748717, 41943023, 46137319, 50331599, 54525917, 58720253, 62914549,
2025 67108859, 75497467, 83886053, 92274671, 100663291, 109051903, 117440509,
2026 125829103, 134217689, 150994939, 167772107, 184549373, 201326557, 218103799,
2027 234881011, 251658227, 268435399, 301989881, 335544301, 369098707, 402653171,
2028 436207613, 469762043, 503316469, 536870909, 603979769, 671088637, 738197503,
2029 805306357, 872415211, 939524087, 1006632947, 1073741789, 1207959503,
2030 1342177237, 1476394991, 1610612711, 1744830457, 1879048183, 2013265907,
2032 assert(num > 0);
2033 foreach (immutable pr; primes) if (num <= pr) return pr;
2034 return primes[$-1];
2037 private:
2038 /** Free `malloc()`ed pointer and set variable to `null`.
2040 * Params:
2041 * ptr = the pointer to variable holding a pointer
2043 static freeptr(T) (ref T* ptr) {
2044 import core.stdc.stdlib : free;
2045 if (ptr !is null) {
2046 free(cast(void*)ptr);
2047 ptr = null;
2051 // `noerr`: "nothrow" --> return `null` on error
2052 static T* alloc(T, string noerr=null) (usize count=1) {
2053 import core.stdc.stdlib : malloc;
2054 import core.stdc.string : memset;
2055 //TODO: overflow checking
2056 usize asz = T.sizeof*count;
2057 if (asz == 0) asz = 1;
2058 void* res = malloc(asz);
2059 if (res is null) {
2060 static if (noerr == "nothrow") throw new SDBMException(Error.ALLOC);
2061 } else {
2062 memset(res, 0, asz);
2064 return cast(T*)res;