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.
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;
<db-handle>
[ class ]
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
open-database
name #key path signal-errors? writable? create? => database
<db-handle>
to
it. #f
is returned on failure. The default behavior is
to signal errors.
Acceptable keywords are:
path:
signal-errors:
error
and a descriptive
message. This is #t
by default.
writable?:
#f
.
create?:
#f
.
close-database
database
<db-handle>
or #f
.
<database-element>
[ class ]
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
create-record
database class => record
for-all-records
database class function #rest rest #key where
#f
.
select-all-records
database class #key
where=> <sequence>
#f
are excluded.
reread-record
database record => result
#f
if the record
can not be read.
insert-record
database record #key overwrite? => result
#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
#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
#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.
<db-constraint-violation>
[ class ]
<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 ...
valid-for-entry?
database record => result
#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
#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.
<db-mapping>
[ class ]
<db-mapping>
is used specify the getter, setter,
input conversion function and output conversion function for a field.
getter:
setter:
map-in:
identity
.
map-out:
identity
.
default-map-in
class => function
default-map-out
class => function