The Database Explorer


Overview


This is a project of my own, started for myself. I came to it in my usual irregegular way. As the amount of data of my harddisk kept growing I started to worry about retrieval of important information or keeping track of urls, activities to be done and the like. Currently glimpse [1] serves well enough, but for the future ? So I decided to get a free RDBMS and start working on databases to keep track of my books, netscape bookmarks, addresses, music, money, etc. Now having MySQL [2] installed (and LinuxThreads, new libc, dynamic linker, compiler, binutils, ... :-) I had a change of heart and decided to work on a tool helping me to manage the RDBMS instead before starting implementation of the databases itself.

Requirements


Develop an application which is able to connect to an relational database management system (RDBMS) and display and manipulate its contents (databases, tables, columns and indices) in graphical manner. The user interface shall be independent of the specific RDBMS it is connected to and reconfigure itself to its abilities.

Why this ? Well, although this project started because of my own needs I decided early on to make it available on the net for other people. And although I am using MySQL these might see it as too simple or have other reasons to opt for a different system (like Sybase, Oracle, whatever). Intertwining user interface and database access will cause them to go through the entire application to fit it to their need, that is, if they don't remove it from their list of possible choices altogether. The modularization on the other hand will constrain all changes to a single module, making an adaption to another RDBMS easy.

The application shall at least allow the following operations:

  1. Creation of a new database.
  2. Creation of a new table in an existing database.
  3. Creation of a new column in an existing table.
  4. Destruction of an existing database.
  5. Destruction of an existing table.
  6. Destruction of an existing column.
  7. Changing the definition of an existing column.

These are the operations defined by the SQL-92 standard [3]. Not supporting them is a major flaw of any application dealing with the DDL part of SQL.

If supported by the underlying RDBMS or synthesizable otherwise the following operations shall be possible too:

  1. Creation of a new index in an existing table.
  2. Destruction of an existing index.
  3. Changing the definition of an existing table.
  4. Changing the definition of an existing index.
  5. Changing the name of an existing database.
  6. Changing the name of an existing table.
  7. Changing the name of an existing column.
  8. Changing the name of an existing index.

The operation ' Changing the definition of an existing database ' (which might be listed for symmetry) requires no implementation as it is sufficiently covered by the operations listed above.

All of the operations above deal with the structure of databases, tables, etc. In a second phase the application shall be extended to allow

  1. access to the different privilege systems of the various RDBMS's thus giving the user the ability to grant and revoke privileges on databases and tables.
  2. save and restore of the database structure or part of it in external files, either as SQL scripts or in a generic way (tcl scripts).
  3. save and restore of the database contents or part of it in external files, either as SQL scripts or in a generic way (tcl scripts). Here we are talking about the contents of the database tables.
  4. immediate entering of sql command in a console window. This window shall display the sql commands generated by the application in response to user operations in the graphical database display too, thus giving the user insight into the SQL DDL commands and teaching him in a friendly and non-obvious way.

In a third phase the application might be extended with a graphical editor to draw and manipulate entity-relationship diagrams, thus pushing the application from the depths of simple database manipulation into the higher realms of database design. As an alternative the editor can be implemented as a separate application, but then must have the ability to connect to and control the explorer. In addition the editor has to start the explorer if it is currently not running.

Design


First some general notes.

Because of the requirement to connect to arbitrary RDBMS the application is divided into 2 parts, the database independent user interface and a database dependent access layer. Just changing the access layer will allow connections to a different RDBMS. See
Figure 9 Screenshot of the database creation dialog

Figure 10 Screenshot of the table creation dialog

Figure 11 Screenshot of the table alter dialog

Figure 12 Screenshot of the column creation dialog

Figure 13 Screenshot of the column alter dialog
Some functionality used by the dialogs and the detail-view was wrapped into their own widgets to reduce their complexity and facilitate sharing of code. They are

  1. A widget to display column types, this one is used by the detail-view
  2. A widget to specify column types, used in the column dialogs and the table editor.
  3. An index editor widget, used by the table editor
  4. A table editor, used by the table dialogs.

The complete relationships between the UI classes is like this:


Figure 14 The relationships between all UI classes

Code

The code described in the following sections is the DBMS Explorer as designed and described in the last 2 sections.

The application is complete with respect to phase I and the features provided by MySQL. The only operations not realized were listed in the section of optional commands, they are:

  1. Creation of a new index in an existing table.
  2. Changing the definition of an existing index.
  3. Changing the name of an existing database.
  4. Changing the name of an existing index.

These are not realized in the user interface as well.

About 90 percent of the code is written in a pure tcl OO extension originally invented by Sam Shen and then modified by myself. Three types of objects can be specified:

  1. non visual objects,
  2. visual objects, aka widgets
  3. and dialogs

The object representation is from the days of tcl 7.3, every instance of a class is realized as a global array and a dispatch procedure having the same name. The array is imported automatically into all member procedures (methods) via upvar, under the name slot. The procedure local variable self contains the name of the object. The second variable is used to call other methods of the object from within the procedure.

An example:
object_class example {
member foo

method hi {what} {
puts "hi $what"
}

method bar {} {
# slot = array containing instance variables.
# self = variable containing the name of the object.

# access to instance variable 'bar', defined
# by the 'member'-statement above.
set slot(bar) zet

# call of member procedure 'hi'.
$self hi ho
}
}

Two more notes before going into the code:
  1. It is not necessary to declare an instance variable via member. Just setting or reading a value into/from slot with the desired key is sufficient. It is bad style, but unavoidable in case of managing dynamically created data. A good example of this is the implementation of the class dbCache in the DBI layer.
  2. The used OO-system does not distinguish between private and public methods. Because of that I am using the convention to prefix all private methods with an underscore character.

File code/ne.tcl

# -*- tcl -*-
#		dbmsExplorer @mFullVersion@, as of @mDate@
#		Node / entry operations
#
# CVS: $Id: dbmsExplorer.rtf,v 1.1.1.1 1998/01/29 21:19:22 aku Exp $
#
# @c This file contains operations to construct widget independent
# @c descriptions of the current selection, to convert such
# @c descriptions into treeview-entries and vice versa and to access
# @c the information stored in them in a structure independent manner.
#
# @s Operations on nodes and entries.
# @i node operations, entry operations
# -----------------------------


proc nodeRoot {} {
    # @c Generates the description of the root node.

    return {root {}}
}


proc nodeDb {dbName} {
    # @c Generates the description of a database node.
    #
    # @argument dbName: Name of the database managed by the node.

    return [list db $dbName]
}


proc nodeTable {dbName tableName} {
    # @c Generate the description of a table node.
    #
    # @argument dbName:    Name of the database the table is part of.
    # @argument tableName: Name of the table managed by the node.

    return [list table [list $dbName $tableName]]
}


proc nodeCol {dbName tableName} {
    # @c Generates the description of the parent node to all column
    # @c nodes.
    #
    # @argument dbName:    Name of the database the table is part of.
    # @argument tableName: Name of the table managed by the node.

    return [list col [list $dbName $tableName]]
}


proc nodeIdx {dbName tableName} {
    # @c Generates the description of the parent node to all index
    # @c nodes.
    #
    # @argument dbName:    Name of the database the table is part of.
    # @argument tableName: Name of the table managed by the node.

    return [list idx [list $dbName $tableName]]
}


proc nodeColumn {dbName tableName colName} {
    # @c Generates the description of a column node.
    #
    # @argument dbName:    Name of the database the column is part of.
    # @argument tableName: Name of the table the column is part of.
    # @argument colName:   Name of the column managed by the node.

    return [list colEntry [list $dbName $tableName $colName]]
}


proc nodeIndex {dbName tableName idxName} {
    # @c Generates the description of an index node.
    #
    # @argument dbName:    Name of the database the index is part of.
    # @argument tableName: Name of the table the index is part of.
    # @argument idxName:   Name of the index managed by the node.

    return [list idxEntry [list $dbName $tableName $idxName]]
}


proc nodeIndexColumn {dbName tableName idxName colName} {
    # @c Generates the description of a column node below an index
    # @c node.
    #
    # @argument dbName:    Name of the database the column is part of.
    # @argument tableName: Name of the table the column is part of.
    # @argument idxName:   Name of the index the column is part of.
    # @argument colName:   Name of the column managed by the node.

    return [list idxColEntry \
        [list $dbName $tableName $idxName $colName]]
}


proc node2Entry {node} {
    # @c Converts the given  description into a path suitable
    # @c for an entry in a TixHList widget (| is separator). Can be
    # @c used immediately by the TixHList contained in instances of
    # @c .
    #
    # @argument node: The node-description to examine.

    # First, extract all available information from the node.
    # Some variables will empty, dependent on the type of the node.

    set type    [nodeType $node]
    set node    [nodeInfo $node]

    set db      [lindex $node 0]
    set table   [lindex $node 1]
    set detail  [lindex $node 2]
    set detaila [lindex $node 3]

    # After that construct the entry path.

    switch -- $type {
	root        {return Databases}
	db          {return Databases|$db}
	table       {return Databases|$db|$table}
	col         {return Databases|$db|$table|col}
	idx         {return Databases|$db|$table|idx}
	colEntry    {return Databases|$db|$table|col|$detail}
	idxEntry    {return Databases|$db|$table|idx|$detail}
	idxColEntry {return Databases|$db|$table|idx|$detail|$detaila}
    }
    error "unknown type of node: $type"
}


proc node2String {node} {
    # @c Converts the given  description into a human
    # @c readable string. Similar to .
    #
    # @argument node: The node-description to examine.

    set type    [nodeType $node]
    set node    [nodeInfo $node]

    set db      [lindex $node 0]
    set table   [lindex $node 1]
    set detail  [lindex $node 2]

    switch -- $type {
	root        {return {DBA Root}}
	db          {return "Database $db"}
	table       {return "Table '$table' in '$db'"}
	col         {return "Columns of '$table' in '$db'"}
	idx         {return "Indices of '$table' in '$db'"}
	colEntry    {return "Column '$detail' of '$table' in '$db'"}
	idxEntry    {return "Index '$detail' of '$table' in '$db'"}
	idxColEntry {
return "Index column '$detail' of '$table' in '$db'"
}
    }

    error "unknown type of node: $type"
}



proc nodeParent {node} {
    # @c Computes the description of the node managing the
    # @c given .
    #
    # @argument node: The node-description to examine.
    #
    # @result A node-description.

    set type    [nodeType $node]
    set node    [nodeInfo $node]

    set db      [lindex $node 0]
    set table   [lindex $node 1]
    set detail  [lindex $node 2]
    set detaila [lindex $node 3]

    switch -- $type {
	root        {error "root has no parent"}
	db          {return [nodeRoot]}
	table       {return [nodeDb $db]}
	col         -
	idx         {return [nodeTable $db $table]}
	colEntry    {return [nodeCol   $db $table]}
	idxEntry    {return [nodeIdx   $db $table]}
	idxColEntry {return [nodeIndex $db $table $detail]}
    }

    error "nodeParent: unknown type of entry: $type"
}


proc nodeType {node} {
    # @c Retrieves the type-information from the
    # @c given -description.
    #
    # @argument node: The node-description to examine.
    #
    # @result The type of the specified .

    return [lindex $node 0]
}


proc nodeInfo {node} {
    # @c Retrieves the type-dependent information from the
    # @c given -description.
    #
    # @argument node: The node-description to examine.
    #
    # @result The type-dependent information of the specified .

    return [lindex $node 1]
}


proc entry2node {entry} {
    # @c Converts the given  into the equivalent node
    # @c description.
    #
    # @argument entry: The path of the entry to examine.
    #
    # @result A node-description equivalent to the given .

    set entry   [split $entry |]
    set eLength [llength $entry]

    # Discard 'Databases' prefix, then extract the different fields.

    lshift entry 
    set db      [lshift entry]
    set table   [lshift entry]
    set subtype [lshift entry]
    set detail  [lshift entry]
    set detaila [lshift entry]

    # Convert according to type of entry. The length of the entry path
    # is used distinguish them.

    switch -- $eLength {
	1 {return [nodeRoot]}
	2 {return [nodeDb $db]}
	3 {return [nodeTable $db $table]}
	4 {
	    if {"$subtype" == "col"} {
		return [nodeCol $db $table]
	    } else {
		return [nodeIdx $db $table]
	    }
	}
	5 {
	    if {"$subtype" == "col"} {
		return [nodeColumn $db $table $detail]
	    } else {
		return [nodeIndex  $db $table $detail]
	    }
	}
	6 {
	    return [nodeIndexColumn $db $table $detail $detaila]
	}
    }

    error "entry2Node: illegal entry $entry"
}


