class Mird { constant Glue=@module@; Glue.Mird mird; #define TABLE2ID_TABLE 1 #define ID2TABLE_TABLE 2 #define NEXT_TABLE "_mird_magic_next_free_table" mapping table2id=([]); mapping id2table=([]); int next_table; function(int,int|string:string) _fetch; int zerorefs=_refs(this_object())-1; //! class Mird //! //! method void create(string filename,void|mapping options) void create(string filename,void|mapping options) { mird=Glue.Mird(filename,options||([])); mixed err=catch { next_table=(int)mird->fetch(TABLE2ID_TABLE,NEXT_TABLE); }; if (err || !next_table) { next_table=max(ID2TABLE_TABLE,TABLE2ID_TABLE)+1; Glue.Transaction mtr=Glue.Transaction(mird); mtr->new_stringkey_table(TABLE2ID_TABLE); mtr->new_hashkey_table(ID2TABLE_TABLE); mtr->store(TABLE2ID_TABLE,NEXT_TABLE,(string)next_table); mtr->close(); } _fetch=mird->fetch; call_out(sync_loop,60,60); } Glue.Scanner _scanner(int ... args) { return Glue.Scanner(mird,@args); } Mird _store(int table_id,int|string key,string data) { Transaction tr=transaction(); tr->_store(table_id,key,data); tr->close(); return this_object(); } //! method Table table(string name) Table table(string name) { if (name==NEXT_TABLE) error("illegal name\n"); if (!table2id[name]) { string d=mird->fetch(TABLE2ID_TABLE,name); if (!d) error("no such table (%O)\n",name); table2id[name]=(int)d; } return Table(this_object(),table2id[name]); } //! method vTable vtable(string name) //! A vTable is just like a table, except that the //! values can be anything, not just strings. The //! data is encode_value()ed before storage. vTable vtable(string name) { if (name==NEXT_TABLE) error("illegal name\n"); if (!table2id[name]) { string d=mird->fetch(TABLE2ID_TABLE,name); if (!d) error("no such table (%O)\n",name); table2id[name]=(int)d; } return vTable(this_object(),table2id[name]); } //! method vzTable vztable(string name) //! A vzTable is just like a table, except that the //! values can be anything, not just strings. The //! data is encode_value()ed before storage, and //! it's packed with Gz, too. vzTable vztable(string name) { if (name==NEXT_TABLE) error("illegal name\n"); if (!table2id[name]) { string d=mird->fetch(TABLE2ID_TABLE,name); if (!d) error("no such table (%O)\n",name); table2id[name]=(int)d; } return vzTable(this_object(),table2id[name]); } //! method table_name(int id) string table_name(int id) { if (id2table[id]) return id2table[id]; string d=mird->fetch(ID2TABLE_TABLE,id); if (!d) return 0; return id2table[id]=d; } //! //! method Table new_stringkey_table(string name) //! method Table new_hashkey_table(string name) //! Table new_stringkey_table(string name) { Transaction tr=transaction(); tr->new_stringkey_table(name); tr->close(); return table(name); } Table new_hashkey_table(string name) { Transaction tr=transaction(); tr->new_hashkey_table(name); tr->close(); return table(name); } //! //! method Transaction transaction() //! creates a new transaction Transaction transaction() { return Transaction(this_object()); } //! //! method array(string) tables() //! array(string) tables() { return indices(Table(this_object(),TABLE2ID_TABLE))-({NEXT_TABLE}); } //! method object sync() //! method object sync_please() //! Syncs the database (syncs against disc and //! starts to reuse any space that is freed), //! sync_please() does this when the last living //! transaction is finished. //! method object sync_loop(int seconds) //! Starts a call_out loop that will call sync_please //! once every given interval. 0 or fewer seconds will //! shut down the loop. //! Default is 1 minute (60 seconds). Mird sync() { gc(); // kill any leftover transactions mird->sync(); return this_object(); } Mird sync_please() { gc(); // kill any leftover transactions mird->sync_please(); return this_object(); } void gurk(int s) { werror(". "+s+"\n"); } Mird sync_loop(int seconds) { remove_call_out(sync_loop); if (seconds<=0) return this_object(); sync_please(); if (_refs(this_object())>zerorefs) // otherwise, we're last -> close() call_out(sync_loop,seconds,seconds); return this_object(); } //! method void close() //! method void destroy() //! syncs and closes the database void close() { werror("close\n"); remove_call_out(sync_loop); if (mird) mird->close(); } function destroy=close; //! //! subclass Table //! class Table { int table_id; object(Mird)|object(Transaction) parent; //! method void create(object parent,int table_id) void create(object _parent,int _table_id) { parent=_parent; table_id=_table_id; } //! method Mird.Glue.Scanner scanner() //! method Mird.Glue.Scanner scanner(int key) //! Creates a scanner over the called table; //! if a key is given, continue at that key //! (as returned from next_key). Mird.Glue.Scanner scanner(int ... key) { return parent->_scanner(table_id,@key); } //! method string `[](int|string key) string `[](int|string key) { string s=parent->_fetch(table_id,key); return s; } //! method string `[]=(int|string key,string value) string `[]=(int|string key,string value) { parent->_store(table_id,key,value); return value; } //! method array(int|string) _indices() array(int|string) _indices() { array keys=({}); Glue.Scanner sc=scanner(); mapping m; while ( (m=sc->read(100)) ) keys+=indices(m); return keys; } //! method array(int|string) _values() array(int|string) _values() { array vals=({}); Glue.Scanner sc=scanner(); mapping m; while ( (m=sc->read(100)) ) vals+=values(m); return vals; } //! method array|mapping cast("mapping"|"array") array|mapping cast(string to) { if (sscanf(to,"array%*s")) { array tupels=({}); Glue.Scanner sc=scanner(); mapping m; while ( (m=sc->read(100)) ) tupels+=(array)m; return tupels; } if (sscanf(to,"mapping%*s")) { mapping tupels=([]); Glue.Scanner sc=scanner(); mapping m; while ( (m=sc->read(100)) ) tupels+=m; return tupels; } error("illegal argument 1 to cast\n"); } } //! //! subclass vTable //! inherits Table //! This is just like a normal table, but all data is encode_value()ed, //! and unpacked if reading, for convinience. //! class vTable { inherit Table; mixed `[](int|string key) { string s=parent->_fetch(table_id,key); if (s) return decode_value(s); return ([])[0]; } string `[]=(int|string key,mixed value) { parent->_store(table_id,key,encode_value(value)); return value; } array(int|string) _values() { array vals=({}); Glue.Scanner sc=parent->_scanner(table_id); mapping m; while ( (m=sc->read(100)) ) vals+=values(m); return map(vals,decode_value); } array|mapping cast(string to) { if (sscanf(to,"array%*s")) { array tupels=({}); Glue.Scanner sc=parent->_scanner(table_id); mapping m; while ( (m=sc->read(100)) ) tupels+=(array)m; return map(tupels,lambda(array(string) v) { return ({v[0],decode_value(v[1])}); }); } if (sscanf(to,"mapping%*s")) { mapping tupels=([]); Glue.Scanner sc=parent->_scanner(table_id); mapping m; while ( (m=sc->read(100)) ) tupels+=m; return (mapping)map((array)tupels, lambda(array(string) v) { return ({v[0],decode_value(v[1])}); }); } error("illegal argument 1 to cast\n"); } } //! //! subclass vzTable //! inherits Table //! This is just like a normal table, but all data is encode_value()ed, //! and gzipped (Gz) //! and unpacked if reading, for convinience and space savings. //! class vzTable { inherit Table; string pack(mixed any) { return Gz.deflate()->deflate(encode_value(any)); } mixed unpack(string data) { return decode_value(Gz.inflate()->inflate(data)); } mixed `[](int|string key) { string s=parent->_fetch(table_id,key); if (s) return unpack(s); return ([])[0]; } string `[]=(int|string key,mixed value) { parent->_store(table_id,key,pack(value)); return value; } array(int|string) _values() { array vals=({}); Glue.Scanner sc=parent->_scanner(table_id); mapping m; while ( (m=sc->read(100)) ) vals+=values(m); return map(vals,unpack); } array|mapping cast(string to) { if (sscanf(to,"array%*s")) { array tupels=({}); Glue.Scanner sc=parent->_scanner(table_id); mapping m; while ( (m=sc->read(100)) ) tupels+=(array)m; return map(tupels,lambda(array(string) v) { return ({v[0],unpack(v[1])}); }); } if (sscanf(to,"mapping%*s")) { mapping tupels=([]); Glue.Scanner sc=parent->_scanner(table_id); mapping m; while ( (m=sc->read(100)) ) tupels+=m; return (mapping)map((array)tupels, lambda(array(string) v) { return ({v[0],unpack(v[1])}); }); } error("illegal argument 1 to cast\n"); } } //! //! subclass Transaction //! class Transaction { Mird parent; Glue.Transaction mtr; mapping table2id=([]); int refresh_at_close=0; function(int,int|string:string) _fetch; function(int:Glue.Scanner) _scanner; void create(object _parent) { parent=_parent; mtr=Glue.Transaction(parent->mird); _fetch=mtr->fetch; _scanner=lambda(int ... args) { return Glue.Scanner(mtr,@args); }; } Transaction _store(int table_id,int|string key,string value) { if (!value) mtr->delete(table_id,key); else mtr->store(table_id,key,value); return this_object(); } //! method Table table(string name) //! Creates a table object for that table, //! which emulates a mapping and in which //! you can make changes to the database or //! do lookups. Table table(string name) { if (name==NEXT_TABLE) error("illegal name\n"); int id; if (!(id=table2id[name])) { if (parent->table2id[name]) id=table2id[name]=parent->table2id[name]; else { string d=mtr->fetch(TABLE2ID_TABLE,name); if (!d) error("no such table (%O)\n",name); id=table2id[name]=(int)d; } } return Table(this_object(),id); } //! method vTable vtable(string name) //! A vTable is just like a table, except that the //! values can be anything, not just strings. The //! data is encode_value()ed before storage. vTable vtable(string name) { if (name==NEXT_TABLE) error("illegal name\n"); if (!table2id[name]) { string d=mird->fetch(TABLE2ID_TABLE,name); if (!d) error("no such table (%O)\n",name); table2id[name]=(int)d; } return vTable(this_object(),table2id[name]); } //! //! method Table new_stringkey_table(string name) //! method Table new_hashkey_table(string name) //! Creates a new table in the database. //! A stringkey table is a mapping from string to string, //! and a hashkey table is mapping from int to string. //! Table new_stringkey_table(string name) { string d=mtr->fetch(TABLE2ID_TABLE,name); if (d) error("table already exist"); int no=parent->next_table++; mtr->store(TABLE2ID_TABLE,name,(string)no); mtr->store(ID2TABLE_TABLE,no,name); mtr->store(TABLE2ID_TABLE,NEXT_TABLE,(string)parent->next_table); mtr->new_stringkey_table(no); refresh_at_close=1; return table(name); } Table new_hashkey_table(string name) { string d=mtr->fetch(TABLE2ID_TABLE,name); if (d) error("table already exist"); int no=parent->next_table++; mtr->store(TABLE2ID_TABLE,name,(string)no); mtr->store(ID2TABLE_TABLE,no,name); mtr->store(TABLE2ID_TABLE,NEXT_TABLE,(string)parent->next_table); mtr->new_hashkey_table(no); refresh_at_close=1; return table(name); } //! //! method array(string) tables() //! returns the names of the tables in the database. //! array(string) tables() { return indices(Table(this_object(),TABLE2ID_TABLE)); } //! //! method void close() //! Finishes a transaction. This throws exceptions //! if there were conflicts. //! void close() { mtr->close(); if (refresh_at_close) parent->table2id=parent->id2table=([]); } //! //! method void cancel() //! method void destroy() //! Cancels (rewinds) a transaction. //! void cancel() { mtr->cancel(); } void destroy() { if (mtr) destruct(mtr); } } } program _module_value=Mird;