The NDBM Database

Before you design your application program around a NDBM database, you need to answer the following questions:

  • Will readers and writer(s) need access at the same time?

  • How many writers will there be at one time?

Having multiple readers is not a problem when there is no write activity occurring. However, the NDBM routines do not provide any protection against readers and writers in conflict. For example, one process might delete a key that conflicts with another process that is visiting all the keys in the database. Additionally, these routines do not permit multiple writers to the database at one time. Despite these limitations, the NDBM routines still find many uses in standalone and single-user solutions.

Error Handling

With the exception of dbm_close(3), all NDBM functions return an indication of success or failure. Some functions return zero for success. A negative value is returned for failure. Other cases are unique. These will be detailed as you review them in the upcoming sections.

You can test for errors using the call to dbm_error(3). This function returns a non-zero value when an error has occurred. However, this function continues to return an error indication until dbm_clearerr(3) is called. A function synopsis is provided as follows:

#include <ndbm.h>

int dbm_error(DBM *db);

int dbm_clearerr(DBM *db);

The NDBM routines will influence the errno value, but there are no standardized errors documented for them. For portability reasons, you should rely on only the dbm_error(3) and dbm_clearerr(3) routines and avoid interpreting errno.

Note

Most UNIX systems will provide man(1) pages for NDBM routines under ndbm(3) or ndbm(3X). FreeBSD does not provide any documentation for these routines. This is perhaps because dbopen(3) is being promoted as its replacement.

Documentation for ndbm(3) can be found on the Internet, however, at the URL http://www.opengroup.org/public/pubs/online/7908799/xsh/dbm_open.html

While FreeBSD lacks documentation on routines such as dbm_open(), this book will use section three, as in dbm_open(3). Sun Solaris places its documentation for these routines in section 3, while others place it in section 3X or 3C.


Opening an NDBM Database

The dbm_open(3) function is used to create or open a database for use. Its synopsis is as follows:

#include <ndbm.h>

DBM *dbm_open(const char *file, int open_flags, int file_mode);

The first argument, file, specifies the pathname of the database. Note that some implementations append a suffix to this name (FreeBSD adds .db). Other implementations may create two files with different suffixes appended. The string supplied in the argument file remains unchanged.

The argument open_flags specifies flag bits that would be supplied to the open(2) call. These include

  • O_RDONLY

  • O_RDWR

  • O_CREAT

  • O_EXCL

The behavior for some flags, such as the O_APPEND flag, will not be defined for this function call.

The third argument, mode, forms the permission bits to apply to the creation of the new file(s). These are passed onto the open(2) call and are subject to the current umask(2) setting.

The return value is a pointer to a DBM object if the call is successful or the value (DBM *)0 if it fails. The following example shows how a database might be created:

DBM *db;

db = dbm_open("mydatabase",O_RDWR|O_CREAT,0666);

Under FreeBSD, this creates a database file named mydatabase.db and opens it for reading and writing.

Closing an NDBM Database

An open database should always be closed before the program exits. This is accomplished with the dbm_close(3) function:

#include <ndbm.h>

void dbm_close(DBM *db);

There is no error return from this function. The input argument db must point to an open database or a fault may occur.

Storing Information

To insert a new record or to update an existing record, the dbm_store(3) function is used. Its function synopsis is as follows:

#include <ndbm.h>

typedef struct {
    char   *dptr;      /* Pointer to data */
    int    dsize;      /* Byte length of data */
} datum;

int dbm_store(DBM *db, datum key, datum content, int store_mode);

The first argument, db, specifies the open database into which to store the record. The arguments key and content are described by the C data type datum. The key argument defines the start of the key and its length. The content argument defines the record content and its length.

The final argument store_mode must contain one of the following values:

  • DBM_INSERT

  • DBM_REPLACE

When store_mode is equal to DBM_INSERT, the new record is inserted into the database, even if a record already exists with a matching key value. When store_mode is equal to DBM_REPLACE, an existing record with a matching key is replaced with the content being supplied. Otherwise, a new record is simply inserted.

The return value from the dbm_store(3) call is 0 or 1 when successful. A negative value represents a failure. The dbm_store(3) function returns a 1 when store_mode equals DBM_INSERT and the function finds an existing record with a matching key value.

The following example shows how a phone number acting as a key and an address acting as the data record are supplied to the dbm_store(3) function:

DBM *db;                       // Open database
int z;                         // Status return code
char phone_no[20];             // Phone #
datum key;                     // Key datum
char address[64];              // Record data (address information)
datum content;                 // Content datum

key.dptr = phone_no;           // Point to key value
key.dsize = strlen(phone_no);  // Set key length
content.dptr = address;        // Point to record content
content.dsize = strlen(address); // Set record length

