Sybase Technical Library - Product Manuals Home
[Search Forms] [Previous Section with Hits] [Next Section with Hits] [Clear Search] Expand Search

Administrative Classes [Table of Contents] Datatypes

Sybase dbQueue Reference Guide

[-] Chapter 1: Using the dbQueue Objects Library
[-] Application Classes

Application Classes

The dbQueue objects library includes three application classes that are used to build queuing applications.

The following sections discuss common scenarios for enqueuing and dequeuing messages.

Connecting to a Queue

To connect to queues, client applications:

  1. Create a connection to the queue database using the DbqConnection class or an ODBC connection.

  2. Allocate a DbqQueueAccessor instance for the queue.

  3. Invoke the Open() method on the queue to initialize it for queuing operations.

After the client application has established a connection, enqueue and dequeue operations using either connection method are identical.

Connecting Using the DbqConnection Class

The following code sample shows how to connect to a queue using the DbqConnection class.

// Allocate connection from environment
DbqConnection* conn;
env->NewDbConnection(&conn);
// Fill in credentials and open...
conn->SetDataSource"myappdb");
conn->SetLogin("hodges");
conn->SetPassword("fleegle");
if (conn->Open() != DBQ_SUCCEED)
    ErrorHandler(conn); 
// Allocate the queue instance. 
DbqQueueAccessor* queue;
if (env->NewQueueAccessor(conn, "q1", &queue) 
        != DBQ_SUCCEED)
{
    // Handle error...
}
// Open up the queue. 
if (queue->Open() != DBQ_SUCCEED)
{
    // Handle error...
}

Connecting Using a Shared ODBC Connection

Client applications can also use DbqConnection instances sharing ODBC connection handles rather than logging in and creating a new connection. This permits dbQueue classes to work on the same database connection used by other code in the application. For example, an application might submit a query using SQL and enqueue a message as part of a single transaction. A "shared" ODBC connection between the code for each task allows the tasks to participate in the same database transaction.

The following code sample shows how to connect using an exiting ODBC connection.

// Establish ODBC connection
retcode = SQLAllocConnect(henv, &hdbc);
if (retcode != SQL_SUCCESS)
    ...handle error
retcode = SQLConnect(hdbc, "myserver", SQL_NTS,
                    "bart", SQL_NTS,
                    "fleegle", SQL_NTS);
if (retcode != SQL_SUCCESS)
    ...handle error
// Allocate the queue instance using ODBC connect handle. 
DbqConnection* conn;
if (env->NewSharedConnection(hdbc, &conn) 
        != DBQ_SUCCEED)
{
    // Handle error...
}
// Initialize the connection. 
if (conn->Open() != DBQ_SUCCEED)
{
    // Handle error...
}

Once created, DbqConnection instances based on shared connections behave as normal instances with independent connections. The only difference is Close() and Delete() have no effect on the connection state.

This connection-sharing approach will work for any class package that makes the ODBC connection handle available. For example, Microsoft Foundation Classes (MFC) for ODBC can expose the ODBC connection handle, which means that dbQueue objects library classes can be easily integrated with other database operations that use MFC.

Enqueuing a Message

To enqueue messages, an application:

  1. Creates a new DbqMessage instance to represent the message.

  2. Adds data to the message.

  3. Sends the message to the queue.

Create a New Message

Use the NewMessage() method of the DbqDomain class to create a new message for queuing. Messages are typed in dbQueue, so calls to NewMessage() take a message type name. If the message type is known to the domain, NewMessage() returns a new message instance of that type.

The following code sample shows how to create a message of type "personnel" and handle any errors that may result from this request.

DbqDomain *dom;
DbqMessage *msg;
DBQ_STATUS status;
...
status = dom->NewMessage("personnel", &msg);
switch (status)
{
    case DBQ_SUCCEED:
        break;
    case DBQ_E_UNSUPPORTED_TYPE:
        printf("Domain does not support message type\n");
        break;
    default:
        printf("Unexpected error!\n");
}

Add Data to the Message

DbqMessage provides special accessor functions to set message header information and column values. These accessor functions copy the data supplied by the client application into an internal structure. The following code example shows how a single accessor can be used to set a column value.

DbqMessage* msg;
DBQ_STATUS status;
DBQ_INT bytesCopied;
DBQ_NUMERIC ssn = "379-76-5421"; 
...
status = msg->PutColNumeric(1, ssn, 11);

The function returns a status value that reveals any translation errors such as if the column uses the wrong datatype, or a data overflow if the field is not large enough.

The PutColNumeric() method and other accessor functions have default values for the last argument. Therefore, applications can omit this value.

The following code sample shows accessor functions that fill in more fields for the "personnel" message type.

DbqMessage* msg;
...
// Fill in message body fields. 
msg->PutColNumeric(1, (DBQ_NUMERIC) "379-76-5421");
msg->PutColVarChar(2, (DBQ_VARCHAR) "Smith"); 
msg->PutColVarChar(3, (DBQ_VARCHAR) "John P.");
msg->PutColInt(4, 35);

Adding data to a message can fail. dbQueue objects prevent invalid data from entering the message wherever possible. The following code sample shows how client applications can check for errors:

status = msg->PutColVarChar(3, (DBQ_CHAR) "John P.");
switch (status)
{
    case DBQ_SUCCEED:
        break;
    case DBQ_E_PUT_DATA_OVERFLOW:
        printf("Input was too big\n");
        break;
    default:
        printf("Unknown failure\n");
}
WARNING! There are no default values for message fields. Applications must provide values for each field in the message.

Send the Message to the Queue

After the data has been added to the message, the application can enqueue the message.

