1 /* ********************************************************************************************* *
2 * The basic API of QDBM Copyright (C) 2000-2007 Mikio Hirabayashi
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 * D translation and "d-fication" by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
17 * ********************************************************************************************* */
18 // key/value database based on tokyo kabinet
19 module iv
.depot
/*is aliced*/;
24 class DepotException
: Exception
{
25 this (Depot
.Error ecode
, string file
=__FILE__
, usize line
=__LINE__
, Throwable next
=null) pure nothrow @safe @nogc {
26 super(errorMessage(ecode
), file
, line
, next
);
29 /** Get a message string corresponding to an error code.
32 * ecode = an error code
35 * The message string of the error code.
37 static string
errorMessage (Depot
.Error ecode
) @safe pure nothrow @nogc {
38 switch (ecode
) with (Depot
.Error
) {
39 case NOERR
: return "no error";
40 case FATAL
: return "with fatal error";
41 case CLOSED
: return "database not opened error";
42 case OPENED
: return "already opened database error";
43 case MODE
: return "invalid mode";
44 case BROKEN
: return "broken database file";
45 case KEEP
: return "existing record";
46 case NOITEM
: return "no item found";
47 case ALLOC
: return "memory allocation error";
48 case MAP
: return "memory mapping error";
49 case OPEN
: return "open error";
50 case CLOSE
: return "close error";
51 case TRUNC
: return "trunc error";
52 case SYNC
: return "sync error";
53 case STAT
: return "stat error";
54 case SEEK
: return "seek error";
55 case READ
: return "read error";
56 case WRITE
: return "write error";
57 case LOCK
: return "lock error";
58 case UNLINK
: return "unlink error";
59 case MKDIR
: return "mkdir error";
60 case RMDIR
: return "rmdir error";
61 case MISC
: return "miscellaneous error";
62 default: return "(invalid ecode)";
70 public final class Depot
{
71 public import core
.sys
.posix
.sys
.types
: time_t
;
72 private import std
.typecons
: Flag
, Yes
, No
;
74 enum QDBM_VERSION
= "1.8.78"; /// library version
75 enum QDBM_LIBVER
= 1414;
77 this (const(char)[] name
, int omode
=READER
, int bnum
=-1) { open(name
, omode
, bnum
); }
81 string m_name
; // name of the database file; terminated with '\0', but '\0' is not a string part
82 bool m_wmode
; // whether to be writable
83 ulong m_inode
; // inode of the database file
84 time_t m_mtime
; // last modified time of the database
85 int m_fd
= -1; // file descriptor of the database file
86 long m_fsiz
; // size of the database file
87 char* m_map
; // pointer to the mapped memory
88 int m_msiz
; // size of the mapped memory
89 int* m_buckets
; // pointer to the bucket array
90 int m_bnum
; // number of the bucket array
91 int m_rnum
; // number of records
92 bool m_fatal
; // whether a fatal error occured
93 int m_ioff
; // offset of the iterator
94 int* m_fbpool
; // free block pool
95 int m_fbpsiz
; // size of the free block pool
96 int m_fbpinc
; // incrementor of update of the free block pool
97 int m_alignment
; // basic size of alignment (can be negative; why?)
100 enum DP_FILEMODE
= 384; // 0o600: permission of a creating file
102 enum DP_MAGIC
= "[DEPOT]\n\f"; // magic on environments of big endian
104 enum DP_MAGIC
= "[depot]\n\f"; // magic on environments of little endian
106 enum DP_DEFBNUM
= 8191; // default bucket number
107 enum DP_FBPOOLSIZ
= 16; // size of free block pool
108 enum DP_ENTBUFSIZ
= 128; // size of the entity buffer
109 enum DP_STKBUFSIZ
= 256; // size of the stack key buffer
110 enum DP_WRTBUFSIZ
= 8192; // size of the writing buffer
111 enum DP_FSBLKSIZ
= 4096; // size of a block of the file system
112 enum DP_OPTBLOAD
= 0.25; // ratio of bucket loading at optimization
113 enum DP_OPTRUNIT
= 256; // number of records in a process of optimization
114 enum DP_NUMBUFSIZ
= 32; // size of a buffer for a number
115 enum DP_IOBUFSIZ
= 8192; // size of an I/O buffer
116 enum DP_TMPFSUF
= ".dptmp"; // suffix of a temporary file
119 // enumeration for the flag of a record
121 DP_RECFDEL
= 0x01, // deleted
122 DP_RECFREUSE
= 0x02, // reusable
125 static align(1) struct QDBMHeader
{
127 char[12] signature
; // DP_MAGICNUMB or DP_MAGICNUML, padded with '\0'
128 char[4] versionstr
; // string, padded with '\0'
133 int nbuckets
; // number of buckets
135 int nrecords
; // number of records
138 static assert(QDBMHeader
.sizeof
== 48);
139 static assert(QDBMHeader
.signature
.offsetof
== 0);
140 static assert(QDBMHeader
.versionstr
.offsetof
== 12);
141 static assert(QDBMHeader
.flags
.offsetof
== 16);
142 static assert(QDBMHeader
.filesize
.offsetof
== 24);
143 static assert(QDBMHeader
.nbuckets
.offsetof
== 32);
144 static assert(QDBMHeader
.nrecords
.offsetof
== 40);
146 static align(1) struct RecordHeader
{
149 int hash2
; // value of the second hash function
150 int ksiz
; // the size of the key
151 int vsiz
; // the size of the value
152 int psiz
; // the size of the padding bytes
153 int left
; // the offset of the left child
154 int right
; // the offset of the right child
156 /* Get the size of a record in a database file.
159 * The return value is the size of a record in a database file
161 @property int recsize () const @safe pure nothrow @nogc {
162 return cast(int)(RecordHeader
.sizeof
+ksiz
+vsiz
+psiz
);
165 static assert(RecordHeader
.sizeof
== 7*int.sizeof
);
166 static assert(RecordHeader
.flags
.offsetof
== 0*int.sizeof
);
167 static assert(RecordHeader
.hash2
.offsetof
== 1*int.sizeof
);
168 static assert(RecordHeader
.ksiz
.offsetof
== 2*int.sizeof
);
169 static assert(RecordHeader
.vsiz
.offsetof
== 3*int.sizeof
);
170 static assert(RecordHeader
.psiz
.offsetof
== 4*int.sizeof
);
171 static assert(RecordHeader
.left
.offsetof
== 5*int.sizeof
);
172 static assert(RecordHeader
.right
.offsetof
== 6*int.sizeof
);
175 /// enumeration for error codes
178 FATAL
, /// with fatal error
179 CLOSED
, /// trying to operate on closed db
180 OPENED
, /// trying to opend an already opened db
181 MODE
, /// invalid mode
182 BROKEN
, /// broken database file
183 KEEP
, /// existing record
184 NOITEM
, /// no item found
185 ALLOC
, /// memory allocation error
186 MAP
, /// memory mapping error
188 CLOSE
, /// close error
189 TRUNC
, /// trunc error
194 WRITE
, /// write error
196 UNLINK
, /// unlink error
197 MKDIR
, /// mkdir error
198 RMDIR
, /// rmdir error
199 MISC
, /// miscellaneous error
202 /// enumeration for open modes
204 READER
= 1<<0, /// open as a reader
205 WRITER
= 1<<1, /// open as a writer
206 CREAT
= 1<<2, /// a writer creating
207 TRUNC
= 1<<3, /// a writer truncating
208 NOLCK
= 1<<4, /// open without locking
209 LCKNB
= 1<<5, /// lock without blocking
210 SPARSE
= 1<<6, /// create as a sparse file
213 /// enumeration for write modes
215 OVER
, /// overwrite an existing value
216 KEEP
, /// keep an existing value
217 CAT
, /// concatenate values
222 @property bool opened () const @safe pure nothrow @nogc { return (m_fd
>= 0); }
224 void checkOpened (string file
=__FILE__
, usize line
=__LINE__
) {
225 if (m_fatal
) raise(Error
.FATAL
, file
, line
);
226 if (!opened
) raise(Error
.CLOSED
, file
, line
);
229 void checkWriting (string file
=__FILE__
, usize line
=__LINE__
) {
230 checkOpened(file
, line
);
231 if (!m_wmode
) raise(Error
.MODE
, file
, line
);
234 /** Free `malloc()`ed pointer and set variable to `null`.
237 * ptr = the pointer to variable holding a pointer
239 static freeptr(T
) (ref T
* ptr
) {
240 import core
.stdc
.stdlib
: free
;
242 free(cast(void*)ptr
);
247 /** Get a database handle.
249 * While connecting as a writer, an exclusive lock is invoked to the database file.
250 * While connecting as a reader, a shared lock is invoked to the database file. The thread
251 * blocks until the lock is achieved. If `NOLCK` is used, the application is responsible
252 * for exclusion control.
255 * name = the name of a database file
256 * omode = specifies the connection mode: `WRITER` as a writer, `READER` as a reader.
257 * If the mode is `WRITER`, the following may be added by bitwise or:
258 * `CREAT`, which means it creates a new database if not exist,
259 * `TRUNC`, which means it creates a new database regardless if one exists.
260 * Both of `READER` and `WRITER` can be added to by bitwise or:
261 * `NOLCK`, which means it opens a database file without file locking, or
262 * `LCKNB`, which means locking is performed without blocking.
263 * `CREAT` can be added to by bitwise or:
264 * `SPARSE`, which means it creates a database file as a sparse file.
265 * bnum = the number of elements of the bucket array.
266 * If it is not more than 0, the default value is specified. The size of a bucket array is
267 * determined on creating, and can not be changed except for by optimization of the database.
268 * Suggested size of a bucket array is about from 0.5 to 4 times of the number of all records
270 * errcode = the error code (can be `null`)
273 * DepotException on various errors
275 void open (const(char)[] name
, int omode
=READER
, int bnum
=-1) {
276 import core
.sys
.posix
.fcntl
: open
, O_CREAT
, O_RDONLY
, O_RDWR
;
277 import core
.sys
.posix
.sys
.mman
: mmap
, munmap
, MAP_FAILED
, PROT_READ
, PROT_WRITE
, MAP_SHARED
;
278 import core
.sys
.posix
.sys
.stat
: fstat
, lstat
, S_ISREG
, stat_t
;
279 import core
.sys
.posix
.unistd
: close
, ftruncate
;
289 if (opened
) raise(Error
.OPENED
);
291 char[] namez
; // unique
292 // add '\0' after string
295 while (len
< name
.length
&& name
[len
]) ++len
;
296 namez
= new char[](len
+1);
297 namez
[0..$-1] = name
[0..len
];
303 if (omode
&CREAT
) mode |
= O_CREAT
;
305 if ((fd
= open(namez
.ptr
, mode
, DP_FILEMODE
)) == -1) raise(Error
.OPEN
);
306 scope(failure
) close(fd
);
307 if ((omode
&NOLCK
) == 0) fdlock(fd
, omode
&WRITER
, omode
&LCKNB
);
308 if ((omode
&WRITER
) && (omode
&TRUNC
)) {
309 if (ftruncate(fd
, 0) == -1) raise(Error
.TRUNC
);
311 if (fstat(fd
, &sbuf
) == -1 ||
!S_ISREG(sbuf
.st_mode
) ||
(sbuf
.st_ino
== 0 && lstat(namez
.ptr
, &sbuf
) == -1)) raise(Error
.STAT
);
313 mtime
= sbuf
.st_mtime
;
315 if ((omode
&WRITER
) && fsiz
== 0) {
316 hbuf
.signature
[] = 0;
317 hbuf
.versionstr
[] = 0;
318 hbuf
.signature
[0..DP_MAGIC
.length
] = DP_MAGIC
[];
320 import core
.stdc
.stdio
: snprintf
;
321 snprintf(hbuf
.versionstr
.ptr
, hbuf
.versionstr
.length
, "%d", QDBM_LIBVER
/100);
323 bnum
= (bnum
< 1 ? DP_DEFBNUM
: bnum
);
324 bnum
= primenum(bnum
);
325 hbuf
.nbuckets
= bnum
;
327 fsiz
= hbuf
.sizeof
+bnum
*int.sizeof
;
328 hbuf
.filesize
= cast(int)fsiz
;
329 fdseekwrite(fd
, 0, (&hbuf
)[0..1]);
332 fdseekwrite(fd
, fsiz
-1, (&c
)[0..1]);
334 ubyte[DP_IOBUFSIZ
] ebuf
= 0; // totally empty buffer initialized with 0 %-)
335 usize pos
= hbuf
.sizeof
;
337 usize left
= cast(usize
)fsiz
-pos
;
338 usize wr
= (left
> ebuf
.length ? ebuf
.length
: left
);
339 fdseekwrite(fd
, pos
, ebuf
[0..wr
]);
345 fdseekread(fd
, 0, (&hbuf
)[0..1]);
346 } catch (Exception
) {
349 //k8: the original code checks header only if ((omode&NOLCK) == 0); why?
350 if (hbuf
.signature
[0..DP_MAGIC
.length
] != DP_MAGIC
) raise(Error
.BROKEN
);
351 if (hbuf
.filesize
!= fsiz
) raise(Error
.BROKEN
);
352 bnum
= hbuf
.nbuckets
;
353 if (bnum
< 1 || hbuf
.nrecords
< 0 || fsiz
< QDBMHeader
.sizeof
+bnum
*int.sizeof
) raise(Error
.BROKEN
);
354 msiz
= QDBMHeader
.sizeof
+bnum
*int.sizeof
;
355 map
= cast(char*)mmap(null, msiz
, PROT_READ|
(mode
&WRITER ? PROT_WRITE
: 0), MAP_SHARED
, fd
, 0);
356 if (map
== MAP_FAILED
) raise(Error
.MAP
);
357 scope(failure
) munmap(map
, msiz
);
360 import core
.stdc
.stdlib
: malloc
;
361 fbpool
= cast(int*)malloc(DP_FBPOOLSIZ
*2*int.sizeof
);
363 if (fbpool
is null) raise(Error
.ALLOC
);
365 import std
.exception
: assumeUnique
;
366 m_name
= namez
[0..$-1].assumeUnique
;
368 m_wmode
= (mode
&WRITER
) != 0;
374 m_msiz
= cast(int)msiz
;
375 m_buckets
= cast(int*)(map
+QDBMHeader
.sizeof
);
377 m_rnum
= hbuf
.nrecords
;
381 m_fbpool
[0..DP_FBPOOLSIZ
*2] = -1;
382 m_fbpsiz
= DP_FBPOOLSIZ
*2;
387 /** Close a database handle.
390 * If successful, the return value is true, else, it is false.
391 * Because the region of a closed handle is released, it becomes impossible to use the handle.
392 * Updating a database is assured to be written when the handle is closed. If a writer opens
393 * a database but does not close it appropriately, the database will be broken.
396 * DepotException on various errors
399 import core
.sys
.posix
.sys
.mman
: munmap
, MAP_FAILED
;
400 import core
.sys
.posix
.unistd
: close
;
402 bool fatal
= m_fatal
;
403 Error err
= Error
.NOERR
;
404 if (m_wmode
) updateHeader();
406 if (munmap(m_map
, m_msiz
) == -1) err
= Error
.MAP
;
409 if (close(m_fd
) == -1) err
= Error
.CLOSE
;
414 if (fatal
) err
= Error
.FATAL
;
415 if (err
!= Error
.NOERR
) raise(err
);
421 * kbuf = the pointer to the region of a key
422 * vbuf = the pointer to the region of a value
423 * dmode = behavior when the key overlaps, by the following values:
424 * `WMode.OVER`, which means the specified value overwrites the existing one,
425 * `WMode.KEEP`, which means the existing value is kept,
426 * `WMode.CAT`, which means the specified value is concatenated at the end of the existing value.
429 * DepotException on various errors
431 void put (const(void)[] kbuf
, const(void)[] vbuf
, WMode dmode
=WMode
.OVER
) {
432 RecordHeader head
, next
;
433 int hash
, bi
, off
, entoff
, newoff
, fdel
, mroff
, mrsiz
, mi
, min
;
436 char[DP_ENTBUFSIZ
] ebuf
;
440 hash
= secondhash(kbuf
);
441 if (recsearch(kbuf
, hash
, &bi
, &off
, &entoff
, head
, ebuf
[], &ee
, Yes
.delhit
)) {
443 fdel
= head
.flags
&DP_RECFDEL
;
444 if (dmode
== WMode
.KEEP
&& !fdel
) raise(Error
.KEEP
);
446 head
.psiz
+= head
.vsiz
;
450 nsiz
= RecordHeader
.sizeof
+kbuf
.length
+vbuf
.length
;
451 if (dmode
== WMode
.CAT
) nsiz
+= head
.vsiz
;
452 if (off
+rsiz
>= m_fsiz
) {
454 head
.psiz
+= nsiz
-rsiz
;
459 while (nsiz
> rsiz
&& off
+rsiz
< m_fsiz
) {
460 rechead(off
+rsiz
, next
, null, null);
461 if ((next
.flags
&DP_RECFREUSE
) == 0) break;
462 head
.psiz
+= next
.recsize
;
463 rsiz
+= next
.recsize
;
465 for (uint i
= 0; i
< m_fbpsiz
; i
+= 2) {
466 if (m_fbpool
[i
] >= off
&& m_fbpool
[i
] < off
+rsiz
) {
467 m_fbpool
[i
] = m_fbpool
[i
+1] = -1;
472 recover(off
, head
, vbuf
, (dmode
== WMode
.CAT ? Yes
.catmode
: No
.catmode
));
475 scope(failure
) { m_fatal
= true; freeptr(tval
); }
476 if (dmode
== WMode
.CAT
) {
477 import core
.stdc
.string
: memcpy
;
478 if (ee
&& RecordHeader
.sizeof
+head
.ksiz
+head
.vsiz
<= DP_ENTBUFSIZ
) {
479 import core
.stdc
.stdlib
: malloc
;
480 tval
= cast(char*)malloc(head
.vsiz
+vbuf
.length
+1);
481 if (tval
is null) { m_fatal
= true; raise(Error
.ALLOC
); }
482 memcpy(tval
, ebuf
.ptr
+(RecordHeader
.sizeof
+head
.ksiz
), head
.vsiz
);
484 import core
.stdc
.stdlib
: realloc
;
485 tval
= recval(off
, head
);
486 swap
= cast(char*)realloc(tval
, head
.vsiz
+vbuf
.length
+1);
487 if (swap
is null) raise(Error
.ALLOC
);
490 memcpy(tval
+head
.vsiz
, vbuf
.ptr
, vbuf
.length
);
491 immutable newsize
= head
.vsiz
+vbuf
.length
;
492 vbuf
= tval
[0..newsize
];
496 for (uint i
= 0; i
< m_fbpsiz
; i
+= 2) {
497 if (m_fbpool
[i
+1] < nsiz
) continue;
498 if (mi
== -1 || m_fbpool
[i
+1] < min
) {
504 mroff
= m_fbpool
[mi
];
505 mrsiz
= m_fbpool
[mi
+1];
512 recdelete(off
, head
, Yes
.reusable
);
513 if (mroff
> 0 && nsiz
<= mrsiz
) {
514 recrewrite(mroff
, mrsiz
, kbuf
, vbuf
, hash
, head
.left
, head
.right
);
517 newoff
= recappend(kbuf
, vbuf
, hash
, head
.left
, head
.right
);
524 scope(failure
) m_fatal
= true;
525 newoff
= recappend(kbuf
, vbuf
, hash
, 0, 0);
530 fdseekwritenum(m_fd
, entoff
, newoff
);
532 m_buckets
[bi
] = newoff
;
540 * kbuf = the pointer to the region of a key
543 * If successful, the return value is true, else, it is false.
544 * False is returned when no record corresponds to the specified key.
547 * DepotException on various errors
549 bool del (const(void)[] kbuf
) {
551 int hash
, bi
, off
, entoff
;
553 char[DP_ENTBUFSIZ
] ebuf
;
555 hash
= secondhash(kbuf
);
556 if (!recsearch(kbuf
, hash
, &bi
, &off
, &entoff
, head
, ebuf
[], &ee
)) return false; //raise(Error.NOITEM);
557 recdelete(off
, head
, No
.reusable
);
562 /** Retrieve a record.
565 * kbuf = the pointer to the region of a key
566 * start = the offset address of the beginning of the region of the value to be read
567 * max = specifies the max size to be read; if it is `uint.max`, the size to read is unlimited
568 * sp = the pointer to a variable to which the size of the region of the return
569 * value is assigned; if it is `null`, it is not used
572 * If successful, the return value is the pointer to the region of the value of the
573 * corresponding record, else, it is `null`. `null` is returned when no record corresponds to
574 * the specified key or the size of the value of the corresponding record is less than `start`.
575 * Because an additional zero code is appended at the end of the region of the return value,
576 * the return value can be treated as a character string. Because the region of the return
577 * value is allocated with the `malloc` call, it should be released with the `freeptr` call if it
578 * is no longer in use.
581 * DepotException on various errors
583 char* get (const(void)[] kbuf
, uint start
=0, uint max
=uint.max
, usize
* sp
=null) {
585 int hash
, bi
, off
, entoff
;
588 char[DP_ENTBUFSIZ
] ebuf
;
590 if (sp
!is null) *sp
= 0;
592 hash
= secondhash(kbuf
);
593 if (!recsearch(kbuf
, hash
, &bi
, &off
, &entoff
, head
, ebuf
[], &ee
)) return null; //raise(Error.NOITEM);
594 if (start
> head
.vsiz
) return null; //raise(Error.NOITEM);
595 if (start
== head
.vsiz
) {
596 import core
.stdc
.stdlib
: malloc
;
597 vbuf
= cast(char*)malloc(1);
601 scope(failure
) m_fatal
= true; // any failure beyond this point is fatal
602 if (ee
&& RecordHeader
.sizeof
+head
.ksiz
+head
.vsiz
<= DP_ENTBUFSIZ
) {
603 import core
.stdc
.stdlib
: malloc
;
604 import core
.stdc
.string
: memcpy
;
606 if (max
== uint.max
) {
609 vsiz
= (max
< head
.vsiz ? max
: head
.vsiz
);
611 vbuf
= cast(char*)malloc(vsiz
+1);
612 if (vbuf
is null) raise(Error
.ALLOC
);
613 memcpy(vbuf
, ebuf
.ptr
+(RecordHeader
.sizeof
+head
.ksiz
+start
), vsiz
);
616 vbuf
= recval(off
, head
, start
, max
);
619 if (max
== uint.max
) {
622 *sp
= (max
< head
.vsiz ? max
: head
.vsiz
);
628 /** Retrieve a record and write the value into a buffer.
631 * vbuf = the pointer to a buffer into which the value of the corresponding record is written
632 * kbuf = the pointer to the region of a key
633 * start = the offset address of the beginning of the region of the value to be read
636 * If successful, the return value is the read data (slice of vbuf), else, it is `null`.
637 * `null` returned when no record corresponds to the specified key or the size of the value
638 * of the corresponding record is less than `start`.
639 * Note that no additional zero code is appended at the end of the region of the writing buffer.
642 * DepotException on various errors
644 char[] getwb (void[] vbuf
, const(void)[] kbuf
, uint start
=0) {
646 int hash
, bi
, off
, entoff
;
649 char[DP_ENTBUFSIZ
] ebuf
;
651 hash
= secondhash(kbuf
);
652 if (!recsearch(kbuf
, hash
, &bi
, &off
, &entoff
, head
, ebuf
[], &ee
)) return null; //raise(Error.NOITEM);
653 if (start
> head
.vsiz
) return null; //raise(Error.NOITEM);
654 scope(failure
) m_fatal
= true; // any failure beyond this point is fatal
655 if (ee
&& RecordHeader
.sizeof
+head
.ksiz
+head
.vsiz
<= DP_ENTBUFSIZ
) {
656 import core
.stdc
.string
: memcpy
;
658 vsiz
= (vbuf
.length
< head
.vsiz ? vbuf
.length
: head
.vsiz
);
659 memcpy(vbuf
.ptr
, ebuf
.ptr
+(RecordHeader
.sizeof
+head
.ksiz
+start
), vsiz
);
661 vsiz
= recvalwb(vbuf
, off
, head
, start
);
663 return cast(char[])(vbuf
[0..vsiz
]);
666 /** Get the size of the value of a record.
669 * kbuf = the pointer to the region of a key
672 * If successful, the return value is the size of the value of the corresponding record, else, it is -1.
673 * Because this function does not read the entity of a record, it is faster than `get`.
676 * DepotException on various errors
678 usize
vsize (const(void)[] kbuf
) {
680 int hash
, bi
, off
, entoff
;
682 char[DP_ENTBUFSIZ
] ebuf
;
684 hash
= secondhash(kbuf
);
685 if (!recsearch(kbuf
, hash
, &bi
, &off
, &entoff
, head
, ebuf
[], &ee
)) return -1; //raise(Error.NOITEM);
689 /** Initialize the iterator of a database handle.
692 * If successful, the return value is true, else, it is false.
693 * The iterator is used in order to access the key of every record stored in a database.
696 * DepotException on various errors
703 /** Get the next key of the iterator.
706 * sp = the pointer to a variable to which the size of the region of the return value is assigned.
707 * If it is `null`, it is not used.
710 * If successful, the return value is the pointer to the region of the next key, else, it is
711 * `null`. `null` is returned when no record is to be get out of the iterator.
712 * Because an additional zero code is appended at the end of the region of the return value,
713 * the return value can be treated as a character string. Because the region of the return
714 * value is allocated with the `malloc` call, it should be released with the `freeptr` call if
715 * it is no longer in use. It is possible to access every record by iteration of calling
716 * this function. However, it is not assured if updating the database is occurred while the
717 * iteration. Besides, the order of this traversal access method is arbitrary, so it is not
718 * assured that the order of storing matches the one of the traversal access.
721 * DepotException on various errors
723 char* itNext (usize
* sp
=null) {
727 char[DP_ENTBUFSIZ
] ebuf
;
729 if (sp
!is null) *sp
= 0;
731 off
= QDBMHeader
.sizeof
+m_bnum
*int.sizeof
;
732 off
= (off
> m_ioff ? off
: m_ioff
);
733 scope(failure
) m_fatal
= true; // any failure is fatal here
734 while (off
< m_fsiz
) {
735 rechead(off
, head
, ebuf
[], &ee
);
736 if (head
.flags
&DP_RECFDEL
) {
739 if (ee
&& RecordHeader
.sizeof
+head
.ksiz
<= DP_ENTBUFSIZ
) {
740 import core
.stdc
.stdlib
: malloc
;
741 import core
.stdc
.string
: memcpy
;
742 kbuf
= cast(char*)malloc(head
.ksiz
+1);
743 if (kbuf
is null) raise(Error
.ALLOC
);
744 memcpy(kbuf
, ebuf
.ptr
+RecordHeader
.sizeof
, head
.ksiz
);
745 kbuf
[head
.ksiz
] = '\0';
747 kbuf
= reckey(off
, head
);
749 m_ioff
= cast(int)(off
+head
.recsize
);
750 if (sp
!is null) *sp
= head
.ksiz
;
754 //raise(Error.NOITEM);
758 /** Set alignment of a database handle.
760 * If alignment is set to a database, the efficiency of overwriting values is improved.
761 * The size of alignment is suggested to be average size of the values of the records to be
762 * stored. If alignment is positive, padding whose size is multiple number of the alignment
763 * is placed. If alignment is negative, as `vsiz` is the size of a value, the size of padding
764 * is calculated with `(vsiz/pow(2, abs(alignment)-1))'. Because alignment setting is not
765 * saved in a database, you should specify alignment every opening a database.
768 * alignment = the size of alignment
771 * DepotException on various errors
773 @property void alignment (int alignment
) {
775 m_alignment
= alignment
;
778 /** Get alignment of a database handle.
781 * The size of alignment
784 * DepotException on various errors
786 @property int alignment () {
791 /** Set the size of the free block pool of a database handle.
793 * The default size of the free block pool is 16. If the size is greater, the space efficiency
794 * of overwriting values is improved with the time efficiency sacrificed.
797 * size = the size of the free block pool of a database
800 * DepotException on various errors
802 @property void freeBlockPoolSize (uint size
) {
803 import core
.stdc
.stdlib
: realloc
;
807 fbpool
= cast(int*)realloc(m_fbpool
, size
*int.sizeof
+1);
808 if (fbpool
is null) raise(Error
.ALLOC
);
809 fbpool
[0..size
] = -1;
814 /** Get the size of the free block pool of a database handle.
817 * The size of the free block pool of a database
820 * DepotException on various errors
822 @property uint freeBlockPoolSize () {
827 /** Synchronize updating contents with the file and the device.
829 * This function is useful when another process uses the connected database file.
832 * DepotException on various errors
835 import core
.sys
.posix
.sys
.mman
: msync
, MS_SYNC
;
836 import core
.sys
.posix
.unistd
: fsync
;
839 if (msync(m_map
, m_msiz
, MS_SYNC
) == -1) {
843 if (fsync(m_fd
) == -1) {
849 /** Optimize a database.
851 * In an alternating succession of deleting and storing with overwrite or concatenate,
852 * dispensable regions accumulate. This function is useful to do away with them.
855 * bnum = the number of the elements of the bucket array. If it is not more than 0,
856 * the default value is specified
859 * DepotException on various errors
861 void optimize (int bnum
=-1) {
862 import core
.sys
.posix
.sys
.mman
: mmap
, munmap
, MAP_FAILED
, MAP_SHARED
, PROT_READ
, PROT_WRITE
;
863 import core
.sys
.posix
.unistd
: ftruncate
, unlink
;
869 int[DP_OPTRUNIT
] ksizs
, vsizs
;
870 char[DP_ENTBUFSIZ
] ebuf
;
871 char*[DP_OPTRUNIT
] kbufs
, vbufs
;
874 bnum
= cast(int)(m_rnum
*(1.0/DP_OPTBLOAD
))+1;
875 if (bnum
< DP_DEFBNUM
/2) bnum
= DP_DEFBNUM
/2;
877 tdepot
= new Depot(m_name
~DP_TMPFSUF
, WRITER|CREAT|TRUNC
, bnum
);
879 import std
.exception
: collectException
;
881 unlink(tdepot
.m_name
.ptr
);
882 collectException(tdepot
.close());
884 scope(exit
) delete tdepot
;
885 tdepot
.flags
= flags
;
886 tdepot
.m_alignment
= m_alignment
;
887 off
= QDBMHeader
.sizeof
+m_bnum
*int.sizeof
;
889 while (off
< m_fsiz
) {
890 rechead(off
, head
, ebuf
[], &ee
);
891 if ((head
.flags
&DP_RECFDEL
) == 0) {
892 if (ee
&& RecordHeader
.sizeof
+head
.ksiz
<= DP_ENTBUFSIZ
) {
893 import core
.stdc
.stdlib
: malloc
;
894 import core
.stdc
.string
: memcpy
;
895 if ((kbufs
[unum
] = cast(char*)malloc(head
.ksiz
+1)) is null) raise(Error
.ALLOC
);
896 memcpy(kbufs
[unum
], ebuf
.ptr
+RecordHeader
.sizeof
, head
.ksiz
);
897 if (RecordHeader
.sizeof
+head
.ksiz
+head
.vsiz
<= DP_ENTBUFSIZ
) {
898 if ((vbufs
[unum
] = cast(char*)malloc(head
.vsiz
+1)) is null) raise(Error
.ALLOC
);
899 memcpy(vbufs
[unum
], ebuf
.ptr
+(RecordHeader
.sizeof
+head
.ksiz
), head
.vsiz
);
901 vbufs
[unum
] = recval(off
, head
);
904 kbufs
[unum
] = reckey(off
, head
);
905 vbufs
[unum
] = recval(off
, head
);
907 ksizs
[unum
] = head
.ksiz
;
908 vsizs
[unum
] = head
.vsiz
;
910 if (unum
>= DP_OPTRUNIT
) {
911 for (uint i
= 0; i
< unum
; ++i
) {
912 assert(kbufs
[i
] !is null && vbufs
[i
] !is null);
913 tdepot
.put(kbufs
[i
][0..ksizs
[i
]], vbufs
[i
][0..vsizs
[i
]], WMode
.KEEP
);
922 for (uint i
= 0; i
< unum
; ++i
) {
923 assert(kbufs
[i
] !is null && vbufs
[i
] !is null);
924 tdepot
.put(kbufs
[i
][0..ksizs
[i
]], vbufs
[i
][0..vsizs
[i
]], WMode
.KEEP
);
929 if (munmap(m_map
, m_msiz
) == -1) raise(Error
.MAP
);
930 m_map
= cast(char*)MAP_FAILED
;
931 if (ftruncate(m_fd
, 0) == -1) raise(Error
.TRUNC
);
932 fcopy(m_fd
, 0, tdepot
.m_fd
, 0);
933 m_fsiz
= tdepot
.m_fsiz
;
934 m_bnum
= tdepot
.m_bnum
;
936 for (uint i
= 0; i
< m_fbpsiz
; i
+= 2) {
937 m_fbpool
[i
] = m_fbpool
[i
+1] = -1;
939 m_msiz
= tdepot
.m_msiz
;
940 m_map
= cast(char*)mmap(null, m_msiz
, PROT_READ|PROT_WRITE
, MAP_SHARED
, m_fd
, 0);
941 if (m_map
== MAP_FAILED
) raise(Error
.MAP
);
942 m_buckets
= cast(int*)(m_map
+QDBMHeader
.sizeof
);
943 string tempname
= tdepot
.m_name
; // with trailing zero
945 if (unlink(tempname
.ptr
) == -1) raise(Error
.UNLINK
);
948 /** Get the name of a database.
951 * If successful, the return value is the pointer to the region of the name of the database,
952 * else, it is `null`.
953 * Because the region of the return value is allocated with the `malloc` call, it should be
954 * released with the `freeptr` call if it is no longer in use.
957 * DepotException on various errors
959 @property string
name () const @safe pure nothrow @nogc {
963 /** Get the size of a database file.
966 * If successful, the return value is the size of the database file, else, it is -1.
969 * DepotException on various errors
971 @property long fileSize () {
977 /** Get the number of the elements of the bucket array.
980 * If successful, the return value is the number of the elements of the bucket array, else, it is -1.
983 * DepotException on various errors
985 @property int bucketCount () {
990 /** Get the number of the used elements of the bucket array.
992 * This function is inefficient because it accesses all elements of the bucket array.
995 * If successful, the return value is the number of the used elements of the bucket array, else, it is -1.
998 * DepotException on various errors
1003 for (uint i
= 0; i
< m_bnum
; ++i
) if (m_buckets
[i
]) ++hits
;
1007 /** Get the number of the records stored in a database.
1010 * If successful, the return value is the number of the records stored in the database, else, it is -1.
1013 * DepotException on various errors
1015 @property int recordCount () {
1020 /** Check whether a database handle is a writer or not.
1023 * The return value is true if the handle is a writer, false if not.
1025 @property bool writable () const @safe pure nothrow @nogc {
1026 return (opened
&& m_wmode
);
1029 /** Check whether a database has a fatal error or not.
1032 * The return value is true if the database has a fatal error, false if not.
1034 @property bool fatalError () const @safe pure nothrow @nogc {
1038 /** Get the inode number of a database file.
1041 * The return value is the inode number of the database file.
1044 * DepotException on various errors
1046 @property long inode () {
1051 /** Get the last modified time of a database.
1054 * The return value is the last modified time of the database.
1057 * DepotException on various errors
1059 @property time_t
mtime () {
1064 /** Get the file descriptor of a database file.
1067 * The return value is the file descriptor of the database file.
1068 * Handling the file descriptor of a database file directly is not suggested.
1071 * DepotException on various errors
1073 @property int fdesc () {
1078 /** Remove a database file.
1081 * name = the name of a database file
1084 * DepotException on various errors
1086 static void remove (const(char)[] name
) {
1087 import core
.stdc
.errno
: errno
, ENOENT
;
1088 import core
.sys
.posix
.sys
.stat
: lstat
, stat_t
;
1089 import core
.sys
.posix
.unistd
: unlink
;
1090 import std
.string
: toStringz
;
1092 assert(name
.length
);
1093 auto namez
= name
.toStringz
;
1094 if (lstat(namez
, &sbuf
) == -1) {
1095 if (errno
!= ENOENT
) straise(Error
.STAT
);
1099 //k8:??? try to open the file to check if it's not locked or something
1100 auto depot
= new Depot(name
, WRITER|TRUNC
);
1103 if (unlink(namez
) == -1) {
1104 if (errno
!= ENOENT
) straise(Error
.UNLINK
);
1109 /** Repair a broken database file.
1111 * There is no guarantee that all records in a repaired database file correspond to the original
1112 * or expected state.
1115 * name = the name of a database file
1118 * true if ok, false is there were some errors
1121 * DepotException on various errors
1123 bool repair (const(char)[] name
) {
1124 import core
.sys
.posix
.fcntl
: open
, O_RDWR
;
1125 import core
.sys
.posix
.sys
.stat
: lstat
, stat_t
;
1126 import core
.sys
.posix
.unistd
: close
, ftruncate
, unlink
;
1131 int fd
, flags
, bnum
, tbnum
, rsiz
, ksiz
, vsiz
;
1135 assert(name
.length
);
1137 import std
.string
: toStringz
;
1138 auto namez
= name
.toStringz
;
1139 if (lstat(namez
, &sbuf
) == -1) raise(Error
.STAT
);
1140 fsiz
= sbuf
.st_size
;
1141 if ((fd
= open(namez
, O_RDWR
, DP_FILEMODE
)) == -1) raise(Error
.OPEN
);
1143 scope(exit
) if (fd
>= 0) close(fd
);
1144 fdseekread(fd
, 0, (&dbhead
)[0..1]);
1145 flags
= dbhead
.flags
;
1146 bnum
= dbhead
.nbuckets
;
1147 tbnum
= dbhead
.nrecords
*2;
1148 if (tbnum
< DP_DEFBNUM
) tbnum
= DP_DEFBNUM
;
1149 tdepot
= new Depot(name
~DP_TMPFSUF
, WRITER|CREAT|TRUNC
, tbnum
);
1150 off
= QDBMHeader
.sizeof
+bnum
*int.sizeof
;
1152 while (off
< fsiz
) {
1154 fdseekread(fd
, off
, (&head
)[0..1]);
1155 } catch (Exception
) {
1158 if (head
.flags
&DP_RECFDEL
) {
1159 rsiz
= head
.recsize
;
1160 if (rsiz
< 0) break;
1166 if (ksiz
>= 0 && vsiz
>= 0) {
1167 import core
.stdc
.stdlib
: malloc
;
1168 kbuf
= cast(char*)malloc(ksiz
+1);
1169 vbuf
= cast(char*)malloc(vsiz
+1);
1170 if (kbuf
!is null && vbuf
!is null) {
1172 fdseekread(fd
, off
+RecordHeader
.sizeof
, kbuf
[0..ksiz
]);
1173 fdseekread(fd
, off
+RecordHeader
.sizeof
+ksiz
, vbuf
[0..vsiz
]);
1174 tdepot
.put(kbuf
[0..ksiz
], vbuf
[0..vsiz
], WMode
.KEEP
);
1175 } catch (Exception
) {
1179 //if (!err) raise(Error.ALLOC);
1182 if (vbuf
!is null) freeptr(vbuf
);
1183 if (kbuf
!is null) freeptr(kbuf
);
1185 //if (!err) raise(Error.BROKEN);
1188 rsiz
= head
.recsize
;
1189 if (rsiz
< 0) break;
1192 tdepot
.flags
= flags
; // err = true;
1195 } catch (Exception
) {
1198 if (ftruncate(fd
, 0) == -1) {
1199 //if (!err) raise(Error.TRUNC);
1202 auto tempname
= tdepot
.m_name
; // with trailing zero
1204 fcopy(fd
, 0, tdepot
.m_fd
, 0);
1206 } catch (Exception
) {
1209 if (close(fd
) == -1) {
1210 //if (!err) raise(Error.CLOSE);
1214 if (unlink(tempname
.ptr
) == -1) {
1215 //if (!err) raise(Error.UNLINK);
1221 /** Hash function used inside Depot.
1223 * This function is useful when an application calculates the state of the inside bucket array.
1226 * kbuf = the pointer to the region of a key
1229 * The return value is the hash value of 31 bits length computed from the key.
1231 static int innerhash (const(void)[] kbuf
) @trusted nothrow @nogc {
1233 if (kbuf
.length
== int.sizeof
) {
1234 import core
.stdc
.string
: memcpy
;
1235 memcpy(&res
, kbuf
.ptr
, res
.sizeof
);
1239 foreach (immutable bt; cast(const(ubyte)[])kbuf
) res
= res
*31+bt;
1240 return (res
*87767623)&int.max
;
1243 /** Hash function which is independent from the hash functions used inside Depot.
1245 * This function is useful when an application uses its own hash algorithm outside Depot.
1248 * kbuf = the pointer to the region of a key
1251 * The return value is the hash value of 31 bits length computed from the key.
1253 static int outerhash (const(void)[] kbuf
) @trusted nothrow @nogc {
1254 int res
= 774831917;
1255 foreach_reverse (immutable bt; cast(const(ubyte)[])kbuf
) res
= res
*29+bt;
1256 return (res
*5157883)&int.max
;
1259 /** Get a natural prime number not less than a number.
1261 * This function is useful when an application determines the size of a bucket array of its
1262 * own hash algorithm.
1265 * num = a natural number
1268 * The return value is a natural prime number not less than the specified number
1270 static int primenum (int num
) @safe pure nothrow @nogc {
1271 static immutable int[217] primes
= [
1272 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 43, 47, 53, 59, 61, 71, 79, 83,
1273 89, 103, 109, 113, 127, 139, 157, 173, 191, 199, 223, 239, 251, 283, 317, 349,
1274 383, 409, 443, 479, 509, 571, 631, 701, 761, 829, 887, 953, 1021, 1151, 1279,
1275 1399, 1531, 1663, 1789, 1913, 2039, 2297, 2557, 2803, 3067, 3323, 3583, 3833,
1276 4093, 4603, 5119, 5623, 6143, 6653, 7159, 7673, 8191, 9209, 10223, 11261,
1277 12281, 13309, 14327, 15359, 16381, 18427, 20479, 22511, 24571, 26597, 28669,
1278 30713, 32749, 36857, 40949, 45053, 49139, 53239, 57331, 61417, 65521, 73727,
1279 81919, 90107, 98299, 106487, 114679, 122869, 131071, 147451, 163819, 180221,
1280 196597, 212987, 229373, 245759, 262139, 294911, 327673, 360439, 393209, 425977,
1281 458747, 491503, 524287, 589811, 655357, 720887, 786431, 851957, 917503, 982981,
1282 1048573, 1179641, 1310719, 1441771, 1572853, 1703903, 1835003, 1966079,
1283 2097143, 2359267, 2621431, 2883577, 3145721, 3407857, 3670013, 3932153,
1284 4194301, 4718579, 5242877, 5767129, 6291449, 6815741, 7340009, 7864301,
1285 8388593, 9437179, 10485751, 11534329, 12582893, 13631477, 14680063, 15728611,
1286 16777213, 18874367, 20971507, 23068667, 25165813, 27262931, 29360087, 31457269,
1287 33554393, 37748717, 41943023, 46137319, 50331599, 54525917, 58720253, 62914549,
1288 67108859, 75497467, 83886053, 92274671, 100663291, 109051903, 117440509,
1289 125829103, 134217689, 150994939, 167772107, 184549373, 201326557, 218103799,
1290 234881011, 251658227, 268435399, 301989881, 335544301, 369098707, 402653171,
1291 436207613, 469762043, 503316469, 536870909, 603979769, 671088637, 738197503,
1292 805306357, 872415211, 939524087, 1006632947, 1073741789, 1207959503,
1293 1342177237, 1476394991, 1610612711, 1744830457, 1879048183, 2013265907,
1296 foreach (immutable pr
; primes
) if (num
<= pr
) return pr
;
1300 /* ********************************************************************************************* *
1301 * features for experts
1302 * ********************************************************************************************* */
1304 /** Synchronize updating contents on memory.
1307 * DepotException on various errors
1310 import core
.sys
.posix
.sys
.mman
: msync
, MS_SYNC
;
1313 if (msync(m_map
, m_msiz
, MS_SYNC
) == -1) {
1319 /** Synchronize updating contents on memory, not physically.
1322 * DepotException on various errors
1327 // there is no mflush() call
1329 if (mflush(m_map
, m_msiz
, MS_SYNC
) == -1) {
1336 /** Get flags of a database.
1339 * The return value is the flags of a database.
1342 * DepotException on various errors
1344 @property int flags () {
1346 auto hdr
= cast(QDBMHeader
*)m_map
;
1350 /** Set flags of a database.
1353 * flags = flags to set. Least ten bits are reserved for internal use.
1356 * If successful, the return value is true, else, it is false.
1359 * DepotException on various errors
1361 @property void flags (int v
) {
1363 auto hdr
= cast(QDBMHeader
*)m_map
;
1368 /* ********************************************************************************************* *
1370 * ********************************************************************************************* */
1372 void raise (Error errcode
, string file
=__FILE__
, usize line
=__LINE__
) {
1373 assert(errcode
>= Error
.NOERR
);
1374 if (errcode
== Error
.FATAL
) m_fatal
= true;
1375 throw new DepotException(errcode
, file
, line
);
1378 static void straise (Error errcode
, string file
=__FILE__
, usize line
=__LINE__
) {
1379 assert(errcode
>= Error
.NOERR
);
1380 throw new DepotException(errcode
, file
, line
);
1383 // get the second hash value
1384 static int secondhash (const(void)[] kbuf
) @trusted nothrow @nogc {
1386 foreach_reverse (immutable bt; cast(const(ubyte)[])kbuf
) res
= res
*37+bt;
1387 return (res
*43321879)&int.max
;
1390 void updateHeader () @trusted nothrow @nogc {
1391 auto hdr
= cast(QDBMHeader
*)m_map
;
1392 hdr
.filesize
= cast(int)m_fsiz
;
1393 hdr
.nrecords
= m_rnum
;
1396 /* Lock a file descriptor.
1399 * fd = a file descriptor
1400 * ex = whether an exclusive lock or a shared lock is performed
1401 * nb = whether to request with non-blocking
1402 * errcode = the error code (can be `null`)
1405 * DepotException on various errors
1407 static void fdlock (int fd
, int ex
, int nb
) {
1408 import core
.stdc
.stdio
: SEEK_SET
;
1409 import core
.stdc
.string
: memset
;
1410 import core
.sys
.posix
.fcntl
: flock
, fcntl
, F_RDLCK
, F_SETLK
, F_SETLKW
, F_WRLCK
;
1413 memset(&lock, 0, lock.sizeof
);
1414 lock.l_type
= (ex ? F_WRLCK
: F_RDLCK
);
1415 lock.l_whence
= SEEK_SET
;
1419 while (fcntl(fd
, nb ? F_SETLK
: F_SETLKW
, &lock) == -1) {
1420 import core
.stdc
.errno
: errno
, EINTR
;
1421 if (errno
!= EINTR
) straise(Error
.LOCK
);
1425 /* Write into a file.
1428 * fd = a file descriptor
1429 * buf = a buffer to write
1432 * The return value is the size of the written buffer, or -1 on failure
1437 static int fdwrite (int fd
, const(void)[] buf
) @trusted nothrow @nogc {
1438 auto lbuf
= cast(const(ubyte)[])buf
;
1441 while (lbuf
.length
> 0) {
1442 import core
.sys
.posix
.unistd
: write
;
1443 auto wb
= write(fd
, lbuf
.ptr
, lbuf
.length
);
1445 import core
.stdc
.errno
: errno
, EINTR
;
1446 if (errno
!= EINTR
) return -1;
1456 /* Write into a file at an offset.
1459 * fd = a file descriptor
1460 * off = an offset of the file
1461 * buf = a buffer to write
1464 * DepotException on various errors
1466 static void fdseekwrite (int fd
, long off
, const(void)[] buf
) {
1467 import core
.stdc
.stdio
: SEEK_END
, SEEK_SET
;
1468 import core
.sys
.posix
.unistd
: lseek
;
1470 if (buf
.length
< 1) return;
1471 if (lseek(fd
, (off
< 0 ?
0 : off
), (off
< 0 ? SEEK_END
: SEEK_SET
)) == -1) straise(Error
.SEEK
);
1472 if (fdwrite(fd
, buf
) != buf
.length
) straise(Error
.WRITE
);
1475 /* Write an integer into a file at an offset.
1478 * fd = a file descriptor
1479 * off = an offset of the file
1483 * DepotException on various errors
1485 void fdseekwritenum (int fd
, long off
, int num
) {
1487 scope(failure
) m_fatal
= true;
1488 fdseekwrite(fd
, off
, (&num
)[0..1]);
1491 /* Read from a file and store the data into a buffer.
1494 * fd = a file descriptor
1495 * buf = a buffer to store into.
1498 * The return value is the size read with, or -1 on failure
1503 static int fdread (int fd
, void[] buf
) @trusted nothrow @nogc {
1504 import core
.sys
.posix
.unistd
: read
;
1505 auto lbuf
= cast(ubyte[])buf
;
1508 while (lbuf
.length
> 0) {
1509 auto bs
= read(fd
, lbuf
.ptr
, lbuf
.length
);
1511 import core
.stdc
.errno
: errno
, EINTR
;
1512 if (errno
!= EINTR
) return -1;
1517 total
+= cast(int)bs
;
1522 /* Read from a file at an offset and store the data into a buffer.
1525 * fd = a file descriptor
1526 * off = an offset of the file
1527 * buf = a buffer to store into
1530 * DepotException on various errors
1532 static void fdseekread (int fd
, long off
, void[] buf
) {
1533 import core
.stdc
.stdio
: SEEK_SET
;
1534 import core
.sys
.posix
.unistd
: lseek
;
1535 assert(fd
>= 0 && off
>= 0);
1536 if (lseek(fd
, off
, SEEK_SET
) != off
) straise(Error
.SEEK
);
1537 if (fdread(fd
, buf
) != buf
.length
) straise(Error
.READ
);
1540 /* Copy data between files.
1543 * destfd = a file descriptor of a destination file
1544 * destoff = an offset of the destination file
1545 * srcfd = a file descriptor of a source file
1546 * srcoff = an offset of the source file
1549 * The return value is the size copied with
1552 * DepotException on various errors
1554 static int fcopy (int destfd
, long destoff
, int srcfd
, long srcoff
) {
1555 import core
.stdc
.stdio
: SEEK_SET
;
1556 import core
.sys
.posix
.unistd
: lseek
;
1557 char[DP_IOBUFSIZ
] iobuf
;
1559 if (lseek(srcfd
, srcoff
, SEEK_SET
) == -1 ||
lseek(destfd
, destoff
, SEEK_SET
) == -1) straise(Error
.SEEK
);
1561 while ((iosiz
= fdread(srcfd
, iobuf
[])) > 0) {
1562 if (fdwrite(destfd
, iobuf
[0..iosiz
]) != iosiz
) straise(Error
.WRITE
);
1565 if (iosiz
< 0) straise(Error
.READ
);
1569 /* Get the padding size of a record.
1572 * ksiz = the size of the key of a record
1573 * vsiz = the size of the value of a record
1576 * The return value is the padding size of a record
1578 usize
padsize (usize ksiz
, usize vsiz
) const @safe pure nothrow @nogc {
1579 if (m_alignment
> 0) {
1580 return cast(usize
)(m_alignment
-(m_fsiz
+RecordHeader
.sizeof
+ksiz
+vsiz
)%m_alignment
);
1581 } else if (m_alignment
< 0) {
1582 usize pad
= cast(usize
)(vsiz
*(2.0/(1<<(-m_alignment
))));
1583 if (vsiz
+pad
>= DP_FSBLKSIZ
) {
1584 if (vsiz
<= DP_FSBLKSIZ
) pad
= 0;
1585 if (m_fsiz
%DP_FSBLKSIZ
== 0) {
1586 return cast(usize
)((pad
/DP_FSBLKSIZ
)*DP_FSBLKSIZ
+DP_FSBLKSIZ
-(m_fsiz
+RecordHeader
.sizeof
+ksiz
+vsiz
)%DP_FSBLKSIZ
);
1588 return cast(usize
)((pad
/(DP_FSBLKSIZ
/2))*(DP_FSBLKSIZ
/2)+(DP_FSBLKSIZ
/2)-
1589 (m_fsiz
+RecordHeader
.sizeof
+ksiz
+vsiz
)%(DP_FSBLKSIZ
/2));
1592 return (pad
>= RecordHeader
.sizeof ? pad
: RecordHeader
.sizeof
);
1598 /* Read the header of a record.
1601 * off = an offset of the database file
1602 * head = specifies a buffer for the header
1603 * ebuf = specifies the pointer to the entity buffer
1604 * eep = the pointer to a variable to which whether ebuf was used is assigned
1607 * DepotException on various errors
1609 void rechead (long off
, ref RecordHeader head
, void[] ebuf
, bool* eep
) {
1611 if (eep
!is null) *eep
= false;
1612 if (off
< 0 || off
> m_fsiz
) raise(Error
.BROKEN
);
1613 scope(failure
) m_fatal
= true; // any failure is fatal here
1614 if (ebuf
.length
>= DP_ENTBUFSIZ
&& off
< m_fsiz
-DP_ENTBUFSIZ
) {
1615 import core
.stdc
.string
: memcpy
;
1616 if (eep
!is null) *eep
= true;
1617 fdseekread(m_fd
, off
, ebuf
[0..DP_ENTBUFSIZ
]);
1618 memcpy(&head
, ebuf
.ptr
, RecordHeader
.sizeof
);
1620 fdseekread(m_fd
, off
, (&head
)[0..1]);
1622 if (head
.ksiz
< 0 || head
.vsiz
< 0 || head
.psiz
< 0 || head
.left
< 0 || head
.right
< 0) raise(Error
.BROKEN
);
1625 /* Read the entitiy of the key of a record.
1628 * off = an offset of the database file
1629 * head = the header of a record
1632 * The return value is a key data whose region is allocated by `malloc`
1635 * DepotException on various errors
1637 char* reckey (long off
, ref in RecordHeader head
) {
1638 //TODO: return slice instead of pointer?
1639 import core
.stdc
.stdlib
: malloc
;
1642 int ksiz
= head
.ksiz
;
1643 kbuf
= cast(char*)malloc(ksiz
+1);
1644 if (kbuf
is null) raise(Error
.ALLOC
);
1645 scope(failure
) freeptr(kbuf
);
1646 fdseekread(m_fd
, off
+RecordHeader
.sizeof
, kbuf
[0..ksiz
]);
1651 /* Read the entitiy of the value of a record.
1654 * off = an offset of the database file
1655 * head = the header of a record
1656 * start = the offset address of the beginning of the region of the value to be read
1657 * max = the max size to be read; if it is `uint.max`, the size to read is unlimited
1660 * The return value is a value data whose region is allocated by `malloc`
1663 * DepotException on various errors
1665 char* recval (long off
, ref RecordHeader head
, uint start
=0, uint max
=uint.max
) {
1666 //TODO: return slice instead of pointer?
1667 import core
.stdc
.stdlib
: malloc
;
1672 if (max
== uint.max
) {
1675 vsiz
= (max
< head
.vsiz ? max
: head
.vsiz
);
1677 vbuf
= cast(char*)malloc(vsiz
+1);
1678 if (vbuf
is null) { m_fatal
= true; raise(Error
.ALLOC
); }
1679 scope(failure
) { m_fatal
= true; freeptr(vbuf
); }
1680 fdseekread(m_fd
, off
+RecordHeader
.sizeof
+head
.ksiz
+start
, vbuf
[0..vsiz
]);
1685 /* Read the entitiy of the value of a record and write it into a given buffer.
1688 * off = an offset of the database file
1689 * head = the header of a record
1690 * start = the offset address of the beginning of the region of the value to be read
1691 * vbuf = the pointer to a buffer into which the value of the corresponding record is written
1694 * The return value is the size of the written data
1697 * DepotException on various errors
1699 usize
recvalwb (void[] vbuf
, long off
, ref RecordHeader head
, uint start
=0) {
1702 usize vsiz
= (vbuf
.length
< head
.vsiz ? vbuf
.length
: head
.vsiz
);
1703 fdseekread(m_fd
, off
+RecordHeader
.sizeof
+head
.ksiz
+start
, vbuf
[0..vsiz
]);
1707 /* Compare two keys.
1710 * abuf = the pointer to the region of the former
1711 * asiz = the size of the region
1712 * bbuf = the pointer to the region of the latter
1713 * bsiz = the size of the region
1716 * The return value is 0 if two equals, positive if the formar is big, else, negative.
1718 static int keycmp (const(void)[] abuf
, const(void)[] bbuf
) @trusted nothrow @nogc {
1719 import core
.stdc
.string
: memcmp
;
1720 //assert(abuf && asiz >= 0 && bbuf && bsiz >= 0);
1721 if (abuf
.length
> bbuf
.length
) return 1;
1722 if (abuf
.length
< bbuf
.length
) return -1;
1723 return memcmp(abuf
.ptr
, bbuf
.ptr
, abuf
.length
);
1726 /* Search for a record.
1729 * kbuf = the pointer to the region of a key
1730 * hash = the second hash value of the key
1731 * bip = the pointer to the region to assign the index of the corresponding record
1732 * offp = the pointer to the region to assign the last visited node in the hash chain,
1733 * or, -1 if the hash chain is empty
1734 * entp = the offset of the last used joint, or, -1 if the hash chain is empty
1735 * head = the pointer to the region to store the header of the last visited record in
1736 * ebuf = the pointer to the entity buffer
1737 * eep = the pointer to a variable to which whether ebuf was used is assigned
1738 * delhit = whether a deleted record corresponds or not
1741 * The return value is true if record was found, false if there is no corresponding record.
1744 * DepotException on various errors
1746 bool recsearch (const(void)[] kbuf
, int hash
, int* bip
, int* offp
, int* entp
,
1747 ref RecordHeader head
, void[] ebuf
, bool* eep
, Flag
!"delhit" delhit
=No
.delhit
)
1752 char[DP_STKBUFSIZ
] stkey
;
1754 assert(ebuf
.length
>= DP_ENTBUFSIZ
);
1755 assert(hash
>= 0 && bip
!is null && offp
!is null && entp
!is null && eep
!is null);
1756 thash
= innerhash(kbuf
);
1757 *bip
= thash
%m_bnum
;
1758 off
= m_buckets
[*bip
];
1764 rechead(off
, head
, ebuf
, eep
);
1767 entoff
= cast(int)(off
+RecordHeader
.left
.offsetof
);
1769 } else if (hash
< thash
) {
1770 entoff
= cast(int)(off
+RecordHeader
.right
.offsetof
);
1773 if (*eep
&& RecordHeader
.sizeof
+head
.ksiz
<= DP_ENTBUFSIZ
) {
1774 immutable ebstart
= RecordHeader
.sizeof
;
1775 kcmp
= keycmp(kbuf
, ebuf
[ebstart
..ebstart
+head
.ksiz
]);
1776 } else if (head
.ksiz
> DP_STKBUFSIZ
) {
1777 if ((tkey
= reckey(off
, head
)) is null) raise(Error
.FATAL
);
1778 kcmp
= keycmp(kbuf
, tkey
[0..head
.ksiz
]);
1782 fdseekread(m_fd
, off
+RecordHeader
.sizeof
, stkey
[0..head
.ksiz
]);
1783 } catch (Exception
) {
1786 kcmp
= keycmp(kbuf
, stkey
[0..head
.ksiz
]);
1789 entoff
= cast(int)(off
+RecordHeader
.left
.offsetof
);
1791 } else if (kcmp
< 0) {
1792 entoff
= cast(int)(off
+RecordHeader
.right
.offsetof
);
1795 if (!delhit
&& (head
.flags
&DP_RECFDEL
)) {
1796 entoff
= cast(int)(off
+RecordHeader
.left
.offsetof
);
1799 *offp
= cast(int)off
;
1806 *offp
= cast(int)off
;
1811 /* Overwrite a record.
1814 * off = the offset of the database file
1815 * rsiz = the size of the existing record
1816 * kbuf = the pointer to the region of a key
1817 * vbuf = the pointer to the region of a value
1818 * hash = the second hash value of the key
1819 * left = the offset of the left child
1820 * right = the offset of the right child
1823 * DepotException on various errors
1825 void recrewrite (long off
, int rsiz
, const(void)[] kbuf
, const(void)[] vbuf
, int hash
, int left
, int right
) {
1826 char[DP_WRTBUFSIZ
] ebuf
;
1828 int hoff
, koff
, voff
, mi
, min
, size
;
1830 assert(off
>= 1 && rsiz
> 0);
1833 head
.ksiz
= cast(int)kbuf
.length
;
1834 head
.vsiz
= cast(int)vbuf
.length
;
1835 head
.psiz
= cast(int)(rsiz
-head
.sizeof
-kbuf
.length
-vbuf
.length
);
1838 asiz
= head
.sizeof
+kbuf
.length
+vbuf
.length
;
1839 if (m_fbpsiz
> DP_FBPOOLSIZ
*4 && head
.psiz
> asiz
) {
1840 rsiz
= cast(int)((head
.psiz
-asiz
)/2+asiz
);
1845 if (asiz
<= DP_WRTBUFSIZ
) {
1846 import core
.stdc
.string
: memcpy
;
1847 memcpy(ebuf
.ptr
, &head
, head
.sizeof
);
1848 memcpy(ebuf
.ptr
+head
.sizeof
, kbuf
.ptr
, kbuf
.length
);
1849 memcpy(ebuf
.ptr
+head
.sizeof
+kbuf
.length
, vbuf
.ptr
, vbuf
.length
);
1850 fdseekwrite(m_fd
, off
, ebuf
[0..asiz
]);
1852 hoff
= cast(int)off
;
1853 koff
= cast(int)(hoff
+head
.sizeof
);
1854 voff
= cast(int)(koff
+kbuf
.length
);
1855 fdseekwrite(m_fd
, hoff
, (&head
)[0..1]);
1856 fdseekwrite(m_fd
, koff
, kbuf
[]);
1857 fdseekwrite(m_fd
, voff
, vbuf
[]);
1860 off
+= head
.sizeof
+kbuf
.length
+vbuf
.length
+head
.psiz
;
1861 head
.flags
= DP_RECFDEL|DP_RECFREUSE
;
1863 head
.ksiz
= cast(int)kbuf
.length
;
1864 head
.vsiz
= cast(int)vbuf
.length
;
1865 head
.psiz
= cast(int)(rsiz
-head
.sizeof
-kbuf
.length
-vbuf
.length
);
1868 fdseekwrite(m_fd
, off
, (&head
)[0..1]);
1869 size
= head
.recsize
;
1872 for (uint i
= 0; i
< m_fbpsiz
; i
+= 2) {
1873 if (m_fbpool
[i
] == -1) {
1874 m_fbpool
[i
] = cast(int)off
;
1875 m_fbpool
[i
+1] = size
;
1880 if (mi
== -1 || m_fbpool
[i
+1] < min
) {
1882 min
= m_fbpool
[i
+1];
1885 if (mi
>= 0 && size
> min
) {
1886 m_fbpool
[mi
] = cast(int)off
;
1887 m_fbpool
[mi
+1] = size
;
1893 /* Write a record at the end of a database file.
1896 * kbuf = the pointer to the region of a key
1897 * vbuf = the pointer to the region of a value
1898 * hash = the second hash value of the key
1899 * left = the offset of the left child
1900 * right = the offset of the right child
1903 * The return value is the offset of the record
1906 * DepotException on various errors
1908 int recappend (const(void)[] kbuf
, const(void)[] vbuf
, int hash
, int left
, int right
) {
1909 char[DP_WRTBUFSIZ
] ebuf
;
1913 psiz
= padsize(kbuf
.length
, vbuf
.length
);
1916 head
.ksiz
= cast(int)kbuf
.length
;
1917 head
.vsiz
= cast(int)vbuf
.length
;
1918 head
.psiz
= cast(int)psiz
;
1921 asiz
= head
.sizeof
+kbuf
.length
+vbuf
.length
+psiz
;
1923 if (asiz
<= DP_WRTBUFSIZ
) {
1924 import core
.stdc
.string
: memcpy
, memset
;
1925 memcpy(ebuf
.ptr
, &head
, head
.sizeof
);
1926 memcpy(ebuf
.ptr
+head
.sizeof
, kbuf
.ptr
, kbuf
.length
);
1927 memcpy(ebuf
.ptr
+head
.sizeof
+kbuf
.length
, vbuf
.ptr
, vbuf
.length
);
1928 memset(ebuf
.ptr
+head
.sizeof
+kbuf
.length
+vbuf
.length
, 0, psiz
);
1929 fdseekwrite(m_fd
, off
, ebuf
[0..asiz
]);
1931 import core
.stdc
.stdlib
: malloc
;
1932 import core
.stdc
.string
: memcpy
, memset
;
1933 auto hbuf
= cast(char*)malloc(asiz
);
1934 if (hbuf
is null) raise(Error
.ALLOC
);
1935 scope(exit
) freeptr(hbuf
);
1936 memcpy(hbuf
, &head
, head
.sizeof
);
1937 memcpy(hbuf
+head
.sizeof
, kbuf
.ptr
, kbuf
.length
);
1938 memcpy(hbuf
+head
.sizeof
+kbuf
.length
, vbuf
.ptr
, vbuf
.length
);
1939 memset(hbuf
+head
.sizeof
+kbuf
.length
+vbuf
.length
, 0, psiz
);
1940 fdseekwrite(m_fd
, off
, hbuf
[0..asiz
]);
1943 return cast(int)off
;
1946 /* Overwrite the value of a record.
1949 * off = the offset of the database file
1950 * head = the header of the record
1951 * vbuf = the pointer to the region of a value
1952 * cat = whether it is concatenate mode or not
1955 * DepotException on various errors
1957 void recover (long off
, ref RecordHeader head
, const(void)[] vbuf
, Flag
!"catmode" catmode
) {
1959 for (uint i
= 0; i
< m_fbpsiz
; i
+= 2) {
1960 if (m_fbpool
[i
] == off
) {
1961 m_fbpool
[i
] = m_fbpool
[i
+1] = -1;
1966 long voff
= off
+RecordHeader
.sizeof
+head
.ksiz
;
1968 head
.psiz
-= vbuf
.length
;
1969 head
.vsiz
+= vbuf
.length
;
1970 voff
+= head
.vsiz
-vbuf
.length
;
1972 head
.psiz
+= head
.vsiz
-vbuf
.length
;
1973 head
.vsiz
= cast(int)vbuf
.length
;
1975 scope(failure
) m_fatal
= true; // any failure is fatal here
1976 fdseekwrite(m_fd
, off
, (&head
)[0..1]);
1977 fdseekwrite(m_fd
, voff
, vbuf
[]);
1983 * off = the offset of the database file
1984 * head = the header of the record
1985 * reusable = whether the region is reusable or not
1988 * DepotException on various errors
1990 void recdelete (long off
, ref in RecordHeader head
, Flag
!"reusable" reusable
) {
1993 auto size
= head
.recsize
;
1996 for (uint i
= 0; i
< m_fbpsiz
; i
+= 2) {
1997 if (m_fbpool
[i
] == -1) {
1998 m_fbpool
[i
] = cast(int)off
;
1999 m_fbpool
[i
+1] = size
;
2004 if (mi
== -1 || m_fbpool
[i
+1] < min
) {
2006 min
= m_fbpool
[i
+1];
2009 if (mi
>= 0 && size
> min
) {
2010 m_fbpool
[mi
] = cast(int)off
;
2011 m_fbpool
[mi
+1] = size
;
2015 fdseekwritenum(m_fd
, off
+RecordHeader
.flags
.offsetof
, DP_RECFDEL|
(reusable ? DP_RECFREUSE
: 0));
2018 /* Make contiguous records of the free block pool coalesce. */
2019 void fbpoolcoal () @trusted {
2020 import core
.stdc
.stdlib
: qsort
;
2021 if (m_fbpinc
++ <= m_fbpsiz
/4) return;
2023 qsort(m_fbpool
, m_fbpsiz
/2, int.sizeof
*2, &fbpoolcmp
);
2024 for (uint i
= 2; i
< m_fbpsiz
; i
+= 2) {
2025 if (m_fbpool
[i
-2] > 0 && m_fbpool
[i
-2]+m_fbpool
[i
-1]-m_fbpool
[i
] == 0) {
2026 m_fbpool
[i
] = m_fbpool
[i
-2];
2027 m_fbpool
[i
+1] += m_fbpool
[i
-1];
2028 m_fbpool
[i
-2] = m_fbpool
[i
-1] = -1;
2033 /* Compare two records of the free block pool.
2034 a = the pointer to one record.
2035 b = the pointer to the other record.
2036 The return value is 0 if two equals, positive if the formar is big, else, negative. */
2037 static extern(C
) int fbpoolcmp (in void* a
, in void* b
) @trusted nothrow @nogc {
2039 return *cast(const int*)a
- *cast(const int*)b
;