Replace Ids in an ODBC target table, order-aligned with the date log core, inside a mandatory transaction
ReplaceDateLogHandleIdentifiers builds and executes a temporary staging table, bulk-inserts the date log core into it, and then uses BulkReplaceTargetTableIdsOrderAligned() to replace the Ids in one or more target tables (as described by pBulk->pIdUpdateSpec), order-aligned with the staging data. All of this happens inside a single, mandatory ODBC transaction: SQL_ATTR_AUTOCOMMIT is forced OFF for the duration of the call.
The typical use is after a date log has been loaded or regenerated in a new environment and the date item identifiers (GUIDs) stored in an ODBC backend must be made consistent with the identifiers in the in-memory log.
SQL_TXN_READ_UNCOMMITTED isolation and autocommit OFF for the duration of the call; isolation is restored to SQL_TXN_READ_COMMITTED afterwards.
HRESULT _Must_inspect_result_ _Success_(SUCCEEDED(return)) ReplaceDateLogHandleIdentifiers(
_In_ DATE_LOG_HANDLE hDateLogHandle,
_In_ DATE_LOG_ODBC_LEVEL OdbcLevel,
_Inout_ PSQL_DATE_BULK pBulk,
_In_ DWORD dwFlags
);
The function takes exactly four arguments, in this order. datelog.h does not list this function; the prototype above is the one documented for this function.
[In] DATE_LOG_HANDLE
The date log whose core supplies the identifiers.
DATE_LOG_MAGIC handle (a date log, not a range or holidays handle) whose core holds at least one and at most DATE_LOG_MAX items. Otherwise ERR_INVALID_DATELOG_HANDLE is returned.HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)).CloseDateLogHandle.[In] DATE_LOG_ODBC_LEVEL
Which of the handle’s ODBC connections to use (DATE_LOG_ODBC_1 through DATE_LOG_ODBC_4).
InitializeDateLogHandleOdbc and connect it with ConnectDateLogHandleToOdbcDatabase. If the connection context cannot be resolved, HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE) is returned.[In, Out] PSQL_DATE_BULK
Describes the replacement to perform.
pBulk->pIdUpdateSpec must pass IsValidOdbcBulkIdUpdateSpec(). It specifies the target table(s), the id column and the correlation key.HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)); an invalid pIdUpdateSpec yields the same error.[In] DWORD
Reserved; unused. Pass 0.
| Value | Meaning |
|---|---|
S_OK | Identifiers were replaced and the transaction was committed; the backing file was swapped successfully. |
HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER) | NULL arguments, or pBulk->pIdUpdateSpec is invalid. |
ERR_INVALID_DATELOG_HANDLE | The handle is not a DATE_LOG_MAGIC handle, or its item count is 0 or exceeds DATE_LOG_MAX. |
HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE) | The connection context could not be resolved for OdbcLevel. |
HRESULT_FROM_WIN32(ERROR_UNIDENTIFIED_ERROR) | Failure setting the isolation level or autocommit, or a commit/rollback failure. |
Other failure HRESULT | Propagated from BuildOdbcDateColumnPlan, CreateOdbcDateLogTable, BulkInsertDateLogCoreToOdbc, BulkReplaceTargetTableIdsOrderAligned, or the file/backup restore functions. |
Use FAILED/SUCCEEDED on the result; it must be inspected. Library ERR_* codes are defined in date_error.h.
On any failure after the transaction is opened, the ODBC transaction is explicitly rolled back in the __finally block before the isolation level is restored to SQL_TXN_READ_COMMITTED. The staging table is built from the date log core, so the order of the staged rows is the order of the core; target rows are replaced order-aligned with that staging data.
This page does not document per-call locking for the whole operation. ODBC connections are owned by the handle, and the operation changes the connection’s autocommit and isolation settings, so do not use the same OdbcLevel from another thread (for example SetDateLogOdbcConnectAttr or EndDateLogOdbcTransaction) while it runs. Use a different OdbcLevel or serialize access to the handle.
CloseDateLogHandle, which also releases the handle’s ODBC connections when the last handle referencing the shared context is closed.PODBC_CONNECT_PARAMS used to connect is not retained by the connection; free it with FreeOdbcConnectParams as soon as the connection is established.InitializeDateLogHandle → LoadDateLogHandle → InitializeOdbcConnectParams → InitializeDateLogHandleOdbc → ConnectDateLogHandleToOdbcDatabase → ReplaceDateLogHandleIdentifiers.FetchDateLogHandleToOdbcDatabase pushes the date log core into an ODBC table; this function replaces ids in existing target tables.EndDateLogOdbcTransaction ends a caller-managed transaction; this function does not need it because it commits or rolls back by itself.GetDateLogOdbcConnectAttr can be used to read the connection’s current attributes.FetchDateLogHandleToOdbcDatabaseInitializeDateLogHandleOdbcConnectDateLogHandleToOdbcDatabaseSetDateLogOdbcConnectAttrGetDateLogOdbcConnectAttrEndDateLogOdbcTransactionInitializeOdbcConnectParamsFreeOdbcConnectParamsCloseDateLogHandleThe sample builds a date log for 2025-01-02 through 2025-03-31, connects slot DATE_LOG_ODBC_1 to a SQL Server database, and calls ReplaceDateLogHandleIdentifiers. The pIdUpdateSpec contents depend on your schema, so they are provided by two application functions (PrepareIdUpdateSpec / ReleaseIdUpdateSpec) that are not part of mdts_date. The connection parameters, the date log and the holidays handle are released on every path.
#include <Windows.h>
#include <stdio.h>
#include "datelog.h"
static BOOL MakeFileTime(WORD wYear, WORD wMonth, WORD wDay, PFILETIME pFt)
{
SYSTEMTIME st = { 0 };
st.wYear = wYear;
st.wMonth = wMonth;
st.wDay = wDay;
return SystemTimeToFileTime(&st, pFt);
}
/* Creates a holidays handle and loads it with the range [first, last]. */
static HRESULT BuildHolidays(PFILETIME pFirst, PFILETIME pLast, HOLIDAYS_HANDLE *phHolidays)
{
HRESULT hr = S_OK;
HOLIDAYS_HANDLE hHolidays = NULL;
DATE_ITEM_HANDLE hFirst = NULL;
DATE_ITEM_HANDLE hLast = NULL;
*phHolidays = NULL;
hr = InitializeDateLogHandle(NULL, DATE_LOG_MAX, NULL, &hHolidays, HandleIsForHolidays);
if (FAILED(hr)) goto cleanup;
hr = InitializeDateItem(hHolidays, pFirst, NULL, &hFirst);
if (FAILED(hr)) goto cleanup;
hr = InitializeDateItem(hHolidays, pLast, NULL, &hLast);
if (FAILED(hr)) goto cleanup;
hr = LoadDateLogHandle(NULL, hFirst, hLast, hHolidays, NULL, 0);
if (FAILED(hr)) goto cleanup;
*phHolidays = hHolidays;
hHolidays = NULL;
cleanup:
FreeDateItem(hLast);
FreeDateItem(hFirst);
if (hHolidays != NULL) {
(void)CloseDateLogHandle(hHolidays);
}
return hr;
}
/* Creates a date log or range handle of the given type and generates [first, last] into it. */
static HRESULT BuildCollection(HOLIDAYS_HANDLE hHolidays, DateHandleType type,
PFILETIME pFirst, PFILETIME pLast, DATE_LOG_HANDLE *phCollection)
{
HRESULT hr = S_OK;
DATE_LOG_HANDLE hCollection = NULL;
DATE_ITEM_HANDLE hFirst = NULL;
DATE_ITEM_HANDLE hLast = NULL;
*phCollection = NULL;
hr = InitializeDateLogHandle(NULL, DATE_LOG_MAX, hHolidays, &hCollection, type);
if (FAILED(hr)) goto cleanup;
hr = InitializeDateItem(hHolidays, pFirst, NULL, &hFirst);
if (FAILED(hr)) goto cleanup;
hr = InitializeDateItem(hHolidays, pLast, NULL, &hLast);
if (FAILED(hr)) goto cleanup;
hr = LoadDateLogHandle(NULL, hFirst, hLast, hCollection, NULL, 0);
if (FAILED(hr)) goto cleanup;
*phCollection = hCollection;
hCollection = NULL;
cleanup:
FreeDateItem(hLast);
FreeDateItem(hFirst);
if (hCollection != NULL) {
(void)CloseDateLogHandle(hCollection);
}
return hr;
}
/* Reserves connection slot 1 on the log and connects it using the parameters. */
static HRESULT ConnectLog(DATE_LOG_HANDLE hDateLog, PODBC_CONNECT_PARAMS pParams)
{
HRESULT hr = InitializeDateLogHandleOdbc(hDateLog, DATE_LOG_ODBC_1);
if (FAILED(hr)) {
return hr;
}
return ConnectDateLogHandleToOdbcDatabase(hDateLog, pParams, DATE_LOG_ODBC_1);
}
/* Application-supplied helpers (not part of mdts_date). PrepareIdUpdateSpec fills
pBulk->pIdUpdateSpec with the target table(s), id column and correlation key for your schema;
ReleaseIdUpdateSpec frees whatever PrepareIdUpdateSpec attached. */
extern HRESULT PrepareIdUpdateSpec(PSQL_DATE_BULK pBulk);
extern void ReleaseIdUpdateSpec(PSQL_DATE_BULK pBulk);
int main(void)
{
int exitCode = 1;
HRESULT hr = S_OK;
FILETIME ftFirst = { 0 };
FILETIME ftLast = { 0 };
HOLIDAYS_HANDLE hHolidays = NULL;
DATE_LOG_HANDLE hDateLog = NULL;
PODBC_CONNECT_PARAMS pParams = NULL;
SQL_DATE_BULK bulk;
BOOL bSpecReady = FALSE;
ZeroMemory(&bulk, sizeof(bulk));
if (!MakeFileTime(2025, 1, 2, &ftFirst) || !MakeFileTime(2025, 3, 31, &ftLast)) {
printf("Invalid calendar date.\n");
return 1;
}
hr = BuildHolidays(&ftFirst, &ftLast, &hHolidays);
if (FAILED(hr)) {
printf("BuildHolidays failed: 0x%08lX\n", (unsigned long)hr);
goto cleanup;
}
hr = BuildCollection(hHolidays, HandleIsForDatelog, &ftFirst, &ftLast, &hDateLog);
if (FAILED(hr)) {
printf("BuildCollection failed: 0x%08lX\n", (unsigned long)hr);
goto cleanup;
}
/* Build the connection string. */
hr = InitializeOdbcConnectParams(&pParams);
if (FAILED(hr)) {
printf("InitializeOdbcConnectParams failed: 0x%08lX\n", (unsigned long)hr);
goto cleanup;
}
hr = pParams->AddParam(pParams, "Driver", "{ODBC Driver 17 for SQL Server}");
if (SUCCEEDED(hr)) hr = pParams->AddParam(pParams, "Server", "localhost\\SQLEXPRESS");
if (SUCCEEDED(hr)) hr = pParams->AddParam(pParams, "Database", "DateDatabase");
if (SUCCEEDED(hr)) hr = pParams->AddParam(pParams, "Trusted_Connection", "yes");
if (FAILED(hr)) {
printf("AddParam failed: 0x%08lX\n", (unsigned long)hr);
goto cleanup;
}
hr = ConnectLog(hDateLog, pParams);
if (FAILED(hr)) {
printf("Connecting to the database failed: 0x%08lX\n", (unsigned long)hr);
goto cleanup;
}
/* The connection does not retain the parameters, so they can be released now. */
(void)FreeOdbcConnectParams(pParams);
pParams = NULL;
hr = PrepareIdUpdateSpec(&bulk);
if (FAILED(hr)) {
printf("Preparing the id update specification failed: 0x%08lX\n", (unsigned long)hr);
goto cleanup;
}
bSpecReady = TRUE;
/* dwFlags is reserved. The call runs in its own transaction and rolls back on failure. */
hr = ReplaceDateLogHandleIdentifiers(hDateLog, DATE_LOG_ODBC_1, &bulk, 0);
if (FAILED(hr)) {
if (hr == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)) {
printf("A required argument or the id update specification is invalid.\n");
} else if (hr == ERR_INVALID_DATELOG_HANDLE) {
printf("The handle is not a date log, or its length is 0 or too large.\n");
} else {
printf("ReplaceDateLogHandleIdentifiers failed (rolled back): 0x%08lX\n", (unsigned long)hr);
}
goto cleanup;
}
printf("Identifiers replaced and committed.\n");
exitCode = 0;
cleanup:
if (bSpecReady) {
ReleaseIdUpdateSpec(&bulk);
}
if (pParams != NULL) {
(void)FreeOdbcConnectParams(pParams);
}
if (hDateLog != NULL) {
(void)CloseDateLogHandle(hDateLog);
}
if (hHolidays != NULL) {
(void)CloseDateLogHandle(hHolidays);
}
return exitCode;
}