@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
|
@@ -0,0 +1,4312 @@
|
|
|
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 "odbc.h"
|
|
20
|
+
#include "odbc_connection.h"
|
|
21
|
+
#include "odbc_statement.h"
|
|
22
|
+
#include "odbc_cursor.h"
|
|
23
|
+
|
|
24
|
+
#define MAX_UTF8_BYTES 4
|
|
25
|
+
|
|
26
|
+
// object keys for the result object
|
|
27
|
+
const char* NAME = "name";
|
|
28
|
+
const char* DATA_TYPE = "dataType";
|
|
29
|
+
const char* DATA_TYPE_NAME = "dataTypeName";
|
|
30
|
+
const char* COLUMN_SIZE = "columnSize";
|
|
31
|
+
const char* DECIMAL_DIGITS = "decimalDigits";
|
|
32
|
+
const char* NULLABLE = "nullable";
|
|
33
|
+
const char* STATEMENT = "statement";
|
|
34
|
+
const char* PARAMETERS = "parameters";
|
|
35
|
+
const char* RETURN = "return";
|
|
36
|
+
const char* COUNT = "count";
|
|
37
|
+
const char* COLUMNS = "columns";
|
|
38
|
+
|
|
39
|
+
Napi::FunctionReference ODBCConnection::constructor;
|
|
40
|
+
|
|
41
|
+
Napi::Object ODBCConnection::Init(Napi::Env env, Napi::Object exports) {
|
|
42
|
+
|
|
43
|
+
Napi::HandleScope scope(env);
|
|
44
|
+
|
|
45
|
+
Napi::Function constructorFunction = DefineClass(env, "ODBCConnection", {
|
|
46
|
+
|
|
47
|
+
InstanceMethod("close", &ODBCConnection::Close),
|
|
48
|
+
InstanceMethod("createStatement", &ODBCConnection::CreateStatement),
|
|
49
|
+
InstanceMethod("query", &ODBCConnection::Query),
|
|
50
|
+
InstanceMethod("beginTransaction", &ODBCConnection::BeginTransaction),
|
|
51
|
+
InstanceMethod("commit", &ODBCConnection::Commit),
|
|
52
|
+
InstanceMethod("rollback", &ODBCConnection::Rollback),
|
|
53
|
+
InstanceMethod("callProcedure", &ODBCConnection::CallProcedure),
|
|
54
|
+
InstanceMethod("getUsername", &ODBCConnection::GetUsername),
|
|
55
|
+
InstanceMethod("primaryKeys", &ODBCConnection::PrimaryKeys),
|
|
56
|
+
InstanceMethod("foreignKeys", &ODBCConnection::ForeignKeys),
|
|
57
|
+
InstanceMethod("tables", &ODBCConnection::Tables),
|
|
58
|
+
InstanceMethod("columns", &ODBCConnection::Columns),
|
|
59
|
+
InstanceMethod("setIsolationLevel", &ODBCConnection::SetIsolationLevel),
|
|
60
|
+
|
|
61
|
+
InstanceAccessor("connected", &ODBCConnection::ConnectedGetter, nullptr),
|
|
62
|
+
InstanceAccessor("autocommit", &ODBCConnection::AutocommitGetter, nullptr),
|
|
63
|
+
InstanceAccessor("connectionTimeout", &ODBCConnection::ConnectionTimeoutGetter, nullptr),
|
|
64
|
+
InstanceAccessor("loginTimeout", &ODBCConnection::LoginTimeoutGetter, nullptr)
|
|
65
|
+
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
constructor = Napi::Persistent(constructorFunction);
|
|
69
|
+
constructor.SuppressDestruct();
|
|
70
|
+
|
|
71
|
+
return exports;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/******************************************************************************
|
|
75
|
+
**************************** SET ISOLATION LEVEL *****************************
|
|
76
|
+
*****************************************************************************/
|
|
77
|
+
|
|
78
|
+
// CloseAsyncWorker, used by Close function (see below)
|
|
79
|
+
class SetIsolationLevelAsyncWorker : public ODBCAsyncWorker {
|
|
80
|
+
|
|
81
|
+
public:
|
|
82
|
+
SetIsolationLevelAsyncWorker(ODBCConnection *odbcConnectionObject, SQLUINTEGER isolationLevel, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
83
|
+
odbcConnectionObject(odbcConnectionObject),
|
|
84
|
+
isolationLevel(isolationLevel){}
|
|
85
|
+
|
|
86
|
+
~SetIsolationLevelAsyncWorker() {}
|
|
87
|
+
|
|
88
|
+
private:
|
|
89
|
+
ODBCConnection *odbcConnectionObject;
|
|
90
|
+
SQLUINTEGER isolationLevel;
|
|
91
|
+
|
|
92
|
+
void Execute() {
|
|
93
|
+
|
|
94
|
+
SQLRETURN return_code;
|
|
95
|
+
|
|
96
|
+
return_code = SQLSetConnectAttr(
|
|
97
|
+
odbcConnectionObject->hDBC, // ConnectionHandle
|
|
98
|
+
SQL_ATTR_TXN_ISOLATION, // Attribute
|
|
99
|
+
(SQLPOINTER) (uintptr_t) isolationLevel, // ValuePtr
|
|
100
|
+
SQL_NTS // StringLength
|
|
101
|
+
);
|
|
102
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
103
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
104
|
+
SetError("[odbc] Error setting isolation level\0");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
void OnOK() {
|
|
110
|
+
|
|
111
|
+
Napi::Env env = Env();
|
|
112
|
+
Napi::HandleScope scope(env);
|
|
113
|
+
|
|
114
|
+
std::vector<napi_value> callbackArguments;
|
|
115
|
+
callbackArguments.push_back(env.Null());
|
|
116
|
+
|
|
117
|
+
Callback().Call(callbackArguments);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
Napi::Value ODBCConnection::SetIsolationLevel(const Napi::CallbackInfo &info) {
|
|
122
|
+
|
|
123
|
+
Napi::Env env = info.Env();
|
|
124
|
+
Napi::HandleScope scope(env);
|
|
125
|
+
|
|
126
|
+
SQLUINTEGER isolationLevel = info[0].As<Napi::Number>().Uint32Value();
|
|
127
|
+
Napi::Function callback = info[1].As<Napi::Function>();
|
|
128
|
+
|
|
129
|
+
if (!(isolationLevel & this->getInfoResults.available_isolation_levels)) {
|
|
130
|
+
std::vector<napi_value> callbackArguments;
|
|
131
|
+
callbackArguments.push_back(Napi::Error::New(env, "Isolation level passed to setIsolationLevel is not valid for the connection!").Value());
|
|
132
|
+
callback.Call(callbackArguments);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
SetIsolationLevelAsyncWorker *worker = new SetIsolationLevelAsyncWorker(this, isolationLevel, callback);
|
|
136
|
+
worker->Queue();
|
|
137
|
+
|
|
138
|
+
return env.Undefined();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Napi::Value ODBCConnection::AutocommitGetter(const Napi::CallbackInfo& info) {
|
|
142
|
+
Napi::Env env = info.Env();
|
|
143
|
+
Napi::HandleScope scope(env);
|
|
144
|
+
|
|
145
|
+
SQLINTEGER autocommit;
|
|
146
|
+
|
|
147
|
+
SQLGetConnectAttr(
|
|
148
|
+
this->hDBC, // ConnectionHandle
|
|
149
|
+
SQL_ATTR_AUTOCOMMIT, // Attribute
|
|
150
|
+
&autocommit, // ValuePtr
|
|
151
|
+
0, // BufferLength
|
|
152
|
+
NULL // StringLengthPtr
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (autocommit == SQL_AUTOCOMMIT_OFF) {
|
|
156
|
+
return Napi::Boolean::New(env, false);
|
|
157
|
+
} else if (autocommit == SQL_AUTOCOMMIT_ON) {
|
|
158
|
+
return Napi::Boolean::New(env, true);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return Napi::Boolean::New(env, false);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
ODBCConnection::ODBCConnection(const Napi::CallbackInfo& info) : Napi::ObjectWrap<ODBCConnection>(info) {
|
|
165
|
+
|
|
166
|
+
this->hENV = *(info[0].As<Napi::External<SQLHENV>>().Data());
|
|
167
|
+
this->hDBC = *(info[1].As<Napi::External<SQLHDBC>>().Data());
|
|
168
|
+
this->connectionOptions = *(info[2].As<Napi::External<ConnectionOptions>>().Data());
|
|
169
|
+
this->getInfoResults = *(info[3].As<Napi::External<GetInfoResults>>().Data());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
ODBCConnection::~ODBCConnection()
|
|
174
|
+
{
|
|
175
|
+
this->Free();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
SQLRETURN ODBCConnection::Free() {
|
|
179
|
+
|
|
180
|
+
SQLRETURN return_code = SQL_SUCCESS;
|
|
181
|
+
|
|
182
|
+
if (this->hDBC != SQL_NULL_HANDLE)
|
|
183
|
+
{
|
|
184
|
+
{
|
|
185
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
186
|
+
return_code =
|
|
187
|
+
SQLFreeHandle
|
|
188
|
+
(
|
|
189
|
+
SQL_HANDLE_DBC,
|
|
190
|
+
this->hDBC
|
|
191
|
+
);
|
|
192
|
+
this->hDBC = SQL_NULL_HANDLE;
|
|
193
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return return_code;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
Napi::Value ODBCConnection::ConnectedGetter(const Napi::CallbackInfo& info) {
|
|
201
|
+
Napi::Env env = info.Env();
|
|
202
|
+
Napi::HandleScope scope(env);
|
|
203
|
+
|
|
204
|
+
SQLINTEGER connection = SQL_CD_TRUE;
|
|
205
|
+
|
|
206
|
+
SQLGetConnectAttr(
|
|
207
|
+
this->hDBC, // ConnectionHandle
|
|
208
|
+
SQL_ATTR_CONNECTION_DEAD, // Attribute
|
|
209
|
+
&connection, // ValuePtr
|
|
210
|
+
0, // BufferLength
|
|
211
|
+
NULL // StringLengthPtr
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (connection == SQL_CD_FALSE) { // connection dead is false
|
|
215
|
+
return Napi::Boolean::New(env, true);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return Napi::Boolean::New(env, false);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
Napi::Value ODBCConnection::ConnectionTimeoutGetter(const Napi::CallbackInfo& info)
|
|
222
|
+
{
|
|
223
|
+
|
|
224
|
+
Napi::Env env = info.Env();
|
|
225
|
+
Napi::HandleScope scope(env);
|
|
226
|
+
|
|
227
|
+
return Napi::Number::New(env, this->connectionOptions.connectionTimeout);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
Napi::Value ODBCConnection::LoginTimeoutGetter(const Napi::CallbackInfo& info)
|
|
231
|
+
{
|
|
232
|
+
|
|
233
|
+
Napi::Env env = info.Env();
|
|
234
|
+
Napi::HandleScope scope(env);
|
|
235
|
+
|
|
236
|
+
return Napi::Number::New(env, this->connectionOptions.loginTimeout);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/******************************************************************************
|
|
240
|
+
********************************** CLOSE *************************************
|
|
241
|
+
*****************************************************************************/
|
|
242
|
+
|
|
243
|
+
// CloseAsyncWorker, used by Close function (see below)
|
|
244
|
+
class CloseAsyncWorker : public ODBCAsyncWorker {
|
|
245
|
+
|
|
246
|
+
public:
|
|
247
|
+
CloseAsyncWorker(ODBCConnection *odbcConnectionObject, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
248
|
+
odbcConnectionObject(odbcConnectionObject) {}
|
|
249
|
+
|
|
250
|
+
~CloseAsyncWorker() {}
|
|
251
|
+
|
|
252
|
+
private:
|
|
253
|
+
ODBCConnection *odbcConnectionObject;
|
|
254
|
+
|
|
255
|
+
void Execute() {
|
|
256
|
+
|
|
257
|
+
SQLRETURN return_code;
|
|
258
|
+
|
|
259
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
260
|
+
// When closing, make sure any transactions are closed as well. Because we don't know whether
|
|
261
|
+
// we should commit or rollback, so we default to rollback.
|
|
262
|
+
if (odbcConnectionObject->hDBC != SQL_NULL_HANDLE) {
|
|
263
|
+
return_code = SQLEndTran(
|
|
264
|
+
SQL_HANDLE_DBC, // HandleType
|
|
265
|
+
odbcConnectionObject->hDBC, // Handle
|
|
266
|
+
SQL_ROLLBACK // CompletionType
|
|
267
|
+
);
|
|
268
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
269
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
270
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
271
|
+
SetError("[odbc] Error ending potential transactions when closing the connection\0");
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return_code = SQLDisconnect(
|
|
276
|
+
odbcConnectionObject->hDBC // ConnectionHandle
|
|
277
|
+
);
|
|
278
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
279
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
280
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
281
|
+
SetError("[odbc] Error disconnecting when closing the connection\0");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return_code = SQLFreeHandle(
|
|
286
|
+
SQL_HANDLE_DBC, // HandleType
|
|
287
|
+
odbcConnectionObject->hDBC // Handle
|
|
288
|
+
);
|
|
289
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
290
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
291
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
292
|
+
SetError("[odbc] Error freeing connection handle when closing the connection\0");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
odbcConnectionObject->hDBC = SQL_NULL_HANDLE;
|
|
296
|
+
}
|
|
297
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
void OnOK() {
|
|
301
|
+
|
|
302
|
+
Napi::Env env = Env();
|
|
303
|
+
Napi::HandleScope scope(env);
|
|
304
|
+
|
|
305
|
+
std::vector<napi_value> callbackArguments;
|
|
306
|
+
callbackArguments.push_back(env.Null());
|
|
307
|
+
|
|
308
|
+
Callback().Call(callbackArguments);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
/*
|
|
313
|
+
* ODBCConnection::Close (Async)
|
|
314
|
+
*
|
|
315
|
+
* Description: Closes the connection asynchronously.
|
|
316
|
+
*
|
|
317
|
+
* Parameters:
|
|
318
|
+
*
|
|
319
|
+
* const Napi::CallbackInfo& info:
|
|
320
|
+
* The information passed by Napi from the JavaScript call, including
|
|
321
|
+
* arguments from the JavaScript function.
|
|
322
|
+
*
|
|
323
|
+
* info[0]: Function: callback function, in the following format:
|
|
324
|
+
* function(error)
|
|
325
|
+
* error: An error object if the connection was not closed, or
|
|
326
|
+
* null if operation was successful.
|
|
327
|
+
*
|
|
328
|
+
* Return:
|
|
329
|
+
* Napi::Value:
|
|
330
|
+
* Undefined. (The return values are attached to the callback function).
|
|
331
|
+
*/
|
|
332
|
+
Napi::Value ODBCConnection::Close(const Napi::CallbackInfo& info) {
|
|
333
|
+
|
|
334
|
+
Napi::Env env = info.Env();
|
|
335
|
+
Napi::HandleScope scope(env);
|
|
336
|
+
|
|
337
|
+
Napi::Function callback = info[0].As<Napi::Function>();
|
|
338
|
+
|
|
339
|
+
CloseAsyncWorker *worker = new CloseAsyncWorker(this, callback);
|
|
340
|
+
worker->Queue();
|
|
341
|
+
|
|
342
|
+
return env.Undefined();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/******************************************************************************
|
|
346
|
+
***************************** CREATE STATEMENT *******************************
|
|
347
|
+
*****************************************************************************/
|
|
348
|
+
|
|
349
|
+
// CreateStatementAsyncWorker, used by CreateStatement function (see below)
|
|
350
|
+
class CreateStatementAsyncWorker : public ODBCAsyncWorker {
|
|
351
|
+
|
|
352
|
+
private:
|
|
353
|
+
ODBCConnection *odbcConnectionObject;
|
|
354
|
+
HSTMT hstmt;
|
|
355
|
+
|
|
356
|
+
void Execute() {
|
|
357
|
+
|
|
358
|
+
SQLRETURN return_code;
|
|
359
|
+
|
|
360
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
361
|
+
return_code =
|
|
362
|
+
SQLAllocHandle
|
|
363
|
+
(
|
|
364
|
+
SQL_HANDLE_STMT, // HandleType
|
|
365
|
+
odbcConnectionObject->hDBC, // InputHandle
|
|
366
|
+
&hstmt // OutputHandlePtr
|
|
367
|
+
);
|
|
368
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
369
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
370
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, hstmt);
|
|
371
|
+
SetError("[odbc] Error allocating a handle to create a new Statement\0");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
void OnOK() {
|
|
377
|
+
|
|
378
|
+
Napi::Env env = Env();
|
|
379
|
+
Napi::HandleScope scope(env);
|
|
380
|
+
|
|
381
|
+
// arguments for the ODBCStatement constructor
|
|
382
|
+
std::vector<napi_value> statementArguments;
|
|
383
|
+
statementArguments.push_back(Napi::External<ODBCConnection>::New(env, odbcConnectionObject));
|
|
384
|
+
statementArguments.push_back(Napi::External<HSTMT>::New(env, &hstmt));
|
|
385
|
+
|
|
386
|
+
// create a new ODBCStatement object as a Napi::Value
|
|
387
|
+
Napi::Value statementObject = ODBCStatement::constructor.New(statementArguments);
|
|
388
|
+
|
|
389
|
+
std::vector<napi_value> callbackArguments;
|
|
390
|
+
callbackArguments.push_back(env.Null()); // callbackArguments[0]
|
|
391
|
+
callbackArguments.push_back(statementObject); // callbackArguments[1]
|
|
392
|
+
|
|
393
|
+
Callback().Call(callbackArguments);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public:
|
|
397
|
+
CreateStatementAsyncWorker(ODBCConnection *odbcConnectionObject, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
398
|
+
odbcConnectionObject(odbcConnectionObject) {}
|
|
399
|
+
|
|
400
|
+
~CreateStatementAsyncWorker() {}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
/*
|
|
404
|
+
* ODBCConnection::CreateStatement
|
|
405
|
+
*
|
|
406
|
+
* Description: Create an ODBCStatement to manually prepare, bind, and
|
|
407
|
+
* execute.
|
|
408
|
+
*
|
|
409
|
+
* Parameters:
|
|
410
|
+
* const Napi::CallbackInfo& info:
|
|
411
|
+
* The information passed from the JavaSript environment, including the
|
|
412
|
+
* function arguments for 'endTransactionSync'.
|
|
413
|
+
*
|
|
414
|
+
* info[0]: Function: callback function:
|
|
415
|
+
* function(error, statement)
|
|
416
|
+
* error: An error object if there was an error creating the
|
|
417
|
+
* statement, or null if operation was successful.
|
|
418
|
+
* statement: The newly created ODBCStatement object
|
|
419
|
+
*
|
|
420
|
+
* Return:
|
|
421
|
+
* Napi::Value:
|
|
422
|
+
* Undefined (results returned in callback)
|
|
423
|
+
*/
|
|
424
|
+
Napi::Value ODBCConnection::CreateStatement(const Napi::CallbackInfo& info) {
|
|
425
|
+
|
|
426
|
+
Napi::Env env = info.Env();
|
|
427
|
+
Napi::HandleScope scope(env);
|
|
428
|
+
|
|
429
|
+
Napi::Function callback = info[0].As<Napi::Function>();
|
|
430
|
+
|
|
431
|
+
CreateStatementAsyncWorker *worker = new CreateStatementAsyncWorker(this, callback);
|
|
432
|
+
worker->Queue();
|
|
433
|
+
|
|
434
|
+
return env.Undefined();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/******************************************************************************
|
|
438
|
+
********************************** QUERY *************************************
|
|
439
|
+
*****************************************************************************/
|
|
440
|
+
|
|
441
|
+
Napi::Value
|
|
442
|
+
parse_query_options
|
|
443
|
+
(
|
|
444
|
+
Napi::Env env,
|
|
445
|
+
Napi::Value options_value,
|
|
446
|
+
QueryOptions *query_options
|
|
447
|
+
)
|
|
448
|
+
{
|
|
449
|
+
query_options->reset();
|
|
450
|
+
|
|
451
|
+
if (options_value.IsNull())
|
|
452
|
+
{
|
|
453
|
+
return env.Null();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
Napi::Object options_object = options_value.As<Napi::Object>();
|
|
457
|
+
|
|
458
|
+
// .cursor property
|
|
459
|
+
if (options_object.HasOwnProperty(QueryOptions::CURSOR_PROPERTY))
|
|
460
|
+
{
|
|
461
|
+
Napi::Value cursor_value =
|
|
462
|
+
options_object.Get(QueryOptions::CURSOR_PROPERTY);
|
|
463
|
+
|
|
464
|
+
if (!(cursor_value.IsString() || cursor_value.IsBoolean()))
|
|
465
|
+
{
|
|
466
|
+
return Napi::TypeError::New(env, std::string("Connection.query options: .") + QueryOptions::CURSOR_PROPERTY + " must be a STRING or BOOLEAN value.").Value();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (cursor_value.IsString())
|
|
470
|
+
{
|
|
471
|
+
query_options->use_cursor = true;
|
|
472
|
+
#ifdef UNICODE
|
|
473
|
+
std::u16string cursor_string;
|
|
474
|
+
cursor_string = cursor_value.As<Napi::String>().Utf16Value();
|
|
475
|
+
#else
|
|
476
|
+
std::string cursor_string;
|
|
477
|
+
cursor_string = cursor_value.As<Napi::String>().Utf8Value();
|
|
478
|
+
#endif
|
|
479
|
+
query_options->cursor_name = (SQLTCHAR *)cursor_string.c_str();
|
|
480
|
+
query_options->cursor_name_length = cursor_string.length();
|
|
481
|
+
}
|
|
482
|
+
else if (cursor_value.IsBoolean())
|
|
483
|
+
{
|
|
484
|
+
query_options->use_cursor = cursor_value.As<Napi::Boolean>().Value();
|
|
485
|
+
query_options->cursor_name = NULL;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// END .cursor property
|
|
489
|
+
|
|
490
|
+
// .fetchSize property
|
|
491
|
+
if (options_object.HasOwnProperty(QueryOptions::FETCH_SIZE_PROPERTY))
|
|
492
|
+
{
|
|
493
|
+
Napi::Value fetch_size_value =
|
|
494
|
+
options_object.Get(QueryOptions::FETCH_SIZE_PROPERTY);
|
|
495
|
+
|
|
496
|
+
if (!fetch_size_value.IsNumber())
|
|
497
|
+
{
|
|
498
|
+
return Napi::TypeError::New(env, std::string("Connection.query options: .") + QueryOptions::FETCH_SIZE_PROPERTY + " must be a NUMBER value.").Value();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
int64_t temp_value = fetch_size_value.As<Napi::Number>().Int64Value();
|
|
502
|
+
|
|
503
|
+
if (temp_value < 1)
|
|
504
|
+
{
|
|
505
|
+
return Napi::RangeError::New(env, std::string("Connection.query options: .") + QueryOptions::FETCH_SIZE_PROPERTY + " must be greater than 0.").Value();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// even if the user didn't explicitly set use_cursor to true, if they are
|
|
509
|
+
// passing a fetch size, it should be assumed.
|
|
510
|
+
query_options->use_cursor = true;
|
|
511
|
+
query_options->fetch_size = (SQLULEN) temp_value;
|
|
512
|
+
}
|
|
513
|
+
// END .fetchSize property
|
|
514
|
+
|
|
515
|
+
// .timeout property
|
|
516
|
+
if (options_object.HasOwnProperty(QueryOptions::TIMEOUT_PROPERTY))
|
|
517
|
+
{
|
|
518
|
+
Napi::Value timeout_value =
|
|
519
|
+
options_object.Get(QueryOptions::TIMEOUT_PROPERTY);
|
|
520
|
+
|
|
521
|
+
if (!timeout_value.IsNumber())
|
|
522
|
+
{
|
|
523
|
+
return Napi::TypeError::New(env, std::string("Connection.query options: .") + QueryOptions::TIMEOUT_PROPERTY + " must be a NUMBER value.").Value();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
int64_t temp_value = timeout_value.As<Napi::Number>().Int64Value();
|
|
527
|
+
|
|
528
|
+
if (temp_value < 1)
|
|
529
|
+
{
|
|
530
|
+
return Napi::RangeError::New(env, std::string("Connection.query options: .") + QueryOptions::TIMEOUT_PROPERTY + " must be greater than 0.").Value();
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
query_options->timeout = (SQLULEN) temp_value;
|
|
534
|
+
}
|
|
535
|
+
// END .timeout property
|
|
536
|
+
|
|
537
|
+
// .initialBufferSize property
|
|
538
|
+
if (options_object.HasOwnProperty(QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY))
|
|
539
|
+
{
|
|
540
|
+
Napi::Value initial_long_data_buffer_size_value =
|
|
541
|
+
options_object.Get(QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY);
|
|
542
|
+
|
|
543
|
+
if (!initial_long_data_buffer_size_value.IsNumber())
|
|
544
|
+
{
|
|
545
|
+
return Napi::TypeError::New(env, std::string("Connection.query options: .") + QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY + " must be a NUMBER value.").Value();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
int64_t temp_value = initial_long_data_buffer_size_value.As<Napi::Number>().Int64Value();
|
|
549
|
+
|
|
550
|
+
if (temp_value < 1)
|
|
551
|
+
{
|
|
552
|
+
return Napi::RangeError::New(env, std::string("Connection.query options: .") + QueryOptions::INITIAL_BUFFER_SIZE_PROPERTY + " must be greater than 0.").Value();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
query_options->initial_long_data_buffer_size = (SQLLEN) temp_value;
|
|
556
|
+
}
|
|
557
|
+
// END .initialBufferSize
|
|
558
|
+
|
|
559
|
+
return env.Null();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
SQLRETURN
|
|
563
|
+
set_fetch_size
|
|
564
|
+
(
|
|
565
|
+
StatementData *data,
|
|
566
|
+
SQLULEN fetch_size
|
|
567
|
+
)
|
|
568
|
+
{
|
|
569
|
+
SQLRETURN return_code;
|
|
570
|
+
|
|
571
|
+
return_code =
|
|
572
|
+
SQLSetStmtAttr
|
|
573
|
+
(
|
|
574
|
+
data->hstmt,
|
|
575
|
+
SQL_ATTR_ROW_ARRAY_SIZE,
|
|
576
|
+
(SQLPOINTER) fetch_size,
|
|
577
|
+
0
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
data->fetch_size = fetch_size;
|
|
581
|
+
|
|
582
|
+
if (!SQL_SUCCEEDED(return_code))
|
|
583
|
+
{
|
|
584
|
+
// Some drivers don't feel the need to implement the
|
|
585
|
+
// SQL_ATTR_ROW_ARRAY_SIZE option for SQLSetStmtAttr, so this call will
|
|
586
|
+
// return an error. Instead of returning an error, first check that the
|
|
587
|
+
// fetch size wasn't set to something bigger than 1, then continue. If the
|
|
588
|
+
// user set the fetch size, then throw an error to let them know that they
|
|
589
|
+
// should rerun with a fetch size of 1.
|
|
590
|
+
if (fetch_size != 1)
|
|
591
|
+
{
|
|
592
|
+
// The user set a fetch size, but their driver doesn't support this call.
|
|
593
|
+
return return_code;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
SQLRETURN diagnostic_return_code;
|
|
597
|
+
SQLSMALLINT textLength;
|
|
598
|
+
SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1];
|
|
599
|
+
SQLINTEGER nativeError;
|
|
600
|
+
SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES];
|
|
601
|
+
|
|
602
|
+
diagnostic_return_code =
|
|
603
|
+
SQLGetDiagRec
|
|
604
|
+
(
|
|
605
|
+
SQL_HANDLE_STMT, // HandleType
|
|
606
|
+
data->hstmt, // Handle
|
|
607
|
+
1, // RecNumber
|
|
608
|
+
errorSQLState, // SQLState
|
|
609
|
+
&nativeError, // NativeErrorPtr
|
|
610
|
+
errorMessage, // MessageText
|
|
611
|
+
ERROR_MESSAGE_BUFFER_CHARS, // BufferLength
|
|
612
|
+
&textLength // TextLengthPtr
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
if (!SQL_SUCCEEDED(diagnostic_return_code))
|
|
616
|
+
{
|
|
617
|
+
return return_code;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// HY092 is the SQLState produced by drivers that are known to not implement
|
|
621
|
+
// SQL_ATTR_ROW_ARRAY_SIZE. Set that return code to SQL_SUCCESS, since fetch
|
|
622
|
+
// size was 1 anyways. We will set simple_binding below
|
|
623
|
+
if (strcmp("HY092", (const char*)errorSQLState) == 0)
|
|
624
|
+
{
|
|
625
|
+
return_code = SQL_SUCCESS;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return return_code;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// SQLSetStmtAttr with option SQL_ATTR_ROW_ARRAY_SIZE can reutrn SQLSTATE of
|
|
632
|
+
// 01S02, indicating that "The driver did not support the value specified ...
|
|
633
|
+
// so the driver substituted a similar value". If return code is
|
|
634
|
+
// SQL_SUCCESS_WITH_INFO, check if the SQLSTATE is 01S02, and then get the
|
|
635
|
+
// substituted value to store. We don't save off any other warnings, just
|
|
636
|
+
// check for that particular SQLSTATE.
|
|
637
|
+
if (return_code == SQL_SUCCESS_WITH_INFO)
|
|
638
|
+
{
|
|
639
|
+
SQLRETURN diagnostic_return_code;
|
|
640
|
+
SQLSMALLINT textLength;
|
|
641
|
+
SQLINTEGER statusRecCount;
|
|
642
|
+
SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1];
|
|
643
|
+
SQLINTEGER nativeError;
|
|
644
|
+
SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES];
|
|
645
|
+
|
|
646
|
+
diagnostic_return_code =
|
|
647
|
+
SQLGetDiagField (
|
|
648
|
+
SQL_HANDLE_STMT, // HandleType
|
|
649
|
+
data->hstmt, // Handle
|
|
650
|
+
0, // RecNumber
|
|
651
|
+
SQL_DIAG_NUMBER, // DiagIdentifier
|
|
652
|
+
&statusRecCount, // DiagInfoPtr
|
|
653
|
+
SQL_IS_INTEGER, // BufferLength
|
|
654
|
+
NULL // StringLengthPtr
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
for (SQLSMALLINT i = 0; i < statusRecCount; i++) {
|
|
658
|
+
|
|
659
|
+
diagnostic_return_code =
|
|
660
|
+
SQLGetDiagRec
|
|
661
|
+
(
|
|
662
|
+
SQL_HANDLE_STMT, // HandleType
|
|
663
|
+
data->hstmt, // Handle
|
|
664
|
+
i + 1, // RecNumber
|
|
665
|
+
errorSQLState, // SQLState
|
|
666
|
+
&nativeError, // NativeErrorPtr
|
|
667
|
+
errorMessage, // MessageText
|
|
668
|
+
ERROR_MESSAGE_BUFFER_CHARS, // BufferLength
|
|
669
|
+
&textLength // TextLengthPtr
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
if (SQL_SUCCEEDED(diagnostic_return_code))
|
|
673
|
+
{
|
|
674
|
+
if (strcmp("01S02", (const char*)errorSQLState) == 0)
|
|
675
|
+
{
|
|
676
|
+
// We found the SQLSTATE we were looking for, need to get the
|
|
677
|
+
// driver-substituted vaue
|
|
678
|
+
return_code =
|
|
679
|
+
SQLGetStmtAttr
|
|
680
|
+
(
|
|
681
|
+
data->hstmt,
|
|
682
|
+
SQL_ATTR_ROW_ARRAY_SIZE,
|
|
683
|
+
(SQLPOINTER) &data->fetch_size,
|
|
684
|
+
SQL_IS_INTEGER,
|
|
685
|
+
IGNORED_PARAMETER
|
|
686
|
+
);
|
|
687
|
+
|
|
688
|
+
if (!SQL_SUCCEEDED(return_code))
|
|
689
|
+
{
|
|
690
|
+
return return_code;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
} else {
|
|
694
|
+
return return_code;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
data->row_status_array =
|
|
700
|
+
new SQLUSMALLINT[fetch_size]();
|
|
701
|
+
|
|
702
|
+
return_code =
|
|
703
|
+
SQLSetStmtAttr
|
|
704
|
+
(
|
|
705
|
+
data->hstmt,
|
|
706
|
+
SQL_ATTR_ROW_STATUS_PTR,
|
|
707
|
+
(SQLPOINTER) data->row_status_array,
|
|
708
|
+
0
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
return return_code;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// QueryAsyncWorker, used by Query function (see below)
|
|
715
|
+
class QueryAsyncWorker : public ODBCAsyncWorker {
|
|
716
|
+
|
|
717
|
+
private:
|
|
718
|
+
|
|
719
|
+
ODBCConnection *odbcConnectionObject;
|
|
720
|
+
Napi::Reference<Napi::Array> napiParameters;
|
|
721
|
+
StatementData *data;
|
|
722
|
+
|
|
723
|
+
void Execute() {
|
|
724
|
+
|
|
725
|
+
SQLRETURN return_code;
|
|
726
|
+
|
|
727
|
+
// allocate a new statement handle
|
|
728
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
729
|
+
if (odbcConnectionObject->hDBC == SQL_NULL_HANDLE) {
|
|
730
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
731
|
+
SetError("[odbc] Database connection handle was no longer valid. Cannot run a query after closing the connection.");
|
|
732
|
+
return;
|
|
733
|
+
} else {
|
|
734
|
+
return_code = SQLAllocHandle(
|
|
735
|
+
SQL_HANDLE_STMT, // HandleType
|
|
736
|
+
odbcConnectionObject->hDBC, // InputHandle
|
|
737
|
+
&(data->hstmt) // OutputHandlePtr
|
|
738
|
+
);
|
|
739
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
740
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
741
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
742
|
+
SetError("[odbc] Error allocating a handle to run the SQL statement\0");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// set SQL_ATTR_QUERY_TIMEOUT
|
|
747
|
+
if (data->query_options.timeout > 0) {
|
|
748
|
+
return_code =
|
|
749
|
+
SQLSetStmtAttr
|
|
750
|
+
(
|
|
751
|
+
data->hstmt,
|
|
752
|
+
SQL_ATTR_QUERY_TIMEOUT,
|
|
753
|
+
(SQLPOINTER) data->query_options.timeout,
|
|
754
|
+
IGNORED_PARAMETER
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
// It is possible that SQLSetStmtAttr returns a warning with SQLSTATE
|
|
758
|
+
// 01S02, indicating that the driver changed the value specified.
|
|
759
|
+
// Although we never use the timeout variable again (and so we don't
|
|
760
|
+
// REALLY need it to be correct in the code), its just good to have
|
|
761
|
+
// the correct value if we need it.
|
|
762
|
+
if (return_code == SQL_SUCCESS_WITH_INFO)
|
|
763
|
+
{
|
|
764
|
+
return_code =
|
|
765
|
+
SQLGetStmtAttr
|
|
766
|
+
(
|
|
767
|
+
data->hstmt,
|
|
768
|
+
SQL_ATTR_QUERY_TIMEOUT,
|
|
769
|
+
(SQLPOINTER) &data->query_options.timeout,
|
|
770
|
+
SQL_IS_UINTEGER,
|
|
771
|
+
IGNORED_PARAMETER
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Both of the SQL_ATTR_QUERY_TIMEOUT calls are combined here
|
|
776
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
777
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
778
|
+
SetError("[odbc] Error setting the query timeout on the statement\0");
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// querying with parameters, need to prepare, bind, execute
|
|
784
|
+
if (data->parameterCount > 0) {
|
|
785
|
+
// binds all parameters to the query
|
|
786
|
+
return_code =
|
|
787
|
+
SQLPrepare
|
|
788
|
+
(
|
|
789
|
+
data->hstmt,
|
|
790
|
+
data->sql,
|
|
791
|
+
SQL_NTS
|
|
792
|
+
);
|
|
793
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
794
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
795
|
+
SetError("[odbc] Error preparing the SQL statement\0");
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
SQLSMALLINT parameterMarkerCount;
|
|
800
|
+
|
|
801
|
+
return_code =
|
|
802
|
+
SQLNumParams
|
|
803
|
+
(
|
|
804
|
+
data->hstmt,
|
|
805
|
+
¶meterMarkerCount
|
|
806
|
+
);
|
|
807
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
808
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
809
|
+
SetError("[odbc] Error getting information about the number of parameter markers in the statment\0");
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (parameterMarkerCount != data->parameterCount) {
|
|
814
|
+
SetError("[odbc] The number of parameter markers in the statement does not equal the number of bind values passed to the function.");
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
return_code = ODBC::DescribeParameters(data->hstmt, data->parameters, data->parameterCount);
|
|
819
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
820
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
821
|
+
SetError("[odbc] Error getting information about parameters\0");
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return_code = ODBC::BindParameters(data->hstmt, data->parameters, data->parameterCount);
|
|
826
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
827
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
828
|
+
SetError("[odbc] Error binding parameters\0");
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return_code = SQLExecute(data->hstmt);
|
|
833
|
+
if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) {
|
|
834
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
835
|
+
SetError("[odbc] Error executing the sql statement\0");
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// querying without parameters, can just use SQLExecDirect
|
|
840
|
+
else {
|
|
841
|
+
return_code =
|
|
842
|
+
SQLExecDirect
|
|
843
|
+
(
|
|
844
|
+
data->hstmt,
|
|
845
|
+
data->sql,
|
|
846
|
+
SQL_NTS
|
|
847
|
+
);
|
|
848
|
+
if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) {
|
|
849
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
850
|
+
SetError("[odbc] Error executing the sql statement\0");
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (return_code != SQL_NO_DATA) {
|
|
856
|
+
|
|
857
|
+
if (data->query_options.use_cursor)
|
|
858
|
+
{
|
|
859
|
+
if (data->query_options.cursor_name != NULL)
|
|
860
|
+
{
|
|
861
|
+
return_code =
|
|
862
|
+
SQLSetCursorName
|
|
863
|
+
(
|
|
864
|
+
data->hstmt,
|
|
865
|
+
data->query_options.cursor_name,
|
|
866
|
+
data->query_options.cursor_name_length
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
870
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
871
|
+
SetError("[odbc] Error setting the cursor name on the statement\0");
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return_code =
|
|
878
|
+
set_fetch_size
|
|
879
|
+
(
|
|
880
|
+
data,
|
|
881
|
+
data->query_options.fetch_size
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
// set_fetch_size will swallow errors in the case that the driver
|
|
885
|
+
// doesn't implement SQL_ATTR_ROW_ARRAY_SIZE for SQLSetStmtAttr and
|
|
886
|
+
// the fetch size was 1. If the fetch size was set by the user to a
|
|
887
|
+
// value greater than 1, throw an error.
|
|
888
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
889
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
890
|
+
SetError("[odbc] Error setting the fetch size on the statement\0");
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return_code =
|
|
895
|
+
prepare_for_fetch
|
|
896
|
+
(
|
|
897
|
+
data
|
|
898
|
+
);
|
|
899
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
900
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
901
|
+
SetError("[odbc] Error preparing for fetch\0");
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
if (!data->query_options.use_cursor)
|
|
907
|
+
{
|
|
908
|
+
bool alloc_error = false;
|
|
909
|
+
return_code =
|
|
910
|
+
fetch_all_and_store
|
|
911
|
+
(
|
|
912
|
+
data,
|
|
913
|
+
true,
|
|
914
|
+
&alloc_error
|
|
915
|
+
);
|
|
916
|
+
if (alloc_error)
|
|
917
|
+
{
|
|
918
|
+
SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0");
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
922
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
923
|
+
SetError("[odbc] Error retrieving the result set from the statement\0");
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
void OnOK() {
|
|
932
|
+
|
|
933
|
+
Napi::Env env = Env();
|
|
934
|
+
Napi::HandleScope scope(env);
|
|
935
|
+
|
|
936
|
+
std::vector<napi_value> callbackArguments;
|
|
937
|
+
|
|
938
|
+
if (data->query_options.use_cursor)
|
|
939
|
+
{
|
|
940
|
+
// arguments for the ODBCCursor constructor
|
|
941
|
+
std::vector<napi_value> cursor_arguments =
|
|
942
|
+
{
|
|
943
|
+
Napi::External<StatementData>::New(env, data),
|
|
944
|
+
Napi::External<ODBCConnection>::New(env, odbcConnectionObject),
|
|
945
|
+
napiParameters.Value(),
|
|
946
|
+
Napi::Boolean::New(env, true)
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
// create a new ODBCCursor object as a Napi::Value
|
|
950
|
+
Napi::Value cursorObject = ODBCCursor::constructor.New(cursor_arguments);
|
|
951
|
+
|
|
952
|
+
// return cursor
|
|
953
|
+
std::vector<napi_value> callbackArguments =
|
|
954
|
+
{
|
|
955
|
+
env.Null(),
|
|
956
|
+
cursorObject
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
Callback().Call(callbackArguments);
|
|
960
|
+
}
|
|
961
|
+
else
|
|
962
|
+
{
|
|
963
|
+
Napi::Array rows = process_data_for_napi(env, data, napiParameters.Value());
|
|
964
|
+
|
|
965
|
+
std::vector<napi_value> callbackArguments =
|
|
966
|
+
{
|
|
967
|
+
env.Null(),
|
|
968
|
+
rows
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// return results
|
|
972
|
+
Callback().Call(callbackArguments);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
public:
|
|
979
|
+
QueryAsyncWorker
|
|
980
|
+
(
|
|
981
|
+
ODBCConnection *odbcConnectionObject,
|
|
982
|
+
Napi::Array napiParameterArray,
|
|
983
|
+
StatementData *data,
|
|
984
|
+
Napi::Function& callback
|
|
985
|
+
)
|
|
986
|
+
:
|
|
987
|
+
ODBCAsyncWorker(callback),
|
|
988
|
+
odbcConnectionObject(odbcConnectionObject),
|
|
989
|
+
data(data)
|
|
990
|
+
{
|
|
991
|
+
napiParameters = Napi::Persistent(napiParameterArray.As<Napi::Array>());
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
~QueryAsyncWorker() {
|
|
995
|
+
if (!data->query_options.use_cursor)
|
|
996
|
+
{
|
|
997
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
998
|
+
// It is possible the connection handle has been freed, which freed the
|
|
999
|
+
// statement handle as well. Freeing again would cause a segfault.
|
|
1000
|
+
if (this->odbcConnectionObject->hDBC != SQL_NULL_HANDLE) {
|
|
1001
|
+
SQLFreeHandle
|
|
1002
|
+
(
|
|
1003
|
+
SQL_HANDLE_STMT,
|
|
1004
|
+
this->data->hstmt
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
this->data->hstmt = SQL_NULL_HANDLE;
|
|
1008
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
1009
|
+
delete data;
|
|
1010
|
+
data = NULL;
|
|
1011
|
+
}
|
|
1012
|
+
napiParameters.Reset();
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
/*
|
|
1017
|
+
* ODBCConnection::Query
|
|
1018
|
+
*
|
|
1019
|
+
* Description: Returns the info requested from the connection.
|
|
1020
|
+
*
|
|
1021
|
+
* Parameters:
|
|
1022
|
+
* const Napi::CallbackInfo& info:
|
|
1023
|
+
* The information passed from the JavaSript environment, including the
|
|
1024
|
+
* function arguments for 'query'.
|
|
1025
|
+
*
|
|
1026
|
+
* info[0]: String: the SQL string to execute
|
|
1027
|
+
* info[1?]: Array: optional array of parameters to bind to the query
|
|
1028
|
+
* info[1/2]: Function: callback function:
|
|
1029
|
+
* function(error, result)
|
|
1030
|
+
* error: An error object if the connection was not opened, or
|
|
1031
|
+
* null if operation was successful.
|
|
1032
|
+
* result: A string containing the info requested.
|
|
1033
|
+
*
|
|
1034
|
+
* Return:
|
|
1035
|
+
* Napi::Value:
|
|
1036
|
+
* Undefined (results returned in callback)
|
|
1037
|
+
*/
|
|
1038
|
+
Napi::Value ODBCConnection::Query(const Napi::CallbackInfo& info) {
|
|
1039
|
+
|
|
1040
|
+
Napi::Env env = info.Env();
|
|
1041
|
+
Napi::HandleScope scope(env);
|
|
1042
|
+
|
|
1043
|
+
StatementData *data = new StatementData();
|
|
1044
|
+
data->henv = this->hENV;
|
|
1045
|
+
data->hdbc = this->hDBC;
|
|
1046
|
+
data->fetch_array = this->connectionOptions.fetchArray;
|
|
1047
|
+
data->maxColumnNameLength = this->getInfoResults.max_column_name_length;
|
|
1048
|
+
data->get_data_supports = this->getInfoResults.sql_get_data_supports;
|
|
1049
|
+
Napi::Array napiParameterArray = Napi::Array::New(env);
|
|
1050
|
+
size_t argument_count = info.Length();
|
|
1051
|
+
|
|
1052
|
+
// For the C++ node-addon-api code, all of the function signatures must
|
|
1053
|
+
// include a callback function as the final parameter because we need to
|
|
1054
|
+
// have a function to pass to the AsyncWorker as a Callback. The JavaScript
|
|
1055
|
+
// wrapper functions enforce the correct number of arguments, so just need
|
|
1056
|
+
// to check for null/undefined in a given spot.
|
|
1057
|
+
if
|
|
1058
|
+
(
|
|
1059
|
+
argument_count != 4 ||
|
|
1060
|
+
info[0].IsNull() ||
|
|
1061
|
+
info[0].IsUndefined() ||
|
|
1062
|
+
!info[0].IsString() ||
|
|
1063
|
+
!(
|
|
1064
|
+
info[1].IsArray() ||
|
|
1065
|
+
info[1].IsNull() ||
|
|
1066
|
+
info[1].IsUndefined()
|
|
1067
|
+
) ||
|
|
1068
|
+
!(
|
|
1069
|
+
info[2].IsObject() ||
|
|
1070
|
+
info[2].IsNull() ||
|
|
1071
|
+
info[2].IsUndefined()
|
|
1072
|
+
) ||
|
|
1073
|
+
info[3].IsNull() ||
|
|
1074
|
+
info[3].IsUndefined() ||
|
|
1075
|
+
!info[3].IsFunction()
|
|
1076
|
+
)
|
|
1077
|
+
{
|
|
1078
|
+
Napi::TypeError::New(env, "[node-odbc]: Wrong function signature in call to Connection.query({string}, {array}[optional], {object}[optional], {function}).").ThrowAsJavaScriptException();
|
|
1079
|
+
return env.Null();
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Store the SQL query string in the data structure
|
|
1083
|
+
Napi::String sql = info[0].ToString();
|
|
1084
|
+
data->sql = ODBC::NapiStringToSQLTCHAR(sql);
|
|
1085
|
+
|
|
1086
|
+
// Store the callback function to call at the end of the AsyncWorker
|
|
1087
|
+
Napi::Function callback = info[3].As<Napi::Function>();
|
|
1088
|
+
|
|
1089
|
+
// If info[1] is not null or undefined, it is an array holding our parameters
|
|
1090
|
+
if (!(info[1].IsNull() || info[1].IsUndefined()))
|
|
1091
|
+
{
|
|
1092
|
+
napiParameterArray = info[1].As<Napi::Array>();
|
|
1093
|
+
data->parameterCount = (SQLSMALLINT)napiParameterArray.Length();
|
|
1094
|
+
data->parameters = new Parameter*[data->parameterCount];
|
|
1095
|
+
for (SQLSMALLINT i = 0; i < data->parameterCount; i++) {
|
|
1096
|
+
data->parameters[i] = new Parameter();
|
|
1097
|
+
}
|
|
1098
|
+
ODBC::StoreBindValues(&napiParameterArray, data->parameters);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
Napi::Value error;
|
|
1102
|
+
|
|
1103
|
+
// if info[2] is not null or undefined or an array or a function, it is an
|
|
1104
|
+
// object holding the query options
|
|
1105
|
+
if (
|
|
1106
|
+
(
|
|
1107
|
+
!info[2].IsObject() ||
|
|
1108
|
+
info[2].IsNull() ||
|
|
1109
|
+
info[2].IsUndefined() ||
|
|
1110
|
+
info[2].IsArray() ||
|
|
1111
|
+
info[2].IsFunction()
|
|
1112
|
+
)
|
|
1113
|
+
)
|
|
1114
|
+
{
|
|
1115
|
+
error =
|
|
1116
|
+
parse_query_options
|
|
1117
|
+
(
|
|
1118
|
+
env,
|
|
1119
|
+
env.Null(),
|
|
1120
|
+
&data->query_options
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
else
|
|
1124
|
+
{
|
|
1125
|
+
error =
|
|
1126
|
+
parse_query_options
|
|
1127
|
+
(
|
|
1128
|
+
env,
|
|
1129
|
+
info[2].As<Napi::Object>(),
|
|
1130
|
+
&data->query_options
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (!error.IsNull())
|
|
1135
|
+
{
|
|
1136
|
+
// Error when parsing the query options. Return the callback with the error
|
|
1137
|
+
std::vector<napi_value> callback_argument =
|
|
1138
|
+
{
|
|
1139
|
+
error
|
|
1140
|
+
};
|
|
1141
|
+
callback.Call(callback_argument);
|
|
1142
|
+
}
|
|
1143
|
+
else
|
|
1144
|
+
{
|
|
1145
|
+
// Have parsed the arguments, now create the AsyncWorker and queue the work
|
|
1146
|
+
QueryAsyncWorker *worker;
|
|
1147
|
+
worker = new QueryAsyncWorker(this, napiParameterArray, data, callback);
|
|
1148
|
+
worker->Queue();
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return env.Undefined();
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// If we have a parameter with input/output params (e.g. calling a procedure),
|
|
1155
|
+
// then we need to take the Parameter structures of the StatementData and create
|
|
1156
|
+
// a Napi::Array from those that were overwritten.
|
|
1157
|
+
void ODBCConnection::ParametersToArray(Napi::Reference<Napi::Array> *napiParameters, StatementData *data, unsigned char *overwriteParameters) {
|
|
1158
|
+
Parameter **parameters = data->parameters;
|
|
1159
|
+
Napi::Array napiArray = napiParameters->Value();
|
|
1160
|
+
Napi::Env env = napiParameters->Env();
|
|
1161
|
+
|
|
1162
|
+
for (SQLSMALLINT i = 0; i < data->parameterCount; i++) {
|
|
1163
|
+
Parameter *parameter = parameters[i];
|
|
1164
|
+
if (overwriteParameters[i] == 1) {
|
|
1165
|
+
Napi::Value value;
|
|
1166
|
+
|
|
1167
|
+
// check for null data
|
|
1168
|
+
if (parameter->StrLen_or_IndPtr == SQL_NULL_DATA) {
|
|
1169
|
+
value = env.Null();
|
|
1170
|
+
} else {
|
|
1171
|
+
// Create a JavaScript value based on the C type that was bound to
|
|
1172
|
+
switch(parameter->ValueType) {
|
|
1173
|
+
case SQL_C_DOUBLE:
|
|
1174
|
+
value = Napi::Number::New(env, *(double*)parameter->ParameterValuePtr);
|
|
1175
|
+
break;
|
|
1176
|
+
case SQL_C_SLONG:
|
|
1177
|
+
value = Napi::Number::New(env, *(SQLINTEGER*)parameter->ParameterValuePtr);
|
|
1178
|
+
break;
|
|
1179
|
+
case SQL_C_ULONG:
|
|
1180
|
+
value = Napi::Number::New(env, *(SQLUINTEGER*)parameter->ParameterValuePtr);
|
|
1181
|
+
break;
|
|
1182
|
+
// Napi::BigInt
|
|
1183
|
+
case SQL_C_SBIGINT:
|
|
1184
|
+
if (parameter->isbigint == true) {
|
|
1185
|
+
value = Napi::BigInt::New(env, *(int64_t*)parameter->ParameterValuePtr);
|
|
1186
|
+
} else {
|
|
1187
|
+
value = Napi::Number::New(env, *(int64_t*)parameter->ParameterValuePtr);
|
|
1188
|
+
}
|
|
1189
|
+
break;
|
|
1190
|
+
case SQL_C_UBIGINT:
|
|
1191
|
+
if (parameter->isbigint == true) {
|
|
1192
|
+
value = Napi::BigInt::New(env, *(uint64_t*)parameter->ParameterValuePtr);
|
|
1193
|
+
} else {
|
|
1194
|
+
value = Napi::Number::New(env, *(uint64_t*)parameter->ParameterValuePtr);
|
|
1195
|
+
}
|
|
1196
|
+
break;
|
|
1197
|
+
case SQL_C_SSHORT:
|
|
1198
|
+
value = Napi::Number::New(env, *(signed short*)parameter->ParameterValuePtr);
|
|
1199
|
+
break;
|
|
1200
|
+
case SQL_C_USHORT:
|
|
1201
|
+
value = Napi::Number::New(env, *(unsigned short*)parameter->ParameterValuePtr);
|
|
1202
|
+
break;
|
|
1203
|
+
// Napi::ArrayBuffer
|
|
1204
|
+
case SQL_C_BINARY:
|
|
1205
|
+
// value = Napi::Buffer<SQLCHAR>::New(env, (SQLCHAR*)parameter->ParameterValuePtr, *parameter->StrLen_or_IndPtr);
|
|
1206
|
+
value = Napi::Buffer<SQLCHAR>::Copy(env, (SQLCHAR*)parameter->ParameterValuePtr, parameter->StrLen_or_IndPtr);
|
|
1207
|
+
break;
|
|
1208
|
+
// Napi::String (char16_t)
|
|
1209
|
+
case SQL_C_WCHAR:
|
|
1210
|
+
value = Napi::String::New(env, (const char16_t*)parameter->ParameterValuePtr, parameter->StrLen_or_IndPtr/sizeof(SQLWCHAR));
|
|
1211
|
+
break;
|
|
1212
|
+
// Napi::String (char)
|
|
1213
|
+
case SQL_C_CHAR:
|
|
1214
|
+
default:
|
|
1215
|
+
value = Napi::String::New(env, (const char*)parameter->ParameterValuePtr);
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
napiArray.Set(i, value);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
/******************************************************************************
|
|
1226
|
+
***************************** CALL PROCEDURE *********************************
|
|
1227
|
+
*****************************************************************************/
|
|
1228
|
+
|
|
1229
|
+
// CallProcedureAsyncWorker, used by CreateProcedure function (see below)
|
|
1230
|
+
class CallProcedureAsyncWorker : public ODBCAsyncWorker {
|
|
1231
|
+
|
|
1232
|
+
private:
|
|
1233
|
+
|
|
1234
|
+
ODBCConnection *odbcConnectionObject;
|
|
1235
|
+
Napi::Reference<Napi::Array> napiParameters;
|
|
1236
|
+
StatementData *data;
|
|
1237
|
+
unsigned char *overwriteParams {nullptr};
|
|
1238
|
+
|
|
1239
|
+
void Execute() {
|
|
1240
|
+
|
|
1241
|
+
SQLRETURN return_code;
|
|
1242
|
+
|
|
1243
|
+
#ifndef UNICODE
|
|
1244
|
+
char *combinedProcedureName = new char[1024]();
|
|
1245
|
+
sprintf (
|
|
1246
|
+
combinedProcedureName,
|
|
1247
|
+
"%s%s%s%s%s",
|
|
1248
|
+
data->catalog ? (const char*)data->catalog : "",
|
|
1249
|
+
data->catalog ? "." : "",
|
|
1250
|
+
data->schema ? (const char*)data->schema : "",
|
|
1251
|
+
data->schema ? "." : "",
|
|
1252
|
+
data->procedure
|
|
1253
|
+
);
|
|
1254
|
+
#else
|
|
1255
|
+
wchar_t *combinedProcedureName = new wchar_t[1024]();
|
|
1256
|
+
swprintf (
|
|
1257
|
+
combinedProcedureName,
|
|
1258
|
+
1024,
|
|
1259
|
+
L"%s%s%s%s%s",
|
|
1260
|
+
data->catalog ? data->catalog : L"",
|
|
1261
|
+
data->catalog ? L"." : L"",
|
|
1262
|
+
data->schema ? data->schema : L"",
|
|
1263
|
+
data->schema ? L"." : L"",
|
|
1264
|
+
data->procedure
|
|
1265
|
+
);
|
|
1266
|
+
#endif
|
|
1267
|
+
|
|
1268
|
+
// allocate a new statement handle
|
|
1269
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
1270
|
+
return_code =
|
|
1271
|
+
SQLAllocHandle
|
|
1272
|
+
(
|
|
1273
|
+
SQL_HANDLE_STMT, // HandleType
|
|
1274
|
+
odbcConnectionObject->hDBC, // InputHandle
|
|
1275
|
+
&data->hstmt // OutputHandlePtr
|
|
1276
|
+
);
|
|
1277
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
1278
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
1279
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
1280
|
+
SetError("[odbc] Error allocating a statment handle to get procedure information\0");
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return_code =
|
|
1285
|
+
set_fetch_size
|
|
1286
|
+
(
|
|
1287
|
+
data,
|
|
1288
|
+
1
|
|
1289
|
+
);
|
|
1290
|
+
|
|
1291
|
+
return_code =
|
|
1292
|
+
SQLProcedures
|
|
1293
|
+
(
|
|
1294
|
+
data->hstmt, // StatementHandle
|
|
1295
|
+
data->catalog, // CatalogName
|
|
1296
|
+
SQL_NTS, // NameLengh1
|
|
1297
|
+
data->schema, // SchemaName
|
|
1298
|
+
SQL_NTS, // NameLength2
|
|
1299
|
+
data->procedure, // ProcName
|
|
1300
|
+
SQL_NTS // NameLength3
|
|
1301
|
+
);
|
|
1302
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
1303
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
1304
|
+
SetError("[odbc] Error retrieving information about the procedures in the database\0");
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
return_code = prepare_for_fetch(data);
|
|
1310
|
+
bool alloc_error = false;
|
|
1311
|
+
return_code =
|
|
1312
|
+
fetch_all_and_store
|
|
1313
|
+
(
|
|
1314
|
+
data,
|
|
1315
|
+
false,
|
|
1316
|
+
&alloc_error
|
|
1317
|
+
);
|
|
1318
|
+
if (alloc_error)
|
|
1319
|
+
{
|
|
1320
|
+
SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0");
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
1324
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
1325
|
+
SetError("[odbc] Error retrieving information about procedures\0");
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
if (data->storedRows.size() == 0) {
|
|
1330
|
+
char errorString[255];
|
|
1331
|
+
#ifndef UNICODE
|
|
1332
|
+
sprintf(errorString, "[odbc] CallProcedureAsyncWorker::Execute: Stored procedure '%s' doesn't exist", combinedProcedureName);
|
|
1333
|
+
#else
|
|
1334
|
+
sprintf(errorString, "[odbc] CallProcedureAsyncWorker::Execute: Stored procedure '%S' doesn't exist", combinedProcedureName);
|
|
1335
|
+
#endif
|
|
1336
|
+
SetError(errorString);
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
data->deleteColumns(); // delete data in columns for next result set
|
|
1341
|
+
|
|
1342
|
+
return_code =
|
|
1343
|
+
set_fetch_size
|
|
1344
|
+
(
|
|
1345
|
+
data,
|
|
1346
|
+
1
|
|
1347
|
+
);
|
|
1348
|
+
|
|
1349
|
+
return_code =
|
|
1350
|
+
SQLProcedureColumns
|
|
1351
|
+
(
|
|
1352
|
+
data->hstmt, // StatementHandle
|
|
1353
|
+
data->catalog, // CatalogName
|
|
1354
|
+
SQL_NTS, // NameLengh1
|
|
1355
|
+
data->schema, // SchemaName
|
|
1356
|
+
SQL_NTS, // NameLength2
|
|
1357
|
+
data->procedure, // ProcName
|
|
1358
|
+
SQL_NTS, // NameLength3
|
|
1359
|
+
NULL, // ColumnName
|
|
1360
|
+
SQL_NTS // NameLength4
|
|
1361
|
+
);
|
|
1362
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
1363
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
1364
|
+
SetError("[odbc] Error retrieving information about the columns in the procedure\0");
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
return_code = prepare_for_fetch(data);
|
|
1369
|
+
return_code =
|
|
1370
|
+
fetch_all_and_store
|
|
1371
|
+
(
|
|
1372
|
+
data,
|
|
1373
|
+
false,
|
|
1374
|
+
&alloc_error
|
|
1375
|
+
);
|
|
1376
|
+
if (alloc_error)
|
|
1377
|
+
{
|
|
1378
|
+
SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0");
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
1382
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
1383
|
+
SetError("[odbc] Error retrieving the result set containing information about the columns in the procedure\0");
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (data->parameterCount != (SQLSMALLINT)data->storedRows.size()) {
|
|
1388
|
+
SetError("[odbc] The number of parameters the procedure expects and and the number of passed parameters is not equal\0");
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
#define SQLPROCEDURECOLUMNS_COLUMN_TYPE_INDEX 4
|
|
1393
|
+
#define SQLPROCEDURECOLUMNS_DATA_TYPE_INDEX 5
|
|
1394
|
+
#define SQLPROCEDURECOLUMNS_COLUMN_SIZE_INDEX 7
|
|
1395
|
+
#define SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX 9
|
|
1396
|
+
#define SQLPROCEDURECOLUMNS_NULLABLE_INDEX 11
|
|
1397
|
+
|
|
1398
|
+
// get stored column parameter data from the result set
|
|
1399
|
+
|
|
1400
|
+
for (int i = 0; i < data->parameterCount; i++) {
|
|
1401
|
+
|
|
1402
|
+
Parameter *parameter = data->parameters[i];
|
|
1403
|
+
|
|
1404
|
+
data->parameters[i]->InputOutputType = data->storedRows[i][SQLPROCEDURECOLUMNS_COLUMN_TYPE_INDEX].smallint_data;
|
|
1405
|
+
data->parameters[i]->ParameterType = data->storedRows[i][SQLPROCEDURECOLUMNS_DATA_TYPE_INDEX].smallint_data; // DataType -> ParameterType
|
|
1406
|
+
data->parameters[i]->ColumnSize = data->storedRows[i][SQLPROCEDURECOLUMNS_COLUMN_SIZE_INDEX].integer_data; // ParameterSize -> ColumnSize
|
|
1407
|
+
data->parameters[i]->Nullable = data->storedRows[i][SQLPROCEDURECOLUMNS_NULLABLE_INDEX].smallint_data;
|
|
1408
|
+
|
|
1409
|
+
// For each parameter, need to manipulate the data buffer and C type
|
|
1410
|
+
// depending on what the InputOutputType is:
|
|
1411
|
+
//
|
|
1412
|
+
// SQL_PARAM_INPUT:
|
|
1413
|
+
// No changes required, data should be able to be parsed based on
|
|
1414
|
+
// the C type defined when the parameters were read from JavaScript
|
|
1415
|
+
//
|
|
1416
|
+
// SQL_PARAM_INPUT_OUTPUT:
|
|
1417
|
+
// Need to preserve the data so that it can be sent in correctly,
|
|
1418
|
+
// but also need to ensure that the buffer is large enough to return
|
|
1419
|
+
// any value returned on the out portion. Also, preserve the bind type
|
|
1420
|
+
// on both the input and output:
|
|
1421
|
+
//
|
|
1422
|
+
// e.g. A user sends in a character string for a BIGINT field. Resize
|
|
1423
|
+
// the buffer for the character string to hold any potential
|
|
1424
|
+
// value on the out portion, but keep it bound to SQL_C_CHAR.
|
|
1425
|
+
|
|
1426
|
+
// declare buffersize, used for many of the code paths below
|
|
1427
|
+
SQLLEN bufferSize = 0;
|
|
1428
|
+
switch (parameter->InputOutputType) {
|
|
1429
|
+
case SQL_PARAM_INPUT_OUTPUT:
|
|
1430
|
+
{
|
|
1431
|
+
switch(parameter->ParameterType)
|
|
1432
|
+
{
|
|
1433
|
+
case SQL_BIGINT: {
|
|
1434
|
+
switch(parameter->ValueType)
|
|
1435
|
+
{
|
|
1436
|
+
case SQL_C_SBIGINT:
|
|
1437
|
+
// Don't need to do anything, should be bound correctly
|
|
1438
|
+
parameter->BufferLength = sizeof(SQLBIGINT);
|
|
1439
|
+
break;
|
|
1440
|
+
|
|
1441
|
+
case SQL_C_CHAR:
|
|
1442
|
+
default:
|
|
1443
|
+
// TODO: Don't just hardcode 21
|
|
1444
|
+
bufferSize = 21;
|
|
1445
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1446
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1447
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1448
|
+
parameter->ParameterValuePtr = temp;
|
|
1449
|
+
parameter->BufferLength = bufferSize;
|
|
1450
|
+
break;
|
|
1451
|
+
}
|
|
1452
|
+
break;
|
|
1453
|
+
}
|
|
1454
|
+
case SQL_BINARY:
|
|
1455
|
+
case SQL_VARBINARY:
|
|
1456
|
+
case SQL_LONGVARBINARY: {
|
|
1457
|
+
switch(parameter->ValueType)
|
|
1458
|
+
{
|
|
1459
|
+
case SQL_C_BINARY:
|
|
1460
|
+
default: {
|
|
1461
|
+
bufferSize = parameter->ColumnSize * sizeof(SQLCHAR);
|
|
1462
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1463
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1464
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1465
|
+
parameter->ParameterValuePtr = temp;
|
|
1466
|
+
parameter->BufferLength = bufferSize;
|
|
1467
|
+
break;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
break;
|
|
1471
|
+
}
|
|
1472
|
+
case SQL_INTEGER: {
|
|
1473
|
+
switch(parameter->ValueType)
|
|
1474
|
+
{
|
|
1475
|
+
case SQL_C_SBIGINT: {
|
|
1476
|
+
parameter->BufferLength = sizeof(SQLBIGINT);
|
|
1477
|
+
break;
|
|
1478
|
+
}
|
|
1479
|
+
case SQL_C_CHAR:
|
|
1480
|
+
default: {
|
|
1481
|
+
// TODO: don't just hardcode 12
|
|
1482
|
+
bufferSize = 12;
|
|
1483
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1484
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1485
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1486
|
+
parameter->ParameterValuePtr = temp;
|
|
1487
|
+
parameter->BufferLength = bufferSize;
|
|
1488
|
+
break;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
case SQL_SMALLINT: {
|
|
1494
|
+
switch(parameter->ValueType)
|
|
1495
|
+
{
|
|
1496
|
+
case SQL_C_SBIGINT: {
|
|
1497
|
+
parameter->BufferLength = sizeof(SQLBIGINT);
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
case SQL_C_CHAR:
|
|
1501
|
+
default: {
|
|
1502
|
+
// TODO: don't just hardcode 7
|
|
1503
|
+
bufferSize = 7;
|
|
1504
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1505
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1506
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1507
|
+
parameter->ParameterValuePtr = temp;
|
|
1508
|
+
parameter->BufferLength = bufferSize;
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
case SQL_TINYINT: {
|
|
1516
|
+
switch(parameter->ValueType)
|
|
1517
|
+
{
|
|
1518
|
+
case SQL_C_CHAR:
|
|
1519
|
+
default: {
|
|
1520
|
+
parameter->BufferLength = sizeof(SQLCHAR);
|
|
1521
|
+
break;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
break;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
case SQL_DECIMAL:
|
|
1528
|
+
case SQL_NUMERIC: {
|
|
1529
|
+
switch(parameter->ValueType)
|
|
1530
|
+
{
|
|
1531
|
+
case SQL_C_DOUBLE: {
|
|
1532
|
+
parameter->BufferLength = sizeof(SQLDOUBLE);
|
|
1533
|
+
break;
|
|
1534
|
+
}
|
|
1535
|
+
case SQL_C_SBIGINT: {
|
|
1536
|
+
parameter->BufferLength = sizeof(SQLBIGINT);
|
|
1537
|
+
break;
|
|
1538
|
+
}
|
|
1539
|
+
case SQL_C_CHAR:
|
|
1540
|
+
default: {
|
|
1541
|
+
// ColumnSize + sign + decimal + null-terminator
|
|
1542
|
+
bufferSize = (data->parameters[i]->ColumnSize + 3);
|
|
1543
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1544
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1545
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1546
|
+
parameter->ParameterValuePtr = temp;
|
|
1547
|
+
parameter->BufferLength = bufferSize;
|
|
1548
|
+
break;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
break;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
case SQL_BIT: {
|
|
1555
|
+
switch(parameter->ValueType)
|
|
1556
|
+
{
|
|
1557
|
+
case SQL_C_BIT:
|
|
1558
|
+
case SQL_C_CHAR:
|
|
1559
|
+
default: {
|
|
1560
|
+
// TODO: don't just hardcode 2
|
|
1561
|
+
bufferSize = 2;
|
|
1562
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1563
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1564
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1565
|
+
parameter->ParameterValuePtr = temp;
|
|
1566
|
+
parameter->BufferLength = bufferSize;
|
|
1567
|
+
break;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
case SQL_REAL: {
|
|
1574
|
+
switch(parameter->ValueType)
|
|
1575
|
+
{
|
|
1576
|
+
case SQL_C_DOUBLE: {
|
|
1577
|
+
parameter->BufferLength = sizeof(SQLDOUBLE);
|
|
1578
|
+
break;
|
|
1579
|
+
}
|
|
1580
|
+
case SQL_C_SBIGINT: {
|
|
1581
|
+
parameter->BufferLength = sizeof(SQLBIGINT);
|
|
1582
|
+
break;
|
|
1583
|
+
}
|
|
1584
|
+
case SQL_C_CHAR:
|
|
1585
|
+
default: {
|
|
1586
|
+
// TODO: don't just hardcode 15
|
|
1587
|
+
bufferSize = 15;
|
|
1588
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1589
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1590
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1591
|
+
parameter->ParameterValuePtr = temp;
|
|
1592
|
+
parameter->BufferLength = bufferSize;
|
|
1593
|
+
break;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
case SQL_FLOAT: {
|
|
1600
|
+
switch(parameter->ValueType)
|
|
1601
|
+
{
|
|
1602
|
+
case SQL_C_DOUBLE: {
|
|
1603
|
+
parameter->BufferLength = sizeof(SQLDOUBLE);
|
|
1604
|
+
break;
|
|
1605
|
+
}
|
|
1606
|
+
case SQL_C_SBIGINT: {
|
|
1607
|
+
parameter->BufferLength = sizeof(SQLBIGINT);
|
|
1608
|
+
break;
|
|
1609
|
+
}
|
|
1610
|
+
case SQL_C_CHAR:
|
|
1611
|
+
default: {
|
|
1612
|
+
// TODO: don't just hardcode 25
|
|
1613
|
+
bufferSize = 25;
|
|
1614
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1615
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1616
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1617
|
+
parameter->ParameterValuePtr = temp;
|
|
1618
|
+
parameter->BufferLength = bufferSize;
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
break;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
case SQL_DOUBLE: {
|
|
1626
|
+
switch(parameter->ValueType)
|
|
1627
|
+
{
|
|
1628
|
+
case SQL_C_DOUBLE: {
|
|
1629
|
+
parameter->BufferLength = sizeof(SQLDOUBLE);
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1632
|
+
case SQL_C_SBIGINT: {
|
|
1633
|
+
parameter->BufferLength = sizeof(SQLBIGINT);
|
|
1634
|
+
break;
|
|
1635
|
+
}
|
|
1636
|
+
case SQL_C_CHAR:
|
|
1637
|
+
default: {
|
|
1638
|
+
// TODO: don't just hardcode 25
|
|
1639
|
+
bufferSize = 25;
|
|
1640
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1641
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1642
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1643
|
+
parameter->ParameterValuePtr = temp;
|
|
1644
|
+
parameter->BufferLength = bufferSize;
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
break;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
case SQL_WCHAR:
|
|
1652
|
+
case SQL_WVARCHAR:
|
|
1653
|
+
case SQL_WLONGVARCHAR: {
|
|
1654
|
+
switch(parameter->ValueType)
|
|
1655
|
+
{
|
|
1656
|
+
case SQL_C_WCHAR: {
|
|
1657
|
+
bufferSize = data->parameters[i]->ColumnSize + 1;
|
|
1658
|
+
SQLWCHAR *temp = new SQLWCHAR[bufferSize]();
|
|
1659
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1660
|
+
delete[] reinterpret_cast<SQLWCHAR*>(parameter->ParameterValuePtr);
|
|
1661
|
+
parameter->ParameterValuePtr = temp;
|
|
1662
|
+
parameter->BufferLength = bufferSize * sizeof(SQLWCHAR);
|
|
1663
|
+
break;
|
|
1664
|
+
}
|
|
1665
|
+
case SQL_C_CHAR:
|
|
1666
|
+
default: {
|
|
1667
|
+
bufferSize = (data->parameters[i]->ColumnSize + 1) * sizeof(SQLCHAR) * MAX_UTF8_BYTES;
|
|
1668
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1669
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1670
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1671
|
+
parameter->ParameterValuePtr = temp;
|
|
1672
|
+
parameter->BufferLength = bufferSize;
|
|
1673
|
+
break;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
case SQL_CHAR:
|
|
1680
|
+
case SQL_VARCHAR:
|
|
1681
|
+
case SQL_LONGVARCHAR: {
|
|
1682
|
+
switch(parameter->ValueType)
|
|
1683
|
+
{
|
|
1684
|
+
case SQL_C_CHAR:
|
|
1685
|
+
default: {
|
|
1686
|
+
bufferSize = (data->parameters[i]->ColumnSize + 1) * sizeof(SQLCHAR) * MAX_UTF8_BYTES;
|
|
1687
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1688
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1689
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1690
|
+
parameter->ParameterValuePtr = temp;
|
|
1691
|
+
parameter->BufferLength = bufferSize;
|
|
1692
|
+
break;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
break;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// It is possible that a driver-specific value was returned.
|
|
1699
|
+
// If so, just go with whatever C type they bound with with
|
|
1700
|
+
// reasonable values.
|
|
1701
|
+
default: {
|
|
1702
|
+
switch(parameter->ValueType)
|
|
1703
|
+
{
|
|
1704
|
+
case SQL_C_BINARY: {
|
|
1705
|
+
bufferSize = parameter->ColumnSize * sizeof(SQLCHAR);
|
|
1706
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1707
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1708
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1709
|
+
parameter->ParameterValuePtr = temp;
|
|
1710
|
+
parameter->BufferLength = bufferSize;
|
|
1711
|
+
break;
|
|
1712
|
+
}
|
|
1713
|
+
case SQL_C_CHAR:
|
|
1714
|
+
default: {
|
|
1715
|
+
bufferSize = (data->parameters[i]->ColumnSize + 1) * sizeof(SQLCHAR) * MAX_UTF8_BYTES;
|
|
1716
|
+
SQLCHAR *temp = new SQLCHAR[bufferSize]();
|
|
1717
|
+
memcpy(temp, parameter->ParameterValuePtr, parameter->BufferLength);
|
|
1718
|
+
delete[] reinterpret_cast<SQLCHAR*>(parameter->ParameterValuePtr);
|
|
1719
|
+
parameter->ParameterValuePtr = temp;
|
|
1720
|
+
parameter->BufferLength = bufferSize;
|
|
1721
|
+
break;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
break;
|
|
1728
|
+
}
|
|
1729
|
+
case SQL_PARAM_OUTPUT:
|
|
1730
|
+
{
|
|
1731
|
+
switch(parameter->ParameterType)
|
|
1732
|
+
{
|
|
1733
|
+
case SQL_DECIMAL:
|
|
1734
|
+
case SQL_NUMERIC:
|
|
1735
|
+
bufferSize = (data->parameters[i]->ColumnSize + 3) * sizeof(SQLCHAR);
|
|
1736
|
+
data->parameters[i]->ValueType = SQL_C_CHAR;
|
|
1737
|
+
data->parameters[i]->ParameterValuePtr = new SQLCHAR[bufferSize];
|
|
1738
|
+
data->parameters[i]->BufferLength = bufferSize;
|
|
1739
|
+
data->parameters[i]->DecimalDigits = data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX].smallint_data;
|
|
1740
|
+
break;
|
|
1741
|
+
|
|
1742
|
+
case SQL_DOUBLE:
|
|
1743
|
+
case SQL_FLOAT:
|
|
1744
|
+
data->parameters[i]->ValueType = SQL_C_DOUBLE;
|
|
1745
|
+
data->parameters[i]->ParameterValuePtr = new SQLDOUBLE();
|
|
1746
|
+
data->parameters[i]->BufferLength = sizeof(SQLDOUBLE);
|
|
1747
|
+
data->parameters[i]->DecimalDigits = data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX].smallint_data;
|
|
1748
|
+
break;
|
|
1749
|
+
|
|
1750
|
+
case SQL_TINYINT:
|
|
1751
|
+
data->parameters[i]->ValueType = SQL_C_UTINYINT;
|
|
1752
|
+
data->parameters[i]->ParameterValuePtr = new SQLCHAR();
|
|
1753
|
+
data->parameters[i]->BufferLength = sizeof(SQLCHAR);
|
|
1754
|
+
data->parameters[i]->DecimalDigits = data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX].smallint_data;
|
|
1755
|
+
break;
|
|
1756
|
+
|
|
1757
|
+
case SQL_SMALLINT:
|
|
1758
|
+
data->parameters[i]->ValueType = SQL_C_SSHORT;
|
|
1759
|
+
data->parameters[i]->ParameterValuePtr = new SQLSMALLINT();
|
|
1760
|
+
data->parameters[i]->BufferLength = sizeof(SQLSMALLINT);
|
|
1761
|
+
data->parameters[i]->DecimalDigits = data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX].smallint_data;
|
|
1762
|
+
break;
|
|
1763
|
+
|
|
1764
|
+
case SQL_INTEGER:
|
|
1765
|
+
data->parameters[i]->ValueType = SQL_C_SLONG;
|
|
1766
|
+
data->parameters[i]->ParameterValuePtr = new SQLINTEGER();
|
|
1767
|
+
data->parameters[i]->BufferLength = sizeof(SQLINTEGER);
|
|
1768
|
+
data->parameters[i]->DecimalDigits = data->storedRows[i][SQLPROCEDURECOLUMNS_DECIMAL_DIGITS_INDEX].smallint_data;
|
|
1769
|
+
break;
|
|
1770
|
+
|
|
1771
|
+
case SQL_BIGINT:
|
|
1772
|
+
parameter->ValueType = SQL_C_SBIGINT;
|
|
1773
|
+
parameter->ParameterValuePtr = new SQLBIGINT();
|
|
1774
|
+
parameter->BufferLength = sizeof(SQLBIGINT);
|
|
1775
|
+
parameter->isbigint = true;
|
|
1776
|
+
break;
|
|
1777
|
+
|
|
1778
|
+
case SQL_BINARY:
|
|
1779
|
+
case SQL_VARBINARY:
|
|
1780
|
+
case SQL_LONGVARBINARY:
|
|
1781
|
+
bufferSize = data->parameters[i]->ColumnSize * sizeof(SQLCHAR);
|
|
1782
|
+
data->parameters[i]->ValueType = SQL_C_BINARY;
|
|
1783
|
+
data->parameters[i]->ParameterValuePtr = new SQLCHAR[bufferSize]();
|
|
1784
|
+
data->parameters[i]->BufferLength = bufferSize;
|
|
1785
|
+
break;
|
|
1786
|
+
|
|
1787
|
+
case SQL_WCHAR:
|
|
1788
|
+
case SQL_WVARCHAR:
|
|
1789
|
+
case SQL_WLONGVARCHAR:
|
|
1790
|
+
bufferSize = (data->parameters[i]->ColumnSize + 1) * sizeof(SQLWCHAR);
|
|
1791
|
+
data->parameters[i]->ValueType = SQL_C_WCHAR;
|
|
1792
|
+
data->parameters[i]->ParameterValuePtr = new SQLWCHAR[bufferSize]();
|
|
1793
|
+
data->parameters[i]->BufferLength = bufferSize;
|
|
1794
|
+
break;
|
|
1795
|
+
|
|
1796
|
+
case SQL_CHAR:
|
|
1797
|
+
case SQL_VARCHAR:
|
|
1798
|
+
case SQL_LONGVARCHAR:
|
|
1799
|
+
default:
|
|
1800
|
+
bufferSize = ((data->parameters[i]->ColumnSize * MAX_UTF8_BYTES) + 1) * sizeof(SQLCHAR);
|
|
1801
|
+
data->parameters[i]->ValueType = SQL_C_CHAR;
|
|
1802
|
+
data->parameters[i]->ParameterValuePtr = new SQLCHAR[bufferSize]();
|
|
1803
|
+
data->parameters[i]->BufferLength = bufferSize;
|
|
1804
|
+
break;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// We saved a reference to parameters passed it. Need to tell which
|
|
1811
|
+
// parameters we have to overwrite, now that we have
|
|
1812
|
+
this->overwriteParams = new unsigned char[data->parameterCount]();
|
|
1813
|
+
for (int i = 0; i < data->parameterCount; i++) {
|
|
1814
|
+
if (data->parameters[i]->InputOutputType == SQL_PARAM_OUTPUT || data->parameters[i]->InputOutputType == SQL_PARAM_INPUT_OUTPUT) {
|
|
1815
|
+
this->overwriteParams[i] = 1;
|
|
1816
|
+
} else {
|
|
1817
|
+
this->overwriteParams[i] = 0;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
return_code = ODBC::BindParameters(data->hstmt, data->parameters, data->parameterCount);
|
|
1822
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
1823
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
1824
|
+
SetError("[odbc] Error binding parameters to the procedure\0");
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// create the statement to call the stored procedure using the ODBC Call escape sequence:
|
|
1829
|
+
// need to create the string "?,?,?,?" where the number of '?' is the number of parameters;
|
|
1830
|
+
size_t parameterStringSize = (data->parameterCount * 2);
|
|
1831
|
+
char *parameterString = new char[parameterStringSize];
|
|
1832
|
+
parameterString[0] = '\0';
|
|
1833
|
+
|
|
1834
|
+
for (int i = 0; i < data->parameterCount; i++) {
|
|
1835
|
+
if (i == (data->parameterCount - 1)) {
|
|
1836
|
+
strcat(parameterString, "?"); // for last parameter, don't add ','
|
|
1837
|
+
} else {
|
|
1838
|
+
strcat(parameterString, "?,");
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
data->deleteColumns(); // delete data in columns for next result set
|
|
1843
|
+
|
|
1844
|
+
// 13 non-template characters in { CALL %s (%s) }\0
|
|
1845
|
+
size_t sqlStringSize = 1024 + parameterStringSize + sizeof("{ CALL () }");
|
|
1846
|
+
data->sql = new SQLTCHAR[sqlStringSize];
|
|
1847
|
+
#ifndef UNICODE
|
|
1848
|
+
sprintf((char *)data->sql, "{ CALL %s (%s) }", combinedProcedureName, parameterString);
|
|
1849
|
+
#else
|
|
1850
|
+
// Note: On Windows, %s and %S change their behavior depending on whether
|
|
1851
|
+
// it's passed to a printf function or a wprintf function. Since we're passing
|
|
1852
|
+
// narrow strings to a wide function in the case of parameters, we need to use %S.
|
|
1853
|
+
swprintf(data->sql, sqlStringSize, L"{ CALL %s (%S) }", combinedProcedureName, parameterString);
|
|
1854
|
+
#endif
|
|
1855
|
+
|
|
1856
|
+
delete[] combinedProcedureName;
|
|
1857
|
+
delete[] parameterString;
|
|
1858
|
+
|
|
1859
|
+
set_fetch_size
|
|
1860
|
+
(
|
|
1861
|
+
data,
|
|
1862
|
+
1
|
|
1863
|
+
);
|
|
1864
|
+
|
|
1865
|
+
// Execute the stored procedure
|
|
1866
|
+
return_code = SQLExecDirect(
|
|
1867
|
+
data->hstmt, // StatementHandle
|
|
1868
|
+
data->sql, // StatementText
|
|
1869
|
+
SQL_NTS // TextLength
|
|
1870
|
+
);
|
|
1871
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
1872
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
1873
|
+
SetError("[odbc] Error calling the procedure\0");
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// MODIFICACIÓN: Soporte para múltiples result sets (Pablo Pimentel)
|
|
1878
|
+
// Loop para obtener todos los result sets del stored procedure
|
|
1879
|
+
bool hasMoreResults = true;
|
|
1880
|
+
|
|
1881
|
+
while (hasMoreResults) {
|
|
1882
|
+
// Preparar para obtener el result set actual
|
|
1883
|
+
return_code = prepare_for_fetch(data);
|
|
1884
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
1885
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
1886
|
+
SetError("[odbc] Error preparing for fetch\0");
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// Obtener todos los datos del result set actual
|
|
1891
|
+
return_code = fetch_all_and_store(
|
|
1892
|
+
data,
|
|
1893
|
+
true,
|
|
1894
|
+
&alloc_error
|
|
1895
|
+
);
|
|
1896
|
+
|
|
1897
|
+
if (alloc_error) {
|
|
1898
|
+
SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.");
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
if (!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) {
|
|
1903
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
1904
|
+
SetError("[odbc] Error retrieving the results from the procedure call\0");
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// Guardar el result set actual en allResultSets
|
|
1909
|
+
if (data->storedRows.size() > 0) {
|
|
1910
|
+
std::vector<ColumnData*> currentResultSet;
|
|
1911
|
+
for (size_t i = 0; i < data->storedRows.size(); i++) {
|
|
1912
|
+
currentResultSet.push_back(data->storedRows[i]);
|
|
1913
|
+
}
|
|
1914
|
+
data->allResultSets.push_back(currentResultSet);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
// Limpiar storedRows para el próximo result set (sin liberar memoria)
|
|
1918
|
+
data->storedRows.clear();
|
|
1919
|
+
|
|
1920
|
+
// Limpiar columnas para el próximo result set
|
|
1921
|
+
data->deleteColumns();
|
|
1922
|
+
|
|
1923
|
+
// Intentar obtener el siguiente result set
|
|
1924
|
+
return_code = SQLMoreResults(data->hstmt);
|
|
1925
|
+
|
|
1926
|
+
if (return_code == SQL_NO_DATA) {
|
|
1927
|
+
// No hay más result sets
|
|
1928
|
+
hasMoreResults = false;
|
|
1929
|
+
} else if (!SQL_SUCCEEDED(return_code)) {
|
|
1930
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
1931
|
+
SetError("[odbc] Error getting more results\0");
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
void OnOK() {
|
|
1938
|
+
|
|
1939
|
+
Napi::Env env = Env();
|
|
1940
|
+
Napi::HandleScope scope(env);
|
|
1941
|
+
|
|
1942
|
+
// MODIFICACIÓN: Procesar múltiples result sets (Pablo Pimentel)
|
|
1943
|
+
Napi::Array allResults = Napi::Array::New(env);
|
|
1944
|
+
|
|
1945
|
+
odbcConnectionObject->ParametersToArray(&napiParameters, data, overwriteParams);
|
|
1946
|
+
|
|
1947
|
+
// Procesar cada result set almacenado
|
|
1948
|
+
for (size_t rs_index = 0; rs_index < data->allResultSets.size(); rs_index++) {
|
|
1949
|
+
// Restaurar temporalmente storedRows para procesarlo
|
|
1950
|
+
data->storedRows = data->allResultSets[rs_index];
|
|
1951
|
+
|
|
1952
|
+
Napi::Array singleResult = process_data_for_napi(
|
|
1953
|
+
env,
|
|
1954
|
+
data,
|
|
1955
|
+
napiParameters.Value()
|
|
1956
|
+
);
|
|
1957
|
+
|
|
1958
|
+
allResults.Set(rs_index, singleResult);
|
|
1959
|
+
|
|
1960
|
+
// Limpiar storedRows después de procesar
|
|
1961
|
+
data->storedRows.clear();
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
std::vector<napi_value> callbackArguments;
|
|
1965
|
+
callbackArguments.push_back(env.Null());
|
|
1966
|
+
callbackArguments.push_back(allResults);
|
|
1967
|
+
|
|
1968
|
+
// return results object
|
|
1969
|
+
Callback().Call(callbackArguments);
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
public:
|
|
1973
|
+
CallProcedureAsyncWorker(ODBCConnection *odbcConnectionObject, Napi::Value napiParameterArray, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
1974
|
+
odbcConnectionObject(odbcConnectionObject),
|
|
1975
|
+
data(data)
|
|
1976
|
+
{
|
|
1977
|
+
if (napiParameterArray.IsArray()) {
|
|
1978
|
+
napiParameters = Napi::Persistent(napiParameterArray.As<Napi::Array>());
|
|
1979
|
+
} else {
|
|
1980
|
+
napiParameters = Napi::Reference<Napi::Array>();
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
~CallProcedureAsyncWorker() {
|
|
1985
|
+
delete[] overwriteParams;
|
|
1986
|
+
delete data;
|
|
1987
|
+
data = NULL;
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
1990
|
+
|
|
1991
|
+
/* TODO: Change
|
|
1992
|
+
* ODBCConnection::CallProcedure
|
|
1993
|
+
*
|
|
1994
|
+
* Description: Calls a procedure in the database.
|
|
1995
|
+
*
|
|
1996
|
+
* Parameters:
|
|
1997
|
+
* const Napi::CallbackInfo& info:
|
|
1998
|
+
* The information passed from the JavaSript environment, including the
|
|
1999
|
+
* function arguments for 'query'.
|
|
2000
|
+
*
|
|
2001
|
+
* info[0]: String: the name of the procedure
|
|
2002
|
+
* info[1?]: Array: optional array of parameters to bind to the procedure call
|
|
2003
|
+
* info[1/2]: Function: callback function:
|
|
2004
|
+
* function(error, result)
|
|
2005
|
+
* error: An error object if the connection was not opened, or
|
|
2006
|
+
* null if operation was successful.
|
|
2007
|
+
* result: A string containing the info requested.
|
|
2008
|
+
*
|
|
2009
|
+
* Return:
|
|
2010
|
+
* Napi::Value:
|
|
2011
|
+
* Undefined (results returned in callback)
|
|
2012
|
+
*/
|
|
2013
|
+
Napi::Value ODBCConnection::CallProcedure(const Napi::CallbackInfo& info) {
|
|
2014
|
+
|
|
2015
|
+
Napi::Env env = info.Env();
|
|
2016
|
+
Napi::HandleScope scope(env);
|
|
2017
|
+
|
|
2018
|
+
StatementData *data = new StatementData();
|
|
2019
|
+
data->henv = this->hENV;
|
|
2020
|
+
data->hdbc = this->hDBC;
|
|
2021
|
+
data->fetch_array = this->connectionOptions.fetchArray;
|
|
2022
|
+
data->maxColumnNameLength = this->getInfoResults.max_column_name_length;
|
|
2023
|
+
data->get_data_supports = this->getInfoResults.sql_get_data_supports;
|
|
2024
|
+
std::vector<Napi::Value> values;
|
|
2025
|
+
Napi::Value napiParameterArray = env.Null();
|
|
2026
|
+
|
|
2027
|
+
if (info[0].IsString()) {
|
|
2028
|
+
data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString());
|
|
2029
|
+
} else if (!info[0].IsNull()) {
|
|
2030
|
+
Napi::TypeError::New(env, "callProcedure: first argument must be a string or null").ThrowAsJavaScriptException();
|
|
2031
|
+
delete data;
|
|
2032
|
+
data = NULL;
|
|
2033
|
+
return env.Null();
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
if (info[1].IsString()) {
|
|
2037
|
+
data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString());
|
|
2038
|
+
} else if (!info[1].IsNull()) {
|
|
2039
|
+
Napi::TypeError::New(env, "callProcedure: second argument must be a string or null").ThrowAsJavaScriptException();
|
|
2040
|
+
delete data;
|
|
2041
|
+
data = NULL;
|
|
2042
|
+
return env.Null();
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
if (info[2].IsString()) {
|
|
2046
|
+
data->procedure = ODBC::NapiStringToSQLTCHAR(info[2].ToString());
|
|
2047
|
+
} else {
|
|
2048
|
+
Napi::TypeError::New(env, "callProcedure: third argument must be a string").ThrowAsJavaScriptException();
|
|
2049
|
+
delete data;
|
|
2050
|
+
data = NULL;
|
|
2051
|
+
return env.Null();
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// check if parameters were passed or not
|
|
2055
|
+
if (info.Length() == 5 && info[3].IsArray() && info[4].IsFunction()) {
|
|
2056
|
+
napiParameterArray = info[3];
|
|
2057
|
+
Napi::Array napiArray = napiParameterArray.As<Napi::Array>();
|
|
2058
|
+
data->parameterCount = (SQLSMALLINT)napiArray.Length();
|
|
2059
|
+
data->parameters = new Parameter*[data->parameterCount];
|
|
2060
|
+
for (SQLSMALLINT i = 0; i < data->parameterCount; i++) {
|
|
2061
|
+
data->parameters[i] = new Parameter();
|
|
2062
|
+
}
|
|
2063
|
+
ODBC::StoreBindValues(&napiArray, data->parameters);
|
|
2064
|
+
} else if ((info.Length() == 4 && info[4].IsFunction()) || (info.Length() == 5 && info[3].IsNull() && info[4].IsFunction())) {
|
|
2065
|
+
data->parameters = 0;
|
|
2066
|
+
} else {
|
|
2067
|
+
Napi::TypeError::New(env, "[odbc]: Wrong function signature in call to Connection.callProcedure({string}, {array}[optional], {function}).").ThrowAsJavaScriptException();
|
|
2068
|
+
return env.Null();
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
Napi::Function callback = info[info.Length() - 1].As<Napi::Function>();
|
|
2072
|
+
|
|
2073
|
+
CallProcedureAsyncWorker *worker = new CallProcedureAsyncWorker(this, napiParameterArray, data, callback);
|
|
2074
|
+
worker->Queue();
|
|
2075
|
+
return env.Undefined();
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/*
|
|
2079
|
+
* ODBCConnection::GetUsername
|
|
2080
|
+
*
|
|
2081
|
+
* Description: Returns the username requested from the connection.
|
|
2082
|
+
*
|
|
2083
|
+
* Parameters:
|
|
2084
|
+
* const Napi::CallbackInfo& info:
|
|
2085
|
+
* The information passed from the JavaSript environment, including the
|
|
2086
|
+
* function arguments for 'getInfo'.
|
|
2087
|
+
*
|
|
2088
|
+
* info[0]: Number: option
|
|
2089
|
+
* info[4]: Function: callback function:
|
|
2090
|
+
* function(error, result)
|
|
2091
|
+
* error: An error object if the connection was not opened, or
|
|
2092
|
+
* null if operation was successful.
|
|
2093
|
+
* result: A string containing the info requested.
|
|
2094
|
+
*
|
|
2095
|
+
* Return:
|
|
2096
|
+
* Napi::Value:
|
|
2097
|
+
* Undefined (results returned in callback)
|
|
2098
|
+
*/
|
|
2099
|
+
Napi::Value ODBCConnection::GetUsername(const Napi::CallbackInfo& info) {
|
|
2100
|
+
|
|
2101
|
+
Napi::Env env = info.Env();
|
|
2102
|
+
Napi::HandleScope scope(env);
|
|
2103
|
+
|
|
2104
|
+
return this->GetInfo(env, SQL_USER_NAME);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
Napi::Value ODBCConnection::GetInfo(const Napi::Env env, const SQLUSMALLINT option) {
|
|
2108
|
+
|
|
2109
|
+
SQLTCHAR infoValue[255];
|
|
2110
|
+
SQLSMALLINT infoLength;
|
|
2111
|
+
SQLRETURN return_code;
|
|
2112
|
+
|
|
2113
|
+
return_code =
|
|
2114
|
+
SQLGetInfo
|
|
2115
|
+
(
|
|
2116
|
+
this->hDBC, // ConnectionHandle
|
|
2117
|
+
SQL_USER_NAME, // InfoType
|
|
2118
|
+
infoValue, // InfoValuePtr
|
|
2119
|
+
sizeof(infoValue), // BufferLength
|
|
2120
|
+
&infoLength // StringLengthPtr
|
|
2121
|
+
);
|
|
2122
|
+
|
|
2123
|
+
if (SQL_SUCCEEDED(return_code)) {
|
|
2124
|
+
#ifdef UNICODE
|
|
2125
|
+
return Napi::String::New(env, (const char16_t *)infoValue, infoLength);
|
|
2126
|
+
#else
|
|
2127
|
+
return Napi::String::New(env, (const char *) infoValue, infoLength);
|
|
2128
|
+
#endif
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// TODO: Fix
|
|
2132
|
+
// Napi::Error(env, Napi::String::New(env, ODBC::GetSQLError(SQL_HANDLE_DBC, this->hDBC, (char *) "[node-odbc] Error in ODBCConnection::GetInfo"))).ThrowAsJavaScriptException();
|
|
2133
|
+
return env.Null();
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
2137
|
+
//
|
|
2138
|
+
// PrimaryKeys
|
|
2139
|
+
//
|
|
2140
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
2141
|
+
|
|
2142
|
+
// PrimaryKeysAsyncWorker, used by the PrimaryKeys function below
|
|
2143
|
+
class PrimaryKeysAsyncWorker : public ODBCAsyncWorker
|
|
2144
|
+
{
|
|
2145
|
+
private:
|
|
2146
|
+
|
|
2147
|
+
ODBCConnection *odbcConnectionObject;
|
|
2148
|
+
StatementData *data;
|
|
2149
|
+
|
|
2150
|
+
void Execute() {
|
|
2151
|
+
|
|
2152
|
+
SQLRETURN return_code;
|
|
2153
|
+
|
|
2154
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
2155
|
+
return_code = SQLAllocHandle(
|
|
2156
|
+
SQL_HANDLE_STMT, // HandleType
|
|
2157
|
+
odbcConnectionObject->hDBC, // InputHandle
|
|
2158
|
+
&data->hstmt // OutputHandlePtr
|
|
2159
|
+
);
|
|
2160
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
2161
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2162
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
2163
|
+
SetError("[odbc] Error allocating a statement handle to get primary key information\0");
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
return_code =
|
|
2168
|
+
set_fetch_size
|
|
2169
|
+
(
|
|
2170
|
+
data,
|
|
2171
|
+
1
|
|
2172
|
+
);
|
|
2173
|
+
|
|
2174
|
+
return_code =
|
|
2175
|
+
SQLPrimaryKeys
|
|
2176
|
+
(
|
|
2177
|
+
data->hstmt, // StatementHandle
|
|
2178
|
+
data->catalog, // CatalogName
|
|
2179
|
+
SQL_NTS, // NameLength1
|
|
2180
|
+
data->schema, // SchemaName
|
|
2181
|
+
SQL_NTS, // NameLength2
|
|
2182
|
+
data->table, // TableName
|
|
2183
|
+
SQL_NTS // NameLength3
|
|
2184
|
+
);
|
|
2185
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2186
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
2187
|
+
SetError("[odbc] Error getting table information\0");
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
return_code = prepare_for_fetch(data);
|
|
2192
|
+
bool alloc_error = false;
|
|
2193
|
+
return_code =
|
|
2194
|
+
fetch_all_and_store
|
|
2195
|
+
(
|
|
2196
|
+
data,
|
|
2197
|
+
false,
|
|
2198
|
+
&alloc_error
|
|
2199
|
+
);
|
|
2200
|
+
if (alloc_error)
|
|
2201
|
+
{
|
|
2202
|
+
SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0");
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2206
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
2207
|
+
SetError("[odbc] Error retrieving table information results set\0");
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
void OnOK() {
|
|
2213
|
+
|
|
2214
|
+
Napi::Env env = Env();
|
|
2215
|
+
Napi::HandleScope scope(env);
|
|
2216
|
+
|
|
2217
|
+
std::vector<napi_value> callbackArguments;
|
|
2218
|
+
|
|
2219
|
+
callbackArguments.push_back(env.Null());
|
|
2220
|
+
|
|
2221
|
+
Napi::Array empty = Napi::Array::New(env);
|
|
2222
|
+
Napi::Array rows = process_data_for_napi(env, data, empty);
|
|
2223
|
+
callbackArguments.push_back(rows);
|
|
2224
|
+
|
|
2225
|
+
Callback().Call(callbackArguments);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
public:
|
|
2229
|
+
|
|
2230
|
+
PrimaryKeysAsyncWorker(ODBCConnection *odbcConnectionObject, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
2231
|
+
odbcConnectionObject(odbcConnectionObject),
|
|
2232
|
+
data(data) {}
|
|
2233
|
+
|
|
2234
|
+
~PrimaryKeysAsyncWorker() {
|
|
2235
|
+
delete data;
|
|
2236
|
+
data = NULL;
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
|
|
2240
|
+
//
|
|
2241
|
+
// ODBCConnection::PrimaryKeys
|
|
2242
|
+
//
|
|
2243
|
+
// Description: Returns the columns names that make up the primary key for a
|
|
2244
|
+
// table.
|
|
2245
|
+
//
|
|
2246
|
+
// Parameters:
|
|
2247
|
+
// const Napi::CallbackInfo& info:
|
|
2248
|
+
// The information passed from the JavaSript environment, including the
|
|
2249
|
+
// function arguments for 'tables'.
|
|
2250
|
+
//
|
|
2251
|
+
// info[0]: String: catalog
|
|
2252
|
+
// info[1]: String: schema
|
|
2253
|
+
// info[2]: String: table
|
|
2254
|
+
// info[3]: Function: callback function:
|
|
2255
|
+
// function(error, result)
|
|
2256
|
+
// error: An error object if there was a database issue
|
|
2257
|
+
// result: The ODBCResult
|
|
2258
|
+
//
|
|
2259
|
+
// Return:
|
|
2260
|
+
// Napi::Value:
|
|
2261
|
+
// Undefined (results returned in callback)
|
|
2262
|
+
//
|
|
2263
|
+
Napi::Value ODBCConnection::PrimaryKeys(const Napi::CallbackInfo& info) {
|
|
2264
|
+
|
|
2265
|
+
Napi::Env env = info.Env();
|
|
2266
|
+
Napi::HandleScope scope(env);
|
|
2267
|
+
|
|
2268
|
+
if (info.Length() != 4) {
|
|
2269
|
+
Napi::TypeError::New(env, "primaryKeys() function takes 4 arguments.").ThrowAsJavaScriptException();
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
Napi::Function callback;
|
|
2273
|
+
StatementData* data = new (std::nothrow) StatementData();
|
|
2274
|
+
// Napi doesn't have LowMemoryNotification like NAN did. Throw standard error.
|
|
2275
|
+
if (!data) {
|
|
2276
|
+
Napi::TypeError::New(env, "Could not allocate enough memory to run query.").ThrowAsJavaScriptException();
|
|
2277
|
+
return env.Null();
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
data->henv = this->hENV;
|
|
2281
|
+
data->hdbc = this->hDBC;
|
|
2282
|
+
data->fetch_array = this->connectionOptions.fetchArray;
|
|
2283
|
+
data->maxColumnNameLength = this->getInfoResults.max_column_name_length;
|
|
2284
|
+
data->get_data_supports = this->getInfoResults.sql_get_data_supports;
|
|
2285
|
+
|
|
2286
|
+
if (info[0].IsString()) {
|
|
2287
|
+
data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString());
|
|
2288
|
+
} else if (!info[0].IsNull()) {
|
|
2289
|
+
Napi::TypeError::New(env, "primaryKeys: first argument must be a string or null").ThrowAsJavaScriptException();
|
|
2290
|
+
delete data;
|
|
2291
|
+
return env.Null();
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
if (info[1].IsString()) {
|
|
2295
|
+
data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString());
|
|
2296
|
+
} else if (!info[1].IsNull()) {
|
|
2297
|
+
Napi::TypeError::New(env, "primaryKeys: second argument must be a string or null").ThrowAsJavaScriptException();
|
|
2298
|
+
delete data;
|
|
2299
|
+
return env.Null();
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
if (info[2].IsString()) {
|
|
2303
|
+
data->table = ODBC::NapiStringToSQLTCHAR(info[2].ToString());
|
|
2304
|
+
} else if (!info[2].IsNull()) {
|
|
2305
|
+
Napi::TypeError::New(env, "primaryKeys: third argument must be a string or null").ThrowAsJavaScriptException();
|
|
2306
|
+
delete data;
|
|
2307
|
+
return env.Null();
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
if (info[3].IsFunction()) { callback = info[3].As<Napi::Function>(); }
|
|
2311
|
+
else {
|
|
2312
|
+
Napi::TypeError::New(env, "primaryKeys: fourth argument must be a function").ThrowAsJavaScriptException();
|
|
2313
|
+
delete data;
|
|
2314
|
+
return env.Null();
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
PrimaryKeysAsyncWorker *worker = new PrimaryKeysAsyncWorker(this, data, callback);
|
|
2318
|
+
worker->Queue();
|
|
2319
|
+
|
|
2320
|
+
return env.Undefined();
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
2324
|
+
//
|
|
2325
|
+
// ForeignKeys
|
|
2326
|
+
//
|
|
2327
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
2328
|
+
|
|
2329
|
+
// ForeignKeysAsyncWorker, used by the ForeignKeys function below
|
|
2330
|
+
class ForeignKeysAsyncWorker : public ODBCAsyncWorker
|
|
2331
|
+
{
|
|
2332
|
+
private:
|
|
2333
|
+
|
|
2334
|
+
ODBCConnection *odbcConnectionObject;
|
|
2335
|
+
StatementData *data;
|
|
2336
|
+
|
|
2337
|
+
void Execute() {
|
|
2338
|
+
|
|
2339
|
+
SQLRETURN return_code;
|
|
2340
|
+
|
|
2341
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
2342
|
+
return_code = SQLAllocHandle(
|
|
2343
|
+
SQL_HANDLE_STMT, // HandleType
|
|
2344
|
+
odbcConnectionObject->hDBC, // InputHandle
|
|
2345
|
+
&data->hstmt // OutputHandlePtr
|
|
2346
|
+
);
|
|
2347
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
2348
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2349
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
2350
|
+
SetError("[odbc] Error allocating a statement handle to get foriegn key information\0");
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
return_code =
|
|
2355
|
+
set_fetch_size
|
|
2356
|
+
(
|
|
2357
|
+
data,
|
|
2358
|
+
1
|
|
2359
|
+
);
|
|
2360
|
+
|
|
2361
|
+
return_code =
|
|
2362
|
+
SQLForeignKeys
|
|
2363
|
+
(
|
|
2364
|
+
data->hstmt, // StatementHandle
|
|
2365
|
+
data->catalog, // PKCatalogName
|
|
2366
|
+
SQL_NTS, // NameLength1
|
|
2367
|
+
data->schema, // PKSchemaName
|
|
2368
|
+
SQL_NTS, // NameLength2
|
|
2369
|
+
data->table, // PKTableName
|
|
2370
|
+
SQL_NTS, // NameLength3
|
|
2371
|
+
data->fkCatalog, // FKCatalogName
|
|
2372
|
+
SQL_NTS, // NameLength4,
|
|
2373
|
+
data->fkSchema, // FKSchemaName
|
|
2374
|
+
SQL_NTS, // NameLength5,
|
|
2375
|
+
data->fkTable, // FKTableName,
|
|
2376
|
+
SQL_NTS // NameLength6
|
|
2377
|
+
);
|
|
2378
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2379
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
2380
|
+
SetError("[odbc] Error getting table information\0");
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
return_code = prepare_for_fetch(data);
|
|
2385
|
+
bool alloc_error = false;
|
|
2386
|
+
return_code =
|
|
2387
|
+
fetch_all_and_store
|
|
2388
|
+
(
|
|
2389
|
+
data,
|
|
2390
|
+
false,
|
|
2391
|
+
&alloc_error
|
|
2392
|
+
);
|
|
2393
|
+
if (alloc_error)
|
|
2394
|
+
{
|
|
2395
|
+
SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0");
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2399
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
2400
|
+
SetError("[odbc] Error retrieving table information results set\0");
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
void OnOK() {
|
|
2406
|
+
|
|
2407
|
+
Napi::Env env = Env();
|
|
2408
|
+
Napi::HandleScope scope(env);
|
|
2409
|
+
|
|
2410
|
+
std::vector<napi_value> callbackArguments;
|
|
2411
|
+
|
|
2412
|
+
callbackArguments.push_back(env.Null());
|
|
2413
|
+
|
|
2414
|
+
Napi::Array empty = Napi::Array::New(env);
|
|
2415
|
+
Napi::Array rows = process_data_for_napi(env, data, empty);
|
|
2416
|
+
callbackArguments.push_back(rows);
|
|
2417
|
+
|
|
2418
|
+
Callback().Call(callbackArguments);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
public:
|
|
2422
|
+
|
|
2423
|
+
ForeignKeysAsyncWorker(ODBCConnection *odbcConnectionObject, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
2424
|
+
odbcConnectionObject(odbcConnectionObject),
|
|
2425
|
+
data(data) {}
|
|
2426
|
+
|
|
2427
|
+
~ForeignKeysAsyncWorker() {
|
|
2428
|
+
delete data;
|
|
2429
|
+
data = NULL;
|
|
2430
|
+
}
|
|
2431
|
+
};
|
|
2432
|
+
|
|
2433
|
+
//
|
|
2434
|
+
// ODBCConnection::ForeignKeys
|
|
2435
|
+
//
|
|
2436
|
+
// Description: Returns either a list of foreign keys in the specified able,
|
|
2437
|
+
// or foriegn keys in other tables that refer to the primary key in the
|
|
2438
|
+
// specified table.
|
|
2439
|
+
//
|
|
2440
|
+
// Parameters:
|
|
2441
|
+
// const Napi::CallbackInfo& info:
|
|
2442
|
+
// The information passed from the JavaSript environment, including the
|
|
2443
|
+
// function arguments for 'tables'.
|
|
2444
|
+
//
|
|
2445
|
+
// info[0]: String: primaryKeyCatalog
|
|
2446
|
+
// info[1]: String: primaryKeySchema
|
|
2447
|
+
// info[2]: String: primaryKeyTable
|
|
2448
|
+
// info[3]: String: foreignKeyCatalog
|
|
2449
|
+
// info[4]: String: foreignKeySchema
|
|
2450
|
+
// info[5]: String: foreignKeyTable
|
|
2451
|
+
// info[6]: Function: callback function:
|
|
2452
|
+
// function(error, result)
|
|
2453
|
+
// error: An error object if there was a database issue
|
|
2454
|
+
// result: The ODBCResult
|
|
2455
|
+
//
|
|
2456
|
+
// Return:
|
|
2457
|
+
// Napi::Value:
|
|
2458
|
+
// Undefined (results returned in callback)
|
|
2459
|
+
//
|
|
2460
|
+
Napi::Value ODBCConnection::ForeignKeys(const Napi::CallbackInfo& info) {
|
|
2461
|
+
|
|
2462
|
+
Napi::Env env = info.Env();
|
|
2463
|
+
Napi::HandleScope scope(env);
|
|
2464
|
+
|
|
2465
|
+
if (info.Length() != 7) {
|
|
2466
|
+
Napi::TypeError::New(env, "foriegnKeys() function takes 7 arguments.").ThrowAsJavaScriptException();
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
Napi::Function callback;
|
|
2470
|
+
StatementData* data = new (std::nothrow) StatementData();
|
|
2471
|
+
|
|
2472
|
+
// Napi doesn't have LowMemoryNotification like NAN did. Throw standard error.
|
|
2473
|
+
if (!data) {
|
|
2474
|
+
Napi::TypeError::New(env, "Could not allocate enough memory to run query.").ThrowAsJavaScriptException();
|
|
2475
|
+
return env.Null();
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
data->henv = this->hENV;
|
|
2479
|
+
data->hdbc = this->hDBC;
|
|
2480
|
+
data->fetch_array = this->connectionOptions.fetchArray;
|
|
2481
|
+
data->maxColumnNameLength = this->getInfoResults.max_column_name_length;
|
|
2482
|
+
data->get_data_supports = this->getInfoResults.sql_get_data_supports;
|
|
2483
|
+
|
|
2484
|
+
if (info[0].IsString()) {
|
|
2485
|
+
data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString());
|
|
2486
|
+
} else if (!info[0].IsNull()) {
|
|
2487
|
+
Napi::TypeError::New(env, "foriegnKeys: first argument must be a string or null").ThrowAsJavaScriptException();
|
|
2488
|
+
delete data;
|
|
2489
|
+
return env.Null();
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
if (info[1].IsString()) {
|
|
2493
|
+
data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString());
|
|
2494
|
+
} else if (!info[1].IsNull()) {
|
|
2495
|
+
Napi::TypeError::New(env, "foriegnKeys: second argument must be a string or null").ThrowAsJavaScriptException();
|
|
2496
|
+
delete data;
|
|
2497
|
+
return env.Null();
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
if (info[2].IsString()) {
|
|
2501
|
+
data->table = ODBC::NapiStringToSQLTCHAR(info[2].ToString());
|
|
2502
|
+
} else if (!info[2].IsNull()) {
|
|
2503
|
+
Napi::TypeError::New(env, "foriegnKeys: third argument must be a string or null").ThrowAsJavaScriptException();
|
|
2504
|
+
delete data;
|
|
2505
|
+
return env.Null();
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
if (info[3].IsString()) {
|
|
2509
|
+
data->fkCatalog = ODBC::NapiStringToSQLTCHAR(info[3].ToString());
|
|
2510
|
+
} else if (!info[3].IsNull()) {
|
|
2511
|
+
Napi::TypeError::New(env, "foriegnKeys: fourth argument must be a string or null").ThrowAsJavaScriptException();
|
|
2512
|
+
delete data;
|
|
2513
|
+
return env.Null();
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
if (info[4].IsString()) {
|
|
2517
|
+
data->fkSchema = ODBC::NapiStringToSQLTCHAR(info[4].ToString());
|
|
2518
|
+
} else if (!info[4].IsNull()) {
|
|
2519
|
+
Napi::TypeError::New(env, "foriegnKeys: fifth argument must be a string or null").ThrowAsJavaScriptException();
|
|
2520
|
+
delete data;
|
|
2521
|
+
return env.Null();
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
if (info[5].IsString()) {
|
|
2525
|
+
data->fkTable = ODBC::NapiStringToSQLTCHAR(info[5].ToString());
|
|
2526
|
+
} else if (!info[5].IsNull()) {
|
|
2527
|
+
Napi::TypeError::New(env, "foriegnKeys: sixth argument must be a string or null").ThrowAsJavaScriptException();
|
|
2528
|
+
delete data;
|
|
2529
|
+
return env.Null();
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
if (info[6].IsFunction()) { callback = info[6].As<Napi::Function>(); }
|
|
2533
|
+
else {
|
|
2534
|
+
Napi::TypeError::New(env, "foriegnKeys: seventh argument must be a function").ThrowAsJavaScriptException();
|
|
2535
|
+
delete data;
|
|
2536
|
+
return env.Null();
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
ForeignKeysAsyncWorker *worker = new ForeignKeysAsyncWorker(this, data, callback);
|
|
2540
|
+
worker->Queue();
|
|
2541
|
+
|
|
2542
|
+
return env.Undefined();
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
/******************************************************************************
|
|
2546
|
+
********************************** TABLES ************************************
|
|
2547
|
+
*****************************************************************************/
|
|
2548
|
+
|
|
2549
|
+
// TablesAsyncWorker, used by Tables function (see below)
|
|
2550
|
+
class TablesAsyncWorker : public ODBCAsyncWorker {
|
|
2551
|
+
|
|
2552
|
+
private:
|
|
2553
|
+
|
|
2554
|
+
ODBCConnection *odbcConnectionObject;
|
|
2555
|
+
StatementData *data;
|
|
2556
|
+
|
|
2557
|
+
void Execute() {
|
|
2558
|
+
|
|
2559
|
+
SQLRETURN return_code;
|
|
2560
|
+
|
|
2561
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
2562
|
+
return_code = SQLAllocHandle(
|
|
2563
|
+
SQL_HANDLE_STMT, // HandleType
|
|
2564
|
+
odbcConnectionObject->hDBC, // InputHandle
|
|
2565
|
+
&data->hstmt // OutputHandlePtr
|
|
2566
|
+
);
|
|
2567
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
2568
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2569
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
2570
|
+
SetError("[odbc] Error allocating a statement handle to get table information\0");
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
return_code =
|
|
2575
|
+
set_fetch_size
|
|
2576
|
+
(
|
|
2577
|
+
data,
|
|
2578
|
+
1
|
|
2579
|
+
);
|
|
2580
|
+
|
|
2581
|
+
return_code =
|
|
2582
|
+
SQLTables
|
|
2583
|
+
(
|
|
2584
|
+
data->hstmt, // StatementHandle
|
|
2585
|
+
data->catalog, // CatalogName
|
|
2586
|
+
SQL_NTS, // NameLength1
|
|
2587
|
+
data->schema, // SchemaName
|
|
2588
|
+
SQL_NTS, // NameLength2
|
|
2589
|
+
data->table, // TableName
|
|
2590
|
+
SQL_NTS, // NameLength3
|
|
2591
|
+
data->type, // TableType
|
|
2592
|
+
SQL_NTS // NameLength4
|
|
2593
|
+
);
|
|
2594
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2595
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
2596
|
+
SetError("[odbc] Error getting table information\0");
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
return_code = prepare_for_fetch(data);
|
|
2601
|
+
bool alloc_error = false;
|
|
2602
|
+
return_code =
|
|
2603
|
+
fetch_all_and_store
|
|
2604
|
+
(
|
|
2605
|
+
data,
|
|
2606
|
+
false,
|
|
2607
|
+
&alloc_error
|
|
2608
|
+
);
|
|
2609
|
+
if (alloc_error)
|
|
2610
|
+
{
|
|
2611
|
+
SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0");
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2615
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
2616
|
+
SetError("[odbc] Error retrieving table information results set\0");
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
void OnOK() {
|
|
2622
|
+
|
|
2623
|
+
Napi::Env env = Env();
|
|
2624
|
+
Napi::HandleScope scope(env);
|
|
2625
|
+
|
|
2626
|
+
std::vector<napi_value> callbackArguments;
|
|
2627
|
+
|
|
2628
|
+
callbackArguments.push_back(env.Null());
|
|
2629
|
+
|
|
2630
|
+
Napi::Array empty = Napi::Array::New(env);
|
|
2631
|
+
Napi::Array rows = process_data_for_napi(env, data, empty);
|
|
2632
|
+
callbackArguments.push_back(rows);
|
|
2633
|
+
|
|
2634
|
+
Callback().Call(callbackArguments);
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
public:
|
|
2638
|
+
|
|
2639
|
+
TablesAsyncWorker(ODBCConnection *odbcConnectionObject, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
2640
|
+
odbcConnectionObject(odbcConnectionObject),
|
|
2641
|
+
data(data) {}
|
|
2642
|
+
|
|
2643
|
+
~TablesAsyncWorker() {
|
|
2644
|
+
delete data;
|
|
2645
|
+
data = NULL;
|
|
2646
|
+
}
|
|
2647
|
+
};
|
|
2648
|
+
|
|
2649
|
+
/*
|
|
2650
|
+
* ODBCConnection::Tables
|
|
2651
|
+
*
|
|
2652
|
+
* Description: Returns the list of table, catalog, or schema names, and
|
|
2653
|
+
* table types, stored in a specific data source.
|
|
2654
|
+
*
|
|
2655
|
+
* Parameters:
|
|
2656
|
+
* const Napi::CallbackInfo& info:
|
|
2657
|
+
* The information passed from the JavaSript environment, including the
|
|
2658
|
+
* function arguments for 'tables'.
|
|
2659
|
+
*
|
|
2660
|
+
* info[0]: String: catalog
|
|
2661
|
+
* info[1]: String: schema
|
|
2662
|
+
* info[2]: String: table
|
|
2663
|
+
* info[3]: String: type
|
|
2664
|
+
* info[4]: Function: callback function:
|
|
2665
|
+
* function(error, result)
|
|
2666
|
+
* error: An error object if there was a database issue
|
|
2667
|
+
* result: The ODBCResult
|
|
2668
|
+
*
|
|
2669
|
+
* Return:
|
|
2670
|
+
* Napi::Value:
|
|
2671
|
+
* Undefined (results returned in callback)
|
|
2672
|
+
*/
|
|
2673
|
+
Napi::Value ODBCConnection::Tables(const Napi::CallbackInfo& info) {
|
|
2674
|
+
|
|
2675
|
+
Napi::Env env = info.Env();
|
|
2676
|
+
Napi::HandleScope scope(env);
|
|
2677
|
+
|
|
2678
|
+
if (info.Length() != 5) {
|
|
2679
|
+
Napi::TypeError::New(env, "tables() function takes 5 arguments.").ThrowAsJavaScriptException();
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
Napi::Function callback;
|
|
2683
|
+
StatementData* data = new StatementData();
|
|
2684
|
+
data->henv = this->hENV;
|
|
2685
|
+
data->hdbc = this->hDBC;
|
|
2686
|
+
data->fetch_array = this->connectionOptions.fetchArray;
|
|
2687
|
+
data->maxColumnNameLength = this->getInfoResults.max_column_name_length;
|
|
2688
|
+
data->get_data_supports = this->getInfoResults.sql_get_data_supports;
|
|
2689
|
+
// Napi doesn't have LowMemoryNotification like NAN did. Throw standard error.
|
|
2690
|
+
if (!data) {
|
|
2691
|
+
Napi::TypeError::New(env, "Could not allocate enough memory to run query.").ThrowAsJavaScriptException();
|
|
2692
|
+
delete data;
|
|
2693
|
+
data = NULL;
|
|
2694
|
+
return env.Null();
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
if (info[0].IsString()) {
|
|
2698
|
+
data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString());
|
|
2699
|
+
} else if (!info[0].IsNull()) {
|
|
2700
|
+
Napi::TypeError::New(env, "tables: first argument must be a string or null").ThrowAsJavaScriptException();
|
|
2701
|
+
delete data;
|
|
2702
|
+
data = NULL;
|
|
2703
|
+
return env.Null();
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
if (info[1].IsString()) {
|
|
2707
|
+
data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString());
|
|
2708
|
+
} else if (!info[1].IsNull()) {
|
|
2709
|
+
Napi::TypeError::New(env, "tables: second argument must be a string or null").ThrowAsJavaScriptException();
|
|
2710
|
+
delete data;
|
|
2711
|
+
data = NULL;
|
|
2712
|
+
return env.Null();
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
if (info[2].IsString()) {
|
|
2716
|
+
data->table = ODBC::NapiStringToSQLTCHAR(info[2].ToString());
|
|
2717
|
+
} else if (!info[2].IsNull()) {
|
|
2718
|
+
Napi::TypeError::New(env, "tables: third argument must be a string or null").ThrowAsJavaScriptException();
|
|
2719
|
+
delete data;
|
|
2720
|
+
data = NULL;
|
|
2721
|
+
return env.Null();
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
if (info[3].IsString()) {
|
|
2725
|
+
data->type = ODBC::NapiStringToSQLTCHAR(info[3].ToString());
|
|
2726
|
+
} else if (!info[3].IsNull()) {
|
|
2727
|
+
Napi::TypeError::New(env, "tables: fourth argument must be a string or null").ThrowAsJavaScriptException();
|
|
2728
|
+
delete data;
|
|
2729
|
+
data = NULL;
|
|
2730
|
+
return env.Null();
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
if (info[4].IsFunction()) { callback = info[4].As<Napi::Function>(); }
|
|
2734
|
+
else {
|
|
2735
|
+
Napi::TypeError::New(env, "tables: fifth argument must be a function").ThrowAsJavaScriptException();
|
|
2736
|
+
delete data;
|
|
2737
|
+
data = NULL;
|
|
2738
|
+
return env.Null();
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
TablesAsyncWorker *worker = new TablesAsyncWorker(this, data, callback);
|
|
2742
|
+
worker->Queue();
|
|
2743
|
+
|
|
2744
|
+
return env.Undefined();
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
/******************************************************************************
|
|
2748
|
+
********************************* COLUMNS ************************************
|
|
2749
|
+
*****************************************************************************/
|
|
2750
|
+
|
|
2751
|
+
// ColumnsAsyncWorker, used by Columns function (see below)
|
|
2752
|
+
class ColumnsAsyncWorker : public ODBCAsyncWorker {
|
|
2753
|
+
|
|
2754
|
+
private:
|
|
2755
|
+
|
|
2756
|
+
ODBCConnection *odbcConnectionObject;
|
|
2757
|
+
StatementData *data;
|
|
2758
|
+
|
|
2759
|
+
void Execute() {
|
|
2760
|
+
|
|
2761
|
+
SQLRETURN return_code;
|
|
2762
|
+
|
|
2763
|
+
uv_mutex_lock(&ODBC::g_odbcMutex);
|
|
2764
|
+
return_code =
|
|
2765
|
+
SQLAllocHandle
|
|
2766
|
+
(
|
|
2767
|
+
SQL_HANDLE_STMT, // HandleType
|
|
2768
|
+
odbcConnectionObject->hDBC, // InputHandle
|
|
2769
|
+
&data->hstmt // OutputHandlePtr
|
|
2770
|
+
);
|
|
2771
|
+
uv_mutex_unlock(&ODBC::g_odbcMutex);
|
|
2772
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2773
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
2774
|
+
SetError("[odbc] Error allocating a statement handle to get column information\0");
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
return_code =
|
|
2779
|
+
set_fetch_size
|
|
2780
|
+
(
|
|
2781
|
+
data,
|
|
2782
|
+
1
|
|
2783
|
+
);
|
|
2784
|
+
|
|
2785
|
+
return_code =
|
|
2786
|
+
SQLColumns
|
|
2787
|
+
(
|
|
2788
|
+
data->hstmt, // StatementHandle
|
|
2789
|
+
data->catalog, // CatalogName
|
|
2790
|
+
SQL_NTS, // NameLength1
|
|
2791
|
+
data->schema, // SchemaName
|
|
2792
|
+
SQL_NTS, // NameLength2
|
|
2793
|
+
data->table, // TableName
|
|
2794
|
+
SQL_NTS, // NameLength3
|
|
2795
|
+
data->column, // ColumnName
|
|
2796
|
+
SQL_NTS // NameLength4
|
|
2797
|
+
);
|
|
2798
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2799
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
2800
|
+
SetError("[odbc] Error getting column information\0");
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
return_code = prepare_for_fetch(data);
|
|
2805
|
+
bool alloc_error = false;
|
|
2806
|
+
return_code =
|
|
2807
|
+
fetch_all_and_store
|
|
2808
|
+
(
|
|
2809
|
+
data,
|
|
2810
|
+
false,
|
|
2811
|
+
&alloc_error
|
|
2812
|
+
);
|
|
2813
|
+
if (alloc_error)
|
|
2814
|
+
{
|
|
2815
|
+
SetError("[odbc] Error allocating or reallocating memory when fetching data. No ODBC error information available.\0");
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2819
|
+
this->errors = GetODBCErrors(SQL_HANDLE_STMT, data->hstmt);
|
|
2820
|
+
SetError("[odbc] Error retrieving column information results set\0");
|
|
2821
|
+
return;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
void OnOK() {
|
|
2826
|
+
|
|
2827
|
+
Napi::Env env = Env();
|
|
2828
|
+
Napi::HandleScope scope(env);
|
|
2829
|
+
|
|
2830
|
+
Napi::Array empty = Napi::Array::New(env);
|
|
2831
|
+
Napi::Array rows = process_data_for_napi(env, data, empty);
|
|
2832
|
+
|
|
2833
|
+
std::vector<napi_value> callbackArguments;
|
|
2834
|
+
callbackArguments.push_back(env.Null());
|
|
2835
|
+
callbackArguments.push_back(rows);
|
|
2836
|
+
Callback().Call(callbackArguments);
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
public:
|
|
2840
|
+
|
|
2841
|
+
ColumnsAsyncWorker(ODBCConnection *odbcConnectionObject, StatementData *data, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
2842
|
+
odbcConnectionObject(odbcConnectionObject),
|
|
2843
|
+
data(data) {}
|
|
2844
|
+
|
|
2845
|
+
~ColumnsAsyncWorker() {
|
|
2846
|
+
delete data;
|
|
2847
|
+
data = NULL;
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
|
|
2851
|
+
/*
|
|
2852
|
+
* ODBCConnection::Columns
|
|
2853
|
+
*
|
|
2854
|
+
* Description: Returns the list of column names in specified tables.
|
|
2855
|
+
*
|
|
2856
|
+
* Parameters:
|
|
2857
|
+
* const Napi::CallbackInfo& info:
|
|
2858
|
+
* The information passed from the JavaSript environment, including the
|
|
2859
|
+
* function arguments for 'columns'.
|
|
2860
|
+
*
|
|
2861
|
+
* info[0]: String: catalog
|
|
2862
|
+
* info[1]: String: schema
|
|
2863
|
+
* info[2]: String: table
|
|
2864
|
+
* info[3]: String: column
|
|
2865
|
+
* info[4]: Function: callback function:
|
|
2866
|
+
* function(error, result)
|
|
2867
|
+
* error: An error object if there was a database error
|
|
2868
|
+
* result: The ODBCResult
|
|
2869
|
+
*
|
|
2870
|
+
* Return:
|
|
2871
|
+
* Napi::Value:
|
|
2872
|
+
* Undefined (results returned in callback)
|
|
2873
|
+
*/
|
|
2874
|
+
Napi::Value ODBCConnection::Columns(const Napi::CallbackInfo& info) {
|
|
2875
|
+
|
|
2876
|
+
Napi::Env env = info.Env();
|
|
2877
|
+
Napi::HandleScope scope(env);
|
|
2878
|
+
|
|
2879
|
+
StatementData* data = new StatementData();
|
|
2880
|
+
data->henv = this->hENV;
|
|
2881
|
+
data->hdbc = this->hDBC;
|
|
2882
|
+
data->fetch_array = this->connectionOptions.fetchArray;
|
|
2883
|
+
data->maxColumnNameLength = this->getInfoResults.max_column_name_length;
|
|
2884
|
+
data->get_data_supports = this->getInfoResults.sql_get_data_supports;
|
|
2885
|
+
Napi::Function callback;
|
|
2886
|
+
|
|
2887
|
+
// Napi doesn't have LowMemoryNotification like NAN did. Throw standard error.
|
|
2888
|
+
if (!data) {
|
|
2889
|
+
Napi::Error::New(env, "Could not allocate enough memory to run query.").ThrowAsJavaScriptException();
|
|
2890
|
+
return env.Null();
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
if (info[0].IsString()) {
|
|
2894
|
+
data->catalog = ODBC::NapiStringToSQLTCHAR(info[0].ToString());
|
|
2895
|
+
} else if (!info[0].IsNull()) {
|
|
2896
|
+
Napi::Error::New(env, "columns: first argument must be a string or null").ThrowAsJavaScriptException();
|
|
2897
|
+
delete data;
|
|
2898
|
+
data = NULL;
|
|
2899
|
+
return env.Null();
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
if (info[1].IsString()) {
|
|
2903
|
+
data->schema = ODBC::NapiStringToSQLTCHAR(info[1].ToString());
|
|
2904
|
+
} else if (!info[1].IsNull()) {
|
|
2905
|
+
Napi::Error::New(env, "columns: second argument must be a string or null").ThrowAsJavaScriptException();
|
|
2906
|
+
delete data;
|
|
2907
|
+
data = NULL;
|
|
2908
|
+
return env.Null();
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
if (info[2].IsString()) {
|
|
2912
|
+
data->table = ODBC::NapiStringToSQLTCHAR(info[2].ToString());
|
|
2913
|
+
} else if (!info[2].IsNull()) {
|
|
2914
|
+
Napi::Error::New(env, "columns: third argument must be a string or null").ThrowAsJavaScriptException();
|
|
2915
|
+
delete data;
|
|
2916
|
+
data = NULL;
|
|
2917
|
+
return env.Null();
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
if (info[3].IsString()) {
|
|
2921
|
+
data->type = ODBC::NapiStringToSQLTCHAR(info[3].ToString());
|
|
2922
|
+
} else if (!info[3].IsNull()) {
|
|
2923
|
+
Napi::Error::New(env, "columns: fourth argument must be a string or null").ThrowAsJavaScriptException();
|
|
2924
|
+
delete data;
|
|
2925
|
+
data = NULL;
|
|
2926
|
+
return env.Null();
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
if (info[4].IsFunction()) { callback = info[4].As<Napi::Function>(); }
|
|
2930
|
+
else {
|
|
2931
|
+
Napi::Error::New(env, "columns: fifth argument must be a function").ThrowAsJavaScriptException();
|
|
2932
|
+
delete data;
|
|
2933
|
+
data = NULL;
|
|
2934
|
+
return env.Null();
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
ColumnsAsyncWorker *worker = new ColumnsAsyncWorker(this, data, callback);
|
|
2938
|
+
worker->Queue();
|
|
2939
|
+
|
|
2940
|
+
return env.Undefined();
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
/******************************************************************************
|
|
2944
|
+
**************************** BEGIN TRANSACTION *******************************
|
|
2945
|
+
*****************************************************************************/
|
|
2946
|
+
|
|
2947
|
+
// BeginTransactionAsyncWorker, used by EndTransaction function (see below)
|
|
2948
|
+
class BeginTransactionAsyncWorker : public ODBCAsyncWorker {
|
|
2949
|
+
|
|
2950
|
+
public:
|
|
2951
|
+
|
|
2952
|
+
BeginTransactionAsyncWorker(ODBCConnection *odbcConnectionObject, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
2953
|
+
odbcConnectionObject(odbcConnectionObject) {}
|
|
2954
|
+
|
|
2955
|
+
~BeginTransactionAsyncWorker() {};
|
|
2956
|
+
|
|
2957
|
+
private:
|
|
2958
|
+
|
|
2959
|
+
ODBCConnection *odbcConnectionObject;
|
|
2960
|
+
|
|
2961
|
+
void Execute() {
|
|
2962
|
+
|
|
2963
|
+
SQLRETURN return_code;
|
|
2964
|
+
|
|
2965
|
+
//set the connection manual commits
|
|
2966
|
+
return_code =
|
|
2967
|
+
SQLSetConnectAttr
|
|
2968
|
+
(
|
|
2969
|
+
odbcConnectionObject->hDBC, // ConnectionHandle
|
|
2970
|
+
SQL_ATTR_AUTOCOMMIT, // Attribute
|
|
2971
|
+
(SQLPOINTER) SQL_AUTOCOMMIT_OFF, // ValuePtr
|
|
2972
|
+
SQL_NTS // StringLength
|
|
2973
|
+
);
|
|
2974
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
2975
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
2976
|
+
SetError("[odbc] Error turning off autocommit on the connection\0");
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
void OnOK() {
|
|
2982
|
+
|
|
2983
|
+
Napi::Env env = Env();
|
|
2984
|
+
Napi::HandleScope scope(env);
|
|
2985
|
+
|
|
2986
|
+
std::vector<napi_value> callbackArguments;
|
|
2987
|
+
|
|
2988
|
+
callbackArguments.push_back(env.Null());
|
|
2989
|
+
|
|
2990
|
+
Callback().Call(callbackArguments);
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2993
|
+
|
|
2994
|
+
/*
|
|
2995
|
+
* ODBCConnection::BeginTransaction (Async)
|
|
2996
|
+
*
|
|
2997
|
+
* Description: Begin a transaction by turning off SQL_ATTR_AUTOCOMMIT.
|
|
2998
|
+
* Transaction is commited or rolledback in EndTransaction or
|
|
2999
|
+
* EndTransactionSync.
|
|
3000
|
+
*
|
|
3001
|
+
* Parameters:
|
|
3002
|
+
* const Napi::CallbackInfo& info:
|
|
3003
|
+
* The information passed from the JavaSript environment, including the
|
|
3004
|
+
* function arguments for 'beginTransaction'.
|
|
3005
|
+
*
|
|
3006
|
+
* info[0]: Function: callback function:
|
|
3007
|
+
* function(error)
|
|
3008
|
+
* error: An error object if the transaction wasn't started, or
|
|
3009
|
+
* null if operation was successful.
|
|
3010
|
+
*
|
|
3011
|
+
* Return:
|
|
3012
|
+
* Napi::Value:
|
|
3013
|
+
* Boolean, indicates whether the transaction was successfully started
|
|
3014
|
+
*/
|
|
3015
|
+
Napi::Value ODBCConnection::BeginTransaction(const Napi::CallbackInfo& info) {
|
|
3016
|
+
|
|
3017
|
+
Napi::Env env = info.Env();
|
|
3018
|
+
Napi::HandleScope scope(env);
|
|
3019
|
+
|
|
3020
|
+
Napi::Function callback;
|
|
3021
|
+
|
|
3022
|
+
if (info[0].IsFunction()) { callback = info[0].As<Napi::Function>(); }
|
|
3023
|
+
else { Napi::Error::New(env, "beginTransaction: first argument must be a function").ThrowAsJavaScriptException(); }
|
|
3024
|
+
|
|
3025
|
+
BeginTransactionAsyncWorker *worker = new BeginTransactionAsyncWorker(this, callback);
|
|
3026
|
+
worker->Queue();
|
|
3027
|
+
|
|
3028
|
+
return env.Undefined();
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
/******************************************************************************
|
|
3032
|
+
***************************** END TRANSACTION ********************************
|
|
3033
|
+
*****************************************************************************/
|
|
3034
|
+
|
|
3035
|
+
// EndTransactionAsyncWorker, used by Commit and Rollback functions (see below)
|
|
3036
|
+
class EndTransactionAsyncWorker : public ODBCAsyncWorker {
|
|
3037
|
+
|
|
3038
|
+
private:
|
|
3039
|
+
|
|
3040
|
+
ODBCConnection *odbcConnectionObject;
|
|
3041
|
+
SQLSMALLINT completionType;
|
|
3042
|
+
|
|
3043
|
+
void Execute() {
|
|
3044
|
+
|
|
3045
|
+
SQLRETURN return_code;
|
|
3046
|
+
|
|
3047
|
+
return_code =
|
|
3048
|
+
SQLEndTran
|
|
3049
|
+
(
|
|
3050
|
+
SQL_HANDLE_DBC, // HandleType
|
|
3051
|
+
odbcConnectionObject->hDBC, // Handle
|
|
3052
|
+
completionType // CompletionType
|
|
3053
|
+
);
|
|
3054
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
3055
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
3056
|
+
SetError("[odbc] Error ending the transaction on the connection\0");
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
//Reset the connection back to autocommit
|
|
3061
|
+
return_code =
|
|
3062
|
+
SQLSetConnectAttr
|
|
3063
|
+
(
|
|
3064
|
+
odbcConnectionObject->hDBC, // ConnectionHandle
|
|
3065
|
+
SQL_ATTR_AUTOCOMMIT, // Attribute
|
|
3066
|
+
(SQLPOINTER) SQL_AUTOCOMMIT_ON, // ValuePtr
|
|
3067
|
+
SQL_NTS // StringLength
|
|
3068
|
+
);
|
|
3069
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
3070
|
+
this->errors = GetODBCErrors(SQL_HANDLE_DBC, odbcConnectionObject->hDBC);
|
|
3071
|
+
SetError("[odbc] Error turning on autocommit on the connection\0");
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
void OnOK() {
|
|
3077
|
+
|
|
3078
|
+
Napi::Env env = Env();
|
|
3079
|
+
Napi::HandleScope scope(env);
|
|
3080
|
+
|
|
3081
|
+
std::vector<napi_value> callbackArguments;
|
|
3082
|
+
|
|
3083
|
+
callbackArguments.push_back(env.Null());
|
|
3084
|
+
|
|
3085
|
+
Callback().Call(callbackArguments);
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
public:
|
|
3089
|
+
|
|
3090
|
+
EndTransactionAsyncWorker(ODBCConnection *odbcConnectionObject, SQLSMALLINT completionType, Napi::Function& callback) : ODBCAsyncWorker(callback),
|
|
3091
|
+
odbcConnectionObject(odbcConnectionObject),
|
|
3092
|
+
completionType(completionType) {}
|
|
3093
|
+
|
|
3094
|
+
~EndTransactionAsyncWorker() {}
|
|
3095
|
+
};
|
|
3096
|
+
|
|
3097
|
+
|
|
3098
|
+
/*
|
|
3099
|
+
* ODBCConnection::Commit
|
|
3100
|
+
*
|
|
3101
|
+
* Description: Commit a transaction by calling SQLEndTran on the connection
|
|
3102
|
+
* in an AsyncWorker with SQL_COMMIT option.
|
|
3103
|
+
*
|
|
3104
|
+
* Parameters:
|
|
3105
|
+
* const Napi::CallbackInfo& info:
|
|
3106
|
+
* The information passed from the JavaSript environment, including the
|
|
3107
|
+
* function arguments for 'endTransaction'.
|
|
3108
|
+
*
|
|
3109
|
+
* info[0]: Function: callback function:
|
|
3110
|
+
* function(error)
|
|
3111
|
+
* error: An error object if the transaction wasn't ended, or
|
|
3112
|
+
* null if operation was successful.
|
|
3113
|
+
*
|
|
3114
|
+
* Return:
|
|
3115
|
+
* Napi::Value:
|
|
3116
|
+
* Undefined
|
|
3117
|
+
*/
|
|
3118
|
+
Napi::Value ODBCConnection::Commit(const Napi::CallbackInfo &info) {
|
|
3119
|
+
|
|
3120
|
+
Napi::Env env = info.Env();
|
|
3121
|
+
Napi::HandleScope scope(env);
|
|
3122
|
+
|
|
3123
|
+
if (!info[0].IsFunction()) {
|
|
3124
|
+
Napi::TypeError::New(env, "[odbc]: commit(callback): first argument must be a function").ThrowAsJavaScriptException();
|
|
3125
|
+
return env.Null();
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
Napi::Function callback = info[0].As<Napi::Function>();
|
|
3129
|
+
|
|
3130
|
+
// calls EndTransactionAsyncWorker with SQL_COMMIT option
|
|
3131
|
+
EndTransactionAsyncWorker *worker = new EndTransactionAsyncWorker(this, SQL_COMMIT, callback);
|
|
3132
|
+
worker->Queue();
|
|
3133
|
+
|
|
3134
|
+
return env.Undefined();
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
/*
|
|
3138
|
+
* ODBCConnection::Rollback
|
|
3139
|
+
*
|
|
3140
|
+
* Description: Rollback a transaction by calling SQLEndTran on the connection
|
|
3141
|
+
* in an AsyncWorker with SQL_ROLLBACK option.
|
|
3142
|
+
*
|
|
3143
|
+
* Parameters:
|
|
3144
|
+
* const Napi::CallbackInfo& info:
|
|
3145
|
+
* The information passed from the JavaSript environment, including the
|
|
3146
|
+
* function arguments for 'endTransaction'.
|
|
3147
|
+
*
|
|
3148
|
+
* info[0]: Function: callback function:
|
|
3149
|
+
* function(error)
|
|
3150
|
+
* error: An error object if the transaction wasn't ended, or
|
|
3151
|
+
* null if operation was successful.
|
|
3152
|
+
*
|
|
3153
|
+
* Return:
|
|
3154
|
+
* Napi::Value:
|
|
3155
|
+
* Undefined
|
|
3156
|
+
*/
|
|
3157
|
+
Napi::Value ODBCConnection::Rollback(const Napi::CallbackInfo &info) {
|
|
3158
|
+
|
|
3159
|
+
Napi::Env env = info.Env();
|
|
3160
|
+
Napi::HandleScope scope(env);
|
|
3161
|
+
|
|
3162
|
+
if (info.Length() != 1 && !info[0].IsFunction()) {
|
|
3163
|
+
Napi::TypeError::New(env, "[odbc]: rollback: first argument must be a function").ThrowAsJavaScriptException();
|
|
3164
|
+
return env.Null();
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
Napi::Function callback = info[0].As<Napi::Function>();
|
|
3168
|
+
|
|
3169
|
+
// calls EndTransactionAsyncWorker with SQL_ROLLBACK option
|
|
3170
|
+
EndTransactionAsyncWorker *worker = new EndTransactionAsyncWorker(this, SQL_ROLLBACK, callback);
|
|
3171
|
+
worker->Queue();
|
|
3172
|
+
|
|
3173
|
+
return env.Undefined();
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
SQLRETURN
|
|
3177
|
+
prepare_for_fetch
|
|
3178
|
+
(
|
|
3179
|
+
StatementData *data
|
|
3180
|
+
)
|
|
3181
|
+
{
|
|
3182
|
+
|
|
3183
|
+
SQLRETURN return_code;
|
|
3184
|
+
|
|
3185
|
+
return_code =
|
|
3186
|
+
SQLRowCount
|
|
3187
|
+
(
|
|
3188
|
+
data->hstmt, // StatementHandle
|
|
3189
|
+
&data->rowCount // RowCountPtr
|
|
3190
|
+
);
|
|
3191
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
3192
|
+
// if SQLRowCount failed, return early with the return_code
|
|
3193
|
+
return return_code;
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
return_code =
|
|
3197
|
+
bind_buffers
|
|
3198
|
+
(
|
|
3199
|
+
data
|
|
3200
|
+
);
|
|
3201
|
+
|
|
3202
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
3203
|
+
// if BindColumns failed, return early with the return_code
|
|
3204
|
+
return return_code;
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
return return_code;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
|
|
3211
|
+
SQLRETURN
|
|
3212
|
+
bind_buffers
|
|
3213
|
+
(
|
|
3214
|
+
StatementData *data
|
|
3215
|
+
)
|
|
3216
|
+
{
|
|
3217
|
+
SQLRETURN return_code;
|
|
3218
|
+
|
|
3219
|
+
return_code =
|
|
3220
|
+
SQLNumResultCols
|
|
3221
|
+
(
|
|
3222
|
+
data->hstmt,
|
|
3223
|
+
&data->column_count
|
|
3224
|
+
);
|
|
3225
|
+
|
|
3226
|
+
if (!SQL_SUCCEEDED(return_code))
|
|
3227
|
+
{
|
|
3228
|
+
return return_code;
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
return_code =
|
|
3232
|
+
SQLSetStmtAttr
|
|
3233
|
+
(
|
|
3234
|
+
data->hstmt,
|
|
3235
|
+
SQL_ATTR_ROW_BIND_TYPE,
|
|
3236
|
+
SQL_BIND_BY_COLUMN,
|
|
3237
|
+
IGNORED_PARAMETER
|
|
3238
|
+
);
|
|
3239
|
+
|
|
3240
|
+
// Some drivers don't feel the need to implement the SQL_ATTR_ROW_BIND_TYPE
|
|
3241
|
+
// option for SQLSetStmtAttr, so this call will return an error. Instead of
|
|
3242
|
+
// returning an error, just set "simple_binding" to true, and bind one row
|
|
3243
|
+
// at a time for fetching.
|
|
3244
|
+
if (!SQL_SUCCEEDED(return_code))
|
|
3245
|
+
{
|
|
3246
|
+
if (data->fetch_size != 1)
|
|
3247
|
+
{
|
|
3248
|
+
return return_code;
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
SQLRETURN diagnostic_return_code;
|
|
3252
|
+
SQLSMALLINT textLength;
|
|
3253
|
+
SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1];
|
|
3254
|
+
SQLINTEGER nativeError;
|
|
3255
|
+
SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES];
|
|
3256
|
+
|
|
3257
|
+
diagnostic_return_code =
|
|
3258
|
+
SQLGetDiagRec
|
|
3259
|
+
(
|
|
3260
|
+
SQL_HANDLE_STMT, // HandleType
|
|
3261
|
+
data->hstmt, // Handle
|
|
3262
|
+
1, // RecNumber
|
|
3263
|
+
errorSQLState, // SQLState
|
|
3264
|
+
&nativeError, // NativeErrorPtr
|
|
3265
|
+
errorMessage, // MessageText
|
|
3266
|
+
ERROR_MESSAGE_BUFFER_CHARS, // BufferLength
|
|
3267
|
+
&textLength // TextLengthPtr
|
|
3268
|
+
);
|
|
3269
|
+
|
|
3270
|
+
// Couldn't even get the diagnostic information! Something is wrong beyond
|
|
3271
|
+
// driver support for the SQLSetStmtAttr call, return the return_code
|
|
3272
|
+
if (!SQL_SUCCEEDED(diagnostic_return_code))
|
|
3273
|
+
{
|
|
3274
|
+
return return_code;
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
//
|
|
3278
|
+
if (strcmp("HY092", (const char*)errorSQLState) != 0)
|
|
3279
|
+
{
|
|
3280
|
+
return return_code;
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
data->simple_binding = true;
|
|
3284
|
+
data->rows_fetched = 1;
|
|
3285
|
+
return_code = SQL_SUCCESS;
|
|
3286
|
+
}
|
|
3287
|
+
else
|
|
3288
|
+
{
|
|
3289
|
+
// Create Columns for the column data to go into
|
|
3290
|
+
data->columns = new Column*[data->column_count]();
|
|
3291
|
+
data->bound_columns = new ColumnBuffer[data->column_count]();
|
|
3292
|
+
|
|
3293
|
+
return_code =
|
|
3294
|
+
SQLSetStmtAttr
|
|
3295
|
+
(
|
|
3296
|
+
data->hstmt,
|
|
3297
|
+
SQL_ATTR_ROWS_FETCHED_PTR,
|
|
3298
|
+
(SQLPOINTER) &data->rows_fetched,
|
|
3299
|
+
IGNORED_PARAMETER
|
|
3300
|
+
);
|
|
3301
|
+
|
|
3302
|
+
if (!SQL_SUCCEEDED(return_code))
|
|
3303
|
+
{
|
|
3304
|
+
if (data->fetch_size != 1)
|
|
3305
|
+
{
|
|
3306
|
+
return return_code;
|
|
3307
|
+
}
|
|
3308
|
+
SQLRETURN diagnostic_return_code;
|
|
3309
|
+
SQLSMALLINT textLength;
|
|
3310
|
+
SQLTCHAR errorSQLState[SQL_SQLSTATE_SIZE + 1];
|
|
3311
|
+
SQLINTEGER nativeError;
|
|
3312
|
+
SQLTCHAR errorMessage[ERROR_MESSAGE_BUFFER_BYTES];
|
|
3313
|
+
|
|
3314
|
+
diagnostic_return_code =
|
|
3315
|
+
SQLGetDiagRec
|
|
3316
|
+
(
|
|
3317
|
+
SQL_HANDLE_STMT, // HandleType
|
|
3318
|
+
data->hstmt, // Handle
|
|
3319
|
+
1, // RecNumber
|
|
3320
|
+
errorSQLState, // SQLState
|
|
3321
|
+
&nativeError, // NativeErrorPtr
|
|
3322
|
+
errorMessage, // MessageText
|
|
3323
|
+
ERROR_MESSAGE_BUFFER_CHARS, // BufferLength
|
|
3324
|
+
&textLength // TextLengthPtr
|
|
3325
|
+
);
|
|
3326
|
+
|
|
3327
|
+
if (!SQL_SUCCEEDED(diagnostic_return_code))
|
|
3328
|
+
{
|
|
3329
|
+
return return_code;
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
// HY092 is the SQLState produced by drivers that are known to not
|
|
3333
|
+
// implement SQL_ATTR_ROWS_FETCHED_PTR
|
|
3334
|
+
if (strcmp("HY092", (const char*)errorSQLState) != 0)
|
|
3335
|
+
{
|
|
3336
|
+
return return_code;
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
data->simple_binding = true;
|
|
3340
|
+
data->rows_fetched = 1;
|
|
3341
|
+
return_code = SQL_SUCCESS;
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
for (int i = 0; i < data->column_count; i++)
|
|
3346
|
+
{
|
|
3347
|
+
Column *column = new Column();
|
|
3348
|
+
column->ColumnName = new SQLTCHAR[data->maxColumnNameLength + 1]();
|
|
3349
|
+
|
|
3350
|
+
// TODO: Could just allocate one large SQLLEN buffer that was of size
|
|
3351
|
+
// column_count * fetch_size, then just do the pointer arithmetic for it..
|
|
3352
|
+
data->bound_columns[i].length_or_indicator_array =
|
|
3353
|
+
new SQLLEN[data->fetch_size]();
|
|
3354
|
+
|
|
3355
|
+
return_code =
|
|
3356
|
+
SQLDescribeCol
|
|
3357
|
+
(
|
|
3358
|
+
data->hstmt, // StatementHandle
|
|
3359
|
+
i + 1, // ColumnNumber
|
|
3360
|
+
column->ColumnName, // ColumnName
|
|
3361
|
+
data->maxColumnNameLength + 1, // BufferLength
|
|
3362
|
+
&column->NameLength, // NameLengthPtr
|
|
3363
|
+
&column->DataType, // DataTypePtr
|
|
3364
|
+
&column->ColumnSize, // ColumnSizePtr
|
|
3365
|
+
&column->DecimalDigits, // DecimalDigitsPtr
|
|
3366
|
+
&column->Nullable // NullablePtr
|
|
3367
|
+
);
|
|
3368
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
3369
|
+
return return_code;
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
// ensuring ColumnSize values are valid according to ODBC docs
|
|
3373
|
+
if (column->DataType == SQL_TYPE_DATE && column->ColumnSize < 10) {
|
|
3374
|
+
// ODBC docs say this should be 10, but some drivers have bugs that
|
|
3375
|
+
// return invalid values. eg. 4D
|
|
3376
|
+
// Ensure that it is a minimum of 10.
|
|
3377
|
+
column->ColumnSize = 10;
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
if (column->DataType == SQL_TYPE_TIME && column->ColumnSize < 8) {
|
|
3381
|
+
// ODBC docs say this should be 8, but some drivers have bugs that
|
|
3382
|
+
// return invalid values. eg. 4D
|
|
3383
|
+
// Ensure that it is a minimum of 8.
|
|
3384
|
+
column->ColumnSize = 8;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// Assume that the column will be bound with SQLBindCol unless:
|
|
3388
|
+
// * The data type returned is a LONG data type AND
|
|
3389
|
+
// * There is only a single row fetched at a time OR
|
|
3390
|
+
// * There are multiple rows returned and the driver can use a block
|
|
3391
|
+
// cursor with SQLGetData
|
|
3392
|
+
column->is_long_data = false;
|
|
3393
|
+
|
|
3394
|
+
// bind depending on the column
|
|
3395
|
+
switch(column->DataType) {
|
|
3396
|
+
|
|
3397
|
+
// LONG data types should be retrieved through SQLGetData and not
|
|
3398
|
+
// SQLBindCol/SQLFetch, as the buffers for SQLBindCol could be absurd
|
|
3399
|
+
// sizes for small amounts of data transferred. However, if the fetch
|
|
3400
|
+
// size is greater than 1 and the user's driver doesn't support SQLGetData
|
|
3401
|
+
// with block cursors, we need to bite the bullet and bind with
|
|
3402
|
+
// SQLBindCol.
|
|
3403
|
+
case SQL_WLONGVARCHAR:
|
|
3404
|
+
column->bind_type = SQL_C_WCHAR;
|
|
3405
|
+
if (data->fetch_size == 1 || data->get_data_supports.block)
|
|
3406
|
+
{
|
|
3407
|
+
column->is_long_data = true;
|
|
3408
|
+
} else {
|
|
3409
|
+
size_t character_count = column->ColumnSize + 1;
|
|
3410
|
+
column->buffer_size = character_count * sizeof(SQLWCHAR);
|
|
3411
|
+
data->bound_columns[i].buffer =
|
|
3412
|
+
new SQLWCHAR[character_count * data->fetch_size]();
|
|
3413
|
+
}
|
|
3414
|
+
break;
|
|
3415
|
+
case SQL_LONGVARCHAR:
|
|
3416
|
+
column->bind_type = SQL_C_CHAR;
|
|
3417
|
+
if (data->fetch_size == 1 || data->get_data_supports.block)
|
|
3418
|
+
{
|
|
3419
|
+
column->is_long_data = true;
|
|
3420
|
+
} else {
|
|
3421
|
+
size_t character_count = column->ColumnSize * MAX_UTF8_BYTES + 1;
|
|
3422
|
+
column->buffer_size = character_count * sizeof(SQLCHAR);
|
|
3423
|
+
data->bound_columns[i].buffer =
|
|
3424
|
+
new SQLCHAR[character_count * data->fetch_size]();
|
|
3425
|
+
}
|
|
3426
|
+
break;
|
|
3427
|
+
case SQL_LONGVARBINARY:
|
|
3428
|
+
column->bind_type = SQL_C_BINARY;
|
|
3429
|
+
if (data->fetch_size == 1 || data->get_data_supports.block)
|
|
3430
|
+
{
|
|
3431
|
+
column->is_long_data = true;
|
|
3432
|
+
} else {
|
|
3433
|
+
column->buffer_size = (column->ColumnSize) * sizeof(SQLCHAR);
|
|
3434
|
+
data->bound_columns[i].buffer = new SQLCHAR[column->buffer_size]();
|
|
3435
|
+
}
|
|
3436
|
+
break;
|
|
3437
|
+
|
|
3438
|
+
case SQL_REAL:
|
|
3439
|
+
case SQL_DECIMAL:
|
|
3440
|
+
case SQL_NUMERIC:
|
|
3441
|
+
{
|
|
3442
|
+
size_t character_count = column->ColumnSize + 2;
|
|
3443
|
+
column->buffer_size = character_count * sizeof(SQLCHAR);
|
|
3444
|
+
column->bind_type = SQL_C_CHAR;
|
|
3445
|
+
data->bound_columns[i].buffer =
|
|
3446
|
+
new SQLCHAR[character_count * data->fetch_size]();
|
|
3447
|
+
break;
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
case SQL_FLOAT:
|
|
3451
|
+
case SQL_DOUBLE:
|
|
3452
|
+
{
|
|
3453
|
+
column->buffer_size = sizeof(SQLDOUBLE);
|
|
3454
|
+
column->bind_type = SQL_C_DOUBLE;
|
|
3455
|
+
data->bound_columns[i].buffer =
|
|
3456
|
+
new SQLDOUBLE[data->fetch_size]();
|
|
3457
|
+
break;
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
case SQL_TINYINT:
|
|
3461
|
+
{
|
|
3462
|
+
column->buffer_size = sizeof(SQLCHAR);
|
|
3463
|
+
column->bind_type = SQL_C_UTINYINT;
|
|
3464
|
+
data->bound_columns[i].buffer =
|
|
3465
|
+
new SQLCHAR[data->fetch_size]();
|
|
3466
|
+
break;
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
case SQL_SMALLINT:
|
|
3470
|
+
{
|
|
3471
|
+
column->buffer_size = sizeof(SQLSMALLINT);
|
|
3472
|
+
column->bind_type = SQL_C_SHORT;
|
|
3473
|
+
data->bound_columns[i].buffer =
|
|
3474
|
+
new SQLSMALLINT[data->fetch_size]();
|
|
3475
|
+
break;
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
case SQL_INTEGER:
|
|
3479
|
+
{
|
|
3480
|
+
column->buffer_size = sizeof(SQLINTEGER);
|
|
3481
|
+
column->bind_type = SQL_C_SLONG;
|
|
3482
|
+
data->bound_columns[i].buffer =
|
|
3483
|
+
new SQLINTEGER[data->fetch_size]();
|
|
3484
|
+
break;
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
case SQL_BIGINT:
|
|
3488
|
+
{
|
|
3489
|
+
column->buffer_size = sizeof(SQLBIGINT);
|
|
3490
|
+
column->bind_type = SQL_C_SBIGINT;
|
|
3491
|
+
data->bound_columns[i].buffer =
|
|
3492
|
+
new SQLBIGINT[data->fetch_size]();
|
|
3493
|
+
break;
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
case SQL_BINARY:
|
|
3497
|
+
case SQL_VARBINARY:
|
|
3498
|
+
{
|
|
3499
|
+
column->bind_type = SQL_C_BINARY;
|
|
3500
|
+
// Fixes a known issue with SQL Server and (max) length fields
|
|
3501
|
+
if (column->ColumnSize == 0)
|
|
3502
|
+
{
|
|
3503
|
+
column->is_long_data = true;
|
|
3504
|
+
break;
|
|
3505
|
+
}
|
|
3506
|
+
column->buffer_size = column->ColumnSize;
|
|
3507
|
+
data->bound_columns[i].buffer =
|
|
3508
|
+
new SQLCHAR[column->buffer_size * data->fetch_size]();
|
|
3509
|
+
break;
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3512
|
+
case SQL_WCHAR:
|
|
3513
|
+
case SQL_WVARCHAR:
|
|
3514
|
+
{
|
|
3515
|
+
column->bind_type = SQL_C_WCHAR;
|
|
3516
|
+
// Fixes a known issue with SQL Server and (max) length fields
|
|
3517
|
+
if (column->ColumnSize == 0)
|
|
3518
|
+
{
|
|
3519
|
+
column->is_long_data = true;
|
|
3520
|
+
break;
|
|
3521
|
+
}
|
|
3522
|
+
size_t character_count = column->ColumnSize + 1;
|
|
3523
|
+
column->buffer_size = character_count * sizeof(SQLWCHAR);
|
|
3524
|
+
data->bound_columns[i].buffer =
|
|
3525
|
+
new SQLWCHAR[character_count * data->fetch_size]();
|
|
3526
|
+
break;
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
case SQL_CHAR:
|
|
3530
|
+
case SQL_VARCHAR:
|
|
3531
|
+
default:
|
|
3532
|
+
{
|
|
3533
|
+
column->bind_type = SQL_C_CHAR;
|
|
3534
|
+
// Fixes a known issue with SQL Server and (max) length fields
|
|
3535
|
+
if (column->ColumnSize == 0)
|
|
3536
|
+
{
|
|
3537
|
+
column->is_long_data = true;
|
|
3538
|
+
break;
|
|
3539
|
+
}
|
|
3540
|
+
size_t character_count = column->ColumnSize * MAX_UTF8_BYTES + 1;
|
|
3541
|
+
column->buffer_size = character_count * sizeof(SQLCHAR);
|
|
3542
|
+
data->bound_columns[i].buffer =
|
|
3543
|
+
new SQLCHAR[character_count * data->fetch_size]();
|
|
3544
|
+
break;
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
if (!column->is_long_data)
|
|
3549
|
+
{
|
|
3550
|
+
// SQLBindCol binds application data buffers to columns in the result set.
|
|
3551
|
+
return_code =
|
|
3552
|
+
SQLBindCol
|
|
3553
|
+
(
|
|
3554
|
+
data->hstmt, // StatementHandle
|
|
3555
|
+
i + 1, // ColumnNumber
|
|
3556
|
+
column->bind_type, // TargetType
|
|
3557
|
+
data->bound_columns[i].buffer, // TargetValuePtr
|
|
3558
|
+
column->buffer_size, // BufferLength
|
|
3559
|
+
data->bound_columns[i].length_or_indicator_array // StrLen_or_Ind
|
|
3560
|
+
);
|
|
3561
|
+
|
|
3562
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
3563
|
+
return return_code;
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
data->columns[i] = column;
|
|
3567
|
+
}
|
|
3568
|
+
return return_code;
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
SQLRETURN
|
|
3572
|
+
fetch_and_store
|
|
3573
|
+
(
|
|
3574
|
+
StatementData *data,
|
|
3575
|
+
bool set_position,
|
|
3576
|
+
bool *alloc_error
|
|
3577
|
+
)
|
|
3578
|
+
{
|
|
3579
|
+
SQLRETURN return_code;
|
|
3580
|
+
|
|
3581
|
+
// If column_count is 0, it likely means that the query was an UPDATE, INSERT,
|
|
3582
|
+
// or DELETE. If SQLFetch is called, it will likely complain about an invalid
|
|
3583
|
+
// cursor. Check here so that SQLFetch is only run if there is an actual
|
|
3584
|
+
// result set.
|
|
3585
|
+
if (data->column_count > 0) {
|
|
3586
|
+
return_code =
|
|
3587
|
+
SQLFetch
|
|
3588
|
+
(
|
|
3589
|
+
data->hstmt
|
|
3590
|
+
);
|
|
3591
|
+
|
|
3592
|
+
if (SQL_SUCCEEDED(return_code))
|
|
3593
|
+
{
|
|
3594
|
+
// iterate through all of the rows fetched (but not the fetch size)
|
|
3595
|
+
for (size_t row_index = 0; row_index < data->rows_fetched; row_index++)
|
|
3596
|
+
{
|
|
3597
|
+
if (set_position && data->get_data_supports.block && data->fetch_size > 1)
|
|
3598
|
+
{
|
|
3599
|
+
// In case the result set contains columns that contain LONG data
|
|
3600
|
+
// types, use SQLSetPos to set the row we are transferring bound data
|
|
3601
|
+
// from, and use SQLGetData in the same loop.
|
|
3602
|
+
return_code =
|
|
3603
|
+
SQLSetPos
|
|
3604
|
+
(
|
|
3605
|
+
data->hstmt,
|
|
3606
|
+
(SQLSETPOSIROW) row_index + 1,
|
|
3607
|
+
SQL_POSITION,
|
|
3608
|
+
SQL_LOCK_NO_CHANGE
|
|
3609
|
+
);
|
|
3610
|
+
|
|
3611
|
+
if (!SQL_SUCCEEDED(return_code))
|
|
3612
|
+
{
|
|
3613
|
+
return return_code;
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
// Copy the data over if the row status array indicates success
|
|
3618
|
+
if
|
|
3619
|
+
(
|
|
3620
|
+
data->simple_binding ||
|
|
3621
|
+
(
|
|
3622
|
+
data->row_status_array[row_index] == SQL_ROW_SUCCESS ||
|
|
3623
|
+
data->row_status_array[row_index] == SQL_ROW_SUCCESS_WITH_INFO
|
|
3624
|
+
)
|
|
3625
|
+
)
|
|
3626
|
+
{
|
|
3627
|
+
ColumnData *row = new ColumnData[data->column_count]();
|
|
3628
|
+
|
|
3629
|
+
// Iterate over each column, putting the data in the row object
|
|
3630
|
+
for (int column_index = 0; column_index < data->column_count; column_index++)
|
|
3631
|
+
{
|
|
3632
|
+
row[column_index].bind_type = data->columns[column_index]->bind_type;
|
|
3633
|
+
|
|
3634
|
+
// The column contained SQL_(W)LONG* data, so we didn't call
|
|
3635
|
+
// SQLBindCol, and therefore there is no data to move from a buffer.
|
|
3636
|
+
// Instead, call SQLGetData, and adjust buffer size accordingly
|
|
3637
|
+
if (data->columns[column_index]->is_long_data)
|
|
3638
|
+
{
|
|
3639
|
+
SQLPOINTER target_buffer;
|
|
3640
|
+
SQLLEN buffer_size =
|
|
3641
|
+
data->query_options.initial_long_data_buffer_size;
|
|
3642
|
+
SQLLEN string_length_or_indicator;
|
|
3643
|
+
SQLLEN data_returned_length = 0;
|
|
3644
|
+
|
|
3645
|
+
// We're allocating with malloc/realloc here, so the destructor
|
|
3646
|
+
// needs to use free instead of delete[].
|
|
3647
|
+
row[column_index].use_free = true;
|
|
3648
|
+
|
|
3649
|
+
if (data->columns[column_index]->bind_type == SQL_C_WCHAR)
|
|
3650
|
+
{
|
|
3651
|
+
row[column_index].wchar_data = (SQLWCHAR *)malloc(buffer_size);
|
|
3652
|
+
// if bad malloc, indicate and return
|
|
3653
|
+
if (row[column_index].wchar_data == NULL)
|
|
3654
|
+
{
|
|
3655
|
+
*alloc_error = true;
|
|
3656
|
+
return return_code;
|
|
3657
|
+
}
|
|
3658
|
+
target_buffer = row[column_index].wchar_data;
|
|
3659
|
+
}
|
|
3660
|
+
else
|
|
3661
|
+
{
|
|
3662
|
+
row[column_index].char_data = (SQLCHAR *)malloc(buffer_size);
|
|
3663
|
+
// if bad malloc, indicate and return
|
|
3664
|
+
if (row[column_index].char_data == NULL)
|
|
3665
|
+
{
|
|
3666
|
+
*alloc_error = true;
|
|
3667
|
+
return return_code;
|
|
3668
|
+
}
|
|
3669
|
+
target_buffer = row[column_index].char_data;
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
// Get the first chunk of data
|
|
3673
|
+
return_code =
|
|
3674
|
+
SQLGetData
|
|
3675
|
+
(
|
|
3676
|
+
data->hstmt,
|
|
3677
|
+
column_index + 1,
|
|
3678
|
+
data->columns[column_index]->bind_type,
|
|
3679
|
+
target_buffer,
|
|
3680
|
+
buffer_size,
|
|
3681
|
+
&string_length_or_indicator
|
|
3682
|
+
);
|
|
3683
|
+
if (!SQL_SUCCEEDED(return_code))
|
|
3684
|
+
{
|
|
3685
|
+
return return_code;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
// If the data is null, simply indicate and continue to the next
|
|
3689
|
+
// column
|
|
3690
|
+
if (string_length_or_indicator == SQL_NULL_DATA)
|
|
3691
|
+
{
|
|
3692
|
+
row[column_index].size = SQL_NULL_DATA;
|
|
3693
|
+
continue;
|
|
3694
|
+
}
|
|
3695
|
+
// If the data (+ whatever null terminator, not included in
|
|
3696
|
+
// string_length_or_indicator) was larger than the size of the
|
|
3697
|
+
// buffer, then need to call SQLGetData at least once more
|
|
3698
|
+
else if (
|
|
3699
|
+
string_length_or_indicator == SQL_NO_TOTAL ||
|
|
3700
|
+
string_length_or_indicator >= buffer_size
|
|
3701
|
+
)
|
|
3702
|
+
{
|
|
3703
|
+
// Continue looping as long as we don't know the final resize
|
|
3704
|
+
// that we need. Once a buffer size is returned instead of
|
|
3705
|
+
// SQL_NO_TOTAL, we might need to call SQLGetData one more time,
|
|
3706
|
+
// and the break out of this loop.
|
|
3707
|
+
while (true)
|
|
3708
|
+
{
|
|
3709
|
+
// Handling the data that was returned depends heavily on the
|
|
3710
|
+
// target type of the binding data.
|
|
3711
|
+
switch(data->columns[column_index]->bind_type)
|
|
3712
|
+
{
|
|
3713
|
+
case SQL_C_BINARY:
|
|
3714
|
+
{
|
|
3715
|
+
data_returned_length = (
|
|
3716
|
+
row[column_index].size + string_length_or_indicator <= buffer_size ?
|
|
3717
|
+
string_length_or_indicator :
|
|
3718
|
+
buffer_size - row[column_index].size
|
|
3719
|
+
);
|
|
3720
|
+
buffer_size =
|
|
3721
|
+
string_length_or_indicator == SQL_NO_TOTAL ?
|
|
3722
|
+
buffer_size * 2 :
|
|
3723
|
+
row[column_index].size + string_length_or_indicator;
|
|
3724
|
+
SQLCHAR *temp_realloc =
|
|
3725
|
+
(SQLCHAR *)
|
|
3726
|
+
realloc
|
|
3727
|
+
(
|
|
3728
|
+
row[column_index].char_data,
|
|
3729
|
+
buffer_size
|
|
3730
|
+
);
|
|
3731
|
+
if (temp_realloc == NULL)
|
|
3732
|
+
{
|
|
3733
|
+
free(row[column_index].char_data);
|
|
3734
|
+
*alloc_error = true;
|
|
3735
|
+
return return_code;
|
|
3736
|
+
}
|
|
3737
|
+
row[column_index].char_data = temp_realloc;
|
|
3738
|
+
row[column_index].size += data_returned_length;
|
|
3739
|
+
target_buffer =
|
|
3740
|
+
row[column_index].char_data + row[column_index].size;
|
|
3741
|
+
break;
|
|
3742
|
+
}
|
|
3743
|
+
case SQL_C_WCHAR:
|
|
3744
|
+
{
|
|
3745
|
+
data_returned_length =
|
|
3746
|
+
strlen16
|
|
3747
|
+
(
|
|
3748
|
+
(const char16_t *) target_buffer
|
|
3749
|
+
) * sizeof(SQLWCHAR);
|
|
3750
|
+
buffer_size =
|
|
3751
|
+
string_length_or_indicator == SQL_NO_TOTAL ?
|
|
3752
|
+
buffer_size * 2 :
|
|
3753
|
+
row[column_index].size + string_length_or_indicator + sizeof(SQLWCHAR);
|
|
3754
|
+
SQLWCHAR *temp_realloc =
|
|
3755
|
+
(SQLWCHAR *)
|
|
3756
|
+
realloc
|
|
3757
|
+
(
|
|
3758
|
+
row[column_index].wchar_data,
|
|
3759
|
+
buffer_size
|
|
3760
|
+
);
|
|
3761
|
+
if (temp_realloc == NULL)
|
|
3762
|
+
{
|
|
3763
|
+
free(row[column_index].wchar_data);
|
|
3764
|
+
*alloc_error = true;
|
|
3765
|
+
return return_code;
|
|
3766
|
+
}
|
|
3767
|
+
row[column_index].wchar_data = temp_realloc;
|
|
3768
|
+
row[column_index].size += data_returned_length;
|
|
3769
|
+
target_buffer =
|
|
3770
|
+
row[column_index].wchar_data + (row[column_index].size) / sizeof(SQLWCHAR);
|
|
3771
|
+
break;
|
|
3772
|
+
}
|
|
3773
|
+
case SQL_C_CHAR:
|
|
3774
|
+
default:
|
|
3775
|
+
{
|
|
3776
|
+
data_returned_length =
|
|
3777
|
+
strlen
|
|
3778
|
+
(
|
|
3779
|
+
(const char *) target_buffer
|
|
3780
|
+
);
|
|
3781
|
+
buffer_size =
|
|
3782
|
+
string_length_or_indicator == SQL_NO_TOTAL ?
|
|
3783
|
+
buffer_size * 2 :
|
|
3784
|
+
row[column_index].size + (string_length_or_indicator * MAX_UTF8_BYTES) + 1;
|
|
3785
|
+
SQLCHAR *temp_realloc =
|
|
3786
|
+
(SQLCHAR *)
|
|
3787
|
+
realloc
|
|
3788
|
+
(
|
|
3789
|
+
row[column_index].char_data,
|
|
3790
|
+
buffer_size
|
|
3791
|
+
);
|
|
3792
|
+
if (temp_realloc == NULL)
|
|
3793
|
+
{
|
|
3794
|
+
free(row[column_index].char_data);
|
|
3795
|
+
*alloc_error = true;
|
|
3796
|
+
return return_code;
|
|
3797
|
+
}
|
|
3798
|
+
row[column_index].char_data = temp_realloc;
|
|
3799
|
+
row[column_index].size += data_returned_length;
|
|
3800
|
+
target_buffer =
|
|
3801
|
+
row[column_index].char_data + row[column_index].size;
|
|
3802
|
+
break;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
SQLLEN buffer_free_space = buffer_size - row[column_index].size;
|
|
3807
|
+
|
|
3808
|
+
return_code =
|
|
3809
|
+
SQLGetData
|
|
3810
|
+
(
|
|
3811
|
+
data->hstmt,
|
|
3812
|
+
column_index + 1,
|
|
3813
|
+
data->columns[column_index]->bind_type,
|
|
3814
|
+
target_buffer,
|
|
3815
|
+
buffer_free_space,
|
|
3816
|
+
&string_length_or_indicator
|
|
3817
|
+
);
|
|
3818
|
+
if (!SQL_SUCCEEDED(return_code))
|
|
3819
|
+
{
|
|
3820
|
+
if (return_code == SQL_NO_DATA)
|
|
3821
|
+
{
|
|
3822
|
+
data->storedRows.push_back(row);
|
|
3823
|
+
}
|
|
3824
|
+
return return_code;
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
bool break_loop = false;
|
|
3828
|
+
if (string_length_or_indicator != SQL_NO_TOTAL)
|
|
3829
|
+
{
|
|
3830
|
+
switch (data->columns[column_index]->bind_type)
|
|
3831
|
+
{
|
|
3832
|
+
case SQL_C_BINARY:
|
|
3833
|
+
if (string_length_or_indicator <= buffer_free_space)
|
|
3834
|
+
{
|
|
3835
|
+
row[column_index].size += string_length_or_indicator;
|
|
3836
|
+
break_loop = true;
|
|
3837
|
+
}
|
|
3838
|
+
break;
|
|
3839
|
+
case SQL_C_WCHAR:
|
|
3840
|
+
// SQLGetData requires space for the null-terminator
|
|
3841
|
+
if (string_length_or_indicator <= buffer_free_space - (SQLLEN)sizeof(SQLWCHAR))
|
|
3842
|
+
{
|
|
3843
|
+
row[column_index].size += string_length_or_indicator;
|
|
3844
|
+
break_loop = true;
|
|
3845
|
+
}
|
|
3846
|
+
break;
|
|
3847
|
+
case SQL_C_CHAR:
|
|
3848
|
+
default:
|
|
3849
|
+
// SQLGetData requires space for the null-terminator
|
|
3850
|
+
if (string_length_or_indicator <= buffer_free_space - (SQLLEN)sizeof(SQLCHAR))
|
|
3851
|
+
{
|
|
3852
|
+
row[column_index].size += string_length_or_indicator;
|
|
3853
|
+
break_loop = true;
|
|
3854
|
+
}
|
|
3855
|
+
break;
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
if (break_loop)
|
|
3860
|
+
{
|
|
3861
|
+
break;
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
// The happy path, where there is no need to resize the buffer
|
|
3866
|
+
// and call SQLGetData again. Just note the size of data returned
|
|
3867
|
+
// and then continue
|
|
3868
|
+
else
|
|
3869
|
+
{
|
|
3870
|
+
row[column_index].size = string_length_or_indicator;
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
// Columns that were bound with SQLBinCol, because they did not
|
|
3874
|
+
// hold SQL_(W)LONG* data, or because the user's driver doesn't
|
|
3875
|
+
// support block cursors + SQLGetData.
|
|
3876
|
+
else
|
|
3877
|
+
{
|
|
3878
|
+
if (data->bound_columns[column_index].length_or_indicator_array[row_index] == SQL_NULL_DATA) {
|
|
3879
|
+
row[column_index].size = SQL_NULL_DATA;
|
|
3880
|
+
}
|
|
3881
|
+
else
|
|
3882
|
+
{
|
|
3883
|
+
switch(data->columns[column_index]->bind_type)
|
|
3884
|
+
{
|
|
3885
|
+
case SQL_C_DOUBLE:
|
|
3886
|
+
row[column_index].double_data =
|
|
3887
|
+
((SQLDOUBLE *)(data->bound_columns[column_index].buffer))[row_index];
|
|
3888
|
+
break;
|
|
3889
|
+
|
|
3890
|
+
case SQL_C_UTINYINT:
|
|
3891
|
+
row[column_index].tinyint_data =
|
|
3892
|
+
((SQLCHAR *)(data->bound_columns[column_index].buffer))[row_index];
|
|
3893
|
+
break;
|
|
3894
|
+
|
|
3895
|
+
case SQL_C_SSHORT:
|
|
3896
|
+
case SQL_C_SHORT:
|
|
3897
|
+
row[column_index].smallint_data =
|
|
3898
|
+
((SQLSMALLINT *)(data->bound_columns[column_index].buffer))[row_index];
|
|
3899
|
+
break;
|
|
3900
|
+
|
|
3901
|
+
case SQL_C_USHORT:
|
|
3902
|
+
row[column_index].usmallint_data =
|
|
3903
|
+
((SQLUSMALLINT *)(data->bound_columns[column_index].buffer))[row_index];
|
|
3904
|
+
break;
|
|
3905
|
+
|
|
3906
|
+
case SQL_C_SLONG:
|
|
3907
|
+
row[column_index].integer_data =
|
|
3908
|
+
((SQLINTEGER *)(data->bound_columns[column_index].buffer))[row_index];
|
|
3909
|
+
break;
|
|
3910
|
+
|
|
3911
|
+
case SQL_C_SBIGINT:
|
|
3912
|
+
row[column_index].bigint_data =
|
|
3913
|
+
((SQLBIGINT *)(data->bound_columns[column_index].buffer))[row_index];
|
|
3914
|
+
break;
|
|
3915
|
+
|
|
3916
|
+
case SQL_C_BINARY:
|
|
3917
|
+
{
|
|
3918
|
+
row[column_index].size = data->bound_columns[column_index].length_or_indicator_array[row_index];
|
|
3919
|
+
row[column_index].char_data = new SQLCHAR[row[column_index].size]();
|
|
3920
|
+
memcpy(
|
|
3921
|
+
row[column_index].char_data,
|
|
3922
|
+
(SQLCHAR *)data->bound_columns[column_index].buffer + row_index * data->columns[column_index]->buffer_size,
|
|
3923
|
+
row[column_index].size
|
|
3924
|
+
);
|
|
3925
|
+
break;
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
case SQL_C_WCHAR:
|
|
3929
|
+
{
|
|
3930
|
+
SQLWCHAR *memory_start = (SQLWCHAR *)data->bound_columns[column_index].buffer + (row_index * (data->columns[column_index]->ColumnSize + 1));
|
|
3931
|
+
row[column_index].size = strlen16((const char16_t *)memory_start) * sizeof(SQLWCHAR);
|
|
3932
|
+
row[column_index].wchar_data = new SQLWCHAR[(row[column_index].size / sizeof(SQLWCHAR)) + 1]();
|
|
3933
|
+
memcpy
|
|
3934
|
+
(
|
|
3935
|
+
row[column_index].wchar_data,
|
|
3936
|
+
memory_start,
|
|
3937
|
+
row[column_index].size
|
|
3938
|
+
);
|
|
3939
|
+
break;
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
case SQL_C_CHAR:
|
|
3943
|
+
default:
|
|
3944
|
+
{
|
|
3945
|
+
SQLCHAR *memory_start = (SQLCHAR *)data->bound_columns[column_index].buffer + (row_index * data->columns[column_index]->buffer_size);
|
|
3946
|
+
row[column_index].size = strlen((const char *)memory_start);
|
|
3947
|
+
// Although fields going from SQL_C_CHAR to Napi::String use
|
|
3948
|
+
// row[column_index].size, NUMERIC data uses atof() which requires
|
|
3949
|
+
// a null terminator. Need to add an aditional byte.
|
|
3950
|
+
row[column_index].char_data = new SQLCHAR[row[column_index].size + 1]();
|
|
3951
|
+
memcpy
|
|
3952
|
+
(
|
|
3953
|
+
row[column_index].char_data,
|
|
3954
|
+
memory_start,
|
|
3955
|
+
row[column_index].size
|
|
3956
|
+
);
|
|
3957
|
+
break;
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
// TODO: Unhandled C types:
|
|
3961
|
+
// SQL_C_SSHORT
|
|
3962
|
+
// SQL_C_SHORT
|
|
3963
|
+
// SQL_C_STINYINT
|
|
3964
|
+
// SQL_C_TINYINT
|
|
3965
|
+
// SQL_C_ULONG
|
|
3966
|
+
// SQL_C_LONG
|
|
3967
|
+
// SQL_C_FLOAT
|
|
3968
|
+
// SQL_C_BIT
|
|
3969
|
+
// SQL_C_STINYINT
|
|
3970
|
+
// SQL_C_TINYINT
|
|
3971
|
+
// SQL_C_SBIGINT
|
|
3972
|
+
// SQL_C_BOOKMARK
|
|
3973
|
+
// SQL_C_VARBOOKMARK
|
|
3974
|
+
// All C interval data types
|
|
3975
|
+
// SQL_C_TYPE_DATE
|
|
3976
|
+
// SQL_C_TYPE_TIME
|
|
3977
|
+
// SQL_C_TYPE_TIMESTAMP
|
|
3978
|
+
// SQL_C_TYPE_NUMERIC
|
|
3979
|
+
// SQL_C_GUID
|
|
3980
|
+
}
|
|
3981
|
+
row[column_index].bind_type = data->columns[column_index]->bind_type;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
data->storedRows.push_back(row);
|
|
3986
|
+
} else {
|
|
3987
|
+
// TODO:
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
} else {
|
|
3992
|
+
return_code = SQL_NO_DATA;
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
// If SQL_SUCCEEDED failed and return code isn't SQL_NO_DATA, there is an error
|
|
3996
|
+
if(!SQL_SUCCEEDED(return_code) && return_code != SQL_NO_DATA) {
|
|
3997
|
+
return return_code;
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
// if the last row status is SQL_ROW_NOROW, or the last call to SQLFetch
|
|
4001
|
+
// returned SQL_NO_DATA, we have reached the end of the result set. Set
|
|
4002
|
+
// result_set_end_reached to true so that SQLFetch doesn't get called again.
|
|
4003
|
+
// SQLFetch again (sort of a short-circuit)
|
|
4004
|
+
if (data->simple_binding == true || return_code == SQL_NO_DATA || data->row_status_array[data->fetch_size - 1] == SQL_ROW_NOROW)
|
|
4005
|
+
{
|
|
4006
|
+
data->result_set_end_reached = true;
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
return return_code;
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
SQLRETURN
|
|
4013
|
+
fetch_all_and_store
|
|
4014
|
+
(
|
|
4015
|
+
StatementData *data,
|
|
4016
|
+
bool set_position,
|
|
4017
|
+
bool *alloc_error
|
|
4018
|
+
)
|
|
4019
|
+
{
|
|
4020
|
+
SQLRETURN return_code;
|
|
4021
|
+
|
|
4022
|
+
do {
|
|
4023
|
+
return_code = fetch_and_store(data, set_position, alloc_error);
|
|
4024
|
+
} while (SQL_SUCCEEDED(return_code));
|
|
4025
|
+
|
|
4026
|
+
// If there was an alloc error when fetching and storing, return and the
|
|
4027
|
+
// the caller will need to handle everything
|
|
4028
|
+
if (*alloc_error == true)
|
|
4029
|
+
{
|
|
4030
|
+
return return_code;
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
// If SQL_SUCCEEDED failed and return code isn't SQL_NO_DATA, there is an error
|
|
4034
|
+
if(return_code != SQL_NO_DATA) {
|
|
4035
|
+
return return_code;
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
// will return either SQL_ERROR or SQL_NO_DATA
|
|
4039
|
+
if (data->column_count > 0) {
|
|
4040
|
+
return_code = SQLCloseCursor(data->hstmt);
|
|
4041
|
+
if (!SQL_SUCCEEDED(return_code)) {
|
|
4042
|
+
return return_code;
|
|
4043
|
+
}
|
|
4044
|
+
} else {
|
|
4045
|
+
return_code = SQL_SUCCESS;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
return return_code;
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
// This macro and function are used to translate the various ODBC data type
|
|
4052
|
+
// macros, returning a string that matches the name of the macro in the ODBC
|
|
4053
|
+
// header files. This is then used when returning column data to the user. If
|
|
4054
|
+
// the user desires the native (non-ODBC) data type, they should call the
|
|
4055
|
+
// columns() function available on the Connection object.
|
|
4056
|
+
#define CASE_RETURN_DATA_TYPE_NAME(n) case n: return #n
|
|
4057
|
+
|
|
4058
|
+
const char*
|
|
4059
|
+
get_odbc_type_name
|
|
4060
|
+
(
|
|
4061
|
+
SQLSMALLINT dataType
|
|
4062
|
+
)
|
|
4063
|
+
{
|
|
4064
|
+
switch(dataType) {
|
|
4065
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_CHAR);
|
|
4066
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_VARCHAR);
|
|
4067
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_LONGVARCHAR);
|
|
4068
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_WCHAR);
|
|
4069
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_WVARCHAR);
|
|
4070
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_WLONGVARCHAR);
|
|
4071
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_DECIMAL);
|
|
4072
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_NUMERIC);
|
|
4073
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_SMALLINT);
|
|
4074
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTEGER);
|
|
4075
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_REAL);
|
|
4076
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_FLOAT);
|
|
4077
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_DOUBLE);
|
|
4078
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_BIT);
|
|
4079
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_TINYINT);
|
|
4080
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_BIGINT);
|
|
4081
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_BINARY);
|
|
4082
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_VARBINARY);
|
|
4083
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_LONGVARBINARY);
|
|
4084
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_TYPE_DATE);
|
|
4085
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_TYPE_TIME);
|
|
4086
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_TYPE_TIMESTAMP);
|
|
4087
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_MONTH);
|
|
4088
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_YEAR);
|
|
4089
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_YEAR_TO_MONTH);
|
|
4090
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_DAY);
|
|
4091
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_HOUR);
|
|
4092
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_MINUTE);
|
|
4093
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_SECOND);
|
|
4094
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_DAY_TO_HOUR);
|
|
4095
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_DAY_TO_MINUTE);
|
|
4096
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_DAY_TO_SECOND);
|
|
4097
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_HOUR_TO_MINUTE);
|
|
4098
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_HOUR_TO_SECOND);
|
|
4099
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_INTERVAL_MINUTE_TO_SECOND);
|
|
4100
|
+
CASE_RETURN_DATA_TYPE_NAME(SQL_GUID);
|
|
4101
|
+
|
|
4102
|
+
default: return "UNKNOWN";
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
// All of data has been loaded into data->storedRows. Have to take the data
|
|
4107
|
+
// stored in there and convert it it into JavaScript to be given to the
|
|
4108
|
+
// Node.js runtime.
|
|
4109
|
+
Napi::Array process_data_for_napi(Napi::Env env, StatementData *data, Napi::Array napiParameters) {
|
|
4110
|
+
|
|
4111
|
+
Column **columns = data->columns;
|
|
4112
|
+
SQLSMALLINT columnCount = data->column_count;
|
|
4113
|
+
|
|
4114
|
+
// The rows array is the data structure that is returned from query results.
|
|
4115
|
+
// This array holds the records that were returned from the query as objects,
|
|
4116
|
+
// with the column names as the property keys on the object and the table
|
|
4117
|
+
// values as the property values.
|
|
4118
|
+
// Additionally, there are four properties that are added directly onto the
|
|
4119
|
+
// array:
|
|
4120
|
+
// 'count' : The returned from SQLRowCount, which returns "the
|
|
4121
|
+
// number of rows affected by an UPDATE, INSERT, or DELETE
|
|
4122
|
+
// statement." For SELECT statements and other statements
|
|
4123
|
+
// where data is not available, returns -1.
|
|
4124
|
+
// 'columns' : An array containing the columns of the result set as
|
|
4125
|
+
// objects, with two properties:
|
|
4126
|
+
// 'name' : The name of the column
|
|
4127
|
+
// 'dataType': The integer representation of the SQL dataType
|
|
4128
|
+
// for that column.
|
|
4129
|
+
// 'parameters' : An array containing all the parameter values for the
|
|
4130
|
+
// query. If calling a statement, then parameter values are
|
|
4131
|
+
// unchanged from the call. If calling a procedure, in/out
|
|
4132
|
+
// or out parameters may have their values changed.
|
|
4133
|
+
// 'return' : For some procedures, a return code is returned and stored
|
|
4134
|
+
// in this property.
|
|
4135
|
+
// 'statement' : The SQL statement that was sent to the server. Parameter
|
|
4136
|
+
// markers are not altered, but parameters passed can be
|
|
4137
|
+
// determined from the parameters array on this object
|
|
4138
|
+
Napi::Array rows = Napi::Array::New(env);
|
|
4139
|
+
|
|
4140
|
+
// set the 'statement' property
|
|
4141
|
+
if (data->sql == NULL) {
|
|
4142
|
+
rows.Set(Napi::String::New(env, STATEMENT), env.Null());
|
|
4143
|
+
} else {
|
|
4144
|
+
#ifdef UNICODE
|
|
4145
|
+
rows.Set(Napi::String::New(env, STATEMENT), Napi::String::New(env, (const char16_t*)data->sql));
|
|
4146
|
+
#else
|
|
4147
|
+
rows.Set(Napi::String::New(env, STATEMENT), Napi::String::New(env, (const char*)data->sql));
|
|
4148
|
+
#endif
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
// set the 'parameters' property
|
|
4152
|
+
if (napiParameters.IsEmpty()) {
|
|
4153
|
+
rows.Set(Napi::String::New(env, PARAMETERS), env.Undefined());
|
|
4154
|
+
} else {
|
|
4155
|
+
rows.Set(Napi::String::New(env, PARAMETERS), napiParameters);
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
// set the 'return' property
|
|
4159
|
+
rows.Set(Napi::String::New(env, RETURN), env.Undefined()); // TODO: This doesn't exist on my DBMS of choice, need to test on MSSQL Server or similar
|
|
4160
|
+
|
|
4161
|
+
// set the 'count' property
|
|
4162
|
+
rows.Set(Napi::String::New(env, COUNT), Napi::Number::New(env, (double)data->rowCount));
|
|
4163
|
+
|
|
4164
|
+
// construct the array for the 'columns' property and then set
|
|
4165
|
+
Napi::Array napiColumns = Napi::Array::New(env);
|
|
4166
|
+
|
|
4167
|
+
for (SQLSMALLINT h = 0; h < columnCount; h++) {
|
|
4168
|
+
Napi::Object column = Napi::Object::New(env);
|
|
4169
|
+
#ifdef UNICODE
|
|
4170
|
+
column.Set(Napi::String::New(env, NAME), Napi::String::New(env, (const char16_t*)columns[h]->ColumnName));
|
|
4171
|
+
#else
|
|
4172
|
+
column.Set(Napi::String::New(env, NAME), Napi::String::New(env, (const char*)columns[h]->ColumnName));
|
|
4173
|
+
#endif
|
|
4174
|
+
column.Set(Napi::String::New(env, DATA_TYPE), Napi::Number::New(env, columns[h]->DataType));
|
|
4175
|
+
column.Set(Napi::String::New(env, DATA_TYPE_NAME), Napi::String::New(env, get_odbc_type_name(columns[h]->DataType)));
|
|
4176
|
+
column.Set(Napi::String::New(env, COLUMN_SIZE), Napi::Number::New(env, columns[h]->ColumnSize));
|
|
4177
|
+
column.Set(Napi::String::New(env, DECIMAL_DIGITS), Napi::Number::New(env, columns[h]->DecimalDigits));
|
|
4178
|
+
column.Set(Napi::String::New(env, NULLABLE), Napi::Boolean::New(env, columns[h]->Nullable));
|
|
4179
|
+
napiColumns.Set(h, column);
|
|
4180
|
+
}
|
|
4181
|
+
rows.Set(Napi::String::New(env, COLUMNS), napiColumns);
|
|
4182
|
+
|
|
4183
|
+
// iterate over all of the stored rows,
|
|
4184
|
+
for (size_t i = 0; i < data->storedRows.size(); i++) {
|
|
4185
|
+
Napi::Object row;
|
|
4186
|
+
|
|
4187
|
+
if (data->fetch_array == true) {
|
|
4188
|
+
row = Napi::Array::New(env);
|
|
4189
|
+
} else {
|
|
4190
|
+
row = Napi::Object::New(env);
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
ColumnData *storedRow = data->storedRows[i];
|
|
4194
|
+
|
|
4195
|
+
// Iterate over each column, putting the data in the row object
|
|
4196
|
+
for (SQLSMALLINT j = 0; j < columnCount; j++) {
|
|
4197
|
+
|
|
4198
|
+
Napi::Value value;
|
|
4199
|
+
|
|
4200
|
+
// check for null data
|
|
4201
|
+
if (storedRow[j].size == SQL_NULL_DATA) {
|
|
4202
|
+
value = env.Null();
|
|
4203
|
+
} else {
|
|
4204
|
+
switch(columns[j]->DataType) {
|
|
4205
|
+
case SQL_REAL:
|
|
4206
|
+
case SQL_DECIMAL:
|
|
4207
|
+
case SQL_NUMERIC:
|
|
4208
|
+
switch(columns[j]->bind_type) {
|
|
4209
|
+
case SQL_C_DOUBLE:
|
|
4210
|
+
value = Napi::Number::New(env, storedRow[j].double_data);
|
|
4211
|
+
break;
|
|
4212
|
+
default:
|
|
4213
|
+
value = Napi::Number::New(env, atof((const char*)storedRow[j].char_data));
|
|
4214
|
+
break;
|
|
4215
|
+
}
|
|
4216
|
+
break;
|
|
4217
|
+
// Napi::Number
|
|
4218
|
+
case SQL_FLOAT:
|
|
4219
|
+
case SQL_DOUBLE:
|
|
4220
|
+
switch(columns[j]->bind_type) {
|
|
4221
|
+
case SQL_C_DOUBLE:
|
|
4222
|
+
value = Napi::Number::New(env, storedRow[j].double_data);
|
|
4223
|
+
break;
|
|
4224
|
+
default:
|
|
4225
|
+
value = Napi::Number::New(env, atof((const char*)storedRow[j].char_data));
|
|
4226
|
+
break;
|
|
4227
|
+
}
|
|
4228
|
+
break;
|
|
4229
|
+
case SQL_TINYINT:
|
|
4230
|
+
case SQL_SMALLINT:
|
|
4231
|
+
case SQL_INTEGER:
|
|
4232
|
+
switch(columns[j]->bind_type) {
|
|
4233
|
+
case SQL_C_TINYINT:
|
|
4234
|
+
case SQL_C_UTINYINT:
|
|
4235
|
+
case SQL_C_STINYINT:
|
|
4236
|
+
value = Napi::Number::New(env, storedRow[j].tinyint_data);
|
|
4237
|
+
break;
|
|
4238
|
+
case SQL_C_SHORT:
|
|
4239
|
+
case SQL_C_USHORT:
|
|
4240
|
+
case SQL_C_SSHORT:
|
|
4241
|
+
value = Napi::Number::New(env, storedRow[j].smallint_data);
|
|
4242
|
+
break;
|
|
4243
|
+
case SQL_C_LONG:
|
|
4244
|
+
case SQL_C_ULONG:
|
|
4245
|
+
case SQL_C_SLONG:
|
|
4246
|
+
value = Napi::Number::New(env, storedRow[j].integer_data);
|
|
4247
|
+
break;
|
|
4248
|
+
default:
|
|
4249
|
+
value = Napi::Number::New(env, *(int*)storedRow[j].char_data);
|
|
4250
|
+
break;
|
|
4251
|
+
}
|
|
4252
|
+
break;
|
|
4253
|
+
// Napi::BigInt
|
|
4254
|
+
case SQL_BIGINT:
|
|
4255
|
+
switch(columns[j]->bind_type) {
|
|
4256
|
+
case SQL_C_SBIGINT:
|
|
4257
|
+
value = Napi::BigInt::New(env, (int64_t)storedRow[j].bigint_data);
|
|
4258
|
+
break;
|
|
4259
|
+
default:
|
|
4260
|
+
value = Napi::String::New(env, (char*)storedRow[j].char_data);
|
|
4261
|
+
break;
|
|
4262
|
+
}
|
|
4263
|
+
break;
|
|
4264
|
+
// Napi::ArrayBuffer
|
|
4265
|
+
case SQL_BINARY :
|
|
4266
|
+
case SQL_VARBINARY :
|
|
4267
|
+
case SQL_LONGVARBINARY : {
|
|
4268
|
+
SQLCHAR *binaryData = new SQLCHAR[storedRow[j].size]; // have to save the data on the heap
|
|
4269
|
+
memcpy((SQLCHAR *) binaryData, storedRow[j].char_data, storedRow[j].size);
|
|
4270
|
+
value = Napi::ArrayBuffer::New(env, binaryData, storedRow[j].size, [](Napi::Env env, void* finalizeData) {
|
|
4271
|
+
delete[] (SQLCHAR*)finalizeData;
|
|
4272
|
+
});
|
|
4273
|
+
break;
|
|
4274
|
+
}
|
|
4275
|
+
// Napi::String (char16_t)
|
|
4276
|
+
case SQL_WCHAR :
|
|
4277
|
+
case SQL_WVARCHAR :
|
|
4278
|
+
case SQL_WLONGVARCHAR :
|
|
4279
|
+
value = Napi::String::New(env, (const char16_t*)storedRow[j].wchar_data, storedRow[j].size / sizeof(SQLWCHAR));
|
|
4280
|
+
break;
|
|
4281
|
+
// Napi::String (char)
|
|
4282
|
+
case SQL_CHAR :
|
|
4283
|
+
case SQL_VARCHAR :
|
|
4284
|
+
case SQL_LONGVARCHAR :
|
|
4285
|
+
default:
|
|
4286
|
+
value = Napi::String::New(env, (const char*)storedRow[j].char_data, storedRow[j].size);
|
|
4287
|
+
break;
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
// TODO: here
|
|
4291
|
+
if (data->fetch_array == true) {
|
|
4292
|
+
row.Set(j, value);
|
|
4293
|
+
} else {
|
|
4294
|
+
#ifdef UNICODE
|
|
4295
|
+
row.Set(Napi::String::New(env, (const char16_t*)columns[j]->ColumnName), value);
|
|
4296
|
+
#else
|
|
4297
|
+
row.Set(Napi::String::New(env, (const char*)columns[j]->ColumnName), value);
|
|
4298
|
+
#endif
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
rows.Set(i, row);
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
// Have to clear out the data in the storedRow, so that they aren't
|
|
4305
|
+
// lingering the next time fetch is called.
|
|
4306
|
+
for (size_t h = 0; h < data->storedRows.size(); h++) {
|
|
4307
|
+
delete[] data->storedRows[h];
|
|
4308
|
+
};
|
|
4309
|
+
data->storedRows.clear();
|
|
4310
|
+
|
|
4311
|
+
return rows;
|
|
4312
|
+
}
|