Qgdbm for Tgdbm (Version 0.5)
Qgdbm wraps around the Tgdbm-functions and provide a higher-level sql-like syntax
Overview/Introduction
The package Qgdbm is wrapped around Tgdbm and provides a more convenient way to store and retrieve data. It is however not a replacement for a “professional” database. But serves well for applications which simply have to store a small/medium amount of data which fits into a simple table-structure.
The version of Tgdbm (from 0.4 to 0.5) is parallel used on Qgdbm (now in version 0.5, too), but there are no changes in Qgdbm (the new Array-Feature is not used in Qgdbm).
With Qgdbm you can access information from gdbm-files in an SQL-like fashion. Not really SQL, but a more Tcl-adopted SQL, let’s name it TSQL for (Tcl or Tiny(?)-SQL).
Qgdbm stores each table in a separate file named <table-name>.qg. All tables are stored in a specified root-directory.
All Qgdbm-Table-files have one reserved key/value-pair with key “@”. This entry is used to store administrative information like table-structure, number of inserts, date of creation, …
Because all needed information about one table is stored in the table-file itself it is easy to copy this file elsewhere, without having to worry about system-information or loss of information.
You simply copy the table-file you need, you can even copy into another user-directory (see below).
Qgdbm may be run in two modes.
Without (the default) system-tables or with system-tables.
The system-tables (or exactly in version 0.5 the one system-table named system.qg) holds information about
users. You can create tables which belong to a specified user (just like in “normal” databases). In this case no rights are given to different users (maybe this will come in one of the next versions of Qgdbm), so the paradigma is ”’everyone sees everything”’.
Each user has his own directory (directly under the root-directory). Tables could be specified as <user>.<table>, like the way you access a different scheme (e.g. in Oracle).
Managing users is only possible in the “With-System”Option.
Properties
Common features of Qgdbm are:
- SQL-Like access to database-files
- Tables with datatypes and constraints
- Exactly one primary key on each table
- Different users/schemes
- Optional logging-mechanism
- Table-locking
- Values of arbitrary length
- Platform-independent
Features which are not realized in Qgdbm:
- Transactions
- Permissions on Tables (like grants)
- Quotas/Tablespaces…
- Multiple primary keys, foreign keys
- Joins
Let’s take a look at some simple examples:
1. Simple Example
package require qgdbm
qgdbm::init -rootdir /tmp/
qgdbm::tsql create table address \
{{name char} {street char} {zip_code integer} {city char}}
qgdbm::tsql insert into address {$name $street} \
values [list {Grobi Sesame-Street}]
qgdbm::tsql insert into address values \
[list {Ernie Sesame-Street 12234 Sesame}]
set result [qgdbm::tsql select * from address]
qgdbm::cleanup
This simple script creates a table address (which is stored in the file <rootdir-parameter>/address.qg) and inserts the value “Grobi”. The variable result has the value: {Grobi Sesame-Street {} {}}.
2. Simple Example
package require qgdbm
qgdbm::init -rootdir /tmp/ -system 1
qgdbm::tsql create user grobi \
identified by grobis_password
qgdbm::tsql create table grobi.cookies \
{{id integer} {name char} {amount integer}}
qgdbm::cleanup
In this example the Qgdbm-package is initialized with the
system-option. With this option we are able to create users, and tables which belong to a specific user with the notation <username>.<table> (e.g.: grobi.cookies).
Commands
These commands are available in Qgdbm:
- qgdbm::init/qgdbm::cleanup (Initialization/Cleanup)
- qgdbm::tsql ALTER TABLE
- qgdbm::tsql ALTER USER
- qgdbm::tsql CREATE TABLE
- qgdbm::tsql CREATE USER
- qgdbm::tsql DROP TABLE
- qgdbm::tsql DROP USER
- qgdbm::tsql DELETE
- qgdbm::tsql INSERT
- qgdbm::tsql SELECT
- qgdbm::tsql UPDATE
Some further helpful commands:
The following conventions are made:
|
<table> |
The name for a table (can contain only characters and numbers [a-zA-Z0-9_]) and is either user-unspecific (e.g. address) or in a user-scheme (e.g. ernie.address). |
|
<coln> |
Name of the n-th column in a table. Valid characters are [a-zA-Z0-9_]. The columns are case-sensitive. But I suggest using either uppercase or lowercase columnnames, because you have to write the corresponding column-variables exactly as you defined them. |
|
<dattypn> |
The (n-th) datatype of a column. real – floating-point numbers (e.g.: 3.141) |
|
<conn> |
A constraint for the n-th column. |
|
<username>, <password> |
The name of a user (valid characters are [a-zA-Z0-9_]) and password (all characters are valid). The password is stored unencrypted in the database. This is not a safety-leak, because we use “everyone sees everything”, anyhow. |
|
where <expr> |
The where-condition in Qgdbm is realized via the Tcl-expression mechanism. The columns are specified like “normal” values of Tcl-Variables. If you have the table
qgdbm::tsql select * from address \
where {[string match "Washington" $city] \
|| ("[string tolower $name]" == "clown")}
Be sure to do the quoting right, because the where-expression is first substituted (to fill in the variable-values you specified) and then send to |
|
pklist <pklist> |
In most cases you would like to access the data stored in Qgdbm with the primary key (this must always be the When you do not tell If both |
|
<Null> |
This is not the normal database NULL-value (or not value). This is simply an empty string. There is no distinction between no-value and empty-value. |
In all the examples the reference-table is the address-table.
qgdbm::init/qgdbm::cleanup (Initialization/Cleanup)
|
Syntax: |
qgdbm::init -root <rootname> ''Default system'' -rootdir <root-directory> ''Default ""'' -hd <header-key> ''Default @'' -dbext <database-file-extension> ''Default qd'' -system <with-system-or-not> ''Default 0'' -log <with-loggin-or-not> ''Default 0'' -reorganize <number-of-deleted-records> ''Default 300'' -help qgdbm::cleanup |
|
Return-Value: |
When |
|
Description: |
With The parameters have the following meaning: -hd: The key of the admin-row in each table. The default is -log: 0 (default) or 1. If Qgdbm gets initialized with
Be sure to call |
qgdbm::tsql ALTER TABLE
|
Syntax: |
qgdbm::tsql alter table <table> add \
{{<col1> <dattyp1> [<con1>]} \
{<col2> <dattyp2> [<con2>]} ...}
qgdbm::tsql alter table <table> modify \
{{<col1> <dattyp1>
[<con_1>]} {...} ...}
qgdbm::tsql alter table drop {<col1> <col2> ...}
qgdbm::tsql alter table rename {<col1> <col1_newvalue>} \
{<col2>...}...}
|
|
Return-Value: |
None |
|
Description: |
These commands are used to modify the columns of a table. With
|
|
Example: |
qgdbm::tsql alter table address add column [list {houseno char}]
alter table address drop column city
|
qgdbm::tsql ALTER USER
| Syntax: | qgdbm::tsql alter user <username> identified by <password> |
| Return-Value: | None |
| Description: | Change the password of user <username>. If the user does not exist, an error is thrown. |
| Example: | qgdbm::tsql alter user grobi identified by grobis_new_password |
qgdbm::tsql CREATE TABLE
|
Syntax: |
qgdbm::tsql create table <table> \
{{<col1> <dattyp1> [<con1>]} \
{<col2> <dattyp2> [<con2>]} ...}
|
|
Return-Value: |
None |
|
Description: |
Create a table with the given columns and their corresponding column-datatypes and constraints. Constraints are optional. But remember the first column is always the primary key and this column is never optional. When an error occurs while inserting multiple rows at one time no row will be inserted. You don’t need a |
|
Example: |
To create a column with a
qgdbm::tsql create table person \
{{persnr integer} \
{last_name char {[string length $last_name]}} \
{first_name char} \
{salary integer}}
Be sure to quote the constraint correctly, because the constraint is substituted with |
qgdbm::tsql CREATE USER
| Syntax: | qgdbm::tsql create user <username> identified by <password> |
| Return-Value: | None |
| Description: | Create the user named <username>. This command is only available when Qgdbm is initialized with the option -system 1, because the user-information is stored in the system-table system.qg in the root-directory. |
| Example: | See the Simple Example. |
qgdbm::tsql DROP TABLE
| Syntax: | qgdbm::tsql drop table <table> |
| Return-Value: | None |
| Description: | The file corresponding to table <table> will be deleted. An error will be thrown if there is no table <table>. |
| Example: | qgdbm::tsql drop table grobi.cookies |
qgdbm::tsql DROP USER
| Syntax: | qgdbm::tsql drop user <username> |
| Return-Value: | None |
| Description: | Delete the directory corresponding to this user and all her tables below. You should be really sure, if you want to do this. |
| Example: | qgdbm::tsql drop user grobi |
qgdbm::tsql DELETE
|
Syntax: |
|
|
Return-Value: |
None |
|
Description: |
Deletes the specified rows from table <table>. The where-expr and the pklist are optional. |
|
Example: |
qgdbm::tsql delete from address \
where {[string equal $street "sesame-street"}
qgdbm::tsql delete from address pklist {Ernie Clown}
|
qgdbm::tsql INSERT
|
Syntax: |
qgdbm::tsql insert into <table> {<col1> <col2> ...} \
values {{<val11> >val12> ...} \
{<val21> <val22> ...} ...}
qgdbm::tsql insert into <table> \
values {{<val11> <val12> ...} \
{<val21> <val22> ...} ...}
|
|
Return-Value: |
None |
|
Description: |
If you insert values in the Qgdbm-Database be sure to always give the primary key-column (the first column in The values are provided as a list of lists. Therefore you can use the result of a select-statement directly to |
|
Example: |
See this Simple Example.
qgdm::tsql insert into address_new {$name $street} \
values [qgdbm::tsql select {$name $street} from address]
|
qgdbm::tsql SELECT
|
Syntax: |
qgdbm::tsql select {<col1> <col2> ...} from <table> \
[where <expr>] [pklist <pklist>] \
[order_asc|order_desc <coln>]
qgdbm::tsql select * from <table> \
[where <expr>] [pklist <pklist>] \
[order_asc|order_desc <coln>]
|
|
Return-Value: |
List of selected and possibly ordered values |
|
Description: |
Select all columns of all rows which are given in The If the order-criterium is provided the values would be order by the given column ascending or descending. Only one column could be provided. |
|
Example: |
See the example-section. Want a row-counter in the selected fields? Simply put your code into the select-statement:
set i 0
qgdbm::tsql select {[uplevel #0 {incr i}] $last_name,$first_name} \
from person
But be careful not to mess with the column-variables. |
qgdbm::tsql UPDATE
|
Syntax: |
qgdbm::tsql update <table> {<col1> <col2>...} \
{<val1> <val2>...} \
[where <expr>] [pklist <pklist>]
|
|
Return-Value: |
None |
|
Description: |
Update the given columns of the affected rows with specified values. The affected rows are determined the same way as in the The given columns (again given with $) are updated with their corresponding values. |
|
Example: |
See example-section |
qgdbm::descTable
|
Syntax: |
|
|
Return-Value: |
None |
|
Description: |
This prints a description of the given table to stdout equal to a This is provided as a convenience-function, so you don’t have to mess with the table-header stored in key |
|
Example: |
% qgdbm::descTable address Current working directory: /usr/svogel/tgdbm/tests Current gdbm-handle : gdbm28 DBFile : address.qg (Version: 0.3) PK : name (char) Fields : street (char) Fields : zip_code (integer) Fields : city (char) constraints: '[string length $city]' Size : 4 Created: 02/12/00 19:09:10 Statistics: No insert: 3 No select: 9 No update: 0 No delete: 0 |
qgdbm::gdbmHandle
|
Syntax: |
|
|
Return-Value: |
Corresponding gdbm-handle to the given table |
|
Description: |
In case you want to access the table-file directly with the commands from Tgdbm, you can retrieve the gdbm-handle with this command. |
|
Example: |
To determine the number of entries: |
qgdbm::forceReorg
| Syntax: | qgdbm::forceReorg <table> |
| Return-Value: | None |
| Description: | If you want to do the reorganization of gdbm-files by hand (specifying -reorganize 0 in the call to qgdbm::init), you may force immediate reorganization by calling forceReorg. |
| Example: | qgdbm::forceReorg address |
qgdbm::headerField
|
Syntax: |
|
|
Return-Value: |
Value of field <field> in header of <table> |
|
Description: |
To access the system-information stored in each table you can read the different fields stored in key version: Versionnumber of Qgdbm (should be 0.3). no_insert: The number of inserts. |
|
Example:n |
returns: |
qgdbm::log
|
Syntax: |
|
|
Return-Value: |
None |
|
Description: |
This command only works when command: The column to hold the command-string (or any other string you provide as command). You can use this command to log your own messages. But because logging is only enabled with |
|
Example: |
|
Examples
Example #1: Create a table
Assume we want the following table-specification for address-data:
| Columnname | Datatype | |
| name | char | primary key |
| street | char | |
| zip_code | integer | |
| city | char | NOT-NULL |
Not that it is a good idea to assume that the zip_code is a number, but anyway.
The corresponding command would be:
qgdbm::tsql create table address \
{{name char} \
{street char} \
{zip_code integer} \
{city char {[string length $city]}}}
Example #2: Insert values into a table
We assume the following data to be in this table:
| name | street | zip_code | city |
| Ernie | sesame-street | 12345 | Sesame |
| Clown | Whitehouse-Av. | 9999 | Washington |
| Quichote | Windmillstreet | 455 | Don’t know where |
These could be inserted in the table address as follows:
set data {Ernie sesame-street 12345 Sesame
Clown Whitehouse-Av. 9999 Washington
Quichote Windmillstreet 455 {Don't know where}}
foreach {name street zip city} $data {
qgdbm::tsql insert into address {$name $street $zip_code $city} \
values [list [list $name $street $zip $city]]
}
or if you have your data in another datastructure:
set data {{Ernie sesame-street 12345 Sesame}
{Clown Whitehouse-Av. 9999 Washington}
{Quichote Windmillstreet 455 {Don't know where}}}
qgdbm::tsql insert into address values $data
You can also insert specific columns as in:
qgdbm::tsql insert into address {$name $city} values Bert Sesame
Would insert: {Bert {} {} Sesame}.
Whereas
qgdbm::tsql insert into address {$name $street} values \
{{Bert sesame-street}}
would throw an error:
Column 'city' with value '' doesn't fulfill constraint '[string length $city]'.
Example #3: Select values from a table
To select entries from this table you can use the following commands:
qgdbm::tsql select {$name $street} from address
would result in (no specific order):
{{Ernie sesame-street} {Quichote Windmillstreet} {Clown Whitehouse-Av.}}
Select with an order-clause:
qgdbm::tsql select * from address order_asc
result is:
{Clown Whitehouse-Av. 9999 Washington} \
{Ernie sesame-street 12345 Sesame} \
{Quichote Windmillstreet 455 {Don't know where}}
Select with an order-clause and where-condition:
qgdbm::tsql select * from address where {"$city" == "Washington"} order_desc
results in:
{Clown Whitehouse-Av. 9999 Washington}
Another select with where and order that fails:
qgdbm::tsql select from address where {$zip_code > 5000} order_asc
would throw an error, because the column in the order-clause has to be
in the selected-column list.
Here is a better way:
qgdbm::tsql select {$name $city} from address where {$zip_code > 5000} order_desc city
(the $-prefix in the order-column is optional!)
result:
{Clown Washington} {Ernie Sesame}
Select using pklist:
qgdbm::tsql select from address pklist {Ernie Clown}
returns:
sesame-street Whitehouse-Av.
Example #4: Update values from a table
To update some rows use the update-command as in these examples:
qgdbm::tsql update address {$street} Sesamestreet where {"$name" == "Ernie"}
qgdbm::tsql update address {$street} Sesamestreet pklist {Ernie Bert}
This will result in an error, because the primary key could not be updated:
qgdbm::tsql update address Hugo pklist Ernie
The following statement updates all rows in address:
qgdbm::tsql update address Berlin
Performance
You may ask it Qgdbm fast enough, because it is based on Tcl. The answer is that depends (as always). Comparing it with a “real” database is a bit unfair, but let’s compare it with pure Tgdbm. Because Qgdbm is based on Tgdbm, we can see the amount of time-overhead added to Tgdbm in benefit for an easier to use and faster to develop “queries”.
The times are taken on a Intel Pentium 133-Computer with Tcl 8.2 running under Linux.

Figure 1: Measured time for Inserts in Tgdbm and Qgdbm
For the first test 1000 rows were inserted. Each row was about 250, 500 or 1000 Bytes (which makes a total of nearly 250, 500, 1000 KB, to be exact 244, 488,
976 KB).
Inserting nearly 1 MB with Qgdbm (and 1000 qgdbm::tsql insert ...-operations) is slow, it takes 8.4 seconds. Inserting these values with one insert is much faster (4 secs). But surely Tgdbm cannot be beaten, it takes 0.7 seconds.
Qgdbm is around 10-15 times slower than Tgdbm.
In fact, inserting 1 MB in one chunk is not really an all-day situation. Qgdbm is surely not a tool for time-critical applications, but in applications with user-interaction and a small/medium amount of data it is worth the overhead.
Let’s have a look at the time for selects:

Figure 2: Measured time for Selects in Tgdbm and Qgdbm
From the database-file with 1000 rows, we select 100 rows (Again each row has 250, 500 or 1000 Bytes (in total nearly 25, 50, 100 KB are returned from the select-statement).
Again, Tgdbm is really fast (under 50 milliseconds). When running a select-statement with a where-expression in Qgdbm the time is between 1 to 1.4 seconds. This is because in case of an unspecific where-expression a full-table-scan is done.
Selecting the same values with the pklist-parameter makes Qgdbm much faster (between 90 and 160 milliseconds).
By the way: when doing a full-table-scan on a table with 500 rows (instead of 1000) the time is half as much (around 0.5 to 0.7 seconds).
Whenever possible you should use the parameter pklist in the select-statement.
Further you should limit your table-size to a maximum of 1000 rows when you want to do unspecified selects.
These measurements did not take the Array-extension from Version 0.5 in account.
Annotation
Since the beginning of Tgdbm there has been much of a development in other fields. Especially the Metakit has grown to a very nice and performant database with Tcl-Wrapper (see http://www.equi4.com/metakit.html for details) from Jean-Claude Wippler.
Metakit has interfaces for python and perl as well and is used in “Scripted Documents” resp. Starkits. So you should have a look at Metakit as well.