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, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 * D translation by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
17 // CDB key/value database creator
18 module iv
.tinycdbmk
/*is aliced*/;
24 private static import iv
.tinycdb
;
26 alias hash
= iv
.tinycdb
.CDB
.hash
;
29 align(1) static struct Rec
{
35 static struct RecList
{
41 int mFD
= -1; /* file descriptor */
43 uint mDataPos
; /* data position so far */
44 uint mRCount
; /* record count so far */
45 ubyte[4096] mBuf
= void; /* write buffer */
46 ubyte* mBufPos
; /* current buf position */
47 RecList
*[256] mTables
; /* list of arrays of record infos */
52 PUT_ADD
= 0, /* add unconditionnaly, like cdb_make_add() */
53 PUT_REPLACE
, /* replace: do not place to index OLD record */
54 PUT_INSERT
, /* add only if not already exists */
55 PUT_WARN
, /* add unconditionally but ret. 1 if exists */
56 PUT_REPLACE0
, /* if a record exists, fill old one with zeros */
59 FIND_REMOVE
= PUT_REPLACE
,
60 FIND_FILL0
= PUT_REPLACE0
,
65 @disable this (this); // no copying
67 this (string fname
) { create(fname
); }
69 bool create (string fname
) {
70 import std
.string
: toStringz
;
71 import core
.sys
.posix
.fcntl
: xopen
= open
, O_CREAT
, O_RDWR
, O_TRUNC
;
73 int fd
= xopen(fname
.toStringz
, O_RDWR|O_CREAT|O_TRUNC
, 384);
85 this (int fd
) @nogc { create(fd
); }
87 @property bool opened () const pure @safe { return (mFD
>= 0); }
88 @property bool valid () const pure @safe { return (mFD
>= 0 && !mWasError
); }
90 //TODO: seek to start and truncate
91 void create (int fd
) {
95 mBufPos
= mBuf
.ptr
+2048;
97 if (!seek(0)) mWasError
= true;
105 import core
.sys
.posix
.unistd
: xclose
= close
;
108 foreach (immutable t
; 0..256) {
109 auto rl
= mTables
[t
];
110 while (rl
!is null) {
111 import core
.stdc
.stdlib
: free
;
129 bool find (const(void)[] key
, int mode
, bool *err
=null) {
130 if (err
!is null) *err
= false;
131 auto res
= findRecord(key
, hash(key
), mode
);
132 if (err
!is null && res
== FindRes
.ERROR
) *err
= true;
133 return (res
== FindRes
.FOUND
);
135 bool exists (const(void)[] key
) { return (findRecord(key
, hash(key
), FIND
) == FindRes
.FOUND
); }
137 bool add (const(void)[] key
, const(void)[] val
) { return addRecord(hash(key
), key
, val
); }
139 // true: record was replaced
140 // false: new record or error
141 bool put (const(void)[] key
, const(void)[] val
, int mode
=PUT_ADD
, bool *err
=null) {
142 if (err
!is null) *err
= false;
143 uint hval
= hash(key
);
150 r
= findRecord(key
, hval
, mode
);
151 if (r
== FindRes
.ERROR
) {
152 if (err
!is null) *err
= true;
155 if (r
== FindRes
.FOUND
&& mode
== PUT_INSERT
) return /*errno = EEXIST,*/true;
158 r
= FindRes
.NOT_FOUND
;
161 if (err
!is null) *err
= true;
162 return /*errno = EINVAL,*/false;
164 if (!addRecord(hval
, key
, val
)) {
165 if (err
!is null) *err
= true;
168 return (r
== FindRes
.FOUND
);
172 private bool seek (uint pos
) {
173 import core
.sys
.posix
.unistd
: lseek
;
174 import core
.stdc
.stdio
: SEEK_SET
;
175 return (lseek(mFD
, pos
, SEEK_SET
) >= 0);
178 private bool fullRead (ubyte* buf
, uint len
) {
180 import core
.stdc
.errno
;
181 import core
.sys
.posix
.unistd
: read
;
182 auto l
= read(mFD
, cast(void*)buf
, len
);
186 } else if (l
== 0 ||
(l
< 0 && errno
!= EINTR
)) {
193 private int read (ubyte* buf
, uint len
) {
194 immutable flen
= len
;
196 import core
.stdc
.errno
;
197 static import core
.sys
.posix
.unistd
;
198 auto l
= core
.sys
.posix
.unistd
.read(mFD
, cast(void*)buf
, len
);
202 } else if (l
< 0 && errno
!= EINTR
) {
208 return cast(int)(flen
-len
);
211 private bool fullWrite (const(void)* bufp
, uint len
) {
212 auto buf
= cast(const(ubyte)*)bufp
;
214 import core
.stdc
.errno
;
215 import core
.sys
.posix
.unistd
: write
;
216 auto l
= write(mFD
, cast(const void*)buf
, len
);
220 } else if (l
== 0 ||
(l
< 0 && errno
!= EINTR
)) {
227 private bool flush () {
228 uint len
= cast(uint)(mBufPos
-mBuf
.ptr
);
230 if (!fullWrite(mBuf
.ptr
, len
)) return false;
236 private bool write (const(void)* ptrp
, uint len
) {
237 import core
.stdc
.string
: memcpy
;
238 auto ptr
= cast(const(ubyte)*)ptrp
;
239 uint l
= cast(uint)(mBuf
.sizeof
-(mBufPos
-mBuf
.ptr
));
242 if (l
) memcpy(mBufPos
, ptr
, l
);
244 if (!flush()) return false;
247 l
= cast(uint)(len
/mBuf
.sizeof
);
249 l
*= cast(uint)mBuf
.sizeof
;
250 if (!fullWrite(ptr
, l
)) return false;
256 memcpy(mBufPos
, ptr
, len
);
262 private bool finish () {
263 uint[256] hcnt
; /* hash table counts */
264 uint[256] hpos
; /* hash table positions */
270 if (((0xffffffffu
-mDataPos
)>>3) < mRCount
) {
272 return /*errno = ENOMEM, -1*/false;
275 /* count htab sizes and reorder reclists */
277 foreach (immutable uint t
; 0..256) {
282 RecList
* rln
= rl
.next
;
289 if (hsize
< (hcnt
[t
] = i
<<1)) hsize
= hcnt
[t
];
292 import core
.stdc
.stdlib
: malloc
, free
;
293 /* allocate memory to hold max htable */
294 htab
= cast(Rec
*)malloc((hsize
+2)*htab
[0].sizeof
);
297 return /*errno = ENOENT, -1*/false;
299 p
= cast(ubyte*)htab
;
302 /* build hash tables */
303 foreach (immutable uint t
; 0..256) {
306 if ((len
= hcnt
[t
]) == 0) continue;
307 foreach (immutable i
; 0..len
) htab
[i
].hval
= htab
[i
].rpos
= 0;
308 for (rl
= mTables
[t
]; rl
!is null; rl
= rl
.next
) {
309 foreach (immutable i
; 0..rl
.count
) {
310 hi
= (rl
.rec
[i
].hval
>>8)%len
;
311 while (htab
[hi
].rpos
) if (++hi
== len
) hi
= 0;
312 htab
[hi
] = rl
.rec
[i
];
315 foreach (immutable i
; 0..len
) {
316 pack(htab
[i
].hval
, p
+(i
<<3));
317 pack(htab
[i
].rpos
, p
+(i
<<3)+4);
319 if (!write(p
, len
<<3)) {
332 foreach (immutable t
; 0..256) {
333 pack(hpos
[t
], p
+(t
<<3));
334 pack(hcnt
[t
], p
+(t
<<3)+4);
337 if (!seek(0) ||
!fullWrite(p
, 2048)) {
345 private bool addRecord (uint hval
, const(void)[] key
, const(void)[] val
) {
346 immutable klen
= cast(uint)key
.length
;
347 if (klen
== 0) return false;
348 immutable vlen
= cast(uint)val
.length
;
352 if (klen
> 0xffffffffu
-(mDataPos
+8) || vlen
> 0xffffffffu
-(mDataPos
+klen
+8)) {
354 return /*errno = ENOMEM, -1*/false;
356 i
= hval
&0xff; // hash table number
358 if (rl
is null || rl
.count
>= rl
.rec
.length
) {
360 import core
.stdc
.stdlib
: malloc
;
361 rl
= cast(RecList
*)malloc(RecList
.sizeof
);
362 if (rl
is null) return /*errno = ENOMEM, -1*/false;
364 rl
.next
= mTables
[i
];
368 rl
.rec
[i
].hval
= hval
;
369 rl
.rec
[i
].rpos
= mDataPos
;
371 pack(klen
, rlen
.ptr
);
372 pack(vlen
, rlen
.ptr
+4);
373 if (!write(rlen
.ptr
, 8) ||
!write(key
.ptr
, klen
) ||
!write(val
.ptr
, vlen
)) {
380 void fixupRPos (uint rpos
, uint rlen
) {
381 hashloop
: foreach (immutable i
; 0..256) {
382 for (auto rl
= mTables
[i
]; rl
!is null; rl
= rl
.next
) {
384 for (rs
= rl
.rec
.ptr
, rp
= rs
+rl
.count
; --rp
>= rs
;) {
385 if (rp
.rpos
<= rpos
) continue hashloop
;
386 else rp
.rpos
-= rlen
;
392 bool removeRecord (uint rpos
, uint rlen
) {
393 uint len
= cast(uint)(mDataPos
-rpos
-rlen
);
397 if (!len
) return false; /* it was the last record, nothing to do */
400 r
= cast(int)(len
> mBuf
.sizeof ? mBuf
.sizeof
: len
);
401 if (!seek(pos
+rlen
) ||
(r
= read(mBuf
.ptr
, r
)) <= 0) {
405 if (!seek(pos
) ||
!fullWrite(mBuf
.ptr
, r
)) {
412 assert(mDataPos
== pos
);
413 fixupRPos(rpos
, rlen
);
417 bool zeroFillRecord (uint rpos
, uint rlen
) {
418 if (rpos
+rlen
== mDataPos
) {
422 if (!seek(rpos
)) return false;
424 pack(rlen
-8, mBuf
.ptr
+4);
426 rpos
= cast(uint)(rlen
> mBuf
.sizeof ? mBuf
.sizeof
: rlen
);
427 if (!fullWrite(mBuf
.ptr
, rpos
)) {
432 if (!rlen
) return true;
439 /* return: 0 = not found, 1 = error; >1 = record length */
440 uint matchKey (uint pos
, const(void)[] key
) {
441 auto klen
= cast(uint)key
.length
;
442 if (klen
< 1) return ERR
;
443 if (!seek(pos
)) return ERR
;
444 if (!fullRead(mBuf
.ptr
, 8)) return ERR
;
445 if (unpack(mBuf
.ptr
) != klen
) return NOTF
;
446 /* record length; check its validity */
447 uint rlen
= unpack(mBuf
.ptr
+4);
448 if (rlen
> mDataPos
-pos
-klen
-8) return /*errno = EPROTO, 1*/ERR
; /* someone changed our file? */
451 if (klen
< mBuf
.sizeof
) {
452 import core
.stdc
.string
: memcmp
;
453 if (!fullRead(mBuf
.ptr
, klen
)) return ERR
;
454 if (memcmp(mBuf
.ptr
, key
.ptr
, klen
) != 0) return 0;
457 import core
.stdc
.string
: memcmp
;
458 uint len
= cast(uint)(klen
> mBuf
.sizeof ? mBuf
.sizeof
: klen
);
459 if (!fullRead(mBuf
.ptr
, len
)) return ERR
;
460 if (memcmp(mBuf
.ptr
, key
.ptr
, len
) != 0) return 0;
468 enum FindRes
{ ERROR
= -1, NOT_FOUND
= 0, FOUND
= 1 }
469 FindRes
findRecord (const(void)[] key
, uint hval
, int mode
) {
474 FindRes
ret = FindRes
.NOT_FOUND
;
475 outerloop
: for (rl
= mTables
[hval
&0xff]; rl
!is null; rl
= rl
.next
) {
476 for (rs
= rl
.rec
.ptr
, rp
= rs
+rl
.count
; --rp
>= rs
;) {
477 import core
.stdc
.string
: memmove
;
478 if (rp
.hval
!= hval
) continue;
479 /*XXX this explicit flush may be unnecessary having
480 * smarter matchKey() that looks into mBuf too, but
481 * most of a time here spent in finding hash values
482 * (above), not keys */
483 if (!seeked
&& !flush()) {
485 return FindRes
.ERROR
;
488 r
= matchKey(rp
.rpos
, key
);
489 if (r
== NOTF
) continue; // not found
492 return FindRes
.ERROR
;
497 if (!removeRecord(rp
.rpos
, r
)) return FindRes
.ERROR
;
500 if (!zeroFillRecord(rp
.rpos
, r
)) return FindRes
.ERROR
;
502 default: break outerloop
;
504 memmove(rp
, rp
+1, (rs
+rl
.count
-1-rp
)*rp
[0].sizeof
);
509 if (seeked
&& !seek(mDataPos
)) {
511 return FindRes
.ERROR
;
517 uint unpack() (const(ubyte)* buf
) {
518 //assert(buf !is null);
520 n
<<= 8; n |
= buf
[2];
521 n
<<= 8; n |
= buf
[1];
522 n
<<= 8; n |
= buf
[0];
526 void pack() (uint num
, ubyte* buf
) nothrow @nogc {
527 //assert(buf !is null);
528 buf
[0] = num
&0xff; num
>>= 8;
529 buf
[1] = num
&0xff; num
>>= 8;
531 buf
[3] = (num
>>8)&0xff;