@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/CHANGELOG.md +179 -0
- package/LICENSE +23 -0
- package/README.md +1314 -0
- package/binding.gyp +100 -0
- package/lib/Connection.js +521 -0
- package/lib/Cursor.js +92 -0
- package/lib/Pool.js +470 -0
- package/lib/Statement.js +207 -0
- package/lib/bindings/napi-v8/odbc.node +0 -0
- package/lib/odbc.d.ts +210 -0
- package/lib/odbc.js +56 -0
- package/package.json +69 -0
- package/src/dynodbc.cpp +189 -0
- package/src/dynodbc.h +383 -0
- package/src/odbc.cpp +850 -0
- package/src/odbc.h +362 -0
- package/src/odbc_connection.cpp +4312 -0
- package/src/odbc_connection.h +114 -0
- package/src/odbc_cursor.cpp +265 -0
- package/src/odbc_cursor.h +52 -0
- package/src/odbc_statement.cpp +610 -0
- package/src/odbc_statement.h +43 -0
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
|
+
¶meter->ParameterType, // DataTypePtr,
|
|
778
|
+
¶meter->ColumnSize, // ParameterSizePtr,
|
|
779
|
+
¶meter->DecimalDigits, // DecimalDigitsPtr,
|
|
780
|
+
¶meter->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
|
+
¶meter->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)
|