The package Qgdbm is wrapped around Tclgdbm 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.
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).
The system-tables (or exactly in version 0.3 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.
Let's take a look at some simple examples:
This simple script creates a tablepackage 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
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 {} {}}
.
In this example the Qgdbm-package is initialized with thepackage 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
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).<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). Because the tablename directly maps to a filename, the tablename is converted to lower characters (no conflicts between Windows or Unix filename-conventions). |
<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. One of the following possible datatypes for a column: char - strings of arbitrary-length, where arbitrary really means arbitrary. I didn't check for a maximum length. Is there a limit for Tcl? integer - not to explain real - floating-point numbers (e.g.: 3.141) date - the internal tcl-representation of date/time, that is: integer (e.g.: [clock seconds]) Datatype are case insensitive, so you can even mix upper- and lowercase in create - or alter -table
statements.These datatypes are used when a sorting-column is provided in a select-statement (the datatype is used to pass it to the tcl lsort command to provide the right
sorting-criterium). Or when inserting or updating is done. |
<conn> | A constraint for the n-th column. A valid tcl-expression which is used to check if a value inserted or updated is valid. Be careful with quoting when specifying constraints. |
<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. The username is used to create a directory directly under root-dir . To avoid conflicts in using Qgdbm under
Windows/Unix, ... the username is converted to lower characters.
So the username is case-insensitiv. |
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
address (see Simple
Example), you could search for an entry like this:qgdbm::tsql select * from address where {"$name" ==
"Ernie"} In the where-expression you can almost specify anything (even your own functions), e.g.: 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 expr .
|
pklist <pklist> | In most cases you would like to access the data
stored in Qgdbm with the primary key (this must always be the
first column you specified in the create
table -statement). To simplify the parsing of tsql-commands
you have to give these primary keys explizitly to
qgdbm::tsql . This is done with the parameter
pklist .When you do not tell qgdbm:tsql the keys you want to
search for with the parameter pklist a
full-table-scan is done. pklist is a
"normal" tcl-list, e.g.qgdbm::tsql select * from address pklist {Ernie
Clown} If both pklist and where-expr are given
to qgdb::tsql [select|delete|update] the given list
of primary keys are searched for a matching
where-expr . That means pklist and
where-expr are combined via AND . |
<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. |
Syntax: | qgdbm::init qgdbm::cleanup |
Return-Value: | When qgdbm::init is called with -help the values of
all parameters are returned. |
Description: | With qgdbm::init the Qgdbm-Database
is initialized. If you forget the call to qgdbm::init
strange errors might happen.The parameters have the following meaning: -dbext: The Qgdbm-database-file-extension. The default is ".qg". This should be changed only for a very good reason. -root: The name of the root-table (if specified with -system 1 ; useless otherwise) without extension
(-dbext). Not to be changed, too.-rootdir: The place of the database. Under this directory the system-table-file and the user-directories will be created. With this option you can have different databases laying around. With a simple call to qgdbm::init you switch to
another database.-hd: The key of the admin-row in each table. The default is @ . In case you need a key with this symbol you may
set the header-key to something different. But remember you have
to do this for the whole database and each time you initialize it
or else nothing will work. It is a good idea to leave this value
untouched.-system: 0 or 1. With -system 1 a system-table
is created (if it doesn't exists). If you want to create different
users you have to initialize with -system 1 . Else
(the default -system 0 ) no users could be created and
all tables are created directly under rootdir .-log: 0 (default) or 1. If Qgdbm gets initialized with -log 1 a log-table named "log.qg" is
created. Nearly all commands select, delete, ... will
leave an entry in this log-table. This option is provided for
debugging-purposes and should not be used unless you really want
to slow down the database-operations.-reorganize 300 (default). When data is deleted from gdbm the space is not freed until a call to reorganize is
made. With this option you can configure when to reoganize the
table-files. It defaults to 300 (that is after 300 deletes a call
to reorganize is automatically made, thereby shrinking the
file-size to the minimum needed). Depending on the size of
data-rows and the file-size you are willing to accept this can be
set to a higher or lower value. With -reorganize 0 or
-reorganize "" no automatically
reorganization is done.Be sure to call qgdbm::cleanup when you are finished
with your database-operations, because Qgdbm keeps the table-files
opened unless told to close them (with cleanup ). All
the opened gdbm-handles are closed and the lock on the
tables are removed. |
Syntax: | qgdbm::tsql alter table <table>
add \ |
Return-Value: | None |
Description: | These commands are used to modify the columns of
a table. With drop all the named columns are removed
(including all data for these columns!). You cannot remove the
primary key-column!modify ,
changes the datatypes and constraints of the given columns. A check is made if all values
are valid in regard for the new datatype and constraint. If not an error is thrown.With add columns can be added to the table.
<Null>-Values are inserted for these columns.rename renames the columnnames of the listed columns
to the given new names. |
Example: | qgdbm::tsql alter table address add column
[list {houseno char}] |
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 |
Syntax: | qgdbm::tsql create table <table> \ |
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. A constraint is defined like the where-expr in a
select -statement. The corresponding column is
provided in a variable with the name of the column.When an error occurs while inserting multiple rows at one time no row will be inserted. When you specify a row with a primary key that's already in the database an error is thrown and no value is inserted. When a table is created a file is generated in the root-directory or in the specified user-directoy named "<table>.qg". (See also description of <table>) You are searching for an aequivalent to the standard SQL: create table xyz as select * from abc (that is create
a new table xyz which has exactly the same
structure and the same data as abc )?You don't need a tsql -statement for that. Simply copy
the file abc.qg to xyz.qg . That's one of
the benefits of using one file for each table.Rename a table? Simply rename the corresponding file. But be sure to have closen your Tcl-application that uses this table! |
Example: | To create a column with a NOT
NULL -Constraint is simple:qgdbm::tsql create table person \ Be sure to quote the constraint correctly, because the constraint is substituted with subst before it is evaluated with
expr .For further examples see Examples. |
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. |
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 |
Syntax: | qgdbm::tsql drop user <username> |
Return-Value: | |
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 |
Syntax: | qgdbm::tsql delete from <table>
[where <expr>] [pklist <pklist>] |
Return-Value: | None |
Description: | Deletes the specified rows from table
<table>. The where-expr and the pklist are optional. If no where-condition or pklist is specified all rows will be deleted. If both are given, the given list of primary keys ( pklist ) will be searched for a matching where-condition |
Example: | qgdbm::tsql delete from
grobi.cookies deletes everythingqgdbm::tsql delete from address where {"$street" ==
"sesame-street"} or better because the quoting is less confusing: qgdbm::tsql delete from address \ |
Syntax: | qgdbm::tsql insert into <table>
{<col1> <col2> ...} \ |
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 table-definition). The columns are specified as usual via the
"value"-notation with a $-prefix. (You could alternatively give the columns without $; The $-prefix is only for consistency) If you do not call insert with the column-parameter,
you have to give values for all columns of the table or else an
error is thrown.The values are provided as a list of lists. Therefore you can use the result of a select-statement directly to insert .
This implies that you have to make an additional list, if you want
to insert only one row. |
Example: | See this Simple Example.qgdm::tsql insert into address_new {$name $street} \ |
Syntax: | qgdbm::tsql select {<col1>
<col2> ...} from <table> \ |
Return-Value: | List of selected and possibly ordered values |
Description: | Select all columns of all rows which are given in
col1 col2... (or all if * ) and which
match the where-expr and/or pklist .The where and pklist are optional.Columns have to be provided in $-notation. You are not limited to specify column-names in the select-statement as in "real" SQL you could also provide constants (see Examples). The result is a list of lists (that is a list of rows with a list of column-values). Be sure to use the parameter pklist as often as
possible, because without this parameter always a full-table-scan
is done.If the order-criterium is provided the values would be order by the given column ascending or descending. Only one column could be provided. The $-prefix of the order-column is optional. In fact the $ is removed from the order-column, but the syntax is provided for consistency with the other columns. |
Example: | See the example-section. Select the system-date and the names of table address
with lowercase-characters:qgdbm::tsql select {[string toupper $name] [clock seconds]}
from address Want a row-counter in the selected fields? Simply put your code into the select-statement: set i 0 But be careful not to mess with the column-variables. |
Syntax: | qgdbm::tsql update <table>
{<col1> <col2>...} {<val1> <val2>...} \ |
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 select or
delete -statement.Keys given in pklist , which are not found in the
table are silently ignored.The given columns (again given with $) are updated with their corresponding values. For the update -statement you need not specify the
columns prefixed with $. The $-notation is only
provided for conistency. For convenience you could give the
column-names only (this avoids a little bit of quoting, too). |
Example: | See example-section. |
Syntax: | qgdbm::descTable <table>
|
Return-Value: | None |
Description: | This prints a description of the given table to
stdout equal to a describe <tablename> in
Oracle-SQL.This is provided as a convenience-function, so you don't have to mess with the table-header stored in key @ .The output (see example) gives the current working directory (this is needed for the determination of the table-file), the associated gdbm-handle, the corresponding database file (joined with the rootdir specified in the initialization-call qgdbm::init ), the primary key (PK) and the other
fields with its names (the datatype) and possible constraints.Furthermore the number of entries is given (Size), remember that the header element is always counted, so the actual number of entries is (size - 1). The date of creation of this table is printed with some primitive statistics of the table: number of insert, selects, updates and deletes. |
Example: |
% qgdbm::descTable address Current working directory: /usr/svogel/tclgdbm/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 |
Syntax: | qgdbm::gdbmHandle <table> |
Return-Value: | Corresponding gdbm-handle to the given table |
Description: | In case you want to access the table-file
directly with the commands from Tclgdbm, you can retrieve the gdbm-handle
with this command. |
Example: | To determine the number of entries:set size [[qgdbm::qgdbmHandle address] count] Remember that this counts the header-key @ . So the
real size of data entries is [expr $size -1]
|
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 |
Syntax: | qgdbm::headerField <table> <field> |
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
@ with this command.The currently defined fields are: version: Versionnumber of Qgdbm (should be 0.3). createdate: The date of creation of the file as the internal tcl-clock-value. fields: A list of field/column-names where the first listelement is the primary key. types: A list of datatypes of the columns in the same order as in fields .constrs: A list of constraints for the columns. no_insert: The number of inserts. no_update: The number of updates. no_delete: The number of deletes. no_update: The number of updates. |
Example: | qgdbm::headerField address fields returns: name street zip_code city |
Syntax: | qgdbm::log command table time
number_of_rows |
Return-Value: | None |
Description: | This command only works when qgdbm::init
-log 1 is called for initialization.log is used internally for time-measurement but you
can use it for logging-purposes, too. The format of the log-table
is:id: primary key, a simple id (which is calculated in log .command: The column to hold the command-string (or any other string you provide as command). table: The table name for which the command is executed. time: The time in microseconds (as returned by time ) needed for command.number_of_rows: The number of rows affected by this command. You can use this command to log your own messages. But because logging is only enabled with -log 1 there is a lot of
overhead done for logging, which slows down Qgdbm. So defining
your own log-table and your own log-command would be much better.
|
Example: | qgdbm::log select address 0 1 |
address
-data:
Columnname | Datatype | |
---|---|---|
name | char | primary key |
street | char | |
zip_code | integer | |
city | char | NOT-NULL |
qgdbm::tsql create table address \ {{name char} \ {street char} \ {zip_code integer} \ {city char {[string length $city]}}}
name | street | zip_code | city |
---|---|---|---|
Ernie | sesame-street | 12345 | Sesame |
Clown | Whitehouse-Av. | 9999 | Washington |
Quichote | Windmillstreet | 455 | Don't know where |
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}} foreach {name street zip city} $data { qgdbm::tsql insert into address {$name $street $zip_code $city} \ values [list [list $name $street $zip $city]] }
You can also insert specific columns as in: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
Would insert:qgdbm::tsql insert into address {$name $city} values {{Bert Sesame}}
{Bert {} {} Sesame}
. Whereas
would throw an error:qgdbm::tsql insert into address {$name $street} values \ {{Bert sesame-street}}
Column 'city' with value '' doesn't fulfill constraint '[string length $city]'.
qgdbm::tsql select {$name $street} from addresswould result in (no specific order):
{{Ernie sesame-street} {Quichote Windmillstreet} {Clown Whitehouse-Av.}}
qgdbm::tsql select * from address order_asc {$name}result is:
{Clown Whitehouse-Av. 9999 Washington} {Ernie sesame-street 12345 Sesame} {Quichote Windmillstreet 455 {Don't know where}}
qgdbm::tsql select * from address where {"$city" == "Washington"} \ order_desc {$city}results in:
{Clown Whitehouse-Av. 9999 Washington}
qgdbm::tsql select {$name} from address where {$zip_code > 5000} \
order_asc {$city}
would throw an error, because the column in the order-clause has to be in the selected-column list.
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}
qgdbm::tsql select {$street} from address pklist {Ernie Clown}returns:
sesame-street Whitehouse-Av.
qgdbm::tsql update address {$street} Sesamestreet \ where {"$name" == "Ernie"}
qgdbm::tsql update address {$street} Sesamestreet pklist {Ernie Bert}
qgdbm::tsql update address {$name} Hugo pklist Ernie
qgdbm::tsql update address {$city} Berlin
qgdbm::tsql insert
...
-operations) is slow, it takes 8.4 seconds. Inserting these
values with one insert is much faster (4 secs). But surely Tclgdbm
cannot be beaten, it takes 0.7 seconds.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.pklist
-parameter makes
Qgdbm much faster (between 90 and 160 milliseconds).pklist
in
the select
-statement.For Suggestions, improvements, bugs or bug-fixes feel free to contact:
Stefan Vogel (stefan.vogel@avinci.de)