[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
6.1 Relational Database | 'relational-database | |
6.2 Relational Infrastructure | ||
6.3 Weight-Balanced Trees | 'wt-tree |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(require 'relational-database)
This package implements a database system inspired by the Relational Model (E. F. Codd, A Relational Model of Data for Large Shared Data Banks). An SLIB relational database implementation can be created from any 6.2.1 Base Table implementation.
Why relational database? For motivations and design issues see
http://swissnet.ai.mit.edu/~jaffer/DBManifesto.html.
6.1.1 Using Databases | 'databases | |
6.1.2 Table Operations | ||
6.1.3 Database Interpolation | 'database-interpolate | |
6.1.4 Embedded Commands | 'database-commands | |
6.1.5 Database Macros | 'within-database | |
6.1.6 Database Browser | 'database-browse |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This enhancement wraps a utility layer on relational-database
which provides:
Auto-sharing refers to a call to the procedure
open-database
returning an already open database (procedure),
rather than opening the database file a second time.
Note: Databases returned by open-database
do not include
wrappers applied by packages like 6.1.4 Embedded Commands. But
wrapped databases do work as arguments to these functions.
When a database is created, it is mutable by the creator and not auto-sharable. A database opened mutably is also not auto-sharable. But any number of readers can (open) share a non-mutable database file.
This next set of procedures mirror the whole-database methods in
6.2.4 Database Operations. Except for create-database
, each
procedure will accept either a filename or database procedure for its
first argument.
filename should be a string naming a file; or #f
. base-table-type must be a
symbol naming a feature which can be passed to require
. create-database
returns a new, open relational database (with base-table type base-table-type)
associated with filename, or a new ephemeral database if filename is #f
.
create-database
is the only run-time use of require in SLIB
which crosses module boundaries. When base-table-type is require
d by create-database
; it
adds an association of base-table-type with its relational-system procedure
to mdbm:*databases*.
alist-table is the default base-table type:
(require 'databases) (define my-rdb (create-database "my.db" 'alist-table)) |
alist-table
and base-table modules which have been
require
d will dispatch correctly from the
open-database
procedures. Therefore, either pass two
arguments to open-database
, or require the base-table of your
database file uses before calling open-database
with one
argument.
Returns mutable open relational database or #f.
Returns an open relational database associated with rdb. The database will be opened with base-table type base-table-type).
open-database
will attempt to deduce the correct base-table-type.
Writes the mutable relational-database rdb to filename.
Writes the mutable relational-database rdb to the filename it was opened with.
Syncs rdb and makes it immutable.
rdb will only be closed when the count of open-database
- close-database
calls for rdb (and its filename) is 0. close-database
returns #t if successful;
and #f otherwise.
Prints a table of open database files. The columns are the base-table type, number of opens, `!' for mutable, the filename, and the lock certificate (if locked).
(mdbm:report) -| alist-table 003 /usr/local/lib/slib/clrnamdb.scm alist-table 001 ! sdram.db jaffer@aubrey.jaffer.3166:1038628199 |
rdb must be a relational database and table-name a symbol.
open-table
returns a "methods" procedure for an existing relational table in
rdb if it exists and can be opened for reading, otherwise returns
#f
.
rdb must be a relational database and table-name a symbol.
open-table!
returns a "methods" procedure for an existing relational table in
rdb if it exists and can be opened in mutable mode, otherwise returns
#f
.
Adds the domain rows row5 ... to the `*domains-data*' table in rdb. The format of the row is given in 6.2.2 Catalog Representation.
(define-domains rdb '(permittivity #f complex? c64 #f)) |
Use define-domains
instead.
Adds tables as specified in spec-0 ... to the open relational-database rdb. Each spec has the form:
(<name> <descriptor-name> <descriptor-name> <rows>) |
(<name> <primary-key-fields> <other-fields> <rows>) |
where <name> is the table name, <descriptor-name> is the symbol name of a descriptor table, <primary-key-fields> and <other-fields> describe the primary keys and other fields respectively, and <rows> is a list of data rows to be added to the table.
<primary-key-fields> and <other-fields> are lists of field descriptors of the form:
(<column-name> <domain>) |
(<column-name> <domain> <column-integrity-rule>) |
where <column-name> is the column name, <domain> is the domain
of the column, and <column-integrity-rule> is an expression whose
value is a procedure of one argument (which returns #f
to signal
an error).
If <domain> is not a defined domain name and it matches the name of this table or an already defined (in one of spec-0 ...) single key field table, a foreign-key domain will be created for it.
If symbol table-name exists in the open relational-database rdb, then returns a list of the table-name, its primary key names and domains, its other key names and domains, and the table's records (as lists). Otherwise, returns #f.
The list returned by list-table-definition
, when passed as an
argument to define-tables
, will recreate the table.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
These are the descriptions of the methods available from an open relational table. A method is retrieved from a table by calling the table with the symbol name of the operation. For example:
((plat 'get 'processor) 'djgpp) => i386 |
Some operations described below require primary key arguments. Primary keys arguments are denoted key1 key2 .... It is an error to call an operation for a table which takes primary key arguments with the wrong number of primary keys for that table.
#f
otherwise.
((plat 'get 'processor) 'djgpp) => i386 ((plat 'get 'processor) 'be-os) => #f |
6.1.2.1 Single Row Operations | ||
6.1.2.2 Match-Keys | ||
6.1.2.3 Multi-Row Operations | ||
6.1.2.4 Indexed Sequential Access Methods | ||
6.1.2.5 Sequential Index Operations | ||
6.1.2.6 Table Administration |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The term row used below refers to a Scheme list of values (one for
each column) in the order specified in the descriptor (table) for this
table. Missing values appear as #f
. Primary keys must not
be missing.
(define telephone-table-desc ((my-database 'create-table) 'telephone-table-desc)) (define ndrp (telephone-table-desc 'row:insert)) (ndrp '(1 #t name #f string)) (ndrp '(2 #f telephone (lambda (d) (and (string? d) (> (string-length d) 2) (every (lambda (c) (memv c '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\+ #\( #\ #\) #\-))) (string->list d)))) string)) |
#f
otherwise.
((plat 'row:retrieve) 'linux) => (linux i386 linux gcc) ((plat 'row:retrieve) 'multics) => #f |
#f
otherwise.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The (optional) match-key1 ... arguments are used to restrict
actions of a whole-table operation to a subset of that table. Those
procedures (returned by methods) which accept match-key arguments will
accept any number of match-key arguments between zero and the number of
primary keys in the table. Any unspecified match-key arguments
default to #f
.
The match-key1 ... restrict the actions of the table command to those records whose primary keys each satisfy the corresponding match-key argument. The arguments and their actions are:
#f
- The false value matches any key in the corresponding position.
- an object of type procedure
- This procedure must take a single argument, the key in the corresponding position. Any key for which the procedure returns a non-false value is a match; Any key for which the procedure returns a
#f
is not.- other values
- Any other value matches only those keys
equal?
to it.
((plat 'get* 'processor)) => (i386 i8086 i386 i8086 i386 i386 i8086 m68000 m68000 m68000 m68000 m68000 powerpc) ((plat 'get* 'processor) #f) => (i386 i8086 i386 i8086 i386 i386 i8086 m68000 m68000 m68000 m68000 m68000 powerpc) (define (a-key? key) (char=? #\a (string-ref (symbol->string key) 0))) ((plat 'get* 'processor) a-key?) => (m68000 m68000 m68000 m68000 m68000 powerpc) ((plat 'get* 'name) a-key?) => (atari-st-turbo-c atari-st-gcc amiga-sas/c-5.10 amiga-aztec amiga-dice-c aix) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
((plat 'row:retrieve*) a-key?) => ((atari-st-turbo-c m68000 atari turbo-c) (atari-st-gcc m68000 atari gcc) (amiga-sas/c-5.10 m68000 amiga sas/c) (amiga-aztec m68000 amiga aztec) (amiga-dice-c m68000 amiga dice-c) (aix powerpc aix -)) |
Note that row:insert*
and row:update*
do not use
match-keys.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Indexed Sequential Access Methods are a way of arranging database information so that records can be accessed both by key and by key sequence (ordering). ISAM is not part of Codd's relational model. Hardcore relational programmers might use some least-upper-bound join for every row to get them into an order.
Associative memory in B-Trees is an example of a database
implementation which can support a native key ordering. SLIB's
alist-table
implementation uses sort
to implement
for-each-row-in-order
, but does not support isam-next
and isam-prev
.
The multi-primary-key ordering employed by these operations is the lexicographic collation of those primary-key fields in their given order. For example:
(12 a 34) < (12 a 36) < (12 b 1) < (13 a 0) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The following procedures are individually optional depending on the base-table implememtation. If an operation is not supported, then calling the table with that operation symbol will return false.
isam-next
, that field, or a field to its left, will be
changed. This allows one to skip over less significant key fields.
isam-next
, that field, or a field to its left, will be
changed. This allows one to skip over less significant key fields.
For example, if a table has key fields:
(col1 col2) (9 5) (9 6) (9 7) (9 8) (12 5) (12 6) (12 7) |
Then:
((table 'isam-next) '(9 5)) => (9 6) ((table 'isam-next 'col2) '(9 5)) => (9 6) ((table 'isam-next 'col1) '(9 5)) => (12 5) ((table 'isam-prev) '(12 7)) => (12 6) ((table 'isam-prev 'col2) '(12 7)) => (12 6) ((table 'isam-prev 'col1) '(12 7)) => (9 8) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(require 'database-interpolate)
Indexed sequential access methods allow finding the keys (having associations) closest to a given value. This facilitates the interpolation of associations between those in the table.
isam-prev
and isam-next
operations. column should be a symbol or exact positive integer
designating a numerically valued column of table.
interpolate-from-table
calculates and returns a value
proportionally intermediate between its values in the next and
previous key records contained in table. For keys larger than
all the stored keys the value associated with the largest stored key
is used. For keys smaller than all the stored keys the value
associated with the smallest stored key is used.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(require 'database-commands)
This enhancement wraps a utility layer on relational-database
which provides:
*commands*
table in database.
When an enhanced relational-database is called with a symbol which
matches a name in the *commands*
table, the associated
procedure expression is evaluated and applied to the enhanced
relational-database. A procedure should then be returned which the user
can invoke on (optional) arguments.
The command *initialize*
is special. If present in the
*commands*
table, open-database
or open-database!
will return the value of the *initialize*
command. Notice that
arbitrary code can be run when the *initialize*
procedure is
automatically applied to the enhanced relational-database.
Note also that if you wish to shadow or hide from the user
relational-database methods described in 6.2.4 Database Operations, this
can be done by a dispatch in the closure returned by the
*initialize*
expression rather than by entries in the
*commands*
table if it is desired that the underlying methods
remain accessible to code in the *commands*
table.
6.1.4.1 Database Extension | ||
6.1.4.2 Command Intrinsics | ||
6.1.4.3 Define-tables Example | ||
6.1.4.4 The *commands* Table | ||
6.1.4.5 Command Service | ||
6.1.4.6 Command Example |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(wrap-command-interface rdb)
.
Adds commands to the *commands*
table as specified in
spec-0 ... to the open relational-database rdb. Each
spec has the form:
((<name> <rdb>) "comment" <expression1> <expression2> ...) |
((<name> <rdb>) <expression1> <expression2> ...) |
where <name> is the command name, <rdb> is a formal passed the calling relational database, "comment" describes the command, and <expression1>, <expression1>, ... are the body of the procedure.
define-*commands*
adds to the *commands*
table a command
<name>:
(lambda (<name> <rdb>) <expression1> <expression2> ...) |
open-command-database
will attempt to deduce the correct
base-table-type. If the database can not be opened or if it lacks the
*commands*
table, #f
is returned.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Some commands are defined in all extended relational-databases. The are called just like 6.2.4 Database Operations.
(car domain-row)
and
returns #t
. Otherwise returns #f
.
For the fields and layout of the domain table, See section 6.2.2 Catalog Representation. Currently, these fields are
The following example adds 3 domains to the `build' database.
`Optstring' is either a string or #f
. filename
is a
string and build-whats
is a symbol.
(for-each (build 'add-domain) '((optstring #f (lambda (x) (or (not x) (string? x))) string #f) (filename #f #f string #f) (build-whats #f #f symbol #f))) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The following example shows a new database with the name of `foo.db' being created with tables describing processor families and processor/os/compiler combinations. The database is then solidified; saved and changed to immutable.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The table *commands*
in an enhanced relational-database has
the fields (with domains):
PRI name symbol parameters parameter-list procedure expression documentation string |
The parameters
field is a foreign key (domain
parameter-list
) of the *catalog-data*
table and should
have the value of a table described by *parameter-columns*
. This
parameter-list
table describes the arguments suitable for passing
to the associated command. The intent of this table is to be of a form
such that different user-interfaces (for instance, pull-down menus or
plain-text queries) can operate from the same table. A
parameter-list
table has the following fields:
PRI index ordinal name symbol arity parameter-arity domain domain defaulter expression expander expression documentation string |
The arity
field can take the values:
single
optional
boolean
#f
is substituted) is acceptable.
nary
nary1
The domain
field specifies the domain which a parameter or
parameters in the index
th field must satisfy.
The defaulter
field is an expression whose value is either
#f
or a procedure of one argument (the parameter-list) which
returns a list of the default value or values as appropriate.
Note that since the defaulter
procedure is called every time a
default parameter is needed for this column, sticky defaults can
be implemented using shared state with the domain-integrity-rule.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
name
field of the command's parameter-table.
index
field of the command's parameter-table.
arity
field of the command's parameter-table. For a
description of arity
see table above.
type-id
field of the contents of the domain
of the
command's parameter-table.
defaulters
field of the command's parameter-table.
nary
arity
parameters.
(alias parameter-name)
. There can be
more than one alias per parameter-name.
For information about parameters, See section 4.4.4 Parameter lists.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Here is an example of setting up a command with arguments and parsing
those arguments from a getopt
style argument list
(see section 4.4.1 Getopt).
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(require 'within-database)
The object-oriented programming interface to SLIB relational databases has failed to support clear, understandable, and modular code-writing for database applications.
This seems to be a failure of the object-oriented paradigm where the type of an object is not manifest (or even traceable) in source code.
within-database
, along with the `databases' package,
reorganizes high-level database functions toward a more declarative
style. Using this package, one can tag database table and command
declarations for emacs:
etags -lscheme -r'/ *(define-\(command\|table\) (\([^; \t]+\)/\2/' \ source1.scm ... |
6.1.5.1 Within-database Example |
within-database
creates a lexical scope in which the commands
define-table
and define-command
create tables and
*commands*
-table entries respectively in open relational
database database.
within-database
Returns database.
Adds to the *commands*
table a command
<name>:
(lambda (<name> <rdb>) <expression1> <expression2> ...) |
where <name> is the table name, <descriptor-name> is the symbol name of a descriptor table, <primary-key-fields> and <other-fields> describe the primary keys and other fields respectively, and <rows> is a list of data rows to be added to the table.
<primary-key-fields> and <other-fields> are lists of field descriptors of the form:
(<column-name> <domain>) |
(<column-name> <domain> <column-integrity-rule>) |
where <column-name> is the column name, <domain> is the domain
of the column, and <column-integrity-rule> is an expression whose
value is a procedure of one argument (which returns #f
to signal
an error).
If <domain> is not a defined domain name and it matches the name of this table or an already defined (in one of spec-0 ...) single key field table, a foreign-key domain will be created for it.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Here is an example of within-database
macros:
(require 'within-database) (define my-rdb (add-command-tables (create-database "foo.db" 'alist-table))) (within-database my-rdb (define-command (*initialize* rdb) "Print Welcome" (display "Welcome") (newline) rdb) (define-command (without-documentation rdb) (display "without-documentation called") (newline)) (define-table (processor-family ((family atom)) ((also-ran processor-family))) (m68000 #f) (m68030 m68000) (i386 i8086) (i8086 #f) (powerpc #f)) (define-table (platform ((name symbol)) ((processor processor-family) (os symbol) (compiler symbol))) (aix powerpc aix -) ;; ... (amiga-aztec m68000 amiga aztec) (amiga-sas/c-5.10 m68000 amiga sas/c) (atari-st-gcc m68000 atari gcc) ;; ... (watcom-9.0 i386 ms-dos watcom)) (define-command (get-processor rdb) "Get processor for given platform." (((rdb 'open-table) 'platform #f) 'get 'processor))) (close-database my-rdb) (set! my-rdb (open-command-database! "foo.db")) -| Welcome (my-rdb 'without-documentation) -| without-documentation called ((my-rdb 'get-processor) 'amiga-sas/c-5.10) => m68000 (close-database my-rdb) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(require 'database-browse)
Prints the names of all the tables in database and sets browse's default to database.
Prints the names of all the tables in the default database.
For each record of the table named by the symbol table-name, prints a line composed of all the field values.
Opens the database named by the string pathname, prints the names of all its tables, and sets browse's default to the database.
Sets browse's default to database and prints the records of the table named by the symbol table-name.
Opens the database named by the string pathname and sets browse's
default to it; browse
prints the records of the table named by
the symbol table-name.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
6.2.1 Base Table | ||
6.2.2 Catalog Representation | ||
6.2.3 Relational Database Objects | ||
6.2.4 Database Operations |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A base-table is the primitive database layer upon which SLIB relational databases are built. At the minimum, it must support the types integer, symbol, string, and boolean. The base-table may restrict the size of integers, symbols, and strings it supports.
A base table implementation is available as the value of the identifier naming it (eg. alist-table) after requiring the symbol of that name.
(require 'alist-table)
Association-list base tables support all Scheme types and are suitable for small databases. In order to be retrieved after being written to a file, the data stored should include only objects which are readable and writeable in the Scheme implementation.
The alist-table base-table implementation is included in the SLIB distribution.
WB is a B-tree database package with SCM interfaces. Being disk-based, WB databases readily store and access hundreds of megabytes of data. WB comes with two base-table embeddings.
(require 'wb-table)
wb-table
supports scheme expressions for keys and values whose
text representations are less than 255 characters in length.
See section `wb-table' in WB.
(require 'rwb-isam)
rwb-isam is a sophisticated base-table implementation built on WB and SCM which uses binary numerical formats for key and non-key fields. It supports IEEE floating-point and fixed-precision integer keys with the correct numerical collation order.
This rest of this section documents the interface for a base table implementation from which the 6.1 Relational Database package constructs a Relational system. It will be of interest primarily to those wishing to port or write new base-table implementations.
open-database
, each base-table
module adds an association to *base-table-implementations* when
loaded. This association is the list of the base-table symbol and the
value returned by (make-relational-system base-table)
.
6.2.1.1 The Base | ||
6.2.1.2 Base Tables | ||
6.2.1.3 Base Field Types | ||
6.2.1.4 Composite Keys | ||
6.2.1.5 Base Record Operations | ||
6.2.1.6 Match Keys | ||
6.2.1.7 Aggregate Base Operations | ||
6.2.1.8 Base ISAM Operations |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
All of these functions are accessed through a single procedure by
calling that procedure with the symbol name of the operation. A
procedure will be returned if that operation is supported and #f
otherwise. For example:
(require 'alist-table) (define my-base (alist-table 'make-base)) my-base => *a procedure* (define foo (alist-table 'foo)) foo => #f |
#f
is returned.
Calling the close-base
method on this database and possibly other
operations will cause filename to be written to. If
filename is #f
a temporary, non-disk based database will be
created if such can be supported by the base table implelentation.
#t
, this database will have methods capable of
effecting change to the database. If mutable is #f
, only
methods for inquiring the database will be available. If the database
cannot be opened as specified #f
is returned.
Calling the close-base
(and possibly other) method on a
mutable database will cause filename to be written to.
close-database
(and possibly other) method on lldb may
cause filename to be written to. If filename is #f
this database will be changed to a temporary, non-disk based database if
such can be supported by the underlying base table implelentation. If
the operations completed successfully, #t
is returned.
Otherwise, #f
is returned.
#f
, no action is taken and #f
is returned. If this
operation completes successfully, #t
is returned. Otherwise,
#f
is returned.
#t
is returned. Otherwise, #f
is returned.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
#f
. The base table can then be opened using
(open-table lldb base-id)
. The positive integer
key-dimension is the number of keys composed to make a
primary-key for this table. The list of symbols
column-types describes the types of each column.
#f
.
As with make-table
, the positive integer key-dimension is
the number of keys composed to make a primary-key for this table.
The list of symbols column-types describes the types of each
column.
#t
if the base table associated with base-id was
removed from the low level database lldb, and #f
otherwise.
open-table
. catalog-id will be used as the base table for
the system catalog.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
#t
if symbol names a type allowed as a column
value by the implementation, and #f
otherwise. At a minimum,
an implementation must support the types integer
,
ordinal
, symbol
, string
, and boolean
.
#t
if symbol names a type allowed as a key value
by the implementation, and #f
otherwise. At a minimum, an
implementation must support the types ordinal
, and
symbol
.
An ordinal is an exact positive integer. The other types are standard Scheme.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Any 2 arguments of the supported type passed to the returned function
which are not equal?
must result in returned values which are not
equal?
.
Any 2 lists of supported types (which must at least include symbols and
non-negative integers) passed to the returned function which are not
equal?
must result in returned values which are not
equal?
.
(make-list-keyifier key-dimension types)
.
This procedure returns a key which is equal?
to the
column-numberth element of the list which was passed to create
composite-key. The list types must have at least
key-dimension elements.
(make-list-keyifier key-dimension
types)
. This procedure returns a list of keys which are
elementwise equal?
to the list which was passed to create
composite-key.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In the following functions, the key argument can always be assumed to be the value returned by a call to a keyify routine.
#f
value if there is a row associated with
key in the table opened in handle and #f
otherwise.
#f
otherwise.
make-getter-1
is a new operation. The relational-database
module works with older base-table implementations by using
make-getter
.
#f
otherwise.
index must be larger than key-dimension.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A match-keys argument is a list of length equal to the number of primary keys. The match-keys restrict the actions of the table command to those records whose primary keys all satisfy the corresponding element of the match-keys list. The elements and their actions are:
#f
- The false value matches any key in the corresponding position.
- an object of type procedure
- This procedure must take a single argument, the key in the corresponding position. Any key for which the procedure returns a non-false value is a match; Any key for which the procedure returns a
#f
is not.- other values
- Any other value matches only those keys
equal?
to it.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The key-dimension and column-types arguments are needed to decode the composite-keys for matching with match-keys.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
These operations are optional for a Base-Table implementation.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Each database (in an implementation) has a system catalog which describes all the user accessible tables in that database (including itself).
The system catalog base table has the following fields. PRI
indicates a primary key for that table.
PRI table-name column-limit the highest column number coltab-name descriptor table name bastab-id data base table identifier user-integrity-rule view-procedure A scheme thunk which, when called, produces a handle for the view. coltab and bastab are specified if and only if view-procedure is not. |
Descriptors for base tables (not views) are tables (pointed to by system catalog). Descriptor (base) tables have the fields:
PRI column-number sequential integers from 1 primary-key? boolean TRUE for primary key components column-name column-integrity-rule domain-name |
A primary key is any column marked as primary-key?
in the
corresponding descriptor table. All the primary-key?
columns
must have lower column numbers than any non-primary-key?
columns.
Every table must have at least one primary key. Primary keys must be
sufficient to distinguish all rows from each other in the table. All of
the system defined tables have a single primary key.
A domain is a category describing the allowable values to occur in a column. It is described by a (base) table with the fields:
PRI domain-name foreign-table domain-integrity-rule type-id type-param |
The type-id field value is a symbol. This symbol may be used by the underlying base table implementation in storing that field.
If the foreign-table
field is non-#f
then that field names
a table from the catalog. The values for that domain must match a
primary key of the table referenced by the type-param (or
#f
, if allowed). This package currently does not support
composite foreign-keys.
The types for which support is planned are:
atom symbol string [<length>] number [<base>] money <currency> date-time boolean foreign-key <table-name> expression virtual <expression> |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This object-oriented interface is deprecated for typical database applications; 6.1.1 Using Databases provides an application programmer interface which is easier to understand and use.
Returns a procedure implementing a relational database using the base-table-implementation.
All of the operations of a base table implementation are accessed
through a procedure defined by require
ing that implementation.
Similarly, all of the operations of the relational database
implementation are accessed through the procedure returned by
make-relational-system
. For instance, a new relational database
could be created from the procedure returned by
make-relational-system
by:
What follows are the descriptions of the methods available from
relational system returned by a call to make-relational-system
.
Returns an open, nearly empty relational database associated with
filename. The only tables defined are the system catalog and
domain table. Calling the close-database
method on this database
and possibly other operations will cause filename to be written
to. If filename is #f
a temporary, non-disk based database
will be created if such can be supported by the underlying base table
implelentation. If the database cannot be created as specified
#f
is returned. For the fields and layout of descriptor tables,
6.2.2 Catalog Representation
Returns an open relational database associated with filename. If
mutable? is #t
, this database will have methods capable of
effecting change to the database. If mutable? is #f
, only
methods for inquiring the database will be available. Calling the
close-database
(and possibly other) method on a mutable?
database will cause filename to be written to. If the database
cannot be opened as specified #f
is returned.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This object-oriented interface is deprecated for typical database applications; 6.1.1 Using Databases provides an application programmer interface which is easier to understand and use.
These are the descriptions of the methods available from an open relational database. A method is retrieved from a database by calling the database with the symbol name of the operation. For example:
(define my-database (create-alist-database "mydata.db")) (define telephone-table-desc ((my-database 'create-table) 'telephone-table-desc)) |
#t
is returned. Otherwise, #f
is returned.
close-database
(and
possibly other) method on this database will cause filename to be
written to. If filename is #f
this database will be
changed to a temporary, non-disk based database if such can be supported
by the underlying base table implelentation. If the operations
completed successfully, #t
is returned. Otherwise, #f
is
returned.
#t
is returned.
Otherwise, #f
is returned.
#t
is returned. Otherwise, #f
is returned.
#t
if table-name exists in the system catalog,
otherwise returns #f
.
#f
.
These methods will be present only in mutable databases.
#f
otherwise.
#f
. For the fields and layout of descriptor tables,
See section 6.2.2 Catalog Representation.
#f
.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Balanced binary trees are a useful data structure for maintaining large sets of ordered objects or sets of associations whose keys are ordered. MIT Scheme has an comprehensive implementation of weight-balanced binary trees which has several advantages over the other data structures for large aggregates:
(+ 1 x)
modifies neither the constant 1 nor the value bound to x
. The
trees are referentially transparent thus the programmer need not worry
about copying the trees. Referential transparency allows space
efficiency to be achieved by sharing subtrees.
These features make weight-balanced trees suitable for a wide range of applications, especially those that require large numbers of sets or discrete maps. Applications that have a few global databases and/or concentrate on element-level operations like insertion and lookup are probably better off using hash-tables or red-black trees.
The size of a tree is the number of associations that it contains. Weight balanced binary trees are balanced to keep the sizes of the subtrees of each node within a constant factor of each other. This ensures logarithmic times for single-path operations (like lookup and insertion). A weight balanced tree takes space that is proportional to the number of associations in the tree. For the current implementation, the constant of proportionality is six words per association.
Weight balanced trees can be used as an implementation for either
discrete sets or discrete maps (associations). Sets are implemented by
ignoring the datum that is associated with the key. Under this scheme
if an associations exists in the tree this indicates that the key of the
association is a member of the set. Typically a value such as
()
, #t
or #f
is associated with the key.
Many operations can be viewed as computing a result that, depending on
whether the tree arguments are thought of as sets or maps, is known by
two different names. An example is wt-tree/member?
, which, when
regarding the tree argument as a set, computes the set membership
operation, but, when regarding the tree as a discrete map,
wt-tree/member?
is the predicate testing if the map is defined at
an element in its domain. Most names in this package have been chosen
based on interpreting the trees as sets, hence the name
wt-tree/member?
rather than wt-tree/defined-at?
.
The weight balanced tree implementation is a run-time-loadable option. To use weight balanced trees, execute
(load-option 'wt-tree) |
once before calling any of the procedures defined here.
6.3.1 Construction of Weight-Balanced Trees | ||
6.3.2 Basic Operations on Weight-Balanced Trees | ||
6.3.3 Advanced Operations on Weight-Balanced Trees | ||
6.3.4 Indexing Operations on Weight-Balanced Trees |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Binary trees require there to be a total order on the keys used to arrange the elements in the tree. Weight balanced trees are organized by types, where the type is an object encapsulating the ordering relation. Creating a tree is a two-stage process. First a tree type must be created from the predicate which gives the ordering. The tree type is then used for making trees, either empty or singleton trees or trees from other aggregate structures like association lists. Once created, a tree `knows' its type and the type is used to test compatibility between trees in operations taking two trees. Usually a small number of tree types are created at the beginning of a program and used many times throughout the program's execution.
a
, b
and c
:
(key<? a a) => #f (and (key<? a b) (key<? b a)) => #f (if (and (key<? a b) (key<? b c)) (key<? a c) #t) => #t |
Two key values are assumed to be equal if neither is less than the other by key<?.
Each call to make-wt-tree-type
returns a distinct value, and
trees are only compatible if their tree types are eq?
. A
consequence is that trees that are intended to be used in binary tree
operations must all be created with a tree type originating from the
same call to make-wt-tree-type
.
Number-wt-type
could have been defined by
(define number-wt-type (make-wt-tree-type <)) |
String-wt-type
could have been defined by
(define string-wt-type (make-wt-tree-type string<?)) |
make-wt-tree-type
; the returned tree has this type.
make-wt-tree-type
; the returned tree has this type.
(lambda (type alist) (let ((tree (make-wt-tree type))) (for-each (lambda (association) (wt-tree/add! tree (car association) (cdr association))) alist) tree)) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This section describes the basic tree operations on weight balanced trees. These operations are the usual tree operations for insertion, deletion and lookup, some predicates and a procedure for determining the number of associations in a tree.
#t
if wt-tree contains no associations, otherwise
returns #f
.
#t
if wt-tree contains an association for
key, otherwise returns #f
. The average and worst-case
times required by this operation are proportional to the logarithm of
the number of associations in wt-tree.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In the following the size of a tree is the number of associations that the tree contains, and a smaller tree contains fewer associations.
wt-tree/union
computes the map override of wt-tree-1 by
wt-tree-2. If the trees are viewed as sets the result is the set
union of the arguments.
The worst-case time required by this operation
is proportional to the sum of the sizes of both trees.
If the minimum key of one tree is greater than the maximum key of
the other tree then the time required is at worst proportional to
the logarithm of the size of the larger tree.
wt-tree/intersection
computes the domain restriction of
wt-tree-1 to (the domain of) wt-tree-2.
The time required by this operation is never worse that proportional to
the sum of the sizes of the trees.
#t
iff the key of each association in wt-tree-1 is
the key of some association in wt-tree-2, otherwise returns #f
.
Viewed as a set operation, wt-tree/subset?
is the improper subset
predicate.
A proper subset predicate can be constructed:
(define (proper-subset? s1 s2) (and (wt-tree/subset? s1 s2) (< (wt-tree/size s1) (wt-tree/size s2)))) |
As a discrete map operation, wt-tree/subset?
is the subset
test on the domain(s) of the map(s). In the worst-case the time
required by this operation is proportional to the size of
wt-tree-1.
#t
iff for every association in wt-tree-1 there is
an association in wt-tree-2 that has the same key, and vice
versa.
Viewing the arguments as sets wt-tree/set-equal?
is the set
equality predicate. As a map operation it determines if two maps are
defined on the same domain.
This procedure is equivalent to
(lambda (wt-tree-1 wt-tree-2) (and (wt-tree/subset? wt-tree-1 wt-tree-2 (wt-tree/subset? wt-tree-2 wt-tree-1))) |
In the worst-case the time required by this operation is proportional to the size of the smaller tree.
wt-tree/fold
takes time
proportional to the size of wt-tree.
A sorted association list can be derived simply:
(wt-tree/fold (lambda (key datum list) (cons (cons key datum) list)) '() wt-tree)) |
The data in the associations can be summed like this:
(wt-tree/fold (lambda (key datum sum) (+ sum datum)) 0 wt-tree) |
wt-tree/for-each
takes time proportional to in the size of
wt-tree.
The example prints the tree:
(wt-tree/for-each (lambda (key value) (display (list key value))) wt-tree)) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Weight balanced trees support operations that view the tree as sorted sequence of associations. Elements of the sequence can be accessed by position, and the position of an element in the sequence can be determined, both in logarthmic time.
wt-tree/index
returns the indexth key,
wt-tree/index-datum
returns the datum associated with the
indexth key and wt-tree/index-pair
returns a new pair
(key . datum)
which is the cons
of the
indexth key and its datum. The average and worst-case times
required by this operation are proportional to the logarithm of the
number of associations in the tree.
These operations signal an error if the tree is empty, if
index<0
, or if index is greater than or equal to the
number of associations in the tree.
Indexing can be used to find the median and maximum keys in the tree as follows:
median: (wt-tree/index wt-tree (quotient (wt-tree/size wt-tree) 2)) maximum: (wt-tree/index wt-tree (-1+ (wt-tree/size wt-tree))) |
#f
if the tree
has no association with for key. This procedure returns either an
exact non-negative integer or #f
. The average and worst-case
times required by this operation are proportional to the logarithm of
the number of associations in the tree.
wt-tree/min
returns the least key,
wt-tree/min-datum
returns the datum associated with the least key
and wt-tree/min-pair
returns a new pair (key . datum)
which is the cons
of the minimum key and its datum. The average
and worst-case times required by this operation are proportional to the
logarithm of the number of associations in the tree.
These operations signal an error if the tree is empty. They could be written
(define (wt-tree/min tree) (wt-tree/index tree 0)) (define (wt-tree/min-datum tree) (wt-tree/index-datum tree 0)) (define (wt-tree/min-pair tree) (wt-tree/index-pair tree 0)) |
(wt-tree/delete wt-tree (wt-tree/min wt-tree)) |
(wt-tree/delete! wt-tree (wt-tree/min wt-tree)) |
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |