teradatasql
Version:
Teradata SQL Driver for Node.js
919 lines • 47.5 kB
JavaScript
"use strict";
// Copyright 2025 by Teradata Corporation. All Rights Reserved.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TeradataConnection = exports.getPackageVersion = void 0;
// Get the Teradata Native Binding
const koffi = __importStar(require("koffi"));
/* eslint-disable @typescript-eslint/no-require-imports */
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require("fs");
const path = require("path");
const os = require("os");
/* eslint-enable @typescript-eslint/no-require-imports */
/* eslint-enable @typescript-eslint/no-var-requires */
const teradata_cursor_1 = require("./teradata-cursor");
const teradata_logging_1 = require("./teradata-logging");
const teradata_exceptions_1 = require("./teradata-exceptions");
/* eslint-enable @typescript-eslint/naming-convention */
let g_sVersionNumber = "";
function getPackageVersion() {
if (g_sVersionNumber === "") {
const sPackageJsonPathName = path.resolve(__dirname, "..", "package.json");
try {
const packageJson = fs.readFileSync(sPackageJsonPathName);
JSON.parse(packageJson, (sKey, sValue) => {
if (sKey === "version") {
g_sVersionNumber = sValue;
}
});
}
catch (ex) {
throw new teradata_exceptions_1.OperationalError(ex.toString());
}
} // end if g_sVersionNumber
return g_sVersionNumber;
} // end getPackageVersion
exports.getPackageVersion = getPackageVersion;
function _safeReadFileSync(sPathName) {
try {
return fs.readFileSync(sPathName, { encoding: "utf-8" });
}
catch (error) {
return "";
}
} // end _safeReadFileSync
class TeradataConnection {
constructor() {
this.sVersionNumber = "";
this.lib = {};
this.bInAsyncConnect = false;
/**
* Registry of all cursors created from this connection.
*
* This Set tracks all active TeradataCursor instances associated with this
* connection to enable cross-object async operation validation. When any
* cursor has an async operation in progress (executeAsync/executemanyAsync),
* the connection must block ALL operations (both sync and async).
*
* Lifecycle:
* - Cursor registered: When cursor() is called and TeradataCursor is created
* - Cursor unregistered: When cursor.close() is called
*
* Purpose: Enforces "When an async operation is in progress, ALL other operations
* are blocked" rule across object boundaries (connection vs cursor).
*/
this.activeCursors = new Set(); // Set<TeradataCursor>, using 'any' to avoid circular dependency
if (!["aarch64", "arm64", "x64"].includes(process.arch)) {
throw new Error("This package requires 64-bit Node.js. 32-bit Node.js is not supported.");
}
this.sVersionNumber = getPackageVersion();
this.nId = ++TeradataConnection.nInstanceCount;
this.poolHandle = null;
this.logLevel = 0;
this.logger = new teradata_logging_1.TeradataLogging();
const sOSType = process.platform;
const bARM = ["aarch64", "arm64"].includes(process.arch);
const bFIPS = sOSType === "linux" && _safeReadFileSync("/proc/sys/crypto/fips_enabled").trim() === "1";
let sExtension = "";
if (sOSType === "win32") {
sExtension = "dll";
}
else if (sOSType === "darwin") {
sExtension = "dylib";
}
else if (bARM && bFIPS) {
sExtension = "arm.fips.so";
}
else if (bARM) {
sExtension = "arm.so";
}
else if (bFIPS) {
sExtension = "fips.so";
}
else {
sExtension = "so";
}
const sLibPathName = path.resolve(__dirname, "teradatasql." + sExtension);
// Load goside library
try {
this.gosideLib = koffi.load(`${sLibPathName}`);
}
catch (error) {
throw new teradata_exceptions_1.OperationalError(error.message);
}
// Load C functions
try {
this.lib.jsgoCreateConnection = this.gosideLib.func("void jsgoCreateConnection(uint64_t, _In_ char*, _In_ char*, _Out_ str*!, _Inout_ uint64_t*)");
this.lib.jsgoCloseConnection = this.gosideLib.func("void jsgoCloseConnection(uint64_t, uint64_t, _Out_ str*!)");
this.lib.jsgoCancelRequest = this.gosideLib.func("void jsgoCancelRequest(uint64_t, uint64_t, _Out_ str*!)");
this.lib.jsgoCreateRows = this.gosideLib.func("void jsgoCreateRows(uint64_t, uint64_t, _In_ char*, uint64_t, _In_ void*, _Out_ str*!, _Inout_ uint64_t*)");
this.lib.jsgoFetchRow = this.gosideLib.func("void jsgoFetchRow(uint64_t, uint64_t, _Out_ str*!, _Inout_ int64_t*, _Out_ void**!, int64_t)");
this.lib.jsgoResultMetaData = this.gosideLib.func("void jsgoResultMetaData(uint64_t, uint64_t, _Out_ str*!, _Inout_ uint64_t*, _Inout_ int*, _Out_ void**!)");
this.lib.jsgoNextResult = this.gosideLib.func("void jsgoNextResult(uint64_t, uint64_t, _Out_ str*!, _Inout_ char*)");
this.lib.jsgoCloseRows = this.gosideLib.func("void jsgoCloseRows(uint64_t, uint64_t, _Out_ str*!)");
this.lib.jsgoAsyncCreateRows = this.gosideLib.func("void jsgoAsyncCreateRows(uint64_t, uint64_t, _In_ char*, uint64_t, _In_ void*, _Out_ str*!, _Inout_ uint64_t*)");
this.lib.jsgoPollRows = this.gosideLib.func("void jsgoPollRows(uint64_t, uint64_t, _Out_ str*!, _Inout_ uint64_t*)");
this.lib.jsgoAsyncCreateConnection = this.gosideLib.func("void jsgoAsyncCreateConnection(uint64_t, _In_ char*, _In_ char*, _Out_ str*!, _Inout_ uint64_t*)");
this.lib.jsgoAsyncCloseConnection = this.gosideLib.func("void jsgoAsyncCloseConnection(uint64_t, uint64_t, _Out_ str*!)");
this.lib.jsgoPollConnection = this.gosideLib.func("void jsgoPollConnection(uint64_t, uint64_t, _Out_ str*!, _Inout_ char*)");
}
catch (error) {
throw new teradata_exceptions_1.OperationalError(error.message);
}
}
// All DBAPI 2.0 methods
// https://www.python.org/dev/peps/pep-0249/#connection-objects
get Id() {
return this.nId;
}
get uLog() {
return this.logLevel;
}
get uPoolHandle() {
return this.poolHandle;
}
get isAsyncConnect() {
return this.bInAsyncConnect;
}
get connected() {
return this.poolHandle !== null;
}
cursor() {
// Block cursor creation during connectAsync/closeAsync operations
this._stopIfInAsyncConnect();
// Block new cursor creation if an existing cursor has async SQL execution in progress
if (this._hasActiveCursorAsyncOp()) {
throw new teradata_exceptions_1.ProgrammingError(TeradataConnection.ERROR_802_OTHER_CURSOR_ASYNC_IN_PROGRESS);
}
// Create new cursor and register it
const newCursor = new teradata_cursor_1.TeradataCursor(this, this.lib);
this._registerCursor(newCursor);
return newCursor;
}
nativeSQL(sSQL) {
this.logger.traceLog("> enter nativeSQL TeradataConnection");
try {
this._stopIfInAsyncConnect();
// cursor() checks _hasActiveCursorAsyncOp()
const cur = this.cursor();
try {
cur.execute("{fn teradata_nativesql}" + sSQL); // teradata_nativesql never produces a fake result set
return cur.fetchone()[0];
}
finally {
cur.close();
}
}
finally {
this.logger.traceLog("< leave nativeSQL TeradataConnection");
}
} // end nativeSQL
get autocommit() {
this.logger.traceLog("> enter autocommit getter");
try {
this._stopIfInAsyncConnect();
return this.nativeSQL("{fn teradata_autocommit}") === "true";
}
finally {
this.logger.traceLog("< leave autocommit getter");
}
} // end autocommit getter
set autocommit(value) {
this.logger.traceLog("> enter autocommit setter");
try {
this._stopIfInAsyncConnect();
this.nativeSQL("{fn teradata_autocommit_" + (value ? "on" : "off") + "}");
}
finally {
this.logger.traceLog("< leave autocommit setter");
}
} // end autocommit setter
connect(connectParams = {}, sJSON = "{}") {
this._stopIfInAsyncConnect();
const combinedConnectParams = this._prepareConnectionParams(connectParams, sJSON);
this.logLevel = combinedConnectParams.log ? Number(combinedConnectParams.log) : 0;
this.logger.setLogLevel(this.logLevel);
this.logger.traceLog("> enter connect TeradataConnection " + JSON.stringify(combinedConnectParams));
try {
let outputPtrPtr = [null];
let poolHandlePtr = [0];
this.lib.jsgoCreateConnection(this.uLog, this.sVersionNumber, JSON.stringify(combinedConnectParams), outputPtrPtr, poolHandlePtr);
const outputString = outputPtrPtr[0];
if (outputString) {
throw new teradata_exceptions_1.OperationalError(outputString);
}
this.poolHandle = poolHandlePtr[0];
}
finally {
this.logger.traceLog("< leave connect TeradataConnection");
}
}
close() {
this.logger.traceLog("> enter close TeradataConnection");
try {
this._stopIfInAsyncConnect();
let outputPtrPtr = [null];
this.lib.jsgoCloseConnection(this.uLog, this.poolHandle, outputPtrPtr);
const outputString = outputPtrPtr[0];
if (outputString) {
throw new teradata_exceptions_1.OperationalError(outputString);
}
// Set poolHandle to null after successful close
this.poolHandle = null;
}
finally {
this.logger.traceLog("< leave close TeradataConnection");
}
}
cancel() {
this.logger.traceLog("> enter cancel TeradataConnection");
try {
this._stopIfInAsyncConnect();
// Does NOT check _hasActiveCursorAsyncOp() to allow cancellation
// during cursor async operations (executeAsync/executemanyAsync)
let outputPtrPtr = [null];
this.lib.jsgoCancelRequest(this.uLog, this.poolHandle, outputPtrPtr);
const outputString = outputPtrPtr[0];
if (outputString) {
throw new teradata_exceptions_1.OperationalError(outputString);
}
}
finally {
this.logger.traceLog("< leave cancel TeradataConnection");
}
}
commit() {
this.logger.traceLog("> enter commit TeradataConnection");
try {
this._stopIfInAsyncConnect();
// cursor() checks _hasActiveCursorAsyncOp()
this.cursor().execute("{fn teradata_commit}");
}
finally {
this.logger.traceLog("< leave commit TeradataConnection");
}
}
rollback() {
this.logger.traceLog("> enter rollback TeradataConnection");
try {
this._stopIfInAsyncConnect();
// cursor() checks _hasActiveCursorAsyncOp()
this.cursor().execute("{fn teradata_rollback}");
}
finally {
this.logger.traceLog("< leave rollback TeradataConnection");
}
}
/**
* Asynchronously connects to the Teradata database server using non-blocking operations.
*
* @param connectParams - Connection parameters (host, user, password, etc.)
* @param sJSON - Additional parameters as JSON string (default: "{}")
* @returns Promise resolving when connection is established
* @throws {ProgrammingError} Error 803 - Connection async operation in progress
* @throws {ProgrammingError} Error 804 - Cursor async operation blocks connection operation
* @throws {OperationalError} Connection failed
*
* @example
* ```typescript
* await conn.connectAsync({ host: "whomooz", user: "guest", password: "please" });
* ```
*
* @remarks
* **Mutual Exclusion:** ALL new operations blocked while an async operation in progress.
*
* **Timeout:** Uses 5-minute timeout if logon_timeout=0 (default), otherwise uses logon_timeout + 5s buffer.
*
* **Polling:** Checks completion every 100ms until done or timeout.
*/
async connectAsync(connectParams = {}, sJSON = "{}") {
try {
// Block if has connection async operation in progress
this._stopIfInAsyncConnect();
// Block if any cursor has async operation in progress
if (this._hasActiveCursorAsyncOp()) {
throw new teradata_exceptions_1.ProgrammingError(TeradataConnection.ERROR_804_CURSOR_ASYNC_BLOCKS_CONNECTION);
}
// Prepare connection parameters using shared helper
const combinedConnectParams = this._prepareConnectionParams(connectParams, sJSON);
this.logLevel = combinedConnectParams.log ? Number(combinedConnectParams.log) : 0;
this.logger.setLogLevel(this.logLevel);
// Trace log moved here after logger is properly initialized with correct log level
this.logger.traceLog("> enter connectAsync TeradataConnection");
this.bInAsyncConnect = true; // async connect starts
const dStartTime = Date.now();
// Calculate effective client-side timeout based on logon_timeout parameter
// Two scenarios (undefined is treated as "0" per driver default behavior):
// 1. Not specified OR "0" -> Use 5-minute extended timeout (compromise)
// 2. Positive value -> Use specified value + 5s buffer
// Parse logon_timeout, treating undefined as 0 (driver default)
const logonTimeoutSeconds = combinedConnectParams.logon_timeout
? parseInt(combinedConnectParams.logon_timeout, 10)
: 0; // undefined defaults to 0 per README documentation
let effectiveTimeoutMs;
let timeoutReason;
if (logonTimeoutSeconds === 0) {
// Scenario 1: logon_timeout not specified or "0" - use 5-minute extended timeout
// This is a compromise: sync connect() waits indefinitely with default/"0",
// but async operations should always have a safety timeout
effectiveTimeoutMs = TeradataConnection.CONNECT_ASYNC_DEFAULT_TIMEOUT_MS;
timeoutReason = "extended (logon_timeout=0 default)";
}
else {
// Scenario 2: logon_timeout > 0 - honor user's timeout + 5s buffer
// Buffer ensures server-side timeout triggers first for cleaner errors
effectiveTimeoutMs = (logonTimeoutSeconds * 1000) + 5000;
timeoutReason = `user-specified (${logonTimeoutSeconds}s + 5s buffer)`;
}
this.logger.debugLog(`connectAsync timeout: ${effectiveTimeoutMs}ms [${timeoutReason}]`);
await this._createConnection(this.logLevel, this.sVersionNumber, JSON.stringify(combinedConnectParams));
let result;
let pollAttempts = 0;
while (true) {
pollAttempts++;
let elapsedTime = Date.now() - dStartTime;
// Client-side timeout protection: prevent infinite polling
// Timeout behavior depends on logon_timeout setting to respect user intent
if (elapsedTime > effectiveTimeoutMs) {
this.logger.debugLog(`connectAsync timeout after ${pollAttempts} polling attempts (${elapsedTime}ms) - handle ${this.poolHandle}`);
// Clear poolHandle on timeout to ensure clean state
const previousHandle = this.poolHandle;
this.poolHandle = null;
this.logger.debugLog(`connectAsync timeout - handle ${previousHandle} cleared`);
throw new teradata_exceptions_1.OperationalError(`Connection timeout: Operation took longer than ${effectiveTimeoutMs / 1000} seconds [${timeoutReason}]`);
}
try {
this.logger.traceLog(`_pollConnection TeradataConnection, bInAsyncConnect=` + this.bInAsyncConnect);
result = await this._pollConnection(this.logLevel, this.poolHandle);
this.logger.debugLog(`connectAsync _pollConnection attempt #${pollAttempts} returned ${result} (elapsed: ${elapsedTime}ms)`);
if (typeof result === "number") {
if (result !== 0) {
// Operation completed successfully
this.logger.debugLog(`connectAsync completed successfully with handle ${this.poolHandle} after ${pollAttempts} attempts (${elapsedTime}ms)`);
break;
}
else {
this.logger.debugLog(`connectAsync will retry _pollConnection after ${TeradataConnection.POLLING_INTERVAL_MS} ms (attempt #${pollAttempts}, elapsed: ${elapsedTime}ms)`);
await new Promise((resolve) => setTimeout(resolve, TeradataConnection.POLLING_INTERVAL_MS));
}
}
else {
// Clear poolHandle on connection failure
this.poolHandle = null;
this.logger.debugLog(`connectAsync error after ${pollAttempts} attempts (${elapsedTime}ms): ${result}`);
throw new teradata_exceptions_1.OperationalError(`connectAsync error: ${result}`);
}
}
catch (error) {
// Clear poolHandle on connection failure to ensure clean state
const previousHandle = this.poolHandle;
this.poolHandle = null;
const errorStr = error.toString();
const errorCode = (0, teradata_exceptions_1.extractErrorCode)(errorStr);
elapsedTime = Date.now() - dStartTime;
// Extract error ID if present for logging
const errorMatch = errorStr.match(/^\[ERR-(\d+-\d+)\]/);
if (errorMatch) {
const errorId = errorMatch[0];
this.logger.debugLog(`connectAsync: operation_failed error_id=${errorId}, total_time=${elapsedTime}ms`);
}
else {
this.logger.debugLog(`connectAsync: operation_failed (no_correlation_id), total_time=${elapsedTime}ms`);
}
this.logger.debugLog(`connectAsync error [${errorCode}] after ${elapsedTime}ms - handle ${previousHandle} cleared: ${errorStr}`);
throw new teradata_exceptions_1.OperationalError(errorStr);
}
} // end infinite loop
this.logger.timingLog("connectAsync took " + (Date.now() - dStartTime) + " ms");
}
finally {
this.bInAsyncConnect = false; // async connect completes
this.logger.traceLog("< leave connectAsync TeradataConnection, bInAsyncConnect=" + this.bInAsyncConnect);
}
}
/**
* Asynchronously closes the connection using non-blocking operations.
*
* @returns Promise resolving when connection is closed
* @throws {ProgrammingError} Error 803 - Connection async operation already in progress
* @throws {ProgrammingError} Error 804 - Cursor async operation blocks connection operation
* @throws {OperationalError} Close operation failed
*
* @example
* ```typescript
* await conn.closeAsync();
* ```
*
* @remarks
* **Mutual Exclusion:** ALL operations blocked while async operation in progress.
*
* **Timeout:** Fixed 30-second timeout for close operations.
*
* **Polling:** Checks completion every 100ms until done or timeout.
*
* **Cleanup:** Sets poolHandle to null after successful close.
*/
async closeAsync() {
this.logger.traceLog("> enter closeAsync TeradataConnection, bInAsyncConnect=" + this.bInAsyncConnect);
try {
// Block if connection already has async operation in progress
this._stopIfInAsyncConnect();
// Block if any cursor has async operation in progress
if (this._hasActiveCursorAsyncOp()) {
throw new teradata_exceptions_1.ProgrammingError(TeradataConnection.ERROR_804_CURSOR_ASYNC_BLOCKS_CONNECTION);
}
this.bInAsyncConnect = true; // async close starts
const dStartTime = Date.now();
await this._closeConnection(this.logLevel, this.poolHandle);
let result;
let pollAttempts = 0;
while (true) {
pollAttempts++;
let elapsedTime = Date.now() - dStartTime;
// Client-side timeout protection: prevent infinite polling
if (elapsedTime > TeradataConnection.CLOSE_ASYNC_TIMEOUT_MS) {
this.logger.debugLog(`closeAsync timeout after ${pollAttempts} polling attempts (${elapsedTime}ms) - handle ${this.poolHandle}`);
throw new teradata_exceptions_1.OperationalError(`Connection close timeout: Operation took longer than ${TeradataConnection.CLOSE_ASYNC_TIMEOUT_MS / 1000} seconds`);
}
try {
this.logger.traceLog(`_pollConnection TeradataConnection, bInAsyncConnect=` + this.bInAsyncConnect);
result = await this._pollConnection(this.logLevel, this.poolHandle);
this.logger.debugLog(`closeAsync _pollConnection attempt #${pollAttempts} returned ${result} (elapsed: ${elapsedTime}ms)`);
if (typeof result === "number") {
if (result !== 0) {
// Connection closed successfully
const closedHandle = this.poolHandle;
this.poolHandle = null;
this.logger.debugLog(`closeAsync completed successfully - handle ${closedHandle} closed after ${pollAttempts} attempts (${elapsedTime}ms)`);
break;
}
else {
this.logger.debugLog(`closeAsync will retry _pollConnection after ${TeradataConnection.POLLING_INTERVAL_MS} ms (attempt #${pollAttempts}, elapsed: ${elapsedTime}ms)`);
await new Promise((resolve) => setTimeout(resolve, TeradataConnection.POLLING_INTERVAL_MS));
}
}
else {
const errorHandle = this.poolHandle;
this.logger.debugLog(`closeAsync error after ${pollAttempts} attempts (${elapsedTime}ms) - handle ${errorHandle}: ${result}`);
throw new teradata_exceptions_1.OperationalError(`closeAsync error: ${result}`);
}
}
catch (error) {
const errorStr = error.toString();
const errorCode = (0, teradata_exceptions_1.extractErrorCode)(errorStr);
elapsedTime = Date.now() - dStartTime;
this.logger.debugLog(`closeAsync error [${errorCode}] after ${elapsedTime}ms, attempt #${pollAttempts} - handle ${this.poolHandle}: ${errorStr}`);
throw new teradata_exceptions_1.OperationalError(errorStr);
}
} // end infinite loop
this.logger.timingLog("closeAsync took " + (Date.now() - dStartTime) + " ms");
}
finally {
this.bInAsyncConnect = false; // async close completes
const closedHandle = this.poolHandle;
this.logger.traceLog("< leave closeAsync TeradataConnection, bInAsyncConnect=" + this.bInAsyncConnect + ", handle=" + closedHandle);
}
}
/**
* Prepares connection parameters by merging user params with system metadata.
*
* This helper method consolidates common parameter preparation logic shared by
* both connect() and connectAsync(). It handles:
* - JSON kwargs parsing and boolean-to-string conversion
* - Stack trace extraction and formatting
* - Client metadata injection (kind, VM, OS, stack, extras)
*
* **Metadata Added:**
* - `client_kind`: "S" for Node.js
* - `client_vmname`: Node.js version
* - `client_osname`: OS version and architecture
* - `client_stack`: Formatted call stack
* - `client_extra`: Node.js version and timezone
*
* @param connectParams - User-provided connection parameters
* @param sJSON - Additional parameters as JSON string
* @returns Merged connection parameters with system metadata
* @private
*/
_prepareConnectionParams(connectParams, sJSON) {
const kwargs = { ...JSON.parse(sJSON) };
Object.keys(kwargs).forEach((key) => {
if (kwargs[key] === true) {
kwargs[key] = "true";
}
else if (kwargs[key] === false) {
kwargs[key] = "false";
}
});
const traceback = Error().stack;
let sFrame;
let sFrames = [];
let listFrames = [];
if (traceback) {
traceback.replace(path.sep, "/");
sFrames = traceback.split("\n at").slice(1);
for (const fr of sFrames) {
sFrame = fr.split("/").pop();
if (sFrame) {
sFrame = sFrame.split(":")[0];
if (!listFrames.includes(sFrame)) {
listFrames.push(sFrame);
}
}
}
}
if (process.platform === "win32") {
// Windows
listFrames = listFrames.map((frame) => frame.split(" (C")[0].trim());
}
kwargs.client_kind = "S"; // G = Go, P = Python, R = R, S = Node.js
kwargs.client_vmname = "Node.js " + process.version;
kwargs.client_osname = os.version().split(":")[0] + " " + os.arch();
kwargs.client_stack = listFrames.join(" ");
kwargs.client_extra = "NODEJS=" + process.version + ";TZ=" + Intl.DateTimeFormat().resolvedOptions().timeZone + ";"; // must be semicolon-terminated
return { ...connectParams, ...kwargs };
}
/**
* Validates if an async connect/close operation is currently in progress.
*
* This method prevents race conditions by ensuring only one async operation
* can be active at a time. It's called at the beginning of synchronous and
* asynchronous methods to enforce mutual exclusion.
*
*
* @throws {ProgrammingError} Error 803 - If bInAsyncConnect is true, indicating
* an async connection operation is already in progress
*
* @private
*/
_stopIfInAsyncConnect() {
if (this.bInAsyncConnect) {
this.logger.debugLog(`_stopIfInAsyncConnect: async operation already in progress - rejecting new operation (handle: ${this.poolHandle})`);
throw new teradata_exceptions_1.ProgrammingError(TeradataConnection.ERROR_803_CONNECTION_ASYNC_IN_PROGRESS);
}
this.logger.debugLog(`_stopIfInAsyncConnect: no async operation in progress - allowing new operation (handle: ${this.poolHandle})`);
}
/**
* Registers a cursor in the active cursor registry.
*
* This method adds a TeradataCursor to the activeCursors Set, enabling
* cross-object async operation validation. Once registered, the connection
* can check if any cursor has an async operation in progress.
*
* **Lifecycle Integration:**
* - Called by: cursor() method when creating new TeradataCursor
* - Paired with: _unregisterCursor() called by cursor.close()
*
* **Purpose:**
* - Enables _hasActiveCursorAsyncOp() to detect cursor async operations
* - Enforces "When async operation is in progress, ALL other operations are blocked" rule
*
* @param cursor - The TeradataCursor instance to register
* @private
*/
_registerCursor(cursor) {
this.activeCursors.add(cursor);
this.logger.debugLog(`_registerCursor: registered cursor (total active: ${this.activeCursors.size})`);
}
/**
* Unregisters a cursor from the active cursor registry.
*
* This method removes a TeradataCursor from the activeCursors Set when
* the cursor is closed. This cleanup ensures accurate async operation
* validation state.
*
* **Lifecycle Integration:**
* - Called by: TeradataCursor.close() method
* - Paired with: _registerCursor() called by cursor()
*
* **Purpose:**
* - Maintains accurate cursor registry state
* - Prevents memory leaks from closed cursors
*
* @param cursor - The TeradataCursor instance to unregister
* @internal - Public for TeradataCursor access, but not part of public API
*/
_unregisterCursor(cursor) {
this.activeCursors.delete(cursor);
this.logger.debugLog(`_unregisterCursor: unregistered cursor (total active: ${this.activeCursors.size})`);
}
/**
* Checks if any active cursor has an async operation in progress.
*
* This method provides cross-object async operation validation by checking
* if any registered cursor is currently executing an async SQL operation
* (executeAsync/executemanyAsync).
*
* **Validation Logic:**
* - Iterates through all cursors in activeCursors Set
* - Optionally excludes a specific cursor from the check (used by that cursor to check others)
* - Checks each cursor's bInAsyncExecute flag
* - Returns true if ANY cursor (except excluded one) has async operation in progress
*
* **Usage:**
* - Called by: connectAsync() and closeAsync() before starting operation
* - Called by: cursor() to check if any cursor has async operation before creating new cursor
* - Purpose: Enforces "When async operation is in progress, ALL other operations are blocked"
*
* @param excludeCursor - Optional cursor to exclude from the check (typically the caller)
* @returns True if any cursor (except excluded) has async operation in progress, false otherwise
* @private
*/
_hasActiveCursorAsyncOp(excludeCursor) {
for (const cursor of this.activeCursors) {
// Skip the excluded cursor if provided
if (excludeCursor && cursor === excludeCursor) {
continue;
}
if (cursor.isAsyncExec) {
this.logger.debugLog(`_hasActiveCursorAsyncOp: found cursor ${cursor.Id} with async operation in progress`);
return true;
}
}
return false;
}
/**
* Initiates asynchronous connection creation on the Go side.
*
* This method starts the async connection process by calling the Go-side
* jsgoAsyncCreateConnection function. It handles immediate errors but does
* not wait for connection completion - that's handled by polling.
*
* **Go-side Integration:**
* - Calls jsgoAsyncCreateConnection with log level, version, and params
* - Handles immediate validation errors from Go side
* - Does not block waiting for connection establishment
*
* **Error Handling:**
* - Throws OperationalError for immediate Go-side failures
* - Provides timing logs for performance monitoring
* - Preserves original Go error messages
*
* @param nLog - Logging level for Go-side operations
* @param sVersionNumber - Driver version number
* @param sConnectParams - JSON-serialized connection parameters
* @throws {OperationalError} For immediate connection creation failures
*
* @private
*/
async _createConnection(nLog, sVersionNumber, sConnectParams) {
this.logger.traceLog("> enter _createConnection TeradataConnection, bInAsyncConnect=" + this.bInAsyncConnect);
this.logger.debugLog(`_createConnection called with logLevel=${nLog}, version=${sVersionNumber}, paramsLength=${sConnectParams.length}`);
let dStartTimeBeginning = Date.now();
let outputPtrPtr = [null];
let poolHandlePtr = [0];
this.logger.debugLog(`_createConnection calling Go-side jsgoAsyncCreateConnection`);
this.lib.jsgoAsyncCreateConnection(nLog, sVersionNumber, sConnectParams, outputPtrPtr, poolHandlePtr);
const outputString = outputPtrPtr[0];
if (outputString) {
throw new teradata_exceptions_1.OperationalError(outputString);
}
// Set the initial connection handle for polling (matches synchronous connect behavior)
this.poolHandle = poolHandlePtr[0];
this.logger.debugLog(`_createConnection received initial handle: ${this.poolHandle}`);
this.logger.timingLog("_createConnection took " + (Date.now() - dStartTimeBeginning) + " ms");
this.logger.traceLog("> leave _createConnection TeradataConnection");
}
/**
* Initiates asynchronous connection closure on the Go side.
*
* This method starts the async connection close process by calling the Go-side
* jsgoAsyncCloseConnection function. It validates the handle first and handles
* immediate errors, but does not wait for close completion.
*
* **Pre-conditions:**
* - Connection handle should be valid (Go-side will validate)
* - No other async operations should be in progress
*
* **Go-side Integration:**
* - Calls jsgoAsyncCloseConnection with log level and pool handle
* - Go-side validates handle and provides proper error messages
* - Does not block waiting for connection closure completion
*
* **Error Handling:**
* - Go-side performs comprehensive handle validation
* - Throws OperationalError for immediate Go-side failures
* - Provides timing logs for performance monitoring
* - Preserves original Go error messages
*
* @param nLog - Logging level for Go-side operations
* @param nPoolHandle - Connection pool handle to close (Go-side validates)
* @throws {OperationalError} For immediate close failures from Go-side validation
*
* @private
*/
async _closeConnection(nLog, nPoolHandle) {
this.logger.traceLog("> enter _closeConnection TeradataConnection, bInAsyncConnect=" + this.bInAsyncConnect);
this.logger.debugLog(`_closeConnection called with logLevel=${nLog}, handle=${nPoolHandle}`);
let dStartTimeBeginning = Date.now();
let outputPtrPtr = [null];
this.logger.debugLog(`_closeConnection calling Go-side jsgoAsyncCloseConnection for handle=${nPoolHandle}`);
this.lib.jsgoAsyncCloseConnection(nLog, nPoolHandle, outputPtrPtr);
const outputString = outputPtrPtr[0];
if (outputString) {
throw new teradata_exceptions_1.OperationalError(outputString);
}
this.logger.timingLog("_closeConnection took " + (Date.now() - dStartTimeBeginning) + " ms");
this.logger.traceLog("> leave _closeConnection TeradataConnection");
}
/**
* Polls the Go side for async connection operation status.
*
* This method directly calls the Go-side jsgoPollConnection function to check
* if an async operation (connect or close) has completed. It implements the
* compatibility fix for the new Go pbReady flag implementation.
*
* **Go-side Compatibility:**
* - Updated for new Go implementation using *pbReady = C.char(1) for completion
* - Changed from string-based status to numeric flag (0 = executing, 1 = complete)
* - Maintains compatibility with enhanced Go-side error handling
*
* **Return Values:**
* - `number`: 0 = operation still executing, non-zero = operation complete
* - `string`: Error message from Go side for immediate failure
*
* **Error Handling:**
* - Go-side validates connection handle with proper error codes
* - Returns error string for Go-side failures
* - Provides comprehensive timing logs for performance analysis
* - Preserves original Go error messages for debugging
*
* **Performance Monitoring:**
* - Logs timing for each poll operation
* - Tracks polling frequency and duration
* - Enables performance optimization analysis
*
* @param nLog - Logging level for Go-side operations
* @param nPoolHandle - Connection pool handle to poll (Go-side validates)
* @returns Operation status as number (0 = executing, non-zero = complete)
* or error string for immediate failures
*
* @private
*/
_jsgoPollConnection(nLog, nPoolHandle) {
this.logger.traceLog("> enter _jsgoPollConnection TeradataConnection, bInAsyncConnect=" + this.bInAsyncConnect);
this.logger.debugLog(`_jsgoPollConnection polling handle=${nPoolHandle}`);
let dStartTime = Date.now();
let outputPtrPtr = [null];
let statusPtr = [0]; // Initialize as number array for pbReady flag
this.lib.jsgoPollConnection(nLog, nPoolHandle, outputPtrPtr, statusPtr);
const outputString = outputPtrPtr[0];
if (outputString) {
// Detect and log error correlation ID
const errorMatch = outputString.match(/^\[ERR-(\d+-\d+)\]/);
if (errorMatch) {
const errorId = errorMatch[0]; // Full [ERR-xxx-yyy]
this.logger.debugLog(`_jsgoPollConnection: received_error error_id=${errorId} from Go layer for handle=${nPoolHandle}`);
}
this.logger.debugLog("_jsgoPollConnection output=" + outputString);
this.logger.timingLog("_jsgoPollConnection took " + (Date.now() - dStartTime) + " ms");
return outputString;
}
this.logger.timingLog("_jsgoPollConnection took " + (Date.now() - dStartTime) + " ms");
const readyFlag = statusPtr[0];
this.logger.debugLog(`_jsgoPollConnection received readyFlag=${readyFlag} for handle=${nPoolHandle}`);
if (readyFlag !== 0) { // 1 means operation complete, 0 means still executing
this.logger.traceLog(`> leave _jsgoPollConnection TeradataConnection, connection operation complete`);
this.logger.debugLog(`_jsgoPollConnection operation completed for handle=${nPoolHandle}`);
return nPoolHandle || 1;
}
this.logger.traceLog("> leave _jsgoPollConnection TeradataConnection, the connection operation is still executing.");
this.logger.debugLog(`_jsgoPollConnection operation still executing for handle=${nPoolHandle}`);
return 0; // Connection operation is still executing
}
/**
* Promise wrapper for async connection polling operations.
*
* This method wraps the synchronous _jsgoPollConnection call in a Promise
* to integrate seamlessly with the async/await pattern used in connectAsync()
* and closeAsync(). It provides clean separation between sync Go calls and
* async JavaScript patterns.
*
* **Promise Resolution:**
* - **Resolves** with numeric result: 0 = operation not complete, non-zero = complete
* - **Rejects** with string result: error message from Go side
*
* **Integration Pattern:**
* - Used in while loops within async methods for non-blocking polling
* - Enables proper error propagation through Promise rejection
* - Maintains clean async/await syntax in calling methods
*
* **Design Benefits:**
* - Clean separation of sync Go calls from async JavaScript patterns
* - Consistent error handling through Promise rejection
* - Enables timeout handling in calling methods
* - Provides foundation for future polling enhancements
*
* @param nLog - Logging level for Go-side operations
* @param nPoolHandle - Connection pool handle to poll
* @returns Promise<number> - 0 for operation not complete, non-zero for complete
* @rejects {string} Error message from Go side
*
* @private
*/
_pollConnection(nLog, nPoolHandle) {
return new Promise((resolve, reject) => {
this.logger.traceLog("_pollConnection TeradataConnection, bInAsyncConnect=" + this.bInAsyncConnect);
const result = this._jsgoPollConnection(nLog, nPoolHandle);
if (typeof result === "number") {
resolve(result); // 0: operation not complete, non-zero: operation complete
}
else {
reject(result); // error text
}
});
}
}
exports.TeradataConnection = TeradataConnection;
TeradataConnection.nInstanceCount = 0;
/**
* Constants for async operation timing and control.
*
* These constants centralize timing parameters and control flags used across
* all async connection operations, ensuring consistency and maintainability.
*/
/**
* Default timeout duration (in milliseconds) for connectAsync() when logon_timeout is "0" or not specified.
*
* This constant is used exclusively by connectAsync() as a safety timeout when the logon_timeout
* parameter is "0" (default) or explicitly set to "0". While synchronous connect() waits indefinitely
* with logon_timeout="0", async operations should always have a safety timeout. The 5-minute duration
* provides a reasonable compromise between:
* - Honoring user intent of "long wait acceptable" (implied by logon_timeout="0")
* - Preventing infinite hangs in async operations
* - Allowing slow connections to succeed
*
* Note: When logon_timeout is set to a positive value, connectAsync() uses that value plus a 5-second
* buffer instead of this constant.
*/
TeradataConnection.CONNECT_ASYNC_DEFAULT_TIMEOUT_MS = 300000; // 5 minutes for connectAsync() with logon_timeout="0"
/**
* Timeout duration (in milliseconds) for closeAsync() operations.
*
* This constant is used exclusively by closeAsync() to provide client-side timeout protection.
* Connection close operations typically complete quickly, so 30 seconds is generous enough
* to handle network delays while preventing indefinite hangs. Note: connectAsync() uses
* a different timeout strategy based on the logon_timeout parameter.
*/
TeradataConnection.CLOSE_ASYNC_TIMEOUT_MS = 30000; // 30 seconds timeout for closeAsync()
/**
* Polling interval (in milliseconds) for connectAsync() and closeAsync() operations.
*
* This constant controls how frequently the client checks Go-side operation status
* during async connection operations. The 100ms interval provides a good balance between:
* - Responsiveness: Quick detection of operation completion
* - CPU efficiency: Avoiding excessive polling overhead
* - Network efficiency: Not overwhelming the Go-side with status requests
*/
TeradataConnection.POLLING_INTERVAL_MS = 100; // 100ms polling interval for connection async operations
/**
* Error message constants for async operation enforcement.
*
* These constants centralize error messages to ensure consistency across methods
* and simplify maintenance. They enforce mutual exclusion during async operations.
* Made public to allow teradata-cursor.ts to reference them.
*/
/**
* Error message when attempting cursor operations while this cursor's own async operation is in progress.
* Used by: TeradataCursor._stopIfInAsyncExecute()
*/
TeradataConnection.ERROR_801_CURSOR_SELF_ASYNC_IN_PROGRESS = "[Error 801] [SQLState HY000] Cannot perform operation while cursor async operation (executeAsync/executemanyAsync) is in progress on this cursor.";
/**
* Error message when attempting operations while another cursor's async operation is in progress.
* Used by: cursor(), TeradataCursor._stopIfInAsyncExecute()
*/
TeradataConnection.ERROR_802_OTHER_CURSOR_ASYNC_IN_PROGRESS = "[Error 802] [SQLState HY000] Cannot perform operation while cursor async operation (executeAsync/executemanyAsync) is in progress on another cursor on this connection";
/**
* Error message when attempting new operations while connection async operation is in progress.
* Used by: _stopIfInAsyncConnect(), TeradataCursor._stopIfInAsyncExecute()
*/
TeradataConnection.ERROR_803_CONNECTION_ASYNC_IN_PROGRESS = "[Error 803] [SQLState HY000] Cannot perform new operation while connection async operation (connectAsync/closeAsync) is in progress.";
/**
* Error message when attempting connection async operations while cursor async operation is in progress.
* Used by: connectAsync(), closeAsync()
*/
TeradataConnection.ERROR_804_CURSOR_ASYNC_BLOCKS_CONNECTION = "[Error 804] [SQLState HY000] Cannot start new connection async operation (connectAsync/closeAsync) while cursor async operation (executeAsync/executemanyAsync) is in progress.";
//# sourceMappingURL=teradata-connection.js.map