#include <string.h> // memset(), memcpy(), strncpy()
#include <stdio.h> // sprintf()
#include <sqludf.h>
#include <db2ApiDf.h>
#if defined(__cplusplus)
extern "C"
#endif
int SQL_API_FN getTransactionId(
SQLUDF_VARCHAR *taId,
SQLUDF_NULLIND *taId_ind,
SQLUDF_TRAIL_ARGS)
{
SQL_API_RC rc = SQL_RC_OK;
struct sqlca sqlca;
db2ReadLogInfoStruct logInfo;
db2ReadLogStruct logData;
SQLU_LSN startLsn;
SQLU_LSN endLsn;
char buffer[64 * 1024] = { '/0' }; // for log record data
EXEC SQL BEGIN DECLARE SECTION;
char uniqueVal[13] = { '/0' };
EXEC SQL END DECLARE SECTION;
// we assume NULL return
*taId_ind = -1;
/*
* Step 1: Set a savepoint to be able to undo the data modifications
*/
EXEC SQL SAVEPOINT get_transaction_id ON ROLLBACK RETAIN CURSORS;
/*
* Step 2: Query the DB2 Log to get the start LSN
*/
memset(&sqlca, 0x00, sizeof sqlca);
memset(&logInfo, 0x00, sizeof logInfo);
memset(&logData, 0x00, sizeof logData);
logData.iCallerAction = DB2READLOG_QUERY;
logData.piStartLSN = NULL;
logData.piEndLSN = NULL;
logData.poLogBuffer = NULL;
logData.iLogBufferSize = 0;
logData.iFilterOption = DB2READLOG_FILTER_OFF;
logData.poReadLogInfo = &logInfo;
rc = db2ReadLog(db2Version810, &logData, &sqlca);
if (rc < 0) {
memcpy(SQLUDF_STATE, "38TA0", SQLUDF_SQLSTATE_LEN);
strncpy(SQLUDF_MSGTX, "Could not query log for last LSN",
SQLUDF_MSGTEXT_LEN);
goto exit;
}
else if (sqlca.sqlcode) {
memcpy(SQLUDF_STATE, "38TA1", SQLUDF_SQLSTATE_LEN);
snprintf(SQLUDF_MSGTX, SQLUDF_MSGTEXT_LEN, "SQL error while "
"reading log records. SQLCODE = %d, SQLSTATE=%s",
sqlca.sqlcode, sqlca.sqlstate);
goto exit;
}
memcpy(&startLsn, &logInfo.nextStartLSN, sizeof startLsn);
/*
* Step 3: Force a log record to be written
*
* Insert a unique value into our table, which triggers a log record to be
* written. The same value is also returned right away so that we can use
* it to search through the new log records.
*/
EXEC SQL
SELECT value
INTO :uniqueVal
FROM NEW TABLE ( INSERT
INTO ta_id_force_logwrite
VALUES ( GENERATE_UNIQUE() ) ) AS t(value);
if (sqlca.sqlcode) {
memcpy(SQLUDF_STATE, "38TA2", SQLUDF_SQLSTATE_LEN);
snprintf(SQLUDF_MSGTX, SQLUDF_MSGTEXT_LEN, "SQL error while "
"triggering log record. SQLCODE = %d, SQLSTATE=%s",
sqlca.sqlcode, sqlca.sqlstate);
goto exit;
}
/*
* Step 4: Search through the new log records to find our INSERT
*/
while (true) {
char *ptr = NULL;
char *transactionId = NULL;
sqlint32 recordLength = 0;
memset(&sqlca, 0x00, sizeof sqlca);
memset(&logInfo, 0x00, sizeof logInfo);
memset(&logData, 0x00, sizeof logData);
memset(&endLsn, 0xFF, sizeof endLsn);
logData.iCallerAction = DB2READLOG_READ_SINGLE;
logData.piStartLSN = &startLsn;
logData.piEndLSN = &endLsn;
logData.poLogBuffer = buffer;
logData.iLogBufferSize = sizeof buffer;
logData.iFilterOption = DB2READLOG_FILTER_OFF;
logData.poReadLogInfo = &logInfo;
rc = db2ReadLog(db2Version810, &logData, &sqlca);
if (rc < 0) {
memcpy(SQLUDF_STATE, "38TA3", SQLUDF_SQLSTATE_LEN);
sprintf(SQLUDF_MSGTX, "Could not read log record. rc = %d",
(int)rc);
goto exit;
}
else if (sqlca.sqlcode == SQLU_RLOG_READ_TO_CURRENT) {
memcpy(SQLUDF_STATE, "38TA4", SQLUDF_SQLSTATE_LEN);
strncpy(SQLUDF_MSGTX, "Last log record reached prematurely.",
SQLUDF_MSGTEXT_LEN);
goto exit;
}
else if (sqlca.sqlcode) {
memcpy(SQLUDF_STATE, "38TA5", SQLUDF_SQLSTATE_LEN);
snprintf(SQLUDF_MSGTX, SQLUDF_MSGTEXT_LEN, "SQL error while "
"reading log records. SQLCODE = %d, SQLSTATE=%s",
sqlca.sqlcode, sqlca.sqlstate);
goto exit;
}
if (logInfo.logBytesWritten < 20) {
memcpy(SQLUDF_STATE, "38TA6", SQLUDF_SQLSTATE_LEN);
strncpy(SQLUDF_MSGTX, "Log Manager Header of record too small.",
SQLUDF_MSGTEXT_LEN);
goto exit;
}
memcpy(&startLsn, &logInfo.nextStartLSN, sizeof startLsn);
// the data in the buffer starts with the LSN, followed by the Log
// Manager Header; skip the LSN
ptr = buffer;
ptr += sizeof(SQLU_LSN);
// get the length of the log record (plus LSN)
recordLength = *(sqlint32 *)ptr + sizeof(SQLU_LSN);
ptr += 4;
// verify that this is a "Normal" log record
if (*(sqlint16 *)ptr != 0x004E) {
continue;
}
ptr += 2;
// skip behind the Log Manager Header (to the DMS Log Record Header);
// (we do not have "Compensation" records here and "Propagatable"
// doesn't occur either)
ptr += 2 + // flags
6; // LSN of previous record in same transaction
// remember the location of the transaction id
transactionId = ptr;
ptr += 6;
// now we are at the beginning of the DML Log Record Header
if (ptr - buffer + 18 + 4 > recordLength) {
continue;
}
// check that the "Function identifier" in the DMS header indicates an
// "INSERT" log record
ptr += 1;
if (*(unsigned char *)ptr != 118) {
continue;
}
// skip to the record data
ptr += 5 + // remainder of DMS Log Record Header
2 + // padding
4 + // RID
2 + // record Length
2 + // free space
2; // record offset
// the record contains data if the 1st byte of the record header (the
// record type) is 0x00 or 0x10, or if the bit 0x04 is set
if (*ptr != 0x00 && *ptr != 0x10 && (*ptr & 0x04) == 0) {
continue;
}
ptr += 4;
// we reached the record data and the unique value can be found after
// the record length
ptr += 1 + // record type
1 + // reserved
2 + // length of fixed length data
4; // RID
// that's where the unique value should be
// once we found the unique value, extract the transaction ID and
// convert it to a string
if (memcmp(ptr, uniqueVal, 13) == 0) {
int i = 0;
char *result = taId;
for (i = 0; i < 6; i++) {
sprintf(result, "%02hhx", ptr[i]);
result += 2;
}
*result = '/0';
*taId_ind = 0;
break; // found the correct log record
}
}
exit:
EXEC SQL ROLLBACK TO SAVEPOINT get_transaction_id;
return SQLZ_DISCONNECT_PROC;
}
|