From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

NAME

Db::GTM - Perl extension to interface with GT.M global data

SYNOPSIS

use Db::GTM;
my($db) = new GTMDB("SPZ"); # Create connection to node ["SPZ"]
# Basic database node storage/retrieval
$db->set(1000); # sets node ["SPZ"] = 1000
$db->set("foo",50); # sets node ["SPZ","foo"] = 50
$db->set("foo",6,1012); # sets node ["SPZ","foo",6] = 1012
$db->set("foo",9,$$); # sets node ["SPZ","foo",9] = PID
$db->set("foo",6,"bar",5); # sets node ["SPZ","foo",6,"bar"] = 5
$db->get("foo",6); # should be 1012
# Iterating through the database
$db->next("foo",6); # should be scalar 9
$db->prev("foo",9); # should be scalar 6
$db->prev("foo",6); # should be undefined
$db->children("foo"); # should be list: (6,9)
$db->query("foo",6); # should be list: ("foo",6,"bar");
# Copying nodes
$db->merge("foo",6,undef,"foo",12);
# copies ["SPZ","foo",6] and subscripts into ["SPZ","foo",12]
$db->clobber("foo",6,undef,"foo",12); # copies [foo,6] over to [foo,12]
# makes ["SPZ","foo",12] a clone of ["SPZ","foo",6]
# Deleting nodes
$db->kill("foo",6); # Kills node ["SPZ","foo",6]
# and ["SPZ","foo",6,"bar"]
$db->kv("foo",12); # Kills node ["SPZ","foo",12] only
$db->ks("foo",12); # Kills node ["SPZ","foo",12,"bar"]
# and any other subscripts...

ABSTRACT

This module attempts to allow access to the GT.M globals database from
PERL. GT.M is a fast and flexible hierarchical database system.
This module documents Db::GTM module version $VERSION

DESCRIPTION

This module provides access to a GT.M database by presenting it as a
PERL module. Since GT.M is a hierarchical database (also known as
an Object-Oriented database), each node of the database is treated
as a list.
Instead of having a simple key like 'JoeSmith' to reference your data,
you can have a list; like ('Joe Smith','Address','Street Number').
Each connection to the database ties it to one primary node (A single
GT.M global) and will invisibly attempt to limit subsequent
stores/retrieves to this main node. You probably won't notice this
limitation if you use the module normally. This allows you to treat a
single GT.M datastore as if it were multiple databases.

SETTING UP A CONNECTION

Create a connection to GT.M with the new() function. Pass new() the
name of the main node ("Global") you wish to link to. You may link
to a subscript if you wish.
Example:
my $dblink1 = new GTMDB("Clients");
my $dblink2 = new GTMDB("Clients","auto dealerships");
In the first instance, all subsequent database access will be
invisibly restricted to the GTM database node "Clients". In the
second, it will be further restricted to the sub-node:
"auto dealerships".
The following two statements would return identical results:
$addr = $dblink1->get("auto dealerships","ACME AUTO","Address");
$addr = $dblink2->get("ACME AUTO","Address");
WARNING: There are special restrictions for the first element of the
main node. It MUST start with an upper or lowercase letter, the
rest of the element MUST contain only alphanumeric characters, and
the full name of the 1st element SHOULD be 1-8 characters long.
On failure, new() will return 'undef' and print an error to STDERR

STORING AND RETRIEVING DATA

