#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; }
|