z = dbm_store(db,key,content,DBM_REPLACE); // Replace if exists
if ( z < 0 ) {
    // Handle error
    dbm_clearerr(db);

The example shown will replace the record if a match is made on the telephone number in the database. Duplicate keys can be inserted by changing the DBM_REPLACE macro to DBM_INSERT.

Fetching Information

Once information is stored, it is necessary to retrieve it quickly. The function dbm_fetch(3) performs this function:

#include <ndbm.h>

datum dbm_fetch(DBM *db, datum key);

The dbm_fetch(3) function accepts a db argument, which specifies the database to search. The key argument specifies the key value to look up.

The return value from dbm_fetch(3) is a datum type. A successful search is indicated by returning a datum, which contains a non-null member, dptr. The following example illustrates:

DBM *db;                       // Open database
char phone_no[20];             // Phone #
datum key;                     // Key datum
char address[64];              // Record data (address information)
datum content;                 // Content datum

key.dptr = phone_no;           // Point to key value
key.dsize = strlen(phone_no);  // Set key length

content = dbm_fetch(db,key);   // Lookup phone #
if ( !content.dptr ) {
    // Key was not found in database
} else {
    // Content was returned:
    strncpy(address,content.dptr,
        min(sizeof address-1,content.dsize));
    address[sizeof address-1] = 0; // Null terminate
}

The example shows how the telephone address is extracted from the returned datum content.

Deleting Information

Data that has been created must sometimes be destroyed later. This includes when the key changes: The record must be deleted and inserted again with the new key. The synopsis of the dbm_delete(3) function is as follows:

#include <ndbm.h>

int dbm_delete(DBM *db, datum key);

The function call setup is identical to the dbm_fetch(3) function. The database is chosen by argument db, and the key value is given by the key argument. The return value is zero if the call is successful and is negative if the call fails.

The following example deletes a telephone entry from a telephone database of addresses:

DBM *db;                       // Open database
char phone_no[20];             // Phone #
datum key;                     // Key datum

key.dptr = phone_no;           // Point to key value
key.dsize = strlen(phone_no);  // Set key length

if ( dbm_delete(db,key) < 0 )  // Delete phone #
    // Key was not found in database
} else {
    // Record was deleted
}

Visiting All Keys

All records managed by a NDBM database are stored and managed by key values. Effective hashing algorithms are applied to keys to make accessing specific records very efficient. However, it often happens that you need to examine all or most records in the database. In these situations, you may not know all the key values in advance.

The functions dbm_firstkey(3) and dbm_nextkey(3) allow you to iterate through the keys stored within your database. The key values will be presented in an unsorted sequence, however. This is because hashing algorithms are used for the index. Hashed indexes cannot offer sorted keys like the B-tree indexing algorithm, for example. If you need a sorted list, you must first visit all the keys and then sort them in a temporary file.

The dbm_firstkey(3) and dbm_nextkey(3) synopsis is as follows:

#include <ndbm.h>

datum dbm_firstkey(DBM *db);
datum dbm_nextkey(DBM *db);

The functions both require one argument db as input. The function dbm_firstkey(3), as its name implies, returns the first database key. Once that function has been invoked successfully, successive calls should be made to dbm_nextkey(3) to retrieve the remaining keys.

To visit all keys within a database, the general loop construct is as follows:

DBM *db;                       // Open database
datum key;                     // Key datum

for ( key=dbm_firstkey(db); key.dptr != NULL; key=dbm_nextkey(db) ) {
    // Process key
}

The functions dbm_firstkey(3) and dbm_nextkey(3) can both indicate the end of the keys, by returning a datum, which has a null dptr pointer. When dbm_firstkey(3) returns null in the datum member dptr, this indicates that there are no keys in the database.

Deleting Keys with dbm_nextkey(3)

Special attention should be paid to modifications to the database during key visits. If you have a loop constructed as in the previous example and you use the key value to delete entries in the database, you will encounter trouble. The following shows what not to do:

DBM *db;                       // Open database
datum key;                     // Key datum

// DO NOT DO THIS:
for ( key=dbm_firstkey(db); key.dptr != NULL; key=dbm_nextkey(db) ) {
    dbm_delete(db,key);        // Delete this key
    if ( dbm_error(db) )
        abort();               // Something failed
}

The example runs into trouble because the routines dbm_firstkey(3) and dbm_nextkey(3) assume that no changes to keys will occur while the loop runs. When keys are deleted, the hash index blocks are modified, which may affect the way the next key is retrieved (these are implementation-specific problems.)

If you need to perform the function just shown, another approach works:

DBM *db;                       // Open database
datum key;                     // Key datum

for ( key=dbm_firstkey(db); key.dptr != NULL; key=dbm_firstkey(db) ) {
    dbm_delete(db,key);        // Delete this key
    if ( dbm_error(db) )
        abort();               // Something failed
}

The change is subtle, but important. The next key is fetched by calling upon dbm_firstkey(3) instead. This works because the loop always deletes the first key. By calling dbm_firstkey(3) again, you get the next "first" key.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.97.189