Once a connection is made, you can store data with the set() function
and retrieve it with get().
$status = set(@nodename,$value); # Returns nonzero on failure
$value = get(@nodename);
Example:
$dblink2->set("ACME AUTO","Address","123 Any Street");
my $addr = $dblink2->get("ACME AUTO","Address");
To find out what sibling nodes are adjacent to a given one:
$scalar = next(@node); # next node element at same depth
$scalar = prev(@node); # previous node element at same depth
To get children (sub-nodes) of the current node:
$scalar = first(@node); # first child node
$scalar = last(@node); # last child node
(@list) = children(@node); # all node elements below given one
(@nodename) = query(@node); # next node element at any depth
You do not have to specify valid data nodes for these operations. You
can ask for the node that would have been next/prior to a nonexistant
node.
Example:
$dblink2->set("ACME AUTO",103309);
$dblink2->set("ACME AUTO","Address","123 Any Street");
$dblink2->set("SMITH AUTO",103306);
$dblink2->set("SMITH AUTO","Address","456 Other Street");
$nxt = $dblink2->next("ACME AUTO"); # Should be "SMITH AUTO"
$nxt = $dblink2->next("BUBBA GUMP AUTO CO."); # Should be "SMITH AUTO"
$prv = $dblink2->prev("SMITH AUTO"); # Should be "ACME AUTO"
@dlrs = $dblink2->children(); # Should be ("ACME AUTO","SMITH AUTO")
@data = $dblink2->children("ACME AUTO"); # Should be ("Address")
@nxt = $dblink2->query("ACME AUTO"); # Should be ("ACME AUTO","Address")
COLLATING ORDER NOTE:
Data nodes are not stored randomly, as in a hash. They are stored in
the MUMPS standard collating order. All numeric nodes come first, sorted
from lowest to highest. Then all string nodes are sorted in ASCII order.
If the following set operations were performed:
$dblink2->set(5.5, "foo");
$dblink2->set(-1000, "bar");
$dblink2->set(12.362,"baz");
$dblink2->set(1, "boo");
$dblink2->set(3, "Foo");
$dblink2->set("x", "Bar");
$dblink2->set("1A", "Baz");
$dblink2->set("SPZ", "Boo");
$dblink2->children() would return the list:
(-1000,1,3,5.5,12.362,"1A","SPZ","x")

CLONING AND MERGING NODES

These two functions do bulk data transfers:
$status = merge(@srcnode,undef,@dstnode); # Returns nonzero on failure
$status = clobber(@srcnode,undef,@dstnode); # Returns nonzero on failure
The first function merges the contents of the source node into the
destination node (including all sub-nodes). Sub-nodes in the destination
will be overwritten if they conflict with sub-nodes in the source. Nodes
that are unique to the destination will be left alone.
In clobber(), nodes that are unique to the destination will be destroyed.
Since both node names are lists, put an 'undef' between them to separate
them.
Example:
$dblink1->set("vendors","ACME Sprocket Co.","Address","999 Main Street");
$dblink1->merge("auto dealerships",undef,"vendors");
$dblink1->get("vendors","ACME AUTO"); # Should be 103309
If you wish to merge/clobber from one database link to another, use the
functions
$status = &GTM::merge($src,$dst); # Returns nonzero on failure
$status = &GTM::clobber($src,$dst); # Returns nonzero on failure
$src or $dst can be either a database object ($dblink1) or a MUMPS-Global
name. The node() function returns the MUMPS-Global name of a child
node of a database link.
Example:
# Copies everything in dblink1 into dblink2
&GTM::merge($dblink1,$dblink2);
# Copies everything in dblink1's sub-node "vendors" into dblink2
&GTM::merge($dblink1->node("vendors"),$dblink2);

DELETING NODES

$status = kill(@node); # Destroys node and all sub-nodes
$status = kv(@node); # Destroys specified node only
$status = ks(@node); # Destroys sub-nodes only
All return nonzero on failure.
Example:
$dblink2->set("BUBBA GUMP AUTO CO.",103009);
$dblink2->set("BUBBA GUMP AUTO CO.","Address","789 Someother Ave.");
$dblink2->set("CRAZY EDDIE USED CARS",103009);
$dblink2->set("CRAZY EDDIE USED CARS","Address","999 Emporium Ave.");
$dblink2->set("PAY-AND-SPRAY",103009);
$dblink2->set("PAY-AND-SPRAY","Address","400 Sunset Bvd");
$dblink2->kill("BUBBA GUMP AUTO CO."); # destroys "Address" sub-node too
$dblink2->ks("CRAZY EDDIE USED CARS"); # destroys ONLY the 'Address' record
$dblink2->kv("PAY-AND-SPRAY"); # leaves 'Address' record intact
$dblink2->kill(); # destroys the "auto dealerships" node and all subnodes

TRANSACTIONS

GT.M and Db::GTM support the concept of "transactions". All database
changes made during a transaction are linked, meaning that either they
are all processed successfully or none of them are. If there is a
fatal error or system crash in the middle of the transaction set, none
of the set/kill operations will take effect.
$status = $dblink->txnstart(); # Begin a transaction
$status = $dblink->txnabort(); # Abort a transaction, make no changes
$status = $dblink->txncommit(); # Save all set/kills made during txn
Example:
$status = $dblink->txnstart();
$dblink->set($acctno,"CHECKING","BALANCE",($oldChkBal - $transferAmt));
$dblink->set($acctno,"SAVINGS","BALANCE", ($oldSavBal + $transferAmt));
$status = $dblink->txncommit();
From the time you initiate txnstart(), all sets/kills are queued until
you do either txncommit() or txnabort().
Note that if you have multiple GTMDB objects and only do a txnstart()
with one of them, then the others will behave normally (their writes
will take effect immediately).
Also note that until a txncommit() is performed, everyone who views
the data that has been set since a txnstart() will see the OLD data,
not the stuff that is in the process of being written.

