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
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.
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.
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, 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 'fluid-let)
(require 'parameters)
(require 'getopt)
(define my-rdb (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 uint
(lambda (pl) '()) #f "zero or one number")
(5 flag boolean boolean
(lambda (pl) '(#f)) #f "a boolean flag")))
'(foo-pnames
((name string))
((parameter-index uint))
(("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 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 defaulters dirs aliases)
(apply comval (getopt->arglist
argc argv 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" "-?"
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. 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)))
- 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:
(<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 foriegn-key domain will be created for it.
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
Listing Tables
..............
- Procedure: 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.