1 /* This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
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 by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
18 // CDB key/value database creator
19 module iv
.tinycdbmk
/*is aliced*/;
25 private static import iv
.tinycdb
;
27 alias hash
= iv
.tinycdb
.CDB
.hash
;
30 align(1) static struct Rec
{
36 static struct RecList
{
42 int mFD
= -1; /* file descriptor */
44 uint mDataPos
; /* data position so far */
45 uint mRCount
; /* record count so far */
46 ubyte[4096] mBuf
= void; /* write buffer */
47 ubyte* mBufPos
; /* current buf position */
48 RecList
*[256] mTables
; /* list of arrays of record infos */
53 PUT_ADD
= 0, /* add unconditionnaly, like cdb_make_add() */
54 PUT_REPLACE
, /* replace: do not place to index OLD record */
55 PUT_INSERT
, /* add only if not already exists */
56 PUT_WARN
, /* add unconditionally but ret. 1 if exists */
57 PUT_REPLACE0
, /* if a record exists, fill old one with zeros */
60 FIND_REMOVE
= PUT_REPLACE
,
61 FIND_FILL0
= PUT_REPLACE0
,
66 @disable this (this); // no copying
68 this (string fname
) { create(fname
); }
70 bool create (string fname
) {
71 import std
.string
: toStringz
;
72 import core
.sys
.posix
.fcntl
: xopen
= open
, O_CREAT
, O_RDWR
, O_TRUNC
;
74 int fd
= xopen(fname
.toStringz
, O_RDWR|O_CREAT|O_TRUNC
, 384);
86 this (int fd
) @nogc { create(fd
); }
88 @property bool opened () const pure @safe { return (mFD
>= 0); }
89 @property bool valid () const pure @safe { return (mFD
>= 0 && !mWasError
); }
91 //TODO: seek to start and truncate
92 void create (int fd
) {
96 mBufPos
= mBuf
.ptr
+2048;
98 if (!seek(0)) mWasError
= true;
106 import core
.sys
.posix
.unistd
: xclose
= close
;
109 foreach (immutable t
; 0..256) {
110 auto rl
= mTables
[t
];
111 while (rl
!is null) {
112 import core
.stdc
.stdlib
: free
;
130 bool find (const(void)[] key
, int mode
, bool *err
=null) {
131 if (err
!is null) *err
= false;
132 auto res
= findRecord(key
, hash(key
), mode
);
133 if (err
!is null && res
== FindRes
.ERROR
) *err
= true;
134 return (res
== FindRes
.FOUND
);
136 bool exists (const(void)[] key
) { return (findRecord(key
, hash(key
), FIND
) == FindRes
.FOUND
); }
138 bool add (const(void)[] key
, const(void)[] val
) { return addRecord(hash(key
), key
, val
); }
140 // true: record was replaced
141 // false: new record or error
142 bool put (const(void)[] key
, const(void)[] val
, int mode
=PUT_ADD
, bool *err
=null) {
143 if (err
!is null) *err
= false;
144 uint hval
= hash(key
);
151 r
= findRecord(key
, hval
, mode
);
152 if (r
== FindRes
.ERROR
) {
153 if (err
!is null) *err
= true;
156 if (r
== FindRes
.FOUND
&& mode
== PUT_INSERT
) return /*errno = EEXIST,*/true;
159 r
= FindRes
.NOT_FOUND
;
162 if (err
!is null) *err
= true;
163 return /*errno = EINVAL,*/false;
165 if (!addRecord(hval
, key
, val
)) {
166 if (err
!is null) *err
= true;
169 return (r
== FindRes
.FOUND
);
173 private bool seek (uint pos
) {
174 import core
.sys
.posix
.unistd
: lseek
;
175 import core
.stdc
.stdio
: SEEK_SET
;
176 return (lseek(mFD
, pos
, SEEK_SET
) >= 0);
179 private bool fullRead (ubyte* buf
, uint len
) {
181 import core
.stdc
.errno
;
182 import core
.sys
.posix
.unistd
: read
;
183 auto l
= read(mFD
, cast(void*)buf
, len
);
187 } else if (l
== 0 ||
(l
< 0 && errno
!= EINTR
)) {
194 private int read (ubyte* buf
, uint len
) {
195 immutable flen
= len
;
197 import core
.stdc
.errno
;
198 static import core
.sys
.posix
.unistd
;
199 auto l
= core
.sys
.posix
.unistd
.read(mFD
, cast(void*)buf
, len
);
203 } else if (l
< 0 && errno
!= EINTR
) {
209 return cast(int)(flen
-len
);
212 private bool fullWrite (const(void)* bufp
, uint len
) {
213 auto buf
= cast(const(ubyte)*)bufp
;
215 import core
.stdc
.errno
;
216 import core
.sys
.posix
.unistd
: write
;
217 auto l
= write(mFD
, cast(const void*)buf
, len
);
221 } else if (l
== 0 ||
(l
< 0 && errno
!= EINTR
)) {
228 private bool flush () {
229 uint len
= cast(uint)(mBufPos
-mBuf
.ptr
);
231 if (!fullWrite(mBuf
.ptr
, len
)) return false;
237 private bool write (const(void)* ptrp
, uint len
) {
238 import core
.stdc
.string
: memcpy
;
239 auto ptr
= cast(const(ubyte)*)ptrp
;
240 uint l
= cast(uint)(mBuf
.sizeof
-(mBufPos
-mBuf
.ptr
));
243 if (l
) memcpy(mBufPos
, ptr
, l
);
245 if (!flush()) return false;
248 l
= cast(uint)(len
/mBuf
.sizeof
);
250 l
*= cast(uint)mBuf
.sizeof
;
251 if (!fullWrite(ptr
, l
)) return false;
257 memcpy(mBufPos
, ptr
, len
);
263 private bool finish () {
264 uint[256] hcnt
; /* hash table counts */
265 uint[256] hpos
; /* hash table positions */
271 if (((0xffffffffu
-mDataPos
)>>3) < mRCount
) {
273 return /*errno = ENOMEM, -1*/false;
276 /* count htab sizes and reorder reclists */
278 foreach (immutable uint t
; 0..256) {
283 RecList
* rln
= rl
.next
;
290 if (hsize
< (hcnt
[t
] = i
<<1)) hsize
= hcnt
[t
];
293 import core
.stdc
.stdlib
: malloc
, free
;
294 /* allocate memory to hold max htable */
295 htab
= cast(Rec
*)malloc((hsize
+2)*htab
[0].sizeof
);
298 return /*errno = ENOENT, -1*/false;
300 p
= cast(ubyte*)htab
;
303 /* build hash tables */
304 foreach (immutable uint t
; 0..256) {
307 if ((len
= hcnt
[t
]) == 0) continue;
308 foreach (immutable i
; 0..len
) htab
[i
].hval
= htab
[i
].rpos
= 0;
309 for (rl
= mTables
[t
]; rl
!is null; rl
= rl
.next
) {
310 foreach (immutable i
; 0..rl
.count
) {
311 hi
= (rl
.rec
[i
].hval
>>8)%len
;
312 while (htab
[hi
].rpos
) if (++hi
== len
) hi
= 0;
313 htab
[hi
] = rl
.rec
[i
];
316 foreach (immutable i
; 0..len
) {
317 pack(htab
[i
].hval
, p
+(i
<<3));
318 pack(htab
[i
].rpos
, p
+(i
<<3)+4);
320 if (!write(p
, len
<<3)) {
333 foreach (immutable t
; 0..256) {
334 pack(hpos
[t
], p
+(t
<<3));
335 pack(hcnt
[t
], p
+(t
<<3)+4);
338 if (!seek(0) ||
!fullWrite(p
, 2048)) {
346 private bool addRecord (uint hval
, const(void)[] key
, const(void)[] val
) {
347 immutable klen
= cast(uint)key
.length
;
348 if (klen
== 0) return false;
349 immutable vlen
= cast(uint)val
.length
;
353 if (klen
> 0xffffffffu
-(mDataPos
+8) || vlen
> 0xffffffffu
-(mDataPos
+klen
+8)) {
355 return /*errno = ENOMEM, -1*/false;
357 i
= hval
&0xff; // hash table number
359 if (rl
is null || rl
.count
>= rl
.rec
.length
) {
361 import core
.stdc
.stdlib
: malloc
;
362 rl
= cast(RecList
*)malloc(RecList
.sizeof
);
363 if (rl
is null) return /*errno = ENOMEM, -1*/false;
365 rl
.next
= mTables
[i
];
369 rl
.rec
[i
].hval
= hval
;
370 rl
.rec
[i
].rpos
= mDataPos
;
372 pack(klen
, rlen
.ptr
);
373 pack(vlen
, rlen
.ptr
+4);
374 if (!write(rlen
.ptr
, 8) ||
!write(key
.ptr
, klen
) ||
!write(val
.ptr
, vlen
)) {
381 void fixupRPos (uint rpos
, uint rlen
) {
382 hashloop
: foreach (immutable i
; 0..256) {
383 for (auto rl
= mTables
[i
]; rl
!is null; rl
= rl
.next
) {
385 for (rs
= rl
.rec
.ptr
, rp
= rs
+rl
.count
; --rp
>= rs
;) {
386 if (rp
.rpos
<= rpos
) continue hashloop
;
387 else rp
.rpos
-= rlen
;
393 bool removeRecord (uint rpos
, uint rlen
) {
394 uint len
= cast(uint)(mDataPos
-rpos
-rlen
);
398 if (!len
) return false; /* it was the last record, nothing to do */
401 r
= cast(int)(len
> mBuf
.sizeof ? mBuf
.sizeof
: len
);
402 if (!seek(pos
+rlen
) ||
(r
= read(mBuf
.ptr
, r
)) <= 0) {
406 if (!seek(pos
) ||
!fullWrite(mBuf
.ptr
, r
)) {
413 assert(mDataPos
== pos
);
414 fixupRPos(rpos
, rlen
);
418 bool zeroFillRecord (uint rpos
, uint rlen
) {
419 if (rpos
+rlen
== mDataPos
) {
423 if (!seek(rpos
)) return false;
425 pack(rlen
-8, mBuf
.ptr
+4);
427 rpos
= cast(uint)(rlen
> mBuf
.sizeof ? mBuf
.sizeof
: rlen
);
428 if (!fullWrite(mBuf
.ptr
, rpos
)) {
433 if (!rlen
) return true;
440 /* return: 0 = not found, 1 = error; >1 = record length */
441 uint matchKey (uint pos
, const(void)[] key
) {
442 auto klen
= cast(uint)key
.length
;
443 if (klen
< 1) return ERR
;
444 if (!seek(pos
)) return ERR
;
445 if (!fullRead(mBuf
.ptr
, 8)) return ERR
;
446 if (unpack(mBuf
.ptr
) != klen
) return NOTF
;
447 /* record length; check its validity */
448 uint rlen
= unpack(mBuf
.ptr
+4);
449 if (rlen
> mDataPos
-pos
-klen
-8) return /*errno = EPROTO, 1*/ERR
; /* someone changed our file? */
452 if (klen
< mBuf
.sizeof
) {
453 import core
.stdc
.string
: memcmp
;
454 if (!fullRead(mBuf
.ptr
, klen
)) return ERR
;
455 if (memcmp(mBuf
.ptr
, key
.ptr
, klen
) != 0) return 0;
458 import core
.stdc
.string
: memcmp
;
459 uint len
= cast(uint)(klen
> mBuf
.sizeof ? mBuf
.sizeof
: klen
);
460 if (!fullRead(mBuf
.ptr
, len
)) return ERR
;
461 if (memcmp(mBuf
.ptr
, key
.ptr
, len
) != 0) return 0;
469 enum FindRes
{ ERROR
= -1, NOT_FOUND
= 0, FOUND
= 1 }
470 FindRes
findRecord (const(void)[] key
, uint hval
, int mode
) {
475 FindRes
ret = FindRes
.NOT_FOUND
;
476 outerloop
: for (rl
= mTables
[hval
&0xff]; rl
!is null; rl
= rl
.next
) {
477 for (rs
= rl
.rec
.ptr
, rp
= rs
+rl
.count
; --rp
>= rs
;) {
478 import core
.stdc
.string
: memmove
;
479 if (rp
.hval
!= hval
) continue;
480 /*XXX this explicit flush may be unnecessary having
481 * smarter matchKey() that looks into mBuf too, but
482 * most of a time here spent in finding hash values
483 * (above), not keys */
484 if (!seeked
&& !flush()) {
486 return FindRes
.ERROR
;
489 r
= matchKey(rp
.rpos
, key
);
490 if (r
== NOTF
) continue; // not found
493 return FindRes
.ERROR
;
498 if (!removeRecord(rp
.rpos
, r
)) return FindRes
.ERROR
;
501 if (!zeroFillRecord(rp
.rpos
, r
)) return FindRes
.ERROR
;
503 default: break outerloop
;
505 memmove(rp
, rp
+1, (rs
+rl
.count
-1-rp
)*rp
[0].sizeof
);
510 if (seeked
&& !seek(mDataPos
)) {
512 return FindRes
.ERROR
;
518 uint unpack() (const(ubyte)* buf
) {
519 //assert(buf !is null);
521 n
<<= 8; n |
= buf
[2];
522 n
<<= 8; n |
= buf
[1];
523 n
<<= 8; n |
= buf
[0];
527 void pack() (uint num
, ubyte* buf
) nothrow @nogc {
528 //assert(buf !is null);
529 buf
[0] = num
&0xff; num
>>= 8;
530 buf
[1] = num
&0xff; num
>>= 8;
532 buf
[3] = (num
>>8)&0xff;