LOCKING

$status = $dblink->lock(@name,$seconds); # Lock a database node
$status = $dblink->unlock(@name); # Unlock something locked prior
seconds: the last parameter to lock() is the number of seconds to
wait to get a lock before giving up. This is important as GT.M
reserves the use of signals for itself and using SIGALRM may
cause problems.
Specifying a seconds count of 0 will make the locking attempt
fail immediately if another lock exists.
Specifying a seconds count of -1 will make the locking attempt
wait forever for a conflicting lock to be released. This can
lead to deadlock, so use with caution.
If you specify a global name, you MUST specify a seconds count.
Bad things will happen to you if you don't.
In order to work gracefully with other processes that are attempting
to update data in the GTM datastore, you can request locks on database
nodes. Locks are advisory (meaning that it's possible to write to a
"locked" node if you don't bother to ask for your lock first). Locks
are automatically released when your process exits.
Lock on a higher-level resources conflict with lower-level ones.
Examples:
(Process 1) $db = new GTMDB("TOPNODE");
$db->lock("MYNODE","A",0); # Lock (TOPNODE.MYNODE.A)
(Process 2) $db = new GTMDB("TOPNODE");
$db->lock("MYNODE",0); # Lock (TOPNODE.MYNODE)
# Fails because process 1 has a conflicting lock
Note that you can always get locks to resources that you have previously
locked, or lock a lower level resource.

FUNCTION LIST

Conventions:
When a function takes '@name' as a parameter, or returns it as output,
@name is a list that makes up the name of a database node. Any node
can store data as well as have child nodes. See the examples above
for explicit usage. If unspecified, functions that operate on @name
will operate on the main node linked to during new()
When a function returns '$status', anything nonzero indicates failure
$db_obj = new GTMDB(@name); # Returns a link to a database node
$db_obj = &GTMDB::new(@name); # Same
@name = $db_obj->getprefix(); # Returns the name of the main node
$scalar = $db_obj->get(@name); # Returns data stored at a node
$scalar = $db_obj->exists(@name); # True if node has data or sub-nodes
$scalar = $db_obj->haschildren(@name); # True if node has sub-nodes
$db_obj = $db_obj->sub(@name); # Returns a link to a sub-node
$status = $db_obj->set(@name,$val); # Sets data at node to $value
$scalar = $db_obj->next(@name); # Returns next node at same level
$scalar = $db_obj->prev(@name); # Returns previous node at same level
$scalar = $db_obj->first(@name); # Returns first child node
$scalar = $db_obj->last(@name); # Returns last child node
@name = $db_obj->query(@name); # Returns next data node at any depth
@list = $db_obj->children(@name); # Returns all immediate child nodes
$status = $db_obj->kill(@name); # Destroys node and all subnodes
$status = $db_obj->ks(@name); # Destroys all sub-nodes only
$status = $db_obj->kv(@name); # Destroys current node only
$status = $dblink->txnstart(); # Begin a transaction
$status = $dblink->txnabort(); # Abort a transaction, make no changes
$status = $dblink->txncommit(); # Save all set/kills made during txn
$status = $dblink->lock(@name,$seconds); # Lock a database node
$status = $dblink->unlock(@name); # Unlock locked database node
$status = $db_obj->merge(@srcname [ ,undef,@dstname ]);
# Copies nodes in @srcname into @dstname, overwriting collisions
# if unspecified, @dstname is assumed to be the main node
$status = $db_obj->clobber(@srcname [ ,undef,@dstname ]);
# Makes @dstname an exact clone of @srcname
$db_obj->clobber() and $db_obj->merge() can take another $db_obj as a source.
$gvn = $db_obj->list2gvn(@name);
# Returns the MUMPS-Global name that is the combination of the main
# node name + the specified one.
@name = $db_obj->gvn2list($GVN); # Converts a MUMPS-Global name to list
@name = &GTM::gvn2list($GVN); # Converts a MUMPS-Global name to list
$gvn = &GTM::list2gvn(@name); # Converts a list into a MUMPS Global
# The functions &GTM::merge / &GTM::clobber can be used to copy the
# contents of one DB object into another. They take either database
# objects or GVNs (such as those returned by list2gvn()) as arguments
$status = &GTM::merge ([ $gvn OR $src_db_obj ],[ $GVN OR $dst_db_obj ]);
$status = &GTM::clobber([ $gvn OR $src_db_obj ],[ $GVN OR $dst_db_obj ]);

USING GTM AS A TIED HASH OR SCALAR

Although PERL's native support of hierarchical databases is somewhat
limited, you can use this module to tie a node of a GTM database to
a hash or scalar value.
# Ties the PERL hash variable %spzhash to the GTM node ["SPZ","foo"]
my(%spzhash); tie %spzhash, 'GTMDB', "SPZ", "foo";
# Ties the PERL scalar variable $spzsclar to the node ["SPZ","foo","bar"]
my($spzsclr); tie $spzsclr, 'GTMDB', "SPZ", "foo", "bar";
my($spzlink) = new GTMDB("SPZ","foo");
# Sets ["SPZ","foo","bar"] to 6
$spzhash{"bar"} = 6; # Equivalent to $spzlink->set("bar",6);
$spzsclr = 6; # Also equivalent...
exists $spzhash{"bar"}; # True if "bar" has data or child nodes
keys %spzhash; # A list of all child-nodes of ["SPZ","foo"]
%spzhash = (); # Equivalent to $spzlink->kill();
As a special note, the 'keys' and 'each' keywords will always return
the keys presorted in the mumps collating order. If you want to use
'tie' to store a simple flat-file hash this works fine.
Multidimensional support, or accessing child nodes is trickier:
$spzhash{"bar","baz"} = 6; # Equivalent to $spzlink->set("bar","baz",6);
keys $spzhash{"bar"}; # Syntax error
@node = ("bar","baz"); # Danger Will Robinson...
$spzhash{@node} = 6; # Does $spzlink->set(2,6);
If you wanted to get child nodes of ["SPZ","foo","bar"] this way, do:
my %spzbarhash; tie %spzbarhash, 'GTMDB', "SPZ", "foo", "bar";
keys $spzbarhash; untie %spzbarhash;
To convert a list to a scalar for purposes of setting a hash variable:
$spzhash{join($;,@node)}; # $; is a PERL special variable

EXPORT

None by default.

KNOWN BUGS AND WARNINGS

The FIRST name in a node should always start with a letter, can contain
only letters and numbers, and should ideally be 1-8 characters long. Names
longer than 8 characters are unsupported by the MUMPS ANSI standard, but
should still work in GTM.
Node elements cannot be empty. ["SPZ","","bar"] is an invalid node name.
Using double-quotes in a nodename probably won't work right now. This
should be fixed eventually. ["SPZ","\"","bar"] won't work.
Using the NULL (0x00) or FS (0x1c) character in a nodename will probably
cause problems or fail.
GTM v4.4-003 adjusts the terminal settings, and traps SIGINT (generated
by CTRL+C) during initialization. Since these would be unexpected, this
package compensates by saving & restoring the signal handler, and
restoring the terminal settings when it exits. If the program is killed
by SIGKILL or SIGSTOP, it will be unable to do this. You can restore the
terminal settings by using the UNIX command '$ stty sane'

SEE ALSO

GT.M Documentation:
MUMPS documentation:
ANSI language standard: 'ISO/IEC 11756:1999'
Book: 'M[UMPS] by Example', by Ed de Moel
Sanchez tech bulletin TB5-027 ("GT.M Callins on UNIX: Invoking M from C")

FILES AND ENVIRONMENT VARIABLES

/usr/local/gtm/xc/calltab.ci
This is the default location for the file that tells GT.M how to
handle call-ins. If it is located somewhere else, it is currently
necessary to specify it's location with the env. variable 'GTMCI'
extapi.m
This is the MUMPS side of the interface between PERL and GT.M. This
collection of M routines should be stored somewhere GT.M can find it
when the program runs.

Environment Variables

GTMCI = location of the 'calltab.ci' file
gtmgbldir = location of the globals file
gtmroutines = folder locations of the routines and routine object files
gtm_dist = folder containing the GT.M distribution and executable

AUTHOR

Steven Zeck, <spzeck@ucdavis.edu>

COPYRIGHT AND LICENSE

Copyright 2005 by Steven Zeck

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.