4.18 Dictionary Functions



RMS includes functions to access information from an RMSfile's data dictionary. Associated with each RMSfile is a dictionary that describes each field and defines the primary and secondary keys. The data dictionary is accessed as if it were a separate file using routines to retrieve the field and key information. In addition, there are routines for opening and closing a data dictionary. The include file for RMS defines the structures and pointers returned by the various dictionary functions. The include file is accessed by the statement:

#include <cbase/dirio.h>

The data dictionary is stored in a separate file; the RMS dictionary file. It is named the same as the RMS data file with a .d suffix. It is never necessary to refer to this file directly. It should always be accessed using the RMS data dictionary functions.

Dtopen(C-3) opens the RMS dictionary file associated with an RMSfile. It returns a pointer to be passed to the other RMS dictionary functions. The function call is similar to the UNIX standard I/O open(2) call. The first parameter passed to dtopen is the name of the RMSfile, not the name of the RMS dictionary file. This can be either the logical RMSfile name or the pathname of the RMS data file. The second parameter should always be "r" (read-only).

Dtopen returns a NULL pointer if the RMS dictionary file cannot be accessed. If a NULL pointer is returned, the RMS error message is set describing the reason. The following example illustrates how to call dtopen:

DICT *filedict;

filedict = dtopen ("sub", "r");

if (filedict == NULL) {

puts (derrmsg());

puts ("Cannot open dictionary for sub");

exit (1);

}

The definition of DICT is in the include file <cbase/dirio.h>.

When an RMSfile is opened, the RMS dictionary file is opened first. The dictionary is left "open" until the RMSfile is closed. The overhead for this is very small. The system file descriptor for the dictionary is closed. The contents of the RMS dictionary file are kept in allocated memory.

A pointer to this "open" dictionary is returned by dtdict(C-3). All of the RMS dictionary functions work as documented using this pointer. You must NOT pass this pointer dtclose(C-3). The dictionary is "closed" when the RMSfile is closed. The net effect of dtdict is to "open" the dictionary without having to open another system file.

Dtdict is passed just one parameter: an open file pointer (from dlopen(C-3) or dopen). The following code illustrates calling dtdict:

DFILE *sub;

DICT *filedict;

sub = dlopen ("sub", "r");

if (sub != NULL)

filedict = dtopen (sub)

if (sub == NULL

|| filedict == NULL) {

puts (derrmsg());

exit (1);

}

Once a data dictionary has been opened, information about individual fields can be retrieved. Dtfind(C-3) returns information about a particular field by name by returning a pointer to a structure describing the requested field. If there is no data field with the requested name, dtfind returns a NULL pointer. The first parameter to dtfind is the pointer returned by dtopen(C-3) or dtdict. The second parameter is the field name desired. An example of dtfind is:

DICT *filedict;

struct fd *field;

filedict = dtopen ("sub", "r");

if (filedict == NULL)

exit (1);

field = dtfind (filedict, "name");

In this example, if the pointer field is not NULL, then it points to information about the name data field. The manual section dirio.h(C-5) contains a detailed description of the structure information returned.

Dtfindn(C-3) retrieves information about a field by field number. It is useful for extracting information about each field when the field names are not known. The function is called with the desired field number. The first field in the data dictionary is numbered 1. Like dtfind(C-3), dtfindn returns a pointer to a structure containing the desired field information. If the RMSfile has fewer fields than the number specified, a NULL pointer is returned. The following example illustrates dtfindn by printing all field names of an RMSfile:

DICT *filedict;

struct fd *field;

int number;

filedict = dtopen ("sub", "r");

if (filedict == NULL)

exit (1);

/* print all field names */

number = 1;

while (1) {

field = dtfindn (filedict, number++);

if (field == NULL)

break;

if (field.dt_alias)

/* skip alias field names */

continue;

puts (field.fd_name);

}

Two functions return information about the indexes associated with an RMSfile. Dtfindk(C-3) is passed a secondary index number, and returns a pointer to a list of field numbers that are the key fields for the requested index. If the RMSfile has fewer secondary indexes than the number specified, dtfindk returns a NULL pointer. The returned field numbers can be passed as arguments to dtfindn(C-3). The following example calls dtfindk and dtfindn to list the field names of all secondary keys of the subscriber file:

DICT *filedict;

struct kd *kp;

struct fd *fp;

int i, keyn, fldn;

filedict = dtopen ("sub", "r");

if (filedict == NULL)

exit (1);

puts ("Secondary keys in sub file");

keyn = 1;

while (1) {

kp = dtfindk (filedict, keyn++);

if (kp == NULL)

break;

/* print fields in key */

for (i=0; i<_NOKEYS; ++i) {

fldn = kp->kd_keyn[i];

if (fldn <= 0)

break;

fp = dtfindn (filedict, fldn);

puts (fp->fd_name);

}

}

_NOKEYS is defined in the dirio.h(C-5) include file. It defines the maximum number of fields allowed in one key. The array of field numbers is terminated by a zero field number or reaching the limit of _NOKEYS.

