Sunteți pe pagina 1din 89

A Guide to Qddb

Version 1.42

Eric H. Herrin, II and Raphael A. Finkel


April, 1995
Copyright (C) 1993, 1994, 1995 Herrin Software Development, Inc.
All rights reserved.
Contents
0.1 Copyright and Warranty : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : vi
0.2 Acknowledgments : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : vi
1 Introduction 1
1.1 Overview : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 1
1.2 Software structure : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 2
1.3 History : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 3
1.4 Feedback : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 5
2 Getting Started 7
3 Generic user interfaces 11
3.1 Text-based user interfaces : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 11
3.2 Graphical user interface: nxqddb : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 13
3.2.1 Data entry : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 15
3.2.2 Adding new tuples : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 16
3.2.3 Searches : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 17
3.2.4 Modifying a tuple : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 20
3.2.5 Con guration : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 20
4 Qddb les, structures and algorithms 23
4.1 The stable and the slippery : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 23
4.2 Stabilization : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 25
4.3 Structure les : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 25
5 Compiling and installing Qddb 27
5.1 Machine-speci c notes : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 29
5.2 Problems, bugs, and suggestions : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 30
iii
6 Schemas 31
6.1 The format : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 31
6.2 Complex data: some advice : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 38
7 Writing Qddb Applications in Tcl 41
7.1 Data abstractions : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 41
7.1.1 Schemas : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 42
7.1.2 Keylists : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 42
7.1.3 Tuples : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 43
7.1.4 Rows : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 44
7.1.5 An example : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 46
7.1.6 Views : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 47
7.2 QTcl Commands : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 48
7.2.1 Schemas: qddb schema : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 48
7.2.2 Searching and keylists: qddb search and qddb keylist : : : : : : : : : : : : : : 50
7.2.3 Tuples: qddb tuple : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 52
7.2.4 Rows: qddb rows : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 53
7.2.5 Views: qddb view : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 55
7.2.6 Instances: qddb instance : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 56
7.3 Searching : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 57
7.4 Retrieving and displaying rows : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 58
7.4.1 Producing rows for display : : : : : : : : : : : : : : : : : : : : : : : : : : : : 58
7.4.2 Updating records : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 59
7.4.3 Tuple manipulation by rows : : : : : : : : : : : : : : : : : : : : : : : : : : : : 60
7.5 Importing data from conventional relational databases : : : : : : : : : : : : : : : : : 61
7.5.1 Single tables : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 61
7.5.2 Multiple tables with a unique key : : : : : : : : : : : : : : : : : : : : : : : : : 62
8 Designing Custom Qddb Interfaces with the Fx Toolkit 65
8.1 Requirements : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 65
8.2 The tools : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 65
8.2.1 Fx:Init : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 66
8.2.2 Fx Menubar : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 66
8.2.3 Fx Frame : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 68
8.2.4 Fx Entry : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 69
8.2.5 Associating Fx Frame and Fx Entry with the Fx Menubar : : : : : : : : : : 69
8.3 A simple example : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 69
8.4 Implementing custom features : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 71
8.4.1 Naming itcl classes, procedures, and global variables : : : : : : : : : : : : : : 71
8.4.2 Augmenting/creating menus : : : : : : : : : : : : : : : : : : : : : : : : : : : : 71
iv
8.4.3 Interfacing with the search results : : : : : : : : : : : : : : : : : : : : : : : : 71
8.4.4 Calculated elds : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 72
8.4.5 Generating unique identi ers : : : : : : : : : : : : : : : : : : : : : : : : : : : 73
8.4.6 Designing an application for a Setup relation : : : : : : : : : : : : : : : : : : 75
8.5 The design and implementation of nxqddb(1) : : : : : : : : : : : : : : : : : : : : : : 77
8.5.1 Building a generic screen with Fx : : : : : : : : : : : : : : : : : : : : : : : : : 77
8.5.2 Implementing nxqddb's look and feel : : : : : : : : : : : : : : : : : : : : : : : 79

v
0.1 Copyright and Warranty
Qddb is free software; you can redistribute it and/or modify it under the terms of the GNU General
Public License Version 2 as published by the Free Software Foundation. Qddb is distributed in the
hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details. You should have received a copy of the GNU General Public
License along with Qddb; see the le LICENSE. If not, write to:
Herrin Software Development, Inc.
R&D Division
41 South Highland Ave.
Prestonsburg, KY 41653

Qddb subscriptions may be purchased for a yearly rate of $75. Subscriptions include a printed
manual, a diskette containing the software and numerous examples, and a newsletter for each new
release of the software (up to 4/year). Send a check or money order (U.S. funds only, please)
payable to:
Herrin Software Development, Inc.
Qddb Subscriptions
41 South Highland Ave.
Prestonsburg, KY 41653
with your mailing address and phone number. Pro ts from subscriptions fund further Qddb devel-
opment. We also build custom Qddb applications for a one-time consulting fee.
You can pick up a copy of Qddb at many ftp sites, but the main site is:
ftp.ms.uky.edu:/pub/unix/qddb/qddb-<version>.tar.gz

0.2 Acknowledgments
We would like to thank Gilbert Benson <gil@iac.net> for his reviews of the generic applications
and the documentation. Gil was invaluable during the development of the Fx toolkit. Ken Kubota
<ken@ms.uky.edu>, Daniel Chaney <chaney@ms.uky.edu>, and Kenny Herron have spent a lot of
time discussing Qddb with us. Andrew Beckett <a.beckett@fml.co.uk> has provided very useful
feedback, both with reporting problems and suggesting features. Tracy Schuhwerk donated the
nice Qddb logo.
We would also like to thank Henry Spencer for his excellent regular expression package. A special
note of thanks to John Ousterhout for his hard work on Tcl/Tk, without which this version of Qddb
would not be so exible.
vi
Chapter 1

Introduction
1.1 Overview
Qddb is an acronym for \Quick and Dirty DataBase," largely due to its origin. Qddb was originally
to be a quickly implemented ecient database tool. Our initial success led to layers of new function
and reworked data structures; it is still ecient, but we can no longer claim that it was quickly
implemented.

