[ Team LiB ] Previous Section Next Section

Metakit

This section provides a short overview of the Metakit database that is used by Starkits to store their data. You do not need to program Metakit directly to use Starkits because of the transparent VFS interface. However, Metakit is an easy-to-use database that provides more power than storing data in flat files, but not as much power (or overhead) as a full SQL database engine. Metakit has a simple, flexible programming API and an efficient implementation. By storing your application data in a Metakit table, you can have persistent data that lives with your application. You can store the data in a file separate from your application, or right inside the application Starkit itself.

This Chapter gives a few introductory examples and explains some of the other features that are available. This Chapter does not provide a complete reference. The following URLs are excellent guides to the Tcl interface for Metakit. The first URL is also on the CD as sdarchive/doc/mk4dok.kit.

http://www.equi4.com/metakit/tcl.html

http://www.equi4.com/metakit/wiki.cgi/mk4tcl

http://www.markroseman.com/tcl/mktcl.html

Metakit Data Model

The Metakit data model is table-oriented. A view is like a table with rows of values. Each row in a view has an index, which is an integer that counts from 0. The elements (i.e., columns or fields) of a row are called properties. A property might itself be a view, which leads to nested views (i.e., nested tables). All the rows in a view have the same properties, and the properties of a view can be changed dynamically. You can directly relate (view, row, property) to (table, row, field) when thinking about Metakit views.

A Metakit data file has one or more views within it. When you open a Metakit file, you specify a tag. Views are specified as tag.view. Row N of a view is specified as tag.view!N. Such a position within a view is called a cursor, and there are operations to create cursor variables and move them through a view. If a property is a nested view, then you can specify a row in the nested view with tag.view!N.subview!M.

Examining a Metakit Database

Our first exercise is to open up a Starkit and look at the Metakit database views inside. The mk::file command implements several operations. The open operation opens a database and associates it with a tag. The views operation lists the views in the database identified by the tag. The close operation commits any outstanding modifications to the database. The other mk::file operations are used to control the commit behavior and to save or restore the database to an external file. Example 22-13 illustrates how to open a Metakit database and examine the views it contains:

Example 22-13 Examining the views in a Metakit database
package require Mk4tcl
=> 2.4.8
mk::file open tclhttpd tclhttpd.kit
=> tclhttpd
mk::file views tclhttpd
=> dirs

The mk::view command has several operations to inspect and manipulate views. The layout operation queries or sets the properties of a view. Given only a view, the layout operation returns the properties defined for the view. Each property has a type, and nested views are represented as a nested list of the property name and its list of properties. Given a set of properties, the layout operation defines new properties for a view. This may involve adding or deleting properties from any existing rows in the table. Example 22-14 shows the layout of the dirs view in a Starkit. The files property is a nested view, which provides a natural way to represent a hierarchical filesystem. The example gets the name property of tclhttpd.dirs!0.files!0, which is the first file in the first directory in the view:

Example 22-14 Examining data in a Metakit view
mk::view layout tclhttpd.dirs
=> name parent:I {files {name size:I date:I contents:B}}
mk::view size tclhttpd.dirs
=> 48
mk::get tclhttpd.dirs!0
=> name <root> parent -1
mk::get tclhttpd.dirs!1
=> name tcllib1.3 parent 0
mk::get tclhttpd.dirs!1 name
=> tcllib1.3
mk::get tclhttpd.dirs!0.files!0 name
=> main.tcl

Of course, real applications will want to query views for values that have certain properties. The mk::select command returns the row numbers for rows that match given criteria, or all the row numbers if no matching criteria are given. You can match on multiple properties, and there are flags that control how the match is done. For example, you can do numeric comparisons, regular expression or glob matches, and min/max comparisons.

Example 22-15 shows two forms of mk::select. The KitWalk procedure enumerates the files in a given directory, which is the view $tag.dirs!$dir.files. Then it queries the row indices for the $tag.dirs view whose parent property equals $dir, and calls itself recursively to process the child directories. KitWalk provides a similar function to sdx lsk:

