Notice: Fields and Table names are remapped to: first char Upper case, the rest are lower case The code generator (x.C) processes limited schema information and creates code which operates a base class Db as schema describes. x.C reads from stdin, and has 1 optional parameter (theDb class name) comments are whitespace, form /*comment*/ or -- comment... ./a.out < schema.txt this produces a source file s.C containing all * table definitions in the form of: constructed as: Entity Tbl(db); * default locators in the form of constructed as: TblLoc tbl(Tbl); = table name, first char capitalized. = table name, all lower case * ikey_ and rkey_ class definitions * key cmpr functions for ikey_ and rkey_ * EntityObj functions: Allocate, Construct, Destruct, Deallocate; * theDb class definition, with base class Db with constructor and functions create, open, and close x.C allowed input data as follow: items in [] are optional ** CREATE TABLE table; * table = ( [, ...] ) * member = [ ...] * dtqual = ( [0-9...] ) | [UNSIGNED | SIGNED | ZEROFILL] * dtype0 = BIT | TINYINT | SMALLINT | MEDIUMINT | INT | INTEGER | BIGINT | REAL | DOUBLE | FLOAT | DECIMAL * dtype1 = DATE | TIME | TIMESTAMP | YEAR | CHAR | VARCHAR [BINARY] | BINARY | VARBINARY | BLOB | MEDIUMBLOB | LONGBLOB | TINYTEXT | TEXT | MEDIUMTEXT | LONGTEXT | BOOL | BOOLEAN | ENUM ( [, ...] ) * dtype = [] | * xrefer = ( [, ...] ) ON [DELETE | UPDATE] [RESTRICT | CASCADE | SET NULL | NO ACTION] * def_int = [+|-][0-9...] * def_dbl = [.[0-9...]] * def_str = '[notany(') | \any() ...]' | "[notany(") | \any() ...]" * def_value = | | | NULL * xtype = [NOT] NULL | DEFAULT | AUTO_INCREMENT | UNIQUE [PRIMARY] [KEY] | PRIMARY [UNIQUE] [KEY] | REFERENCES xrefer ** CREATE online modifier INDEX index; * index = ON ( [, ...] ); * online = [ONLINE | OFFLINE] * modifier = [UNIQUE | FULLTEXT | SPATIAL] note: C++ type Schema field type unsigned char: BOOLEAN, BIT, BINARY [unsigned] char: CHAR, TINYINT [unsigned] short: ENUM, SMALLINT [unsigned] int: DECIMAL, MEDIUMINT, INT, INTEGER [unsigned] long: BIGINT double: REAL, DOUBLE float: FLOAT char[n]: DATE[8], TIME[6], YEAR[4] TIMESTAMP[14], DATETIME[14] char[]: VARCHAR, TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT unsigned char[]: VARBINARY, TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB not implemented: SET examples: CREATE TABLE user_newtalk ( user_id int NOT NULL default 0, user_ip varbinary(40) NOT NULL default '', user_last_timestamp varbinary(14) NULL default NULL ) ; CREATE INDEX un_user_id ON user_newtalk (user_id); CREATE INDEX un_user_ip ON user_newtalk (user_ip); CREATE TABLE text ( old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, old_text mediumblob NOT NULL, old_flags tinyblob NOT NULL ) MAX_ROWS=10000000 AVG_ROW_LENGTH=10240; (MAX_ROWS,AVG_ROW_LENGTH are ignored) To initialize/open a db instance: #include #include "db.h" #include "s.C" theDb db; int main(int ac, char **av) { if( db.create(dfn) ) { fprintf(stderr,"create_db failed\n"); exit(1); } if( db.open(dfn) ) { fprintf(stderr,"open failed\n"); exit(1); } return 0; } Normally, almost everything is done with Key classes and locators. There is a default locator for every entity (lower case tbl) in theDb. defined similar to: Entity Tbl(db); TblLoc tbl(Tbl); Locators provide access to the record member fields: Access is provided by the .member() function. value = tbl.Field(); // read access tbl.Field(value); // write access tbl.size_Field(); // size(value) tbl._Field(); // addr(value) tbl.Field(value,sz); // write access to varObj/blob field tbl.set_wr(); // mark dirty To create database records: void insert() { db.tbl.Allocate(); // allocates a Tbl record assigned to tbl locator db.tbl.Field1(value1); // assignes values to new Tbl record fields db.tbl.Field2(value2); // ... db.tbl.Construct(); // runs theDb record constructor } the record constructor is normally generated by x.C and typically: if_err( insertProhibit() ); // run consistancy checks if_err( construct() ); // load id and reference int id = this->id(); { rkey_Tbl_Key1 rkey(*this); // add all keys associated to new id if_err( entity->index("Tbl_Key1")->Insert(rkey,&id) ); } if_err( insertCascade() ); // run insert cascade functions return 0; To access database records: The TblLoc::ikey_Field key classes use immediate data for key values The TblLoc::rkey_Field key classes use the current record for key values The Db::Key class provides: int Find(); // immedate ikey_ only int Locate(int op=keyGE); // immedate ikey_ and relative rkey_ int First(); // relative rkey_ only int Last(); // relative rkey_ only int Next(); // relative rkey_ only int First(pgRef &pos); // relative rkey_ only int Next(pgRef &pos); // relative rkey_ only To Locate/Find a table record (= EntityObj), use: theDb db; db.open("/path/the.db"); int ret = TblLoc::ikey_Key1(db.tbl, key).Find(); if( ret != 0 ) printf(" not found, ret = %d\n",ret); printf(" the record data is now %d\n", ++db.tbl.Data()); db.commit(); db.close(); ** NOTE: will be automatically constructed from basic types, but for varObj types they should be explicity constructed as in: int ret = TblLoc::ikey_Key1(db.tbl, TblObj::t_Key1Field1(key, ksz)).Find(); Locate may be called with op=keyLT,keyLE,keyEQ,keyGE,keyGT the index will be positioned to the appropriate boundry keyEQ is mapped to keyLE when used with Locate First/Next have a default cursor position associated to the index. First/Next may return/use a cursor position locator which may be used if multiple simultainious cursor positions are needed. To iterate use Locate/First to set the cursor at a table record then use Next to iterate until need records are exhausted. theDb db; db.open("/path/the.db"); TblLoc::rkey_KeyField1 key1(db.tbl); if( !(ret=key1.First()) ) do { printf(" the record data is now %d\n", ++db.tbl.Data()); } while( !(ret=key1.Next()) ); db.commit(); db.close(); To delete database records: theDb db; db.open("/path/the.db"); while( !db.tbl.Last() ) { db.tbl.Destruct(); // run theDb record destructor db.tbl.Deallocate(); // release db storage assigned to db.tbl } the record destructor is normally generated by x.C and typically: if_err( deleteProhibit() ); // run consistency checks { rkey_Tbl_Key1 rkey(*this); // remove all keys associated to id if_err( entity->index("Tbl_Key1")->Delete(rkey) ); } if_err( destruct() ); // delete id and reference if_err( deleteCascade() ); // run delete cascade functions return 0; There are also "media" keys, which are bit field key records which are organized by total "weight" = unsigned sum of bit fields. The weight is recursively subdefined in left/right subfields, so that all of the key record is encoded. This usually requires a little over 3 times as much storage. This kind of key can index audio/video data (and others, biometric, dna, images...). The key data can be: built, recovered, and compared for both magnitude and error as sum of squared/linear error. The work on this not complete at this time. to insert (8 bit fields): int ksz = Db::mediaKey::byte_size(sz, 8); uint8_t *key = db.Tbl._key1(ksz); Db::mediaKey::build(key, dat, 8, sz); to access (8 bit fields): int ksz = Db::mediaKey::byte_size(len, 8); uint8_t key[ksz]; Db::mediaKey::build(key,dat,8,len); PrefixLoc::ikey_Prefix_key_data ikey(db.prefix, key); if( (ret=ikey.Find()) != 0 ) { Example: Makefile: CPPFLAGS := -Wall -ggdb $(CFLAGS) LDFLAGS := all: prog clean: rm -f *.o xsch prog prog: prog.o db.o g++ -o $@ $(LDFLAGS) $+ s.C: x.o sch.txt g++ -o xsch x.o ./xsch < sch.txt db.o: db.C db.h prog.o: prog.C prog.h s.C db.h x.o: x.C sch.txt: CREATE TABLE a_tbl ( a_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, a_name varchar(255) binary NOT NULL default '', a_data int ); CREATE UNIQUE INDEX a_names ON a_tbl (a_name); CREATE UNIQUE INDEX a_datum ON a_tbl (a_data); prog.C: #include #include #include #include #include "db.h" #include "s.C" theDb db; void chk(int ret, const char *msg) { fprintf(stderr,"fail %s = %d\n",msg,ret); exit(1); } void insert(const char *nm, int dat) { chk( db.a_tbl.Allocate(), "Allocate"); chk( db.a_tbl.A_name(nm), "A_name(nm)"); chk( db.a_tbl.A_data(dat, "A_data(dat)"); chk( db.a_tbl.Construct(), "Construct"); } int main(int ac, char **av) { const char *dfn = ac > 1 ? av[1] : "the.db"; chk( db.create(dfn), "db.create"); chk( db.open(dfn), "db.open"); // insert some data insert("a borrower", 321); insert("nor lendor", 232); insert("be", 123); chk( db.commit(), "commit()"); chk( db.close(), "close()"); chk( db.open(dfn, "db.reopen"); // lookup at datum using immedate key int dat = 232; A_tblLoc::ikey_A_datum ky(db.a_tbl,dat); chk( ky.Find(), "ky.Find()"); printf("Found %d name %s\n",dat,db.a_tbl.A_name()); // look at datum using relative key int ret = 0; A_tblLoc::rkey_A_datum rky(db.a_tbl); if( !(ret=rky.First()) ) do { printf("Data %d name %s\n",db.a_tbl.A_data(),db.a_tbl.A_name()); } while( !(ret=rky.Next()) ); if( ret == Db::errNotFound ) ret = 0; chk( ret, "datam"); // get cursor position using immediate key A_tblLoc::ikey_A_names iky(db.a_tbl,"b"); if( !(ret=iky.Locate()) ) { // switch to relative key A_tblLoc::rkey_A_names rky(db.a_tbl); do { printf("Name %s data %d\n",db.a_tbl.A_name(),db.a_tbl.A_data()); } while( !(ret=rky.Next()) ); } if( ret == Db::errNotFound ) ret = 0; chk( ret, "names"); chk( db.commit(1), "commit(1)"); db.close(); return 0; } Traveling db: only one process allowed write access, no readers allowed all allowed read lock while not write locked to access database, typically: int resetDb(int rw) { int result = 0; if( access("/path/t.db", F_OK) ) db->create("/path/t.db"); if( !result ) result = openDb(rw); return result; } int openDb(int rw) { return db->access("/path/tdb.db", SHM_KEY, rw); } void closeDb() { detachDb(); db->close(); } void commitDb() { db->commit(); } // detaches void undoDb() { db->undo(); } // detaches int attachDb(int rw) { return db->attach(rw); } int detachDb() { return db->detach(); } typical session: openDb(); detachDb(); ... //typical read access attachDb(); do_read(); detachDb(); ... //typical write access attachDb(1); do_write(); commitDb(); ... closeDb();