proc entryParent {entry} {
    # @c Computes the path to the entry managing the given .
    #
    # @argument entry: The path of the entry to examine.
    #
    # @result An entry.

    set  entry [split $entry |]
    lpop entry
    return [join $entry |]
}


proc entryType {entry} {
    # @c Retrieves the type-information from the given .
    #
    # @argument entry: The path of the entry to examine.
    #
    # @result The type of the node equivalent to the
    # @result specified .

    set entry [split $entry |]

    # Basic type information is derived from the length of the path.

    switch -- [llength $entry] {
	1 {return root}
	2 {return db}
	3 {return table}
	4 {return [expr {"[lindex $entry 3]" == "col" ? "col" : "idx"}]}
	5 {
return [expr {
"[lindex $entry 3]" == "col" ?
"colEntry" : "idxEntry"
}]
}
	6 {return idxColEntry}
    }

    error "entryType: illegal entry $entry"

    # An easier solution, but maybe not as efficient as the one above:
    # return [nodeType [entry2Node $entry]]
}

File code/util.tcl

# -*- tcl -*-
#		dbmsExplorer @mFullVersion@, as of @mDate@
#		General utilities
#
# CVS: $Id: dbmsExplorer.rtf,v 1.1.1.1 1998/01/29 21:19:22 aku Exp $
#
# @c Some utility functions written during development.
# @c They should go into  in the future.
#
# @s Utility functions
# @i utilities
# -----------------------------

proc bool2state {booleanValue} {
    # @c Converts a boolean value as accepted by 'expr', 'if',
    # @c etc. into a state value as accepted by most widgets
    # @c (option -state). True is equivalent to 'normal', false
    # @c means 'disabled'.
    #
    # @argument booleanValue: The value to convert.
    #
    # @result One of 'normal' and 'disabled'.

    #                             true v     false v
    return [expr {$booleanValue ? "normal" : "disabled"}]
}



proc lexchange {list_var position newItem} {
    # @c Removes the item at  from the list stored
    # @c in variable  and inserts  in
    # @c its place.
    #
    # @argument list_var: Name of variable containing the list
    # @argument list_var: to be manipulated.
    # @argument position: Index of the element to remove.
    # @argument newItem:  The element to insert into the place
    # @argument newItem:  of the removed one.
    #
    # @result The changed list.

    upvar $list_var list
    set list [lreplace $list $position $position $newItem]
}


proc string_uncap {string} {
    # @comment unCapitalizes first character of the given .
    #
    # @argument string: The string to be manipulated.
    #
    # @result The  with its first character capitalized.

    return [string tolower \
[string index $string 0]][string range $string 1 end]
}

File code/db_notifier.cls


# -*- tcl -*-
#		dbmsExplorer @mFullVersion@, as of @mDate@
#		Event system for concrete RDBMS managers
#
# CVS: $Id: dbmsExplorer.rtf,v 1.1.1.1 1998/01/29 21:19:22 aku Exp $
#
# @c This is a fundamental baseclass of all database managers.
# @c It contains the generic event system to be used by them.
#
# @s Baseclass for database managers. Generic notifier.
# @i db manager, notifier
# -----------------------------

object_class dbNotifier {
    # @c This class has no directly declared static member variables.
    # @c Everything is handled dynamically. Each registered watch is
    # @c stored into the member variable w,data,, with  the
    # @c serial number of the watch. The associated information are
    # @c the event to watch out for and the script to execute, packed
    # @c into a list. The variable w,ids, on the other hand has
    # @c a list containing the id's of all watches waiting for this
    # @c particular event (this may include a part of its parameters).


    method watch {what watchCmd} {
	# @c Registers a new watch at the event system.
	#
	# @argument what: The event to watch for, this may include
	# @argument what: all or part of its parameters.
	# @argument watchCmd: The script to execute if the event
	# @argument watchCmd: is raised.
	#
	# @result The id given to the new watch.

	# Generate a new id and place the watch info into the base
	# database.

	set id [serial_new]
	set slot(w,data,$id) [list $what $watchCmd]

	# After that add it to the event-based index for fast lookup
	# of all associated watches during the raise of an event. The
	# index entry may not exist, either create it or append the
	# new id to it.

	if {[info exists slot(w,ids,$what)]} {
	    lappend slot(w,ids,$what) $id
	} else {
	    set slot(w,ids,$what) $id
	}

	return $id
    }


    method watchGet {id} {
	# @c Retrieves the information (event and script) of a
	# @c particular watch. Its  is used to probe the
	# @c database.
	#
	# @argument id: The id of the watch we are asking for.

	return $slot(w,data,$id)
    }


    method unWatch {id} {
	# @c Remove the watch specified by its  from the
	# @c event system.
	#
	# @argument id: The id of the watch to remove.

	# Determines the event the watch is waiting for, then
	# removes the id from the associated index entry. After
	# that the base data is destroyed too. An index entry
	# just emptied of all watches is removed completely.
	# At last the system tells the id generator to recycle
	# it as it wishes.

	set what [lindex $slot(w,data,$id) 0]

	ldelete slot(w,ids,$what) $id
	unset   slot(w,data,$id)

	if {$slot(w,ids,$what) == {}} {
	    unset slot(w,ids,$what)
	}

	serial_delete $id
	return
    }


    method notify {event} {
	# @c This is the method called by the concrete database
	# @c managers to post a particular event.
	#
	# @argument event: A list containing the name of the
	# @argument event: posted event as its first argument,
	# @argument event: followed by its arguments.

	# Log the call, to help in debugging the event system.

	syslog debug $self notify -> $event

	# The given information is split into event and its
	# arguments, to prepare for the execution of event scripts.

	set eventCode [lindex $event 0]
	set eventArgs [lrange $event 1 end]

	# Every iteration of the loop checks for matching watches
	# in the index and executes them. After that the given event
	# description is shortened at end. The process stops after
	# exhaustion of the list. Because of this not only watches
	# set upon the specific event are executed, but watches
	# waiting for the general class of the event as well. Due
	# to the implementation here the most specific watches are
	# executed first, then the more general ones.

	# Example:
	# Upon posting the event 'evColumnCreate db table column'
	# the system will check for watches set on
	#
	# * evColumnCreate db table column
	# * evColumnCreate db table
	# * evColumnCreate db
	# * evColumnCreate
	#
	# in this order.

	while {$event != {}} {
	    if {[info exists slot(w,ids,$event)]} {
		foreach id $slot(w,ids,$event) {

		    # We have to be a bit careful here. The scripts
		    # are called synchronously and may invoke
		    # operations on the event database (like 'unWatch').
		    # The latter may delete handlers currently waiting
		    # for execution. We have to check for that and skip
		    # these ids.

		    if {![info exists slot(w,data,$id)]} {
			continue
		    }

		    # Having jumped over the previous hurdle the script
		    # is executed now, its arguments are the id of the
		    # watch, the name of the event and its arguments.

		    set _what   [lindex $slot(w,data,$id) 0]
		    set _script [lindex $slot(w,data,$id) 1]

		    syslog debug $self notify ---> $_what, $_script

		    uplevel #0 $_script $id $eventCode [list $eventArgs]
		}
	    }

	    # Cut one item off the end of the list describing the
    # posted event

	    lpop event
	}

	return
    }
}
File code/db_cache.cls


# -*- tcl -*-
#		dbmsExplorer @mFullVersion@, as of @mDate@
#		Cache manager
#
# CVS: $Id: dbmsExplorer.rtf,v 1.1.1.1 1998/01/29 21:19:22 aku Exp $
#
# @c Helper class for database managers. Contains the generic
# @c cache system.
#
# @s Helper class for database managers. Generic cache
# @i db manager, cache
# -----------------------------

