Skip to content

Commit cb229f9

Browse files
authored
Fixup Statement.execute query options (#278)
Add execution timeout code path for Statement.execute Add README.md entries for using query options with Statement.execute Add tests to ensure setting query timeout at this point won't crash ODBC Signed-off-by: Mark Irish <[email protected]>
1 parent 995dcb7 commit cb229f9

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,11 +962,16 @@ odbc.connect(`${process.env.CONNECTION_STRING}`, (error, connection) => {
962962

963963
---
964964

965-
### `.execute(callback?)`
965+
### `.execute(options?, callback?)`
966966

967967
Executes the prepared and optionally bound SQL statement.
968968

969969
#### Parameters:
970+
* **options?**: An object containing options that affect execution behavior. Valid properties include:
971+
* `cursor`: A boolean value indicating whether or not to return a cursor instead of results immediately. Can also be a string naming the cursor, which will assume that a cursor will be returned. Closing the `Statement` will also close the `Cursor`, but closing the `Cursor` will keep the `Statement` valid.
972+
* `fetchSize`: Used with a cursor, sets the number of rows that are returned on a call to `fetch` on the Cursor.
973+
* `timeout`: The amount of time (in seconds) that the query will attempt to execute before returning to the application.
974+
* `initialBufferSize`: Sets the initial buffer size (in bytes) for storing data from SQL_LONG* data fields. Useful for avoiding resizes if buffer size is known before the call.
970975
* **callback?**: The function called when `.execute` has finished execution. If no callback function is given, `.execute` will return a native JavaScript `Promise`. Callback signature is:
971976
* error: The error that occured in execution, or `null` if no error
972977
* result: The [result array](#result-array) returned from the executed statement

src/odbc_statement.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,43 @@ class ExecuteAsyncWorker : public ODBCAsyncWorker {
316316

317317
SQLRETURN return_code;
318318

319+
// set SQL_ATTR_QUERY_TIMEOUT
320+
if (data->query_options.timeout > 0) {
321+
return_code =
322+
SQLSetStmtAttr
323+
(
324+
data->hstmt,
325+
SQL_ATTR_QUERY_TIMEOUT,
326+
(SQLPOINTER) data->query_options.timeout,
327+
IGNORED_PARAMETER
328+
);
329+
330+
// It is possible that SQLSetStmtAttr returns a warning with SQLSTATE
331+
// 01S02, indicating that the driver changed the value specified.
332+
// Although we never use the timeout variable again (and so we don't
333+
// REALLY need it to be correct in the code), its just good to have
334+
// the correct value if we need it.
335+
if (return_code == SQL_SUCCESS_WITH_INFO)
336+
{
337+
return_code =
338+
SQLGetStmtAttr
339+
(
340+
data->hstmt,
341+
SQL_ATTR_QUERY_TIMEOUT,
342+
(SQLPOINTER) &data->query_options.timeout,
343+
SQL_IS_UINTEGER,
344+
IGNORED_PARAMETER
345+
);
346+
}
347+
348+
// Both of the SQL_ATTR_QUERY_TIMEOUT calls are combined here
349+
if (!SQL_SUCCEEDED(return_code)) {
350+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
351+
SetError("[odbc] Error setting the query timeout on the statement\0");
352+
return;
353+
}
354+
}
355+
319356
return_code =
320357
set_fetch_size
321358
(

test/statement/execute.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,23 @@ describe('.execute([calback])...', () => {
251251
});
252252
});
253253
});
254+
it.only('...should accept a timeout option without crashing', (done) => {
255+
connection.createStatement((error1, statement) => {
256+
assert.deepEqual(error1, null);
257+
assert.notDeepEqual(statement, null);
258+
statement.prepare(`SELECT * FROM ${process.env.DB_SCHEMA}.${process.env.DB_TABLE}`, (error2) => {
259+
assert.deepEqual(error2, null);
260+
statement.execute({timeout: 10}, (error3, result) => {
261+
assert.deepEqual(error3, null);
262+
assert.notDeepEqual(result, null);
263+
statement.close((error4) => {
264+
assert.deepEqual(error4, null);
265+
done();
266+
});
267+
});
268+
});
269+
});
270+
});
254271
}); // '...with callbacks...'
255272
describe('...with promises...', () => {
256273
it('...should execute if a valid SQL string has been prepared and valid values bound.', async () => {
@@ -366,5 +383,13 @@ describe('.execute([calback])...', () => {
366383
assert.deepEqual(statementResult[0].AGE, 10);
367384
await statement.close();
368385
});
386+
it.only('...should accept a timeout option without crashing', async () => {
387+
const statement = await connection.createStatement();
388+
assert.notDeepEqual(statement, null);
389+
await statement.prepare(`SELECT * FROM ${process.env.DB_SCHEMA}.${process.env.DB_TABLE}`);
390+
const result = await statement.execute({timeout: 10});
391+
assert.notDeepEqual(result, null);
392+
await statement.close();
393+
});
369394
}); // '...with promises...'
370395
});

0 commit comments

Comments
 (0)