Example 22-15 Selecting data with mk::select
proc KitWalk {tag dir {indent 0}} {
   set prefix [string repeat " " $indent]
   puts "$prefix[mk::get $tag.dirs!$dir name]/"
   incr indent 2

   # List the plain files in the directory, if any

   foreach j [mk::select $tag.dirs!$dir.files] {
      puts "$prefix  [mk::get $tag.dirs!$dir.files!$j name]"
   }

   # Recursively process directories where $dir is the parent

   foreach i [mk::select $tag.dirs parent $dir] {
      KitWalk $tag $i $indent
   }
}
proc KitInit {starkit} {
   mk::file open starkit $starkit
   if {[mk::file views starkit] != "dirs"} {
      mk::file close $starkit
      error "This database is not a starkit"
   }
   return starkit        ;# db tag
}
proc KitTest {} {
   set tag [KitInit tclhttpd.kit]
   KitWalk $tag 0
}

Creating a Metakit View

Creating a new view is simple. Example 22-16 opens a database file mydb.tkd and creates a view test with three properties: name, blob, and i. If the file does not exist, then it gets created automatically. If the test view doesn't exist, it gets created. If it already exists, it is reformatted to have the new properties. The name property has the default type, which is a null-terminated string. The blob property is a binary value (B) which can store anything, including null characters. The i property is a 32-bit integer (I). Other types include 64-bit integer (L), 32-bit floating point (F), 64-bit double-precision floating point (D), and null-terminated string (S), which is the default and needn't be specified.

Example 22-16 Creating a new view
mk::file open mydb mydb.tkd
=> mydb
mk::view layout mydb.test {name blob:B i:I}
=> mydb.test
mk::file close mydb

The mk::set command sets property values, and the mk::row command modifies rows. Example 22-17 adds a few values to the test view. Note that you can insert into rows beyond the end of the view and it is automatically extended. If you only define some properties for a row, the other properties get default values. Other mk::row operations include insert, replace, and delete.

Example 22-17 Adding data to a view
mk::set mydb.test!0 name hello
=> mydb.test!0
mk::get mydb.test!0
=> hello {} 0
mk::row append mydb.test "line two" 0x0 65
=> mydb.test!1
mk::view size mydb.test
=> 2
mk::set mydb.test!100 i 1234
=> mydb.test!100
mk::view size mydb.test
=> 101

Storing Application Data in a Starkit

Your application can create new views in a Starkit to store persistent data. Remember to wrap your application with the -writable flag. You can determine the name of the Starkit from $starkit::topdir, and then define a new view within it. Of course, remember that Starkits use dirs view to store files, but you can create any number of other views within your Starkits. This is illustrated in Example 22-18, which records each time the application was run in a simple audit view.

Example 22-18 is careful to find the existing Metakit handle that is already opened by Tclkit. The vfs::filesystem info command returns an alternating list of VFS names and their Metakit database handle. The example extracts the handle and saves it in the $db variable. This is important because opening the same Metakit file twice (for writing) can cause corruption:

Example 22-18 Storing data in a Starkit
package require starkit
starkit::startup
set db [lindex [vfs::filesystem info [$starkit::topdir]] 1]
mk::view layout $db.audit {action timestamp:I}
mk::row append $db.audit "Run as pid [pid]" [clock seconds]
puts "$argv0 has been run [mk::view size $db.audit] times"

To test this, put this example into the main.tcl of a trivial Starkit. When you create the Starkit, remember the -writable option with sdx:

mkdir bundle.vfs
cp 22_18.tcl bundle.vfs/main.tcl
sdx wrap bundle.kit -writable-

Wikit and the Tcler's Wiki

The alternative to storing data in the Starkit file is to have a separate Metakit data file. This is the approach taken by Wikit. The wikit.kit file is the Wikit application, and the wikit.tkd file is a Metakit database file that stores all the pages in the Wiki. (Creating a new Wiki is simple, just specify a different .tkd file name.) The advantage of having a separate Metakit file is that you can easily maintain your application by unwrapping and wrapping your application Starkit. Otherwise, if you put the application data directly into the Starkit you have to extract it and restore it as an additional maintenance step. In that case, you must use the mk::file save and load operations to save and restore your Metakit views to a file.

A Wiki is a web site that users can easily edit using a simplified markup syntax. Wikit is a Wiki implementation in Tcl using Metakit to store pages. It can run as a stand-alone Tk application, a GGI script, as its own little web server, or embedded into another application as a documentation bundle. There is a copy of wikit.tkd on the CD-ROM. For example, you run a stand alone copy of the Tcler's Wiki as:

tclkit wikit.kit wikit.tkd

The live Wiki is at wiki.tcl.tk[*], and you can find out more about Wikit at:

[*] http://wiki.tcl.tk is an alias for http://mini.net/tcl.

http://wiki.tcl.tk/wikit

    [ Team LiB ] Previous Section Next Section