object_class dbCache {
    # @c This is another class with dynamic member variables. In
    # @c contrast to  it contains a statically
    # @c declared one as well (databases).


    #################################################
    # I. known databases, definition / accessors

    # In this section we deal with the part of the cache storing
    # the databases known to the concrete RDBMS manager. A simple
    # list is used to do that.

    # @member databases: The list of databases known to the cache
    # @member databases: (and the database manager).
    member databases {}


    method setDatabases {dbList} {
	# @c Defines the complete list of known databases.
	#
	# @argument dbList: The list of databases set into the cache.

	set slot(databases) $dbList
	return
    }


    method getDatabases {} {
	# @c Retrieves the list of known databases.

	return $slot(databases)
    }


    method addDatabase {db} {
	# @c Adds a new database to the cache.
	#
	# @argument db: The name of the new database.

	lappend slot(databases) $db
	return
    }


    method hasDatabase {db} {
	# @c Checks wether the given database is known to the
	# @c cache or not.
	#
	# @argument db: The name of the database to check for.
	#
	# @result A boolean value. True signals that the
	# @result name was found.

	return [expr {[lsearch $slot(databases) $db] >= 0}]
    }



    #################################################
    # II. tables contained in databases, definition / acessor

    # In this section we deal with the part of the cache storing
    # the names of the tables in a database and known to the
    # concrete RDBMS manager. A simple list is used to do that,
    # but we need a list for each known database. The member
    # variables are named
    #
    #	db,
    #
    #  is the name of the database we are looking at.


    method setTables {db tableList} {
	# @c Defines the complete list of known tables for a
	# @c particular database.
	#
	# @argument db:        The name of the database in question.
	# @argument tableList: The list of tables set into the cache.

	set slot(db,$db) $tableList
	return
    }


    method getTables {db} {
	# @c Retrieves the list of known tables for a particular
	# @c database.
	#
	# @argument db: The name of the database to query.

	return $slot(db,$db)
    }


    method hasTables {db} {
	# @c Checks wether the given database has tables or not.
	#
	# @argument db: The name of the database to query.
	#
	# @result A boolean value. True signals that the
	# @result database has tables.

	return [info exists slot(db,$db)]
    }


    method addTable {db table} {
	# @c Adds a new table to the database in the cache.
	#
	# @argument db:    The name of the database to manipulate.
	# @argument table: The name of the new table.

	lappend slot(db,$db) $table
	return
    }


    method hasTable {db table} {
	# @c Checks wether the given table is known to the
	# @c database in the cache or not.
	#
	# @argument db:    The name of the database to query.
	# @argument table: The name of the table to check for.
	#
	# @result A boolean value. True signals that the
	# @result name was found.

	return [expr {[lsearch $slot(db,$db) $table] >= 0}]
    }


    #################################################
    # III. columns of tables in databases, definition / accessor

    # In this section we deal with the part of the cache storing
    # the names of the columns of a table in a database and known
    # to the concrete RDBMS manager. A simple list is used to do
    # that, but we need a list for each known database and table.
    # The member variables are named
    #
    #	table/C,,
    #
    #     is the name of the database we are looking at.
    # 
is the name of the table we are looking at. method setColumns {db table columnList} { # @c Defines the complete list of known columns for a # @c particular database and table. # # @argument db: The name of the database in question. # @argument table: The name of the table in question. # @argument columnList: The list of columns set into the cache. set slot(table/C,$db,$table) $columnList return } method getColumns {db table} { # @c Retrieves the list of known columns for a particular # @c database and table. # # @argument db: The name of the database to query. # @argument table: The name of the table to query. return $slot(table/C,$db,$table) } method hasColumns {db table} { # @c Checks wether the given database/table combination # @c has columns or not. # # @argument db: The name of the database to query. # @argument table: The name of the table to query. # # @result A boolean value. True signals that the # @result database/table combination has columns. return [info exists slot(table/C,$db,$table)] } method addColumn {db table column} { # @c Adds a new column to the database/table combination # @c in the cache. # # @argument db: The name of the database to manipulate. # @argument table: The name of the table to manipulate. # @argument column: The name of the new column. lappend slot(table/C,$db,$table) $column return } ################################################# # IV. indices of tables in databases, definition / accessor # In this section we deal with the part of the cache storing # the names of the indices of a table in a database and known # to the concrete RDBMS manager. A simple list is used to do # that, but we need a list for each known database and table. # The member variables are named # # table/I,,
# # is the name of the database we are looking at. #
is the name of the table we are looking at. method setIndices {db table indexList} { # @c Defines the complete list of known indices for a # @c particular database and table. # # @argument db: The name of the database in question. # @argument table: The name of the table in question. # @argument columnList: The list of columns set into the cache. set slot(table/I,$db,$table) $indexList return } method getIndices {db table} { # @c Retrieves the list of known indices for a particular # @c database and table. # # @argument db: The name of the database to query. # @argument table: The name of the table to query. return $slot(table/I,$db,$table) } method hasIndices {db table} { # @c Checks wether the given database/table combination # @c has indices or not. # # @argument db: The name of the database to query. # @argument table: The name of the table to query. # # @result A boolean value. True signals that the # @result database/table combination has indices. return [info exists slot(table/I,$db,$table)] } method addIndex {db table index} { # @c Adds a new index to the database/table combination # @c in the cache. # # @argument db: The name of the database to manipulate. # @argument table: The name of the table to manipulate. # @argument index: The name of the new index. lappend slot(table/I,$db,$table) $index return } ################################################# # V. column information # In this section we deal with the part of the cache storing # column descriptions. These are type specifications as # described in the design section. # # The member variables are named # # column,,
, # # is the name of the database we are looking at. #
is the name of the table we are looking at. # is the name of the table we are looking at. method setColumn {db table column columnSpec} { # @c Sets a column description into the cache. # # @argument db: The name of the database to manipulate. # @argument table: The name of the table to manipulate. # @argument column: The name of the column to manipulate. # @argument columnSpec: The specification to set into the cache. set slot(column,$db,$table,$column) $columnSpec return } method getColumn {db table column} { # @c Retrieves the description of the specified # @c column from the cache. # # @argument db: The name of the database to query. # @argument table: The name of the table to query. # @argument column: The name of the column to query. # # @result The description of the specified column. return $slot(column,$db,$table,$column) } method hasColumn {db table column} { # @c Checks wether the given column is known to the # @c database/table combination in the cache or not. # # @argument db: The name of the database to query. # @argument table: The name of the table to query. # @argument column: The name of the column to check for. # # @result A boolean value. True signals that the # @result name was found. return [info exists slot(column,$db,$table,$column)] } ################################################# # VI. index information # In this section we deal with the part of the cache storing # index descriptions. These are described in the design section. # # The member variables are named # # index,,
, # # is the name of the database we are looking at. #
is the name of the table we are looking at. # is the name of the table we are looking at. method setIndex {db table index indexSpec} { # @c Sets an index description into the cache. # # @argument db: The name of the database to manipulate. # @argument table: The name of the table to manipulate. # @argument index: The name of the index to manipulate. # @argument indexSpec: The specification to set into the cache. set slot(index,$db,$table,$index) $indexSpec return } method getIndex {db table index} { # @c Retrieves the description of the specified # @c index from the cache. # # @argument db: The name of the database to query. # @argument table: The name of the table to query. # @argument index: The name of the index to query. # # @result The description of the specified index. return $slot(index,$db,$table,$index) } method hasIndex {db table index} { # @c Checks wether the given index is known to the # @c database/table combination in the cache or not. # # @argument db: The name of the database to query. # @argument table: The name of the table to query. # @argument index: The name of the index to check for. # # @result A boolean value. True signals that the # @result name was found. return [info exists slot(index,$db,$table,$index)] } ################################################# # Everything above this line dealt with single pieces # (databases, table, columns and indices) only. From # now on we are defining procedures to manipulate sets # of entities. # # This involves removing and renaming the stored entities. ################################################# # VII. clear parts or all of the cache method clearDatabases {} { # @c This method could have been named 'clearCache' # @c too. It not only clears out the list of known # @c databases, but all information associated to # @c them (tables, columns and indices) as well. array_clear slot * set slot(databases) {} return } method clearDatabase {db} { # @c The specified database is removed from the list # @c of known databases. All information associated # @c to it (its tables, columns and indices) are # @c removed as well. # # @argument db: The name of the database to remove # @argument db: from the cache. catch {ldelete slot(databases) $db} catch {unset slot(db,$db)} catch {array_clear slot table/C,$db,*} catch {array_clear slot table/I,$db,*} catch {array_clear slot column,$db,*,*} catch {array_clear slot index,$db,*,*} return } method clearTable {db table} { # @c The specified table is removed from the cache. # @c All information associated to it (its columns # @c and indices) are removed as well. # # @argument db: The name of the database containing the table. # @argument table: The name of the table to remove from # @argument table: the cache. catch {ldelete slot(db,$db) $table} catch {unset slot(table/C,$db,$table)} catch {unset slot(table/I,$db,$table)} catch {array_clear slot column,$db,$table,*} catch {array_clear slot index,$db,$table,*} return } method clearColumn {db table column} { # @c The specified column is removed from the cache. # @c All indices using this column are corrected. All # @c indices having no columns afterward are removed # @c too, a list containing their names is returned # @c to the caller. # # @argument db: The name of the database containing the # @argument db: table. # @argument table: The name of the table containing the column. # @argument column: The name of the index to remove from # @argument column: the cache. # Remove the column information from the cache and # its name from the list managed by its table. catch {ldelete slot(table/C,$db,$table) $column} catch {unset slot(column,$db,$table,$column)} set lostIndices {} if {[$self hasIndices $db $table]} { # Now correct the information of all indices using # the column. This must be done only if there are # indices associated to the table. foreach index [$self getIndices $db $table] { # Each iteration checks an index associated to the # table. Its list of columns is extracted from the # index specification and the column searched. # If the column is not found the loop goes to the # next index. # spec = {unique columnlist} set colList [lindex $slot(index,$db,$table,$index) 1] set pos [lsearch -exact $colList $column] if {$pos < 0} {continue} # The index uses the column. It is removed if that # was its last column. The column is removed from # the list otherwise and the corrected information # then placed into the index specification. if {[llength $colList] == 1} { $self clearIndex $db $table $index lappend lostIndices $index } else { ldelete colList $column } lexchange slot(index,$db,$table,$index) 1 $colList } } # At last we return the names of all indices we had to # remove during this operation. return $lostIndices } method clearIndex {db table index} { # @c The specified index is removed from the cache. # # @argument db: The name of the database containing the table. # @argument table: The name of the table containing the index. # @argument index: The name of the index to remove from # @argument index: the cache. catch {ldelete slot(table/I,$db,$table) $index} catch {unset slot(index,$db,$table,$index)} return } ###################################### # VIII. rename entities (and correct the location of their # subordinates!) method renameDatabase {db new} { # @c The specified database is given a new name and all # @c associated information (tables, columns and indices) # @c updated accordingly. # # @argument db: The name of the database to rename. # @argument new: The new name of the database. # The database is removed from and then reentered into # the list of known database first. Care is taken not # to change its position in the list. set pos [lsearch -exact $slot(databases) $db] lexchange slot(databases) $pos $new if {[$self hasTables $db]} { # We know the tables associated to the database. # Move the list containing their names to its new # place, then handle their columns and indices. $self _move db,$db db,$new foreach table [$self getTables $new] { if {[$self hasColumns $db $table]} { # We know the columns associated to the # current table. Move the list containing # their names to its new place, then move # the column specifications according to # the new name of the table. $self _move table/C,$db,$table table/C,$new,$table foreach col [$self getColumns $new $table] { $self _move \ column,$db,$table,$col column,$new,$table,$col } } if {[$self hasIndices $db $table]} { # We know the indices associated to the # current table. Move the list containing # their names to its new place, then move # the index specifications according to # the new name of the table. $self _move table/I,$db,$table table/I,$new,$table foreach idx [$self getIndices $new $table] { $self _move \ index,$db,$table,$idx index,$new,$table,$idx } } } } return } method renameTable {db table new} { # @c The specified table is given a new name and all # @c associated information (columns and indices) # @c updated accordingly. # # @argument db: The name of the database containing the table. # @argument table: The name of the table to rename. # @argument new: The new name of the table. # The table is removed from and then reentered into the # list of tables associated to the containing database # first. Care is taken not to change its position in the # list. set pos [lsearch -exact $slot(db,$db) $table] lexchange slot(db,$db) $pos $new if {[$self hasColumns $db $table]} { # We know the columns associated to the table. Move # the list containing their names to its new place, # then move the column specifications according to # the new name of the table. $self _move table/C,$db,$table table/C,$db,$new foreach col [$self getColumns $db $new] { $self _move column,$db,$table,$col column,$db,$new,$col } } if {[$self hasIndices $db $table]} { # We know the indices associated to the table. Move # the list containing their names to its new place, # then move the index specifications according to # the new name of the table. $self _move table/I,$db,$table table/I,$db,$new foreach idx [$self getIndices $db $new] { $self _move index,$db,$table,$idx index,$db,$new,$idx } } return } method renameColumn {db table column new} { # @c The specified column is given a new name and all # @c indices using it are updated accordingly. # # @argument db: The name of the database containing the table. # @argument table: The name of the table containing the column. # @argument index: The name of the column to rename. # @argument new: The new name of the column. # The column is removed from and then reentered into the # list of columns associated to the containing table # first. Care is taken not to change its position in the # list. Then the column specification is moved to its # new place. set pos [lsearch -exact $slot(table/C,$db,$table) $column] lexchange slot(table/C,$db,$table) $pos $new $self _move column,$db,$table,$column column,$db,$table,$new if {[$self hasIndices $db $table]} { # Now correct the information of all indices using # the column. This must be done only if there are # indices associated to the table. foreach idx [$self getIndices $db $table] { # Each iteration checks an index associated to the # table. Its list of columns is extracted from the # index specification and the column searched. # If the column is not found the loop goes to the # next index. # spec = {unique columnlist} set colList [lindex $slot(index,$db,$table,$idx) 1] set pos [lsearch -exact $colList $column] if {$pos < 0} {continue} # The index uses the column. It is removed and # reentered into the column list which is then # placed back into the index specification. Care # is taken not to change its position in the # column list. lexchange colList $pos $new lexchange slot(index,$db,$table,$idx) 1 $colList } } return } method renameIndex {db table index new} { # @c The specified index is given a new name. # # @argument db: The name of the database containing the table. # @argument table: The name of the table containing the index. # @argument index: The name of the index to rename. # @argument new: The new name of the index. # The index is removed from and then reentered into the # list of indices associated to the containing table # first. Care is taken not to change its position in the # list. Then the index specification is moved to its # new place. set pos [lsearch -exact $slot(table/I,$db,$table) $index] lexchange slot(table/I,$db,$table) $pos $new $self _move index,$db,$table,$index index,$db,$table,$new return } ################################################# # IX. Helper methods method _move {key keyNew} { # @c A private helper method. Moves the contents of # @c variable to . The variable # @c will not exist anymore after that. Any contents # @c the variable might have had are lost. # # @argument key: The name of the variable to move (rename). # @argument keyNew: The new name of the variable. # Discard stupid nop requests. if {"$key" == "$keyNew"} {return} set slot($keyNew) $slot($key) unset slot($key) return } } File code/db_manager.cls


# -*- tcl -*-
#		dbmsExplorer  @mFullVersion@, as of @mDate@
#		Generic manager class
#
# CVS: $Id: dbmsExplorer.rtf,v 1.1.1.1 1998/01/29 21:19:22 aku Exp $
#
# @c Here we define the generic parts of all database managers. Even
# @c procedures without implementation are defined here, to make this
# @c file an explanation of the full interface too. Users of a
# @c database manager have to consult this class description as
# @c reference. Concrete database managers must inherit from this
# @c class.
#
# @s Interface of all RDBMS managers
# @i generic db manager
# -----------------------------

object_class dbGenericManager {

    #######################################################
    # Use event subsystem
    # All member variables matching 'w,*' are reserved by the
    # event system and must not be used in this class.

    object_include dbNotifier


    # @member cache: Reference to the object containing cached
    # @member cache: database information. Controlling the object
    # @member cache: is the responsibility of the concrete classes.

    member cache


    method init {} {
	# @c This method is called by the framework ()
	# @c to execute class-specific instance initialization code.
	# @c Here we create the cache to be used by the concrete
	# @c manager.

	set slot(cache) [dbCache ${self}_c]
	return
    }


    method destroy {} {
	# @c This method is called by the framework ()
	# @c to execute class-specific instance destruction code.
	# @c Here we destroy the cache used by the concrete
	# @c manager.

	object_destroy $slot(cache)
	return
    }



    #######################################################
    # Internal interface from this class to its subclasses, the
    # concrete managers. Provides methods for setting up generic
    # tables containing information about types, commands, special
    # commands and managers provided by the subclass and the RDBMS
    # it connects to. Using this tables the generic class is able
    # to handle some queries by itself, without delegation to
    # the subclass.

    # @member meta,types: List containing the names of all types
    # @member meta,types: supported by the underlying DBMS

    # @member meta,specials: List containing the names of all
    # @member meta,specials: special commands provided by the
    # @member meta,specials: concrete subclass.

    # @member meta,managers: List containing the names of all parts
    # @member meta,managers: a table supported by the RDBMS the
    # @member meta,managers: concrete subclass connects to. The UI
    # @member meta,managers: is currently only able to handle
    # @member meta,managers: 'columns' and 'indices'.

    member meta,types    {}
    member meta,specials {}
    member meta,managers {}


    # The following member variables are dynamically used:
    #
    #	meta,type, - Defined for all names listed in 'meta,types'
    #			   Refers to the description of the specified
    #			   type.
    #
    #	meta,cmd,   - Defined for all commands set via
    #			   'setSupportedCommands' and
    #			   'setSupportedSpecials'.
    #
    #			   The value is 'available' for the first and
    #			   the command description for the second.


    method setSupportedCommands {cmdList} {
	# @c Used by the concrete subclass to set the names of all
	# @c standard commands supported by it.
	#
	# @argument cmdList: The list of supported commands.

	# Recode the list into something easier accessible.
	# See method 'isCommandAvailable' too.

	foreach cmd $cmdList {
	    set slot(meta,cmd,$cmd) available
	}

	return
    }


    method setSupportedTypes {typeList} {
	# @c Used by the concrete subclass to set the names of all
	# @c types. The list is in 'array set'-format, the type
	# @c names are used as keys, the associated description
	# @c as values.
	#
	# @argument typeList: The list of supported types and their
	# @argument typeList: descriptions.

	set slot(meta,types) {}

	# Split the list into type names and descriptions. Collect
	# the former in a list and save the latter in the array,
	# indexed by name.

	foreach {typeName typeSpec} $typeList {
	    set slot(meta,type,$typeName) $typeSpec
	    lappend slot(meta,types) $typeName
	}

	set slot(meta,types) [lsort $slot(meta,types)]
	return
    }


    method setSupportedManagers {mgrList} {
	# @c Used by the concrete subclass to set the names of all
	# @c parts of a table supported by it.
	#
	# @argument mgrList: The list of supported parts of a table.

	set slot(meta,managers) $mgrList
    }


    method setSupportedSpecials {cmdList} {
	# @c Used by the concrete subclass to set the names of all
	# @c provided special commands. The list is in 'array set'
	# @c format, the command names are used as keys, the
	# @c associated description as values.
	#
	# @argument cmdList: The list of provided special commands.

	# Recode the list into something easier accessible. The
	# command names are collected in the list, the description
	# put into the array, indexed by their name.
	# See method 'cmdSpec' too.

	set slot(meta,specials) {}

	foreach {cmdName cmdSpec} $cmdList {
	    set slot(meta,cmd,$cmdName) $cmdSpec
	    lappend slot(meta,specials) $cmdName
	}

	set slot(meta,specials) [lsort $slot(meta,specials)]
	return
    }


    #######################################################
    # Accessors for meta information. There is no need to override
    # them in a concrete subclass.

    method isCommandAvailable {cmd} {
	# @c Checks wether the standard command (specified by its
	# @c symbolic name) is supported by the concrete subclass
	# @c or not.
	#
	# @argument cmd: Symbolic name of the command to check for.

	return [info exists slot(meta,cmd,$cmd)]
    }


    method availableManagers {} {
	# @c Retrieves the list of all parts of a table supported
	# @c by the concrete subclass.

	return $slot(meta,managers)
    }


    method knownTypes {} {
	# @c Retrieves the list containing the names of all types
	# @c supported by the RDBMS the concrete subclass connects
	# @c to.

	return $slot(meta,types)
    }


    method typeSpec {type} {
	# @c Retrieves the description the of the specified .
	#
	# @argument type: Symbolic name of the type whose is
	# @argument type: description is asked for.

	return $slot(meta,type,$type)
    }


    method knownSpecials {} {
	# @c Retrieves the list containing the names of all special
	# @c commands supported by the concrete subclass.

	return $slot(meta,specials)
    }


    method cmdSpec {cmdName} {
	# @c Retrieves the description the of the specified special
	# @c command. Calling this method with name of a standard
	# @c command will return the value 'available'.
	#
	# @argument type: Symbolic name of the special command whose
	# @argument type: is description is asked for.

	return $slot(meta,cmd,$cmdName)
    }


    #######################################################
    # Methods to override in derived classes. 
    # Not overiding them will a cause the generation an exception.
    # They define the interfaces to query the manager about itself
    # and the RDBMS it is connected to.
    #
    # 'NYI' is a short for 'not yet implemented'

    method who {which} {
	# @c Returns either the symbolic name of the RDBMS
	# @c (which = -name) used as prefix for special types and
	# @c commands, or a text usable for display by the UI
	# @c layer (which = -text). Other values of 'which' are
	# @c not supported.

	# -W- This method should be a generic one, with a corresponding
	# -W- method in the setup section.

	error "abstract method not overidden by subclass"
    }


    method getDatabases {} {
	# @c Returns the list of all databases found in the RDBMS
	# @c the concrete instance is connected to.

	error "abstract method not overidden by subclass"
    }


    method getTables {db} {
	# @c Returns the list of all tables found in the RDBMS
	# @c the concrete instance is connected to, for a specific
	# @c database.
	#
	# @argument db: The name of the database to look at.

	error "abstract method not overidden by subclass"
    }


    method getColumns {db_table} {
	# @c Returns the list of all columns found in the RDBMS
	# @c the concrete instance is connected to, for a specific
	# @c combination of database and table.
	#
	# @argument db_table: A 2-element list containing the names
	# @argument db_table: of database and table to look at, in
	# @argument db_table: this order.

	error "abstract method not overidden by subclass"
    }


    method getIndices {db_table} {
	# @c Returns the list of all indices found in the RDBMS
	# @c the concrete instance is connected to, for a specific
	# @c combination of database and table.
	#
	# @argument db_table: A 2-element list containing the names
	# @argument db_table: of database and table to look at, in
	# @argument db_table: this order.

	error "abstract method not overidden by subclass"
    }


    method getTableDefinition {db_table} {
	# @c Returns the definition of the specified table, see the
	# @c design section for an explanation about the structure
	# @c of the result.
	#
	# @argument db_table: A 2-element list containing the names
	# @argument db_table: of database and table to look at, in
	# @argument db_table: this order.

	error "abstract method not overidden by subclass"
    }


    method getColumnDefinition {db_table_col} {
	# @c Returns the definition of the specified column, see the
	# @c design section for an explanation about the structure
	# @c of the result.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: names of database, table and
	# @argument db_table_col: column to look at, in this order.

	error "abstract method not overidden by subclass"
    }


    method getIndexDefinition {db_table_idx} {
	# @c Returns the definition of the specified index, see the
	# @c design section for an explanation about the structure
	# @c of the result.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: names of database, table and
	# @argument db_table_idx: index to look at, in this order.

	error "abstract method not overidden by subclass"
    }


    method getIndexColumns {db_table_idx} {
	# @c A convenience method, returns a list containing the
	# @c names of all columns used by the specified index.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: names of database, table and
	# @argument db_table_idx: index to look at, in this order.

	error "abstract method not overidden by subclass"
    }


    method getIndexUniqeness {db_table_idx} {
	# @c A convenience method, returns a boolean value
	# @c indicating wether the specified index is unique or not.
	# @c True signals uniqueness.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: names of database, table and
	# @argument db_table_idx: index to look at, in this order.

	error "abstract method not overidden by subclass"
    }


    method isCommandAllowed {operation opArgs} {
	# @c Similar to 'isCommandAvailable', but its results
	# @c depends not only on the availability of the command,
	# @c but on the specified entity as well.
	#
	# @argument operation: The symbolic name of the operation
	# @argument operation: to check.
	#
	# @argument opArgs: A description of the argument which will
	# @argument opArgs: be given to the method implementing the
	# @argument opArgs: command. The format is described in the
	# @argument opArgs: design section.
	#
	# @result A boolean value. True signals that the command is
	# @result allowed to act upon the specified entity.

	error "abstract method not overidden by subclass"
    }


    #######################################################
    # It is allowed to leave a method unimplemented in a subclass,
    # but only if the associated standard command *is not* defined
    # as supported, see 'setSupportedCommands'.


    method dbCreate {db} {
	# @c Creates a new database. Associated command and event
	# @c are 'cmdDbCreate' and 'evDbCreate'.
	#
	# @argument db: The name of the new database.

	error "abstract method not overidden by subclass"
    }


    method dbDrop {db} {
	# @c Destroys the specified database. Associated command
	# @c and event are 'cmdDbDrop' and 'evDbDrop'.
	#
	# @argument db: The name of the database to destroy.

	error "abstract method not overidden by subclass"
    }


    method dbRename {db newName} {
	# @c Changes the name of the specified database. Associated
	# @c command and event are 'cmdDbRename' and 'evDbRename'.
	#
	# @argument db: The name of the database to rename.
	# @argument newName: The new name of the database.

	error "abstract method not overidden by subclass"
    }


    method tableCreate {db_table spec} {
	# @c Creates a new table. Associated command and event
	# @c are 'cmdTableCreate' and 'evTableCreate'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: new table, in this order.
	#
	# @argument spec: The specification of the table to create.

	error "abstract method not overidden by subclass"
    }


    method tableDrop {db_table} {
	# @c Destroys the specified table. Associated command
	# @c and event are 'cmdTableDrop' and 'evTableDrop'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: table to destroy, in this order.

	error "abstract method not overidden by subclass"
    }


    method tableRename {db_table newName} {
	# @c Changes the name of the specified table. Associated
	# @c command and event are 'cmdTableRename' and
	# @c 'evTableRename'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: table to rename, in this order.
	# @argument newName: The new name of the table.

	error "abstract method not overidden by subclass"
    }


    method tableAlter {db_table newName newSpec} {
	# @c Changes the definition of the specified table.
	# @c Associated command and event are 'cmdTableAlter'
	# @c and 'evTableAlter'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: table to change, in this order.
	#
	# @argument newName: The new name of the table.
	# @argument newSpec: The new specification of the table.

	error "abstract method not overidden by subclass"
    }


    method columnCreate {db_table_col spec} {
	# @c Creates a new column. Associated command and event
	# @c are 'cmdColumnCreate' and 'evColumnCreate'.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: name of the database, the table
	# @argument db_table_col: and the name of the new column,
	# @argument db_table_col: in this order.
	#
	# @argument spec: The type specification to associate with
	# @argument spec: the column.

	error "abstract method not overidden by subclass"
    }


    method columnDrop {db_table_col} {
	# @c Destroys the specified column. Associated command and
	# @c event are 'cmdColumnDrop' and 'evColumnDrop'.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: name of the database, the table
	# @argument db_table_col: and the name of the column to
	# @argument db_table_col: destroy, in this order.

	error "abstract method not overidden by subclass"
    }


    method columnRename {db_table_col newName} {
	# @c Changes the name of the specified column. Associated
	# @c command and event are 'cmdColumnRename'
	# @c and 'evColumnRename'.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: name of the database, the table
	# @argument db_table_col: and the name of the column to
	# @argument db_table_col: rename, in this order.
	#
	# @argument newName: The new name of the column.

	error "abstract method not overidden by subclass"
    }


    method columnAlter {db_table_col newName newSpec} {
	# @c Changes the definition of the specified column.
	# @c Associated command and event are 'cmdColumnAlter'
	# @c and 'evColumnAlter'.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: name of the database, the table
	# @argument db_table_col: and the name of the column to
	# @argument db_table_col: change, in this order.
	#
	# @argument newName: The new name of the column.
	# @argument newSpec: The new specification of the column.

	error "abstract method not overidden by subclass"
    }


    method indexCreate {db_table_idx spec} {
	# @c Creates a new index. Associated command and event
	# @c are 'cmdIndexCreate' and 'evIndexCreate'.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: name of the database, the table
	# @argument db_table_idx: and the name of the new index,
	# @argument db_table_idx: in this order.
	#
	# @argument spec: The specification of the new index.

	error "abstract method not overidden by subclass"
    }


    method indexDrop {db_table_idx} {
	# @c Destroys the specified index. Associated command and
	# @c event are 'cmdIndexDrop' and 'evIndexDrop'.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: name of the database, the table
	# @argument db_table_idx: and the name of the index to
	# @argument db_table_idx: destroy, in this order.

	error "abstract method not overidden by subclass"
    }


    method indexRename {db_table_idx newName} {
	# @c Changes the name of the specified index. Associated
	# @c command and event are 'cmdIndexRename'
	# @c and 'evIndexRename'.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: name of the database, the table
	# @argument db_table_idx: and the name of the index to
	# @argument db_table_idx: rename, in this order.
	#
	# @argument newName: The new name of the index.

	error "abstract method not overidden by subclass"
    }


    method indexAlter {db_table_idx newName newSpec} {
	# @c Changes the definition of the specified index.
	# @c Associated command and event are 'cmdIndexAlter'
	# @c and 'evIndexAlter'.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: name of the database, the table
	# @argument db_table_idx: and the name of the index to
	# @argument db_table_idx: change, in this order.
	#
	# @argument newName: The new name of the index.
	# @argument newSpec: The new specification of the index.

	error "abstract method not overidden by subclass"
    }



    #######################################################
    # special interfaces


    method convertGenericType2Sql {spec} {
	# @c This method converts a generic type specification into
	# @c the coressponding SQL syntax used by the RDBMS. It is
	# @c exported for usage by the table editor
	# @c () as its output is more readble
	# @c than the generic representation.

	error "abstract method not overidden by subclass"
    }


    method specialType {what parentWin typeName args} {
	# @c This method is called by the type editor to manage the
	# @c UI of special types.
	#
	# @argument what:      The operation to execute. Must be one
	# @argument what:      of 'createPane', 'getDef', 'setDef'
	# @argument what:      and 'setState'.
	# @argument parentWin: The widget to place the generated UI into.
	# @argument typeName:  The name of the special type to handle
	# @argument args:      Other arguments, special to chosen
	# @argument args:      subcommand. Described in the design
	# @argument args:      section.

	# -W- specialType -
	# -W- I don't like having UI code in the DBI layer. This
	# -W- interface might therefore change in future versions.
	# -W- See footnote (3) in the design section too.

	error "abstract method not overidden by subclass"
    }

    #######################################################
}
File code/db_mysql.cls


# -*- tcl -*-
#		dbmsExplorer  @mFullVersion@, as of @mDate@
#		Manager class for mySQL database systems
#
# CVS: $Id: dbmsExplorer.rtf,v 1.1.1.1 1998/01/29 21:19:22 aku Exp $
#
# @c This the concrete subclass of  for
# @c handling the MySQL database management system. Version
# @c 3.21.15 or higher is required, as it is the first one
# @c implementing 'create/drop database' as SQL commands.
#
# @s Manager for mySQL DBS
# @i mySQL, db manager
# -----------------------------

object_class dbMysqlManager {
    # @c This defines the logic of managing a mySQL DBMS.

    #######################################################
    # generic interface is superclass
    # we need its 'init' and 'destroy' functionality inside
    # our own, so save it.

    object_include dbGenericManager
    object_savedef dbGenericManager init
    object_savedef dbGenericManager destroy

    # The above declaration imported the following member variables:
    #
    # cache; meta,*; w,*
    #
    # These names must not be used here.

    # @m conn: Stores the connection handle returned
    # @m conn: by the DBMS accessor library

    member conn



    #######################################################
    # OO framework hooks

    class_initialization {
	# global initialization code, executed upon autoload of the
	# compiled class. Forces the logger package and the DBMS
	# accessor library into the intepreter.

	package require Pool_Log
	package require Sql
    }


    method init {} {
	# @c This method is called by the framework ()
	# @c to execute class-specific instance initialization code.
	# @c Here we initialize the superclass structures and open
	# @c a new connection to the DBMS.

	$self dbGenericManager:init
	set slot(conn) [sql connect]

	# The commands 'cmdDbRename', 'cmdIndexCreate',
	# 'cmdIndexRename' and 'cmdIndexAlter' are not implemented
	# here and therefore not declared as supported.
	#
	# The UI layer should not call their methods, but errors are
	# always possible, so I decided to define their methods
	# nevertheless, to generate an error message more appropriate
	# than 'abstract method not overidden'.

	$self setSupportedCommands {
	    cmdDbCreate     cmdDbDrop
	    cmdColumnCreate cmdColumnDrop cmdColumnRename cmdColumnAlter
	    cmdTableCreate  cmdTableDrop  cmdTableRename  cmdTableAlter
	    cmdIndexDrop
	}

	# No specials, at least not yet.

	$self setSupportedSpecials {
	}

	# All currently known parts of a table are supported.

	$self setSupportedManagers {columns indices}

	# This lengthy list contains all types known to MySQL,
	# and their descriptions. Read the design section if
	# you want to know more about their structure.

	$self setSupportedTypes {
	    blob       {nullable 1 defaultable 0 autoincr 0 linked 0 
par {}}
	    tinyblob   {nullable 1 defaultable 0 autoincr 0 linked 0
par {}}
	    mediumblob {nullable 1 defaultable 0 autoincr 0 linked 0
par {}}
	    longblob   {nullable 1 defaultable 0 autoincr 0 linked 0
par {}}

	    date       {nullable 1 defaultable 1 autoincr 0 linked 0
par {}}
	    datetime   {nullable 1 defaultable 1 autoincr 0 linked 0
par {}}
	    time       {nullable 1 defaultable 1 autoincr 0 linked 0
par {}}

	    timestamp  {nullable 0 defaultable 1 autoincr 0 linked 0
par {
			{type enum name length text Length optional 0
values {12 14}}}}

	    mysql-enum {nullable 1 defaultable 1 autoincr 0 linked 0
par {}}
	    mysql-set  {nullable 1 defaultable 1 autoincr 0 linked 0
par {}}

	    char       {nullable 1 defaultable 1 autoincr 0 linked 0
par {
			{type range  name length text Length optional 0
minval 1 maxval 255}
			{type option name binary text Binary optional 0}}}
	    varchar    {nullable 1 defaultable 1 autoincr 0 linked 0
par {
			{type range  name length text Length optional 0
minval 1 maxval 255}
		{type option name binary text Binary optional 0}}}

	    float      {nullable 1 defaultable 1 autoincr 0 linked 0
par {
			{type enum name prec text Precision optional 1
values {4 8}}}}

	    decimal    {nullable 1 defaultable 1 autoincr 0 linked 0
par {
		{type range name length text Length    optional 0 minval 1}
		{type range name prec   text Precision optional 0
minval 1}}}
	    float2    {nullable 1 defaultable 1 autoincr 0 linked 1
par {
		{type range name length text Length    optional 1 minval 1}
		{type range name prec   text Precision optional 1
minval 1}}}
	    real      {nullable 1 defaultable 1 autoincr 0 linked 1
par {
		{type range name length text Length    optional 1 minval 1}
		{type range name prec   text Precision optional 1
minval 1}}}
	    double    {nullable 1 defaultable 1 autoincr 0 linked 1
par {
		{type range name length text Length    optional 1 minval 1}
		{type range name prec   text Precision optional 1
minval 1}}}

	    tinyint   {nullable 1 defaultable 1 autoincr 1 linked 0
par {
		{type range  name length   text Length   optional 1
minval 1 maxval 4}
		{type option name unsigned text Unsigned optional 0}
		{type option name zerofill text Zerofill optional 0}}}
	    smallint  {nullable 1 defaultable 1 autoincr 1 linked 0
par {
		{type range  name length   text Length   optional 1
minval 1 maxval 6}
		{type option name unsigned text Unsigned optional 0}
		{type option name zerofill text Zerofill optional 0}}}
	    mediumint {nullable 1 defaultable 1 autoincr 1 linked 0
par {
		{type range  name length   text Length   optional 1
minval 1 maxval 9}
		{type option name unsigned text Unsigned optional 0}
		{type option name zerofill text Zerofill optional 0}}}
	    int       {nullable 1 defaultable 1 autoincr 1 linked 0
par {
		{type range  name length   text Length   optional 1
minval 1 maxval 11}
		{type option name unsigned text Unsigned optional 0}
		{type option name zerofill text Zerofill optional 0}}}
	    integer   {nullable 1 defaultable 1 autoincr 1 linked 0
par {
		{type range  name length   text Length   optional 1
minval 1 maxval 11}
		{type option name unsigned text Unsigned optional 0}
		{type option name zerofill text Zerofill optional 0}}}
	    bigint    {nullable 1 defaultable 1 autoincr 1 linked 0
par {
		{type range  name length   text Length   optional 1
minval 1 maxval 21}
		{type option name unsigned text Unsigned optional 0}
		{type option name zerofill text Zerofill optional 0}}}
	}

	# The following is internal information about types. It is
	# (manually) derived from the above and associates types
	# with a list of their parameters, if they have any. Types
	# without parameters are not part of this map.
	#
	# Used by -> _parseCol to handle the parameters enclosed in
	# parentheses. *ONLY* range and enum parameters are listed.
	# Handling of options is done differently. The order of the
	# parameters is important

	array set slot {
	    __pn,timestamp {length}
	    __pn,char      {length}
	    __pn,varchar   {length}
	    __pn,float     {prec}
	    __pn,float2    {length prec}
	    __pn,real      {length prec}
	    __pn,double    {length prec}
	    __pn,decimal   {length prec}
	    __pn,tinyint   {length}
	    __pn,smallint  {length}
	    __pn,mediumint {length}
	    __pn,int       {length}
	    __pn,integer   {length}
	    __pn,bigint    {length}
	}

	return
    }


    method destroy {} {
	# @c This method is called by the framework ()
	# @c to execute class-specific instance destruction code.
	# @c Here we close the connection to the DBMS created during
	# @c object construction.

	$self dbGenericManager:destroy
	sql disconnect $slot(conn)
	return
    }



    #######################################################
    # meta information helping in configuration of the user
    # interface.
    #######################################################

    method who {which} {
	# @c Returns either the symbolic name of the RDBMS
	# @c (which = -name) used as prefix for special types and
	# @c commands, or a text usable for display by the UI
	# @c layer (which = -text). Other values of 'which' are
	# @c not supported.

	# -W- who -
	# -W- This method should be a generic one, with a
	# -W- corresponding method in the setup section.

	switch -- $which {
	    -name {return mysql}
	    -text {return MySQL}
	    default {error "unknown option $which"}
	}
    }


    method isCommandAllowed {operation opArgs} {
	# @c Similar to 'isCommandAvailable', but its results
	# @c depends not only on the availability of the command,
	# @c but on the specified entity as well.
	#
	# @argument operation: The symbolic name of the operation
	# @argument operation: to check.
	#
	# @argument opArgs: A description of the argument which will
	# @argument opArgs: be given to the method implementing the
	# @argument opArgs: command. The format is described in the
	# @argument opArgs: design section.
	#
	# @result A boolean value. True signals that the command is
	# @result allowed to act upon the specified entity.


	# The general rule followed here is that all operations are
	# allowed, except for entities in the 'myssql' database (and
	# of course this database). The tables in there contain the
	# privilege information used by the server to determine
	# wether a person is allowed to operate on the database or not.

	# -W- isCommandAllowed -
	# -W- The privilege information is currently *not* used here,
	# -W- but it should.

	# Another exception: If a table contains only one column this
	# column cannot be dropped. Drop the table instead!


	set result 1

	switch -- $operation {
	    cmdDbAlter  -
	    cmdDbRename -
	    cmdDbDrop   {
		# opArgs = db
		# protect system database
		set result [string compare $opArgs "mysql"]
	    }

	    cmdTableCreate  -
	    cmdTableDrop    -
	    cmdTableRename  -
	    cmdTableAlter   -

	    cmdColumnCreate -
	    cmdColumnRename -
	    cmdColumnAlter  -

	    cmdIndexCreate  -
	    cmdIndexDrop    -
	    cmdIndexRename  -
	    cmdIndexAlter   {
		#     opArgs = {db table}
		# or  opArgs = {db table column|index}
		#
		# Protect system tables, columns and indices.

		set result [string compare [lindex $opArgs 0] "mysql"]
	    }

	    cmdColumnDrop {
		# opArgs = {db table column}
		#
		# Protect system columns. Additionally disallow
		# the deletion of columns in single-column tables.

		if {![string compare [lindex $opArgs 0] "mysql"]} {
		    # Protect system column

		    set result 0
		} else {
		    set db    [lindex $opArgs 0]
		    set table [lindex $opArgs 1]

		    if {[llength \
[$slot(cache) getColumns $db $table]] == 1} {
			# The column is alone in its table =>can't delete it,
			# the table must be dropped instead

			set result 0
		    }
		}
	    }
	    default {
		# Ignore all unknown operations, allow them all
	    }
	}

	return $result
    }


    #######################################################
    # Handle queries for various items:
    # databases, tables, columns and indices
    #######################################################


    method getDatabases {} {
	# @c Returns the list of all databases found in the RDBMS
	# @c the concrete instance is connected to.

	# Check the cache first. If it is empty delegate the task
	# to the DBMS and populate the cache afterward.

	if {[$slot(cache) getDatabases] == {}} {
	    $slot(cache) setDatabases \
		    [lsort [$self _sqlGetRows "show databases"]]
	}

	return [$slot(cache) getDatabases]
    }


    method getTables {db} {
	# @c Returns the list of all tables found in the RDBMS
	# @c the concrete instance is connected to, for a specific
	# @c database.
	#
	# @argument db: The name of the database to look at.

	# Check the cache first. If it is empty delegate the task
	# to the DBMS and populate the cache afterward.

	if {![$slot(cache) hasTables $db]} {
	    $slot(cache) setTables \
		    $db [$self _sqlGetRows "show tables from $db"]
	}

	return [$slot(cache) getTables $db]
    }


    method getColumns {db_table} {
	# @c Returns the list of all columns found in the RDBMS
	# @c the concrete instance is connected to, for a specific
	# @c combination of database and table.
	#
	# @argument db_table: A 2-element list containing the names
	# @argument db_table: of database and table to look at, in
	# @argument db_table: this order.

	set db    [lindex $db_table 0]
	set table [lindex $db_table 1]

	# Check the cache first. If it is empty delegate the task
	# to the DBMS and populate the cache afterward.

	if {![$slot(cache) hasColumns $db $table]} {
	    $self _getTableDefinition $db $table
	}

	return [$slot(cache) getColumns $db $table]
    }


    method getIndices {db_table} {
	# @c Returns the list of all indices found in the RDBMS
	# @c the concrete instance is connected to, for a specific
	# @c combination of database and table.
	#
	# @argument db_table: A 2-element list containing the names
	# @argument db_table: of database and table to look at, in
	# @argument db_table: this order.

	set db    [lindex $db_table 0]
	set table [lindex $db_table 1]

	# Check the cache first. If it is empty delegate the task
	# to the DBMS and populate the cache afterward.

	if {![$slot(cache) hasIndices $db $table]} {
	    $self _getTableDefinition $db $table
	}

	return [$slot(cache) getIndices $db $table]
    }


    method getTableDefinition {db_table} {
	# @c Returns the definition of the specified table, see the
	# @c design section for an explanation about the structure
	# @c of the result.
	#
	# @argument db_table: A 2-element list containing the names
	# @argument db_table: of database and table to look at, in
	# @argument db_table: this order.

	set db    [lindex $db_table 0]
	set table [lindex $db_table 1]

	# Check the cache first. If it is empty delegate the task
	# to the DBMS and populate the cache afterward.

	if {![$slot(cache) hasTable $db $table]} {
	    $self _getTableDefinition $db $table
	}

	# The information about the table is a bit scattered in the
	# cache. Collect the data about its columns first, then
	# about its indices. These are then put together into the
	# final specification.

	set cd {}
	foreach c [$slot(cache) getColumns $db $table] {
	    lappend cd $c [$slot(cache) getColumn $db $table $c]
	}

	set id {}
	foreach i [$slot(cache) getIndices $db $table] {
	    lappend id $i [$slot(cache) getIndex $db $table $i]
	}

	return [list columns $cd indices $id]
    }


    method getColumnDefinition {db_table_col} {
	# @c Returns the definition of the specified column, see the
	# @c design section for an explanation about the structure
	# @c of the result.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: names of database, table and
	# @argument db_table_col: column to look at, in this order.

	set db     [lindex $db_table_col 0]
	set table  [lindex $db_table_col 1]
	set column [lindex $db_table_col 2]

	# Check the cache first. If it is empty delegate the task
	# to the DBMS and populate the cache afterward.

	if {![$slot(cache) hasColumn $db $table $column]} {
	    $self _getTableDefinition $db $table
	}

	return [$slot(cache) getColumn $db $table $column]
    }


    method getIndexDefinition {db_table_idx} {
	# @c Returns the definition of the specified index, see the
	# @c design section for an explanation about the structure
	# @c of the result.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: names of database, table and
	# @argument db_table_idx: index to look at, in this order.

	set db    [lindex $db_table_idx 0]
	set table [lindex $db_table_idx 1]
	set index [lindex $db_table_idx 2]

	# Check the cache first. If it is empty delegate the task
	# to the DBMS and populate the cache afterward.

	if {![$slot(cache) hasIndex $db $table $index]} {
	    $self _getTableDefinition $db $table
	}

	return [$slot(cache) getIndex $db $table $index]
    }


    method getIndexColumns {db_table_idx} {
	# @c A convenience method, returns a list containing the
	# @c names of all columns used by the specified index.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: names of database, table and
	# @argument db_table_idx: index to look at, in this order.

	# spec = {unique columnlist}

	return [lindex [$self getIndexDefinition $db_table_idx] 1]
    }


    method getIndexUniqueness {db_table_idx} {
	# @c A convenience method, returns a boolean value
	# @c indicating wether the specified index is unique or not.
	# @c True signals uniqueness.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: names of database, table and
	# @argument db_table_idx: index to look at, in this order.

	# spec = {unique columnlist}

	return [lindex [$self getIndexDefinition $db_table_idx] 0]
    }



    #######################################################
    # Manipulation of DBMS entities.
    # - Databases
    #######################################################


    method dbCreate {db} {
	# @c Creates a new database. Associated command and event
	# @c are 'cmdDbCreate' and 'evDbCreate'.
	#
	# @argument db: The name of the new database.

	if {[$slot(cache) hasDatabase $db]} {
	    error "database $db already in existence"
	}

	# The task is delegated to the DBMS, the cache updated
	# afterward. At last the associated event is posted.

	$self _sqlExec "create database $db"
	$slot(cache) addDatabase $db

	$self notify [list evDbCreate $db]
	return
    }


    method dbDrop {db} {
	# @c Destroys the specified database. Associated command
	# @c and event are 'cmdDbDrop' and 'evDbDrop'.
	#
	# @argument db: The name of the database to destroy.

	if {![$slot(cache) hasDatabase $db]} {
	    error "database $db does not exist"
	}

	# The task is delegated to the DBMS, the cache updated
	# afterward. At last the associated event is posted.

	$self _sqlExec "drop database $db"
	$slot(cache) clearDatabase $db

	$self notify [list evDbDrop $db]
	return
    }


    method dbRename {db newName} {
	# @c Changes the name of the specified database. Associated
	# @c command and event are 'cmdDbRename' and 'evDbRename'.
	#
	# @argument db: The name of the database to rename.
	# @argument newName: The new name of the database.

	error "dbRename: impossible operation"
    }



    #######################################################
    # Manipulation of DBMS entities.
    # - Tables
    #######################################################


    method tableCreate {db_table spec} {
	# @c Creates a new table. Associated command and event
	# @c are 'cmdTableCreate' and 'evTableCreate'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: new table, in this order.
	#
	# @argument spec: The specification of the table to create.

	# 'tableAlter' requires this functionality too, for
	# restauration of the old definition in case of a failure.
	# The difference is that *no* events must be posted then.
	# Because of this an internal method having an additional
	# parameter controlling the posting of events is used.

	$self _tableCreate $db_table $spec 1
	return
    }


    method _tableCreate {db_table spec events} {
	# @c Creates a new table. Associated command and event
	# @c are 'cmdTableCreate' and 'evTableCreate'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: new table, in this order.
	#
	# @argument spec: The specification of the table to create.
	#
	# @argument events: Boolean value, true allows posting of
	# @argument events: the associated event.

	set db    [lindex $db_table 0]
	set table [lindex $db_table 1]

	if {[$slot(cache) hasTable $db $table]} {
	    error "table {$db $table} already in existence"
	}

	# The generic table description is converted into the
	# equivalent SQL code, then given to the creation command
	# send to the DBMS. The cache is updated afterward, at last
	# the associated event is posted, if allowed that is.
	#
	# Note: MySQL has the concept of a 'current' database. All
	# commands without a database name in them implicitly
	# operate on this one. Because of this the database we want
	# to operate on is set as the current one before actually
	# sending the sql command.

	set spec [$self _cvtTableSpec $spec]

	$self _selectDb $db
	$self _sqlExec  "create table ${table} ($spec)"

	$slot(cache) addTable $db $table

	if {$events} {
	    $self notify [list evTableCreate $db $table]
	}
	return
    }


    method tableDrop {db_table} {
	# @c Destroys the specified table. Associated command
	# @c and event are 'cmdTableDrop' and 'evTableDrop'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: table to destroy, in this order.

	# 'tableAlter' requires this functionality too.
	# The difference is that *no* events must be posted then.
	# Because of this an internal method having an additional
	# parameter controlling the posting of events is used.

	$self _tableDrop $db_table 1
	return
    }


    method _tableDrop {db_table events} {
	# @c Destroys the specified table. Associated command
	# @c and event are 'cmdTableDrop' and 'evTableDrop'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: table to destroy, in this order.
	#
	# @argument events: Boolean value, true allows posting of
	# @argument events: the associated event.

	set db    [lindex $db_table 0]
	set table [lindex $db_table 1]

	if {![$slot(cache) hasTable $db $table]} {
	    error "table {$db $table} does not exist"
	}

	# The task is delegated to the DBMS, the cache updated
	# afterward. At last the associated event is posted.
	# If allowed, that is.

	$self _selectDb $db
	$self _sqlExec  "drop table ${table}"

	$slot(cache) clearTable $db $table

	if {$events} {
	    $self notify [list evTableDrop $db $table]
	}
	return
    }


    method tableRename {db_table newName} {
	# @c Changes the name of the specified table. Associated
	# @c command and event are 'cmdTableRename' and
	# @c 'evTableRename'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: table to rename, in this order.
	#
	# @argument newName: The new name of the table.

	# 'tableAlter' requires this functionality too.
	# The difference is that *no* events must be posted then.
	# Because of this an internal method having an additional
	# parameter controlling the posting of events is used.

	$self _tableRename $db_table $newName 1
	return
    }


    method _tableRename {db_table newName events} {
	# @c Changes the name of the specified table. Associated
	# @c command and event are 'cmdTableRename' and
	# @c 'evTableRename'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: table to rename, in this order.
	# @argument newName: The new name of the table.
	#
	# @argument events: Boolean value, true allows posting of
	# @argument events: the associated event.

	#puts "db_table=<$db_table> newName=<$newName>"

	set db    [lindex $db_table 0]
	set table [lindex $db_table 1]

	if {![$slot(cache) hasTable $db $table]} {
	    error "table {$db $table} does not exist"
	}

	# The task is delegated to the DBMS, the cache updated
	# afterward. At last the associated event is posted.
	# If allowed, that is.

	set cmd "alter table ${table} rename as ${newName}"

	$self _selectDb $db
	$self _sqlExec  $cmd

	$slot(cache) renameTable $db $table $newName

	if {$events} {
	    $self notify [list evTableRename $db $table $newName]
	}
	return
    }


    method tableAlter {db_table newName newSpec} {
	# @c Changes the definition of the specified table.
	# @c Associated command and event are 'cmdTableAlter'
	# @c and 'evTableAlter'.
	#
	# @argument db_table: A 2-element list containing the name
	# @argument db_table: of the database and the name of the
	# @argument db_table: table to change, in this order.
	#
	# @argument newName: The new name of the table.
	# @argument newSpec: The new specification of the table.

	# drop and recreate, don't send out events
	# the latter is done here.

	set db    [lindex $db_table 0]
	set table [lindex $db_table 1]

	# Changing the specification of a table is not som simple.
	#
	# First we remember the old specification, to have it handy
	# in case of problems. Then we destroy the old table
	# (*including its contents*) and generate a new one its place,
	# the latter having the new structure. In case of a changed
	# name we do a rename afterward. At last we have to update
	# the cache and post the associated event.
	#
	# In case of problems reported by the DBMS we recreate the
	# old definition and refill the cache. Only after that we
	# are allowed to report the error upward.

	set oldSpec [$self getTableDefinition $db_table]

	$self _tableDrop   $db_table 0
	set fail [catch {$self _tableCreate $db_table $newSpec 0} error]
	if {$fail} {
	    # recreate old definition in case of failure. this destroys
	    # the table contents, but this is no problem, as a sucessful
	    # alteration would have done so too.

	    $self _tableCreate        $db_table $oldSpec 0
	    $self _getTableDefinition $db $table
	    error $error
	}

	if {"$table" != "$newName"} {
	    $self _tableRename $db_table $newName 0
	}

	$self _getTableDefinition $db $newName
	$self notify [list evTableAlter $db $table $newName]
	return
    }



    #######################################################
    # Manipulation of DBMS entities.
    # - Columns
    #######################################################


    method columnCreate {db_table_col spec} {
	# @c Creates a new column. Associated command and event
	# @c are 'cmdColumnCreate' and 'evColumnCreate'.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: name of the database, the table
	# @argument db_table_col: and the name of the new column,
	# @argument db_table_col: in this order.
	#
	# @argument spec: The type specification to associate with
	# @argument spec: the column.

	set db     [lindex $db_table_col 0]
	set table  [lindex $db_table_col 1]
	set column [lindex $db_table_col 2]

	if {[$slot(cache) hasColumn $db $table $column]} {
	    error "column {$db $table $column} already in existence"
	}

	# The generic type specification is converted into the
	# equivalent SQL code, then given to the creation command
	# send to the DBMS. The cache is updated afterward, at last
	# the associated event is posted.

	set colSpec [$self convertGenericType2Sql $spec]
	set cmd     "alter table $table add column $column $colSpec"

	$self _selectDb $db
	$self _sqlExec  $cmd

	$slot(cache) addColumn $db $table $column
	$slot(cache) setColumn $db $table $column $spec

	$self notify [list evColumnCreate $db $table $column]
	return
    }


    method columnDrop {db_table_col} {
	# @c Destroys the specified column. Associated command and
	# @c event are 'cmdColumnDrop' and 'evColumnDrop'.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: name of the database, the table
	# @argument db_table_col: and the name of the column to
	# @argument db_table_col: destroy, in this order.

	set db     [lindex $db_table_col 0]
	set table  [lindex $db_table_col 1]
	set column [lindex $db_table_col 2]

	if {![$slot(cache) hasColumn $db $table $column]} {
	    error "column {$db $table $column} does not exist"
	}

	# The task is delegated to the DBMS, afterward we update
	# the cache and post the associated event.
	#
	# The only special thing is the addtional posting of
	# 'evColumnDrop' for all indices which had to be dropped
	# too, as they lost their one and only remaining column.
	# This destruction happened automatically in the database,
	# but is detected here during the cache-update. Without
	# the cache we would have no way of detecting this
	# condition short of rereading the complete table
	# specification.

	set cmd "alter table ${table} drop column ${column}"

	$self _selectDb $db
	$self _sqlExec  $cmd

	set lostIndices [$slot(cache) clearColumn $db $table $column]

	$self notify [list evColumnDrop $db $table $column]

	foreach index $lostIndices {
	    $self notify [list evIndexDrop $db $table $index]
	}

	return
    }


    method columnRename {db_table_col newName} {
	# @c Changes the name of the specified column. Associated
	# @c command and event are 'cmdColumnRename'
	# @c and 'evColumnRename'.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: name of the database, the table
	# @argument db_table_col: and the name of the column to
	# @argument db_table_col: rename, in this order.
	#
	# @argument newName: The new name of the column.

	set db     [lindex $db_table_col 0]
	set table  [lindex $db_table_col 1]
	set column [lindex $db_table_col 2]

	# Renaming a column was not considered as separate operation,
	# only a complete 'alter' is possible. Using the information
	# stored in the cache it is possible to regenerate the type
	# specification, thereby restricting the 'alter' to the name.

	$self _columnAlter evColumnRename $db_table_col $newName \
		[$slot(cache) getColumn $db $table $column]
	return
    }


    method columnAlter {db_table_col newName newSpec} {
	# @c Changes the definition of the specified column.
	# @c Associated command and event are 'cmdColumnAlter'
	# @c and 'evColumnAlter'.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: name of the database, the table
	# @argument db_table_col: and the name of the column to
	# @argument db_table_col: change, in this order.
	#
	# @argument newName: The new name of the column.
	# @argument newSpec: The new specification of the column.

	# The task at hand is delegated to the internal method
	# handling this (and 'rename').

	$self _columnAlter evColumnAlter $db_table_col $newName $newSpec
	return
    }


    method _columnAlter {event db_table_col newName newSpec} {
	# @c Changes the definition of the specified column.
	# @c Associated command and event are 'cmdColumnAlter'
	# @c and 'evColumnAlter'. Or 'cmdColumnRename' and
	# @c and 'evColumnReaname' as this method handles both
	# @c situations.
	#
	# @argument event: The name of the event to post after
	# @argument event: completion of the task. Is an
	# @argument event: indication of the caller too.
	#
	# @argument db_table_col: A 3-element list containing the
	# @argument db_table_col: name of the database, the table
	# @argument db_table_col: and the name of the column to
	# @argument db_table_col: change, in this order.
	#
	# @argument newName: The new name of the column.
	# @argument newSpec: The new specification of the column.

	set db     [lindex $db_table_col 0]
	set table  [lindex $db_table_col 1]
	set column [lindex $db_table_col 2]

	if {![$slot(cache) hasColumn $db $table $column]} {
	    error "column {$db $table $column} does not exist"
	}

	# The generic type specification is converted into the
	# equivalent SQL code, then given to the change command
	# send to the DBMS. The cache is updated afterward, at last
	# the specified event is posted.

	set colSpec [$self convertGenericType2Sql $newSpec]
	set cmd \
"alter table ${table} change column $column $newName $colSpec"

	$self _selectDb $db
	$self _sqlExec  $cmd

	if {"$column" != "$newName"} {
	    $slot(cache) renameColumn $db $table $column $newName
	}

	$slot(cache) setColumn $db $table $newName $newSpec

	$self notify [list $event $db $table $column $newName]
	return
    }



    #######################################################
    # Manipulation of DBMS entities.
    # - Indices
    #######################################################


    method indexCreate {db table index spec} {
	# @c Creates a new index. Associated command and event
	# @c are 'cmdIndexCreate' and 'evIndexCreate'.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: name of the database, the table
	# @argument db_table_idx: and the name of the new index,
	# @argument db_table_idx: in this order.
	#
	# @argument spec: The specification of the new index.

	error "indexCreate: impossible operation"

	# -W- indexCreate -
	# -W- The statement above might not be true. Adding the
	# -W- 'spec' to the cached table specification and the
	# -W- executing a 'tableAlter' using that might do the
	# -W- trick. But this destroys the table contents,
	# -W- something which should not be necessary as the
	# -W- primary goal is the *addition* of more information.

	# -W- Hm, if I decide to implement the operation in this
	# -W- destructive way then I should expand the generic
	# -W- interface too, to allow the markup of such dangerous
	# -W- operations in the UI. All commands must a have
	# -W- command description then, even the standard ones.
    }


    method indexDrop {db_table_idx} {
	# @c Destroys the specified index. Associated command and
	# @c event are 'cmdIndexDrop' and 'evIndexDrop'.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: name of the database, the table
	# @argument db_table_idx: and the name of the index to
	# @argument db_table_idx: destroy, in this order.

	set db     [lindex $db_table_idx 0]
	set table  [lindex $db_table_idx 1]
	set index  [lindex $db_table_idx 2]

	# The task at hand is delegated to the DBMS, the cache
	# updated afterward, at last we post the associated event.
	# Beware, MySQL distinguishes between the index associated
	# to the primary key of the table and all else.

	if {"$index" == "PRIMARY"} {
	    set cmd "alter table ${table} drop primary key"
	} else {
	    set cmd "alter table ${table} drop index ${index}"
	}

	$self _selectDb $db
	$self _sqlExec  $cmd

	$slot(cache) clearIndex $db $table $index

	$self notify [list evIndexDrop $db $table $index]
	return
    }


    method indexRename {db_table_idx newName} {
	# @c Changes the name of the specified index. Associated
	# @c command and event are 'cmdIndexRename'
	# @c and 'evIndexRename'.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: name of the database, the table
	# @argument db_table_idx: and the name of the index to
	# @argument db_table_idx: rename, in this order.
	#
	# @argument newName: The new name of the index.

	error "indexRename: impossible operation"

	# -W- indexRename -
	# -W- See '-W-' of 'indexCreate'.
    }


    method indexAlter {db table index newSpec} {
	# @c Changes the definition of the specified index.
	# @c Associated command and event are 'cmdIndexAlter'
	# @c and 'evIndexAlter'.
	#
	# @argument db_table_idx: A 3-element list containing the
	# @argument db_table_idx: name of the database, the table
	# @argument db_table_idx: and the name of the index to
	# @argument db_table_idx: change, in this order.
	#
	# @argument newName: The new name of the index.
	# @argument newSpec: The new specification of the index.

	error "indexAlter: impossible operation"

	# -W- indexAlter -
	# -W- See '-W-' of 'indexCreate'.
    }



    #######################################################
    # internal helper procedures

    method _getTableDefinition {db table} {
	# @c This method is called by various query methods in the
	# @c provided interface to get at the specification of the
	# @c given table. All results are stored in the cache.
	#
	# @argument db:    The name of the database to look ak.
	# @argument table: The name of the table whose specification
	# @argument table: is asked for.

	# One, ask the database for information about the columns
	# the table is made of. This information is then split into
	# its pieces and converted into the generic representation.
	# The main work of this is done in '_parseCol'. Its results
	# are immediately added to the cache. The column names are
	# collected in a list which is placed in the cache after the
	# loop.

	set columns {}
	foreach column \
[$self _sqlGetRows "show columns from $table from $db"] {
	    # MySQL returns six fields per row.
	    #
	    # These are: column name, type, nullable, key info,
	    #            default, xtra
	    #
	    # The 'key' information is ignored here, we use the
	    # 'show keys' command later to get a more detailed
	    # description of this aspect.

	    set name [lindex $column 0]
	    set type [lindex $column 1]
	    set null [lindex $column 2]
	    # 'key' at position      3 is ignored
	    set def  [lindex $column 4]
	    set xtra [lindex $column 5]

	    lappend columns $name

	    $slot(cache) setColumn $db $table $name \
		    [$self _parseCol $type $null $def $xtra]
	}

	$slot(cache) setColumns $db $table $columns

	# Two, ask the database for information about the columns
	# the table is made of. We will get a row for each column of
	# each index associated to the table. Because of this an
	# array is used to collect the column list of all indices
	# during the loop. A second loop then adds this information
	# to the information entered during the first loop.
	#
	# Note: The order of the rows for one index is important, it
	#       is the order of usage for the columns in the index.

	set indices {}
	array_def icol

	foreach index \
[$self _sqlGetRows "show keys from $table from $db"] {
	    # MySQL returns 8 fields per row:
	    #
	    # These are: table, not!unique, name, seq, colName,
	    #            collate, card, subpart

	    set table [lindex $index 0]
	    set uniq  [expr {![lindex $index 1]}]
	    set name  [lindex $index 2]
	    set seq   [lindex $index 3]
	    set colN  [lindex $index 4]
	    set coll  [lindex $index 5]
	    set card  [lindex $index 6]
	    set subp  [lindex $index 7]

	    # Note: Indices having more than one column are entered
	    # multiple times! (*) This is no problem for 'setIndex',
	    # but the list of names must be corrected later.

	    lappend indices $name
	    $slot(cache) setIndex $db $table $name [list $uniq {}]

	    # Here we remember the columns, they are added later.
	    lappend icol($name) $colN
	}

	# See (*) above, weed out the duplicates, ...

	set indices [luniq [lsort $indices]]

	$slot(cache) setIndices $db $table $indices

	# ... and add the column lists to the indices in the cache.

	foreach index $indices {
	    set iDef [$slot(cache) getIndex $db $table $index]

	    lexchange iDef 1 $icol($index)
	    $slot(cache) setIndex $db $table $index $iDef
	}

	unset icol
	return
    }


    method _cvtTypeDependentData {type pArray} {
	# @c This method converts a type and its specific parameters
	# #c into the equivalent SQL representation.
	#
	# @argument type:   The name of the type to convert.
	# @argument pArray: The name of an array holding the
	# @argument pArray: parameter information of the type
	# @argument pArray: specification.

	upvar $pArray par

	# The conversion process is simple. We just use the typename
	# to dispatch the correct set of actions.

	switch -- $type {

	    blob       -
	    tinyblob   -
	    longblob   -
	    mediumblob -
	    date       -
	    time       -
	    datetime   {
		# Unparameterized types, just return the name.
		return $type
	    }

	    timestamp {
		# A single required parameter
		return "${type}($par(length))"
	    }

	    char      -
	    varchar   {
		# Two parameters: 'length', 'binary'.
		# The 2nd is a boolean option.

		if {$par(binary)} {
		    return "${type}($par(length)) binary"
		} else {
		    return "${type}($par(length))"
		}
	    }

	    float {
		# A single optional parameter.
		if {$par(prec) == {}} {
		    return $type
		} else {
		    return "${type}($par(prec))"
		}
	    }

	    float2 -
	    real   -
	    double {
		if {"$type" == "float2"} {
		    # 'float2' is essentially 'float', but with a
		    # different set of parameters! This was handled
		    # here by splitting the definition into two
		    # separate types. If such constructions are
		    # common I might need to think about a type
		    # description format with the ability to handle
		    # more than one parameter set.

		    set type float
		}

		# The length and precision parameters are optional,
		# but linked. That is, either both of them are
		# missing or none. The UI knows this too.

		set len  $par(length)
		set prec $par(prec)

		if {($len == {}) || ($prec == {})} {
		    return $type
		} else {
		    return "${type}($len,$prec)"
		}
	    }

	    decimal {
		# The same set of parameters as for 'double' and
		# consorts, but here are they required.

		set len  $par(length)
		set prec $par(prec)

		return "${type}($len,$prec)"
	    }

	    tinyint   -
	    smallint  -
	    mediumint -
	    int       -
	    integer   -
	    bigint    {
		# 3 parameters, an optional length and 2 boolean options.

		set length   $par(length)
		set unsigned $par(unsigned)
		set zerofill $par(zerofill)

		if {$length != {}} {append type "($length)"}
		if {$unsigned}     {append type " unsigned"}
		if {$zerofill}     {append type " zerofill"}
		return $type
	    }

	    mysql-enum -
	    mysql-set  {
		# The special types. Stripping the database prefix
		# reveals their true names. Their parameter is a
		# list of strings, added in parentheses to the
		# base type. The loop has no need to handle the last
		# element in the list differently than the others,
		# the superfluous comma is just trimmed off afterward.

		regsub -- {^mysql-} $type {} type

		append type "("
		foreach v $par(values) {
		    append type "\"$v\","
		}
		set type [string trimright $type ,]
		append type ")"

		return $type
	    }
	}
    }


    method convertGenericType2Sql {spec} {
	# @c This method converts a generic type specification into
	# @c the coressponding SQL syntax used by the RDBMS. It is
	# @c exported for usage by the table editor
	# @c () as its output is more readble
	# @c than the generic representation.

	# The first element of the 'spec' is the name of the type.
	# This is followed by its specification in 'array set'
	# format.

	set       type [lshift spec]
	array set par  $spec

	# From now on 'spec' is used to hold the SQL representation
	# of the specified type. The first part comes from the
	# conversion of the type-specific parameters.

	set spec [$self _cvtTypeDependentData $type par]

	# After that we place the information gathered from the
	# standard options (null, default and autoincr).

	if {!$par(null)} {
	    append spec " not null"
	}

	if {$par(default) != {}} {
	    # Force handling of default value as string for some types.
	    if {[regexp -- \
{^(mysql-enum|mysql-set|char|varchar)} $type]} {
		set par(default) "\"$par(default)\""
	    }

	    append spec " default $par(default)"
	}

	if {$par(autoincr)} {
	    append spec " auto_increment"
	}

	return $spec
    }


    method _cvtTableSpec {tableSpec} {
	# @c This method converts a table specification into the
	# @c equivalent SQL representation.
	#
	# @argument tableSpec: The specification of a table
	# @argument tableSpec: (without name), in the format as
	# @argument tableSpec: described in the design section.

	# tableSpec = {columns {..} indices {..}}
	#                       ^^ == columnName columnType
	#    indexName {unique colName..} == ^^

	# First, parse the specification into its parts, place the
	# result into an array. Then convert the column
	# specifications and add them to the sql string. At last
	# loop over the index specifications and convert them too.
	# Superfluous commata at the end are removed.

	array set tSpec $tableSpec

	set sql ""
	foreach {col type} $tSpec(columns) {
	    append sql "$col [$self convertGenericType2Sql $type],"
	}

	foreach {idx idxDef} $tSpec(indices) {
	    # idxDef = {unique {column...}}

	    set unique  [lindex $idxDef 0]
	    set columns [lindex $idxDef 1]

	    set idxCmd [expr {$unique ? "unique": "index"}]
	    append sql "$idxCmd $idx ([join $columns ,]),"
	}

	return [string trimright $sql ,]
    }



    method _parseCol {type null def xtra} {
	# @c The main method to convert the information given by
	# @c 'show columns' into the generic type specification.
	#
	# @argument type: The base type and its parameters.
	# @argument null: empty or 'YES'. The latter signals a
	# @argument null: nullable column.
	# @argument def:  empty or the default value
	# @argument xtra: empty or 'auto_increment'

	# General syntax of 'type':
	#  [ '(' ,... ')' ] ['unsigned'] ['zerofill']
	#  [ '(' ,... ')' ] ['binary']
	#
	# For types 'enum' and 'set' the parenthesised list of
	# values does not contain parameters but is a single
	# parameter, the list of allowed strings.

	# tSpec is the array we are building the specification in.

	# 1. Standard configuration options:
	#    default value, (not) null, auto_increment 

	array set tSpec {null 0 autoincr 0 default {}}

	if {"$null" == "YES"}            {set tSpec(null)     1}
	if {"$xtra" == "auto_increment"} {set tSpec(autoincr) 1}
	if {$def    != {}}               {set tSpec(default)  $def}

	# Now we take a look at the boolean options behind base
	# name and possible parenthises. We have to differentiate
	# between 'int' and 'char' types, 'string match' is used
	# to get a that information (we have not extracted the base
	# name yet).

	set type [split $type]

	# Initialize the options associated to the type.

	if {[string match *int* [lindex $type 0]]} {
	    array set tSpec {unsigned 0 zerofill 0}
	} elseif {[string match *char* [lindex $type 0]]} {
	    array set tSpec {binary 0}
	}

	if {[llength $type] > 1} {
	    # We have options! Remember the information before
	    # them, then set their corresponding array entries.

	    set baseType [lshift type]

	    foreach option $type {
		set tSpec($option) 1
	    }

	    set type $baseType
	    unset baseType
	}

	# At last we handle the 2nd set of parameters stored as part
	# of the type (immediately behind the basename, in the
	# parentheses). In the special case of 'enum' and 'set' the
	# list is a single parameter. These types must be sent out
	# with the database name as prefix too.

	if {[regexp -- {^(enum|set)} $type]} {
	    # The next two commands change the string into a comma
	    # separated list, with the type name as its first
	    # element

	    regsub -- {\(} $type {,} type
	    regsub -- {\)} $type {}  type

	    set type     [split $type ,]
	    set typeName [lshift type]

	    # Remember the values found, but without the
	    # surrounding ' or "    ->" fake out tcl mode in emacs
	    set tSpec(values) {}
	    foreach v $type {
		lappend tSpec(values) [string trim $v {'"}] ;#"
	    }

	    set   type mysql-$typeName
	    unset typeName

	} elseif {[regexp -- {\(} $type]} {
	    # The next two commands change the string into a comma
	    # separated list, with the type name as its first
	    # element

	    regsub -- {\(} $type {,} type
	    regsub -- {\)} $type {}  type

	    set type     [split $type ,]
	    set typeName [lshift type]

	    # 'float' has two different parameter sets. They are
	    # realized as 2 separate types. The variant having 2
	    # parameters is shown as 'float2' in the UI.

	    if {("$typeName" == "float") && ([llength $type] > 1)} {
		set typeName float2
	    }

	    # The mapping from types to their list of range/enum-
	    # parameters (__pn,*, see 'init') is used here to
	    # associate parameter position and parameter name.

	    foreach pv $type pn $slot(__pn,$typeName) {
		set tSpec($pn) $pv
	    }

	    set type $typeName
	    unset typeName
	}

	# Finishing touches: Convert the array into a list and place
	# the typename before all else.

	set result [array get tSpec]
	lunshift result $type

	return $result
    }



    #######################################################
    # Handling of special types (enum, set)
    #
    #######################################################


    method specialType {what parentWin typeName args} {
	# @c This method is called by the type editor to manage the
	# @c UI of special types.
	#
	# @argument what:      The operation to execute. Must be one
	# @argument what:      of 'createPane', 'getDef', 'setDef'
	# @argument what:      and 'setState'.
	# @argument parentWin: The widget to place the generated UI
	# @argument parentWin: into.
	# @argument typeName:  The name of the special type to handle.
	# @argument args:      Other arguments, special to chosen
	# @argument args:      subcommand. Described in the design
	# @argument args:      section.

	# Dispatch to an internal method according to the chosen
	# subcommand.

	return [eval $self _st_$what $parentWin $typeName $args]
    }

    method _st_createPane {parentWin typeName} {
	# @c Internal method to create the parameter specification
	# @c panel for special types. These are 'enum' and 'set'.
	# @c Both use the same type of panel for their specification.
	#
	# @argument parentWin: The widget to place the generated UI into.
	# @argument typeName:  The name of the special type to handle.

	set w $parentWin

	# The structure created here is very simple:
	#
	# A listbox (with scrollbar) on the left, an entry field on
	# the right, two buttons for shifting values between the two
	# in the middle.
	#
	# The listbox contains all enum/set values already specified.
	# The entry field allows entering new values.
	# The buttons move values between the 2 areas.

	listbox      $w.lb -selectmode single -height 10
	scrollbar    $w.sb
	multi_scroll $w.sb y $w.lb

	frame  $w.bt
	button $w.add -text < -command [list $self _st_add $w]
	button $w.sub -text > -command [list $self _st_sub $w]

	entry  $w.string -width 20 -relief sunken -bd 2

	pack $w.string -side top  -fill both -expand 1
	pack $w.sb     -side left -fill both -expand 0
	pack $w.lb     -side left -fill both -expand 1
	pack $w.bt     -side left -fill both -expand 0 \
-ipadx 1m -ipady 1m

	pack $w.add -side top -fill both -expand 0 -in $w.bt
	pack $w.sub -side top -fill both -expand 0 -in $w.bt
	return
    }


    method _st_setState {parentWin typeName state} {
	# @c Internal method to set the state of the parameter
	# @c specification panel for special types.
	#
	# @argument parentWin: The widget containing the panel.
	# @argument typeName:  The name of the special type to handle.
	# @argument state:     The state to set, either 'normal' or
	# @argument state:     'disabled'.

	set w $parentWin

	$w.add    configure -state $state
	$w.sub    configure -state $state
	$w.string configure -state $state

	# $w.lb, $w.sb were not forgotten. They don't understand
	# state. And should not be switched off too.

	return
    }


    method _st_getDef {parentWin typeName} {
	# @c Internal method. Returns the specified values for
	# @c inclusion into the full type specification.
	#
	# @argument parentWin: The widget containing the panel.
	# @argument typeName:  The name of the special type to handle.

	return [list values [$parentWin.lb get 0 end]]
    }


    method _st_setDef {parentWin typeName tSpec} {
	# @c Internal method. Extracts the relevant values from the
	# @c given type specification and puts them into the panel.
	#
	# @argument parentWin: The widget containing the panel.
	# @argument typeName:  The name of the special type to handle.
	# @argument tSpec:     Name of the array variable containing
	# @argument tSpec:     the type specification.

	# Beware: 'upvar 2' is required to skip over stackframe of
	#         'specialType'.

	upvar 2 $tSpec t
	set w $parentWin

	$w.lb delete 0 end
	eval $w.lb insert end $t(values)
	return
    }


    method _st_add {w} {
	# @c Internal method, not used 'specialType'. The callback
	# @c of the '<'-button in the created panels. Transfers the
	# @c value in the entry to the listbox.
	#
	# @argument w: The widget containing the panel.

	# Get the entry value and append it to the listbox. But only
	# if it is not already part of the list.

	set new [$w.string get]
	if {[lsearch [$w.lb get 0 end] $new] < 0} {
	    $w.lb insert end $new
	    $w.string selection clear
	    $w.string selection range 0 end
	}
	return
    }


    method _st_sub {w} {
	# @c Internal method, not used 'specialType'. The callback
	# @c of the '<'-button in the created panels. Transfers the
	# @c selected value in the listbox to the entry.
	#
	# @argument w: The widget containing the panel.

	# Nothing is done if no selection was made.

	set sel [$w.lb curselection]
	if {$sel == {}} {
	    return
	}

	# The selected value is retrieved, removed from the listbox
	# and then inserted into the entry field.

	set text [$w.lb get $sel]
	$w.lb delete $sel

	$w.string delete 0 end
	$w.string insert 0 $text
	return
    }


    #######################################################
    # Wrappers around code actually executing sql commands,
    # to do logs, notifications, and the like.

    member lastDatabase ""

    method _selectDb {db} {
	# @c Internal method. Used by all methods operating on the
	# @c RDBMS to announce the database they will be working
	# @c with.
	#
	# @argument db: The name of the database to work with.

	# Note: MySQL has the concept of a 'current' database. All
	# commands without a database name in them implicitly
	# operate on this one. Because of this the database we want
	# to operate on is set as the current one before actually
	# sending the sql command.

	# Nothing is done if the database dis not change.

	if {"$db" == "$slot(lastDatabase)"} {
	    return
	}

	# The task is delegated to the RDBMS. In case of success the
	# new name is recorded for further invocations and the
	# associated event posted.
	#
	# Here we have to use some special commands of the DBMS
	# accessor library, not SQL.

	syslog info sql selectdb $slot(conn) $db
	sql             selectdb $slot(conn) $db

	set slot(lastDatabase)               $db

	$self notify [list evSqlSelectedDb   $db]
	return
    }


    method _sqlExec {script} {
	# @c Internal method. Wrapped around the execution of
	# @c arbitrary sql commands, logs them, and posts the
	# @c associated event.
	#
	# @argument script: The sql command to execute.

	syslog info sql exec $slot(conn) $script
	sql exec             $slot(conn) $script

	$self notify [list evSqlExecuted $script]
	return
    }


    method _sqlGetRows {query} {
	# @c Internal method. Wrapped around all sql command
	# @c returning a set of rows. Logs the command, posts the
	# @c associated event, and most of all, retrieves the
	# @c result set of the command.
	#
	# @argument query: The sql command to execute, must be
	# @argument query: a query.
	#
	# @result A list containing all rows returned by the quer