Next: , Previous: , Up: Database Packages   [Contents][Index]

6.1 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 Base Table implementation.

Why relational database? For motivations and design issues see
http://people.csail.mit.edu/jaffer/DBManifesto.html.


6.1.1 Using Databases

(require 'databases)

This enhancement wraps a utility layer on relational-database which provides:

Database Sharing

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 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 Database Operations. Except for create-database, each procedure will accept either a filename or database procedure for its first argument.

Function: create-database filename base-table-type

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 required 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))

Only alist-table and base-table modules which have been required 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.

Procedure: open-database! rdb base-table-type

Returns mutable open relational database or #f.

Function: open-database rdb base-table-type

Returns an open relational database associated with rdb. The database will be opened with base-table type base-table-type).

Function: open-database rdb

Returns an open relational database associated with rdb. open-database will attempt to deduce the correct base-table-type.

Function: write-database rdb filename

Writes the mutable relational-database rdb to filename.

Function: sync-database rdb

Writes the mutable relational-database rdb to the filename it was opened with.

Function: solidify-database rdb

Syncs rdb and makes it immutable.

Function: close-database rdb

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.

Function: mdbm:report

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

Opening Tables

Function: open-table rdb table-name

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.

Procedure: open-table! rdb table-name

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.

Defining Tables

Function: define-domains rdb row5 …

Adds the domain rows row5 … to the ‘*domains-data*’ table in rdb. The format of the row is given in Catalog Representation.

