dydbi

dydbi is a library which provides reasonably efficient database support to Dylan programs, specifically programs written in project Gwydion's mindy.

The library is implemented as a C sharable library that incorporates the BSD database library to store and retreive keyed records and a Dylan library that implements the higher level functions.

You will need Jim Studt's shared library patches for mindy in order to use this library.

A simple phone book example

The following Dylan code implements a simple phone book class. It inherits all of its primitive operations from the <database-element> class.


//
// Define a kind of <database-element>.  Note the inherited slots where
// we tell which fields are part of the key and which fields are data to
// be stored.  Any field not listed will not be stored.
//
define class <phone-book-entry> ( <database-element> )
  instance slot number :: <integer>, init-keyword: number:;
  instance slot name :: <string>, init-keyword: name:;

  inherited slot key-fields, init-value: list( name);
  inherited slot data-fields, init-value: list( number);
end class <phone-book-entry>;

//
// Define a 'tag' which is used to identify this type of record in the 
// database.  These must be unique for each class which is stored in the
// database.  Try to keep them short since they are stored with each record.
// I always use 4 letters.
//
define method table-tag( c == <phone-book-entry> ) => ( tag :: <string> );
  "PHN#"
end method table-tag;

This class can then be used by programs such as the following:


//
// A sample program that:
//   - opens a database ( creating it if required)
//   - wipes out the test names
//   - defines some ( name, number) pairs and watches the results
//   - closes the database ( very important!)
//
define method main (argv0, #rest noise)
  //
  // open the database
  //
  let book = open-database( "phone.book", writable?: #t, create?: #t) |
    error("Failed to open database.");

  //
  // A method to lookup a name and print what it found.
  //
  local method probe-entry( find-name :: <string>) => ();
	  let r = make( <phone-book-entry>, name: find-name, number: 0);

	  if ( reread-record( book, r))
	    signal("look(%=) => %=\n", find-name, r.number);
	  else 
	    signal("look(%=) => not found\n", find-name);
	  end if;
	end method probe-entry;

  //
  // A method to create an entry and print while it does it.
  //
  local method create-entry( name :: <string>, number :: <integer>) => ();
	  signal("------\ndefn(%=) => %=\n", name, number);

	  let r = make( <phone-book-entry>, name: name, number: number );
	  insert-record( book, r, overwrite?: #t);
	end method create-entry;
  
  //
  // A method to silently delete an entry.
  //
  local method delete-entry( find-name :: <string> ) => ();
	  let r = make( <phone-book-entry>, name: find-name, number: 0);
	  delete-record( book, r);
	end method delete-entry;

  //
  // Do some operations...
  //
  let test-names = #( "jim", "amy", "audrey", "alaina" );

  do( delete-entry, test-names);
  
  do( probe-entry, test-names);

  create-entry( "jim", 9664549 );
  do( probe-entry, test-names);

  create-entry( "amy", 9664549 );
  do( probe-entry, test-names);

  create-entry( "audrey", 9666309 );
  do( probe-entry, test-names);

  close-database( book);
end method main;

Database Selection

The following are used for database selection:

<db-handle>     [ class ]

A db-handle is used to reference a connection to a database. They are created obtained by calling open-database and are used are parameters to all of the functions which require access to a database.

Any number of databases may be open simultaneously.

db-error => string

Return the textual description of the last database error. This cleared after each successful database operation.

open-database name #key path signal-errors? writable? create? => database

Open the named database and reutrn a <db-handle> to it. #f is returned on failure. The default behavior is to signal errors.

Acceptable keywords are:

path:
Currently ignored. It is intended for search path support.
signal-errors:
Signal errors using error and a descriptive message. This is #t by default.
writable?:
Open the database to allow writing. The default value is #f.
create?:
Create the database if it does not exist. The default value is #f.

close-database database

Close the specified database. It is imperitive that databases be closed before the program exits in order to prevent data loss. The database parameter must be either a <db-handle> or #f.

Data Element Operations

These are the highest level data operations. For basic database work these should be sufficient. There are other methods defined later which can be used to extend the capabilities for new datatypes.

<database-element>     [ class ]

For each type of record to be stored in the database the programmer should make a subclass of this which adds a slot for each field of the record. In addition, the subclass should define the key-fields slot and the data-fields slot using the inherited slot mechanism.

The key-fields and data-fields slots should contain sequences where each element of a sequence designates a slot of the instance. Slots may be designated by their name as a <symbol>, their getter, or a <db-mapping>. By name is the preferred mechanism, but in cases where field specific data conversion is required the <db-mapping> method must be used.

table-tag record => tag

This method returns a distinct tag string for each class of record. It is used internally by dydbi to differentiate record types. Failure to return a distinct string will almost certainly cause dydbi to fail horribly. The strings are stored in the database for each record type and should be reasonably short. Four characters is recommended.

create-record database class => record

This method makes a new instance of class, inserts it into the database and returns it. This will usually need to be overridden for each record type since the object will need to be initialized before being inserted.

for-all-records database class function #rest rest #key where

For each record of class class this function calls function with arguments of the record and rest. If where is specified then it is used to filter the records by only passing those which where( record) returns non-#f.

select-all-records database class #key
where=> <sequence>

Returns a sequence of all records of class class. If where is specified then records for which where(record) returns #f are excluded.

reread-record database record => result

This method reads the record from database record whose key matches record's key. The method returns #f if the record can not be read.

insert-record database record #key overwrite? => result

This method inserts record into database. If overwrite? is #f (the default) and a record exists with the same key as record then an error is raised. The method returns #f if the record can not be inserted.

update-record database record #key insert?=> result

This method writes record into database. If insert? is #f (the default) and a record does not exist with the same key as record then an error is raised. The method returns #f if the record can not be updated.

delete-record database record #key must-exist?=> result

This method delete from database the record with the same key as record. If must-exist? is not #f (the default is #f) and a record does not exist with the same key as record then an error is raised. The method returns #f if the record can not be deleted.

Constraint Checking

<db-constraint-violation>     [ class ]

This is a subclass of <error> which is raised to indicate a violation of the databases constraints as defined by the programmer. The dydbi will not raise these on its own, but provides them for the client code.

constraint-violation format arg1 ...

This provides a convenient way of creating, populating, and raising a <db-constraint-violation>

valid-for-entry? database record => result

This method returns #t if the data contained in record may be entered into database. It does not indicate whether or not a record exists with the same key.

In the case where record is not valid for entry a constraint violation should be raised using constraint-violation.

The most general function always returns #t. Clients should specialize this method to provide validation for their types of records.

valid-for-delete? database record => result

This method returns #t if the data contained in record may be deleted from the database. Typically this involves checking for dependant records. It does not indicate whether or not a record exists with the same key.

In the case where record is not valid for delete a constraint violation should be raised using constraint-violation.

The most general function always returns #t. Clients should specialize this method to provide validation for their types of records.

Data Mapping

This section describes the data conversion functions. None of this is required for databases composed of purely integer and string fields.

<db-mapping>    [ class ]

A <db-mapping> is used specify the getter, setter, input conversion function and output conversion function for a field.
getter:
The getter function for this field.
setter:
The setter function for this field.
map-in:
The input mapping function. Values from the database are run through this function when read in. The default value is identity.
map-out:
The output mapping function. Values from the record are run through this function before being written to the database. The default value is identity.

default-map-in class => function

default-map-out class => function

Return the default inbound or outbound data conversion function for fields of type class. These are used when keys or data fields are specified by name rather than by <db-mapping> and dydbi must select a mapping. Refining these methods extend the set of types which can be stored in the database. You can provide functions to convert your types into (and from) either strings or integers.
Send comments and problems to
jim@federated.com
Last modified: Tue Oct 17 15:28:07 CDT 1995