Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ jobs:
node-version:
- 20.x
os:
- windows-latest
# windows-2022 pinned: the node-gyp bundled with npm on Node 20
# fails to locate Visual Studio on newer windows images
# (ERR_CHILD_PROCESS_STDIO_MAXBUFFER in "find VS").
- windows-2022
- ubuntu-latest
- macos-latest
runs-on: ${{ matrix.os }}
Expand All @@ -25,5 +28,7 @@ jobs:
- name: Install dependencies
if: ${{ startsWith(matrix.os, 'macos') }}
run: brew install unixodbc
- name: Upgrade NPM
run: npm install -g npm
- name: Build package
run: npm ci --production --build-from-source
24 changes: 5 additions & 19 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,12 @@ jobs:
matrix:
node-version:
- 20.x
# ppc64le removed: that runner is not available on this fork.
os:
- windows-2022
- ubuntu-22.04
- macos-15
- macos-15-intel
- ubuntu-24.04-ppc64le
include:
- os: ubuntu-24.04-ppc64le
arch: ppc64le
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -65,34 +62,23 @@ jobs:
- name: get-npm-version
id: package-version
run: jq -r '"version=" + .version' package.json >> "$GITHUB_OUTPUT"
- name: get-npm-tag
id: package-tag
run: .github/npm-tag.sh $PACKAGE_VERSION >> "$GITHUB_OUTPUT"
env:
PACKAGE_VERSION: ${{ steps.package-version.outputs.version }}
# Download all of the various binaries that were created
- name: Download built binaries from workflow
uses: actions/download-artifact@v5
with:
path: binaries
pattern: binaries-*
merge-multiple: true
- name: Create Release
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v2
with:
draft: false
prerelease: false
prerelease: true
name: ${{ steps.package-version.outputs.version }}
generate_release_notes: true
fail_on_unmatched_files: true
files:
./binaries/odbc-*.tar.gz
- name: Publish to NPM
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- run: npm publish --tag $PACKAGE_TAG
env:
PACKAGE_TAG: ${{ steps.package-tag.outputs.tag }}
# npm publish removed: this fork only distributes prebuilt binaries
# through GitHub Releases; the package is never published to npm.
128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,66 @@ 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.

**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

#### 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.
Expand Down Expand Up @@ -1027,6 +1087,74 @@ 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").

**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

#### 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.
Expand Down
5 changes: 5 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
],
}],
],
# Reserve header padding so install_name_tool can add LC_RPATH
# entries to the prebuilt binaries without corrupting them.
'xcode_settings': {
'OTHER_LDFLAGS': [ '-Wl,-headerpad_max_install_names' ]
},
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ]
}],
[ 'OS == "freebsd"', {
Expand Down
38 changes: 38 additions & 0 deletions lib/Connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Expand Down
35 changes: 34 additions & 1 deletion lib/Statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
8 changes: 8 additions & 0 deletions lib/odbc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ declare namespace odbc {

execute<T>(callback: (error: NodeOdbcError, result: Result<T>) => undefined): undefined;

cancel(callback: (error: NodeOdbcError) => undefined): undefined;

close(callback: (error: NodeOdbcError) => undefined): undefined;

////////////////////////////////////////////////////////////////////////////
Expand All @@ -51,6 +53,8 @@ declare namespace odbc {

execute<T>(): Promise<Result<T>>;

cancel(): Promise<void>;

close(): Promise<void>;
}

Expand Down Expand Up @@ -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;

////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -142,6 +148,8 @@ declare namespace odbc {

rollback(): Promise<void>;

cancel(): Promise<void>;

close(): Promise<void>;

connected(): boolean;
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "odbc",
"description": "unixodbc bindings for node",
"version": "2.5.0",
"version": "2.5.0-eva.1",
"homepage": "http://github.com/IBM/node-odbc/",
"main": "lib/odbc.js",
"types": "lib/odbc.d.ts",
Expand Down Expand Up @@ -60,7 +60,7 @@
"binary": {
"module_name": "odbc",
"module_path": "./lib/bindings/napi-v{napi_build_version}",
"host": "https://github.com/IBM/node-odbc/releases/download/v{version}",
"host": "https://github.com/rendonvelez/node-odbc/releases/download/v{version}",
"package_name": "{name}-v{version}-{platform}-{arch}-napi-v{napi_build_version}.tar.gz",
"napi_versions": [
8
Expand Down
4 changes: 1 addition & 3 deletions src/odbc.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ typedef struct ColumnData {
SQLCHAR *char_data;
SQLWCHAR *wchar_data;
SQLDOUBLE double_data;
SQLCHAR tinyint_data;
SQLUSMALLINT usmallint_data;
SQLSMALLINT smallint_data;
SQLINTEGER integer_data;
Expand Down Expand Up @@ -190,7 +189,7 @@ typedef struct StatementData {

SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;
SQLHSTMT hstmt = SQL_NULL_HANDLE;

QueryOptions query_options;

Expand Down Expand Up @@ -279,7 +278,6 @@ typedef struct StatementData {
for (int i = 0; i < this->column_count; i++) {
switch (this->columns[i]->bind_type) {
case SQL_C_CHAR:
case SQL_C_UTINYINT:
case SQL_C_BINARY:
delete[] (SQLCHAR *)this->bound_columns[i].buffer;
break;
Expand Down
Loading
Loading