(define-domains rdb '(permittivity #f complex? c64 #f))
Function: add-domain rdb row5

Use define-domains instead.

Function: define-tables rdb spec-0 …

Adds tables as specified in spec-0 … to the open relational-database rdb. Each spec has the form:

(<name> <descriptor-name> <descriptor-name> <rows>)

or

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

or

(<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.

Listing Tables

Function: list-table-definition rdb table-name

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.


6.1.2 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:

((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.

Operation on relational-table: get column-name

Returns a procedure of arguments key1 key2 … which returns the value for the column-name column of the row associated with primary keys key1, key2 … if that row exists in the table, or #f otherwise.

((plat 'get 'processor) 'djgpp) ⇒ i386
((plat 'get 'processor) 'be-os) ⇒ #f

6.1.2.1 Single Row Operations

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.

Operation on relational-table: row:insert

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.

(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
                                  #\+ #\( #\space #\) #\-)))
                  (string->list d))))
          string))
Operation on relational-table: row:update

Returns a procedure of one argument, row, which 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.

Operation on relational-table: row:retrieve

Returns a procedure of arguments key1 key2 … which returns the row associated with primary keys key1, key2 … if it exists, or #f otherwise.

((plat 'row:retrieve) 'linux) ⇒ (linux i386 linux gcc)
((plat 'row:retrieve) 'multics) ⇒ #f
Operation on relational-table: row:remove

Returns a procedure of arguments key1 key2 … which removes and returns the row associated with primary keys key1, key2 … if it exists, or #f otherwise.

Operation on relational-table: row:delete

Returns a procedure of arguments key1 key2 … which deletes the row associated with primary keys key1, key2 … if it exists. The value returned is unspecified.


6.1.2.2 Match-Keys

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.

Operation on relational-table: get* column-name

Returns a procedure of optional arguments match-key1 … which returns a list of the values for the specified column for all rows in this table. The optional match-key1 … arguments restrict actions to a subset of the table.

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

6.1.2.3 Multi-Row Operations

Operation on relational-table: row:retrieve*

Returns a procedure of optional arguments match-key1 … which returns a list of all rows in this table. The optional match-key1 … arguments restrict actions to a subset of the table. For details see See Match-Keys.

((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 -))
Operation on relational-table: row:remove*

Returns a procedure of optional arguments match-key1 … which removes and returns a list of all rows in this table. The optional match-key1 … arguments restrict actions to a subset of the table.

Operation on relational-table: row:delete*

Returns a procedure of optional arguments match-key1 … which Deletes all rows from this table. The optional match-key1 … arguments restrict deletions to a subset of the table. The value returned is unspecified. The descriptor table and catalog entry for this table are not affected.

Operation on relational-table: for-each-row

Returns a procedure of arguments proc match-key1 … which calls proc with each row in this table. The optional match-key1 … arguments restrict actions to a subset of the table. For details see See Match-Keys.

Note that row:insert* and row:update* do not use match-keys.

Operation on relational-table: row:insert*

Returns a procedure of one argument, rows, which adds each row in the list of rows, 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.

Operation on relational-table: row:update*

Returns a procedure of one argument, rows, which adds each row in the list of rows, 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.


6.1.2.4 Indexed Sequential Access Methods

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.

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)

6.1.2.5 Sequential Index Operations

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.

Operation on relational-table: for-each-row-in-order

Returns a procedure of arguments proc match-key1 … which calls proc with each row in this table in the (implementation-dependent) natural, repeatable ordering for rows. The optional match-key1 … arguments restrict actions to a subset of the table. For details see See Match-Keys.

Operation on relational-table: isam-next

Returns a procedure of arguments key1 key2 … which returns the key-list identifying the lowest record higher than key1 key2 … which is stored in the relational-table; or false if no higher record is present.

Operation on relational-table: isam-next column-name

The symbol column-name names a key field. In the list returned by isam-next, that field, or a field to its left, will be changed. This allows one to skip over less significant key fields.

Operation on relational-table: isam-prev

Returns a procedure of arguments key1 key2 … which returns the key-list identifying the highest record less than key1 key2 … which is stored in the relational-table; or false if no lower record is present.

Operation on relational-table: isam-prev column-name

The symbol column-name names a key field. In the list returned by 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)

6.1.2.6 Table Administration

Operation on relational-table: column-names
Operation on relational-table: column-foreigns
Operation on relational-table: column-domains
Operation on relational-table: 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.

Operation on relational-table: primary-limit

Returns the number of primary keys fields in the relations in this table.

Operation on relational-table: close-table

Subsequent operations to this table will signal an error.


6.1.3 Database Interpolation

(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.

Function: interpolate-from-table table column

Table should be a relational table with one numeric primary key field which supports the 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.


6.1.4 Embedded Commands

(require 'database-commands)

This enhancement wraps a utility layer on relational-database which provides:

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 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

Function: wrap-command-interface rdb

Returns relational database rdb wrapped with additional commands defined in its *commands* table.

Function: add-command-tables rdb

The relational database rdb must be mutable. add-command-tables adds a *command* table to rdb; then returns (wrap-command-interface rdb).

Function: define-*commands* rdb spec-0 …

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> …)

or

((<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> …)
Function: open-command-database filename
Function: open-command-database filename base-table-type

Returns an open enhanced 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-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.

Function: open-command-database! filename
Function: open-command-database! filename base-table-type

Returns mutable open enhanced relational database …

Function: open-command-database database

Returns database if it is an immutable relational database; #f otherwise.

Function: open-command-database! database

Returns database if it is a mutable relational database; #f otherwise.


6.1.4.2 Command Intrinsics

Some commands are defined in all extended relational-databases. The are called just like Database Operations.

Operation on relational-database: 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, See Catalog Representation. Currently, these fields are

  • domain-name
  • foreign-table
  • domain-integrity-rule
  • type-id
  • type-param

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)))
Operation on relational-database: delete-domain domain-name

Removes and returns the domain-name row from the domains table.

Operation on relational-database: domain-checker domain

Returns a procedure to check an argument for conformance to domain domain.


6.1.4.3 Define-tables Example

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.

(require 'databases)
(define my-rdb (create-database "foo.db" 'alist-table))
(define-tables my-rdb
  '(processor-family
    ((family    atom))
    ((also-ran  processor-family))
    ((m68000           #f)
     (m68030           m68000)
     (i386             i8086)
     (i8086            #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    i8086   ms-dos  borland-c)
     (djgpp            i386    ms-dos  gcc)
     (linux            i386    linux   gcc)
     (microsoft-c      i8086   ms-dos  microsoft-c)
     (os/2-emx         i386    os/2    gcc)
     (turbo-c-2        i8086   ms-dos  turbo-c)
     (watcom-9.0       i386    ms-dos  watcom))))

(solidify-database my-rdb)

6.1.4.4 The *commands* Table

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

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 indexth 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.


6.1.4.5 Command Service

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.

types

A list of the type name of each parameter. Correspnds to the type-id field of the contents of the domain of the command’s parameter-table.

defaulters

A list of the defaulters for each parameter. Corresponds to the defaulters 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, See Parameter lists.


6.1.4.6 Command Example

Here is an example of setting up a command with arguments and parsing those arguments from a getopt style argument list (see Getopt).

(require 'database-commands)
(require 'databases)
(require 'getopt-parameters)
(require 'parameters)
(require 'getopt)
(require 'fluid-let)
(require 'printf)

(define my-rdb (add-command-tables (create-database #f 'alist-table)))

(define-tables my-rdb
  '(foo-params
    *parameter-columns*
    *parameter-columns*
    ((1 single-string single string
        (lambda (pl) '("str")) #f "single string")
     (2 nary-symbols nary symbol
        (lambda (pl) '()) #f "zero or more symbols")
     (3 nary1-symbols nary1 symbol
        (lambda (pl) '(symb)) #f "one or more symbols")
     (4 optional-number optional ordinal
        (lambda (pl) '()) #f "zero or one number")
     (5 flag boolean boolean
        (lambda (pl) '(#f)) #f "a boolean flag")))
  '(foo-pnames
    ((name string))
    ((parameter-index ordinal))
    (("s" 1)
     ("single-string" 1)
     ("n" 2)
     ("nary-symbols" 2)
     ("N" 3)
     ("nary1-symbols" 3)
     ("o" 4)
     ("optional-number" 4)
     ("f" 5)
     ("flag" 5)))
  '(my-commands
    ((name symbol))
    ((parameters parameter-list)
     (parameter-names parameter-name-translation)
     (procedure expression)
     (documentation string))
    ((foo
      foo-params
      foo-pnames
      (lambda (rdb) (lambda args (print args)))
      "test command arguments"))))

(define (dbutil:serve-command-line rdb command-table command argv)
  (set! *argv* (if (vector? argv) (vector->list argv) argv))
  ((make-command-server rdb command-table)
   command
   (lambda (comname comval options positions
                    arities types defaulters dirs aliases)
     (apply comval (getopt->arglist options positions
                    arities types defaulters dirs aliases)))))

(define (cmd . opts)
  (fluid-let ((*optind* 1))
    (printf "%-34s ⇒ "
            (call-with-output-string
             (lambda (pt) (write (cons 'cmd opts) pt))))
    (set! opts (cons "cmd" opts))
    (force-output)
    (dbutil:serve-command-line
     my-rdb 'my-commands 'foo (length opts) opts)))

(cmd)                              ⇒ ("str" () (symb) () #f)
(cmd "-f")                         ⇒ ("str" () (symb) () #t)
(cmd "--flag")                     ⇒ ("str" () (symb) () #t)
(cmd "-o177")                      ⇒ ("str" () (symb) (177) #f)
(cmd "-o" "177")                   ⇒ ("str" () (symb) (177) #f)
(cmd "--optional" "621")           ⇒ ("str" () (symb) (621) #f)
(cmd "--optional=621")             ⇒ ("str" () (symb) (621) #f)
(cmd "-s" "speciality")            ⇒ ("speciality" () (symb) () #f)
(cmd "-sspeciality")               ⇒ ("speciality" () (symb) () #f)
(cmd "--single" "serendipity")     ⇒ ("serendipity" () (symb) () #f)
(cmd "--single=serendipity")       ⇒ ("serendipity" () (symb) () #f)
(cmd "-n" "gravity" "piety")       ⇒ ("str" () (piety gravity) () #f)
(cmd "-ngravity" "piety")          ⇒ ("str" () (piety gravity) () #f)
(cmd "--nary" "chastity")          ⇒ ("str" () (chastity) () #f)
(cmd "--nary=chastity" "")         ⇒ ("str" () ( chastity) () #f)
(cmd "-N" "calamity")              ⇒ ("str" () (calamity) () #f)
(cmd "-Ncalamity")                 ⇒ ("str" () (calamity) () #f)
(cmd "--nary1" "surety")           ⇒ ("str" () (surety) () #f)
(cmd "--nary1=surety")             ⇒ ("str" () (surety) () #f)
(cmd "-N" "levity" "fealty")       ⇒ ("str" () (fealty levity) () #f)
(cmd "-Nlevity" "fealty")          ⇒ ("str" () (fealty levity) () #f)
(cmd "--nary1" "surety" "brevity") ⇒ ("str" () (brevity surety) () #f)
(cmd "--nary1=surety" "brevity")   ⇒ ("str" () (brevity surety) () #f)
(cmd "-?")
-|
Usage: cmd [OPTION ARGUMENT ...] ...

  -f, --flag
  -o, --optional[=]<number>
  -n, --nary[=]<symbols> ...
  -N, --nary1[=]<symbols> ...
  -s, --single[=]<string>

ERROR: getopt->parameter-list "unrecognized option" "-?"

6.1.5 Database Macros

(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

Function: within-database database statement-1 …

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. The expressions in ‘within-database’ form are executed in order.

within-database Returns database.

Syntax: define-command (<name> <rdb>) "comment" <expression1> <expression2>
Syntax: define-command (<name> <rdb>) <expression1> <expression2>

Adds to the *commands* table a command <name>:

(lambda (<name> <rdb>) <expression1> <expression2> …)
Syntax: define-table <name> <descriptor-name> <descriptor-name> <rows>
Syntax: define-table <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>)

or

(<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.

Function: add-macro-support database

The relational database database must be mutable. add-macro-support adds a *macros* table and define-macro macro to database; then database is returned.

Syntax: define-macro (<name> arg1 …) "comment" <expression1> <expression2>
Syntax: define-macro (<name> arg1 …) <expression1> <expression2>

Adds a macro <name> to the *macros*.

Note: within-database creates lexical scope where not only define-command and define-table, but every command and macro are defined, ie.:

(within-database my-rdb
  (define-command (message rdb)
    (lambda (msg)
      (display "message: ")
      (display msg)
      (newline)))
  (message "Defining FOO...")
  ;; ... defining FOO ...
  (message "Defining BAR...")
  ;; ... defining BAR ...
  )

6.1.5.2 Within-database Example

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)

6.1.6 Database Browser

(require ’database-browse)

Procedure: browse database

Prints the names of all the tables in database and sets browse’s default to database.

Procedure: browse

Prints the names of all the tables in the default database.

Procedure: browse table-name

For each record of the table named by the symbol table-name, prints a line composed of all the field values.

Procedure: browse pathname

Opens the database named by the string pathname, prints the names of all its tables, and sets browse’s default to the database.

Procedure: browse database table-name

Sets browse’s default to database and prints the records of the table named by the symbol table-name.

Procedure: browse pathname 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.


Next: , Previous: , Up: Database Packages   [Contents][Index]