ReplaceDateLogHandleIdentifiers

Replace Ids in an ODBC target table, order-aligned with the date log core, inside a mandatory transaction

Overview

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.

Key Features

Warning: This function manages its own transaction. Do not call it while you are in the middle of your own transaction on the same connection level, and do not rely on the connection’s autocommit setting having its previous value while the call is running.

Function Signature


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.

Parameters

hDateLogHandle

[In] DATE_LOG_HANDLE

The date log whose core supplies the identifiers.

  • Valid values: must be a 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.
  • NULL: not allowed (HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)).
  • Ownership / lifetime: remains owned by the caller; the connection(s) belong to the handle and are closed by CloseDateLogHandle.
OdbcLevel

[In] DATE_LOG_ODBC_LEVEL

Which of the handle’s ODBC connections to use (DATE_LOG_ODBC_1 through DATE_LOG_ODBC_4).

pBulk

[In, Out] PSQL_DATE_BULK

Describes the replacement to perform.

  • Direction: in/out; the library may update the structure.
  • Valid values: pBulk->pIdUpdateSpec must pass IsValidOdbcBulkIdUpdateSpec(). It specifies the target table(s), the id column and the correlation key.
  • NULL: not allowed (HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)); an invalid pIdUpdateSpec yields the same error.
  • Ownership / lifetime: owned by the caller. Any memory the caller attached to the structure (for example the id update specification) remains the caller’s to release after the call. See SQL_DATE_BULK.
dwFlags

[In] DWORD

Reserved; unused. Pass 0.

Return Values

ValueMeaning
S_OKIdentifiers 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_HANDLEThe 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 HRESULTPropagated 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.

Behavior and Operations

Begin Transaction→Stage + Bulk Insert→Replace Target Ids→Swap File→Commit

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.

Remarks

Thread Safety

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.

Handle Lifetime and Ownership

Ordering and Related Functions

See Also

Sample Code

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