Dtfindi(C-3) works like dtfindk except that dtfindi returns the field names that make up the secondary index. The field names are returned into a structure that is defined in the include file <cbase/dirio.h>. If the RMSfile contains fewer secondary keys than the specified number, dtfindi returns a NULL pointer. The following example calls dtfindi to list the field names of all secondary keys of the subscriber file:

DICT *filedict;

struct nd *kp;

int i, keyn;

char *fld;

filedict = dtopen ("sub", "r");

if (filedict == NULL)

exit (1);

puts ("Secondary keys in sub file");

keyn = 1;

while (1) {

kp = dtfindi (filedict, keyn++);

if (kp == NULL)

break;

/* print fields in key */

for (i=0; i<_NOKEYS; ++i) {

fld = kp->nd_keys[i];

if (*fld == '\0')

break;

puts (fld);

}

}

Like dtfindk, the list of field names is terminated either by reaching the _NOKEYS limit or an empty string.

If the index number passed to dtfindk is zero, dtfindk returns the field numbers that make up the primary key to determine the primary key fields. If the index number passed to dtfindi is zero, dtfindi returns the field names that make up the primary key. If there are no primary key fields, both functions return a NULL pointer.

Any field can have an alias name defined using the alias data type. When dtfind(C-3) or dtfindn(C-3) is called, the aliasing feature is all but invisible. The alias field is almost exactly like the original. Dtefld(C-3) returns an effective field number of a field name. The field number is the number of the real field (not an alias) and can be passed to dtfindn.

Dtefldn(C-3) works like dtefld(C-3) except that it is passed a field number. It also returns the field number of the real field. The dtefldn and dtefld functions are needed when trying to search the key definitions for a particular combination of fields.

Both dtfindi(C-3) and dtfindk(C-3), return the values as declared in the RMS dictionary file. In particular, it is possible to assign an alias name for any field. This same alias name can be used as the key field name. In this case, either field can be used as the key field, but the dictionary routines only return the defined fields as key fields.

The following example may help clarify this concept. You need to find an index on the subscription file for the fields magazine and subscriber as one key. Someone has added an alias of key to the subscriber field and made an index of magazine and key. This index would have the same effect as the one you are searching for, but dtfindi(C-3) will return the names magazine and key, not magazine and subscriber. The following code shows a foolproof way to find this index regardless of the aliases defined:

DFILE *fp;

DICT *dp;

struct nd *kp;

int magn, subn;

int n, i, fldn;

fp = dlopen ("sub", "r");

dp = dtdict (fp);

if (fp == NULL || dp == NULL)

exit (1);

/* get effective numbers */

magn = dtefld (dp, "magazine");

subn = dtefld (dp, "subscriber");

if (magn < 0 || subn < 0)

/* fields not in dictionary */

exit (1);

/* search indexes for match */

for (n=0; ; ++n) {

kp = dtfindk (dp, n);

if (kp == NULL)

break;

/* make sure key is indexed */

if (dseti (n, sub) < 0)

continue;

/* check for matching magazine */

fldn = kp->kd_keyn[0];

if (magn != dtefldn (dp, fldn))

continue;

/* check for matching subscriber */

fldn = kp->kd_keyn[1];

if (subn != dtefldn (dp, fldn))

continue;

/* If dfindm calls will be used,

* kp->kd_keyn[2] should be zero.

* If dfindi calls will be used,

* the remaining fields don't

* matter... */

break;

}

if (kp)

printf ("index number is %d\n", n);

This code goes to great lengths to handle aliases. It would be easier to call dtfindi to get field names and insist that the user define the keys the way your program needs them. However, doing so would make your program very sensitive to file changes. Also, this code is only needed when your program is looking for a particular index for doing searching or sorting.

In very rare circumstances, a program must know the file organization method of an RMSfile. For these occasions, dtacces(C-3) can be called to determine file organization of an RMSfile. A sample call would be:

DICT *filedict;

filedict = dtopen ("sub", "r");

file_org = dtacces (filedict);

if (file_org == HD_HASH)

/* file is hashed file */

else if (file_org == HD_SEQ)

/* file is sequential */

else if (file_org == HD_IDX)

/* file is indexed sequential */

The symbols HD_HASH, HD_SEQ and HD_IDX, are defined in the include file <cbase/dirio.h>. If you must call this function, we strongly recommended that you also use these symbols to test the value returned.

After all processing of the RMS data dictionary has been completed, dtclose(C-3) is called to close the dictionary. Dtclose is called with the dictionary pointer returned by the dtopen(C-3) call. An example of dtclose is:

DICT *filedict;

filedict = dtopen ("sub", "r");

if (filedict == NULL)

exit (1);

/* process dictionary information */

dtclose (filedict);

WARNING: if you close a dictionary pointer returned by dtdict(C-3), you can expect weird behavior at best! Do NOT close pointers returned by dtdict.

References

1) Kernighan and D. M. Ritchie, The C Programming Language, Prentice-Hall, 1978.

2) Knuth, Sorting and Searching, Chapter 6.2.4, The Art of Computer Programming, Vol. 3, Addison-Wesley Publ. Co., Reading, Mass., 1973.