DbqMessage* msg; 
DbqQueueAccessor* queue;
...
if (queue->Enqueue(msg) != DBQ_SUCCEED)
{
    printf("Error enqueuing message\n");
    // handle error
}
// Deallocate message. 
msg->Delete();

The application is responsible for deallocating the message instance after it has finished using it.

Applications can enqueue the same message instance multiple times on the same queue or on different queues. This is useful for applications that need to broadcast the same message to multiple targets or send messages that are partly or completely identical in content.

Dequeuing a Message

An application's dequeuing operation consists of two steps:

  1. Call the Dequeue() method or the associated DequeueBy<header_field>() methods on the queue. Dequeue() generates a message instance that contains the message. DequeueBy<header_field>() includes methods for dequeuing by message type name, message ID, subject, and priority.

  2. Extract data from the message instance.

The following code sample shows how to dequeue a message from a queue and handle any errors that may result.

// Declare variables. 
DbqMessage* msg;
DbqQueueAccessor* queue;
...
// Dequeue a message and print the header and 
// first two columns. 
status = queue->Dequeue(&msg);
switch (status)
{
    case DBQ_SUCCEED:
        // ... Extract data
        break;
    case DBQ_E_QUEUE_EMPTY:
        printf("No messages in queue\n");
        break;
    default:
        printf("Unexpected error\n"); 
        exit(1);
}
// Get rid of the message. 
msg->Delete(); 

Applications use special accessor functions to retrieve data from the message. The following code sample shows how to retrieve message body information:

// Declare variables. 
char col1[DBQ_MAX_NUMERIC_SIZE+1];
char col2[DBQ_MAX_CHAR_SIZE+1];
char co13[DBQ_MAX_CHAR_SIZE+1];
DBQ_INT col4;
// Fetch information from the message. 
msg->FetchColNumeric(1, col1, DBQ_MAX_NUMERIC_SIZE+1, 0);
msg->FetchColVarChar(2, col2, DBQ_MAX_CHAR_SIZE+1, 0);
msg->FetchColVarChar(3, col3, DBQ_MAX_CHAR_SIZE+1, 0);
msg->FetchColInt(4, &col4);

FetchCol() returns a status value that shows the conversion status and signals any errors that have occurred. It has the same status values as PutCol().

Message header values have the same set and get methods for object attributes. The fields for these values have fixed sizes and types and do not need the extra information provided in put and fetch methods.

WARNING! If there is a failure, FetchCol() methods do not update the user-supplied buffer value. Applications should check status values carefully to ensure that buffers are actually valid after calling FetchCol().

Dynamic Message Processing

A DbqMessage instance carries information about its message type and the datatype of each individual column. This permits applications to process dequeued messages dynamically by obtaining the message type information from the dequeued message. Applications can use this technique to process any message that has been dequeued, regardless of its message type.

The message type of each message instance is stored in its MessageTypeName attribute. To obtain the type information of each column in the message, use the ListColumns() method on the message. ListColumns() returns a DbqEnumerator with DbqMessageColumn instances that describe each column of the body of the message.

The following code sample shows how to retrieve the contents of dequeued messages of different message types.

DbqEnumerator* colList;
status = queue->Dequeue(msg);
// Get the column descriptions. 
msg->ListColumns(&colList);
// Fetch and print each column in the message. 
// We handle a subset of column types here, but it 
// is easy to add more. 
for (i = 1; i <= colList->Size(); i++)
{
    printf("Col %d: ", i);
    DbqMessageColumn* mc = (DbqMessageColumn*)
            colList->GetElement(i); 
    char outputBuf[256];
    switch (mc->GetType())
    {
        case DBQ_C_INT:
            msg->FetchColInt(i, buf);
            printf("%d\n", buf);
            break; 
        case DBQ_C_VARCHAR:
            msg->FetchColVarChar(i, buf, 256);
            printf("%s\n", buf);
            break;
        // etc. 
    }
}
// Delete list.
colList->Delete();

As the example indicates, column metadata permits applications to perform dynamic processing over a wide range of message types, including messages that the application has never seen before. This feature is an important capability of systems built on dbQueue.

Transactions and Queue Operations

By default, each enqueue or dequeue operation is a single database transaction. The operation automatically commits (auto-commits) immediately upon completion or failure.

Many applications need to control transactions explicitly in order to group queue operations together, combine queue operations with other database operations, or hold the transaction open until some other action, such as printing a check, can be completed. The DbqConnection class provides methods for explicit transaction control.

The following code sample shows how to use DbqConnection to enqueue under transaction control.

// Start transaction. 
conn->BeginTran();
// Create and enqueue a message. 
domain->NewMessage("msg1", &msg);
msg->PutColNumeric(1, col1, DBQ_MAX_NUMERIC_PRECISION);
...
q->Enqueue(msg);
// Commit the transaction. 
conn->Commit();

You can use the same sequence of operations when you are using shared ODBC connections. The following code sample shows how to commit an enqueue operation on a connection directly managed through ODBC. It assumes that the queue accessor instance uses a shared connection.

// Tell connection to put SQL in a transaction. 
SQLSetConnectionOption(hdbc, SQL_AUTO_COMMIT, 0);
// Create and enqueue message as usual.
domain->NewMessage("msg1", &msg);
msg->PutColNumeric(1, col1, DBQ_MAX_NUMERIC_PRECISION);
...
status = q->Enqueue(msg);
// Commit or rollback according to the status. 
if (status == DBQ_SUCCEED)
    SQLTransact(henv, hdbc, SQL_COMMIT);
else
    SQLTransact(henv, hdbc, SQL_ROLLBACK);

Administrative Classes [Table of Contents] Datatypes