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... <cr>
    ./a.out < schema.txt <class_name=defaults to theDb>
      this produces a source file s.C containing all
      * table definitions in the form of: <Tbl>
          constructed as:  Entity Tbl(db);
      * default locators in the form of <tbl>
          constructed as:  TblLoc tbl(Tbl);
          <Tbl> = table name, first char capitalized.
          <tbl> = table name, all lower case
      * ikey_<Key_name> and rkey_<Key_name> class definitions
      * key cmpr functions for ikey_<Key_name> and rkey_<Key_name>
      * 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 = <table_name> ( <member> [, <member> ...] )
* member = <field_name> <dtype> [<xtype> ...]
* 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 ( <enum_value> [, <enum_value> ...] )
* dtype = <dtype0> [<dtqual>] | <dtype1>
* xrefer = <table_name> ( <key_field> [, <key_field> ...] )
   ON [DELETE | UPDATE] [RESTRICT | CASCADE | SET NULL | NO ACTION]
* def_int = [+|-][0-9...]
* def_dbl = <def_int>[.[0-9...]]
* def_str = '[notany(') | \any() ...]' | "[notany(") | \any() ...]"
* def_value = <def_str> | <def_int> | <def_dbl> | NULL
* xtype = [NOT] NULL | DEFAULT <def_value> | AUTO_INCREMENT |
          UNIQUE [PRIMARY] [KEY] | PRIMARY [UNIQUE] [KEY] |
          REFERENCES xrefer

** CREATE online modifier INDEX index;
* index = <index_name> ON <table_name> ( <key_field> [, <key_field> ...] );
* 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 <cstdio>
#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: <key field parameters> 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 <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#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();

