@miatechnet/node-odbc 2.4.10-multiresult.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/odbc.cpp ADDED
@@ -0,0 +1,850 @@
1
+ /*
2
+ Copyright (c) 2019, 2021 IBM
3
+ Copyright (c) 2013, Dan VerWeire <dverweire@gmail.com>
4
+ Copyright (c) 2010, Lee Smith<notwink@gmail.com>
5
+
6
+ Permission to use, copy, modify, and/or distribute this software for any
7
+ purpose with or without fee is hereby granted, provided that the above
8
+ copyright notice and this permission notice appear in all copies.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
+ */
18
+
19
+ #include <time.h>
20
+ #include <stdlib.h>
21
+
22
+ #include "odbc.h"
23
+ #include "odbc_connection.h"
24
+ #include "odbc_statement.h"
25
+ #include "odbc_cursor.h"
26
+
27
+ #ifdef dynodbc
28
+ #include "dynodbc.h"
29
+ #endif
30
+
31
+ // // object keys for the result object
32
+ // const char* NAME = "name\0";
33
+ // const char* DATA_TYPE = "dataType\0";
34
+ // const char* STATEMENT = "statement\0";
35
+ // const char* PARAMETERS = "parameters\0";
36
+ // const char* RETURN = "return\0";
37
+ // const char* COUNT = "count\0";
38
+ // const char* COLUMNS = "columns\0";
39
+
40
+ size_t strlen16(const char16_t* string)
41
+ {
42
+ const char16_t* str = string;
43
+ while(*str) str++;
44
+ return str - string;
45
+ }
46
+
47
+ // error strings
48
+ const char* ODBC_ERRORS = "odbcErrors\0";
49
+ const char* STATE = "state\0";
50
+ const char* CODE = "code\0";
51
+ const char* MESSAGE = "message\0";
52
+ #ifdef UNICODE
53
+ const SQLTCHAR NO_STATE_TEXT = L'\0';
54
+ const SQLTCHAR* NO_MSG_TEXT = (SQLTCHAR *)L"<No error information available>\0";
55
+ const size_t NO_MSG_TEXT_LENGTH = strlen16((char16_t *)NO_MSG_TEXT);
56
+ #else
57
+ const SQLTCHAR NO_STATE_TEXT = '\0';
58
+ const char* NO_MSG_TEXT = "<No error information available>\0";
59
+ const size_t NO_MSG_TEXT_LENGTH = strlen(NO_MSG_TEXT);
60
+ #endif
61
+ // byte count, needed for memcpy
62
+ const size_t NO_MSG_TEXT_SIZE = NO_MSG_TEXT_LENGTH * sizeof(SQLTCHAR);
63
+
64
+ uv_mutex_t ODBC::g_odbcMutex;
65
+ SQLHENV ODBC::hEnv;
66
+
67
+ Napi::Value ODBC::Init(Napi::Env env, Napi::Object exports) {
68
+
69
+ hEnv = NULL;
70
+ Napi::HandleScope scope(env);
71
+
72
+ // Wrap ODBC constants in an object that we can then expand
73
+ std::vector<Napi::PropertyDescriptor> ODBC_CONSTANTS;
74
+
75
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("ODBCVER", Napi::Number::New(env, ODBCVER), napi_enumerable));
76
+
77
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_COMMIT", Napi::Number::New(env, SQL_COMMIT), napi_enumerable));
78
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_ROLLBACK", Napi::Number::New(env, SQL_ROLLBACK), napi_enumerable));
79
+
80
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_USER_NAME", Napi::Number::New(env, SQL_USER_NAME), napi_enumerable));
81
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_PARAM_INPUT", Napi::Number::New(env, SQL_PARAM_INPUT), napi_enumerable));
82
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_PARAM_INPUT_OUTPUT", Napi::Number::New(env, SQL_PARAM_INPUT_OUTPUT), napi_enumerable));
83
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_PARAM_OUTPUT", Napi::Number::New(env, SQL_PARAM_OUTPUT), napi_enumerable));
84
+
85
+ // Export the integer values for each data type so developers can utilize
86
+ // them programmatically if needed
87
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_CHAR", Napi::Number::New(env, SQL_CHAR), napi_enumerable));
88
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_VARCHAR", Napi::Number::New(env, SQL_VARCHAR), napi_enumerable));
89
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_LONGVARCHAR", Napi::Number::New(env, SQL_LONGVARCHAR), napi_enumerable));
90
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_WCHAR", Napi::Number::New(env, SQL_WCHAR), napi_enumerable));
91
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_WVARCHAR", Napi::Number::New(env, SQL_WVARCHAR), napi_enumerable));
92
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_WLONGVARCHAR", Napi::Number::New(env, SQL_WLONGVARCHAR), napi_enumerable));
93
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_DECIMAL", Napi::Number::New(env, SQL_DECIMAL), napi_enumerable));
94
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_NUMERIC", Napi::Number::New(env, SQL_NUMERIC), napi_enumerable));
95
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_SMALLINT", Napi::Number::New(env, SQL_SMALLINT), napi_enumerable));
96
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTEGER", Napi::Number::New(env, SQL_INTEGER), napi_enumerable));
97
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_REAL", Napi::Number::New(env, SQL_REAL), napi_enumerable));
98
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_FLOAT", Napi::Number::New(env, SQL_FLOAT), napi_enumerable));
99
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_DOUBLE", Napi::Number::New(env, SQL_DOUBLE), napi_enumerable));
100
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_BIT", Napi::Number::New(env, SQL_BIT), napi_enumerable));
101
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TINYINT", Napi::Number::New(env, SQL_TINYINT), napi_enumerable));
102
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_BIGINT", Napi::Number::New(env, SQL_BIGINT), napi_enumerable));
103
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_BINARY", Napi::Number::New(env, SQL_BINARY), napi_enumerable));
104
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_VARBINARY", Napi::Number::New(env, SQL_VARBINARY), napi_enumerable));
105
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_LONGVARBINARY", Napi::Number::New(env, SQL_LONGVARBINARY), napi_enumerable));
106
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_DATE", Napi::Number::New(env, SQL_TYPE_DATE), napi_enumerable));
107
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_TIME", Napi::Number::New(env, SQL_TYPE_TIME), napi_enumerable));
108
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_TIMESTAMP", Napi::Number::New(env, SQL_TYPE_TIMESTAMP), napi_enumerable));
109
+ // These are listed in the Microsoft ODBC documentation, but don't appear to
110
+ // be in unixODBC
111
+ // ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_UTCDATETIME", Napi::Number::New(env, SQL_TYPE_UTCDATETIME), napi_enumerable));
112
+ // ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TYPE_UTCTIME", Napi::Number::New(env, SQL_TYPE_UTCTIME), napi_enumerable));
113
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_MONTH", Napi::Number::New(env, SQL_INTERVAL_MONTH), napi_enumerable));
114
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_YEAR", Napi::Number::New(env, SQL_INTERVAL_YEAR), napi_enumerable));
115
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_YEAR_TO_MONTH", Napi::Number::New(env, SQL_INTERVAL_YEAR_TO_MONTH), napi_enumerable));
116
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_DAY", Napi::Number::New(env, SQL_INTERVAL_DAY), napi_enumerable));
117
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_HOUR", Napi::Number::New(env, SQL_INTERVAL_HOUR), napi_enumerable));
118
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_MINUTE", Napi::Number::New(env, SQL_INTERVAL_MINUTE), napi_enumerable));
119
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_SECOND", Napi::Number::New(env, SQL_INTERVAL_SECOND), napi_enumerable));
120
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_DAY_TO_HOUR", Napi::Number::New(env, SQL_INTERVAL_DAY_TO_HOUR), napi_enumerable));
121
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_DAY_TO_MINUTE", Napi::Number::New(env, SQL_INTERVAL_DAY_TO_MINUTE), napi_enumerable));
122
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_DAY_TO_SECOND", Napi::Number::New(env, SQL_INTERVAL_DAY_TO_SECOND), napi_enumerable));
123
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_HOUR_TO_MINUTE", Napi::Number::New(env, SQL_INTERVAL_HOUR_TO_MINUTE), napi_enumerable));
124
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_HOUR_TO_SECOND", Napi::Number::New(env, SQL_INTERVAL_HOUR_TO_SECOND), napi_enumerable));
125
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_INTERVAL_MINUTE_TO_SECOND", Napi::Number::New(env, SQL_INTERVAL_MINUTE_TO_SECOND), napi_enumerable));
126
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_GUID", Napi::Number::New(env, SQL_GUID), napi_enumerable));
127
+ // End data types
128
+
129
+
130
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_NO_NULLS", Napi::Number::New(env, SQL_NO_NULLS), napi_enumerable));
131
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_NULLABLE", Napi::Number::New(env, SQL_NULLABLE), napi_enumerable));
132
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_NULLABLE_UNKNOWN", Napi::Number::New(env, SQL_NULLABLE_UNKNOWN), napi_enumerable));
133
+
134
+ // setIsolationLevel options
135
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TXN_READ_UNCOMMITTED", Napi::Number::New(env, SQL_TXN_READ_UNCOMMITTED), napi_enumerable));
136
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TRANSACTION_READ_UNCOMMITTED", Napi::Number::New(env, SQL_TRANSACTION_READ_UNCOMMITTED), napi_enumerable));
137
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TXN_READ_COMMITTED", Napi::Number::New(env, SQL_TXN_READ_COMMITTED), napi_enumerable));
138
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TRANSACTION_READ_COMMITTED", Napi::Number::New(env, SQL_TRANSACTION_READ_COMMITTED), napi_enumerable));
139
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TXN_REPEATABLE_READ", Napi::Number::New(env, SQL_TXN_REPEATABLE_READ), napi_enumerable));
140
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TRANSACTION_REPEATABLE_READ", Napi::Number::New(env, SQL_TRANSACTION_REPEATABLE_READ), napi_enumerable));
141
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TXN_SERIALIZABLE", Napi::Number::New(env, SQL_TXN_SERIALIZABLE), napi_enumerable));
142
+ ODBC_CONSTANTS.push_back(Napi::PropertyDescriptor::Value("SQL_TRANSACTION_SERIALIZABLE", Napi::Number::New(env, SQL_TRANSACTION_SERIALIZABLE), napi_enumerable));
143
+
144
+ Napi::Object odbcConstants = Napi::Object::New(env);
145
+ odbcConstants.DefineProperties(ODBC_CONSTANTS);
146
+ exports.Set("odbcConstants", odbcConstants);
147
+
148
+ exports.Set("connect", Napi::Function::New(env, ODBC::Connect));
149
+
150
+ SQLRETURN return_code;
151
+
152
+ // Initialize the cross platform mutex provided by libuv
153
+ uv_mutex_init(&ODBC::g_odbcMutex);
154
+
155
+ uv_mutex_lock(&ODBC::g_odbcMutex);
156
+ // Initialize the Environment handle
157
+ return_code =
158
+ SQLAllocHandle
159
+ (
160
+ SQL_HANDLE_ENV,
161
+ SQL_NULL_HANDLE,
162
+ &hEnv
163
+ );
164
+ uv_mutex_unlock(&ODBC::g_odbcMutex);
165
+
166
+ if (!SQL_SUCCEEDED(return_code)) {
167
+ // TODO: Redo
168
+ // Napi::Error(env, Napi::String::New(env, (const char*)ODBC::GetSQLErrors(SQL_HANDLE_ENV, hEnv)[0].message)).ThrowAsJavaScriptException();
169
+ return env.Null();
170
+ }
171
+
172
+ // Use ODBC 3.x behavior
173
+ return_code =
174
+ SQLSetEnvAttr
175
+ (
176
+ hEnv,
177
+ SQL_ATTR_ODBC_VERSION,
178
+ (SQLPOINTER) SQL_OV_ODBC3,
179
+ SQL_IS_UINTEGER
180
+ );
181
+
182
+ return exports;
183
+ }
184
+
185
+ ODBC::~ODBC() {
186
+
187
+ uv_mutex_lock(&ODBC::g_odbcMutex);
188
+
189
+ if (hEnv) {
190
+ SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
191
+ hEnv = NULL;
192
+ }
193
+
194
+ uv_mutex_unlock(&ODBC::g_odbcMutex);
195
+ }
196
+
197
+ ////////////////////////////////////////////////////////////////////////////////
198
+ /////////////////////////////// ODBCAsyncWorker ////////////////////////////////
199
+ ////////////////////////////////////////////////////////////////////////////////
200
+ //
201
+ // This class extends Napi::AsyncWorker to standardize error handling for all
202
+ // AsyncWorkers used by the package
203
+ //
204
+ ////////////////////////////////////////////////////////////////////////////////
205
+
206
+ ODBCAsyncWorker::ODBCAsyncWorker(Napi::Function& callback)
207
+ : Napi::AsyncWorker(callback) {};
208
+
209
+ // TODO: Documentation for this function
210
+ void ODBCAsyncWorker::OnError(const Napi::Error &e) {
211
+ Napi::Env env = Env();
212
+ Napi::HandleScope scope(env);
213
+
214
+ // add the additional information to the Error object
215
+ Napi::Error error = Napi::Error::New(env, e.Message());
216
+ Napi::Array odbcErrors = Napi::Array::New(env);
217
+
218
+ for (SQLINTEGER i = 0; i < errorCount; i++) {
219
+ ODBCError odbcError = errors[i];
220
+ Napi::Object errorObject = Napi::Object::New(env);
221
+
222
+ errorObject.Set
223
+ (
224
+ Napi::String::New(env, STATE),
225
+ #ifdef UNICODE
226
+ Napi::String::New(env, (odbcError.state != NULL) ? (const char16_t*)odbcError.state : (const char16_t*)L"")
227
+ #else
228
+ Napi::String::New(env, (odbcError.state != NULL) ? (const char*)odbcError.state : "")
229
+ #endif
230
+ );
231
+
232
+ errorObject.Set
233
+ (
234
+ Napi::String::New(env, CODE),
235
+ Napi::Number::New(env, odbcError.code)
236
+ );
237
+
238
+ errorObject.Set
239
+ (
240
+ Napi::String::New(env, MESSAGE),
241
+ #ifdef UNICODE
242
+ Napi::String::New(env, (odbcError.message != NULL) ? (const char16_t *)odbcError.message : (const char16_t *)NO_MSG_TEXT)
243
+ #else
244
+ Napi::String::New(env, (odbcError.message != NULL) ? (const char *)odbcError.message : (const char *)NO_MSG_TEXT)
245
+ #endif
246
+ );
247
+
248
+ // Error message has been copied off of the C ODBC error stucture, and can
249
+ // now be deleted
250
+ if (odbcError.message != NULL)
251
+ {
252
+ delete[] odbcError.message;
253
+ odbcError.message = NULL;
254
+ }
255
+
256
+ odbcErrors.Set(i, errorObject);
257
+ }
258
+
259
+ error.Set(
260
+ Napi::String::New(env, ODBC_ERRORS),
261
+ odbcErrors
262
+ );
263
+
264
+ std::vector<napi_value> callbackArguments;
265
+ callbackArguments.push_back(error.Value());
266
+
267
+ Callback().Call(callbackArguments);
268
+ }
269
+
270
+ // After a SQL Function doesn't pass SQL_SUCCEEDED, the handle type and handle
271
+ // are sent to this function, which gets the information and stores it in an
272
+ // array of ODBCErrors
273
+ ODBCError* ODBCAsyncWorker::GetODBCErrors
274
+ (
275
+ SQLSMALLINT handleType,
276
+ SQLHANDLE handle
277
+ )
278
+ {
279
+ SQLRETURN return_code;
280
+ SQLSMALLINT error_message_length = ERROR_MESSAGE_BUFFER_CHARS;
281
+ SQLINTEGER statusRecCount;
282
+
283
+ return_code = SQLGetDiagField
284
+ (
285
+ handleType, // HandleType
286
+ handle, // Handle
287
+ 0, // RecNumber
288
+ SQL_DIAG_NUMBER, // DiagIdentifier
289
+ &statusRecCount, // DiagInfoPtr
290
+ SQL_IS_INTEGER, // BufferLength
291
+ NULL // StringLengthPtr
292
+ );
293
+
294
+ if (!SQL_SUCCEEDED(return_code))
295
+ {
296
+ ODBCError *odbcErrors = new ODBCError[1];
297
+ ODBCError error;
298
+ error.state[0] = NO_STATE_TEXT;
299
+ error.code = 0;
300
+ error.message = new SQLTCHAR[NO_MSG_TEXT_LENGTH + 1];
301
+ memcpy(error.message, NO_MSG_TEXT, NO_MSG_TEXT_SIZE + sizeof(SQLTCHAR));
302
+ odbcErrors[0] = error;
303
+ return odbcErrors;
304
+ }
305
+
306
+ ODBCError *odbcErrors = new ODBCError[statusRecCount];
307
+ this->errorCount = statusRecCount;
308
+
309
+ for (SQLSMALLINT i = 0; i < statusRecCount; i++) {
310
+
311
+ ODBCError error;
312
+
313
+ SQLSMALLINT new_error_message_length;
314
+ return_code = SQL_SUCCESS;
315
+
316
+ while(SQL_SUCCEEDED(return_code))
317
+ {
318
+ error.message = new SQLTCHAR[error_message_length];
319
+
320
+ return_code =
321
+ SQLGetDiagRec
322
+ (
323
+ handleType, // HandleType
324
+ handle, // Handle
325
+ i + 1, // RecNumber
326
+ error.state, // SQLState
327
+ &error.code, // NativeErrorPtr
328
+ error.message, // MessageText
329
+ error_message_length, // BufferLength
330
+ &new_error_message_length // TextLengthPtr
331
+ );
332
+
333
+ if (error_message_length > new_error_message_length)
334
+ {
335
+ break;
336
+ }
337
+
338
+ delete[] error.message;
339
+ error_message_length = new_error_message_length + 1;
340
+ }
341
+
342
+ if (!SQL_SUCCEEDED(return_code))
343
+ {
344
+ error.state[0] = NO_STATE_TEXT;
345
+ error.code = 0;
346
+ memcpy(error.message, NO_MSG_TEXT, NO_MSG_TEXT_SIZE + 1);
347
+ }
348
+
349
+ odbcErrors[i] = error;
350
+ }
351
+
352
+ return odbcErrors;
353
+ }
354
+
355
+ // TODO: Documentation for this function
356
+ bool ODBCAsyncWorker::CheckAndHandleErrors(SQLRETURN return_code, SQLSMALLINT handleType, SQLHANDLE handle, const char *message) {
357
+ if (!SQL_SUCCEEDED(return_code)) {
358
+ this->errors = GetODBCErrors(handleType, handle);
359
+ SetError(message);
360
+ return true;
361
+ }
362
+ return false;
363
+ }
364
+
365
+ // End ODBCAsyncWorker /////////////////////////////////////////////////////////
366
+
367
+ /*
368
+ * Connect
369
+ */
370
+ class ConnectAsyncWorker : public ODBCAsyncWorker {
371
+
372
+ private:
373
+
374
+ SQLTCHAR *connectionStringPtr;
375
+ ConnectionOptions *options;
376
+ GetInfoResults get_info_results;
377
+
378
+ SQLHENV hEnv;
379
+ SQLHDBC hDBC;
380
+
381
+ void Execute() {
382
+
383
+ SQLRETURN return_code;
384
+
385
+ uv_mutex_lock(&ODBC::g_odbcMutex);
386
+
387
+ return_code = SQLAllocHandle(
388
+ SQL_HANDLE_DBC,
389
+ hEnv,
390
+ &hDBC
391
+ );
392
+ if (!SQL_SUCCEEDED(return_code)) {
393
+ this->errors = GetODBCErrors(SQL_HANDLE_ENV, hEnv);
394
+ SetError("[odbc] Error allocating the connection handle");
395
+ return;
396
+ }
397
+
398
+ if (options->connectionTimeout > 0) {
399
+ return_code = SQLSetConnectAttr(
400
+ hDBC, // ConnectionHandle
401
+ SQL_ATTR_CONNECTION_TIMEOUT, // Attribute
402
+ (SQLPOINTER) (intptr_t) options->connectionTimeout, // ValuePtr
403
+ SQL_IS_UINTEGER // StringLength
404
+ );
405
+ if (!SQL_SUCCEEDED(return_code)) {
406
+ this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC);
407
+ SetError("[odbc] Error setting the connection timeout");
408
+ return;
409
+ }
410
+ }
411
+
412
+ if (options->loginTimeout > 0) {
413
+ return_code =
414
+ SQLSetConnectAttr
415
+ (
416
+ hDBC, // ConnectionHandle
417
+ SQL_ATTR_LOGIN_TIMEOUT, // Attribute
418
+ (SQLPOINTER) (intptr_t) options->loginTimeout, // ValuePtr
419
+ SQL_IS_UINTEGER // StringLength
420
+ );
421
+ if (!SQL_SUCCEEDED(return_code)) {
422
+ this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC);
423
+ SetError("[odbc] Error setting the login timeout");
424
+ return;
425
+ }
426
+ // "If the specified timeout exceeds the maximum login timeout in the
427
+ // data source, the driver substitutes that value and returns SQLSTATE
428
+ // 01S02 (Option value changed)."
429
+ if (return_code == SQL_SUCCESS_WITH_INFO)
430
+ {
431
+ return_code =
432
+ SQLGetConnectAttr
433
+ (
434
+ hDBC,
435
+ SQL_ATTR_LOGIN_TIMEOUT,
436
+ (SQLPOINTER) &options->loginTimeout,
437
+ IGNORED_PARAMETER,
438
+ NULL
439
+ );
440
+ if (!SQL_SUCCEEDED(return_code)) {
441
+ this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC);
442
+ SetError("[odbc] Error setting retrieving the changed login timeout");
443
+ return;
444
+ }
445
+ }
446
+ }
447
+
448
+ //Attempt to connect
449
+ return_code =
450
+ SQLDriverConnect
451
+ (
452
+ hDBC, // ConnectionHandle
453
+ NULL, // WindowHandle
454
+ connectionStringPtr, // InConnectionString
455
+ SQL_NTS, // StringLength1
456
+ NULL, // OutConnectionString
457
+ 0, // BufferLength - in characters
458
+ NULL, // StringLength2Ptr
459
+ SQL_DRIVER_NOPROMPT // DriverCompletion
460
+ );
461
+ uv_mutex_unlock(&ODBC::g_odbcMutex);
462
+ if (!SQL_SUCCEEDED(return_code)) {
463
+ this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC);
464
+ SetError("[odbc] Error connecting to the database");
465
+ return;
466
+ }
467
+
468
+ // get information about the connection
469
+ // maximum column length
470
+ return_code =
471
+ SQLGetInfo
472
+ (
473
+ hDBC, // ConnectionHandle
474
+ SQL_MAX_COLUMN_NAME_LEN, // InfoType
475
+ &get_info_results.max_column_name_length, // InfoValuePtr
476
+ sizeof(SQLSMALLINT), // BufferLength
477
+ NULL // StringLengthPtr
478
+ );
479
+ // Some poorly-behaved drivers do not implement SQL_MAX_COLUMN_NAME_LEN,
480
+ // and return SQL_ERROR instead of setting the value to 0 like the spec
481
+ // requires. Bite the bullet and ignore any errors here, instead setting
482
+ // the value to something sane like 128 ("An FIPS Intermediate
483
+ // level-conformant driver will return at least 128.").
484
+ if (!SQL_SUCCEEDED(return_code) || get_info_results.max_column_name_length == 0) {
485
+ get_info_results.max_column_name_length = 128;
486
+ // this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC);
487
+ // SetError("[odbc] Error getting information about maximum column length from the connection");
488
+ // return;
489
+ }
490
+
491
+ // valid transaction levels
492
+ return_code =
493
+ SQLGetInfo
494
+ (
495
+ hDBC, // ConnectionHandle
496
+ SQL_TXN_ISOLATION_OPTION, // InfoType
497
+ &get_info_results.available_isolation_levels, // InfoValuePtr
498
+ sizeof(SQLUINTEGER), // BufferLength
499
+ NULL // StringLengthPtr
500
+ );
501
+ // Some poorly-behaved drivers do not implement SQL_TXN_ISOLATION_OPTION,
502
+ // and return SQL_ERROR. Bite the bullet again and ignore any errors here,
503
+ // instead setting the bitmask to 0 so no isolation levels are listed as
504
+ // supported.
505
+ if (!SQL_SUCCEEDED(return_code)) {
506
+ get_info_results.available_isolation_levels = 0;
507
+ // this->errors = GetODBCErrors(SQL_HANDLE_DBC, hDBC);
508
+ // SetError("[odbc] Error getting information about available transaction isolation options from the connection");
509
+ // return;
510
+ }
511
+
512
+ SQLUINTEGER sql_getdata_extensions_bitmask;
513
+
514
+ // valid get data extensions
515
+ return_code =
516
+ SQLGetInfo
517
+ (
518
+ hDBC, // ConnectionHandle
519
+ SQL_GETDATA_EXTENSIONS, // InfoType
520
+ (SQLPOINTER) &sql_getdata_extensions_bitmask, // InfoValuePtr
521
+ IGNORED_PARAMETER, // BufferLength
522
+ IGNORED_PARAMETER // StringLengthPtr
523
+ );
524
+ if (!SQL_SUCCEEDED(return_code)) {
525
+ // Some drivers don't feel the need to implement the
526
+ // SQL_GETDATA_EXTENSIONS option for SQLGetInfo, so this call will
527
+ // return an error. Instead of returning an error, just set all of
528
+ // the SQLGetData extensions to false and continue.
529
+ get_info_results.sql_get_data_supports.any_column = false;
530
+ get_info_results.sql_get_data_supports.any_order = false;
531
+ get_info_results.sql_get_data_supports.block = false;
532
+ get_info_results.sql_get_data_supports.bound = false;
533
+ get_info_results.sql_get_data_supports.output_params = false;
534
+ } else {
535
+ // call the bitmask to populate the sql_get_data_supports struct
536
+ get_info_results.sql_get_data_supports.any_column =
537
+ (bool) (sql_getdata_extensions_bitmask & SQL_GD_ANY_COLUMN);
538
+ get_info_results.sql_get_data_supports.any_order =
539
+ (bool) (sql_getdata_extensions_bitmask & SQL_GD_ANY_ORDER);
540
+ get_info_results.sql_get_data_supports.block =
541
+ (bool) (sql_getdata_extensions_bitmask & SQL_GD_BLOCK);
542
+ get_info_results.sql_get_data_supports.bound =
543
+ (bool) (sql_getdata_extensions_bitmask & SQL_GD_BOUND);
544
+ get_info_results.sql_get_data_supports.output_params =
545
+ (bool) (sql_getdata_extensions_bitmask & SQL_GD_OUTPUT_PARAMS);
546
+ }
547
+ }
548
+
549
+ void OnOK() {
550
+
551
+ Napi::Env env = Env();
552
+ Napi::HandleScope scope(env);
553
+
554
+ // pass the HENV and HDBC values to the ODBCConnection constructor
555
+ std::vector<napi_value> connectionArguments;
556
+ connectionArguments.push_back(Napi::External<SQLHENV>::New(env, &hEnv)); // connectionArguments[0]
557
+ connectionArguments.push_back(Napi::External<SQLHDBC>::New(env, &hDBC)); // connectionArguments[1]
558
+ connectionArguments.push_back(Napi::External<ConnectionOptions>::New(env, options)); // connectionArguments[2]
559
+ connectionArguments.push_back(Napi::External<GetInfoResults>::New(env, &get_info_results)); // connectionArguments[3]
560
+
561
+ Napi::Value connection = ODBCConnection::constructor.New(connectionArguments);
562
+
563
+ // pass the arguments to the callback function
564
+ std::vector<napi_value> callbackArguments;
565
+ callbackArguments.push_back(env.Null()); // callbackArguments[0]
566
+ callbackArguments.push_back(connection); // callbackArguments[1]
567
+
568
+ Callback().Call(callbackArguments);
569
+ }
570
+
571
+ public:
572
+ ConnectAsyncWorker(HENV hEnv, SQLTCHAR *connectionStringPtr, ConnectionOptions *options, Napi::Function& callback) : ODBCAsyncWorker(callback),
573
+ connectionStringPtr(connectionStringPtr),
574
+ options(options),
575
+ hEnv(hEnv) {}
576
+
577
+ ~ConnectAsyncWorker() {
578
+ delete options;
579
+ delete[] connectionStringPtr;
580
+ }
581
+ };
582
+
583
+ // Connect
584
+ Napi::Value ODBC::Connect(const Napi::CallbackInfo& info) {
585
+
586
+ Napi::Env env = info.Env();
587
+ Napi::HandleScope scope(env);
588
+
589
+ Napi::String connectionString;
590
+ Napi::Function callback;
591
+
592
+ SQLTCHAR *connectionStringPtr = nullptr;
593
+
594
+ ConnectionOptions *options = new ConnectionOptions();
595
+ options->connectionTimeout = 0;
596
+ options->loginTimeout = 0;
597
+ options->fetchArray = false;
598
+
599
+ if(info.Length() != 2) {
600
+ Napi::TypeError::New(env, "connect(connectionString, callback) requires 2 parameters.").ThrowAsJavaScriptException();
601
+ return env.Null();
602
+ }
603
+
604
+ if (info[0].IsString()) {
605
+ connectionString = info[0].As<Napi::String>();
606
+ connectionStringPtr = ODBC::NapiStringToSQLTCHAR(connectionString);
607
+ } else if (info[0].IsObject()) {
608
+ Napi::Object connectionObject = info[0].As<Napi::Object>();
609
+ if (connectionObject.Has("connectionString") && connectionObject.Get("connectionString").IsString()) {
610
+ connectionString = connectionObject.Get("connectionString").As<Napi::String>();
611
+ connectionStringPtr = ODBC::NapiStringToSQLTCHAR(connectionString);
612
+ } else {
613
+ Napi::TypeError::New(env, "connect: A configuration object must have a 'connectionString' property that is a string.").ThrowAsJavaScriptException();
614
+ return env.Null();
615
+ }
616
+ if (connectionObject.Has("connectionTimeout") && connectionObject.Get("connectionTimeout").IsNumber()) {
617
+ options->connectionTimeout = connectionObject.Get("connectionTimeout").As<Napi::Number>().Int32Value();
618
+ }
619
+ if (connectionObject.Has("loginTimeout") && connectionObject.Get("loginTimeout").IsNumber()) {
620
+ options->loginTimeout = connectionObject.Get("loginTimeout").As<Napi::Number>().Int32Value();
621
+ }
622
+ if (connectionObject.Has("fetchArray") && connectionObject.Get("fetchArray").IsBoolean()) {
623
+ options->fetchArray = connectionObject.Get("fetchArray").As<Napi::Boolean>();
624
+ }
625
+ } else {
626
+ Napi::TypeError::New(env, "connect: first parameter must be a string or an object.").ThrowAsJavaScriptException();
627
+ return env.Null();
628
+ }
629
+
630
+ if (info[1].IsFunction()) {
631
+ callback = info[1].As<Napi::Function>();
632
+ } else {
633
+ Napi::TypeError::New(env, "connect: second parameter must be a function.").ThrowAsJavaScriptException();
634
+ return env.Null();
635
+ }
636
+
637
+ ConnectAsyncWorker *worker = new ConnectAsyncWorker(hEnv, connectionStringPtr, options, callback);
638
+ worker->Queue();
639
+
640
+ return env.Undefined();
641
+ }
642
+
643
+ ////////////////////////////////////////////////////////////////////////////////
644
+ ///////////////////////////// UTILITY FUNCTIONS ////////////////////////////////
645
+ ////////////////////////////////////////////////////////////////////////////////
646
+
647
+ // Take a Napi::String, and convert it to an SQLTCHAR*, which maps to:
648
+ // UNICODE : SQLWCHAR*
649
+ // no UNICODE : SQLCHAR*
650
+ SQLTCHAR* ODBC::NapiStringToSQLTCHAR(Napi::String string) {
651
+
652
+ size_t byteCount = 0;
653
+
654
+ #ifdef UNICODE
655
+ std::u16string tempString = string.Utf16Value();
656
+ byteCount = (tempString.length() + 1) * 2;
657
+ #else
658
+ std::string tempString = string.Utf8Value();
659
+ byteCount = tempString.length() + 1;
660
+ #endif
661
+ SQLTCHAR *sqlString = new SQLTCHAR[byteCount];
662
+ std::memcpy(sqlString, tempString.c_str(), byteCount);
663
+ return sqlString;
664
+ }
665
+
666
+ /******************************************************************************
667
+ **************************** BINDING PARAMETERS ******************************
668
+ *****************************************************************************/
669
+
670
+ /*
671
+ * GetParametersFromArray
672
+ * Array of parameters can hold either/and:
673
+ * Value:
674
+ * One value to bind, In/Out defaults to SQL_PARAM_INPUT, dataType defaults based on the value
675
+ * Arrays:ns when you elect n
676
+ * between 1 and 3 entries in lenth, with the following signfigance and default values:
677
+ * 1. Value (REQUIRED): The value to bind
678
+ * 2. In/Out (Optional): Defaults to SQL_PARAM_INPUT
679
+ * 3. DataType (Optional): Defaults based on the value
680
+ * Objects:
681
+ * can hold any of the following properties (but requires at least 'value' property)
682
+ * value (Requited): The value to bind
683
+ * inOut (Optional): the In/Out type to use, Defaults to SQL_PARAM_INPUT
684
+ * dataType (Optional): The data type, defaults based on the value
685
+ *
686
+ *
687
+ */
688
+
689
+
690
+ // This function solves the problem of losing access to "Napi::Value"s when entering
691
+ // an AsyncWorker. In Connection::Query, once we enter an AsyncWorker we do not leave it again,
692
+ // but we can't call SQLNumParams and SQLDescribeParam until after SQLPrepare. So the array of
693
+ // values to bind to parameters must be saved off in the closest, largest data type to then
694
+ // convert to the right C Type once the SQL Type of the parameter is known.
695
+ void ODBC::StoreBindValues(Napi::Array *values, Parameter **parameters) {
696
+
697
+ uint32_t numParameters = values->Length();
698
+
699
+ for (uint32_t i = 0; i < numParameters; i++) {
700
+
701
+ Napi::Value value = values->Get(i);
702
+ Parameter *parameter = parameters[i];
703
+
704
+ if(value.IsNull()) {
705
+ parameter->ValueType = SQL_C_DEFAULT;
706
+ parameter->ParameterValuePtr = NULL;
707
+ parameter->StrLen_or_IndPtr = SQL_NULL_DATA;
708
+ } else if (value.IsBigInt()) {
709
+ // TODO: need to check for signed/unsigned?
710
+ bool lossless = true;
711
+ parameter->ValueType = SQL_C_SBIGINT;
712
+ parameter->ParameterValuePtr = new SQLBIGINT(value.As<Napi::BigInt>().Int64Value(&lossless));
713
+ parameter->isbigint = true;
714
+ } else if (value.IsNumber()) {
715
+ double double_val = value.As<Napi::Number>().DoubleValue();
716
+ int64_t int_val = value.As<Napi::Number>().Int64Value();
717
+ if (double_val == int_val) {
718
+ parameter->ValueType = SQL_C_SBIGINT;
719
+ parameter->ParameterValuePtr = new SQLBIGINT(value.As<Napi::Number>().Int64Value());
720
+ parameter->isbigint = false;
721
+ } else {
722
+ parameter->ValueType = SQL_C_DOUBLE;
723
+ parameter->ParameterValuePtr = new SQLDOUBLE(value.As<Napi::Number>().DoubleValue());
724
+ }
725
+ } else if (value.IsBoolean()) {
726
+ parameter->ValueType = SQL_C_BIT;
727
+ parameter->ParameterValuePtr = new bool(value.As<Napi::Boolean>().Value());
728
+ } else if (value.IsBuffer()) {
729
+ Napi::Buffer<SQLCHAR> bufferValue = value.As<Napi::Buffer<SQLCHAR>>();
730
+ parameter->ValueType = SQL_C_BINARY;
731
+ parameter->BufferLength = bufferValue.Length();
732
+ parameter->ParameterValuePtr = new SQLCHAR[parameter->BufferLength]();
733
+ parameter->StrLen_or_IndPtr = parameter->BufferLength;
734
+ memcpy((SQLCHAR *) parameter->ParameterValuePtr, bufferValue.Data(), parameter->BufferLength);
735
+ } else if (value.IsArrayBuffer()) {
736
+ Napi::ArrayBuffer arrayBufferValue = value.As<Napi::ArrayBuffer>();
737
+ parameter->ValueType = SQL_C_BINARY;
738
+ parameter->BufferLength = arrayBufferValue.ByteLength();
739
+ parameter->ParameterValuePtr = new SQLCHAR[parameter->BufferLength];
740
+ parameter->StrLen_or_IndPtr = parameter->BufferLength;
741
+ memcpy((SQLCHAR *) parameter->ParameterValuePtr, arrayBufferValue.Data(), parameter->BufferLength);
742
+ } else if (value.IsString()) {
743
+ // Napi::String string = value.ToString();
744
+ // parameter->ValueType = SQL_C_WCHAR;
745
+ // parameter->BufferLength = (string.Utf16Value().length() + 1) * sizeof(SQLWCHAR);
746
+ // parameter->ParameterValuePtr = new SQLWCHAR[parameter->BufferLength];
747
+ // parameter->StrLen_or_IndPtr = SQL_NTS;
748
+ // memcpy((SQLWCHAR*) parameter->ParameterValuePtr, string.Utf16Value().c_str(), parameter->BufferLength);
749
+ Napi::String string = value.ToString();
750
+ parameter->ValueType = SQL_C_CHAR;
751
+ parameter->BufferLength = (string.Utf8Value().length() + 1);
752
+ parameter->ParameterValuePtr = new SQLCHAR[parameter->BufferLength]();
753
+ parameter->StrLen_or_IndPtr = SQL_NTS;
754
+ memcpy((SQLCHAR*) parameter->ParameterValuePtr, string.Utf8Value().c_str(), parameter->BufferLength);
755
+ } else {
756
+ // TODO: Throw error, don't support other types
757
+ }
758
+ }
759
+ }
760
+
761
+ SQLRETURN ODBC::DescribeParameters(SQLHSTMT hstmt, Parameter **parameters, SQLSMALLINT parameterCount) {
762
+
763
+ SQLRETURN return_code = SQL_SUCCESS; // if no parameters, will return SQL_SUCCESS
764
+
765
+ for (SQLSMALLINT i = 0; i < parameterCount; i++) {
766
+
767
+ Parameter *parameter = parameters[i];
768
+
769
+ // "Except in calls to procedures, all parameters in SQL statements are input parameters."
770
+ parameter->InputOutputType = SQL_PARAM_INPUT;
771
+
772
+ return_code =
773
+ SQLDescribeParam
774
+ (
775
+ hstmt, // StatementHandle,
776
+ i + 1, // ParameterNumber,
777
+ &parameter->ParameterType, // DataTypePtr,
778
+ &parameter->ColumnSize, // ParameterSizePtr,
779
+ &parameter->DecimalDigits, // DecimalDigitsPtr,
780
+ &parameter->Nullable // NullablePtr
781
+ );
782
+
783
+ // if there is an error, return early and retrieve error in calling function
784
+ if (!SQL_SUCCEEDED(return_code)) {
785
+ return return_code;
786
+ }
787
+ }
788
+
789
+ return return_code;
790
+ }
791
+
792
+ SQLRETURN ODBC::BindParameters(SQLHSTMT hstmt, Parameter **parameters, SQLSMALLINT parameterCount) {
793
+
794
+ SQLRETURN return_code = SQL_SUCCESS; // if no parameters, will return SQL_SUCCESS
795
+
796
+ for (int i = 0; i < parameterCount; i++) {
797
+
798
+ Parameter* parameter = parameters[i];
799
+
800
+ return_code = SQLBindParameter(
801
+ hstmt, // StatementHandle
802
+ i + 1, // ParameterNumber
803
+ parameter->InputOutputType, // InputOutputType
804
+ parameter->ValueType, // ValueType
805
+ parameter->ParameterType, // ParameterType
806
+ parameter->ColumnSize, // ColumnSize
807
+ parameter->DecimalDigits, // DecimalDigits
808
+ parameter->ParameterValuePtr, // ParameterValuePtr
809
+ parameter->BufferLength, // BufferLength
810
+ &parameter->StrLen_or_IndPtr // StrLen_or_IndPtr
811
+ );
812
+ // If there was an error, return early
813
+ if (!SQL_SUCCEEDED(return_code)) {
814
+ return return_code;
815
+ }
816
+ }
817
+
818
+ // If returns success, know that SQLBindParameter returned SUCCESS or
819
+ // SUCCESS_WITH_INFO for all calls to SQLBindParameter.
820
+ return return_code;
821
+ }
822
+
823
+ Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
824
+
825
+ ODBC::Init(env, exports);
826
+ ODBCConnection::Init(env, exports);
827
+ ODBCStatement::Init(env, exports);
828
+ ODBCCursor::Init(env, exports);
829
+
830
+ #ifdef dynodbc
831
+ exports.Set(Napi::String::New(env, "loadODBCLibrary"),
832
+ Napi::Function::New(env, ODBC::LoadODBCLibrary);());
833
+ #endif
834
+
835
+ return exports;
836
+ }
837
+
838
+ #ifdef dynodbc
839
+ Napi::Value ODBC::LoadODBCLibrary(const Napi::CallbackInfo& info) {
840
+ Napi::HandleScope scope(env);
841
+
842
+ REQ_STR_ARG(0, js_library);
843
+
844
+ bool result = DynLoadODBC(*js_library);
845
+
846
+ return (result) ? env.True() : env.False();W
847
+ }
848
+ #endif
849
+
850
+ NODE_API_MODULE(odbc_bindings, InitAll)