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
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
16 * ********************************************************************************************* */
17 // key/value database based on QDBM
18 module iv
.sdbm
/*is aliced*/;
20 import std
.traits
: isArray
, isDynamicArray
;
22 import iv
.hash
.fasthash
;
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.
34 * ecode = an error code
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)";
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
); }
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?)
103 public static ushort DP_FILEMODE
= 384; // 0o600: permission of a creating file
105 enum DP_MAGIC
= "[SDBMFILE]\n\f"; // magic on environments of big endian
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
124 DP_RECFDEL
= 0x01, // deleted
125 DP_RECFREUSE
= 0x02, // reusable
128 static align(1) struct SDBMHeader
{
130 char[12] signature
; // DP_MAGICNUMB or DP_MAGICNUML, padded with '\0'
131 char[4] versionstr
; // string, padded with '\0'
136 int nbuckets
; // number of buckets
138 int nrecords
; // number of records
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
{
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.
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
);
178 /// enumeration for error codes
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
191 CLOSE
, /// close error
192 TRUNC
, /// trunc error
197 WRITE
, /// write 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
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
218 OVER
, /// overwrite an existing value
219 KEEP
, /// keep an existing value
220 CAT
, /// concatenate values
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.
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
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
;
276 if (opened
) raise(Error
.OPENED
);
278 char[] namez
; // unique
279 // add '\0' after string
282 while (len
< name
.length
&& name
[len
]) ++len
;
283 namez
= new char[](len
+1);
284 namez
[0..$-1] = name
[0..len
];
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
);
300 mtime
= sbuf
.st_mtime
;
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
;
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
;
318 usize left
= cast(usize
)fsiz
-pos
;
319 usize wr
= (left
> ebuf
.length ? ebuf
.length
: left
);
320 fdseekwrite(fd
, pos
, ebuf
[0..wr
]);
326 fdseekread(fd
, 0, (&hbuf
)[0..1]);
327 } catch (Exception
) {
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;
350 m_msiz
= cast(int)msiz
;
351 m_buckets
= cast(int*)(map
+SDBMHeader
.sizeof
);
353 m_rnum
= hbuf
.nrecords
;
357 m_fbpool
[0..DP_FBPOOLSIZ
*2] = -1;
358 m_fbpsiz
= DP_FBPOOLSIZ
*2;
363 /** Close a database handle.
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.
372 * SDBMException on various errors
375 import core
.sys
.posix
.sys
.mman
: munmap
, MAP_FAILED
;
376 import core
.sys
.posix
.unistd
: close
;
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
;
385 if (close(m_fd
) == -1) err
= Error
.CLOSE
;
390 if (fatal
) err
= Error
.FATAL
;
391 if (err
!= Error
.NOERR
) raise(err
);
394 /** Retrieve a record.
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
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.
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.
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
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.
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
) {
439 char[DP_ENTBUFSIZ
] ebuf
;
441 if (sp
!is null) *sp
= 0;
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
;
449 if (max
== uint.max
) {
452 vsiz
= (max
< head
.vsiz ? max
: head
.vsiz
);
456 memcpy(vbuf
.ptr
, ebuf
.ptr
+(RecordHeader
.sizeof
+head
.ksiz
+start
), vsiz
);
458 vbuf
= new ubyte[](1);
462 vbuf
= recReadValue
!ubyte(off
, head
, start
, max
);
465 if (max
== uint.max
) {
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.
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
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.
490 * SDBMException on various errors
492 T
[] getToBuf(T
=char) (T
[] vbuff
, const(void)[] kbuf
, uint start
=0) if (!isArray
!T
) {
497 char[DP_ENTBUFSIZ
] ebuf
;
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
;
506 vsiz
= (vbuf
.length
< head
.vsiz ? vbuf
.length
: head
.vsiz
);
507 memcpy(vbuf
.ptr
, ebuf
.ptr
+(RecordHeader
.sizeof
+head
.ksiz
+start
), vsiz
);
509 vbuf
= recReadValueToBuf(vbuf
, off
, head
, start
);
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");
521 auto res
= getToBuf(cast(ubyte[])(data
[]), kbuf
);
522 static if (mode
== "throw") {
523 if (res
.length
!= T
.sizeof
) raise(Error
.NOTFOUND
);
525 if (res
.length
!= T
.sizeof
) data
[0] = T
.init
;
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
);
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.
545 * kbuf = the pointer to the region of a key
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`.
552 * SDBMException on various errors
554 int vsize (const(void)[] kbuf
) {
558 char[DP_ENTBUFSIZ
] ebuf
;
560 if (!recsearch(kbuf
, &bi
, &off
, &entoff
, head
, ebuf
[], &ee
)) return -1; //raise(Error.NOITEM);
564 /** Check if a record is existing.
567 * kbuf = the pointer to the region of a key
570 * `true` if a record is in database, `false` otherwise.
573 * SDBMException on various errors
575 bool exists (const(void)[] kbuf
) {
579 char[DP_ENTBUFSIZ
] ebuf
;
581 if (!recsearch(kbuf
, &bi
, &off
, &entoff
, head
, ebuf
[], &ee
)) return false;
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.
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
;
603 char[DP_ENTBUFSIZ
] ebuf
;
606 if (recsearch(kbuf
, &bi
, &off
, &entoff
, head
, ebuf
[], &ee
, Yes
.delhit
, &hash
)) {
608 fdel
= head
.flags
&DP_RECFDEL
;
609 if (dmode
== WMode
.KEEP
&& !fdel
) raise(Error
.KEEP
);
611 head
.psiz
+= head
.vsiz
;
615 nsiz
= RecordHeader
.sizeof
+kbuf
.length
+vbuf
.length
;
616 if (dmode
== WMode
.CAT
) nsiz
+= head
.vsiz
;
617 if (off
+rsiz
>= m_fsiz
) {
619 head
.psiz
+= nsiz
-rsiz
;
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;
637 recover(off
, head
, vbuf
, (dmode
== WMode
.CAT ? Yes
.catmode
: No
.catmode
));
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
);
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
];
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
) {
664 mroff
= m_fbpool
[mi
];
665 mrsiz
= m_fbpool
[mi
+1];
672 recdelete(off
, head
, Yes
.reusable
);
673 if (mroff
> 0 && nsiz
<= mrsiz
) {
674 recrewrite(mroff
, mrsiz
, kbuf
, vbuf
, hash
, head
.left
, head
.right
);
677 newoff
= recappend(kbuf
, vbuf
, hash
, head
.left
, head
.right
);
683 scope(failure
) m_fatal
= true;
684 newoff
= recappend(kbuf
, vbuf
, hash
, 0, 0);
689 fdseekwritenum(m_fd
, entoff
, newoff
);
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
);
704 * kbuf = the pointer to the region of a key
707 * If successful, the return value is true, else, it is false.
708 * False is returned when no record corresponds to the specified key.
711 * SDBMException on various errors
713 bool del (const(void)[] kbuf
) {
717 char[DP_ENTBUFSIZ
] ebuf
;
719 if (!recsearch(kbuf
, &bi
, &off
, &entoff
, head
, ebuf
[], &ee
)) return false; //raise(Error.NOITEM);
720 recdelete(off
, head
, No
.reusable
);
725 /** Initialize the iterator of a database handle.
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.
732 * SDBMException on various errors
739 /** Get the next key of the iterator.
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.
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.
755 * SDBMException on various errors
757 T
[] itNext(T
=char) (usize
* sp
=null) if (T
.sizeof
== 1) {
761 char[DP_ENTBUFSIZ
] ebuf
;
763 if (sp
!is null) *sp
= 0;
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
) {
773 if (ee
&& RecordHeader
.sizeof
+head
.ksiz
<= DP_ENTBUFSIZ
) {
774 import core
.stdc
.string
: memcpy
;
776 kbuf
.length
= head
.ksiz
;
777 memcpy(kbuf
.ptr
, ebuf
.ptr
+RecordHeader
.sizeof
, head
.ksiz
);
780 kbuf
= recReadKey(off
, head
);
782 m_ioff
= cast(int)(off
+head
.recsize
);
783 if (sp
!is null) *sp
= head
.ksiz
;
787 //raise(Error.NOITEM);
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.
801 * alignment = the size of alignment
804 * SDBMException on various errors
806 @property void alignment (int alignment
) {
808 m_alignment
= alignment
;
811 /** Get alignment of a database handle.
814 * The size of alignment
817 * SDBMException on various errors
819 @property int 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.
830 * size = the size of the free block pool of a database
833 * SDBMException on various errors
835 @property void freeBlockPoolSize (uint size
) {
836 import core
.stdc
.stdlib
: realloc
;
838 if (size
> 0x3fff_ffffu
) raise(Error
.ALLOC
);
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;
847 /** Get the size of the free block pool of a database handle.
850 * The size of the free block pool of a database
853 * SDBMException on various errors
855 @property uint freeBlockPoolSize () {
860 /** Synchronize updating contents with the file and the device.
862 * This function is useful when another process uses the connected database file.
865 * SDBMException on various errors
868 import core
.sys
.posix
.sys
.mman
: msync
, MS_SYNC
;
869 import core
.sys
.posix
.unistd
: fsync
;
872 if (msync(m_map
, m_msiz
, MS_SYNC
) == -1) {
876 if (fsync(m_fd
) == -1) {
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.
888 * bnum = the number of the elements of the bucket array. If it is not more than 0,
889 * the default value is specified
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
;
902 int[DP_OPTRUNIT
] ksizs
, vsizs
;
903 char[DP_ENTBUFSIZ
] ebuf
;
904 char[][DP_OPTRUNIT
] kbufs
, vbufs
;
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
);
912 import std
.exception
: collectException
;
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
;
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
);
933 vbufs
[unum
] = recReadValue(off
, head
);
936 kbufs
[unum
] = recReadKey(off
, head
);
937 vbufs
[unum
] = recReadValue(off
, head
);
939 ksizs
[unum
] = head
.ksiz
;
940 vsizs
[unum
] = head
.vsiz
;
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
);
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
);
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
;
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
977 if (unlink(tempname
.ptr
) == -1) raise(Error
.UNLINK
);
980 /** Get the name of a database.
983 * If successful, the return value is the pointer to the region of the name of the database,
984 * else, it is `null`.
989 @property string
name () const @safe pure nothrow @nogc {
993 /** Get the size of a database file.
996 * If successful, the return value is the size of the database file, else, it is -1.
999 * SDBMException on various errors
1001 @property long fileSize () {
1007 /** Get the number of the elements of the bucket array.
1010 * If successful, the return value is the number of the elements of the bucket array, else, it is -1.
1013 * SDBMException on various errors
1015 @property int bucketCount () {
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.
1025 * If successful, the return value is the number of the used elements of the bucket array, else, it is -1.
1028 * SDBMException on various errors
1033 foreach (immutable idx
; 0..m_bnum
) if (m_buckets
[idx
]) ++hits
;
1037 /** Get the number of the records stored in a database.
1040 * If successful, the return value is the number of the records stored in the database, else, it is -1.
1043 * SDBMException on various errors
1045 @property int recordCount () {
1050 /** Check whether a database handle is a writer or not.
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.
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 {
1068 /** Get the inode number of a database file.
1071 * The return value is the inode number of the database file.
1074 * SDBMException on various errors
1076 @property long inode () {
1081 /** Get the last modified time of a database.
1084 * The return value is the last modified time of the database.
1087 * SDBMException on various errors
1089 @property time_t
mtime () {
1094 /** Get the file descriptor of a database file.
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.
1101 * SDBMException on various errors
1103 @property int fdesc () {
1108 /* ********************************************************************************************* *
1109 * features for experts
1110 * ********************************************************************************************* */
1112 /** Synchronize updating contents on memory.
1115 * SDBMException on various errors
1118 import core
.sys
.posix
.sys
.mman
: msync
, MS_SYNC
;
1121 if (msync(m_map
, m_msiz
, MS_SYNC
) == -1) {
1127 /** Synchronize updating contents on memory, not physically.
1130 * SDBMException on various errors
1135 // there is no mflush() call
1137 if (mflush(m_map
, m_msiz
, MS_SYNC
) == -1) {
1144 /** Get flags of a database.
1146 * Engine doesn't use database flags in any way.
1149 * The return value is the flags of a database.
1152 * SDBMException on various errors
1154 @property int flags () {
1156 auto hdr
= cast(SDBMHeader
*)m_map
;
1160 /** Set flags of a database.
1162 * Engine doesn't use database flags in any way.
1168 * If successful, the return value is true, else, it is false.
1171 * SDBMException on various errors
1173 @property void flags (int v
) {
1175 auto hdr
= cast(SDBMHeader
*)m_map
;
1180 /* ********************************************************************************************* *
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.
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`)
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
;
1218 memset(&lock, 0, lock.sizeof
);
1219 lock.l_type
= (ex ? F_WRLCK
: F_RDLCK
);
1220 lock.l_whence
= SEEK_SET
;
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.
1233 * fd = a file descriptor
1234 * buf = a buffer to write
1237 * The return value is the size of the written buffer, or -1 on failure
1242 static int fdwrite (int fd
, const(void)[] buf
) @trusted nothrow @nogc {
1243 auto lbuf
= cast(const(ubyte)[])buf
;
1246 while (lbuf
.length
> 0) {
1247 import core
.sys
.posix
.unistd
: write
;
1248 auto wb
= write(fd
, lbuf
.ptr
, lbuf
.length
);
1250 import core
.stdc
.errno
: errno
, EINTR
;
1251 if (errno
!= EINTR
) return -1;
1261 /* Write into a file at an offset.
1264 * fd = a file descriptor
1265 * off = an offset of the file
1266 * buf = a buffer to write
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
;
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.
1283 * fd = a file descriptor
1284 * off = an offset of the file
1288 * SDBMException on various errors
1290 void fdseekwritenum (int fd
, long off
, int num
) {
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.
1299 * fd = a file descriptor
1300 * buf = a buffer to store into.
1303 * The return value is the size read with, or -1 on failure
1308 static int fdread (int fd
, void[] buf
) @trusted nothrow @nogc {
1309 import core
.sys
.posix
.unistd
: read
;
1310 auto lbuf
= cast(ubyte[])buf
;
1313 while (lbuf
.length
> 0) {
1314 auto bs
= read(fd
, lbuf
.ptr
, lbuf
.length
);
1316 import core
.stdc
.errno
: errno
, EINTR
;
1317 if (errno
!= EINTR
) return -1;
1322 total
+= cast(int)bs
;
1327 /* Read from a file at an offset and store the data into a buffer.
1330 * fd = a file descriptor
1331 * off = an offset of the file
1332 * buf = a buffer to store into
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.
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
1354 * The return value is the size copied with
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
;
1364 if (lseek(srcfd
, srcoff
, SEEK_SET
) == -1 ||
lseek(destfd
, destoff
, SEEK_SET
) == -1) straise(Error
.SEEK
);
1366 while ((iosiz
= fdread(srcfd
, iobuf
[])) > 0) {
1367 if (fdwrite(destfd
, iobuf
[0..iosiz
]) != iosiz
) straise(Error
.WRITE
);
1370 if (iosiz
< 0) straise(Error
.READ
);
1374 /* Get the padding size of a record.
1377 * ksiz = the size of the key of a record
1378 * vsiz = the size of the value of a record
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
);
1393 return cast(usize
)((pad
/(DP_FSBLKSIZ
/2))*(DP_FSBLKSIZ
/2)+(DP_FSBLKSIZ
/2)-
1394 (m_fsiz
+RecordHeader
.sizeof
+ksiz
+vsiz
)%(DP_FSBLKSIZ
/2));
1397 return (pad
>= RecordHeader
.sizeof ? pad
: RecordHeader
.sizeof
);
1403 /* Read the header of a record.
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
1412 * SDBMException on various errors
1414 void rechead (long off
, ref RecordHeader head
, void[] ebuf
, bool* eep
) {
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
);
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.
1433 * off = an offset of the database file
1434 * head = the header of a record
1437 * The return value is a key data whose region is allocated by GC
1440 * SDBMException on various errors
1442 T
[] recReadKey(T
=char) (long off
, ref in RecordHeader head
) if (T
.sizeof
== 1) {
1445 int ksiz
= head
.ksiz
;
1448 fdseekread(m_fd
, off
+RecordHeader
.sizeof
, kbuf
[0..ksiz
]);
1456 /* Read the entitiy of the value of a record.
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
1465 * The return value is a value data whose region is allocated with GC.
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) {
1475 if (max
== uint.max
) {
1478 vsiz
= (max
< head
.vsiz ? max
: head
.vsiz
);
1480 scope(failure
) m_fatal
= true;
1483 fdseekread(m_fd
, off
+RecordHeader
.sizeof
+head
.ksiz
+start
, vbuf
[0..vsiz
]);
1491 /* Read the entitiy of the value of a record and write it into a given buffer.
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
1500 * Read data (can be less then vbuf)
1503 * SDBMException on various errors
1505 T
[] recReadValueToBuf(T
=char) (T
[] vbuf
, long off
, ref RecordHeader head
, uint start
=0) if (T
.sizeof
== 1) {
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.
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
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.
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
1548 * The return value is true if record was found, false if there is no corresponding record.
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)
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
];
1572 rechead(off
, head
, ebuf
, eep
);
1575 entoff
= cast(int)(off
+RecordHeader
.left
.offsetof
);
1577 } else if (hash
< thash
) {
1578 entoff
= cast(int)(off
+RecordHeader
.right
.offsetof
);
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
]);
1590 fdseekread(m_fd
, off
+RecordHeader
.sizeof
, stkey
[0..head
.ksiz
]);
1591 } catch (Exception
) {
1594 kcmp
= keycmp(kbuf
, stkey
[0..head
.ksiz
]);
1597 entoff
= cast(int)(off
+RecordHeader
.left
.offsetof
);
1599 } else if (kcmp
< 0) {
1600 entoff
= cast(int)(off
+RecordHeader
.right
.offsetof
);
1603 if (!delhit
&& (head
.flags
&DP_RECFDEL
)) {
1604 entoff
= cast(int)(off
+RecordHeader
.left
.offsetof
);
1607 *offp
= cast(int)off
;
1614 *offp
= cast(int)off
;
1619 /* Overwrite a record.
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
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
;
1636 int hoff
, koff
, voff
, mi
, min
, size
;
1638 assert(off
>= 1 && rsiz
> 0);
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
);
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
);
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
]);
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
[]);
1668 off
+= head
.sizeof
+kbuf
.length
+vbuf
.length
+head
.psiz
;
1669 head
.flags
= DP_RECFDEL|DP_RECFREUSE
;
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
);
1676 fdseekwrite(m_fd
, off
, (&head
)[0..1]);
1677 size
= head
.recsize
;
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
;
1688 if (mi
== -1 || m_fbpool
[i
+1] < min
) {
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
;
1701 /* Write a record at the end of a database file.
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
1711 * The return value is the offset of the record
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
;
1721 psiz
= padsize(kbuf
.length
, vbuf
.length
);
1724 head
.ksiz
= cast(int)kbuf
.length
;
1725 head
.vsiz
= cast(int)vbuf
.length
;
1726 head
.psiz
= cast(int)psiz
;
1729 asiz
= head
.sizeof
+kbuf
.length
+vbuf
.length
+psiz
;
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
]);
1739 import core
.stdc
.string
: memcpy
, memset
;
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
]);
1750 return cast(int)off
;
1753 /* Overwrite the value of a record.
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
1762 * SDBMException on various errors
1764 void recover (long off
, ref RecordHeader head
, const(void)[] vbuf
, Flag
!"catmode" catmode
) {
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;
1773 long voff
= off
+RecordHeader
.sizeof
+head
.ksiz
;
1775 head
.psiz
-= vbuf
.length
;
1776 head
.vsiz
+= vbuf
.length
;
1777 voff
+= head
.vsiz
-vbuf
.length
;
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
[]);
1790 * off = the offset of the database file
1791 * head = the header of the record
1792 * reusable = whether the region is reusable or not
1795 * SDBMException on various errors
1797 void recdelete (long off
, ref in RecordHeader head
, Flag
!"reusable" reusable
) {
1800 auto size
= head
.recsize
;
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
;
1811 if (mi
== -1 || m_fbpool
[i
+1] < min
) {
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
;
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;
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 {
1846 return *cast(const int*)a
- *cast(const int*)b
;
1849 /* ********************************************************************************************* *
1851 * ********************************************************************************************* */
1854 /** Remove a database file.
1857 * name = the name of a database file
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
;
1868 assert(name
.length
);
1869 auto namez
= name
.toStringz
;
1870 if (lstat(namez
, &sbuf
) == -1) {
1871 if (errno
!= ENOENT
) straise(Error
.STAT
);
1875 //k8:??? try to open the file to check if it's not locked or something
1876 auto depot
= new SDBM(name
, WRITER|TRUNC
);
1879 if (unlink(namez
) == -1) {
1880 if (errno
!= ENOENT
) straise(Error
.UNLINK
);
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.
1891 * name = the name of a database file
1894 * true if ok, false is there were some errors
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
;
1907 int fd
, flags
, bnum
, tbnum
, rsiz
, ksiz
, vsiz
;
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
;
1928 while (off
< fsiz
) {
1930 fdseekread(fd
, off
, (&head
)[0..1]);
1931 } catch (Exception
) {
1934 if (head
.flags
&DP_RECFDEL
) {
1935 rsiz
= head
.recsize
;
1936 if (rsiz
< 0) break;
1942 if (ksiz
>= 0 && vsiz
>= 0) {
1943 kbuf
= alloc
!char(ksiz
);
1944 vbuf
= alloc
!char(vsiz
);
1945 if (kbuf
!is null && vbuf
!is null) {
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
) {
1954 //if (!err) raise(Error.ALLOC);
1957 if (vbuf
!is null) freeptr(vbuf
);
1958 if (kbuf
!is null) freeptr(kbuf
);
1960 //if (!err) raise(Error.BROKEN);
1963 rsiz
= head
.recsize
;
1964 if (rsiz
< 0) break;
1967 tdepot
.flags
= flags
; // err = true;
1970 } catch (Exception
) {
1973 if (ftruncate(fd
, 0) == -1) {
1974 //if (!err) raise(Error.TRUNC);
1977 auto tempname
= tdepot
.m_name
; // with trailing zero
1979 fcopy(fd
, 0, tdepot
.m_fd
, 0);
1981 } catch (Exception
) {
1984 if (close(fd
) == -1) {
1985 //if (!err) raise(Error.CLOSE);
1989 if (unlink(tempname
.ptr
) == -1) {
1990 //if (!err) raise(Error.UNLINK);
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.
2002 * num = a natural number
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,
2033 foreach (immutable pr
; primes
) if (num
<= pr
) return pr
;
2038 /** Free `malloc()`ed pointer and set variable to `null`.
2041 * ptr = the pointer to variable holding a pointer
2043 static freeptr(T
) (ref T
* ptr
) {
2044 import core
.stdc
.stdlib
: free
;
2046 free(cast(void*)ptr
);
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
);
2060 static if (noerr
== "nothrow") throw new SDBMException(Error
.ALLOC
);
2062 memset(res
, 0, asz
);