From 1298ccc62a10663839af782ee8db06eb6878a9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Rend=C3=B3n=20V=C3=A9lez?= <22383219+rendonvelez@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:24:52 -0500 Subject: [PATCH 1/4] feat: add connection.cancel() and statement.cancel() via SQLCancel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #434. Add the ability to cancel in-flight operations: - connection.cancel(): ODBCConnection keeps a registry of statement handles for in-flight operations (std::set guarded by a uv_mutex_t). QueryAsyncWorker and CallProcedureAsyncWorker register their handle right after allocating it and deregister it in their destructors. cancel() walks the registry and calls SQLCancel on each handle. - statement.cancel(): calls SQLCancel directly on the statement's handle, which lives for the whole lifetime of the Statement object. The cancelled operations return with SQLSTATE HY008 ("Operation canceled"), so their promises reject (or their callbacks receive an error) and callers can distinguish cancellation from other failures. SQLCancel is invoked synchronously on the main thread rather than through an AsyncWorker: cancellation is most needed precisely when the libuv thread pool is saturated by the operations being cancelled, so queueing the cancel behind them could delay it indefinitely. Calling SQLCancel from a different thread than the one running the statement is the documented multithreaded-cancel use of the function. StatementData.hstmt is now initialized to SQL_NULL_HANDLE so that cleanup paths that run before the handle is allocated read a defined value. Signed-off-by: Gustavo Rendón Vélez <22383219+rendonvelez@users.noreply.github.com> --- README.md | 124 ++++++++++++++++++++++++++++++++++++++++ lib/Connection.js | 38 ++++++++++++ lib/Statement.js | 35 +++++++++++- lib/odbc.d.ts | 8 +++ src/odbc.h | 2 +- src/odbc_connection.cpp | 92 +++++++++++++++++++++++++++++ src/odbc_connection.h | 11 ++++ src/odbc_statement.cpp | 57 ++++++++++++++++++ src/odbc_statement.h | 1 + 9 files changed, 366 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7559fa1e..de87ab8a 100644 --- a/README.md +++ b/README.md @@ -640,6 +640,64 @@ odbc.connect(`${process.env.CONNECTION_STRING}`, (error, connection) => { --- +### `.cancel(callback?)` + +Cancels all operations currently running on the connection (queries and procedure calls) by calling `SQLCancel` on their statement handles. The cancelled operations return with SQLSTATE HY008 ("Operation canceled"), so their promises reject (or their callbacks are called with an error). If no operations are running, `.cancel` is a no-op. + +#### Parameters: +* **callback?**: The function called when `.cancel` has finished execution. If no callback function is given, `.cancel` will return a native JavaScript `Promise`. Callback signature is: + * error: The error that occured in execution, or `null` if no error + +#### Examples: + +**Promises** + +```javascript +const odbc = require('odbc'); + +// can only use await keyword in an async function +async function cancelExample() { + const connection = await odbc.connect(`${process.env.CONNECTION_STRING}`); + + // cancel the query if it is still running after 10 seconds + const timer = setTimeout(() => { + connection.cancel(); + }, 10000); + + try { + const result = await connection.query('SELECT * FROM HUGE_TABLE'); + console.log(result); + } catch (error) { + // if cancelled, error contains an odbcError with state 'HY008' + } finally { + clearTimeout(timer); + } +} + +cancelExample(); +``` + +**Callbacks** + +```javascript +const odbc = require('odbc'); + +odbc.connect(`${process.env.CONNECTION_STRING}`, (error, connection) => { + const timer = setTimeout(() => { + connection.cancel((cancelError) => { + if (cancelError) { return; } // handle + }); + }, 10000); + + connection.query('SELECT * FROM HUGE_TABLE', (queryError, result) => { + clearTimeout(timer); + // if cancelled, queryError contains an odbcError with state 'HY008' + }); +}); +``` + +--- + ### `.close(callback?)` Closes an open connection. Any transactions on the connection that have not been ended will be rolledback. @@ -1027,6 +1085,72 @@ odbc.connect(`${process.env.CONNECTION_STRING}`, (error, connection) => { --- +### `.cancel(callback?)` + +Cancels any operation currently running on the Statement (e.g. a long `.execute()`) by calling `SQLCancel` on its handle. The cancelled operation returns with SQLSTATE HY008 ("Operation canceled"). + +#### Parameters: +* **callback?**: The function called when `.cancel` has finished execution. If no callback function is given, `.cancel` will return a native JavaScript `Promise`. Callback signature is: + * error: The error that occured in execution, or `null` if no error + +#### Examples: + +**Promises** + +```javascript +const odbc = require('odbc'); + +// can only use await keyword in an async function +async function cancelExample() { + const connection = await odbc.connect(`${process.env.CONNECTION_STRING}`); + const statement = await connection.createStatement(); + await statement.prepare('SELECT * FROM HUGE_TABLE WHERE FIELD_1 = ?'); + await statement.bind([1]); + + // cancel the execution if it is still running after 10 seconds + const timer = setTimeout(() => { + statement.cancel(); + }, 10000); + + try { + const result = await statement.execute(); + console.log(result); + } catch (error) { + // if cancelled, error contains an odbcError with state 'HY008' + } finally { + clearTimeout(timer); + } +} + +cancelExample(); +``` + +**Callbacks** + +```javascript +const odbc = require('odbc'); + +odbc.connect(`${process.env.CONNECTION_STRING}`, (error, connection) => { + connection.createStatement((error1, statement) => { + if (error1) { return; } // handle + statement.prepare('SELECT * FROM HUGE_TABLE', (error2) => { + if (error2) { return; } // handle + const timer = setTimeout(() => { + statement.cancel((cancelError) => { + if (cancelError) { return; } // handle + }); + }, 10000); + statement.execute((error3, result) => { + clearTimeout(timer); + // if cancelled, error3 contains an odbcError with state 'HY008' + }); + }); + }); +}); +``` + +--- + ### `.close(callback?)` Closes the Statement, freeing the statement handle. Running functions on the statement after closing will result in an error. diff --git a/lib/Connection.js b/lib/Connection.js index a9efd3a5..735849c6 100644 --- a/lib/Connection.js +++ b/lib/Connection.js @@ -295,6 +295,44 @@ class Connection { }); } + /** + * Cancels all operations currently running on this connection (queries and + * procedure calls) by calling SQLCancel on their statement handles. The + * cancelled operations return with SQLSTATE HY008 ("Operation canceled"). + * Asynchronous, can be used either with a callback function or a Promise. + * @param {function} [callback] - Callback function. If not passed, a Promise will be returned. + */ + cancel(callback = undefined) { + // type-checking + if (typeof callback !== 'function' && typeof callback !== 'undefined') { + throw new TypeError('[node-odbc]: Incorrect function signature for call to connection.cancel({function}[optional]).'); + } + + // promise... + if (callback === undefined) { + if (!this.odbcConnection) + { + throw new Error(Connection.CONNECTION_CLOSED_ERROR); + } + return new Promise((resolve, reject) => { + this.odbcConnection.cancel((error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + } + + // ...or callback + if (!this.odbcConnection) + { + return callback(new Error(Connection.CONNECTION_CLOSED_ERROR)); + } + return this.odbcConnection.cancel((error) => callback(error)); + } + // TODO: Documentation primaryKeys(catalog, schema, table, callback = undefined) { // promise... diff --git a/lib/Statement.js b/lib/Statement.js index 34ea9ad3..053ef9de 100644 --- a/lib/Statement.js +++ b/lib/Statement.js @@ -168,10 +168,43 @@ class Statement { } } + /** + * Cancels any operation currently running on this statement (e.g. a long + * execute()) by calling SQLCancel on its handle. The cancelled operation + * returns with SQLSTATE HY008 ("Operation canceled"). + * @param {function} [callback] - The callback function that returns the result. If omitted, uses a Promise. + */ + cancel(callback = undefined) { + if (typeof callback !== 'function' && typeof callback !== 'undefined') { + throw new TypeError('[node-odbc]: Incorrect function signature for call to statement.cancel({function}[optional]).'); + } + + if (typeof callback === 'undefined') { + if (!this.odbcStatement) { + throw new Error(Statement.STATEMENT_CLOSED_ERROR); + } + return new Promise((resolve, reject) => { + this.odbcStatement.cancel((error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + } + + // ...or callback + if (!this.odbcStatement) { + return callback(new Error(Statement.STATEMENT_CLOSED_ERROR)); + } + return this.odbcStatement.cancel((error) => callback(error)); + } + /** * Closes the statement, deleting the prepared statement and freeing the handle, making further * calls on the object invalid. - * @param {function} [callback] - The callback function that returns the result. If omitted, uses a Promise. + * @param {function} [callback] - The callback function that returns the result. If omitted, uses a Promise. */ close(callback = undefined) { if (typeof callback !== 'function' && typeof callback !== 'undefined') { diff --git a/lib/odbc.d.ts b/lib/odbc.d.ts index 3fab1a01..f058fd45 100644 --- a/lib/odbc.d.ts +++ b/lib/odbc.d.ts @@ -39,6 +39,8 @@ declare namespace odbc { execute(callback: (error: NodeOdbcError, result: Result) => undefined): undefined; + cancel(callback: (error: NodeOdbcError) => undefined): undefined; + close(callback: (error: NodeOdbcError) => undefined): undefined; //////////////////////////////////////////////////////////////////////////// @@ -51,6 +53,8 @@ declare namespace odbc { execute(): Promise>; + cancel(): Promise; + close(): Promise; } @@ -112,6 +116,8 @@ declare namespace odbc { rollback(callback: (error: NodeOdbcError) => undefined): undefined; + cancel(callback: (error: NodeOdbcError) => undefined): undefined; + close(callback: (error: NodeOdbcError) => undefined): undefined; //////////////////////////////////////////////////////////////////////////// @@ -142,6 +148,8 @@ declare namespace odbc { rollback(): Promise; + cancel(): Promise; + close(): Promise; connected(): boolean; diff --git a/src/odbc.h b/src/odbc.h index 4d5e7c58..3c0a3a5f 100644 --- a/src/odbc.h +++ b/src/odbc.h @@ -190,7 +190,7 @@ typedef struct StatementData { SQLHENV henv; SQLHDBC hdbc; - SQLHSTMT hstmt; + SQLHSTMT hstmt = SQL_NULL_HANDLE; QueryOptions query_options; diff --git a/src/odbc_connection.cpp b/src/odbc_connection.cpp index 4a6e2e81..178fed47 100644 --- a/src/odbc_connection.cpp +++ b/src/odbc_connection.cpp @@ -47,6 +47,7 @@ Napi::Object ODBCConnection::Init(Napi::Env env, Napi::Object exports) { InstanceMethod("close", &ODBCConnection::Close), InstanceMethod("createStatement", &ODBCConnection::CreateStatement), InstanceMethod("query", &ODBCConnection::Query), + InstanceMethod("cancel", &ODBCConnection::Cancel), InstanceMethod("beginTransaction", &ODBCConnection::BeginTransaction), InstanceMethod("commit", &ODBCConnection::Commit), InstanceMethod("rollback", &ODBCConnection::Rollback), @@ -167,12 +168,27 @@ ODBCConnection::ODBCConnection(const Napi::CallbackInfo& info) : Napi::ObjectWra this->hDBC = *(info[1].As>().Data()); this->connectionOptions = *(info[2].As>().Data()); this->getInfoResults = *(info[3].As>().Data()); + + uv_mutex_init(&this->activeStatementsMutex); } ODBCConnection::~ODBCConnection() { this->Free(); + uv_mutex_destroy(&this->activeStatementsMutex); +} + +void ODBCConnection::RegisterActiveStatement(SQLHSTMT hstmt) { + uv_mutex_lock(&this->activeStatementsMutex); + this->activeStatements.insert(hstmt); + uv_mutex_unlock(&this->activeStatementsMutex); +} + +void ODBCConnection::UnregisterActiveStatement(SQLHSTMT hstmt) { + uv_mutex_lock(&this->activeStatementsMutex); + this->activeStatements.erase(hstmt); + uv_mutex_unlock(&this->activeStatementsMutex); } SQLRETURN ODBCConnection::Free() { @@ -743,6 +759,10 @@ class QueryAsyncWorker : public ODBCAsyncWorker { return; } + // make the handle reachable by connection.cancel() while this worker + // is executing + odbcConnectionObject->RegisterActiveStatement(data->hstmt); + // set SQL_ATTR_QUERY_TIMEOUT if (data->query_options.timeout > 0) { return_code = @@ -992,6 +1012,9 @@ class QueryAsyncWorker : public ODBCAsyncWorker { } ~QueryAsyncWorker() { + if (data != NULL) { + odbcConnectionObject->UnregisterActiveStatement(data->hstmt); + } if (!data->query_options.use_cursor) { uv_mutex_lock(&ODBC::g_odbcMutex); @@ -1151,6 +1174,68 @@ Napi::Value ODBCConnection::Query(const Napi::CallbackInfo& info) { return env.Undefined(); } +/* + * ODBCConnection::Cancel + * + * Description: Calls SQLCancel on the statement handles of all in-flight + * operations on this connection (queries and procedure + * calls). The cancelled operations will return with SQLSTATE + * HY008 ("Operation canceled"), rejecting their promises or + * calling their callbacks with an error. + * + * SQLCancel is called synchronously on the main thread + * instead of through an AsyncWorker: cancel() is most needed + * precisely when the libuv thread pool is saturated by the + * operations being cancelled, so queueing the cancel behind + * them could delay it indefinitely. Calling SQLCancel from a + * different thread than the one running the statement is the + * documented multithreaded-cancel use of the function, and it + * returns quickly. + * + * Parameters: + * const Napi::CallbackInfo& info: + * The information passed from the JavaSript environment, including the + * function arguments for 'cancel'. + * + * info[0]: Function: callback function: + * function(error) + * error: An error object if one or more statements could not be + * cancelled, or null if the operation was successful. + * + * Return: + * Napi::Value: + * Undefined (results returned in callback) + */ +Napi::Value ODBCConnection::Cancel(const Napi::CallbackInfo& info) { + + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + + Napi::Function callback = info[0].As(); + + size_t failure_count = 0; + + uv_mutex_lock(&this->activeStatementsMutex); + for (SQLHSTMT hstmt : this->activeStatements) { + SQLRETURN return_code = SQLCancel(hstmt); + if (!SQL_SUCCEEDED(return_code)) { + failure_count++; + } + } + uv_mutex_unlock(&this->activeStatementsMutex); + + std::vector callbackArguments; + if (failure_count > 0) { + Napi::Error error = Napi::Error::New(env, "[odbc] Error canceling one or more active statements"); + callbackArguments.push_back(error.Value()); + } else { + callbackArguments.push_back(env.Null()); + } + callback.Call(callbackArguments); + + return env.Undefined(); +} + // If we have a parameter with input/output params (e.g. calling a procedure), // then we need to take the Parameter structures of the StatementData and create // a Napi::Array from those that were overwritten. @@ -1282,6 +1367,10 @@ class CallProcedureAsyncWorker : public ODBCAsyncWorker { return; } + // make the handle reachable by connection.cancel() while this worker + // is executing + odbcConnectionObject->RegisterActiveStatement(data->hstmt); + return_code = set_fetch_size ( @@ -1924,6 +2013,9 @@ class CallProcedureAsyncWorker : public ODBCAsyncWorker { } ~CallProcedureAsyncWorker() { + if (data != NULL) { + odbcConnectionObject->UnregisterActiveStatement(data->hstmt); + } delete[] overwriteParams; delete data; data = NULL; diff --git a/src/odbc_connection.h b/src/odbc_connection.h index afd74428..b5688329 100644 --- a/src/odbc_connection.h +++ b/src/odbc_connection.h @@ -20,6 +20,7 @@ #define _SRC_ODBC_CONNECTION_H #include +#include class ODBCConnection : public Napi::ObjectWrap { @@ -63,6 +64,7 @@ class ODBCConnection : public Napi::ObjectWrap { Napi::Value Close(const Napi::CallbackInfo& info); Napi::Value CreateStatement(const Napi::CallbackInfo& info); Napi::Value Query(const Napi::CallbackInfo& info); + Napi::Value Cancel(const Napi::CallbackInfo& info); Napi::Value CallProcedure(const Napi::CallbackInfo& info); Napi::Value BeginTransaction(const Napi::CallbackInfo& info); @@ -99,6 +101,15 @@ class ODBCConnection : public Napi::ObjectWrap { SQLHENV hENV; SQLHDBC hDBC; + // Statement handles for in-flight operations, registered by the + // AsyncWorkers so that cancel() can call SQLCancel on them from the main + // thread while a worker thread is blocked on the ODBC call. + uv_mutex_t activeStatementsMutex; + std::set activeStatements; + + void RegisterActiveStatement(SQLHSTMT hstmt); + void UnregisterActiveStatement(SQLHSTMT hstmt); + ConnectionOptions connectionOptions; GetInfoResults getInfoResults; diff --git a/src/odbc_statement.cpp b/src/odbc_statement.cpp index b1802446..e7b79f9d 100644 --- a/src/odbc_statement.cpp +++ b/src/odbc_statement.cpp @@ -34,6 +34,7 @@ Napi::Object ODBCStatement::Init(Napi::Env env, Napi::Object exports) { InstanceMethod("prepare", &ODBCStatement::Prepare), InstanceMethod("bind", &ODBCStatement::Bind), InstanceMethod("execute", &ODBCStatement::Execute), + InstanceMethod("cancel", &ODBCStatement::Cancel), InstanceMethod("close", &ODBCStatement::Close), }); @@ -546,6 +547,62 @@ Napi::Value ODBCStatement::Execute(const Napi::CallbackInfo& info) { return env.Undefined(); } +/****************************************************************************** + ********************************** CANCEL ************************************ + *****************************************************************************/ + +/* + * ODBCStatement::Cancel + * + * Description: Calls SQLCancel on this statement's handle, aborting any + * operation currently running on it (e.g. a long execute()). + * The cancelled operation will return with SQLSTATE HY008 + * ("Operation canceled"). + * + * SQLCancel is called synchronously on the main thread + * instead of through an AsyncWorker: cancel() is most needed + * precisely when the libuv thread pool is saturated by the + * operations being cancelled, so queueing the cancel behind + * them could delay it indefinitely. Calling SQLCancel from a + * different thread than the one running the statement is the + * documented multithreaded-cancel use of the function, and it + * returns quickly. + * + * Parameters: + * const Napi::CallbackInfo& info: + * The information passed from the JavaSript environment, including the + * function arguments for 'cancel'. + * + * info[0]: Function: callback function: + * function(error) + * error: An error object if the statement could not be + * cancelled, or null if the operation was successful. + * + * Return: + * Napi::Value: + * Undefined (results returned in callback) + */ +Napi::Value ODBCStatement::Cancel(const Napi::CallbackInfo& info) { + + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + + Napi::Function callback = info[0].As(); + + SQLRETURN return_code = SQLCancel(this->data->hstmt); + + std::vector callbackArguments; + if (!SQL_SUCCEEDED(return_code)) { + Napi::Error error = Napi::Error::New(env, "[odbc] Error canceling the statement"); + callbackArguments.push_back(error.Value()); + } else { + callbackArguments.push_back(env.Null()); + } + callback.Call(callbackArguments); + + return env.Undefined(); +} + /****************************************************************************** ********************************** CLOSE ************************************* *****************************************************************************/ diff --git a/src/odbc_statement.h b/src/odbc_statement.h index 6f9325a1..c23e30f8 100644 --- a/src/odbc_statement.h +++ b/src/odbc_statement.h @@ -38,6 +38,7 @@ class ODBCStatement : public Napi::ObjectWrap { Napi::Value Prepare(const Napi::CallbackInfo& info); Napi::Value Bind(const Napi::CallbackInfo& info); Napi::Value Execute(const Napi::CallbackInfo& info); + Napi::Value Cancel(const Napi::CallbackInfo& info); Napi::Value Close(const Napi::CallbackInfo& info); }; #endif From 6354a7d6fb1e822a02bc02eac155875fed300703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Rend=C3=B3n=20V=C3=A9lez?= <22383219+rendonvelez@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:33:53 -0500 Subject: [PATCH 2/4] ci: re-run checks after windows runner fix landed on main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustavo Rendón Vélez <22383219+rendonvelez@users.noreply.github.com> From 061d35207a654d1f5cb8771e7644430d1b8d1ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Rend=C3=B3n=20V=C3=A9lez?= <22383219+rendonvelez@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:40:51 -0500 Subject: [PATCH 3/4] refactor: remove redundant NULL checks in async worker destructors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The data pointer is always valid: the only constructors of QueryAsyncWorker and CallProcedureAsyncWorker receive it as a required argument, so checking it for NULL before unregistering while dereferencing it unconditionally right after was inconsistent. Addresses review feedback. Signed-off-by: Gustavo Rendón Vélez <22383219+rendonvelez@users.noreply.github.com> --- src/odbc_connection.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/odbc_connection.cpp b/src/odbc_connection.cpp index 178fed47..f20f8aa4 100644 --- a/src/odbc_connection.cpp +++ b/src/odbc_connection.cpp @@ -1012,9 +1012,7 @@ class QueryAsyncWorker : public ODBCAsyncWorker { } ~QueryAsyncWorker() { - if (data != NULL) { - odbcConnectionObject->UnregisterActiveStatement(data->hstmt); - } + odbcConnectionObject->UnregisterActiveStatement(data->hstmt); if (!data->query_options.use_cursor) { uv_mutex_lock(&ODBC::g_odbcMutex); @@ -2013,9 +2011,7 @@ class CallProcedureAsyncWorker : public ODBCAsyncWorker { } ~CallProcedureAsyncWorker() { - if (data != NULL) { - odbcConnectionObject->UnregisterActiveStatement(data->hstmt); - } + odbcConnectionObject->UnregisterActiveStatement(data->hstmt); delete[] overwriteParams; delete data; data = NULL; From 25e61bd6be35da2e0cef4817f2cb4f4dbe3d858a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Rend=C3=B3n=20V=C3=A9lez?= <22383219+rendonvelez@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:54:01 -0500 Subject: [PATCH 4/4] docs: document cancellation-checkpoint caveat for cancel() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SQLCancel only requests cancellation; when the operation actually aborts depends on the engine reaching a cancellation checkpoint. Note the known cases: Impala's sleep() completes its full wait, and stored procedure calls on Db2 for IBM i are not cancelable. Signed-off-by: Gustavo Rendón Vélez <22383219+rendonvelez@users.noreply.github.com> --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index de87ab8a..dd33ca16 100644 --- a/README.md +++ b/README.md @@ -644,6 +644,8 @@ odbc.connect(`${process.env.CONNECTION_STRING}`, (error, connection) => { Cancels all operations currently running on the connection (queries and procedure calls) by calling `SQLCancel` on their statement handles. The cancelled operations return with SQLSTATE HY008 ("Operation canceled"), so their promises reject (or their callbacks are called with an error). If no operations are running, `.cancel` is a no-op. +**Note:** `SQLCancel` only _requests_ cancellation — when the operation actually aborts depends on the driver and on the database engine reaching a cancellation checkpoint. Row-producing operations (scans, fetches) usually abort promptly, but some operations never check for cancellation and only fail with HY008 once they finish on their own. Known examples: Impala's `sleep()` function completes its full wait before honoring the cancel, and calls to stored procedures on Db2 for IBM i are not cancelable at all. + #### Parameters: * **callback?**: The function called when `.cancel` has finished execution. If no callback function is given, `.cancel` will return a native JavaScript `Promise`. Callback signature is: * error: The error that occured in execution, or `null` if no error @@ -1089,6 +1091,8 @@ odbc.connect(`${process.env.CONNECTION_STRING}`, (error, connection) => { Cancels any operation currently running on the Statement (e.g. a long `.execute()`) by calling `SQLCancel` on its handle. The cancelled operation returns with SQLSTATE HY008 ("Operation canceled"). +**Note:** the cancellation-checkpoint caveat described in [connection.cancel()](#cancelcallback) applies here as well. + #### Parameters: * **callback?**: The function called when `.cancel` has finished execution. If no callback function is given, `.cancel` will return a native JavaScript `Promise`. Callback signature is: * error: The error that occured in execution, or `null` if no error