Qddb provides a simple yet intuitive means of storing and accessing data. Data are stored
in relations; each relation follows a format described by a schema. For example, a database
of friends' telephone numbers might be stored in a single relation. Data within a relation are
organized into tuples. Continuing the example, each friend would be associated with a di erent
tuple. Within each tuple, data are stored as values of attributes. There might be, for example,
an attribute for the friend's name and another for the telephone number. The value of an attribute
may be arbitrary Ascii data (any character is valid in someone's name) and may have arbitrary
length (names may be arbitrarily long). An attribute may be expandable, that is, may have
an arbitrary number of occurrences (called instances), each with its own value. For example, a
single friend may have several phone numbers, each represented as a di erent instance of the same
attribute. Finally, attributes may be structured to contain subattributes. For example, phone
numbers may be subdivided into country code, area code, and local phone number.

The le structures used by Qddb are intended for relatively stable data, where for the most
part, data don't change after they are entered. Searches are very fast within the stable region of
data, because the le structures (principally, hash tables) allow for fast access. Tuples that have
been modi ed or added since the last stabilization are equally accessible, but searching them is
signi cantly slower. Many databases t this model; stabilization becomes necessary only when
searches are too slow. For example, a nightly stabilization may be appropriate for on-line data
1
acquisition, but monthly stabilization is likely to be adequate for a personal database of friends'
phone numbers.

Qddb is an alternative to pure relational databases. It promotes a style in which individual tuples
store large amounts of related information. For example, a record might be a software customer
along with all its bug reports and purchases. When you access the customer's record, you would
like to get all the related information, not just pieces of it. You would like to be able to browse
through the information and extract certain parts. You would like to generate a list of all the
tuples in which you have entered a comment on the customer's temperament. You might want to
generate reports based on values, ranges of values, or regular expressions in certain attributes. Of
course, you don't want to spend a week or more designing the relational tables and you certainly
don't want to spend a fortune for the software.

Qddb is able to help you build such databases. After reading Chapter 2, you should be able to
build a simple relation that meets your needs in about ve minutes. You will automatically get
tools customized to that relation that allow the sort of searches mentioned above. If you require
something more specialized, Chapter 8 describes how to design custom applications.

1.2 Software structure


Qddb is built in several levels, as described in Figure 1.1. At the bottom is libQddb.a, the Qddb
library, containing such routines as Qddb_ChangeEntry. Built on those routines are some fairly
simple programs such as qedit and qadd that provide interactive access to a Qddb relation.

Tcl is an extensible programming language developed by John Ousterhout at Berkeley. The


Qddb package includes an extension to Tcl in the library libTclQddb.a. One result is an interactive
interpreter, qtclsh, in which a programmer may execute not only Tcl commands but also Qddb
extensions such as qddb_search.

Tk is another extension to Tcl that provides a graphical user interface for the X-windows package.
Qwish is an interactive Tcl interpreter that understands both the Qddb and the Tk extensions. The
xqddb program is written in this extended language; it provides a graphical interface to a Qddb
relation.

To allow programmers to customize applications, the Qddb package includes the Fx toolkit of
routines written in Tcl extended by Qddb, Tk, Itcl, Tktable (spreadsheet), Xpm (color button
bitmaps and logos), and Blt (the last three are optional). This library provides routines such as
Fx_Entry that can be used to build custom graphical interfaces to speci c databases. The nxqddb

2
nxqddb
Interpreted
(written in Tcl)
Fx Toolkit xqddb

qwish qtclsh
Compiled
(written in C) libBLT.a

qedit,
libitcl.a libtk.a libTclQddb.a
qadd, ...

libtcl.a libQddb.a

Figure 1.1: Levels of software in Qddb

program is written using this library; it has much the same function as xqddb but is much shorter,
because it rests on more powerful machinery.

1.3 History
Qddb development began around 1989 as a summer project to build a small set of database tools
for student records. We saw that many schools were either spending tens of thousands of dollars on
database tools or were hacking something together that didn't do the job very well. We believed
the tools should be freely available so that small educational institutions could use the software
without fee. Generic tools would make the software useful to a wide population.

Student records typically do not change very much during the course of a year, so we decided to
make the assumption that the data would be relatively stable. We used the bibliographic database
program refer [3] as a model. After some thought, we decided that more exibility was necessary
for generally useful tools. At this point, Qddb schemas were born. They allowed us to specify
attributes in an intuitive manner, group them together, and have an unlimited number of instances
of each attribute or group of attributes in any tuple. Our rst production schema was for graduate
student records. Each student is associated with one tuple, with attributes for the student's name,
3
identi cation number, addresses, phone numbers, and semesters, each of which contains courses
with corresponding grades. The student-records relation is still in heavy use.

Around the same time, we built many schemas for our personal use. Music collections, family
trees, coin collections, libraries, technical reports, bibliographies, antiques, and personal contacts
were a few of the rst Qddb databases. Our interfaces were crude but useful, and they still exist:
qedit, qadd, and the like.

The original Qddb took about a month to build and was around 1,000 lines of C code. Over the
next few months, Qddb grew to over 10,000 lines of code, and its usefulness grew with it. Around
the end of 1991, development of Qddb stalled except for the occasional bug x. The reason for
the delay was not that our interest had waned, but that Eric (the primary implementor) had some
school work to do. Qddb was placed on the back burner.

After nishing his PhD dissertation, Eric turned to improving Qddb. The most neglected part of
Qddb was its user interface, and Eric wanted something graphical. We needed enough exibility to
allow users and programmers to build specialized interfaces, but powerful enough to build something
general. We developed several prototypes that would build generic (that is, independent of any
particular schema) screens under the X Window System (using the Xt toolkit), but we were not
satis ed with the result. Motif was pretty enough, but it was too much e ort to deal with, so we
began to look elsewhere.

John Ousterhout's Tcl and Tk Toolkit were perfect for our needs. We introduced the Qddb
extension to Tcl, including the interactive programs qtclsh and qwish. The xqddb program is a
generic user interface written in this extension. Qddb was now around 40,000 lines of C and Tcl
code.

The most recent development has been the distillation of xqddb ideas into Fx, a Tcl library that
makes it far easier to write custom graphical Qddb applications. The proof of this idea is nxqddb,
which has very similar function to xqddb but requires less than 300 lines of code.

Qddb continues to develop. New features and tools are being added at a very fast rate, mostly
to the Fx layer, but to some extent at the qtclsh layer and the libQddb.a layers as well.

Qddb is being used world-wide by thousands of sites. Applications include many fairly large
(20{30 megabyte) databases, both commercial and non-commercial. However, we still consider the
software to be in a beta condition. Use it at your own risk.
4
1.4 Feedback
We are interested in how Qddb is being used, so if you do something novel with it, send a
brief description of your application to eric@ms.uky.edu and raphael@cs.engr.uky.edu, or to
qddb-users@ms.uky.edu.

We welcome all suggestions for improvements, so feel free to ask for or implement new features.
We welcome all submissions.
There is a mailing list for general discussion about Qddb. To join this list, send an e-mail message
to qddb-users-request@ms.uky.edu with a subject line of Subscribe. After you subscribe to the
mailing list, you can send mail to qddb-users@ms.uky.edu for general discussion about Qddb.
We usually monitor this mailing list, so almost any question you might have about Qddb can be
answered this way. Please read the documentation before posting a question to the list; we prefer
to keep the majority of the trac concentrated on Qddb development.

5
6
Chapter 2

Getting Started
This short chapter is for readers who do not want to know any more about the software than is
absolutely necessary; you simply want to do your work. We understand your situation, so we are
spending some time making sure this chapter will help you with your quest. We show you a nice
small example, take you through creating the database, writing the schema, then adding/editing
tuples. You will want to learn more about the generic user interface, Qddb le structures, and
stabilization, but that can wait until the next chapter.
The problem:
You want to keep track of your extensive home library. Sometimes people borrow your books,
so you'd like to be able to nd out who has borrowed books in the past and who currently has
your books. You need to have a friendly user interface that provides operations such as adding,
updating, querying, and deleting data. In particular, you don't want to write any code and would
like to have the application up and running in ve minutes (not counting the time it takes to read
this chapter!).
The rst step is to choose a directory to keep all your Qddb databases. For concreteness, let's
say that you want to store all your databases in ~/Databases.
Your application only needs a single relation. The qnewdb command will create the relation les
for you in a new subdirectory of ~/Databases. Next, you edit the schema le. The sequence of
commands looks like this:
1$ cd ~
2$ mkdir Databases
3$ QDDBDIRS=~/Databases; export QDDBDIRS
4$ cd ~/Databases

7
5$ qnewdb Library
6$ vi Library/Schema

Lines 1 and 2 set up your databases directory. Line 3 establishes a search path so Qddb programs
can nd any relations in that directory1 . Lines 4 and 5 create a new relation in a subdirectory
called Library. Line 6 invokes a text editor (you could use any editor you like) to initialize the
schema (more on that soon).
The following contents belong in the Schema le:
# Qddb relation for personal library
Book (
ISBN Title Authors * Subject
Keywords verbosename "Key Words"
Publisher *
Series verbosename "Book Series"
Year Edition Printing Abstract
)
Borrowers (
Name
Address (
Street City State
Zip verbosename "Zip Code"
)*
Phones (
Desc verbosename "Description"
Number (
Area type integer
Prefix type integer
Suffix type integer
)
)*
Borrowed verbosename "Date Borrowed" type date
Returned verbosename "Date Returned" type date
)*

The schema indicates that each tuple is a combination of information about a particular book
and a history of who has borrowed it. The Book attribute is structured; its subattributes identify
information such as its title. Some of this information, such as Author, is expandable (marked with
an asterisk `*'), because a book may have multiple authors. The Borrowers attribute may itself
have multiple instances (over time, a book may be borrowed more than once). It has subattributes
1
You can put this line in your .pro le so that you will have a nice path set up each time your log in. We have
shown the syntax for the bash shell; other shells establish environment variables by other syntax.

8
for the borrower's name, address (there could be several), and other identifying information. A
particular tuple may have empty attributes (that is, attributes whose values are empty). Not every
book has an edition number, and not every borrower has a phone number.
If you have designed databases before, you will nd several aspects of this schema surprising.
First, although some attributes are constrained to hold date or integer data, many attributes have
no associated type. They will hold arbitrary character strings.
Second, both book and borrower information are stored in the same relation. A classical rela-
tional database would most likely use two relations, one for books and another for borrowers, with
unique borrower identi ers linking them. Qddb databases often avoid multiple relations and the
concomitant need to invent unique identi ers. However, if the same person borrows several books,
the schema shown here will cause borrower information to be repeated for each book.
Third, attributes may optionally have verbose names. These longer and more descriptive names
are used by the graphical user interface to display a user-oriented, instead of an implementation-
oriented, attribute name. The internal workings of the program use the more cryptic single-word
names.
All Qddb programs use the QDDBDIRS environment variable to establish a search path for your
relations. You can name a relation by an absolute path name to its directory, or you can use a
relative path from your current directory, but you may also use a relative path from any directory
in QDDBDIRS, which is a colon-separated list of absolute path names, such as this:
$ QDDBDIRS=~/Databases:~/NewDatabases:/usr/databases

Now your ~/Databases/Library relation can be referred to just as Library.


You can now start using user-interface programs. Try these:
1$ EDITOR=`which vi`; export EDITOR
2$ qadd Library
3$ qedit e Library <a list of words to search on>
4$ nxqddb Library

Line 1 establishes that vi is your preferred editor. If you prefer emacs or something else, you
can modify line 1 appropriately. Line 2 shows how to invoke qadd to let you put a tuple in the
Library relation. Line 3 shows how to edit the tuples that match all the words that you list. If
you prefer a graphical user interface, line 4 shows how to invoke nxqddb, which allows you to add,
modify, and search for tuples. You can most likely guess how it works by playing with it. Both the
text-based and the graphical user interfaces are described fully in Chapter 3.
9
You are now ready to begin. Try making a simple database and play with the programs we
have described. Soon you will be ready for a more detailed look at these programs and at Qddb in
general.

10
Chapter 3

Generic user interfaces


Qddb provides several generic (that is, application-independent) interfaces. You don't have to
design your own custom user interface in order to use a new relation. Once you become expert, if
you enjoy programming, you will be able to build custom interfaces yourself.

Qddb provides both text-based and graphical generic interfaces. The text-based interface (pri-
marily qadd and qedit) allows you to add, modify, and search for data with your favorite text editor.
The graphical interface nxqddb, designed to use the X Window System, makes it easier to select
and modify existing tuples.

3.1 Text-based user interfaces


The qadd and qedit programs give you a textual user interface you are already used to: a text
editor such as vi or emacs. You specify your favorite text editor in the EDITOR environment
variable.

You use the editor to ll in attribute values in a le that Qddb prepares for you containing either
the skeleton of a new tuple or the current value of an existing tuple.

The contents of an attribute may be any Ascii characters, but double quotes and newlines must
be preceded by a backslash. Null characters (Ascii 0) are invalid and will produce unspeci ed
results. The text-based user interfaces do not check attribute types such as date or integer; any
Ascii value is accepted. You can enter integers by just typing them in. Dates need to be converted
to integers, and are most likely not appropriate to enter with these tools.
11
The text-based user interfaces allow you to expand attributes that are not marked expandable
in the Schema. The resulting tuples may not be readable by more careful user interfaces such as
nxqddb.
qadd RelationName lets you add new tuples into the given relation. When you invoke qadd,
you are placed in your editor editing a temporary le that contains the readable form of an empty
tuple. Here is the readable form of an empty tuple for the Schema in Chapter 2.
$NUMBER$ = "1";
Book (
ISBN = ""
Title = ""
Authors = ""
Subject = ""
Keywords = ""
Publisher = ""
Series = ""
Year = ""
Edition = ""
Printing = ""
)
Borrower (
Name = ""
Address (
Street = ""
City = ""
State = ""
Zip = ""
)
Phones (
Desc = ""
Number (
Area = ""
Prefix = ""
Suffix = ""
)
)
Borrowed = ""
Returned = ""
)

You may replicate any expandable attribute or subattribute. For example, the
Borrowers.Address attribute (that is,
the Address subattribute of the Borrowers attribute) might
have two instances:
12
Borrowers (
Name = "Henry Coulett"
Address (
Street = "123 Hibersham Way"
City = "Leslietown"
State = "KY"
Zip = "40511"
)
Address (
Street = "966 Licking Creek Drive"
City = "Prestonsburg"
State = "KY"
)
)

Any attribute or subattribute may be omitted; in the example above, the second address for this
borrower is missing a zip code. Qddb understands omitted attributes to have empty values.
When you exit the editor, qadd checks the le for syntactic correctness and then adds the tuple
to the relation. If there is a problem, qadd identi es the line in the le that seems wrong and lets
you re-edit the same le.
Qedit lets you perform simple queries and invoke the editor on each matching tuple in turn. Qedit
may also be invoked to just list the matching tuples in readable format. To invoke the editor on
each tuple in the Library relation that has both henry (case is ignored) and coulett:
$ qedit e Library henry coulett

Once the editor is called, you may modify the tuple in any way you like, subject to the constraints
in the Schema.
To view all the matching tuples in readable form:
$ qedit l MyRelation henry coulett

Qedit allows only very simple queries on words, word ranges, numbers, numeric ranges, and
regular expressions. The man pages give full details.

3.2 Graphical user interface: nxqddb


Nxqddb is a generic user interface written at a much higher level than the text-based interfaces.
It makes use of the graphical facilities in the X Window System. Because it is fancier, and because
many more software levels are involved (such as the Tcl interpreter, the Tk interpreter, and the Fx
13
Figure 3.1: nxqddb interface for Library relation

library), nxqddb is slower than the text-based user interfaces. However, the reduction in speed is
amply repaid in ease of use and extra function.

Nxqddb parses queries speci ed in attributes, provides reporting/viewing facilities, and generates
a tailor-made graphical interface from the Schema le. Figure 3.1 shows nxqddb's automatically-
generated interface for the Library relation de ned earlier.

Nxqddb lets you add, modify, and search for tuples within the relation. Its extensive con guration
options let you customize the graphical presentation of data on a relation-by-relation basis.

When you invoke nxqddb, you see a screen similar to the one shown in Figure 3.1. The buttons at
the top give you access to commands, and the mode line directly underneath them tells you what
nxqddb is doing.
14
There are four modes: Search, Add, Change, and Read-only. When nxqddb starts, it enters
Search Mode to let you search for tuples. You can press the Modes button (actually, you click the
mouse when the screen cursor is on the menu button) and choose Add Mode if you prefer. You
automatically enter Change Mode when a search identi es a tuple. Changes you make to the tuple
only take e ect when you save the tuple. You can select Read-only Mode to allow other users to
modify tuples while you are just browsing. Qddb does not allow a tuple to be subject to change by
more than one program at the same time (even when the relation is shared on a network through
the network le system).
The menu bar at the top of the nxqddb window displays the following buttons:
File Save the current modi ed tuple or quit nxqddb
Edit Cut, paste, or clear data or start a search
Modes Switch to a di erent mode
View See the result of the last search again
Templates Read or write a data template
Con gure Establish the appearance of the graphical interface
Help View the documentation
Some menu items are not available in some modes; those items are stippled (displayed in a less
readable color) and disabled when not available.
You can bring up a menu associated with a menu button either by pressing the button or by
holding down the <Alt> key and typing the character that is underlined in the desired menubutton.
(You may need to con gure your X session to establish an <Alt> key; it needs to have Mod2 set
and nothing else.) Once the menu is displayed, you can select an item by typing the underlined
character in the item's label. For example, to save a tuple, you may press <Alt>fs (hold down the
<Alt> key, then press f and s.)

3.2.1 Data entry


Most of the display is dedicated to lines for attributes, with the attribute name on the left and
a place to enter information on the right. Information you enter on the right guides a search (in
Search Mode) or modi es the value of the current tuple (in Change Mode or Add Mode).
Information is generally placed in one-line entry boxes, but you can con gure nxqddb to use
multiple-line text boxes or multiple-choice radio buttons on an attribute-by-attribute basis.
To direct nxqddb's attention to an entry or text box, you can either click on the box or type
the tab or down-arrow key until the vertical cursor appears in the box you want. (Shift-tab and
15
<Tab>, <DownArrow> Go to the next entry or text box
<Shift-Tab>, <UpArrow> Go to the previous entry or text box
<Control-a> Go to the beginning of the line
<Control-b>, <LeftArrow> Go back one character
<Control-d> Delete one character to the right of the cursor
<Control-e> Go to the end of the line
<Control-f>, <RightArrow> Go forward one character
<Control-h> Delete one character to the left of the cursor
<Control-k> Delete to the end of line, placing in the cut bu er
<Control-o> (Text) Open a blank line above the current line
<Control-u> (Entry) Clear the entry
<Control-w> Delete the current selection, placing it in the cut bu er
<Control-y> Paste the cut bu er at the current cursor position
<Alt-w> Copy the current selection to the cut bu er
<Button-2-Motion> Scroll vertically (Text) or horizontally (Entry)
<Button-3> Paste the current selection at the cursor
<Return> (Entry, Search Mode) Start the search;
(Text) Go to next line

Table 3.1: Key interpretations

up-arrow move backward through the boxes.) Once attention is on an entry box, characters you
type are placed in that box. Some keystrokes are treated specially, as shown in Table 3.1. (This
table is also available via the Help button.) These interpretations are similar to the default bindings
of the emacs text editor.

Only one instance of each expandable attribute is displayed at one time. Each expandable
attribute has three buttons next to its label. The Add button introduces a new, initially empty,
instance of the attribute. The View button displays a box listing all instances of the attribute; you
may select one to display. The Del button deletes the instance that is currently being displayed.

3.2.2 Adding new tuples


To add a new tuple, you enter Add Mode by clicking the menu button called Modes, then selecting
Add Mode. You can view all instances that you have added, just as in Change Mode. When you
have nished adding your tuple, click the File button and choose the Save option. The screen will
be cleared (if Auto-Clear is enabled) to prepare for another added tuple.
16
3.2.3 Searches
The simplest way to search is to enter search keys in one or more attributes and then either type
<return> or activate the Search menu item in the Edit menu. Qddb will search for all tuples
that have all the speci ed words in the indicated attributes. The search ignores the case in which
you specify the words and the case of the data. A word in the data is any string of contiguous
alphanumeric characters within a single attribute1 .

Actually, the result of a search is not a set of tuples, but rather a set of rows. A row is a
view of a tuple that restricts each multiple-instance attribute to one of the instances. If none of
the attributes has multiple instances, a tuple has only one view, and hence, only one row. But
if you search in the Library relation for Title = "Is Sex Necessary", the tuple that matches
has two authors: E. B. White and James Thurber. Therefore, there are two matching rows, one
with Authors = "E. B. White" and one with Authors = "James Thurber". If the book has been
borrowed by two friends, then the tuple has four rows. If one of the friends has two addresses, then
the tuple has six rows.

If a search yields more than one tuple, you must choose which tuple you wish to see. More
generally, if a search yields more than one row, and the rows are distinguished either by coming
from di erent tuples or by di erent instances of signi cant attributes, you will need to make a
choice. (You can con gure what makes an attribute signi cant; see Section 3.2.5.)

For example, if Sarah has two addresses and both name and address attributes are signi cant,
then a search that matches a book borrowed by Sarah will generate two rows, one for each of her
addresses. If only one of those addresses matches your query, then only one row is generated. If
addresses are not signi cant, then no matter how many addresses match the query, only one row
results.

If only one row is generated by a search, nxqddb will display the single resulting row by copying
its data into the attributes shown on the screen and then enter Change Mode.

If a search yields more than one row, nxqddb produces a results box. It has one column for each
signi cant attribute of the relation. (Again, you can con gure this set, as you will see in Section
3.2.5.) The results box shows one line for each row returned by the search. (If there are too many
rows, only some are shown and a scrollbar is provided to let you see the rest.)
1
You can de ne a word to be delimited by other characters by adding speci cations to the Schema, as described
in Chapter 6.

17
You may select a line (using arrow keys or by clicking the left mouse button), which causes the
associated row to be displayed in the main window. The results box will then disappear, and you
may update the tuple whose row is displayed. You may view other rows of the same tuple by
clicking on View buttons for expandable attributes.
If you pin the results box (there is a check button for that purpose in the results box), it will
persist even after you have selected a row. You can select one row after another, modifying each
one if you like. The results box will persist until you unpin it, cancel it, or start a new search.
Nxqddb can perform fancier searches than we have shown so far. (Still, it does not use the full
searching capabilities of Qddb.) A query in nxqddb is a list of atoms joined by operators. Words
are one form of atom; here is the full list of search patterns:
word alphanumeric characters
word range word{word
number integer or real number
number range number{number
date mm/dd/yy
date range date{date
regexp any regular expression
intersection (and) pattern pattern
union (or) pattern , pattern
exclusion (but not) pattern ! pattern
grouping f pattern g

A single atom could be interpreted in di erent ways. First, nxqddb checks for a \{" in the atom
and checks the string types on either side. If both strings appear to be either a word, number, or
date, then the appropriate range searching method is used. Otherwise, nxqddb attempts to treat
the string as a word, a number, or a date, in that order. If all fail, nxqddb treats the atom as a
regular expression.
Search patterns may be speci ed in any attribute eld and/or in the attribute-independent Search
For eld. The resulting query is the logical intersection (and) of the individual queries in each eld.
White space is important: Don't put any around the \{" that indicates a range, but do surround
the union and exclusion operators with space.
The operators for intersection, union, and exclusion have identical precedence and associate to
the left. Here is a complex query in a particular attribute:
18
{ joe , henry { amy john } a-z .123. { 1 2 { 3 4 } } }

This pattern looks for rows that have values matching a complex criterion for this attribute.
The value of the attribute must either contain \Joe" (case is insensitive) or \Henry", and it must
also contain all of \Amy", \John", some 1-letter alphabetic word, a word containing an arbitary
character followed by \123" followed by an arbitrary last character, a \1", a \2", a \3", and a \4".
These various words may be present in any order.

Here are more search examples.

Search for rows with either the words \eric" and \herrin" or the words \raphael" and \ nkel":
{eric herrin} , {raphael finkel}

Search for rows with the words \herrin" or \herron" but not the word \eric":
{herrin , herron} ! {eric}

Search for rows with \eric" and \herrin", then perform a union with rows containing \raphael",
then remove all rows containing \ nkel":
{{eric herrin , raphael} ! finkel}

Search for rows containing either a date from January 1, 1994 through June 1, 1994 or the words
\henry' and \blue," but not \leslie":
{1/1/94-6/1/94 , {henry blue}} ! leslie

Search for rows matching the regular expression (.*)abc(.*) or containing \special" and \cus-
tomer," but not containing the word \delinquent." Merge that result with rows containing words
ranging from \ba" through \bz":
{{(.*)abc(.*) , {special customer}} ! delinquent} , ba-bz

Here is a nal complex example:


{ { leslie ! walker } , { leslie walker junior }
{ 01/01/94-12/31/94 ! 01/20/94-01/24/94 }
{ 1000.00-1500.00 }
}

19
Figure 3.2: Con guring Entries

3.2.4 Modifying a tuple


When Search Results are displayed, you may select a row. Once a row is selected, nxqddb will
bring the associated tuple into the main window for viewing and modi cation. It will switch to
Change Mode. If you modify the tuple, you will be prompted to either save or discard the changes
whenever you try to switch to another tuple or go to Search Mode or Add Mode.

3.2.5 Con guration


Nxqddb lets you con gure attribute presentation, fonts, how search results are formatted and sorted,
how multiple instances are formatted and sorted, and whether elds are automatically cleared when
you enter Search and Add Modes. You can save the current con guration so that the next time
you run nxqddb on the same relation, you get the same con guration back.

You can con gure the presentation of each leaf attribute by clicking <Control>-Button-3 on a
leaf (that is, non-structured) attribute's label. You can set the style (entry, text, radio button), the
width, and whether the attribute is mandatory and/or read-only. Figure 3.2 shows the presentation-
con guration window. Radio buttons (multiple choices) present a xed set of values; you must
specify the valid values in a space-separated list.

Most other con guration is accessed through the Con gure menu (see Figure 3.3). The Con gure
menu has several check buttons (square boxes with a label on the right) that allow you to turn on
and o various options. When you click on one of these check buttons, you toggle its state to either
on or o . The check button's square box highlighted when the option is set.

The auto-save personal con guration option tells nxqddb to save the current con guration
as your \personal" con guration for this relation when you exit nxqddb. The auto-clear option
20
Figure 3.3: The Con gure submenu

has nxqddb clear all search parameters when entering Search Mode and all attributes when entering
Add Mode and after saving an added tuple. The force read-only mode option makes nxqddb
enter Read-only Mode instead of Change Mode whenever a tuple is selected from the search results.
Searches can result in a great number of tuples that match the search criteria. Nxqddb saves all
the matched tuples in memory. To limit the amount of data a search can produce, the con gure
menu lets you set two limits via submenus. The soft search limit is the number of tuples that
can be matched before nxqddb warns you and asks if you want to continue with the search. The
hard search limit is the number of tuples that can be matched before nxqddb will stop searching
altogether. These options are especially useful for naive users who are not yet used to specifying
highly constrained searches.
Searches that match more than one signi cant row give rise to results boxes. An expandable
attribute has a View button that you can press to get an instances box showing all the instances.
Results and instances boxes have the same appearance: Attributes are listed horizontally, and each
row presents values of those attributes pertaining to one row.
You can con gure results boxes and instances boxes. From the Con gure menu, you can select
Search Results and get a con guration window similar to the one shown in Figure 3.4 for the Library
relation. You get a con guration window for instance boxes by clicking <Control-Button-3> on
the expandable attribute's label. Both kinds of con guration window have the same look and feel.
You can select which attributes should be shown in results and instances boxes by clicking on
attributes in the Print or Don't Print regions and moving them to the other region by clicking
on the left or right arrows. By dragging the mouse with the left mouse button pressed, you can
select multiple contiguous attributes to move. You can decide what left-to-right order to display
21
Figure 3.4: Search Results Con guration for the Library Relation

the selected attributes by clicking on them and moving them up or down by clicking on the up or
down arrows. You can assign a width to the column dedicated to each attribute by clicking on it
and then entering the width in the entry box below. (0 indicates width adequate to present the
data; this is the default.) You can specify whether data for each column should be left or right
justi ed, and you can specify a separator string between columns. You can determine the order in
which the lines of data will be presented by modifying the order of the attributes in the Sort-by
region. Nxqddb will sort by the higher attributes rst. For each attribute, you can select ascending
or descending order.

22
Chapter 4

Qddb les, structures and algorithms


This chapter describes the les for everyday operation of your Qddb database. We will discuss how
changes and additions to the database are stored, when you should stabilize your database, and
how Qddb uses structure les to satisfy queries.

4.1 The stable and the slippery


All the data for a relation are stored within the directory that names the relation. Data that
haven't changed recently are all stored in Database. You generally don't want to look at this le,
but if you do, you will nd that it is in Ascii with one block of lines per tuple, separated by a
blank line from the next tuple. Each attribute has a line of its own, but only if the attribute has a
non-empty value for that tuple. Each line has a cryptic rst part that identi es which attribute it
refers to. For example, a line like:
%2.1.4.5.6.1 Frederick Douglass

refers to the second attribute, rst instance, and that this attribute is structured; the line refers to
the fourth subattribute, which is expandable; we are dealing with the fth instance, which is itself
structured; we are dealing with the sixth sub-subattribute, rst instance. The value of this sub-
subattribute is \Frederick Douglass." If the value contains a linefeed, the linefeed is preceded
by \\" to avoid confusing the Qddb routines.

The actual format of a tuple is shown in Figure 4.1. The \V" means that the tuple is valid, that
is, it has not been modi ed or deleted. Invalid tuples, denoted by an \I" instead of a \V," are
generally ignored by Qddb. Each line begins with a \%" character, immediately followed by a list of
numbers delimited by periods (\."). These numbers are pairs of the form attribute.instance, except
for the \%0" attribute, which just holds Qddb information about the tuple.
23
%0 V 2
%1.1.1.1 0-201-63337-X
%1.1.2.1 Tcl and the Tk Toolkit
%1.1.3.1 John K. Ousterhout
%1.1.4.1 Computer science, TCL, TK
%1.1.6.1 Addison-Wesley
%1.1.7.1 Professional Computing Series
%1.1.8.1 1994
%1.1.10.1 1st

Figure 4.1: A Qddb tuple in external format

The rst attribute in Figure 4.1 is equivalent to the Book.ISBN attribute of our Library relation.
The rst attribute's number is %1.1.1.1. This number states that it is the rst attribute at level
1 in the schema, that is, Book, and that this attribute is in the rst instance of Book. The second
pair states that this attribute corresponds to the rst instance of ISBN (the rst attribute at level
2 within the attribute Book). Attributes with empty values are absent from the tuple. Disk space
is often at a premium, so we don't reserve space for empty attribute values1. Although we don't
store empty attributes on disk, we do ll in the empty spots when the tuples are in memory.

To make searches fast, Qddb maintains structure les that allow it to nd tuples quickly in
Database, which can grow very long (hundreds of megabytes), and which you certainly don't want
to scan through from beginning to end. These les include HashTable, KeyIndex, NumericIndex,
Index, and Database.key. They are described in some detail in Section 4.3. You don't need to look
through these les, but they are also Ascii, so you certainly may if you want.

Whenever you change or add a tuple to your Qddb database, the entire tuple is replaced or
added. But the structure les wouldn't work if Qddb actually modi ed the Database le. Instead,
changes are placed in a subdirectory called Changes, with one le for each changed tuple. The name
of the le is the serial number of the tuple that has been changed. Qddb subtly substitutes an \I"
(for \invalid") for a \V" (for \valid") in the rst line of the tuple in Database to indicate to the
search routines that if they nd this tuple, they should ignore its version in Database. New tuples
are placed in a di erent subdirectory called Additions, again, one le per tuple.
1
Also in the interest of space savings, we allow the schema to specify that attribute-instance identi ers should be
stored in an abbreviated form, as discussed in Section 6.1.

24
4.2 Stabilization
Searches work just as well for tuples in Changes and Additions as for tuples in Database, but much
more slowly, since they don't have the advantage of the structure les. Searches must inspect all
the les in both directories. After a while, perhaps after hundreds of changes, you will nd searches
too slow. It is time to collect all the updates and additions and rebuild both Database and the
structure les. This event is called stabilization. Stabilization is time-consuming, but it is our
experience that only very heavily modi ed large databases cannot be stabilized overnight.
You should only stabilize a relation when no one is accessing it. (Qddb uses locks to enforce this
restriction.) The stabilization process takes time depending on the type and speed of the machine
that you have. Stabilizing a 20{30MB relation on a 20 mips machine can take about 15-20 minutes.
To begin the stabilization process, type:
$ qstall Relation

If you have manually edited Database (not generally a good practice), qstall will notice and not
trust any of the structure les; otherwise, it will trust some of the les in order to speed the process
of integrating your changed or new data.
When Qddb is installed, it is con gured with a maximum stabilization growth value. This limit
determines how much information the stabilization routines try to keep in memory before they are
forced to output partial results to an intermediate le. At the end, all the intermediate les are
merged to form the structure les. The more that Qddb can hold in memory, the faster stabilization
will be, but Qddb should not be con gured to use more memory than is available. If you have a
16MB machine, we nd that around 5MB (which is the default) is a good maximum stabilization
growth. For a 32MB machine, around 12MB is usually pretty good. A 64MB machine can usually
handle about 22MB.

4.3 Structure les


Qddb builds a collection of structure les on each stabilization. The les are used internally by
Qddb and should not usually be edited (although you can, of course!). Each le has a speci c
purpose related to searching.
 Database: The stable data. Tuples are separated from each other by a blank line. Each
non-empty attribute is on its own line, with an attribute identifer followed by the value.
 Database.key: One line for each stable tuple in the form: (o set length), referring to Database.
This le is used mostly for displaying the entire relation quickly and in stabilization.
25
 HashTable: Each line is the header for a bucket of the hash table. A header has the form:
(number-of-collisions hashval o set length). The o set and length refer to the le Index. This
le is used in full-word searches, which employ hashing. If you have enabled \cached hashing"
(see Section 6.1), the format has a xed width and no missing lines, facilitating random access.
 Index: Each (possibly very long) line holds the partial contents of one bucket of the hash table.
It begins with the word (or key). The rest of the line is a list of pointers to tuples called
a KeyList. Each member of the KeyList is of the form: (o set length attribute instance).
The o set and length point to a tuple in the Database le. The attribute and instance parts
specify where in the tuple the key is found.
 KeyIndex: A sorted list of all words (keys) in the Database le. Each line is of the format:
(key o set length). The o set and length point into the Index le to the part of the word's
hash bucket that holds the KeyList for that word. This le assists in searches based on word
ranges and on regular expressions.
 NumericIndex: This le has the same format as KeyIndex, but it contains only words that
happen to be numbers. This le assists in searches based on numbers or numeric ranges.
 RedAttrIndex: A list of all full attribute identi ers that occur in Database. These are used to
generate a shorter representation (in base 36) that can be used to signi cantly reduce the size
of Database and Index.
Behind the scenes, the stabilizer program qstall invokes qstab, qkeys, and qindex to perform
various phases of stabilization. Qstab rebuilds the Database le by removing any invalid tuples and
merging in all les from the Changes and Additions directories. Qkeys builds the Database.key le
by parsing the Database le. Finally, qindex builds the other structure les.

26
Chapter 5

Compiling and installing Qddb


This chapter is important if you are the maintainer of Qddb at your site. It describes how to
acquire, con gure, compile, and install Qddb. If you are an application programmer or an end
user, you might nd this chapter interesting, but it will not be directly useful to you.

Qddb uses the Gnu autoconf utility to customize header les to your computer and to build
Make les. It determines the location of important libraries and sets ags that indicate conventions
appropriate to your version of Unix.

Qddb compilation requires that you have both bison and ex. (You need at least version 1.22 of
bison and 2.4.7 of ex.) The autoconf utility will use certain environment variables such as MAKE
and CC, if they are set, to establish your conventions. For example, you might con gure Qddb as
follows:
$ export MAKE=gmake CC=gcc2 CFLAGS="-O2 -m486"
$ ./configure

Unless you indicate otherwise, con gure will assume that you don't have Tcl or its extensions:
Tk, Itcl, Blt, and others. By default, installation will only produce libQddb.a and routines such as
qedit. You can specify that you want to use Tcl and its extensions. (You need at least version 7.3
of Tcl and 3.6a of Tk.)
$ ./configure --with-tk --with-tclx --with-blt \
--with-itcl --with-tktable

If the Tcl extensions are in strange places, you can specify their locations with environment
variables:
27
$ export XPM_LIBRARY_DIR=/usr/myXdir/lib \
BLT_LIBRARY_DIR=/usr/mylocaldir/blt/lib \
CC=gcc2 CFLAGS="-O2 -m486" LDFLAGS="-s" \
MAKE=gmake
$ ./configure --with-tk --with-xpm --with-blt

The following Qddb-speci c con gure options are available. A full list of options can be found
by typing ./configure --help.
1. --with-diagnostics: turn on various sanity-checking mechanisms. This option is highly
recommended.
2. --with-gnumalloc: use GNU malloc (under Lib/GNUmalloc).
3. --with-mallocstats: print malloc statistics at stabilization. Automatically includes {with-
gnumalloc.
4. --enable-maxmem=X: set maximum Qddb stabilization growth to `X' kilobytes. (5MB is the
default, reasonable for a 16MB machine.) This number is just a soft limit; qindex and qstall
may grow somewhat larger if necessary.
5. --disable-maxmem: disable maximum stabilization growth. This option is not recommended.

6. --with-default-editor=/X/Y/editor: Change the default editor to /X/Y/editor for qedit


and qadd.
7. --enable-record-locking: Turn on fcntl-style record locking (default)

8. --disable-record-locking: Turn o fcntl-style record locking. This should only be used


for single-user relations.
9. --with-tcl: Compile and install qtclsh (TCL LIBRARY DIR, TCL INCLUDE DIR)

10. --with-tk: Compile and install both qwish and qtclsh (TK LIBRARY DIR,
TK INCLUDE DIR)
11. --with-xpm: Required if Tk libraries are compiled with Xpm extensions.
(XPM LIBRARY DIR)
12. --with-blt: Include Blt Tk extensions in qwish. This package should be included to get the
\I am busy" cursor for the Fx toolkit. (BLT LIBRARY DIR)
13. --with-tcltcp: Include TclTCP-2.X in qtclsh and qwish. (TCLTCP LIBRARY DIR)

14. --with-tclx: Include TclX in qtclsh and qwish. (TCLX LIBRARY DIR)

28
15. --with-expect:Include Expect in qtclsh and qwish. (EXPECT LIBRARY DIR)
16. --with-tktable: Include TkTable 0.2a in qtclsh and qwish. The TkTable package is included
in the Qddb sources for convenience.
17. --with-itcl: Include Itcl 1.5 in qtclsh and qwish. This package is required for the Fx toolkit.
(ITCL LIBRARY DIR)
18. --with-default-date-format=%m/%d/%y: Set the default date format to %m/%d/%y or
%d/%m/%y.

See Section 5.1 below for information on options for particular architectures.
Once you have con gured the distribution, you may issue make in the top-level directory to
make the binaries and libraries. make install will install the distribution in the place speci ed
by --prefix (default: /usr/local/qddb). make clean removes object les, while make distclean
cleans the distribution so that you can re-con gure for another platform.
Some versions of make won't work well with Qddb. You might want to use Gnu make instead.

5.1 Machine-speci c notes


 DEC Alpha OSF/1: Use the standard C compiler:
$ export CC="cc -std1" ; ./configure [options]

Gcc is apparently broken for DEC Alphas. --with-mallocstats and --with-gnumalloc


cannot be used with DEC Alpha OSF/1. --enable-maxmem=22528 seems to be about right
for an active 64MB DEC Alpha.
 BSD/386 V1.1, BSD/OS V2.0: Use GNU make (gmake).
$ export MAKE=gmake ; ./configure [options]

 Solaris 2.3: Tcl incorrectly includes dirent.h, getcwd.c, and opendir.c from the tcl7.3/compat
directory. Make sure these les are not used in the con guration. You probably will see
random core dumps otherwise, and the glob * command will fail in qwish. Solaris 2.3 has
both the getcwd() and readdir() functions in its standard library.
 Linux: If you have problems with locking under Linux, please report them. Linux record
locking code is nonstandard, but Qddb attempts to work around it. We know of no problems
with our workaround. Versions of Linux starting around 2.0 claim to be POSIX-compliant
and should have no problem.
29
5.2 Problems, bugs, and suggestions
If you have problems compiling Qddb, please send qddb-bugs@ms.uky.edu the con g.status script
generated by con gure and a script of the compile. If any Qddb program aborts or core dumps,
please send us a stack trace and a detailed description of what you did. We'll try to work with you
on the problem. Also, send reports about inaccuracies, de ciencies, or typos in the documentation
to the same e-mail address.

30
Chapter 6

Schemas
The Schema is the heart of a Qddb database. Each relation has its own schema that describes
the form of the data. You build the schema with an ordinary text editor.
The schema is where you de ne the structure of tuples by setting out the attributes. Structured
attributes exist only for grouping purposes. Non-structured attributes are called leaf attributes.
Leaf attributes of a tuple are the containers for data. Both structured and leaf attributes may be
expandable, which means that a tuple may have more than one instance. A tuple may have zero
instances of any attribute.
Each attribute has a name. In addition, you may give it a type (for a leaf attribute, to restrict the
values it may hold), a verbose name (for display purposes), an alias (as a programming convenience),
and a convention for splitting values in that attribute into words (for a leaf attribute, to guide word
searches).
The schema also lets you de ne a few storage and format options, such as how the HashTable
structure le should be built and the format in which dates are to be input and output.

6.1 The format


The Schema le begins with optional storage format options:
 HashSize = <Number>: Sets the number of buckets in the HashTable le. If you leave this
number out, Qddb will use a default of 20,000.
 Use Cached Hashing: Instructs qstall and qindex to build a HashTable le suitable for caching
hash values instead of reading the entire le at startup.
31
 CacheSize = <Number>: Sets the maximum number of hash values to cache. Ignored unless
\Use Cached Hashing" is speci ed.
 Use Reduced Attribute Identifiers: Instructs qstall to build a RedAttrIndex structure le
holding the full attribute identi ers. This option reduces the size of the Database and Index
les, because the lengthy attribute identi ers and instance numbers are represented by a
shorter base-36 number. You can add and delete this option from the Schema at any time,
but you must immediately restabilize with qstall. qstall will notice that you are converting
from reduced attribute identi ers to full attribute identi ers (or vice-versa) and make the
appropriate conversion.
 DateFormat = "Format String": Sets the default format string for outputting dates (this
relation only). Any format suitable for strftime(3) is allowed.
Qddb ignores the case of keywords such as HashSize.
After the format options, Schema contains the attribute descriptions. Each attribute takes the
form AttributeName ?<options>? ?(<subattributes>)? ?*?. (We are following the Tcl con-
vention that parts surrounded in ?? are optional, and that parts with capital letters need to be
speci ed further by a variable or a speci c literal.)
Example: Suppose that you want to build a database for your CD-ROM collection. You want
to keep series together, along with their individual costs and date of arrival. The following Schema
might ful ll your wish:
# My CD-ROM collection
Use cached hashing
Use reduced attribute identifiers
HASHSIZE = 1000
CACHESIZE = 100
# *** attributes begin here ***
Series verbosename "CD-ROMs" (
BarCode verbosename "Barcode on CD cover" separators ""
Title
Num verbosename "# CDs" type integer
Mfg verbosename "Manufacturer" (
Name
Address (Street City State Zip)*
Phones (Desc Area Number)*
)
DateReleased verbosename "Release date" type date

32
DatePurchased verbosename "Acquisition date" type date
Cost type real format "%.2f"
)*
Comments verbosename "Series comments"

Attribute names must begin with an alphabetic character (\a" through \z," either upper or lower
case) and may continue with any alphanumeric characters. The outer-level attribute names must
be unique, and within each structured attribute, the attribute names must be unique. However,
you may use the same name inside several di erent structured attributes, although you may nd
it confusing.
If you specify *, then this attribute is expandable. If you include subattributes, then this attribute
is structured. The subattributes obey the same syntax as the attribute itself. (You may start each
subattribute on a new line if you like, but the Schema le does not follow any xed format.) You
may terminate attribute de nitions with a semicolon if it helps you read the schema, but they are
optional. Comments may be placed anywhere on a line after a pound sign (\#").
The options may be any of:
verbosename "VerboseName"
type string
type date
type integer
type real
separators string
format "PrintfStyleFormatString"
alias AttributeName
exclude

The words verbosename, type, separators, format, alias, and exclude are keywords and
are invalid as attribute names.
Types restrict the values accepted by nxqddb (but not by qadd and qedit.) If you don't specify
the type of a leaf attribute, it accepts arbitrary strings. Integer types may be negative. Real types
are stored as double precision oating point. Dates are stored as seconds after an arbitrary time
called \the epoch".
Separators indicate how the values are to be parsed into words for the purpose of word seach.
If you don't specify separators, Qddb uses all non-alphanumeric characters as delimiters. (If the
attribute has type real, then -+. is not a delimiter; if the attribute has type integer or date, then
33
+- are not delimiters.) If you want strings like "foo:bar/whatever this is" to be considered
three words, foo, bar, and whatever this is, then you should set the separators to ":/". A
newline (\\n") is always a delimiter.

Aliases allow you to give a simple name to a deeply nested leaf attribute. This is handy if you
write programs in qtcl, the Qddb extension to Tcl, described in Chapter 7.

The exclude option instructs qstall to exclude the attribute from indexing. You should probably
use this option if you never need search on a particular attribute's value. Intelligent use of this
option can signi cantly decrease the size of the Index le.

Formats indicate how you want nxqddb to display numeric data. They do not in uence how you
enter data. They are ignored except for integer, real, and date attributes.

For integers, you may specify any of the standard printf(3) \%d" formats. Most commonly, you
will want to specify the padding:

integer formats
format "%10d" 10 digits, pad on left
format "%.4d" Zero- ll if less than 4 digits

For reals, you may specify any of the standard printf(3) \%f" formats. Most commonly, you will
want to specify the precision:

real formats
format "%10.2f" 10 digits, two to the right of decimal
format "%.2f" Arbitrary digits, two to the right of decimal

For dates, you may specify almost any combination of the following (also see the man pages for
strftime(3), asctime(3), and mktime(3)):
34
Date formats
%A replaced by the full weekday name.
%a replaced by the abbreviated weekday name, where
the abbreviation is the rst three characters.
%B replaced by the full month name.
%b or %h replaced by the abbreviated month name
(the abbreviation is the rst three characters.)
%C equivalent to \%a %b %e %H:%M:%S %Y"
%c equivalent to \%m/%d/%y".
%D replaced by the date in the format \`mm/dd/yy"'.
%d replaced by the day of the month as a decimal number (01-31).
%e replaced by the day of month as a decimal number
(1-31); single digits are preceded by a blank.
%H replaced by the hour (24-hour clock) as a decimal number (00-23).
%I replaced by the hour (12-hour clock) as a decimal number (01-12).
%j replaced by the day of the year as a decimal number (001-366).
%k replaced by the hour (24-hour clock) as a decimal
number (0-23); single digits are preceded by a blank.
%l replaced by the hour (12-hour clock) as a decimal
number (1-12); single digits are preceded by a blank.
%M replaced by the minute as a decimal number (00-59).
%m replaced by the month as a decimal number (01-12).
%p replaced by either \AM" or \PM" as appropriate.
%R equivalent to \%H:%M"
%S replaced by the second as a decimal number (00-60).
%T or %X equivalent to \%H:%M:%S".
%U replaced by the week number of the year (Sunday as the
rst day of the week) as a decimal number (00-53).
%W replaced by the week number of the year (Monday as the
rst day of the week) as a decimal number (00-53).
%w replaced by the weekday (Sunday as the rst day of the
week) as a decimal number (0-6).
%Y replaced by the year with century as a decimal number.
%y replaced by the year without century as a decimal number
(00-99).
%Z replaced by the time zone name.

You may enter dates in almost any format, except that \nn/nn/nn" is interpreted as
\%m/%d/%y". You can override this interpretation when you install Qddb so that such input
35
will be interpreted \%d/%m/%y". Some combinations of date format, such as \%d-%b-%y", are
not currently recognized by Qddb. If you provide a date format that Qddb cannot interpret, you
will see an error when entering dates. The most common formats are supported.
You may also enter dates relative to some date:
Date entry keywords
Keyword Meaning Example
tomorrow 24 hours tomorrow 12:00AM
today current date/time today 1:00PM
yesterday current date/time - 24 hours yesterday 1:00PM
last pre x (-1 unit) last week
next pre x (+1 unit) next year
ago sux (-X units) 3 years ago
epoch beginning of time epoch + 1 year
year(s) unit of time last year + 1 week
month(s) unit of time next month
week(s) unit of time next week
day(s) unit of time today + 3 days
hour(s) unit of time now + 3 hours
minute(s) unit of time now - 20 minutes
second(s) unit of time now - 1000 secs
You may also use the day-of-week keywords Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, and Sunday. Month names are also ne. For example, the following are all valid input
to a date attribute:
next tuesday
next december
next 9 dec
december 1, 1995 + 1 week
9 dec 1995
wednesday

The input \next tuesday" does not mean this coming (or current) Tuesday, but the one after.
The keyword \tuesday" means the coming (or current) Tuesday.
Figure 6.1 shows a schema using all the options. It represents a family that might have many
members, addresses and phone numbers. The attributes n, a, and p are expandable so you can
have multiple names, addresses and phone numbers grouped together in one record.
36
# Database of families I know
HashSize = 2000
CacheSize = 100
Use Cached Hashing
Use reduced attribute identifiers
DateFormat = "%d %B %Y" # "27 February 1956"
n verbosename "Name" (
t verbosename "Dr./Mr./Mrs./Ms./Miss"
f verbosename "First"
m verbosename "Middle" exclude
l verbosename "Last"
)*
a verbosename "Address" (
street city state # three attributes on one line
zip verbosename "zip code" type integer
)*
e verbosename "E-mail" separators "@!%"
p verbosename "Phones" (
d verbosename "Desc"
a type integer verbosename "" alias pa format "%10d"
p verbosename "" alias pp type integer
s verbosename "" type integer
)*
z verbosename "Extra stuff" (
a ( a b c ) b c d e f g h
)
c verbosename "Comments"

Figure 6.1: An example of a schema showing all options

37
Person (
Name
Address*
Phones ( Desc Number )*
SSN
)
RecordTypes verbosename "Faculty/Staff/Student" *
Faculty (
IdNumber
StartDate type date
EndDate type date
Salary type real
Dept
ResearchTopics
)
Staff (
IdNumber
StartDate type date
EndDate type date
Stype verbosename "Hourly/Monthly"
Station
)
Student (
Courses ( Number Desc Grade )*
GPA type real
)

Figure 6.2: A student/faculty/sta Schema

6.2 Complex data: some advice


A useful technique for your applications is to keep tuples of di erent types in the same schema.
You can write your own graphical user interface (building on nxqddb) that only shows the relevant
attributes for any given tuple.

For example, I may want a university database of students, faculty, and sta . They have some
attributes in common, but some unique to the particular individual. And a few individuals may be
both students and sta . A nice schema for this scenario might look something like the one shown
in Figure 6.2.
38
The question often arises whether you should use several relations or only one. In general, you
should try to use only one relation unless you are planning to write a custom application. All the
generic user interfaces deal with one relation at a time. Even so, Qddb is perfectly happy to deal
with multiple relations if you are willing to program a custom application.
Generally speaking, the reasons to de ne multiple schemas are: (1) There is a many-to-many re-
lationship among values in several relations, or (2) queries tend to search either one set of attributes
or a di erent, disjoint set of attributes.
A good example is a customer-invoice database. You generally want to nd a customer based on
name, address, phone number, or customer identi er. You'd also like to be able to nd a customer
when you query an invoice number. When you generate a sales report, you will generally ignore
the customer-identi cation data. In this case, the database can be logically divided into two pieces:
the customer and the invoice.
Another example is the Library example from Chapter 2. If the same person is likely to borrow
multiple books, you may want to separate books and borrowers into two relations. On the other
hand, you can use a single relation with tuples that either have the Book attribute or the Borrower
attribute non-empty. Each Book and each Borrower has a unique identi er. The Book attribute
includes a list of borrower identi ers, and the Borrower attribute includes a list of book identi ers.

39
40
Chapter 7

Writing Qddb Applications in Tcl


You may never need to write custom Qddb applications. But if you do, we suggest that you
consider using the Fx toolkit. In order to program Fx e ectively, you need to understand the Qddb
extensions to Tcl that allow you to deal directly with schemas, tuples, and the like. This chapter
discusses those extensions. Chapter 8 discusses the Fx toolkit itself.

There is a lower level you could use instead, the Qddb C library level. We have implemented
such utility programs as qstall, qedit, and qadd on this level. Although the Qddb C library interface
is reasonably nice, Tcl and Tk [4] provide a much more convenient ground for applications pro-
gramming. The e ort you need to spend to learn Tcl (if you don't already know it) will be repaid
in the ease with which you will be able to write Qddb applications.

The interactive Qddb shells qtclsh and its graphical partner, qwish, provide a set of convenient
abstractions accessible through Tcl commands. Any programmer familiar with relational databases
and Tcl should have no trouble learning the Qddb Tcl abstractions and commands.

7.1 Data abstractions


The basic Qddb data abstractions are schemas, keylists, tuples, rows, views, and instances.
Each of these datatypes refers to Qddb structures introduced in Chapter 4.

In this section, we will discuss these abstractions without descending into Tcl commands. The
next section discusses the commands that manipulate these abstractions.
41
ROOT
(implicit)
Level

Attr1 Attr2 Attr3 1

Attr2.A Attr2.B 2

Figure 7.1: Tree Form of the Qddb Schema \Attr1* Attr2 (A B)* Attr3"

7.1.1 Schemas
The schema is an abstraction of the Schema le in the Qddb relation directory. It represents the
schema as a tree. (See Figure 7.1).
The tree form implies two operations:
1. Find the deepest common ancestor of two given nodes in the tree.
2. Find all leaves for whom a given node is an ancestor.
Section 7.1.3 details the importance of the schema abstraction in relation to tuples.
7.1.2 Keylists
A keylist describes a set of tuples, often those that satisfy some query. Each node in the keylist
refers to a tuple in the database (perhaps in the Database le, perhaps in a le in the directories
Additions or Changes). Within the tuple, a keylist node can refer to a particular attribute. Keylists
have several binary operations:
Description Operation Result
Intersection A\B All nodes in both A and B
Union A[B All nodes in either A or B
Exclusion A\B All nodes in A that are not in B
The Qddb query mechanism supports searches on individual attributes and on entire tuples
based on: (1) a word, (2) a number, (3) a date, (4) a regular expression, and (5) a range of words,
numbers, or dates. Each query returns a keylist; logical combinations of queries are accomplished
by binary operations on the resulting keylists. For example, if you want to nd all the tuples that
contain the word Joe and Machey, you would:
42
Query for the word Joe
Query for the word Machey
Intersect the two resulting keylists
Read tuples based on the resulting keylist

Each keylist node is a quadruple fstart, length, type, numberg and a leaf identi er that points
into the data les. The pair ftype, numberg speci es whether the tuple resides in the stable or
instable part of the database. Not all nodes in a keylist necessarily point to a valid tuple; if a tuple
has been changed or deleted, then a query might still match the stable tuple. Invalid tuples are
empty when you try to read them.

7.1.3 Tuples
The tuple is the basis for data manipulation. You can read, write, add, delete, and change
tuples. You can also translate tuples from internal tree form into several other formats: external,
tclexternal, and readable. A tuple can be locked to prevent others from writing it, but even a
locked tuple can always be read. The act of writing a tuple is synchronization-atomic; Qddb will
wait until any writes are complete before reading. When a tuple is locked for writing, it is refreshed
from disk immediately after the lock is acquired so that you are guaranteed the latest version of
the tuple.

Like schemas, tuples are most conveniently visualized in the form of a tree. Using the schema
shown in Figure 7.1, we might have the following tuple (shown in readable form):
Attr1 = "the"
Attr1 = "moon"
Attr2 (
A = "was"
B = "a"
)
Attr2 (
A = "ghostly"
B = "galleon"
)
Attr3 = "tossed"

The tree form of this tuple is shown in Figure 7.2. There are three types of node in this tree:
attribute, instance, and data nodes. An attribute node describes an attribute in the schema.
Instance nodes describe the instance numbers of their children. These two node types alternate
on paths from the root to leaves. The data nodes are the leaves.
43
ROOT
(implicit)

Attr1 1 Attr2 2 Attr3 3


x x Attributes

1 + 2 1 + 2 1
Instances

A1 x B2 A1 x B2 Attributes

1 1 1 1
Instances

the moon was a ghostly galleon tossed Leaves

Figure 7.2: Tree Form of a Qddb Tuple

The tree form of a tuple is closely related to the tree form of a schema. In its simplest form,
the tuple tree is an exact duplicate of the schema tree, with instance nodes inserted below each
attribute node and leaves inserted at the end of each branch. Multiple instances of an expandable
attribute are represented by separate branches.

The operations on tuples include adding and removing instances of attributes. The tuple shown
in Figure 7.2 has two instances of Attr2. You might wish to add another instance of Attr2 and ll
in just the B subattribute. When you request a new instance of Attr2, you will get a new branch of
Attr2 that includes Attr2.A and Attr2.B. You can then ll in Attr2.B and leave Attr2.A empty.
When the tuple is stored, the empty attribute will be omitted, and no storage will be consumed.

7.1.4 Rows
In a sense, a Qddb tuple is the join of a set of related rows from several ordinary relational tables.
The relational tables are implicit in Qddb's tree-structured tuples. In order to reduce a Qddb tuple
to tabular form, you must extract the rows of interest.

A row is a view of a tuple that restricts each multiple-instance attribute to one of the instances.
Rows are built from tuples. A complete row includes each leaf of the schema tree. A partial
44
row omits some leaves. You would omit leaves if the data they contain is not important to display
at the moment.

For example, the following is one complete row of the tuple shown in 7.1:
Attr1 = "the"
Attr2 (
A = "ghostly"
B = "galleon"
)
Attr3 = "tossed"

The following is a partial row of the same tuple:


Attr1 = "the"
Attr3 = "tossed"

Typically, you want to prune identical partial rows (you cannot have identical complete rows1 .)

All complete rows can be extracted from a tuple tree with the following algorithm, written in an
ad-hoc, C-like notation (see Figure 7.2):
rowList procedure ProcessAttributes (AttributeNode) {
rowList r = NULL;
foreach Inst = instance of attributeNode {
r += ProcessInstances(Inst) /* Union */
}
return r
}
rowList procedure ProcessInstances (InstanceNode) {
rowList r = NULL
if leaf(InstanceNode)
return InstanceNode.Value
else {
foreach Attr = attribute of InstanceNode
r *= ProcessAttributes(Attr) /* Cartesian product */
return r
}
}
rowList r = ProcessInstances(RootNode)
1
You may have complete rows that look identical because the data in them are the same.

45
The above algorithm works as follows. To generate all rows in a tuple, you call ProcessInstances
with the root node of the tuple. ProcessInstances iteratively forms the cartesian product of the
rows produced at that instance by ProcessAttributes. ProcessAttributes iteratively forms the
union of all rows produced at that attribute by calling ProcessInstances.

7.1.5 An example
Consider the following animal-records schema:
Kind (
Desc*
Which
Breed
)*
Whose

Here is a tuple in that relation:


Kind (
Desc = "My dog's name is 'Bonnie'"
Desc = "My dog's name is 'Boomer'"
Which = "Dog"
Breed = "Shekie"
)
Kind (
Desc = "My cat's name is 'Vinnie'"
Desc = "My other cat's name is 'Bud'"
Which = "Cat"
Breed = "Domestic short hair"
)
Whose = "Eric"

The following are the complete rows of this tuple:


Kind.Desc Kind.Which Kind.Breed Whose
My dog's name is 'Bonnie' Dog Sheltie Eric
My other dog's name is 'Boomer' Dog Sheltie Eric
My cat's name is 'Vinnie' Cat Domestic short hair Eric
My other cat's name is 'Bud' Cat Domestic short hair Eric
Partial rows can be extracted by excluding particular columns from the result, possibly deleting
duplicate rows.
46
7.1.6 Views
A view is a mechanism for navigating among the rows of a tuple. At any instant, a view corresponds
to one complete row in the tuple. The view can be shifted to other rows of the same tuple. When
you de ne a view, you specify the Tcl global variables that should be bound to each leaf attribute.
When the view corresponds to a particular row of the tuple, those variables have the values of
the associated attributes. You can read those variables to see what is in the tuple, and you can
set those variables to modify what is in the tuple. If you aren't interested in following some leaf
attributes, you can omit variables for those attributes. If you provide no variables at all, then you
can't use the view to help you extract values (although you can still get at the values by instance
commands, described later).

A newly-de ned view initially corresponds to the rst row, that is, the row that has the rst
instance of each attribute in the tuple. After de nition, a view may be shifted to any row. The
usual way is to switch to a di erent instance of an expandable attribute.

For example, using the Schema and tuple de ned in Section 7.1.5, we might have the following
view:
Kind
Desc Which Breed Whose
My dog's name is 'Bonnie' Dog Sheltie Eric

This view describes a row containing the rst instance of Kind and the rst instance of
Kind.Desc within Kind. By shifting the the Kind.Desc attribute to the second instance, the
view becomes:
Kind
Desc Which Breed Whose
My other dog's name is 'Boomer' Dog Sheltie Eric

You can also shift the instance of the entire Kind attribute, in which case the view would contain
the rst instance of Kind.Desc within the second instance of Kind:
Kind
Desc Which Breed Whose
My cat's name is 'Vinnie' Cat Domestic short hair Eric

47
7.2 QTcl Commands
The data abstractions we have just introduced are manipulated by commands that extend the
Tcl language. We will call the extended language QTcl. There are only eight new commands:
qddb_schema, qddb_search, qddb_keylist, qddb_tuple, qddb_rows, qddb_view, qddb_instance,
and qddb_util. Each of these commands has variants that are distinguished by their rst param-
eter. We will deal with each command in order. We don't show full details of the syntax here; you
can refer to the man pages for complete information.

7.2.1 Schemas: qddb schema


The qddb_schema command is used to open, close, and get information from a schema. There are
several forms of the qddb_schema command:
Command Purpose
qddb_schema open Relation Open a new schema
qddb_schema close SchemaId Close an existing schema
qddb_schema leaves SchemaId ?Attr? Get schema leaves
qddb_schema option OptionName SchemaId Attr Get schema options
qddb_schema path SchemaId Get the full path of the relation
qddb_schema print SchemaId Convert the schema to list form

The qddb_schema open command opens a relation and returns a schema descriptor (an opaque
identi er) that may be used in subsequent commands. A typical usage of this command is:
set s [qddb_schema open MyRelation]

The Tcl variable s will be set to a value such as qddb_schema32. This value is only usable as a
parameter to other Qtcl commands. qddb_schema close closes an open schema and discards any
internal storage devoted to it:
qddb_schema close $s

The qddb_schema leaves command returns a Tcl list containing all leaves under a given at-
tribute in the schema tree. For example, if we have a schema A B ( C D ), then the command
qddb_schema leaves $s B

returns a Tcl list containing B.C B.D. If no attribute is speci ed, the root is assumed. In our
example, the value returned would be A B.C B.D.
48
The qddb_schema option command returns the value of a given option for a given attribute
in the schema. The options you can specify are: type, verbosename, alias, isexpandable,
exclude, format, and separators. The isexpandable option returns yes if the given attribute is
expandable and no otherwise.
The qddb_schema path command returns the full pathname of the relation associated with
the given schema descriptor. This command is often used by generic applications to nd global
con guration les and other information stored in the relation directory.
The qddb_schema print command returns a Tcl list that describes all the attributes. This ability
is used heavily by generic applications. Each element of the list is a single attribute, represented
by the sublist {Attribute Options Children}. The Attribute element is not the dot-separated
path name, but just the local attribute name. The Options element is a Tcl list containing both
the verbosename option and the isexpandable option. The Children element recursively contains
lists of the attribute's children in the same form.
Example: Suppose we have the following schema in a relation TmpDB:
A ( B C D )* E (F* G verbosename "Junk")

The following gure shows the output of various qddb_schema commands typed directly into
qtclsh. The output of the qddb_schema print command is formatted for clarity.
% set s [qddb_schema open TmpDB]
qddb_schema0
% qddb_schema leaves $s
A.B A.C A.D E.F E.G
% qddb_schema option isexpandable $s A.B
no
% qddb_schema path $s
/tmp/TmpDB
% set p [qddb_schema print $s]
{
A {{} yes} {
{B {{} no} {}}
{C {{} no} {}}
{D {{} no} {}}
}
}
{
E {{} no} {
{F {{} yes} {}}

49
{G {Junk no} {}}
}
}
% qddb_schema close $s

7.2.2 Searching and keylists: qddb search and qddb keylist


Qtcl provides two commands for searching and then combining search results: qddb_search, which
searches the relation for tuples, and qddb_keylist, which performs set operations on the resulting
keylists.

The qddb search command takes the form:


qddb_search $s ?-prunebyattr Attribute? Type KeyOrRange

By convention, we will always use s to refer to an open schema descriptor. Type is one of word,
word_range, number, numeric_range, date_range, and regexp. Each of these speci es a di erent
sort of search. For those searches that need a single key, KeyOrRange is a single word or number.
For range searches, KeyOrRange is of the form
?lower-bound key? - ?upper-bound key?

If you specify the optional -prunebyattr Attribute, the search only applies to the given leaf
attribute, not to entire tuples. The return value of qddb_search is a keylist descriptor, which is
an opaque identi er used as input to other commands.

The qddb_keylist command performs unary and binary operations on keylists. You may either
choose to retain the original keylists or to free the resources they are occupying. You can also
choose to prune the resulting keylist by removing redundant nodes, such as nodes that refer to the
same tuple or those that refer to the same tuple and attribute. These choices are controlled by the
following options:
Option Meaning
-copy on|off Retain original keylist(s); off is default.
-deldup_sameentry on|off Remove redundant nodes referring to the
same tuple in the result.
-deldup_sameattr on|off Remove redundant nodes referring to the
same tuple and attribute in the result.

The two most useful qddb_keylist subcommands are operation and process.
50
The qddb_keylist operation command performs binary operations. It takes as a parameter
one of intersection, union, or exclusion followed by two keylist descriptors. Its options are:
Option Meaning
-exact on|off Attribute and instance number are signi cant.
The qddb_keylist process command performs unary operations. Its options are:
Option Meaning
prune Prune the keylist based on some criterion.
sort Sort the keylist.
nullop Perform only functions speci ed by the options.

The qddb_keylist process prune command requires either -prunebyattr or -prunebyrow.


The former requires an attribute parameter and will prune the keylist of all nodes that do not refer
to the given attribute. The latter requires a list of attributes as a parameter and will prune the
keylist of all nodes that are not part of a row matching each listed attribute. Typically, the list of
attributes contains each attribute that was searched on to obtain the keylist.
Keylists are hidden, in the sense that you must deal with keylist descriptors. Before you can
read a tuple based on a keylist, however, you must convert the keylist into a Tcl list that you can
scan through. This list is called a read list, and its elements are read nodes. The command
qddb_keylist get KeyList

produces a read list from a keylist.


Example: The following program fragment nds all tuples in the relation MyRelation and prints
them out (using commands soon to be introduced) in readable form. Each tuple is printed exactly
once.
set s [qddb_schema open MyRelation]
set k [qddb_search $s regexp .*] ;# get all tuples
set k [qddb_keylist process nullop -deldup_sameentry on $k]
foreach i [qddb_keylist get $k] { ;# one iteration per keylist node
set t [qddb_tuple read $s $i]
if {[string length $t] == 0} {continue} ;# deleted or modified
puts [qddb_tuple get readable $t] ;# output readable qddb_tuple
qddb_tuple delete $t ;# free storage
}
qddb_keylist delete $k ;# free storage
qddb_schema close $s

51
7.2.3 Tuples: qddb tuple
The qddb_tuple command manipulates an individual tuple. This command has many subcom-
mands, distinguished by its rst parameter:
Command Purpose
qddb_tuple read read an existing tuple from relation
qddb_tuple new create new tuple
qddb_tuple write write a tuple to relation
qddb_tuple get convert to an available format
qddb_tuple put convert from an available format
qddb_tuple lock lock tuple for writing
qddb_tuple unlock unlock tuple
qddb_tuple isempty check to see if tuple is empty
qddb_tuple refresh refresh in-memory tuple from database
qddb_tuple flush free memory associated with tuple
qddb_tuple remove remove tuple from disk
qddb_tuple delete delete in-memory tuple
The rst step in manipulating a tuple is to read it from the relation based on a read node.
Reading is accomplished by
qddb_tuple read ReadNode

which returns a tuple descriptor (another opaque identi er). If the tuple is invalid (in particular,
if it has been deleted), the tuple descriptor is a null string.
The values of the tuple's attributes are accessed by converting the tuple into one of three formats
by the qddb_tuple get command. These three varieties are provided because you may have various
purposes in mind when you access the values of a tuple. The formats look like this:
 external: A Tcl list with attributes and instances identi es much as they are in the Database
le, such as:
{
{%1.1.1.1 data in the attribute}
{%1.2.1.1 more data}
}

 readable: A Tcl list containing just one element, which looks much as the qedit and qadd
programs present tuples:
52
{
$NUMBER$ = "319";
a (
b = "some data for attribute a.b"
b = "some data for attribute a.b"
c = "some data for attribute a.c"
c = "some data for attribute a.c"
)
a (
d = "some data for attribute a.d"
)
}

 tclexternal: A list containing a number of (attribute,value) pairs, omitting attributes for


which there are no values:
{
a.b,1.1 "some data for attribute a.b"
a.c,2.1 "some data for attribute a.c"
}

You can modify the data in any of these formats, and then you can construct a new tuple
descriptor for the modi ed data and write out the modi ed tuple:
set t [qddb_tuple put Format $s TupleValue]
qddb_tuple write t

Here, Format is, for example, tclexternal. The intermediate value stored in t is a tuple
descriptor.
7.2.4 Rows: qddb rows
The qddb_rows command builds, sorts and formats rows. The various subcommands accept
keylist descriptors, tuple descriptors, or row descriptors. For example, given a keylist returned
by qddb_search, qddb_rows can return a list of the rows matching a query. Given a tuple descrip-
tor returned by qddb_tuple, qddb_rows can return all rows associated with that tuple. There are
several forms of the qddb_rows command:
Command Purpose
qddb_rows all return all rows associated with a tuple
qddb_rows select return selected rows based on a keylist
qddb_rows get get the attribute/instance numbers of a row
qddb_rows getval get values associated with a row
qddb_rows sort sort a list of row descriptors
qddb_rows delete delete in-memory row descriptor(s)

53
All row descriptors returned or manipulated by qddb_rows describe complete rows; that is, all
leaf attributes in the schema tree are included. You may elide certain attributes for the purpose of
deleting duplicates, but each resultant row will always be complete.
The two most useful forms of the qddb_rows command are qddb_rows all and qddb_rows
select. The qddb_rows all command traverses the tuple tree, possibly beginning with a partic-
ular instance of an attribute. When complete, a call to qddb_rows all returns the requested rows
associated with a particular tuple. If the -sortby and/or the -ascending options are speci ed,
the resultant row descriptors are sorted by the speci ed attributes, either in ascending (use the
-ascending option) or descending order (default). The -print option speci es the attributes you
are interested in for printing purposes; -sortby is independent of these attributes. Thus, you can
sort by attributes not speci ed by the -print option, and you can print attributes not speci ed by
the -sortby option.
The qddb_rows select command returns a list of elements for each row describing the results
of a search. Each element contains (1) a tuple descriptor, (2) a row descriptor, and (3) possibly
a list of attribute values. You must provide: (1) a keylist describing the results of a search, (2)
a list of the constraining2 attributes (if any), and (3) the attributes you are interested in seeing.
Duplicate rows are removed if the -deldup_rows on option is given. The return value format of
qddb_rows select -query off is:

{qddb_tuple0 qddb_row0 {some values}}


{qddb_tuple1 qddb_row1 {some other values}}
{qddb_tuple2 qddb_row2 {still some other values}}

Example: Suppose you want have a relation Debtors containing a set of people who owe you
money. The Schema might look like this:
Name (First Last)
Address (City Street State Zip)*
Phones (Desc Area Number)*
SS verbosename "Social Security Number"
Date verbosename "Date loaned" type date
Amount type real format "%.2f"
Rate verbosename "Interest Rate" type real format "%.2f"

Now suppose that you want to add interest to all records with a Date older than 30 days. The
following segment of code accomplishes this:
2
The constraining attributes are de ned by the pruning attributes used to obtain the keylist.

54
#!/usr/local/qddb/bin/qtclsh
# Monthly update procedure for Debtors
set s [qddb_schema open Debtors]
set k [qddb_search $s -prunebyattr Date date_range - {today - 30 days}]
set r [qddb_rows select -attrs Date -suppress on \
-print {Date Amount Rate} -deldup_rows on $k]
qddb_keylist delete $k ;# finished with the keylist
foreach i $r {
set t [lindex $i 0] ;# tuple descriptor
while {[qddb_tuple lock $t] == 0} {
puts "Waiting for tuple to be released"
exec sleep 1
}
set v [qddb_view define $t {}]
qddb_view set $v [lindex $i 1] ;# row descriptor
set cur [qddb_instance getval $v Amount]
set rate [qddb_instance getval $v Rate]
qddb_instance setval Amount [expr $cur * $rate + $cur]
qddb_tuple write $t
qddb_tuple delete $t ;# deletes row, tuple, and view
}
qddb_schema close $s

First, we (1) open the schema, (2) search for a date range meaning \older than 30 days ago,"
then (3) produce all unique rows. In this particular case, we know that no tuple will produce more
than one row. Next, we iterate through the list returned by qddb_rows select, lock each record,
then update, write and remove each record from memory. Finally, we close the schema, implicitly
deleting all storage associated with it (tuples, rows, and views).
7.2.5 Views: qddb view
The qddb_view command manipulates views within a Qddb tuple. You can de ne views and and
set them to particular rows (described by a row descriptor returned by qddb_rows) in a tuple.
You can also create new row descriptors describing the current row in a view. There are a few
subcommands of the qddb_view command:
Command Purpose
qddb_view define de ne a new view
qddb_view get get a new row descriptor for the view
qddb_view set set the view to a particular row
qddb_view delete delete in-memory storage for a view

Since the view always describes one complete row in a tuple, manipulating the view implicitly
manipulates the row it describes. We can bind the individual columns of the row to Tcl variables
55
when we de ne the view, or we can bind no columns and choose to manipulate the view only with
the Qtcl command qddb_instance. When you provide qddb_view define an empty list of Tcl
variable names, you bind no columns to Tcl variables. For example, qddb_view define $tuple {}
will return an opaque identi er referring to the new view that is bound to no Tcl variables.
Example: You have a relation \Library" (like the one presented in Chapter 2) and you want to
write a Qtcl procedure to build a new complete view. By convention, we typically use a Tcl array
with attribute names as indices. The code for such a routine might look like this:
proc new_view {schema_desc tuple_desc array_name} {
set l [qddb_schema leaves $schema_desc]
foreach i $l {
lappend attrs [list $i ${array_name}($i)]
}
return [qddb_view define $tuple_desc $attrs]
}

We rst get the list of all leaves in the schema with qddb_schema leaves. Next, we iterate
through the list, appending an (attribute,variable) element at each step. Finally, we de ne the view
with qddb_view define and return the result (an opaque identi er to the view.) This opaque
identi er can then be used with qddb_view and qddb_instance Qtcl commands.
7.2.6 Instances: qddb instance
The qddb_instance command manipulates de ned rows. There are many subcommands of
qddb_instance:

Command Purpose
qddb_instance new create a new instance
qddb_instance current get current instance number
qddb_instance getval get value of an instance
qddb_instance isempty check to see if an instance is empty
qddb_instance maxnum max instance number
qddb_instance move swap instances
qddb_instance remove delete instances
qddb_instance setval set the value of an instance
qddb_instance switch shift view to a di erent instance
Example: Suppose you have a relation \Family" with the following schema:
Name ( First Last )
Address ( Street City State Zip )*

56
You want to write a two Qtcl procedures: (1) to add a new Address to a given tuple, and (2)
to delete the current instance of Address. The procedures might look like these:
proc add_address {view street city state zip} {
qddb_instance switch $view Address [qddb_instance new $view Address]
qddb_instance setval $view Address.Street $street
qddb_instance setval $view Address.City $city
qddb_instance setval $view Address.State $state
qddb_instance setval $view Address.Zip $zip
}
proc del_address {view} {
qddb_instance remove $view Address \
[qddb_instance current $view Address]
}

The add_address procedure accepts a view descriptor view and a set of values, one for
each subattribute. The current instance of Address is switched to the new instance created by
qddb_instance new. Next, the value of each attribute in the new instance is set to the correspond-
ing parameter. The del_address procedure accepts a view descriptor and deletes the current
instance of Address from the tuple.

7.3 Searching
Qddb provides a plethora of searching techniques. You can search by word, numbers, dates, ranges,
and regular expressions. You can tell Qddb what a word looks like with the separators option in
the Schema. You can also prune the resultant keylist of uninteresting attributes, in e ect searching
on a particular attribute.
The qddb_search command returns a keylist descriptor. This keylist descriptor can then be
(1) used to produce rows matching the query, (2) combined with other keylists using intersection,
union, and exclusion, (3) pruned of duplicate rows or entries, (4) copied and sorted, and (5) used
to directly read tuples.
Example: Suppose you have a relation \MyRelation" and you want to print a list of all tuples
that match the word range \r-z" in an attribute named \A". You aren't interested in rows, but in
entire tuples. You also only want to print each tuple once and in readable form.
set s [qddb_schema open MyRelation]
set k [qddb_search $s -prunebyattr A word_range r - z]
set k [qddb_keylist process nullop -deldup_sameentry on] $k
foreach i [qddb_keylist get $k] {
set t [qddb_tuple read $s $i]

57
if {[string length $t] == 0} {continue}
puts [qddb_tuple get readable $t]
qddb_tuple delete $t
}
qddb_keylist delete $k
qddb_schema close $s

First, we open the schema and search for a word range \r-z" within attribute \A"; the
qddb_search command returns a keylist that we use to nd the matching tuples. We may have
multiple keylist nodes for any individual matching tuple, so now we must prune the keylist of all
nodes referring to the same entry. Finally, we walk through the keylist and (1) read the tuple, (2)
check to see if the tuple we read is valid (not deleted), (3) print the tuple in readable form, and
(4) delete the tuple from memory. Notice that we did not lock the tuple before we read it. This is
because Qddb takes care of read locks for you. A qddb_tuple read (or any other Qddb command
that reads a tuple) will wait for any writes to complete before reading. Read and write operations
are atomic in Qddb, so even if the tuple is locked for writing, you will still be able to read it. You
only need to invoke qddb_tuple lock before writing a tuple.

7.4 Retrieving and displaying rows


Once we have a keylist describing the tuple trees that satisfy our query, we can determine all
matching full or partial rows with qddb_rows. There are many applications of qddb_rows, but
here we will discuss only the most common. Typically, you want to do one of the following: (1)
produce rows (possibly sorted) for viewing purposes, (2) produce rows for update purposes, and (3)
produce rows to determine whether a tuple meets some criterion, perhaps for deletion purposes.
This section discusses these.
7.4.1 Producing rows for display
The most common reason to produce rows is for viewing purposes. A user has some query and
wants to see all the matching rows in some predetermined order. Usually, you want to provide a
sorted list of matching rows, displaying only those columns (attributes) that are interesting. You
might want to arrange the columns in a di erent order than they are presented in the Schema.
Example: Suppose you have the relation described in Section 7.2.4. You want to produce a
report containing the list of everyone who owes you money, how much they owe, and their phone
numbers. You want the list in a descending order based on the Amount attribute. The following
code accomplishes this task:
#!/usr/local/qddb/bin/qtclsh
set s [qddb_schema open Debtors]

58
set k [qddb_search $s -prunebyattr Amount number_range 0.01 -]
set r [qddb_rows select -attrs Amount -suppress on \
-print {Amount Name.First Name.Last} $k]
qddb_keylist delete $k
foreach i $r {lappend sorted [lindex $i 0]}
set sorted [qddb_rows sort Amount $sorted]
foreach i $sorted {
set t [qddb_rows tuplename $i]
set v [qddb_view define $t {
{Amount amt}
{Name.First fn}
{Name.Last ln}
{Phones.Desc ph_desc}
{Phones.Area ph_area}
{Phones.Number ph_num}
}]
puts [format "%30s %6.2f (%s) %s-%s" \
"$ln,$fn" $amt $ph_desc $ph_area $ph_num]
set max [qddb_instance maxnum $v Phones]
if {$max > 1} {
for {set i 2} {$i <= $max} {incr i} {
qddb_instance switch $v Phones $i
puts [format "%30s %6s (%s) %s-%s" \
" " " " $ph_desc $ph_area $ph_num]
}
}
qddb_tuple delete $t
}
qddb_schema close $s

The rows are sorted immediately after calling qddb_rows select. Each row is processed in
order. If a record has multiple phone numbers, then one extra line for each phone number is
printed immediately after the initial row.
7.4.2 Updating records
A common database task is to update all records containing rows that match some query. Section
7.2.4 provides one example; here's another.
Example: Using our Debtors relation, suppose you want to give a gift of $100 to everyone living
in Lexington, Kentucky that owes you more than $500. The following Qtcl script does this.
#!/usr/local/qddb/bin/qtclsh
set s [qddb_schema open Debtors]

59
set k [qddb_search $s -prunebyattr Amount number_range 500.00 -]
set k1 [qddb_search $s -prunebyattr Address.City Lexington]
set k [qddb_keylist op intersection $k $k1] ;# deletes old $k & $k1
set k2 [qddb_search $s -prunebyattr Address.State Kentucky]
set k [qddb_keylist op intersection $k $k2]
set r [qddb_rows select -attrs {Amount Address.City Address.State} \
-suppress on -deldup_rows on $k]
qddb_keylist delete $k ;# finished with the keylist
foreach i $r {
set t [lindex $i 0] ;# tuple descriptor
set v [qddb_view define $t {}]
while {[qddb_tuple lock $t] == 0} {
puts "Waiting for tuple to be released"
exec sleep 1
}
set amt [qddb_instance getval $v Amount]
qddb_instance setval Amount [expr $amt - 100.0]
qddb_tuple write $t
qddb_tuple delete $t ;# deletes row, tuple, and view
}
qddb_schema close $s

7.4.3 Tuple manipulation by rows


Finding tuples containing a row matching some query is useful for deleting outdated records, moving
them to a di erent relation, and so forth. The qddb_rows select command can be used to nd
tuples, but there is a more ecient way. The qddb_keylist prune command is a nice way to nd
tuples matching a query without the expense of producing the rows.

Example: Suppose you want to delete everyone from your Debtors relation that has a balance of
$0.00.
#!/usr/local/qddb/bin/qtclsh
set s [qddb_schema open Debtors]
set k [qddb_search $s -prunebyattr Amount n 0.00]
set k [qddb_keylist process prune -prunebyrow {Amount} \
-deldup_sameentry on $k]
foreach i $k {
set t [qddb_tuple read $i] ;# tuple descriptor
if {[string compare $t ""] == 0} {continue}
qddb_tuple remove $t ;# delete from relation
}
qddb_schema close $s

60
7.5 Importing data from conventional relational databases
This section describes how to write common Tcl applications for importing data from standard
relational tables. You must be able to produce an Ascii table with columns separated by some
character in order to take advantage of these methods.
7.5.1 Single tables
Single tables are the easiest to translate; typically they will translate into Qddb format on a row-
by-row basis. Each eld in the table should have a corresponding attribute in the Qddb Schema.
Suppose you have the schema
Name Address * Phone ( Desc Number ) *
and you are translating from a table of unique rows of the following format:
Name Address Home Phone Work Phone
Further suppose that you can export the data into a form that is separated by the character \@"
and \@" cannot occur in any attribute's value. Some sample data might look like:
George Henry Mustard@1232 Rambling Road@(521) 123-1243@(231) 312-9876
Jane Mustard@1232 Rambling Road@(521) 123-1243@(231) 312-9432
John Saluman@754 Rambling Road@(521) 123-1125@(231) 312-3214
The rst step is to build a le, say ExportFile, containing the table in the above form. Next, you
must map the exported data into the Qddb Schema that you have created. In our example, the
Name and Address attributes map directly. The Phone attribute, however, must have two instances
in the Qddb tuple rather than two explicit attributes as is in the data. The Phone.Desc attribute
must be provided because our Qddb schema does not distinquish between \Home Phone" and
\Work Phone" explicitly. Here is a Qddb Tcl script that does the translation.
#!/usr/local/qddb/bin/qtclsh
set s [qddb_schema open Relation]
set fd [open ExportFile r]
while {[gets $fd var] != -1} {
set var [split $var @]
set t [qddb_tuple new $s]
set v [qddb_view define $t {
{ Name arr(Name) }
{ Address arr(Address) }
{ Phone.Desc arr(Phone.Desc) }

61
{ Phone.Number arr(Phone.Number) }
}]
set arr(Name) [lindex $var 0]
set arr(Address) [lindex $var 1]
set arr(Phone.Desc) Home
set arr(Phone.Number) [lindex $var 2]
qddb_instance switch $v Phone \
[qddb_instance new $v Phone]
set arr(Phone.Desc) Work
set arr(Phone.Number) [lindex $var 3]
qddb_tuple write $t ;# saves tuple to disk
qddb_tuple delete $t ;# frees up storage
}

First, we open the ExportFile and the Qddb schema. Next, we read each line in the ExportFile
and split the row into its components with the Tcl command split. A new Qddb tuple is created
with qddb_tuple new and a view is created into the tuple. For each row, there are two phone
numbers. We must rst set the Tcl variables in the view to the corresponding data in the row, and
then create a new instance of the Phone attribute. Finally, we (1) set the new instance to be the
current instance in the view, (2) set the new instance's Tcl variables, and (3) write the tuple.

7.5.2 Multiple tables with a unique key


Multiple tables must have a unique key on which to join the tables before translating them into
Qddb format. Typically, what you want to do is read lines from each exported le (much like in a
single table translation) but keep them in memory. Use the unique key as an index into an array for
each table, then after all tables have been read, merge them into the Qddb database as appropriate.

Example: Suppose we have two relational tables, Client and Address:

Client
First Name Last Name Id
Joe Murphy 100
John Doe 102
Jane Matt 104
Harriett Mattingly 109

62
Address
Street1 Street2 City State Zip Id
123 Water Pike Watertown Missouri 32123 100
1021 Versailles Road Lexington Kentucky 40508 100
Star Rt. 1 Mud Creek Kentucky 41654 102
1232 Henry Park Drive Suite 10232 New York New York 61232 104
501 Gentilly Park Auburn Alabama 12345 109
The equivalent Qddb Schema might look like:
# Qddb Schema for the "Client" relation
Use reduced attribute identifiers
Use cached hashing
CacheSize = 1000
HashSize = 10000

Name (First Last)


Address (Street City State Zip) *

To perform the translation into Qddb tuples, we must rst join the tables based on the link
eld Id. Assuming the Client table and the Address table are in the les Client.table and Addr.table
respectively, the following Tcl code accomplishes this task:
set fd_c [open Client.table r]
while {[gets $fd_c str] != -1} {
set l [split $str ,] ;# ',' separates fields
set gv_client([lindex $l 2]) $str
}
close $fd_c
set fd_a [open Addr.table r]
while {[gets $fd_a str] != -1} {
set l [split $str ,] ;# ',' separates fields
lappend gv_addrs([lindex $l 5]) $str
}
foreach i [qddb_schema leaves $s] {
lappend attr_pairs [list $i gv_attr($i)]
} ;# build a list of {attr,var} pairs

We rst read the entire contents of the Client.table and Addr.table les. Each line of the
Client.table le will eventually become a unique Qddb tuple, so we build a Tcl array (gv_client)
to temporarily hold the values and use the unique Id as an index. Each line of the Addr.table le
is then read and appended to another Tcl array gv_addrs, where each element is a list of rows
(from Addr.table) for a unique Id. Finally, we iterate through the indices of the gv_client array,
63
building a new tuple for each element. To build the addresses for a particular client, we look at
the gv_addrs array element with the same index. If a client has more than one address, we must
build and initialize a new instance of Address in the current Qddb tuple. To build all the tuples
from our Tcl arrays, we do the following:
set s [qddb_schema open Client]
foreach i [array names gv_client] {
set t [qddb_tuple new $s] ;# new tuple
set v [qddb_view define $t $attr_pairs]
initClientProc $i
if [info exists gv_addrs($i)] {
set addrs [array names gv_addrs($i)]
initAddrProc [lindex $addrs 0]
foreach j [lrange $addrs 1 end] {
qddb_instance switch $v Address \
[qddb_instance new $v Address]
initAddrProc $j
}
}
qddb_tuple write $t
qddb_tuple delete $t ;# delete in-memory copy
}
qddb_schema close $s

For clarity, we have separated the Tcl procedures responsible for initializing values. Notice the
view's Tcl variable (gv_attr) must be declared global within any Tcl procedure:
proc initClientProc {idx} {
global gv_client gv_attr
set gv_attr(Name.First) [lindex $gv_client($idx) 0]
set gv_attr(Name.Last) [lindex $gv_client($idx) 1]
}
proc initAddrProc {idx} {
global gv_client gv_attr
set gv_attr(Address.Street) [lindex $gv_addrs($idx) 0]
append gv_attr(Address.Street) "\n[lindex $gv_addrs($idx) 1]"
set gv_attr(Address.City) [lindex $gv_addrs($idx) 2]
set gv_attr(Address.State) [lindex $gv_addrs($idx) 3]
set gv_attr(Address.Zip) [lindex $gv_addrs($idx) 4]
}

64
Chapter 8

Designing Custom Qddb Interfaces


with the Fx Toolkit
Although the generic application (nxqddb) is quite useful, many database applications require a
special interface. The Fx Toolkit is a set of Itcl and Tcl tools that provide a very high-level
interface to the Qddb Tcl commands. This chapter describes the Fx tools and how the tools t
together to form a custom application. Section 8.3 and section 8.5 demonstrate the tools with
examples.

This chapter assumes familiarity with Tcl, Tk, and Itcl. If you are not familiar with Tcl, you
should review the Tcl man pages and some of the available Tcl literature.

8.1 Requirements
The Fx Toolkit requires the following Qddb con gure options: --with-itcl and --with-tk. We
also recommend --with-blt, --with-tclx, and --with-tktable. You'll need --with-blt to
include the busy cursor (a watch by default) in Fx applications. BLT is also nice for graphing data
and building hypertext applications. Tktable gives you the ability to display data in a tabular or
spreadsheet-style form.

8.2 The tools


The Fx toolkit consists of three major Itcl classes: Fx_Menubar, Fx_Frame, and Fx_Entry. The
Fx toolkit is initialized by a call to Fx:Init, which must be called before accessing any other Fx
procedures or declaring any Fx class instances.
65
8.2.1 Fx:Init
The Fx toolkit routine Fx:Init must be called before creating the menubar, frames, and entries.
You must set the global Tcl variable fx_config_dir, which points to a directory relative to your
home directory, before calling Fx:Init. It holds your application-speci c initialization code. The
typical initialization sequence for an application looks like this:
lappend auto_path $qddb_library/fx ;# autoload Fx toolkit
if {[info exists blt_library]} {
lappend auto_path $blt_library ;# autoload BLT library
}
if {[catch "qddb_schema open [lindex $argv 0]" s] != 0} {
puts "Cannot open Schema for relation: [lindex $argv 0]"
puts "$s"
exit 1
}
set fx_config_dir .myapplication_config
Fx:Init $s

Fx:Init takes care of (1) executing personal and global con guration les, (2) initializing fonts
and other resources, and (3) setting up the key/mouse bindings.

8.2.2 Fx Menubar
Fx_Menubar should be one of the rst Tcl commands after calling Fx:Init. It creates the standard
menubar, menubuttons, and menus for the Fx toolkit. It also controls the current schema, tuple,
and view. Since Fx is very con gurable, Fx_Menubar has many options.

Fx_Menubar is actually an Itcl class. When it is invoked, it creates a menubar instance. You can
then link Fx_Frame and Fx_Entry instances to that menubar. The invocation that creates a new
instance called menubar is as follows:
Fx_Menubar menubar -w .menubar -schema $s \
-config_dir .myapplication_config -array my_tuple_array

The above command creates not only creates the instance (called menubar), it also constructs a
Tk frame containing the menubar (called .menubar). It also speci es the application's con guration
directory with the -config_dir .myapplication_config option. The Tcl associative array that
will hold the contents of the current tuple (in Add Mode, Change Mode, or Read-only Mode) is
given by the -array my_tuple_array option.
66
You can specify actions (as Tcl commands) to perform before and after entering any of the modes.
To do this, you specify the appropriate -after<modename>mode or -before<modename>mode option
to the Fx_Menubar command. For example, to execute a Tcl command that sets a default value
upon entering Add Mode, you could do the following:
Fx_Menubar menubar -w .menubar -schema $s \
-config_dir .myapplication_config -array my_tuple_array \
-afteraddmode {
global my_tuple_array
uplevel \#0 {set my_tuple_array(MyAttribute) "Some default value"}
}

Most of the special options are interactively con gurable from the menubar, but sometimes it
is useful to restrict the user to certain options. All the menus (except Help and Con gure) allow
you to specify a Tcl list to evaluate after a menu button is selected. These options have the form
-afterpost_<menuname>, where <menuname> is one of file, edit, modes, view, and templates.
The -afterpost_ options are typically used to disable or enable menu entries based on some
condition.

You can add your own menu buttons to the menubar by setting up the frame and declaring one
Tk menubutton and menu before calling Fx_Menubar. If the frame you pass to Fx_Menubar already
exists, Fx_Menubar will not create a new one. The new buttons are passed to Fx_Menubar by an
-auxbuttons option. For example:

frame .mb ;# set up the frame; Fx_Menubar will notice it exists.


pack .mb -side top -fill x
menubutton .mb.newitems -text "New Items" -menu .mb.newitems.menu \
-underline 0
# don't pack .mf.newitems if you want to maintain the Fx_Menubar's order
menu .mb.newitems.menu
.mb.newitems.menu add command -label "One new item" -underline 0
.mb.newitems.menu add command -label "Two new items" -underline 0
Fx_Menubar menubar -w .mb -schema $s \
-config_dir .myapplication_config -auxbuttons .mb.newitems

By convention, all menu buttons are named by their text label in all lower case. The corre-
sponding menu is called menu. For example, to add a new item to the View menu button, you
might:
Fx_Menubar menubar -w .mb -schema $s -config_dir .myapp_config
.mb.view.menu add command -label "New view command"

67
8.2.3 Fx Frame
The command Fx_Frame (again, an Itcl class) instantiates an Fx frame, which associates a struc-
tured (and possibly expandable) attribute with a Tk frame. If the attribute given by the -attr
option is expandable, then the command will create Add, View, and Del buttons. An example of
creating an instance of Fx_Frame called \myframe.f" might be:
Fx_Frame myframe.f -w .myframe.f -attr Attr1.Attr2 -setschema $s \
-labelfg deeppink

The -w and -attr options are mandatory. The -setschema option must be supplied on the rst
call to Fx_Frame. After that, it is unnecessary; setschema is a common variable for the Fx_Frame
class.
The Fx_Frame instance creates a Tk frame (speci ed by -w), an internal frame called f_0, a label
f_0.l, and possibly three buttons f_0.b_add, f_0.b_view, and f_0.b_del. These widgets may
be repacked with the Tk pack command if you want to arrange them in a particular way.
After an Fx frame has been built for an attribute, the application usually builds subframes for
its subattributes by further calls to Fx_Frame. Leaf attributes are given Tk frames by a call to
Fx_Entry, described below. After all the subattributes have been treated, the outer Fx frame
instance is generally invoked with the -focus option to specify the entry where focus should be
placed when the Add or View button is pressed for that Fx frame. For example, we might have an
Fx frame with one Fx entry:
Fx_Frame myframe -w .myframe -attr A;# outer attribute
Fx_Entry myframe.b -w .myframe.b -attr A.B;# leaf attribute
myframe configure -focus [myframe.b GetEntry]

The buttons (Add, View, Del) perform speci c operations attribute instances. You can tell Fx to
evaluate a Tcl command before and/or after each operation with the -after<op> and -before<op>
options, where <op> is one of add, view, and delete. For example, you might want to prevent the
user from deleting non-empty instances:
Fx_Frame myframe -w .myframe -attr A \
-beforedelete {
if {[qddb_instance isempty $view A] == 0} {
return
}
}

Because the command lists are evaluated (in an environment in which view is set), the return
statement returns from the DelInstance method instead of deleting the instance.
68
8.2.4 Fx Entry
The command Fx_Entry (again, an Itcl class) instantiates an Fx entry, which assiciates a leaf (and
possibly expandable) attribute with a Tk frame. You may place the Fx entry's Tk frame anywhere
in the Tk widget hierarchy; traditionally, it is placed in its parent Fx frame's Tk frame.
Here is an example that creates an instance of Fx_Entry called myframe.f.f:
Fx_Entry myframe.f.f -w .myframe.f.f -attr Attr1.Attr2.Attr3 \
-setschema $s -labelfg blue -searchfor_entry [menubar SearchForEntry]

This example would place the new entry's frame inside the parent's Tk frame .myframe.f,
which we declared earlier.
The -w and -attr options are mandatory for each call to Fx_Entry. The rst call to Fx_Entry
must also contain the -setschema and -searchfor_entry options. After the rst call to Fx_Entry,
you need not specify them again; they are common variables for the Fx_Entry class. The
-searchfor_entry option establishes the Tk widget path for the "Search for:" entry. Usually,
this value is discovered by invoking the Fx_Menubar instance's SearchForEntry method.
The Fx_Entry instance creates a Tk frame (speci ed by -w), an internal frame called f_0, a label
f_0.l, and possibly three buttons f_0.b_add, f_0.b_view, and f_0.b_del. These widgets may
be repacked with the Tk pack command if you want to arrange them in a particular way.
You can specify default entry types with the -type option. A type may be one of: Entry, Text,
or Radiobutton (case is sensitive). If the type is Radiobutton, then you can specify a set of default
values with the -default_values option. Each default value will produce one radio button with
the value as the label.

8.2.5 Associating Fx Frame and Fx Entry with the Fx Menubar


After you have created all the instances of Fx_Frame and Fx_Entry that you want, you must link
the instances with the menubar instance. You can accomplish this with the following Tcl command,
assuming your Fx menubar instance is called menubar:
menubar configure -instances [Fx_Entry :: GetInstances] \
-frames [Fx_Frame :: GetInstances]

8.3 A simple example


Suppose you have the following Schema for the relation MyRelation:
69
a ( b c )* d

Here is a simple Fx script tailored to this Schema:


#!/usr/local/qddb/bin/qwish -f
lappend auto_path $qddb_library/fx
if [info exists blt_library] {
lappend auto_path $blt_library
}
set s [qddb_schema open MyRelation]
set fx_config_dir .myapplication_config
Fx:Init $s
Fx_Menubar menubar -w .mb -schema $s -array gv_attr \
-config_dir $fx_config_dir
menubar configure -aftersearchmode {wm title . "Search Mode"}
menubar configure -afterchangemode {
global gv_attr ; wm title . $gv_attr(a.b)
}
menubar configure -afteraddmode {
wm title . "Add Mode"
uplevel \#0 [list set gv_attr(a.c) "My default value"]
}
Fx_Frame a -w .a -setschema $s -attr a -side top \
-anchor nw -relief raised -afteradd {
uplevel \#0 [list set gv_attr(a.c) "My default value"]
}
Fx_Entry a.b -w .a.b -searchfor_entry [menubar SearchForEntry] \
-setschema $s -attr a.b -relief sunken -bd 3 -side left
Fx_Entry a.c -w .a.c -attr a.c -relief sunken -bd 3 -side left
a configure -focus [a.b GetEntry]
Fx_Entry d -w .d -attr d -relief sunken -bd 3 -side left
menubar configure -instances [Fx_Entry :: GetInstances] \
-frames [Fx_Frame :: GetInstances]
menubar SearchModeProc ;# start out in Search Mode

The example rst sets up the auto_path global Tcl variable, opens the schema, and initial-
izes Fx with Fx:Init. It de nes the menubar and con gures it to perform certain tasks when
entering Search, Change, and Add Mode. Next, it builds the Fx_Frame and Fx_Entrys corre-
sponding to the schema. It passes a schema to the rst call to Fx_Frame and Fx_Entry and a
searchfor_entry to the rst call to Fx_Entry. The frame is con gured with the -focus option
after all the nested Fx_Entrys are de ned; this is to specify where the focus will be placed af-
ter pressing the Add or View button for that frame. Finally, we associate all the Fx_Frames and
Fx_Entrys with the menubar and go to Search Mode. Note: within the call to menubar configure

70
-afteraddmode we use an uplevel \#0 to set the value of the linked variable gv_attr(a.c) (cre-
ated with Tcl_LinkVar); this is necessary due to an interaction with [incr Tcl]. You must use
uplevel \#0 when setting the value of a linked variable from within an [incr Tcl] class.

8.4 Implementing custom features


One of the goals of the Fx toolkit is to allow the programmer to customize as much of the interface
as possible without losing the common look and feel. Any user familiar with one Fx application
should be able to muddle through almost all Fx applications. Of course, there may be exceptions
to this rule (for example, custom secondary screens for invoice entry).
This section describes some of the common custom features that you can implement with Fx.
We cannot, of course, describe all the possible things you can do. The manual pages describe all
the options available to the Fx commands.
8.4.1 Naming itcl classes, procedures, and global variables
When you write custom Fx applications, you need to realize that Fx uses the global Tcl name space
to hold many variables and procedures. For this reason, you should take care not to con ict with
any of Fx's prede ned names. You should avoid pre xing your names with fx in any case or case
mixture. Consider these names reserved for future use by the toolkit even if the name you want
isn't in use at the moment.
8.4.2 Augmenting/creating menus
Fx allows you to augment existing menus or create new ones. To augment an existing menu, you
must explicitly add a menu item to the appropriate menu. Menus are named according to their
label in all lowercase. For example, to add a menu item to kill the last search results, you might
do this:
.mb.view.menu add command -underline 0 -label "Kill last search" \
-command {menubar KillLastSearch}

The -underline option speci es the character to be underlined in the menu item's label and
used as an accelerator.
8.4.3 Interfacing with the search results
Fx generates a reasonably nice Search Results listbox for displaying the result of a query. Since
you might want to add menus for special prede ned searches, Fx provides a mechanism for you to
change the contents of the Search Results. There are several Fx_Menubar methods available to aid
this sort of manipulation:
71
Method Description
DisplayLastSearch Display the contents of the last search.
KillLastSearch Delete the contents of the last search.
GetLastSearch Get the contents of the last search.
SetLastSearch list Set the contents of the last search to list.
The SetLastSearch method takes a list argument that must be the result of a call to
qddb_rows select -query off; it implicitly calls KillLastSearch. GetLastSearch returns such
a list or {} if there are no previous search results. DisplayLastSearch displays the contents of the
last search results in the standard Search Results listbox.
Example: We can add a menu item to the View menu to generate a search that matches all
tuples:
Fx_Menubar menubar -w .mb -schema $schema
.mb.view.menu add separator
.mb.view.menu add command -label "All records" -command MyApp_ViewAll

proc MyApp_ViewAll {} {
set s [menubar info public schema -value]
set k [qddb_search $s regexp .*]
set k [qddb_keylist process nullop -deldup_sameentry on $k]
set r [qddb_rows select $k]
qddb_keylist delete $k
menubar SetLastSearch $r
menubar DisplayLastSearch
}

8.4.4 Calculated elds


You can calculate some elds in your relation calculated from other elds. For example, suppose
you have a Students relation containing:
Name
SS verbosename "Social Security Number" separators ""
Course verbosename "Course Number"
Grades (
Description
Weight type real
Score type real format "%.2f"
)*
Total type real format "%.2f"
FinalGrade verbosename "Final Grade"

72
You want to recalculate the Total every time one of the scores (or weights) changes. You might
do something like this (in the global context, of course):
#...stuff deleted...
Fx_Entry Grades.Weight -w .grades.weight -attr Grades.Weight \
-read_only 1 -userconfig 0 -width 5
Fx_Entry Grades.Score -w .grades.score -attr Grades.Score \
-read_only 1 -userconfig 0 -width 5
proc MyApp_Recalc {} {
global weight score gv_attr
if {[Fx_Entry :: TupleChanged] == 0} {return} ;# nothing changed
set t [menubar info public tuple -value]
set v [qddb_view define $t {
{Grades.Weight weight}
{Grades.Score score}
}]
set max [qddb_instance maxnum $v Grades]
set tot 0.00
for {set i 1} {$i <= $max} {incr i} {
qddb_instance switch $v Grades $i
set tot [expr $tot + ($weight * $score)]
}
# setting gv_attr(Total) automatically sets tuple as changed
set gv_attr(Total) [format "%.2f" $tot]
update idletasks
qddb_view delete $v
}
bind [Grades.Weight GetEntry] <FocusOut> MyApp_Recalc
bind [Grades.Score GetEntry] <FocusOut> MyApp_Recalc

Now whenever the user presses the <Tab> key or selects a menu button, the Total will be
recalculated if the cursor was in either the Grades.Weight or Grades.Score eld and the tuple
has been modi ed.
8.4.5 Generating unique identi ers
Suppose you have a client/invoice database containing two major components: client information
and invoices. You could describe this database with a single Qddb relation:
Client (
Name (First Last)
Address (Street City State Zip)
Phones (Description Area Number)
)

73
Invoices (
Number type integer verbosename "Invoice Number"
Date type date format "%I:%M %p, %B %d, %Y"
Items (
Qty type integer
Number verbosename "Item number" separators ""
Description
Price type real format "%.2f"
Total type real format "%.2f"
)*
Total type real format "%.2f"
)*

The Invoices.Number eld should be a unique integer and must be generated whenever an
invoice is created. One common practice is to create a Setup relation containing some of the
standard information you commonly need: business name/address, next invoice number, etc. A
Setup Schema might look like:

Name verbosename "Business name"


Address verbosename "Business address" (
Street City State
Zip verbosename "Zip Code"
)
NextInvoice verbosename "Next invoice number" type integer

Using this relation, we can de ne a new invoice number whenever a new invoice for a particular
client is created. For example, the following code fragment explains what must be done to generate
a new invoice number and to prevent the user from changing it:
Fx_Frame Invoices -w .invoices -attr Invoices -afteradd AddInvoiceProc
proc AddInvoiceProc {} {
global gv_attr next {fx:status_variable}
set s [qddb_schema open Setup]
set k [qddb_search $s -prunebyattr Name regexp .*]
set k [qddb_keylist process nullop -deldup_sameentry on $k]
set t {}
foreach i [qddb_keylist get $k] {
set t [qddb_tuple read $s $i]
if {[string compare $t {}] == 0} {continue}
}
qddb_keylist delete $k
if {[string compare $t {}] == 0} {

74
set {fx:status_variable} {Error! Must set up Setup relation!}
return
}
while {[qddb_tuple lock $t] == 0} {
set {fx:status_variable} "Waiting for Setup screen to close."
exec sleep 1 ; set {fx:status_variable} {} ; exec sleep 1
}
set v [qddb_view define $t {
{NextInvoice next}
}]
set gv_attr(Invoices.Number) $next
incr next
qddb_tuple write $t
qddb_schema delete $s ;# deletes/unlocks tuple, view and schema.
set gv_attr(Invoices.Date) [exec date {+%I:%M %p, %B %d, %Y}]
}
Fx_Entry Invoices.Number -w .invoices.number -attr Invoices.Number \
-read_only 1 -userconfig 0 -date_search 0 -regexp_search 0
Fx_Entry Invoices.Date -w .invoices.date -attr Invoices.Date \
-read_only 1 -userconfig 0 -regexp_search 0

Since the procedure AddInvoiceProc is called after creating and switching to the new instance
of Invoices, we just need to set the invoice number and date. The last few lines disable regular-
expression and date searching on the invoice number eld.
8.4.6 Designing an application for a Setup relation
The typical Setup application manipulates a relation containing a single tuple. When you run your
Setup application, you want that tuple to come up immediately and you never want to search for
tuples.
Suppose we have a Setup relation with the following Schema:
Business ( Name Address ( Street City State Zip ) )
NextInvoice verbosename "Next Invoice Number #" type integer

After we de ne the Fx_Menubar, Fx_Frames and Fx_Entrys, we want to go directly into Change
Mode if the record has been created, and go into Add Mode otherwise. The full Setup application
might look like:
#!/usr/local/qddb/bin/qwish -f
lappend auto_path $qddb_library/fx
if {[info exists blt_library]} {
lappend auto_path $blt_library
}

75
wm title . "Setup"
set s [qddb_schema open Setup]
wm withdraw . ;# withdraw so the user doesn't watch the drawing.
Fx:Init $s
Fx_Menubar menubar -w .mb -schema $s -array gv_attr
set search_entry [menubar SearchForEntry]
Fx_Frame Business -w .biz -attr Business -setschema $s \
-side top -anchor nw -relief sunken -bd 2
Fx_Entry Business.Name -w .biz.name -attr Business.Name \
-searchfor_entry $search_entry -side top -anchor e -side left
Fx_Frame Business.Address -w .biz.addr -attr Business.Address \
-side top -anchor nw -relief sunken -bd 2
Fx_Entry Business.Address.Street -w .biz.addr.str \
-attr Business.Address.Street -width 40 -side left
.biz.addr.str.f_0.l configure -width 20
Fx_Entry Business.Address.City -w .biz.addr.city \
-attr Business.Address.City -width 40 -side left
.biz.addr.city.f_0.l configure -width 20
Fx_Entry Business.Address.State -w .biz.addr.state \
-attr Business.Address.State -width 40 -side left
.biz.addr.state.f_0.l configure -width 20
Fx_Entry Business.Address.Zip -w .biz.addr.zip \
-attr Business.Address.Zip -width 40 -side left
.biz.addr.zip.f_0.l configure -width 20
Fx_Entry NextInvoice -w .nextinv -attr NextInvoice -side top
pack .nextinv -side top -fill x
menubar configure -instances [Fx_Entry :: GetInstances] \
-frames [Fx_Frame :: GetInstances]
menubar configure -afterpost_modes {
.mb.modes.menu entryconfigure 0 -state disabled
.mb.modes.menu entryconfigure 1 -state disabled
}
menubar configure -afterpost_edit {
.mb.edit.menu entryconfigure 7 -state disabled
}
set k [Fx_QddbSearchParser :: MultiSearch $s {.*} on Business.Name]
set mykey [qddb_keylist get $k]
if {[llength $mykey] == 0} {
menubar configure -aftersave {
set k [Fx_QddbSearchParser :: MultiSearch $schema {.*} on Business.Name]
set mykey [qddb_keylist get $k]
menubar SetLastSearch [qddb_rows select -attrs Business.Name \
-print Business.Name $k]
menubar ChangeModeProc 0
}

76
menubar AddModeProc
} else {
menubar SetLastSearch [qddb_rows select -attrs Business.Name \
-print Business.Name $k]
menubar ChangeModeProc 0
}
qddb_keylist delete $k
wm deiconify .

First, we set up the Fx_Menubar and all the Fx_Frame and Fx_Entry instances. Next, we link
the frames and entries with the menubar, then disable some of the standard Fx features we aren't
interested in. Finally, we search with '.*' to provide the single record, then depending on whether
a record exists, go to Change Mode or Add Mode. After saving the record in Add Mode, we switch
to Change Mode to prevent entry of further records.

8.5 The design and implementation of nxqddb(1)


To demonstrate the ease in which an Fx application can be built, we designed an Fx-based replace-
ment for the original (and now obsolete) xqddb called nxqddb. Nxqddb has all the standard Fx-style
features and all the function of the original generic application. The entire nxqddb application is
around 300 lines of Tcl code.

8.5.1 Building a generic screen with Fx


The generic application nxqddb builds its interface from the format of the Schema. The Qddb Tcl
command qddb_schema print returns a list structure in a form similar to the Schema. This list
can be traversed to build an interface similar in appearance to the Schema. Figure 8.1 shows the
main Tcl procedure (NxqddbSetup) used by nxqddb to build the generic interface. NxqddbSetup is
called in the following way:
NxqddbSetup .c.f $s [qddb_schema print $s] "" 0

The Tk window path .c.f refers to a frame within a scrollable canvas. $s refers to the schema
returned by a call to qddb_schema open.

NxqddbSetup recursively traverses the list returned by the call to [qddb_schema print $s],
building Fx_Frames for all non-leaf attributes and Fx_Entrys for leaf attributes. The buttons
for expandable attributes are automatically included in both Fx_Frames and Fx_Entrys. The Tk
frames and entries are repacked after the calls to the Fx routines to give the application the look
and feel of the original xqddb.
77
proc NxqddbSetup {w s p n fnum} {
global gv_search_entry fx_config
set first 0
foreach i $p {
if {[string compare $n ""] == 0} {
set newn [lindex $i 0]
} else {
set newn $n.[lindex $i 0]
}
set f $w.f$fnum
incr fnum
if {[llength [lindex $i 2]] > 0} {
Fx_Frame f_$newn -w $f -attr $newn -side top -setschema $s \
-relief raised -bd 2 -labelfg deeppink -pady 0 -padx 0
pack $f.f_0 -side left -anchor ne -expand on -fill x
$f.f_0.l configure -anchor ne
pack $f.f_0.l -expand on -fill x -anchor ne
set f $f.f
frame $f -relief raised -bd 2
pack $f -side right -anchor ne
NxqddbSetup $f $s [lindex $i 2] $newn $fnum
} else {
Fx_Entry f_$newn -w $f -attr $newn -side left -anchor ne \
-width $fx_config(entry_width) -type Entry -setschema $s \
-padx 0 -pady 0 -searchfor_entry $gv_search_entry
pack $f.f_0 -expand on -fill x -anchor ne
$f.f_0.l configure -anchor ne
pack $f.f_0.l -anchor ne -expand on -fill x
pack $f.e -expand off -fill none
if {!$first && [string compare $n ""] != 0} {
set first 1
f_$n configure -focus [f_$newn GetEntry]
}
}
}
}

Figure 8.1: Tcl procedure for building nxqddb(1) screen

78
8.5.2 Implementing nxqddb's look and feel
nxqddb(1) requires several features not available from the standard Fx toolkit:
1. Vertical scrolling of all the entries
2. Resizable windows
3. Fixed-width entry boxes and con guration menu
This section discusses the implementation of each.
Vertical scrolling and resizable windows
To perform vertical scrolling of the Fx widgets, you must rst build the Fx_Menubar, then a canvas
and scrollbar. Place a frame inside the canvas in which to place the Fx widgets:
set fx_config_dir .nxqddb_config
if {[catch "qddb_schema open [lindex argv 0]" s] != 0} {
puts "Cannot open schema for relation: [lindex argv 0]"
exit 1
}
Fx_Menubar menubar -w .mb -schema $s -array gv_myattr \
-config_dir .nxqddb_config
set search_for_entry [menubar SearchForEntry]
scrollbar .s -command {.c yview}
pack .s -side right -expand on -fill y
canvas .c -yscroll {.s set}
pack .c -side left -expand on -fill both
frame .c.f
pack .c.f -side top -expand on -fill both
.c create window 0 0 -anchor nw -window .c.f
set gv_init 0
NxqddbSetup .c.f $s [qddb_schema print $s] "" 0
update idletasks
set scrht [winfo screenheight .]
set scrwid [winfo screenwidth .]
incr scrwid -50
XqddbReconfigure
bind . <Configure> {
global fx_config
set geo [split [wm geometry .] +]

79
set x [lindex $geo 1]
set y [lindex $geo 2]
set fx_config(maingeom) [list %w %h $x $y]
.c yview 0
}
menubar configure -instances [Fx_Entry :: GetInstances] \
-frames [Fx_Frame :: GetInstances]
# the following is a trick that knows 'this' will be a local
# variable when the list is evaluated.
Fx_Entry :: AfterReconfigure {XqddbReconfigure [$this GetEntry] 0}
Fx_Menubar :: AfterReconfigure {XqddbReconfigure}
Fx_Entry :: ScrollbarSide left
menubar SearchModeProc

This code rst opens the requested relation with qddb_schema open, then builds the menubar,
a scrollbar, a canvas, and a frame to place within the canvas. It then calls NxqddbSetup, which
recursively traverses the schema to build Fx frames and entries. Next, the code xes the canvas
width to the size of the frame built by NxqddbSetup by calling XqddbReconfigure (shown below).
Finally, it informs Fx_Entry and Fx_Menubar that after any con guration, XqddbReconfigure
needs to be called to resize the canvas and toplevel window. XqddbReconfigure looks like this:
proc XqddbReconfigure {{widg ""} {withdraw 1}} {
global scrwid scrht fx_config

if {$withdraw} {
wm withdraw .
}
if {[string compare $widg ""] != 0} {
pack $widg -expand off -fill none
}
wm minsize . $scrwid [expr $scrht / 10]; wm maxsize . $scrwid $scrht
update
update idletasks
set wid [winfo reqwidth .c.f]
set ht [winfo reqheight .c.f]
.c configure -width $wid -height $ht
.c configure -scrollregion [list 0 0 $wid $ht]
update
update idletasks
set reqwid [winfo reqwidth .]; set reqht [winfo reqheight .]

80
wm minsize . $reqwid [expr int(($reqht - $ht + $ht)/10)]
wm maxsize . $reqwid [min $reqht $scrht]
if {[info exists fx_config(maingeom)] && \
[lindex $fx_config(maingeom) 1] < [min $reqht $scrht]} {
wm geometry . \
${reqwid}x[lindex $fx_config(maingeom) 1]+\
[lindex $fx_config(maingeom) 2]+\
[lindex $fx_config(maingeom) 3]
} else {
wm geometry . ${reqwid}x[min $reqht $scrht]
if {[info exists fx_config(maingeom)]} {
wm geometry . \
+[lindex $fx_config(maingeom) 2]+[lindex $fx_config(maingeom) 3]
}
}
if {$withdraw} {
wm deiconify .
}
}

Fixed-width entry boxes


Because of the general layout of nxqddb's main screen, entries must be of similar widths for aesthetic
reasons. A global Tcl variable called fx_config(entry_widths) stores the xed width of all entries.
Whenever the contents of fx_config(entry_widths) change, nxqddb must recon gure all the Entry
and Text widgets. The following routine accomplishes this task:
proc ReconfigureEntryWidths {} {
global fx_config

foreach i [Fx_Entry :: GetInstances] {


$i configure -width $fx_config(entry_width)
}
XqddbReconfigure
}

Cascaded menu options con gure some pre-de ned entry widths:
.mb.config.menu add separator
.mb.config.menu add cascade -label "Entry Widths" \
-menu .mb.config.menu.entrywidth

81
.mb.config.menu.entrywidth add radiobutton \
-variable fx_config(entry_width) -value 10 -label 10 \
-command ReconfigureEntryWidths
.mb.config.menu.entrywidth add radiobutton \
-variable fx_config(entry_width) -value 20 -label 20 \
-command ReconfigureEntryWidths
.mb.config.menu.entrywidth add radiobutton \
-variable fx_config(entry_width) -value 30 -label 30 \
-command ReconfigureEntryWidths

The source for nxqddb's will show you further tutorial information on building generic screens.

82
Bibliography
[1] C. J. Date. A Guide to The SQL Standard. Addison-Wesley, 1987. ISBN 0-201-05777-8.
[2] H. F. Korth and A. Silberschatz. Database System Concepts. Computer Science Series. McGraw-
Hill, 1986. ISBN 0-07-044752-7.
[3] M. E. Lesk. Some applications of inverted indexes on the unix system. Unix Programmer's
Manual, 2b, 1978.
[4] J. K. Ousterhout. Tcl and the Tk Toolkit. Professional Computing Series. Addison-Wesley,
1994. ISBN 0-201-63337-X.

83

S-ar putea să vă placă și