This is Info file slib.info, produced by Makeinfo-1.64 from the input file slib.texi. This file documents SLIB, the portable Scheme library. Copyright (C) 1993 Todd R. Eigenschink Copyright (C) 1993, 1994, 1995 Aubrey Jaffer Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies. Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one. Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the author.  File: slib.info, Node: Records, Next: Base Table, Prev: Queues, Up: Data Structures Records ======= `(require 'record)' The Record package provides a facility for user to define their own record data types. - Function: make-record-type TYPE-NAME FIELD-NAMES Returns a "record-type descriptor", a value representing a new data type disjoint from all others. The TYPE-NAME argument must be a string, but is only used for debugging purposes (such as the printed representation of a record of the new type). The FIELD-NAMES argument is a list of symbols naming the "fields" of a record of the new type. It is an error if the list contains any duplicates. It is unspecified how record-type descriptors are represented. - Function: record-constructor RTD [FIELD-NAMES] Returns a procedure for constructing new members of the type represented by RTD. The returned procedure accepts exactly as many arguments as there are symbols in the given list, FIELD-NAMES; these are used, in order, as the initial values of those fields in a new record, which is returned by the constructor procedure. The values of any fields not named in that list are unspecified. The FIELD-NAMES argument defaults to the list of field names in the call to `make-record-type' that created the type represented by RTD; if the FIELD-NAMES argument is provided, it is an error if it contains any duplicates or any symbols not in the default list. - Function: record-predicate RTD Returns a procedure for testing membership in the type represented by RTD. The returned procedure accepts exactly one argument and returns a true value if the argument is a member of the indicated record type; it returns a false value otherwise. - Function: record-accessor RTD FIELD-NAME Returns a procedure for reading the value of a particular field of a member of the type represented by RTD. The returned procedure accepts exactly one argument which must be a record of the appropriate type; it returns the current value of the field named by the symbol FIELD-NAME in that record. The symbol FIELD-NAME must be a member of the list of field-names in the call to `make-record-type' that created the type represented by RTD. - Function: record-modifier RTD FIELD-NAME Returns a procedure for writing the value of a particular field of a member of the type represented by RTD. The returned procedure accepts exactly two arguments: first, a record of the appropriate type, and second, an arbitrary Scheme value; it modifies the field named by the symbol FIELD-NAME in that record to contain the given value. The returned value of the modifier procedure is unspecified. The symbol FIELD-NAME must be a member of the list of field-names in the call to `make-record-type' that created the type represented by RTD. - Function: record? OBJ Returns a true value if OBJ is a record of any type and a false value otherwise. Note that `record?' may be true of any Scheme value; of course, if it returns true for some particular value, then `record-type-descriptor' is applicable to that value and returns an appropriate descriptor. - Function: record-type-descriptor RECORD Returns a record-type descriptor representing the type of the given record. That is, for example, if the returned descriptor were passed to `record-predicate', the resulting predicate would return a true value when passed the given record. Note that it is not necessarily the case that the returned descriptor is the one that was passed to `record-constructor' in the call that created the constructor procedure that created the given record. - Function: record-type-name RTD Returns the type-name associated with the type represented by rtd. The returned value is `eqv?' to the TYPE-NAME argument given in the call to `make-record-type' that created the type represented by RTD. - Function: record-type-field-names RTD Returns a list of the symbols naming the fields in members of the type represented by RTD. The returned value is `equal?' to the field-names argument given in the call to `make-record-type' that created the type represented by RTD.  File: slib.info, Node: Base Table, Next: Relational Database, Prev: Records, Up: Data Structures Base Table ========== A base table implementation using Scheme association lists is available as the value of the identifier `alist-table' after doing: (require 'alist-table) Association list base tables are suitable for small databases and support all Scheme types when temporary and readable/writeable Scheme types when saved. I hope support for other base table implementations will be added in the future. This rest of this section documents the interface for a base table implementation from which the *Note Relational Database:: package constructs a Relational system. It will be of interest primarily to those wishing to port or write new base-table implementations. 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 open-base (alist-table 'make-base)) make-base => *a procedure* (define foo (alist-table 'foo)) foo => #f - Function: make-base FILENAME KEY-DIMENSION COLUMN-TYPES Returns a new, open, low-level database (collection of tables) associated with FILENAME. This returned database has an empty table associated with CATALOG-ID. The positive integer KEY-DIMENSION is the number of keys composed to make a PRIMARY-KEY for the catalog table. The list of symbols COLUMN-TYPES describes the types of each column for that table. If the database cannot be created as specified, `#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. - Function: open-base FILENAME MUTABLE Returns an open low-level 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. 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. - Function: write-base LLDB FILENAME Causes the low-level database LLDB to be written to FILENAME. If the write is successful, also causes LLDB to henceforth be associated with FILENAME. Calling the `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. - Function: sync-base LLDB Causes the file associated with the low-level database LLDB to be updated to reflect its current state. If the associated filename is `#f', no action is taken and `#f' is returned. If this operation completes successfully, `#t' is returned. Otherwise, `#f' is returned. - Function: close-base LLDB Causes the low-level database LLDB to be written to its associated file (if any). If the write is successful, subsequent operations to LLDB will signal an error. If the operations complete successfully, `#t' is returned. Otherwise, `#f' is returned. - Function: make-table LLDB KEY-DIMENSION COLUMN-TYPES Returns the BASE-ID for a new base table, otherwise returns `#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. - Constant: catalog-id A constant BASE-ID suitable for passing as a parameter to `open-table'. CATALOG-ID will be used as the base table for the system catalog. - Function: open-table LLDB BASE-ID KEY-DIMENSION COLUMN-TYPES Returns a HANDLE for an existing base table in the low-level database LLDB if that table exists and can be opened in the mode indicated by MUTABLE?, otherwise returns `#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. - Function: kill-table LLDB BASE-ID KEY-DIMENSION COLUMN-TYPES Returns `#t' if the base table associated with BASE-ID was removed from the low level database LLDB, and `#f' otherwise. - Function: make-keyifier-1 TYPE Returns a procedure which accepts a single argument which must be of type TYPE. This returned procedure returns an object suitable for being a KEY argument in the functions whose descriptions follow. 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?'. - Function: make-list-keyifier KEY-DIMENSION TYPES The list of symbols TYPES must have at least KEY-DIMENSION elements. Returns a procedure which accepts a list of length KEY-DIMENSION and whose types must corresopond to the types named by TYPES. This returned procedure combines the elements of its list argument into an object suitable for being a KEY argument in the functions whose descriptions follow. 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?'. - Function: make-key-extractor KEY-DIMENSION TYPES COLUMN-NUMBER Returns a procedure which accepts objects produced by application of the result of `(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 COMBINED-KEY. The list TYPES must have at least KEY-DIMENSION elements. - Function: make-key->list KEY-DIMENSION TYPES Returns a procedure which accepts objects produced by application of the result of `(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 COMBINED-KEY. In the following functions, the KEY argument can always be assumed to be the value returned by a call to a *keyify* routine. - Function: for-each-key HANDLE PROCEDURE Calls PROCEDURE once with each KEY in the table opened in HANDLE in an unspecified order. An unspecified value is returned. - Function: map-key HANDLE PROCEDURE Returns a list of the values returned by calling PROCEDURE once with each KEY in the table opened in HANDLE in an unspecified order. - Function: ordered-for-each-key HANDLE PROCEDURE Calls PROCEDURE once with each KEY in the table opened in HANDLE in the natural order for the types of the primary key fields of that table. An unspecified value is returned. - Function: present? HANDLE KEY Returns a non-`#f' value if there is a row associated with KEY in the table opened in HANDLE and `#f' otherwise. - Function: delete HANDLE KEY Removes the row associated with KEY from the table opened in HANDLE. An unspecified value is returned. - Function: make-getter KEY-DIMENSION TYPES Returns a procedure which takes arguments HANDLE and KEY. This procedure returns a list of the non-primary values of the relation (in the base table opened in HANDLE) whose primary key is KEY if it exists, and `#f' otherwise. - Function: make-putter KEY-DIMENSION TYPES Returns a procedure which takes arguments HANDLE and KEY and VALUE-LIST. This procedure associates the primary key KEY with the values in VALUE-LIST (in the base table opened in HANDLE) and returns an unspecified value. - Function: supported-type? SYMBOL Returns `#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', `symbol', `string', `boolean', and `base-id'. - Function: supported-key-type? SYMBOL Returns `#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 `integer', and `symbol'. `integer' Scheme exact integer. `symbol' Scheme symbol. `boolean' `#t' or `#f'. `base-id' Objects suitable for passing as the BASE-ID parameter to `open-table'. The value of CATALOG-ID must be an acceptable `base-id'.  File: slib.info, Node: Relational Database, Next: Weight-Balanced Trees, Prev: Base Table, Up: Data Structures Relational Database =================== `(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 *Note Base Table:: implementation. * Menu: * Motivations:: Database Manifesto * Creating and Opening Relational Databases:: * Relational Database Operations:: * Table Operations:: * Catalog Representation:: * Unresolved Issues:: * Database Utilities:: 'database-utilities  File: slib.info, Node: Motivations, Next: Creating and Opening Relational Databases, Prev: Relational Database, Up: Relational Database Motivations ----------- Most nontrivial programs contain databases: Makefiles, configure scripts, file backup, calendars, editors, source revision control, CAD systems, display managers, menu GUIs, games, parsers, debuggers, profilers, and even error reporting are all rife with databases. Coding databases is such a common activity in programming that many may not be aware of how often they do it. A database often starts as a dispatch in a program. The author, perhaps because of the need to make the dispatch configurable, the need for correlating dispatch in other routines, or because of changes or growth, devises a data structure to contain the information, a routine for interpreting that data structure, and perhaps routines for augmenting and modifying the stored data. The dispatch must be converted into this form and tested. The programmer may need to devise an interactive program for enabling easy examination and modification of the information contained in this database. Often, in an attempt to foster modularity and avoid delays in release, intermediate file formats for the database information are devised. It often turns out that users prefer modifying these intermediate files with a text editor to using the interactive program in order to do operations (such as global changes) not forseen by the program's author. In order to address this need, the concientous software engineer may even provide a scripting language to allow users to make repetitive database changes. Users will grumble that they need to read a large manual and learn yet another programming language (even if it *almost* has language "xyz" syntax) in order to do simple configuration. All of these facilities need to be designed, coded, debugged, documented, and supported; often causing what was very simple in concept to become a major developement project. This view of databases just outlined is somewhat the reverse of the view of the originators of the "Relational Model" of database abstraction. The relational model was devised to unify and allow interoperation of large multi-user databases running on diverse platforms. A fairly general purpose "Comprehensive Language" for database manipulations is mandated (but not specified) as part of the relational model for databases. One aspect of the Relational Model of some importance is that the "Comprehensive Language" must be expressible in some form which can be stored in the database. This frees the programmer from having to make programs data-driven in order to use a database. This package includes as one of its basic supported types Scheme "expression"s. This type allows expressions as defined by the Scheme standards to be stored in the database. Using `slib:eval' retrieved expressions can be evaluated (in the top-level environment). Scheme's `lambda' facilitates closure of environments, modularity, etc. so that procedures (which could not be stored directly most databases) can still be effectively retrieved. Since `slib:eval' evaluates expressions in the top-level environment, built-in and user defined procedures can be easily accessed by name. This package's purpose is to standardize (through a common interface) database creation and usage in Scheme programs. The relational model's provision for inclusion of language expressions as data as well as the description (in tables, of course) of all of its tables assures that relational databases are powerful enough to assume the roles currently played by thousands of ad-hoc routines and data formats. Such standardization to a relational-like model brings many benefits: * Tables, fields, domains, and types can be dealt with by name in programs. * The underlying database implementation can be changed (for performance or other reasons) by changing a single line of code. * The formats of tables can be easily extended or changed without altering code. * Consistency checks are specified as part of the table descriptions. Changes in checks need only occur in one place. * All the configuration information which the developer wishes to group together is easily grouped, without needing to change programs aware of only some of these tables. * Generalized report generators, interactive entry programs, and other database utilities can be part of a shared library. The burden of adding configurability to a program is greatly reduced. * Scheme is the "comprehensive language" for these databases. Scripting for configuration no longer needs to be in a separate language with additional documentation. * Scheme's latent types mesh well with the strict typing and logical requirements of the relational model. * Portable formats allow easy interchange of data. The included table descriptions help prevent misinterpretation of format.  File: slib.info, Node: Creating and Opening Relational Databases, Next: Relational Database Operations, Prev: Motivations, Up: Relational Database Creating and Opening Relational Databases ----------------------------------------- - Function: make-relational-system BASE-TABLE-IMPLEMENTATION 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: (require 'alist-table) (define relational-alist-system (make-relational-system alist-table)) (define create-alist-database (relational-alist-system 'create-database)) (define my-database (create-alist-database "mydata.db")) What follows are the descriptions of the methods available from relational system returned by a call to `make-relational-system'. - Function: create-database FILENAME 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, *Note Catalog Representation:: - Function: open-database FILENAME MUTABLE? 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.  File: slib.info, Node: Relational Database Operations, Next: Table Operations, Prev: Creating and Opening Relational Databases, Up: Relational Database Relational Database Operations ------------------------------ 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)) - Function: close-database Causes the relational database to be written to its associated file (if any). If the write is successful, subsequent operations to this database will signal an error. If the operations completed successfully, `#t' is returned. Otherwise, `#f' is returned. - Function: write-database FILENAME Causes the relational database to be written to FILENAME. If the write is successful, also causes the database to henceforth be associated with FILENAME. Calling the `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. - Function: table-exists? TABLE-NAME Returns `#t' if TABLE-NAME exists in the system catalog, otherwise returns `#f'. - Function: open-table TABLE-NAME MUTABLE? Returns a "methods" procedure for an existing relational table in this database if it exists and can be opened in the mode indicated by MUTABLE?, otherwise returns `#f'. These methods will be present only in databases which are MUTABLE?. - Function: delete-table TABLE-NAME Removes and returns the TABLE-NAME row from the system catalog if the table or view associated with TABLE-NAME gets removed from the database, and `#f' otherwise. - Function: create-table TABLE-DESC-NAME Returns a methods procedure for a new (open) relational table for describing the columns of a new base table in this database, otherwise returns `#f'. For the fields and layout of descriptor tables, *Note Catalog Representation::. - Function: create-table TABLE-NAME TABLE-DESC-NAME Returns a methods procedure for a new (open) relational table with columns as described by TABLE-DESC-NAME, otherwise returns `#f'. - Function: create-view ?? - Function: project-table ?? - Function: restrict-table ?? - Function: cart-prod-tables ?? Not yet implemented.  File: slib.info, Node: Table Operations, Next: Catalog Representation, Prev: Relational Database Operations, Up: Relational Database Table Operations ---------------- 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: (define telephone-table-desc ((my-database 'create-table) 'telephone-table-desc)) (require 'common-list-functions) (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)) Operations on a single column of a table are retrieved by giving the column name as the second argument to the methods procedure. For example: (define column-ids ((telephone-table-desc 'get* 'column-number))) 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. 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 may not be missing. - Function: get KEY1 KEY2 ... Returns the value for the specified column of the row associated with primary keys KEY1, KEY2 ... if it exists, or `#f' otherwise. - Function: get* Returns a list of the values for the specified column for all rows in this table. - Function: row:retrieve KEY1 KEY2 ... Returns the row associated with primary keys KEY1, KEY2 ... if it exists, or `#f' otherwise. - Function: row:retrieve* Returns a list of all rows in this table. - Function: row:remove KEY1 KEY2 ... Removes and returns the row associated with primary keys KEY1, KEY2 ... if it exists, or `#f' otherwise. - Function: row:remove* Removes and returns a list of all rows in this table. - Function: row:delete KEY1 KEY2 ... Deletes the row associated with primary keys KEY1, KEY2 ... if it exists. The value returned is unspecified. - Function: row:delete* Deletes all rows in this table. The value returned is unspecified. The descriptor table and catalog entry for this table are not affected. - Function: row:update ROW Adds the row, ROW, to this table. If a row for the primary key(s) specified by ROW already exists in this table, it will be overwritten. The value returned is unspecified. - Function: row:update* ROWS Adds each row in the list ROWS, to this table. If a row for the primary key specified by an element of ROWS already exists in this table, it will be overwritten. The value returned is unspecified. - Function: row:insert ROW Adds the row ROW to this table. If a row for the primary key(s) specified by ROW already exists in this table an error is signaled. The value returned is unspecified. - Function: row:insert* ROWS Adds each row in the list ROWS, to this table. If a row for the primary key specified by an element of ROWS already exists in this table, an error is signaled. The value returned is unspecified. - Function: for-each-row PROC Calls PROC with each ROW in this table in the natural ordering for the primary key types. *Real* relational programmers would use some least-upper-bound join for every row to get them in order; But we don't have joins yet. - Function: close-table Subsequent operations to this table will signal an error. - Constant: column-names - Constant: column-foreigns - Constant: column-domains - Constant: column-types Return a list of the column names, foreign-key table names, domain names, or type names respectively for this table. These 4 methods are different from the others in that the list is returned, rather than a procedure to obtain the list. - Constant: primary-limit Returns the number of primary keys fields in the relations in this table.  File: slib.info, Node: Catalog Representation, Next: Unresolved Issues, Prev: Table Operations, Up: Relational Database Catalog Representation ---------------------- 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. This package currently supports tables having from 1 to 4 primary keys if there are non-primary columns, and any (natural) number if *all* columns are primary keys. If you need more than 4 primary keys, I would like to hear what you are doing! 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 [] number [] money date-time boolean foreign-key expression virtual  File: slib.info, Node: Unresolved Issues, Next: Database Utilities, Prev: Catalog Representation, Up: Relational Database Unresolved Issues ----------------- Although `rdms.scm' is not large I found it very difficult to write (six rewrites). I am not aware of any other examples of a generalized relational system (although there is little new in CS). I left out several aspects of the Relational model in order to simplify the job. The major features lacking (which might be addressed portably) are views, transaction boundaries, and protection. Protection needs a model for specifying priveledges. Given how operations are accessed from handles it should not be difficult to restrict table accesses to those allowed for that user. The system catalog has a field called `view-procedure'. This should allow a purely functional implementation of views. This will work but is unsatisfying for views resulting from a "select"ion (subset of rows); for whole table operations it will not be possible to reduce the number of keys scanned over when the selection is specified only by an opaque procedure. Transaction boundaries present the most intriguing area. Transaction boundaries are actually a feature of the "Comprehensive Language" of the Relational database and not of the database. Scheme would seem to provide the opportunity for an extremely clean semantics for transaction boundaries since the builtin procedures with side effects are small in number and easily identified. These side-effect builtin procedures might all be portably redefined to versions which properly handled transactions. Compiled library routines would need to be recompiled as well. Many system extensions (delete-file, system, etc.) would also need to be redefined. There are 2 scope issues that must be resolved for multiprocess transaction boundaries: Process scope The actions captured by a transaction should be only for the process which invoked the start of transaction. Although standard Scheme does not provide process primitives as such, `dynamic-wind' would provide a workable hook into process switching for many implementations. Shared utilities with state Some shared utilities have state which should *not* be part of a transaction. An example would be calling a pseudo-random number generator. If the success of a transaction depended on the pseudo-random number and failed, the state of the generator would be set back. Subsequent calls would keep returning the same number and keep failing. Pseudo-random number generators are not reentrant and so would require locks in order to operate properly in a multiprocess environment. Are all examples of utilities whose state should not part of transactions also non-reentrant? If so, perhaps suspending transaction capture for the duration of locks would fix it.  File: slib.info, Node: Database Utilities, Prev: Unresolved Issues, Up: Relational Database Database Utilities ------------------ `(require 'database-utilities)' This enhancement wraps a utility layer on `relational-database' which provides: * Automatic loading of the appropriate base-table package when opening a database. * Automatic execution of initialization commands stored in database. * Transparent execution of database commands stored in `*commands*' table in database. Also included are utilities which provide: * Data definition from Scheme lists and * Report generation for any SLIB relational database. - Function: create-database FILENAME BASE-TABLE-TYPE Returns an open, nearly empty enhanced (with `*commands*' table) relational database (with base-table type BASE-TABLE-TYPE) associated with FILENAME. - Function: open-database FILENAME - Function: open-database FILENAME BASE-TABLE-TYPE Returns an open enchanced relational database associated with FILENAME. The database will be opened with base-table type BASE-TABLE-TYPE) if supplied. If BASE-TABLE-TYPE is not supplied, `open-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. - Function: open-database! FILENAME - Function: open-database! FILENAME BASE-TABLE-TYPE Returns *mutable* open enchanced relational database ... 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 uint name symbol arity parameter-arity domain domain default expression documentation string The `arity' field can take the values: `single' Requires a single parameter of the specified domain. `optional' A single parameter of the specified domain or zero parameters is acceptable. `boolean' A single boolean parameter or zero parameters (in which case `#f' is substituted) is acceptable. `nary' Any number of parameters of the specified domain are acceptable. The argument passed to the command function is always a list of the parameters. `nary1' One or more of parameters of the specified domain are acceptable. The argument passed to the command function is always a list of the parameters. The `domain' field specifies the domain which a parameter or parameters in the `index'th field must satisfy. The `default' field is an expression whose value is either `#f' or a procedure of no arguments which returns a parameter or parameter list as appropriate. If the expression's value is `#f' then no default is appropriate for this parameter. Note that since the `default' 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. Invoking Commands ................. 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 *Note Relational 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. - Function: make-command-server RDB TABLE-NAME Returns a procedure of 2 arguments, a (symbol) command and a call-back procedure. When this returned procedure is called, it looks up COMMAND in table TABLE-NAME and calls the call-back procedure with arguments: COMMAND The COMMAND COMMAND-VALUE The result of evaluating the expression in the PROCEDURE field of TABLE-NAME and calling it with RDB. PARAMETER-NAME A list of the "official" name of each parameter. Corresponds to the `name' field of the COMMAND's parameter-table. POSITIONS A list of the positive integer index of each parameter. Corresponds to the `index' field of the COMMAND's parameter-table. ARITIES A list of the arities of each parameter. Corresponds to the `arity' field of the COMMAND's parameter-table. For a description of `arity' see table above. DEFAULTS A list of the defaults for each parameter. Corresponds to the `defaults' field of the COMMAND's parameter-table. DOMAIN-INTEGRITY-RULES A list of procedures (one for each parameter) which tests whether a value for a parameter is acceptable for that parameter. The procedure should be called with each datum in the list for `nary' arity parameters. ALIASES A list of lists of `(alias parameter-name)'. There can be more than one alias per PARAMETER-NAME. For information about parameters, *Note Parameter lists::. Here is an example of setting up a command with arguments and parsing those arguments from a `getopt' style argument list (*note Getopt::.). (require 'database-utilities) (require 'parameters) (require 'getopt) (define my-rdb (create-database #f 'alist-table)) (define-tables my-rdb '(foo-params *parameter-columns* *parameter-columns* ((1 first-argument single string "hithere" "first argument") (2 flag boolean boolean #f "a flag"))) '(foo-pnames ((name string)) ((parameter-index uint)) (("l" 1) ("a" 2))) '(my-commands ((name symbol)) ((parameters parameter-list) (parameter-names parameter-name-translation) (procedure expression) (documentation string)) ((foo foo-params foo-pnames (lambda (rdb) (lambda (foo aflag) (print foo aflag))) "test command arguments")))) (define (dbutil:serve-command-line rdb command-table command argc argv) (set! argv (if (vector? argv) (vector->list argv) argv)) ((make-command-server rdb command-table) command (lambda (comname comval options positions arities types defaults dirs aliases) (apply comval (getopt->arglist argc argv options positions arities types defaults dirs aliases))))) (define (test) (set! *optind* 1) (dbutil:serve-command-line my-rdb 'my-commands 'foo 4 '("dummy" "-l" "foo" "-a"))) (test) -| "foo" #t Some commands are defined in all extended relational-databases. The are called just like *Note Relational Database Operations::. - Function: add-domain DOMAIN-ROW Adds DOMAIN-ROW to the "domains" table if there is no row in the domains table associated with key `(car DOMAIN-ROW)' and returns `#t'. Otherwise returns `#f'. For the fields and layout of the domain table, *Note Catalog Representation:: - Function: delete-domain DOMAIN-NAME Removes and returns the DOMAIN-NAME row from the "domains" table. - Function: domain-checker DOMAIN Returns a procedure to check an argument for conformance to domain DOMAIN. Defining Tables --------------- - Procedure: define-tables RDB SPEC-0 ... Adds tables as specified in SPEC-0 ... to the open relational-database RDB. Each SPEC has the form: ( ) or ( ) where is the table name, is the symbol name of a descriptor table, and describe the primary keys and other fields respectively, and is a list of data rows to be added to the table. and are lists of field descriptors of the form: ( ) or ( ) where is the column name, is the domain of the column, and is an expression whose value is a procedure of one argument (and returns non-`#f' to signal an error). If 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 foriegn-key domain will be created for it. - Procedure: create-report RDB DESTINATION REPORT-NAME TABLE - Procedure: create-report RDB DESTINATION REPORT-NAME The symbol REPORT-NAME must be primary key in the table named `*reports*' in the relational database RDB. DESTINATION is a port, string, or symbol. If DESTINATION is a: port The table is created as ascii text and written to that port. string The table is created as ascii text and written to the file named by DESTINATION. symbol DESTINATION is the primary key for a row in the table named *printers*. Each row in the table *reports* has the fields: name The report name. default-table The table to report on if none is specified. header, footer A `format' string. At the beginning and end of each page respectively, `format' is called with this string and the (list of) column-names of this table. reporter A `format' string. For each row in the table, `format' is called with this string and the row. minimum-break The minimum number of lines into which the report lines for a row can be broken. Use `0' if a row's lines should not be broken over page boundaries. Each row in the table *printers* has the fields: name The printer name. print-procedure The procedure to call to actually print. The report is prepared as follows: `Format' (*note Format::.) is called with the `header' field and the (list of) `column-names' of the table. `Format' is called with the `reporter' field and (on successive calls) each record in the natural order for the table. A count is kept of the number of newlines output by format. When the number of newlines to be output exceeds the number of lines per page, the set of lines will be broken if there are more than `minimum-break' left on this page and the number of lines for this row is larger or equal to twice `minimum-break'. `Format' is called with the `footer' field and the (list of) `column-names' of the table. The footer field should not output a newline. A new page is output. This entire process repeats until all the rows are output. 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 command `define-tables' is defined to call `define-tables' with its arguments. The database is also configured to print `Welcome' when the database is opened. The database is then closed and reopened. (require 'database-utilities) (define my-rdb (create-database "foo.db" 'alist-table)) (define-tables my-rdb '(*commands* ((name symbol)) ((parameters parameter-list) (procedure expression) (documentation string)) ((define-tables no-parameters no-parameter-names (lambda (rdb) (lambda specs (apply define-tables rdb specs))) "Create or Augment tables from list of specs") (*initialize* no-parameters no-parameter-names (lambda (rdb) (display "Welcome") (newline) rdb) "Print Welcome")))) ((my-rdb 'define-tables) '(processor-family ((family atom)) ((also-ran processor-family)) ((m68000 #f) (m68030 m68000) (i386 8086) (8086 #f) (powerpc #f))) '(platform ((name symbol)) ((processor processor-family) (os symbol) (compiler symbol)) ((aix powerpc aix -) (amiga-dice-c m68000 amiga dice-c) (amiga-aztec m68000 amiga aztec) (amiga-sas/c-5.10 m68000 amiga sas/c) (atari-st-gcc m68000 atari gcc) (atari-st-turbo-c m68000 atari turbo-c) (borland-c-3.1 8086 ms-dos borland-c) (djgpp i386 ms-dos gcc) (linux i386 linux gcc) (microsoft-c 8086 ms-dos microsoft-c) (os/2-emx i386 os/2 gcc) (turbo-c-2 8086 ms-dos turbo-c) (watcom-9.0 i386 ms-dos watcom)))) ((my-rdb 'close-database)) (set! my-rdb (open-database "foo.db" 'alist-table)) -| Welcome