diff --git a/lib/modules/Yabu.pmod/module.pmod b/lib/modules/Yabu.pmod/module.pmod index 79dd845d02d6142ec7f8e63510200a9a9334d418..227acdce5b9b1eabaa20bb07798446c34cdeae1c 100644 --- a/lib/modules/Yabu.pmod/module.pmod +++ b/lib/modules/Yabu.pmod/module.pmod @@ -1,41 +1,154 @@ -// Yabu by Fredrik Noring -// $Id: module.pmod,v 1.3 1998/12/12 01:06:50 noring Exp $ +/* Yabu. By Fredrik Noring, 1999. + * + * Yabu is an all purpose transaction database, used to store data records + * associated with a unique key. + */ + +constant cvs_id = "$Id: module.pmod,v 1.4 1999/02/14 16:19:47 noring Exp $"; + +#define ERR(msg) throw(({ "(Yabu) "+msg+"\n", backtrace() })) +#define WARN(msg) werror(msg) +#define DEB(msg) /* werror(msg) */ +#define CHECKSUM(s) (hash(s) & 0xffffffff) #if constant(thread_create) #define THREAD_SAFE -#define LOCK() do { object key; catch(key=lock()) -#define UNLOCK() key=0; } while(0) +#define LOCK() do { object key___; catch(key___=lock()) +#define UNLOCK() key___=0; } while(0) +#define INHERIT_MUTEX static inherit Thread.Mutex #else -#undef THREAD_SAFE +#undef THREAD_SAFE #define LOCK() do { #define UNLOCK() } while(0) +#undef INHERIT_MUTEX #endif -#define ERR(msg) throw(({ "(Yabu) "+msg+"\n", backtrace() })) -#define WARN(msg) werror(msg) -#define DEB(msg) /* werror(msg) */ -#define CHECKSUM(s) (hash(s) & 0xffffffff) + + + +/* + * ProcessLock handles lock files with PID:s. It is + * not 100 % safe, but much better than nothing. + * + * ProcessLock will throw an error if the lock could not be obtained. + */ +static private class ProcessLock { + static private int write = 0; + static private string lock_file; + + static private int get_locked_pid() + { + return (int) (Stdio.read_file(lock_file)||"0"); + } + + void destroy() + { + /* Release PID lock when the object is destroyed. */ + if(write) + rm(lock_file); + } + + void create(string _lock_file, string mode) + { + lock_file = _lock_file; + + /* Check if a process has locked the lock. */ + int pid = get_locked_pid(); + if(pid && kill(pid, 0)) + ERR(sprintf("Out-locked by PID %d", pid)); + + if(search(mode, "w")+1) { + /* Enable write mode. */ + write = 1; + + /* Remove old lock, since there does not seem to be + * any other process using it. + * + * Note: There may be race conditions here! + */ + rm(lock_file); + + /* Write our own lock file. + * + * Open with `x', which will make it a bit safer + * regarding race conditions. + */ + object f = Stdio.File(); + if(!f->open(lock_file, "cxw")) + ERR("PID lock error"); + f->write(sprintf("%d", getpid())); + f->close(); + + /* The last protection against race conditions, make + * sure that the PID file stays the way it should. + */ + if(search(mode, "P")+1) + sleep(1); + pid = get_locked_pid(); + if(pid != getpid()) + ERR(sprintf("Lock was overridden by PID %d", pid)); + } + } +} + + + + + +/* + * With logging enabled, nearly everything Yabu does will + * be put in a log file. + * + */ +class YabuLog { + int pid; + object f = Stdio.File(); + + void log(string ... entries) + { + + } + + void create(string logfile) + { + pid = getpid(); + if(!f->open(logfile, "caw")) + ERR("Cannot open log file."); + } +} + + + + +/* + * FileIO handles all basic file operations in Yabu. + * + */ static private class FileIO { + INHERIT_MUTEX; static private inherit Stdio.File:file; static private void seek(int offset) { if(file::seek(offset) == -1) - ERR(sprintf("seek failed with errno %d\n",file::errno())); + ERR("seek failed"); } string read_at(int offset, int|void size) { + LOCK(); seek(offset); mixed s = size?file::read(size):file::read(); if(!stringp(s)) ERR("read failed"); return s; + UNLOCK(); } void write_at(int offset, string s) { + LOCK(); seek(offset); while(sizeof(s)) { int n = file::write(s); @@ -50,6 +163,7 @@ static private class FileIO { WARN("disk seems to be full. (sleeping)"); sleep(1); } + UNLOCK(); } void create(string filename, string mode) @@ -60,16 +174,30 @@ static private class FileIO { } } + + + + +/* + * The Yabu chunk. + * + */ class Chunk { + INHERIT_MUTEX; static private inherit FileIO:file; static private object parent; static private int magic, compress, write; static private string start_time, filename; + /* Point in file from which new chunks can be allocated. */ static private int eof = 0; + static private mapping frees = ([]), keys = ([]); + /* Escape special characters used for synchronizing + * the contents of Yabu files. + */ static private string escape(string s) { return replace(s, ({ "\n", "%" }), ({ "%n", "%p" })); @@ -80,21 +208,30 @@ class Chunk { return replace(s, ({ "%n", "%p" }), ({ "\n", "%" })); } + /* + * Encode/decode Yabu numbers (8 digit hex numbers). + */ static private string encode_num(int x) { - return sprintf("%08x", x); + string s = sprintf("%08x", x); + if(sizeof(s) != 8) + ERR("Encoded number way too large ("+s+")"); + return s; } static private int decode_num(string s, int offset) { int x; if(sizeof(s) < offset+8) - ERR("cunk short"); + ERR("chunk short"); if(sscanf(s[offset..offset+7], "%x", x) != 1) - ERR("cunk number decode error"); + ERR("chunk number decode error"); return x; } + /* + * Encode/decode Yabu keys. + */ static private string encode_key(int offset, int type) { return encode_num(offset)+":"+encode_num(type); @@ -104,16 +241,24 @@ class Chunk { { if(sizeof(key) != 17 || sscanf(key, "%x:%x", offset, type) != 2) \ ERR("Key not valid"); } + /* This magic string of characters surrounds all chunk in order to + * check if the same magic appears on both sides of the chunk contents. + */ static private string next_magic() { return start_time + encode_num(magic++); } + /* The chunk block allocation policy. By using fixed chunk sizes, reuse + * of empty chunks is encouraged. + */ static private int find_nearest_2x(int num) { - for(int b=4;b<32;b++) if((1<<b) >= num) return (1<<b); + for(int b=4;b<30;b++) if((1<<b) >= num) return (1<<b); + ERR("Chunk too large (max 1 gigabyte)"); } + /* Generate the null chuck, which is the same as the empty chunk. */ static private mapping null_chunk(int t) { string entry = ""; @@ -126,6 +271,9 @@ class Chunk { "entry":("\n"+magic+type+checksum+size+entry+"%m"+magic)]); } + /* + * Encode/decode chunks. + */ static private mapping encode_chunk(mixed x) { string entry = encode_value(x); @@ -133,7 +281,7 @@ class Chunk { if(compress) catch { entry = Gz.deflate()->deflate(entry); }; #endif - string entry = escape(entry); + entry = escape(entry); string magic = next_magic(); string checksum = encode_num(CHECKSUM(entry)); string size = encode_num(sizeof(entry)); @@ -149,7 +297,7 @@ class Chunk { { mapping m = ([]); - if(chunk[0] != '\n') + if(!sizeof(chunk) || chunk[0] != '\n') ERR("No chunk start"); chunk = chunk[1..]; m->magic = chunk[0..15]; @@ -179,6 +327,7 @@ class Chunk { return m; } + /* Allocate chunks by reuse or from to the end of the file. */ static private int allocate_chunk(int type, mapping m) { array f; @@ -195,50 +344,69 @@ class Chunk { } /* Perform consistency check. Returns 0 for failure, otherwise success. */ - int consistency() + static private int consistency() { multiset k = mkmultiset(indices(keys)); foreach(indices(frees), int type) foreach(frees[type], int offset) - if(k[encode_key(offset, type)]) - return 0; + if(k[encode_key(offset, type)]) + return 0; return 1; } array(string) list_keys() { + LOCK(); return indices(keys); + UNLOCK(); } string set(mixed x) { + LOCK(); + if(!write) + ERR("Cannot set in read mode"); mapping m = encode_chunk(x); int offset = allocate_chunk(m->type, m); string key = encode_key(offset, m->type); file::write_at(offset, m->entry); keys[key] = offset; return key; + UNLOCK(); } mixed get(string key, void|int attributes) { - if(!attributes && zero_type(keys[key])) - ERR(sprintf("Unknown key '%O'", key)); + LOCK(); + if(!attributes && zero_type(keys[key])) { + if(attributes) + return 0; + else + ERR(sprintf("Unknown key '%O'", key)); + } int offset, type; DECODE_KEY(key, offset, type); mapping m = decode_chunk(file::read_at(offset, type)); - if(m->size == 0) - ERR(sprintf("Cannot decode free chunk! [consistency status: %s]", - consistency()?"#OK":"#FAILURE")); + if(m->size == 0) { + if(attributes) + return 0; + else + ERR(sprintf("Cannot decode free chunk! [consistency status: %s]", + consistency()?"#OK":"#FAILURE")); + } if(attributes) return m; return m->entry; + UNLOCK(); } void free(string key) { + LOCK(); + if(!write) + ERR("Cannot free in read mode"); if(zero_type(keys[key])) ERR(sprintf("Unknown key '%O'", key)); @@ -248,32 +416,54 @@ class Chunk { m_delete(keys, key); file::write_at(offset, null_chunk(type)->entry); frees[type] = (frees[type]||({})) | ({ offset }); + UNLOCK(); } mapping state(array|void almost_free) { + LOCK(); mapping m_frees = copy_value(frees); mapping m_keys = copy_value(keys); foreach(almost_free||({}), string key) { + if(zero_type(keys[key])) + ERR(sprintf("Unknown state key '%O'", key)); + int offset, type; DECODE_KEY(key, offset, type); m_frees[type] = (m_frees[type]||({})) | ({ offset }); m_delete(m_keys, key); } return copy_value(([ "eof":eof, "keys":m_keys, "frees":m_frees ])); + UNLOCK(); } void purge() { + LOCK(); if(!write) ERR("Cannot purge in read mode"); rm(filename); - destruct(this_object()); + keys = 0; + frees = 0; + write = 0; + filename = 0; + UNLOCK(); } + void move(string new_filename) + { + LOCK(); + if(!write) + ERR("Cannot move in read mode"); + if(!mv(filename, new_filename)) + ERR("Move failed"); + filename = new_filename; + UNLOCK(); + } + void destroy() { - if(parent) + if(parent && write) destruct(parent); } @@ -314,8 +504,8 @@ class Chunk { int size = (i+1 < sizeof(offsets)?offsets[i+1]-offset:0); string key = encode_key(offset, size); if(!size || size == find_nearest_2x(size)) { - mapping m; - if(catch(m = get(key, 1))) { + mapping m = get(key, 1); + if(!m) { if(size) { frees[size] = (frees[size]||({})) + ({ offset }); eof = offset+size; @@ -330,6 +520,14 @@ class Chunk { } } + + + + +/* + * The transaction table. + * + */ class Transaction { static private int id; static private object table, keep_ref; @@ -398,23 +596,35 @@ class Transaction { } } + + + + +/* + * The basic Yabu table. + * + */ class Table { -#ifdef THREAD_SAFE - static inherit Thread.Mutex; -#endif + INHERIT_MUTEX; static private object index, db; - static private string filename; - static private mapping handles, new, changes; + static private string mode, filename; + static private mapping handles, changes; static private mapping t_start, t_changes, t_handles, t_deleted; static private int sync_timeout, write, dirty, magic, id = 0x314159; + static private void modified() + { + dirty++; + if(sync_timeout && dirty >= sync_timeout) + sync(); + } + void sync() { + LOCK(); if(!write || !dirty) return; - LOCK(); - new = ([]); array almost_free = db->list_keys() - values(handles); string key = index->set(([ "db":db->state(almost_free), "handles":handles ])); @@ -433,57 +643,83 @@ class Table { UNLOCK(); } - static private int next_magic() + int reorganize(float|void ratio) { - return magic++; - } + LOCK(); + if(!write) ERR("Cannot reorganize in read mode"); - static private void modified() + ratio = ratio || 0.70; + + /* Check if the level of usage is above the given ratio. */ + if(ratio < 1.0) { + mapping st = this_object()->statistics(); + float usage = (float)st->used/(float)(st->size||1); + if(usage > ratio) + return 0; + } + + /* Create new database. */ + object opt = Chunk(filename+".opt", mode, this_object(), ([])); + + /* Remap all ordinary handles. */ + mapping new_handles = ([]); + foreach(indices(handles), string handle) + new_handles[handle] = opt->set(db->get(handles[handle])); + + /* Remap all transaction handles. */ + mapping new_t_handles = ([]); + foreach(indices(t_handles), int id) { + new_t_handles[id] = ([]); + foreach(indices(t_handles[id]), string t_handle) + new_t_handles[id][t_handle] = + opt->set(db->get(t_handles[id][t_handle])); + } + + /* Switch databases. */ + db->purge(); + index->purge(); + opt->move(filename+".chk"); + handles = new_handles; + t_handles = new_t_handles; + db = opt; + index = Chunk(filename+".inx", mode, this_object()); + + /* Reconstruct db and index. */ + modified(); + sync(); + + return 1; + UNLOCK(); + } + + static private int next_magic() { - dirty++; - if(sync_timeout && dirty >= sync_timeout) - sync(); + return magic++; } - static private mixed _set(string handle, mixed x, - mapping handles, mapping new) + static private mixed _set(string handle, mixed x, mapping handles) { if(!write) ERR("Cannot set in read mode"); - string key = handles[handle]; - if(key && new[key]) { - m_delete(new, key); - db->free(key); - } - handles[handle] = key = db->set(([ "handle":handle, "entry":x ])); - new[key] = 1; - modified(); + handles[handle] = db->set(([ "handle":handle, "entry":x ])); + // modified(); return x; } static private mixed _get(string handle, mapping handles) { - if(!handles[handle]) - return 0; - return db->get(handles[handle])->entry; + return handles[handle]?db->get(handles[handle])->entry:0; } void delete(string handle) { - if(!write) ERR("Cannot delete in read mode"); - LOCK(); + if(!write) ERR("Cannot delete in read mode"); if(!handles[handle]) ERR(sprintf("Unknown handle '%O'", handle)); - string key = handles[handle]; m_delete(handles, handle); if(changes) changes[handle] = next_magic(); - // m_delete(changes, handle); - if(new[key]) { - m_delete(new, key); - db->free(key); - } modified(); UNLOCK(); } @@ -491,9 +727,11 @@ class Table { mixed set(string handle, mixed x) { LOCK(); + if(!write) ERR("Cannot set in read mode"); if(changes) changes[handle] = next_magic(); - return _set(handle, x, handles, new); + modified(); + return _set(handle, x, handles); UNLOCK(); } @@ -505,24 +743,24 @@ class Table { } // - // Transactions + // Transactions. // mixed t_set(int id, string handle, mixed x) { + LOCK(); + if(!write) ERR("Cannot set in read mode"); if(!t_handles[id]) ERR("Unknown transaction id"); - LOCK(); t_changes[id][handle] = t_start[id]; - mapping t_new = mkmapping(values(t_handles[id]), indices(t_handles[id])); - return _set(handle, x, t_handles[id], t_new); + return _set(handle, x, t_handles[id]); UNLOCK(); } mixed t_get(int id, string handle) { + LOCK(); if(!t_handles[id]) ERR("Unknown transaction id"); - LOCK(); t_changes[id][handle] = t_start[id]; if(t_deleted[id][handle]) return 0; @@ -532,80 +770,72 @@ class Table { void t_delete(int id, string handle) { + LOCK(); + if(!write) ERR("Cannot delete in read mode"); if(!t_handles[id]) ERR("Unknown transaction id"); - LOCK(); t_deleted[id][handle] = 1; t_changes[id][handle] = t_start[id]; - if(t_handles[id][handle]) { - db->free(t_handles[id][handle]); + if(t_handles[id][handle]) m_delete(t_handles[id], handle); - } UNLOCK(); } void t_commit(int id) { + LOCK(); if(!write) ERR("Cannot commit in read mode"); if(!t_handles[id]) ERR("Unknown transaction id"); - LOCK(); foreach(indices(t_changes[id]), string handle) if(t_changes[id][handle] < changes[handle]) ERR("Transaction conflict"); + + foreach(indices(t_handles[id]), string handle) { + changes[handle] = next_magic(); + handles[handle] = t_handles[id][handle]; + } - mapping tmp_t_changes = copy_value(t_changes); - mapping tmp_t_handles = copy_value(t_handles); + foreach(indices(t_deleted[id]), string handle) { + changes[handle] = next_magic(); + m_delete(handles, handle); + } + t_start[id] = next_magic(); t_changes[id] = ([]); t_handles[id] = ([]); t_deleted[id] = ([]); - - array obsolete = ({}); - foreach(indices(tmp_t_changes[id]), string handle) { - if(new[handles[handle]]) - obsolete += ({ handles[handle] }); - changes[handle] = next_magic(); - handles[handle] = tmp_t_handles[id][handle]; - } - - foreach(obsolete, string key) { - m_delete(new, key); - db->free(key); - } + modified(); UNLOCK(); } void t_rollback(int id) { + LOCK(); if(!t_handles[id]) ERR("Unknown transaction id"); - LOCK(); - array keys = values(t_handles[id]); t_start[id] = next_magic(); t_changes[id] = ([]); t_handles[id] = ([]); t_deleted[id] = ([]); - foreach(keys, string key) - db->free(key); UNLOCK(); } array t_list_keys(int id) { + LOCK(); if(!t_handles[id]) ERR("Unknown transaction id"); - LOCK(); - return Array.uniq(indices(handles) + indices(t_handles[id])); + return (Array.uniq(indices(handles) + indices(t_handles[id])) - + indices(t_deleted[id])); UNLOCK(); } void t_destroy(int id) { + LOCK(); if(!t_handles[id]) ERR("Unknown transaction id"); - t_rollback(id); - LOCK(); m_delete(t_start, id); m_delete(t_changes, id); m_delete(t_handles, id); @@ -615,9 +845,9 @@ class Table { object transaction(object|void keep_ref) { + LOCK(); if(!changes) ERR("Transactions are not enabled"); - LOCK(); id++; t_start[id] = next_magic(); t_changes[id] = ([]); @@ -638,11 +868,12 @@ class Table { } // - // Interface functions + // Interface functions. // void sync_schedule() { + remove_call_out(sync_schedule); sync(); call_out(sync_schedule, 120); } @@ -659,6 +890,7 @@ class Table { void destroy() { + remove_call_out(sync_schedule); sync(); } @@ -676,9 +908,9 @@ class Table { void purge() { + LOCK(); if(!write) ERR("Cannot purge in read mode"); - LOCK(); write = 0; rm(filename+".inx"); rm(filename+".chk"); @@ -690,48 +922,109 @@ class Table { { return list_keys(); } + + mapping(string:string|int) statistics() + { + LOCK(); + mapping m = ([ "keys":sizeof(handles), + "size":Stdio.file_size(filename+".inx")+ + Stdio.file_size(filename+".chk"), + "used":`+(@Array.map((values(handles) | + (t_handles && sizeof(t_handles)? + `+(@Array.map(values(t_handles), + values)):({}))) + + index->list_keys(), + lambda(string s) + { int x; + sscanf(s, "%*x:%x", x); + return x; })) ]); + + m->size = max(m->size, m->used); + return m; + UNLOCK(); + } - void create(string filename_in, string mode) + void create(string filename_in, string _mode) { filename = filename_in; + mode = _mode; if(search(mode, "w")+1) write = 1; - if(search(mode, "t")+1) { + if(search(mode, "t")+1) changes = ([]); - t_start = ([]); - t_changes = ([]); - t_handles = ([]); - t_deleted = ([]); - } + t_start = ([]); + t_changes = ([]); + t_handles = ([]); + t_deleted = ([]); index = Chunk(filename+".inx", mode, this_object()); mapping m = ([]); if(sizeof(index->list_keys())) m = index->get(sort(index->list_keys())[-1]); handles = m->handles||([]); - new = ([]); db = Chunk(filename+".chk", mode, this_object(), m->db||([])); - if(write && search(mode, "s")+1) { - sync_timeout = 32; - sync_schedule(); + if(write) { + sync_timeout = 128; + if(search(mode, "s")+1) + sync_schedule(); } } } + + + + +/* + * The shadow table. + * + */ class _Table { + static object table; static string handle; - static object table, parent; + static function table_destroyed; - void sync() { table->sync(); } - void delete(string handle) { table->delete(handle); } - mixed set(string handle, mixed x) { return table->set(handle, x); } - mixed get(string handle) { return table->get(handle); } - array list_keys() { return table->list_keys(); } - object transaction() { return table->transaction(this_object()); } - void close() { destruct(this_object()); } - void purge() { table->purge(); } + void sync() + { + table->sync(); + } + + void delete(string handle) + { + table->delete(handle); + } + + mixed set(string handle, mixed x) + { + return table->set(handle, x); + } + + mixed get(string handle) + { + return table->get(handle); + } + + array list_keys() + { + return table->list_keys(); + } + + object transaction() + { + return table->transaction(this_object()); + } + + void close() + { + destruct(this_object()); + } + + void purge() + { + table->purge(); + } mixed `[]=(string handle, mixed x) { @@ -750,26 +1043,82 @@ class _Table { void destroy() { - if(parent) - parent->_table_destroyed(handle); + if(table_destroyed) + table_destroyed(handle); + } + + int reorganize(float|void ratio) + { + return table->reorganize(ratio); + } + + /* + * Compile table statistics. + */ + static private string st_keys(int size, mapping m) + { + return sprintf("%4d", size); + } + + static private string st_size(int size, mapping m) + { + return sprintf("%7.3f Mb", (float)size/(1024.0*1024.0)); + + string r = (string)size; + int e = (int) (log((float)(size||1))/log(1024.0)); + + if(`<=(1, e, 3)) + r = sprintf("%7.2f",(float)size/(float)(((int)pow(1024.0,(float)e))||1)); + return sprintf("%s %s", r, ([ 1:"Kb", 2:"Mb", 3:"Gb" ])[e]||"b "); + } + + static private string st_used(int used, mapping m) + { + return sprintf("%3d %%", (int) (100.0*(float)used/(float)m->size)); } - void create(string _handle, object _table, object _parent) + mapping(string:string|int) statistics() + { + return table->statistics(); + } + + string ascii_statistics() + { + mapping m = statistics(); + return "["+Array.map(sort(indices(m)), + lambda(string inx, mapping m) + { + mapping f = ([ "keys":st_keys, + "size":st_size, + "used":st_used ]); + return sprintf("%s:%s", inx,f[inx]?f[inx](m[inx],m): + (string)m[inx]); + }, m)*" "+"] \""+handle+"\""; + } + + void create(string _handle, object _table, function _table_destroyed) { handle = _handle; table = _table; - parent = _parent; + table_destroyed = _table_destroyed; } } + + + + +/* + * The Yabu database object itself. + * + */ class db { -#ifdef THREAD_SAFE - static inherit Thread.Mutex; -#endif + INHERIT_MUTEX; static string dir, mode; static mapping tables = ([]), table_refs = ([]); static int write, id; + static object pid_lock; void sync() { @@ -780,20 +1129,7 @@ class db { UNLOCK(); } - object table(string handle) - { - LOCK(); - if(!tables[handle]) - tables[handle] = Table(dir+"/"+handle, mode); - table_refs[handle]++; - // DEB(sprintf("### refs[%s]++ = %d\n", handle, table_refs[handle])); - DEB(sprintf("# tables '%s': %d (%s)\n", - reverse(reverse(dir/"/")[0..1])*"/", sizeof(tables), handle)); - return _Table(handle, tables[handle], this_object()); - UNLOCK(); - } - - void _table_destroyed(string handle) + static void _table_destroyed(string handle) { LOCK(); table_refs[handle]--; @@ -809,6 +1145,19 @@ class db { UNLOCK(); } + object table(string handle) + { + LOCK(); + if(!tables[handle]) + tables[handle] = Table(dir+"/"+handle, mode); + table_refs[handle]++; + // DEB(sprintf("### refs[%s]++ = %d\n", handle, table_refs[handle])); + DEB(sprintf("# tables '%s': %d (%s)\n", + reverse(reverse(dir/"/")[0..1])*"/", sizeof(tables), handle)); + return _Table(handle, tables[handle], _table_destroyed); + UNLOCK(); + } + mixed `[](string handle) { return table(handle); @@ -827,15 +1176,15 @@ class db { return list_tables(); } - // Remove maximum one level of directories and files + /* Remove maximum one level of directories and files. */ static private void level2_rm(string f) { if(sizeof(f) > 1 && f[-1] == '/') - f = f[0..sizeof(f)-2]; // remove /'s - if((file_stat(f)||({0,0}))[1] == -2) // directory + f = f[0..sizeof(f)-2]; // Remove /'s. + if((file_stat(f)||({0,0}))[1] == -2) // Directory. foreach(get_dir(f)||({}), string file) - rm(f+"/"+file); // delete file - rm(f); // delete file/directory + rm(f+"/"+file); // Delete file. + rm(f); // Delete file/directory. } void purge() @@ -864,22 +1213,59 @@ class db { } } + void destroy() + { + sync(); + } + + int reorganize(float|void ratio) + { + int r = 0; + foreach(list_tables(), string name) + r |= table(name)->reorganize(ratio); + return r; + } + + mapping(string:int) statistics() + { + mapping m = ([]); + foreach(list_tables(), string name) + m[name] = table(name)->statistics(); + return m; + } + + string ascii_statistics() + { + string r = ""; + foreach(list_tables(), string name) + r += table(name)->ascii_statistics()+"\n"; + return r; + } + void create(string dir_in, string mode_in) { dir = dir_in; mode = mode_in; - + if(search(mode, "w")+1) { write = 1; - mkdirhier(dir+"/"); + if(search(mode, "c")+1) + mkdirhier(dir+"/"); } + pid_lock = ProcessLock(dir+"/lock.pid", mode); } } + + + +/* + * Special extra bonus. This database is optimized for lots of very small + * data records. + */ class LookupTable { -#ifdef THREAD_SAFE - static inherit Thread.Mutex; -#endif + INHERIT_MUTEX; + static private int minx; static private object table; @@ -935,6 +1321,13 @@ class LookupTable { } } + + + +/* + * The lookup database. + * + */ class lookup { inherit db; static private int minx; diff --git a/lib/modules/Yabu.pmod/test.pike b/lib/modules/Yabu.pmod/test.pike index 8e69b6f0bcce2f47a9df5890d38395e469fdf409..a414c45eb179da49740c9142fc5d4759eae8d686 100644 --- a/lib/modules/Yabu.pmod/test.pike +++ b/lib/modules/Yabu.pmod/test.pike @@ -1,7 +1,21 @@ +#!/usr/local/bin/pike // Yabu test program #define ERR(msg) throw(({ msg+"\n", backtrace() })); +void check_db(object db, mapping m) +{ + for(int i = 0; i < 10; i++) { + string s = (string)(i%3); + object t = db[s]; + for(int j = 0; j < 100; j++) { + string q = (string)(j%43); + if(t[q] != m[s][q]) + ERR("Table diff #10!"); + } + } +} + int main(int argc, array argv) { if(argc == 3 && argv[1] == "dbck") { @@ -47,35 +61,39 @@ int main(int argc, array argv) if(transaction["Blixt"] != "Gordon") ERR("Table diff #8!"); - transaction->commit(); - - if(table["Buck"] != "Rogers") - ERR("Table diff #9!"); - // Test multiple commands. mapping m = ([]); for(int i = 0; i < 10; i++) { string s = (string)(i%3); m[s] = m[s] || ([]); object t = db[s]; + if((i%3 == 0)) + table->reorganize(1.0); for(int j = 0; j < 100; j++) { string q = (string)(j%43); m[s][q] += 1; t[q] = t[q]+1; } - t->sync(); + if(i == 7) + t->sync(); } - for(int i = 0; i < 10; i++) { - string s = (string)(i%3); - object t = db[s]; - for(int j = 0; j < 100; j++) { - string q = (string)(j%43); - if(t[q] != m[s][q]) - ERR("Table diff #10!"); - } - } + transaction->commit(); + if(table["Buck"] != "Rogers") + ERR("Table diff #9!"); + + if(!catch { + object db2 = .module.db("test.db", "wct"); + }) + ERR("Table lock error!"); + check_db(db, m); + destruct(db); + check_db(db = .module.db("test.db", "w"), m); + db->reorganize(1.0); + destruct(db); + check_db(db = .module.db("test.db", "wct"), m); + // Remove test database. db->purge();