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

- Identification of open databases by filename.
- Automatic sharing of open (immutable) databases.
- Automatic loading of base-table package when creating a database.
- Detection and automatic loading of the appropriate base-table package when opening a database.
- Table and data definition from Scheme lists.

#### 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 `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))

Only `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.

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:

- Automatic execution of initialization commands stored in database.
- Transparent execution of database commands stored in `*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 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 `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.

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