![]() | ![]() |
Home |
|
|
XA Interface Integration Guide for CICS, Encina, and TUXEDO Adaptive Server Version 12.5 |
|
| Chapter 4 Application Programming Guidelines |
Chapter 4
Embedded SQL and Client-Library applications must conform to certain coding constraints in order to function within the Sybase XA environment. This chapter summarizes these constraints and provides a Client-Library code fragment and two Embedded SQL code fragments.
The X/Open DTP model of transaction processing differs substantially from the traditional Sybase model. The traditional Sybase TP environment is connection oriented. Programs set up connections directly between the application program and Adaptive Server using connection management SQL statements. In the XA Interface environment, the XA Interface, using LRMs, sets up connections for the application.
Table 4-1 summarizes the differences.
Traditional TP model | X/Open DTP model |
There is one or more transaction per client/server connection. | There is no notion of connections. Components communicate through interfaces. |
Transactions are usually local, with each transaction confined to a single Adaptive Server. | Transactions are global. They span resource managers. The work done within a transaction is accomplished using more than one resource manager. |
Each Adaptive Server is responsible for the recovery of the data it contains. | The transaction manager is responsible for recovering the data stored in all of the resource managers. |
Applications must pay special attention to commands related to:
The XA Interface uses an ANSI default isolation level of 3. To minimize read-only locking, programs can set the transaction isolation level in the XA configuration file, or they can use select xxx from table noholdlock in individual SQL operations. See the Transact-SQL User's Guide for additional information on transaction isolation levels.
The CICS, Encina, or TUXEDO TM is responsible for transaction management. This includes creating a global transaction in which all of an application's work is either committed or rolled back. Consequently, applications cannot issue SQL statements that manage transactions.
Specifically, applications cannot invoke the following Embedded SQL commands:
begin transaction
commit
rollback
Client-Library applications cannot execute (via ct_command, ct_dynamic, or ct_cursor) any of these Transact-SQL commands:
begin transaction
commit transaction
rollback transaction
set (chained, noexec, isolation, parseonly, statistics io, statistics time)
save transaction
The application must recognize Adaptive-Server-detected errors, and abort or roll back the transaction through the TM. This is especially important for Adaptive Server detected-deadlocks.
Applications rely on the Sybase XA environment for management of client/server connections. Connection management occurs transparently to the application. Consequently, Embedded SQL applications cannot invoke the following commands for XA-managed connections:
connect
disconnect
Client-Library applications cannot call these Client-Library commands using XA-managed connections:
ct_close
ct_con_alloc
ct_con_drop
ct_con_props
ct_config with the parameters:
CS_ENDPOINT
CS_EXPOSE_FMTS
CS_HIDDENKEYS
CS_MAX_CONNECT
CS_NETIO
CS_TRANSACTION_NAME
ct_connect
ct_exit
ct_getloginfo
ct_init
ct_options with the parameters:
CS_OPT_CHAINXACTS
CS_OPT_FORCEPLAN
CS_OPT_FORMATONLY
CS_OPT_NOEXEC
CS_OPT_PARSEONLY
CS_OPT_STATS_IO
ct_remote_pwd
ct_setloginfo
CS_OPT_STATS_TIME
In addition, Client-Library applications cannot call these CS-Library commands:
cs_ctx_drop (with global context handle)
cs_objects (CS_CLEAR, CS_SET)
The notion of a default connection, as described in the Open Client Embedded SQL documentation, does not exist in the Sybase XA environment. Consequently, applications must always explicitly specify a current connection.
There are two ways to specify the current connection in Embedded SQL. They are:
The set connection command
The at connection name clause
A current connection does not span transactions. For example, an application must reset the current connection after each CICS SYNCPOINTcommand or Encina onCommit command. To avoid confusion about the scope of the current connection, Sybase recommends that you use the at connection_name clause with all Embedded SQL statements.
Applications can open and use nontransactional connections with the normal Open Client or Embedded SQL Interfaces. Operations on such connections do not participate in the transaction and are not committed or rolled back. They may be useful for queries of unchanging databases and updates of data which can be inaccurate.
Application programs use and reuse connections that have been allocated for them via the XA Interface. Sybase's implementation of cursors starting with SQL Server version 10.1 requires cursor structures on both the client (TM/RM program) side and the Adaptive Server side.
When a client explicitly deallocates a cursor, or when the client connection is closed, Adaptive Server deallocates the server cursor structures.
When the first iteration of a program opens or closes a cursor but the connection stays allocated (as it does with XA-Library), the second iteration of the same program fails, as it attempts to open the same cursor name. Adaptive Server informs us that it already has a cursor by this name at the same nesting level.
The application program must explicitly close and deallocate the cursor before it commits or aborts its transaction. This must be done in the transaction program that allocates the cursor. Embedded SQL records information about cursors which allows the XA Interface to perform the deallocation.
With Client-Library, you must handle error paths so that cursors are deallocated before a TM abort code is called. That is, if the open cursor works, deallocate it.
Use ct_cursor( ) with type CS_CURSOR_CLOSE and option CS_DEALLOC.
The use of dynamic SQL statements has many characteristics in common with cursors, with the additional complexity that temporary stored procedures are sometimes placed into Adaptive Server. The use of dynamic SQL is not recommended in transactional applications, but if they are used, the following guidelines must be adhered to:
In Embedded SQL use "Method 3: Prepare and Fetch with a Cursor" (see the ESQL document or a description of this method) if possible. When this method is used, Embedded SQL places information in the system which allows the XA Interface to locate and deallocate all dynamic SQL and cursors.
In all other cases, the dynamic SQL statements and all associated cursors must be closed and deallocated to avoid adverse effects on other transactions. Any associated Client-Library command structures should be dropped to avoid memory leaks. See the Open Client and ESQL documentation for information on how to drop these command structures.
Obtaining a connection handle is an issue specific to Client-Library applications.
When the TM opens a connection to Adaptive Server, the XA Interface allocates a CS_CONNECTION structure for its own use. Once control passes to the application, that application must use the connection handle contained in this structure.
To get the connection handle, specify CS_GET for the cs_object routine's action parameter with an object type of CS_CONNECTION. cs_object's objdata parameter returns a structure containing a connection field. This field contains the handle to the CS_CONNECTION structure.
Warning!
The XA Interface also allocates a CS_COMMAND structure whose handle is returned in the command field of the structure to which the objdata parameter points. An application cannot use this command handle, as the XA Interface continues to use this handle, itself.
The following code fragment demonstrates how to retrieve the handle to the CS_CONNECTION structure:
/*
** Arguments:
**connection null-terminated name of the connection
**(ESQL) or LRM connHloaded with the CS_CONNECTION
** handle if the lookup is successful
**
** Returns:
** CS_SUCCEED connection handle found successfully
** CS_FAIL unable to find connection handle for given
** connection /#include <stdio.h> #include <strings.h>
** #include <cspublic.h>CS_RETCODE getConn(connection,
** connH)CS_CHAR connection[128];CS_CONNECTION connH;
{
CS_INT retcode;
CS_CONTEXT *ctx;
CS_OBJNAME name;
CS_OBJDATA data;
CS_THREAD thread_functions;
CS_INT outlen;
#define THREADID_SIZE 8
CS_BYTE thread_id[THREADID_SIZE];
/* Check arguments */
if (strlen(connection) >= 128)
{
/* Connection name is too long */
return(CS_FAIL);
}
/* Get the global context handle */
retcode = cs_ctx_global(CS_VERSION_100, &ctx);
if (retcode != CS_SUCCEED)
{
/* Major environment problems! */
return(CS_FAIL)
}
/*
** Initialize the CS_OBJNAME structure to look
** for the specified connection name.
*/
name.thinkexists = CS_FALSE;
name.object_type = CS_CONNECTNAME;
strcpy(name.last_name, connection);
name.fnlen = CS_UNUSED;
name.lnlen = CS_NULLTERM;
name.scopelen = CS_UNUSED;
/*
** Set the current thread-id so we get the instance of
** this connection that this thread should be using.
*/
retcode = cs_config(ctx, CS_GET,
CS_THREAD_RESOURCE, &thread_functions,
CS_UNUSED, &outlen);
if (retcode != CS_SUCCEED)
{
/*
** Even in an non-threaded environment,this should be
** successful.
*/
return(CS_FAIL);
}
name.thread = (CS_VOID *) thread_id;
retcode = (*thread_functions.thread_id_fn)(
name.thread, THREADID_SIZE,
&name.threadlen);
if (retcode != CS_SUCCEED)
{
return(CS_FAIL);
}
/*
** Initialize the CS_OBJDATA structure to receive the
** connection handle for this connection name
*/
data.actuallyexists = CS_FALSE;
data.connection = (CS_CONNECTION *) NULL;
data.command = (CS_COMMAND *) NULL;
data.buffer = (CS_VOID *) NULL;
data.buflen = CS_UNUSED;
/* Retrieve the connection information */
retcode = cs_objects(ctx, CS_GET, &name,
&data);
if (retcode == CS_SUCCEED)
{
if (data.actuallyexists == CS_TRUE)
{
*connH = data.connection;
return(CS_SUCCEED);
}
else
{
/* No connection by that name exists */
return(CS_FAIL);
}
}
else
{
/*
** The global CS_CONTEXT handle is probably not
** initialized with connection information yet
*/
return(CS_FAIL);
}
}Threads are multiple, simultaneous paths of execution in a single operating system process, and share access to the resources allocated to that process.
Some application programming interfaces (APIs) allow an application developer to effectively use threads in the transaction environment. In turn, Sybase's XA Interface supports a maximum level of concurrency, enabling it to take advantage of those environments.
However, this raises several issues for an application developer. For background information and a complete discussion of the issues, see the OSF's DCE Application Developer's Guide.
The Open Client Reference Manual contains a section on thread-safe programming. XA Interface assigns connections to threads at the request of the TM. These assignments ensure that only one thread at a time is working on the connection and is the reason the thread ID is included in the cs_object request described in "Getting a Client-Library connection handle". As long as connections assigned by XA Interface are used in the thread to which they are assigned and the restrictions on their use are followed, there should be no Open Client or ESQL threading-related problems.
Client-Library uses a connection state machine to verify that applications call Client-Library routines in a logical sequence. See Chapter 2, "Program Structure" in the Open Client Client-Library/C Programmer's Guide for an explanation of the steps involved in structuring a Client-Library application.
The assumption underlying the use of threads is that when a thread disassociates from a transaction branch, it leaves the state machine in an inactive state. By default, all Embedded SQL statements leave the connection quiescent. With Client-Library, this is true only in the following circumstances:
When ct_results returns CS_END_RESULTS, or CS_SUCCEED with a result type of CS_CURSOR_RESULT.
After an application calls ct_cancel with type as CS_CANCEL_ALL.
When an application returns CS_CANCELED. The APIs that return CS_CANCELED include ct_send(), ct_results(), and ct_get_data().
Warning!
If connections are not left in an inactive state, the consequences may include transaction rollbacks, extra overhead as the XA Interface cleans up the connection (which may require full connection close and reopen), and the possible failure of subsequent transactions. In such a situation, XA Interface attempts to maintain application operation while it minimizes failure.
Thread-safe code protects the use of shared resources with a mutex (MUTual EXclusion semaphore). A mutex protects shared resources, such as files and global variables, by preventing them from being accessed by more than one thread at a time.
Use the -h (UNIX) or /threadsafe (VMS) precompiler option to generate thread-safe code.
The XA environment treats each thread or process that works on a transaction as a transaction branch. Each transaction branch is assigned a different xid and works independently of the other branches. However, all branches are committed or rolled back as a unit.
Some TMs allow branches to be tightly coupled. Tightly coupled branches are assigned the same xid and work together on the transaction. In such cases the open string can contain the -O1 option. This option causes Adaptive Server to move the work among connections on demand and eliminates any lock that might otherwise occur between the connections. See your TM documentation to determine how the TM can be configured for tightly coupled operation.
Warning!
Set the -O1 option only when the application design is guaranteed to avoid conflicting updates. Normally this is true only when the application branches are fully serialized, (branch B operates only after branch A completes). Data inconsistency may occur if the interaction of the tightly coupled branches is not well designed.
Without the -O1 option, attempts by the branches to update the same database row can result in a deadlock internal to the transaction. The -O1 option has no practical effect when the branches are not tightly coupled through the TM and are assigned different xids.
Warning!
Cursors and dynamic SQL cannot be retained when the transaction is assigned to a different connection. Therefore, they should not be used unless the application structure guarantees that they are opened and closed during a period when no other branch will work on the transaction.
The transaction is reassigned to another connection only between SQL batches. A tightly coupled application can ensure that a set of operations is completed without conflict by performing all the operations in a single batch. This implies that operations within a single stored procedure are also completed without conflict.
For example, if row z in table B must contain the sum of rows x and y in table A. The following can result in an invalid value in row z:
Branch 1: Branch 2
Updates Row x -> 5
Reads Row y (= 4)
Updates Row y -> 5
Reads Row x (= 5)
Updates Row z -> 10
Updates Row z -> 9 (wrong value)No problem occurs if the branches are performed serially:
Branch 1: Branch 2
Updates Row x -> 5
Reads Row y (= 4)
Updates Row z -> 9
Updates Row y -> 5
Reads Row x (=5)
Updates Row z -> 10A control branch can also be used to resolve the problem:
Branch 0: Branch 1: Branch 2
(controller)
Starts Branches 1 and 2
Waits for both to complete
Updates Row x Updates Row y -> 5
Terminates Terminates
Reads Row y
Reads Row x
Updates Row zTM specific branch control mechanisms must be used to implement these serialization mechanisms.
The XA Interface requires that the application be linked with the threaded versions of the Open Client Libraries. See the Open Client/Server Supplement for your platform to identify the libraries you must specify. If you do not link the proper thread-safe libraries, you may experience a variety of Open Client failures.
This code fragment sets the current connection, and inserts data into an Adaptive Server database:
*REMARKS. TRANSACTION-ID IS 'POPS'.
*THIS TRANSACTION POPULATES A DATABASE'S DATA TABLE *WITH STOCK DATA ENTRIES.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY DFHBMSCA.
COPY DFHAID.
COPY AIXCSET.
EXEC SQL INCLUDE SQLCA END-EXEC.
77 RESPONSE PIC 9(8) COMP.
01 MSG-LIST.
02 MSG-1 PIC X(70) VALUE
'Transaction Failed: Unable To Prime Stock'
-'Table.'.
02 MSG-2 PIC X(70) VALUE
'Stock Records Added Successfully.'.
01 TRANSFAIL PIC X(70).
EXEC SQL BEGIN DECLARE SECTION END-EXEC.
01 STOCK-RECORD.
02 STOCK-NUM PIC X(5).
02 ITEM-DESC PIC X(30).
02 STOCK-QTY PIC X(7).
02 UNIT-PRICE PIC S9(4)V99 VALUE ZEROES.
EXEC SQL END DECLARE SECTION END-EXEC.
PROCEDURE DIVISION.
* CHECK BASIC REQUEST TYPE
*
IF EIBAID = DFHCLEAR
EXEC CICS SEND CONTROL FREEKB
END-EXEC
EXEC CICS RETURN
END-EXEC
END-IF.
* MAIN PROCESSING
*SET UP STOCK RECORD DETAILS AND THEN WRITE OUT
*STOCK RECORD.
*
MOVE '31421'TO STOCK-NUM.
MOVE 'Widget (No.7)'TO ITEM-DESC.
MOVE '0050035'TO STOCK-QTY.
MOVE 25.55 TO UNIT-PRICE.
PERFORM WRITE-STOCKREC.
MOVE '43567'TO STOCK-NUM.
MOVE 'Splunkett ZR-1'TO ITEM-DESC.
MOVE '0005782'TO STOCK-QTY.
MOVE 143.79 TO UNIT-PRICE.
PERFORM WRITE-STOCKREC.
EXEC CICS SYNCPOINT
RESP(RESPONSE)
END-EXEC.
IF RESPONSE NOT = DFHRESP(NORMAL)
MOVE MSG-1 TO TRANSFAIL
PERFORM FAIL-TRANS
END-IF.
MOVE MSG-2 TO MSGOUTO.
EXEC CICS SEND MAP('MSGLINE')
MAPSET('AIXCSET')
FREEKB
END-EXEC.
EXEC CICS RETURN
END-EXEC.
GOBACK.
* ATTEMPT TO WRITE OUT NEW STOCK RECORD.
*
WRITE-STOCKREC.
EXEC SQL SET CONNECTION connection_2
END-EXEC
IF SQLCODE NOT = 0
MOVE MSG-1 TO TRANSFAIL
PERFORM FAIL-TRANS
END-IF.
EXEC SQL INSERT INTO STOCK VALUES (:STOCK-RECORD)
END-EXEC
IF SQLCODE NOT = 0
MOVE MSG-1 TO TRANSFAIL
PERFORM FAIL-TRANS
END-IF.
* IF UNABLE TO APPLY CREATE, END TRANSACTION
* AND DISPLAY REASON FOR FAILURE.
*
FAIL-TRANS.
MOVE TRANSFAIL TO MSGOUTO
EXEC CICS SEND MAP('MSGLINE')
MAPSET('AIXCSET')
FREEKB
END-EXEC
EXEC CICS RETURN
END-EXEC.This code fragment sets the current connection, and accesses data stored on Adaptive Server:
EXEC SQL INCLUDE sqlca;
int rcode;
EXEC SQL BEGIN DECLARE SECTION;
char name[15];
char supplier[30];
char supplier_address[30];
int order_quantity;
EXEC SQL END DECLARE SECTION;
main()
{
char errmsg[400];
char qmsg[400];
short mlen;
EXEC SQL WHENEVER SQLERROR GOTO :errexit;
EXEC SQL WHENEVER SQLWARNING GOTO :errexit
EXEC SQL WHENEVER NOT FOUND GOTO :errexit
/* Get addressability for EIB... */
/*
** Write record to CICS temporary storage queue...
*/
/* Send the first map */
EXEC CICS SEND MAP("PANEL1") MAPSET("UXA1")
FREEKB ERASE RESP(rcode);
if (rcode != DFHRESP(NORMAL))
EXEC CICS ABEND ABCODE("X001");
/* Receive the response */
EXEC CICS RECEIVE MAP("PANEL1") MAPSET("UXA1")
RESP(rcode);
if (rcode != DFHRESP(NORMAL))
EXEC CICS ABEND ABCODE("X002");
/* Select a record from the table based on user input. */
sprintf(name, "%s", panel1.panel1i.newnamei);
EXEC SQL SET CONNECTION connection_1;
EXEC SQL SELECT name, supplier, supplier_address,order_quantity
INTO
:name, :supplier, :supplier_address, :order_quantity
FROM cheese
WHERE name = :name;
/* Handle "no rows returned" from SELECT */
if (sqlca.sqlcode == 100)
{
sprintf(panel4.panel4o.messageo, "%s",
NOCHEESE);
EXEC CICS SEND MAP("PANEL4") MAPSET("UXA1") FREEKB ERASE RESP(rcode);
if (rcode != DFHRESP(NORMAL))
EXEC CICS ABEND ABCODE("X009");
EXEC CICS SEND CONTROL FREEKB;
EXEC CICS RETURN;
}
/* Fill in and send the second map */
memset ( &panel2.panel2o, '0',
sizeof(panel2.panel2o));
sprintf(panel2.panel2o.nameo, "%s", name);
sprintf(panel2.panel2o.supplo, "%s",supplier);
sprintf(panel2.panel2o.addresso, "%s",
supplier_address);
sprintf(panel2.panel2o.ordero, "%d", order_quantity);
EXEC CICS SEND MAP("PANEL2") MAPSET("UXA1")
FREEKB ERASE RESP(rcode);
if (rcode != DFHRESP(NORMAL))
EXEC CICS ABEND ABCODE("X003");
/* Receive the response */
if (panel2.panel2i.questi == 'y')
{
/* Send the third map... */
/* Receive the response... */
/* Update the database */
order_quantity = atoi(panel3.panel3i.newordi);
EXEC SQL UPDATE cheese
set order_quantity = :order_quantity
where name = :name;
/* Write a record to the temporary queue */
sprintf(qmsg, "%s", "The cheese table was updated");
mlen = strlen(qmsg);
EXEC CICS WRITEQ TS QUEUE("TEMPXAQ1")
FROM(qmsg) LENGTH(mlen) RESP(rcode);
if (rcode != DFHRESP(NORMAL))
EXEC CICS ABEND ABCODE("X010");
}
else
{
/*
** The user does not wish to update so free the keyboard and return...
*/
}
/* Commit the update */
EXEC CICS SYNCPOINT RESP(rcode);
if (rcode != DFHRESP(NORMAL))
EXEC CICS ABEND ABCODE("X011");
/*
** Send the fourth map confirming successful update...
*/
EXEC CICS RETURN;
errexit:
fprintf(stderr,"error in cheeseland %d\n",sqlca.sqlcode);
/* Handle general errors */
sprintf(errmsg, "%.60s\n",sqlca.sqlerrm.sqlerrmc);
strncpy(panel4.panel4o.messageo, errmsg, 60);
sprintf(panel4.panel4o.codeo, "%d", sqlca.sqlcode);
/*
** Send the fourth map with appropriate message...
*/
/* Rollback the transaction */
EXEC CICS SYNCPOINT ROLLBACK;
EXEC CICS SEND CONTROL FREEKB;
EXEC CICS RETURN;
}
|
|