f75f753b1013c8a0d35faedc4f1a8b7a1bc4e0a8
[public/netxms.git] / src / db / dbdrv / db2 / db2.cpp
1 /*
2 ** DB2 Database Driver
3 ** Copyright (C) 2010-2016 Raden Solutinos
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 **/
20
21 #define _CRT_SECURE_NO_WARNINGS
22
23 #include "db2drv.h"
24
25 DECLARE_DRIVER_HEADER("DB2")
26
27 /**
28 * Convert DB2 state to NetXMS database error code and get error text
29 */
30 static DWORD GetSQLErrorInfo(SQLSMALLINT nHandleType, SQLHANDLE hHandle, NETXMS_WCHAR *errorText)
31 {
32 SQLRETURN nRet;
33 SQLSMALLINT nChars;
34 DWORD dwError;
35 SQLWCHAR buffer[DBDRV_MAX_ERROR_TEXT];
36
37 // Get state information and convert it to NetXMS database error code
38 nRet = SQLGetDiagFieldW(nHandleType, hHandle, 1, SQL_DIAG_SQLSTATE, buffer, 16, &nChars);
39 if (nRet == SQL_SUCCESS)
40 {
41 #if UNICODE_UCS2
42 if (
43 (!wcscmp((WCHAR *)buffer, L"08003")) || // Connection does not exist
44 (!wcscmp((WCHAR *)buffer, L"08S01")) || // Communication link failure
45 (!wcscmp((WCHAR *)buffer, L"HYT00")) || // Timeout expired
46 (!wcscmp((WCHAR *)buffer, L"HYT01")) || // Connection timeout expired
47 (!wcscmp((WCHAR *)buffer, L"08506"))) // SQL30108N: A connection failed but has been re-established.
48 #else
49 char state[16];
50 ucs2_to_mb(buffer, -1, state, 16);
51 if (
52 (!strcmp(state, "08003")) || // Connection does not exist
53 (!strcmp(state, "08S01")) || // Communication link failure
54 (!strcmp(state, "HYT00")) || // Timeout expired
55 (!strcmp(state, "HYT01")) || // Connection timeout expired
56 (!strcmp(state, "08506"))) // SQL30108N: A connection failed but has been re-established.
57 #endif
58 {
59 dwError = DBERR_CONNECTION_LOST;
60 }
61 else
62 {
63 dwError = DBERR_OTHER_ERROR;
64 }
65 }
66 else
67 {
68 dwError = DBERR_OTHER_ERROR;
69 }
70
71 // Get error message
72 if (errorText != NULL)
73 {
74 #if UNICODE_UCS2
75 nRet = SQLGetDiagFieldW(nHandleType, hHandle, 1, SQL_DIAG_MESSAGE_TEXT, errorText, DBDRV_MAX_ERROR_TEXT, &nChars);
76 #else
77 nRet = SQLGetDiagFieldW(nHandleType, hHandle, 1, SQL_DIAG_MESSAGE_TEXT, buffer, DBDRV_MAX_ERROR_TEXT, &nChars);
78 #endif
79 if (nRet == SQL_SUCCESS)
80 {
81 #if UNICODE_UCS4
82 buffer[DBDRV_MAX_ERROR_TEXT - 1] = 0;
83 ucs2_to_ucs4(buffer, -1, errorText, DBDRV_MAX_ERROR_TEXT);
84 #endif
85 RemoveTrailingCRLFW(errorText);
86 }
87 else
88 {
89 wcscpy(errorText, L"Unable to obtain description for this error");
90 }
91 }
92
93 return dwError;
94 }
95
96 /**
97 * Clear any pending result sets on given statement
98 */
99 static void ClearPendingResults(SQLHSTMT statement)
100 {
101 while(1)
102 {
103 SQLRETURN rc = SQLMoreResults(statement);
104 if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO))
105 {
106 break;
107 }
108 }
109 }
110
111 /**
112 * Prepare string for using in SQL query - enclose in quotes and escape as needed
113 */
114 extern "C" WCHAR EXPORT *DrvPrepareStringW(const WCHAR *str)
115 {
116 int len = (int)wcslen(str) + 3; // + two quotes and \0 at the end
117 int bufferSize = len + 128;
118 WCHAR *out = (WCHAR *)malloc(bufferSize * sizeof(WCHAR));
119 out[0] = L'\'';
120
121 const WCHAR *src = str;
122 int outPos;
123 for(outPos = 1; *src != 0; src++)
124 {
125 if (*src == L'\'')
126 {
127 len++;
128 if (len >= bufferSize)
129 {
130 bufferSize += 128;
131 out = (WCHAR *)realloc(out, bufferSize * sizeof(WCHAR));
132 }
133 out[outPos++] = L'\'';
134 out[outPos++] = L'\'';
135 }
136 else
137 {
138 out[outPos++] = *src;
139 }
140 }
141 out[outPos++] = L'\'';
142 out[outPos++] = 0;
143
144 return out;
145 }
146
147 extern "C" char EXPORT *DrvPrepareStringA(const char *str)
148 {
149 int len = (int)strlen(str) + 3; // + two quotes and \0 at the end
150 int bufferSize = len + 128;
151 char *out = (char *)malloc(bufferSize);
152 out[0] = '\'';
153
154 const char *src = str;
155 int outPos;
156 for(outPos = 1; *src != 0; src++)
157 {
158 if (*src == '\'')
159 {
160 len++;
161 if (len >= bufferSize)
162 {
163 bufferSize += 128;
164 out = (char *)realloc(out, bufferSize);
165 }
166 out[outPos++] = '\'';
167 out[outPos++] = '\'';
168 }
169 else
170 {
171 out[outPos++] = *src;
172 }
173 }
174 out[outPos++] = '\'';
175 out[outPos++] = 0;
176
177 return out;
178 }
179
180 /**
181 * Initialize driver
182 */
183 extern "C" bool EXPORT DrvInit(const char *cmdLine)
184 {
185 return true;
186 }
187
188 /**
189 * Unload handler
190 */
191 extern "C" void EXPORT DrvUnload()
192 {
193 }
194
195 /**
196 * Connect to database
197 * pszHost should be set to DB2 source name, and pszDatabase is ignored
198 */
199 extern "C" DBDRV_CONNECTION EXPORT DrvConnect(char *pszHost, char *pszLogin,
200 char *pszPassword, char *pszDatabase, const char *schema, NETXMS_WCHAR *errorText)
201 {
202 long iResult;
203 DB2DRV_CONN *pConn;
204 SQLHSTMT sqlStatement = NULL;
205
206 // Allocate our connection structure
207 pConn = (DB2DRV_CONN *)malloc(sizeof(DB2DRV_CONN));
208
209 // Allocate environment
210 iResult = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &pConn->sqlEnv);
211 if ((iResult != SQL_SUCCESS) && (iResult != SQL_SUCCESS_WITH_INFO))
212 {
213 wcscpy(errorText, L"Cannot allocate environment handle");
214 goto connect_failure_0;
215 }
216
217 // Set required DB2 version
218 iResult = SQLSetEnvAttr(pConn->sqlEnv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
219 if ((iResult != SQL_SUCCESS) && (iResult != SQL_SUCCESS_WITH_INFO))
220 {
221 wcscpy(errorText, L"Call to SQLSetEnvAttr failed");
222 goto connect_failure_1;
223 }
224
225 // Allocate connection handle, set timeout
226 iResult = SQLAllocHandle(SQL_HANDLE_DBC, pConn->sqlEnv, &pConn->sqlConn);
227 if ((iResult != SQL_SUCCESS) && (iResult != SQL_SUCCESS_WITH_INFO))
228 {
229 wcscpy(errorText, L"Cannot allocate connection handle");
230 goto connect_failure_1;
231 }
232 SQLSetConnectAttr(pConn->sqlConn, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER *)15, 0);
233 SQLSetConnectAttr(pConn->sqlConn, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER *)30, 0);
234
235 // Connect to the datasource
236 iResult = SQLConnect(pConn->sqlConn, (SQLCHAR *)pszHost, SQL_NTS, (SQLCHAR *)pszLogin, SQL_NTS, (SQLCHAR *)pszPassword, SQL_NTS);
237 if ((iResult != SQL_SUCCESS) && (iResult != SQL_SUCCESS_WITH_INFO))
238 {
239 GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
240 goto connect_failure_2;
241 }
242
243 // Set current schema
244 if ((schema != NULL) && (schema[0] != 0))
245 {
246 char query[256];
247 snprintf(query, 256, "SET CURRENT SCHEMA = '%s'", schema);
248
249 iResult = SQLAllocHandle(SQL_HANDLE_STMT, pConn->sqlConn, &sqlStatement);
250 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
251 {
252 // Execute statement
253 SQLWCHAR *temp = (SQLWCHAR *)UCS2StringFromMBString(query);
254 iResult = SQLExecDirectW(sqlStatement, temp, SQL_NTS);
255 free(temp);
256 if ((iResult != SQL_SUCCESS) &&
257 (iResult != SQL_SUCCESS_WITH_INFO) &&
258 (iResult != SQL_NO_DATA))
259 {
260 GetSQLErrorInfo(SQL_HANDLE_STMT, sqlStatement, errorText);
261 goto connect_failure_3;
262 }
263 SQLFreeHandle(SQL_HANDLE_STMT, sqlStatement);
264 }
265 else
266 {
267 GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
268 goto connect_failure_2;
269 }
270 }
271
272 // Create mutex
273 pConn->mutexQuery = MutexCreate();
274
275 // Success
276 return (DBDRV_CONNECTION)pConn;
277
278 // Handle failures
279 connect_failure_3:
280 SQLFreeHandle(SQL_HANDLE_STMT, sqlStatement);
281
282 connect_failure_2:
283 SQLFreeHandle(SQL_HANDLE_DBC, pConn->sqlConn);
284
285 connect_failure_1:
286 SQLFreeHandle(SQL_HANDLE_ENV, pConn->sqlEnv);
287
288 connect_failure_0:
289 free(pConn);
290 return NULL;
291 }
292
293 /**
294 * Disconnect from database
295 */
296 extern "C" void EXPORT DrvDisconnect(DB2DRV_CONN *pConn)
297 {
298 MutexLock(pConn->mutexQuery);
299 MutexUnlock(pConn->mutexQuery);
300 SQLDisconnect(pConn->sqlConn);
301 SQLFreeHandle(SQL_HANDLE_DBC, pConn->sqlConn);
302 SQLFreeHandle(SQL_HANDLE_ENV, pConn->sqlEnv);
303 MutexDestroy(pConn->mutexQuery);
304 free(pConn);
305 }
306
307 /**
308 * Prepare statement
309 */
310 extern "C" DBDRV_STATEMENT EXPORT DrvPrepare(DB2DRV_CONN *pConn, NETXMS_WCHAR *pwszQuery, DWORD *pdwError, NETXMS_WCHAR *errorText)
311 {
312 long iResult;
313 SQLHSTMT statement;
314 DB2DRV_STATEMENT *result;
315
316 MutexLock(pConn->mutexQuery);
317
318 // Allocate statement handle
319 iResult = SQLAllocHandle(SQL_HANDLE_STMT, pConn->sqlConn, &statement);
320 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
321 {
322 // Prepare statement
323 #if defined(_WIN32) || defined(UNICODE_UCS2)
324 iResult = SQLPrepareW(statement, (SQLWCHAR *)pwszQuery, SQL_NTS);
325 #else
326 SQLWCHAR *temp = UCS2StringFromUCS4String(pwszQuery);
327 iResult = SQLPrepareW(statement, temp, SQL_NTS);
328 free(temp);
329 #endif
330 if ((iResult == SQL_SUCCESS) ||
331 (iResult == SQL_SUCCESS_WITH_INFO))
332 {
333 result = (DB2DRV_STATEMENT *)malloc(sizeof(DB2DRV_STATEMENT));
334 result->handle = statement;
335 result->buffers = new Array(0, 16, true);
336 result->connection = pConn;
337 *pdwError = DBERR_SUCCESS;
338 }
339 else
340 {
341 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, statement, errorText);
342 SQLFreeHandle(SQL_HANDLE_STMT, statement);
343 result = NULL;
344 }
345 }
346 else
347 {
348 *pdwError = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
349 result = NULL;
350 }
351
352 MutexUnlock(pConn->mutexQuery);
353 return result;
354 }
355
356
357 //
358 // Bind parameter to statement
359 //
360
361 extern "C" void EXPORT DrvBind(DB2DRV_STATEMENT *statement, int pos, int sqlType, int cType, void *buffer, int allocType)
362 {
363 static SQLSMALLINT odbcSqlType[] = { SQL_VARCHAR, SQL_INTEGER, SQL_BIGINT, SQL_DOUBLE, SQL_LONGVARCHAR };
364 static SQLSMALLINT odbcCType[] = { SQL_C_WCHAR, SQL_C_SLONG, SQL_C_ULONG, SQL_C_SBIGINT, SQL_C_UBIGINT, SQL_C_DOUBLE };
365 static DWORD bufferSize[] = { 0, sizeof(LONG), sizeof(DWORD), sizeof(INT64), sizeof(QWORD), sizeof(double) };
366
367 int length = (int)wcslen((WCHAR *)buffer) + 1;
368
369 SQLPOINTER sqlBuffer;
370 switch(allocType)
371 {
372 case DB_BIND_STATIC:
373 #if defined(_WIN32) || defined(UNICODE_UCS2)
374 sqlBuffer = buffer;
375 #else
376 if (cType == DB_CTYPE_STRING)
377 {
378 sqlBuffer = UCS2StringFromUCS4String((WCHAR *)buffer);
379 statement->buffers->add(sqlBuffer);
380 }
381 else
382 {
383 sqlBuffer = buffer;
384 }
385 #endif
386 break;
387 case DB_BIND_DYNAMIC:
388 #if defined(_WIN32) || defined(UNICODE_UCS2)
389 sqlBuffer = buffer;
390 #else
391 if (cType == DB_CTYPE_STRING)
392 {
393 sqlBuffer = UCS2StringFromUCS4String((WCHAR *)buffer);
394 free(buffer);
395 }
396 else
397 {
398 sqlBuffer = buffer;
399 }
400 #endif
401 statement->buffers->add(sqlBuffer);
402 break;
403 case DB_BIND_TRANSIENT:
404 #if defined(_WIN32) || defined(UNICODE_UCS2)
405 sqlBuffer = nx_memdup(buffer, (cType == DB_CTYPE_STRING) ? (DWORD)(length * sizeof(WCHAR)) : bufferSize[cType]);
406 #else
407 if (cType == DB_CTYPE_STRING)
408 {
409 sqlBuffer = UCS2StringFromUCS4String((WCHAR *)buffer);
410 }
411 else
412 {
413 sqlBuffer = nx_memdup(buffer, bufferSize[cType]);
414 }
415 #endif
416 statement->buffers->add(sqlBuffer);
417 break;
418 default:
419 return; // Invalid call
420 }
421 SQLBindParameter(statement->handle, pos, SQL_PARAM_INPUT, odbcCType[cType], odbcSqlType[sqlType],
422 (cType == DB_CTYPE_STRING) ? length : 0, 0, sqlBuffer, 0, NULL);
423 }
424
425
426 //
427 // Execute prepared statement
428 //
429
430 extern "C" DWORD EXPORT DrvExecute(DB2DRV_CONN *pConn, DB2DRV_STATEMENT *statement, NETXMS_WCHAR *errorText)
431 {
432 DWORD dwResult;
433
434 MutexLock(pConn->mutexQuery);
435 long rc = SQLExecute(statement->handle);
436 if (
437 (rc == SQL_SUCCESS) ||
438 (rc == SQL_SUCCESS_WITH_INFO) ||
439 (rc == SQL_NO_DATA))
440 {
441 ClearPendingResults(statement->handle);
442 dwResult = DBERR_SUCCESS;
443 }
444 else
445 {
446 dwResult = GetSQLErrorInfo(SQL_HANDLE_STMT, statement->handle, errorText);
447 }
448 MutexUnlock(pConn->mutexQuery);
449 return dwResult;
450 }
451
452
453 //
454 // Destroy prepared statement
455 //
456
457 extern "C" void EXPORT DrvFreeStatement(DB2DRV_STATEMENT *statement)
458 {
459 if (statement == NULL)
460 {
461 return;
462 }
463
464 MutexLock(statement->connection->mutexQuery);
465 SQLFreeHandle(SQL_HANDLE_STMT, statement->handle);
466 MutexUnlock(statement->connection->mutexQuery);
467 delete statement->buffers;
468 free(statement);
469 }
470
471 /**
472 * Perform non-SELECT query
473 */
474 extern "C" DWORD EXPORT DrvQuery(DB2DRV_CONN *pConn, NETXMS_WCHAR *pwszQuery, NETXMS_WCHAR *errorText)
475 {
476 long iResult;
477 DWORD dwResult;
478
479 MutexLock(pConn->mutexQuery);
480
481 // Allocate statement handle
482 SQLHSTMT sqlStatement;
483 iResult = SQLAllocHandle(SQL_HANDLE_STMT, pConn->sqlConn, &sqlStatement);
484 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
485 {
486 // Execute statement
487 #if defined(_WIN32) || defined(UNICODE_UCS2)
488 iResult = SQLExecDirectW(sqlStatement, (SQLWCHAR *)pwszQuery, SQL_NTS);
489 #else
490 SQLWCHAR *temp = UCS2StringFromUCS4String(pwszQuery);
491 iResult = SQLExecDirectW(sqlStatement, temp, SQL_NTS);
492 free(temp);
493 #endif
494 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO) || (iResult == SQL_NO_DATA))
495 {
496 dwResult = DBERR_SUCCESS;
497 }
498 else
499 {
500 dwResult = GetSQLErrorInfo(SQL_HANDLE_STMT, sqlStatement, errorText);
501 }
502 SQLFreeHandle(SQL_HANDLE_STMT, sqlStatement);
503 }
504 else
505 {
506 dwResult = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
507 }
508
509 MutexUnlock(pConn->mutexQuery);
510 return dwResult;
511 }
512
513 /**
514 * Get complete field data
515 */
516 static NETXMS_WCHAR *GetFieldData(SQLHSTMT sqlStatement, short column)
517 {
518 NETXMS_WCHAR *result = NULL;
519 SQLINTEGER dataSize;
520 #if defined(_WIN32) || defined(UNICODE_UCS2)
521 WCHAR buffer[256];
522 SQLRETURN rc = SQLGetData(sqlStatement, column, SQL_C_WCHAR, buffer, sizeof(buffer), &dataSize);
523 if (((rc == SQL_SUCCESS) || ((rc == SQL_SUCCESS_WITH_INFO) && (dataSize >= 0) && (dataSize <= (SQLLEN)(sizeof(buffer) - sizeof(WCHAR))))) && (dataSize != SQL_NULL_DATA))
524 {
525 result = wcsdup(buffer);
526 }
527 else if ((rc == SQL_SUCCESS_WITH_INFO) && (dataSize != SQL_NULL_DATA))
528 {
529 if (dataSize > (SQLLEN)(sizeof(buffer) - sizeof(WCHAR)))
530 {
531 WCHAR *temp = (WCHAR *)malloc(dataSize + sizeof(WCHAR));
532 memcpy(temp, buffer, sizeof(buffer));
533 rc = SQLGetData(sqlStatement, column, SQL_C_WCHAR, &temp[255], dataSize - 254 * sizeof(WCHAR), &dataSize);
534 if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO))
535 {
536 result = temp;
537 }
538 else
539 {
540 free(temp);
541 }
542 }
543 else if (dataSize == SQL_NO_TOTAL)
544 {
545 size_t tempSize = sizeof(buffer) * 4; // temporary buffer size in bytes
546 WCHAR *temp = (WCHAR *)malloc(tempSize);
547 memcpy(temp, buffer, sizeof(buffer));
548 size_t offset = sizeof(buffer) - sizeof(WCHAR); // offset in buffer in bytes
549 while(true)
550 {
551 SQLINTEGER readSize = (SQLINTEGER)(tempSize - offset);
552 rc = SQLGetData(sqlStatement, column, SQL_C_WCHAR, (char *)temp + offset, readSize, &dataSize);
553 if ((rc == SQL_SUCCESS) || (rc == SQL_NO_DATA))
554 break;
555 if (dataSize == SQL_NO_TOTAL)
556 {
557 tempSize += sizeof(buffer) * 4;
558 }
559 else
560 {
561 tempSize += dataSize - readSize;
562 }
563 temp = (WCHAR *)realloc(temp, tempSize);
564 offset += readSize - sizeof(WCHAR);
565 }
566 result = temp;
567 }
568 }
569 #else
570 UCS2CHAR buffer[256];
571 SQLRETURN rc = SQLGetData(sqlStatement, column, SQL_C_WCHAR, buffer, sizeof(buffer), &dataSize);
572 if (((rc == SQL_SUCCESS) || ((rc == SQL_SUCCESS_WITH_INFO) && (dataSize >= 0) && (dataSize <= (SQLLEN)(sizeof(buffer) - sizeof(UCS2CHAR))))) && (dataSize != SQL_NULL_DATA))
573 {
574 int len = ucs2_strlen(buffer);
575 result = (NETXMS_WCHAR *)malloc((len + 1) * sizeof(NETXMS_WCHAR));
576 ucs2_to_ucs4(buffer, -1, pResult->values[i], len + 1);
577 }
578 else if ((rc == SQL_SUCCESS_WITH_INFO) && (dataSize != SQL_NULL_DATA))
579 {
580 if (dataSize > (SQLLEN)(sizeof(buffer) - sizeof(UCS2CHAR)))
581 {
582 UCS2CHAR *temp = (UCS2CHAR *)malloc(dataSize + sizeof(UCS2CHAR));
583 memcpy(temp, buffer, sizeof(buffer));
584 rc = SQLGetData(sqlStatement, column, SQL_C_WCHAR, &temp[255], dataSize - 254 * sizeof(UCS2CHAR), &dataSize);
585 if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO))
586 {
587 int len = ucs2_strlen(temp);
588 result = (NETXMS_WCHAR *)malloc((len + 1) * sizeof(NETXMS_WCHAR));
589 ucs2_to_ucs4(temp, -1, result, len + 1);
590 }
591 free(temp);
592 }
593 else if (dataSize == SQL_NO_TOTAL)
594 {
595 size_t tempSize = sizeof(buffer) * 4;
596 UCS2CHAR *temp = (UCS2CHAR *)malloc(tempSize);
597 memcpy(temp, buffer, sizeof(buffer));
598 size_t offset = sizeof(buffer) - sizeof(UCS2CHAR);
599 while(true)
600 {
601 SQLLEN readSize = tempSize - offset;
602 rc = SQLGetData(sqlStatement, column, SQL_C_WCHAR, (char *)temp + offset, readSize, &dataSize);
603 if ((rc == SQL_SUCCESS) || (rc == SQL_NO_DATA))
604 break;
605 if (dataSize == SQL_NO_TOTAL)
606 {
607 tempSize += sizeof(buffer) * 4;
608 }
609 else
610 {
611 tempSize += dataSize - readSize;
612 }
613 temp = (UCS2CHAR *)realloc(temp, tempSize);
614 offset += readSize - sizeof(UCS2CHAR);
615 }
616 int len = ucs2_strlen(temp);
617 result = (NETXMS_WCHAR *)malloc((len + 1) * sizeof(NETXMS_WCHAR));
618 ucs2_to_ucs4(temp, -1, result, len + 1);
619 free(temp);
620 }
621 }
622 #endif
623 return (result != NULL) ? result : wcsdup(L"");
624 }
625
626 /**
627 * Process results of SELECT query
628 */
629 static DB2DRV_QUERY_RESULT *ProcessSelectResults(SQLHSTMT statement)
630 {
631 // Allocate result buffer and determine column info
632 DB2DRV_QUERY_RESULT *pResult = (DB2DRV_QUERY_RESULT *)malloc(sizeof(DB2DRV_QUERY_RESULT));
633 short wNumCols;
634 SQLNumResultCols(statement, &wNumCols);
635 pResult->numColumns = wNumCols;
636 pResult->numRows = 0;
637 pResult->values = NULL;
638
639 // Get column names
640 pResult->columnNames = (char **)malloc(sizeof(char *) * pResult->numColumns);
641 for(int i = 0; i < (int)pResult->numColumns; i++)
642 {
643 UCS2CHAR name[256];
644 SQLSMALLINT len;
645
646 SQLRETURN iResult = SQLColAttributeW(statement, (SQLSMALLINT)(i + 1), SQL_DESC_NAME, (SQLPOINTER)name, 256, &len, NULL);
647 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
648 {
649 name[len] = 0;
650 pResult->columnNames[i] = MBStringFromUCS2String(name);
651 }
652 else
653 {
654 pResult->columnNames[i] = strdup("");
655 }
656 }
657
658 // Fetch all data
659 long iCurrValue = 0;
660 SQLRETURN iResult;
661 while(iResult = SQLFetch(statement), (iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
662 {
663 pResult->numRows++;
664 pResult->values = (NETXMS_WCHAR **)realloc(pResult->values, sizeof(NETXMS_WCHAR *) * (pResult->numRows * pResult->numColumns));
665 for(int i = 1; i <= (int)pResult->numColumns; i++)
666 {
667 pResult->values[iCurrValue++] = GetFieldData(statement, (short)i);
668 }
669 }
670
671 return pResult;
672 }
673
674 /**
675 * Perform SELECT query
676 */
677 extern "C" DBDRV_RESULT EXPORT DrvSelect(DB2DRV_CONN *pConn, NETXMS_WCHAR *pwszQuery, DWORD *pdwError, NETXMS_WCHAR *errorText)
678 {
679 DB2DRV_QUERY_RESULT *pResult = NULL;
680
681 MutexLock(pConn->mutexQuery);
682
683 // Allocate statement handle
684 SQLHSTMT sqlStatement;
685 long iResult = SQLAllocHandle(SQL_HANDLE_STMT, pConn->sqlConn, &sqlStatement);
686 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
687 {
688 // Execute statement
689 #if defined(_WIN32) || defined(UNICODE_UCS2)
690 iResult = SQLExecDirectW(sqlStatement, (SQLWCHAR *)pwszQuery, SQL_NTS);
691 #else
692 SQLWCHAR *temp = UCS2StringFromUCS4String(pwszQuery);
693 iResult = SQLExecDirectW(sqlStatement, temp, SQL_NTS);
694 free(temp);
695 #endif
696 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
697 {
698 pResult = ProcessSelectResults(sqlStatement);
699 *pdwError = DBERR_SUCCESS;
700 }
701 else
702 {
703 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, sqlStatement, errorText);
704 }
705 SQLFreeHandle(SQL_HANDLE_STMT, sqlStatement);
706 }
707 else
708 {
709 *pdwError = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
710 }
711
712 MutexUnlock(pConn->mutexQuery);
713 return pResult;
714 }
715
716 /**
717 * Perform SELECT query using prepared statement
718 */
719 extern "C" DBDRV_RESULT EXPORT DrvSelectPrepared(DB2DRV_CONN *pConn, DB2DRV_STATEMENT *statement, DWORD *pdwError, NETXMS_WCHAR *errorText)
720 {
721 DB2DRV_QUERY_RESULT *pResult = NULL;
722
723 MutexLock(pConn->mutexQuery);
724 long rc = SQLExecute(statement->handle);
725 if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO))
726 {
727 pResult = ProcessSelectResults(statement->handle);
728 *pdwError = DBERR_SUCCESS;
729 }
730 else
731 {
732 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, statement->handle, errorText);
733 }
734 MutexUnlock(pConn->mutexQuery);
735 return pResult;
736 }
737
738 /**
739 * Get field length from result
740 */
741 extern "C" LONG EXPORT DrvGetFieldLength(DB2DRV_QUERY_RESULT *pResult, int iRow, int iColumn)
742 {
743 LONG nLen = -1;
744
745 if (pResult != NULL)
746 {
747 if ((iRow < pResult->numRows) && (iRow >= 0) &&
748 (iColumn < pResult->numColumns) && (iColumn >= 0))
749 {
750 nLen = (LONG)wcslen(pResult->values[iRow * pResult->numColumns + iColumn]);
751 }
752 }
753 return nLen;
754 }
755
756 /**
757 * Get field value from result
758 */
759 extern "C" NETXMS_WCHAR EXPORT *DrvGetField(DB2DRV_QUERY_RESULT *pResult, int iRow, int iColumn, NETXMS_WCHAR *pBuffer, int nBufSize)
760 {
761 NETXMS_WCHAR *pValue = NULL;
762
763 if (pResult != NULL)
764 {
765 if ((iRow < pResult->numRows) && (iRow >= 0) &&
766 (iColumn < pResult->numColumns) && (iColumn >= 0))
767 {
768 #ifdef _WIN32
769 wcsncpy_s(pBuffer, nBufSize, pResult->values[iRow * pResult->numColumns + iColumn], _TRUNCATE);
770 #else
771 wcsncpy(pBuffer, pResult->values[iRow * pResult->numColumns + iColumn], nBufSize);
772 pBuffer[nBufSize - 1] = 0;
773 #endif
774 pValue = pBuffer;
775 }
776 }
777 return pValue;
778 }
779
780 /**
781 * Get number of rows in result
782 */
783 extern "C" int EXPORT DrvGetNumRows(DB2DRV_QUERY_RESULT *pResult)
784 {
785 return (pResult != NULL) ? pResult->numRows : 0;
786 }
787
788 /**
789 * Get column count in query result
790 */
791 extern "C" int EXPORT DrvGetColumnCount(DB2DRV_QUERY_RESULT *pResult)
792 {
793 return (pResult != NULL) ? pResult->numColumns : 0;
794 }
795
796 /**
797 * Get column name in query result
798 */
799 extern "C" const char EXPORT *DrvGetColumnName(DB2DRV_QUERY_RESULT *pResult, int column)
800 {
801 return ((pResult != NULL) && (column >= 0) && (column < pResult->numColumns)) ? pResult->columnNames[column] : NULL;
802 }
803
804 /**
805 * Free SELECT results
806 */
807 extern "C" void EXPORT DrvFreeResult(DB2DRV_QUERY_RESULT *pResult)
808 {
809 if (pResult == NULL)
810 return;
811
812 int i, iNumValues;
813
814 iNumValues = pResult->numColumns * pResult->numRows;
815 for(i = 0; i < iNumValues; i++)
816 {
817 free(pResult->values[i]);
818 }
819 safe_free(pResult->values);
820
821 for(i = 0; i < pResult->numColumns; i++)
822 {
823 free(pResult->columnNames[i]);
824 }
825 free(pResult->columnNames);
826 free(pResult);
827 }
828
829 /**
830 * Perform unbuffered SELECT query
831 */
832 extern "C" DBDRV_UNBUFFERED_RESULT EXPORT DrvSelectUnbuffered(DB2DRV_CONN *pConn, NETXMS_WCHAR *pwszQuery, DWORD *pdwError, NETXMS_WCHAR *errorText)
833 {
834 DB2DRV_UNBUFFERED_QUERY_RESULT *pResult = NULL;
835
836 MutexLock(pConn->mutexQuery);
837
838 // Allocate statement handle
839 SQLHSTMT sqlStatement;
840 SQLRETURN iResult = SQLAllocHandle(SQL_HANDLE_STMT, pConn->sqlConn, &sqlStatement);
841 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
842 {
843 // Execute statement
844 #if defined(_WIN32) || defined(UNICODE_UCS2)
845 iResult = SQLExecDirectW(sqlStatement, (SQLWCHAR *)pwszQuery, SQL_NTS);
846 #else
847 SQLWCHAR *temp = UCS2StringFromUCS4String(pwszQuery);
848 iResult = SQLExecDirectW(sqlStatement, temp, SQL_NTS);
849 free(temp);
850 #endif
851 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
852 {
853 // Allocate result buffer and determine column info
854 pResult = (DB2DRV_UNBUFFERED_QUERY_RESULT *)malloc(sizeof(DB2DRV_UNBUFFERED_QUERY_RESULT));
855 pResult->sqlStatement = sqlStatement;
856 pResult->isPrepared = false;
857
858 short wNumCols;
859 SQLNumResultCols(sqlStatement, &wNumCols);
860 pResult->numColumns = wNumCols;
861 pResult->pConn = pConn;
862 pResult->noMoreRows = false;
863
864 // Get column names
865 pResult->columnNames = (char **)malloc(sizeof(char *) * pResult->numColumns);
866 for(int i = 0; i < pResult->numColumns; i++)
867 {
868 SQLWCHAR name[256];
869 SQLSMALLINT len;
870
871 iResult = SQLColAttributeW(sqlStatement, (SQLSMALLINT)(i + 1), SQL_DESC_NAME, name, 256, &len, NULL);
872 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
873 {
874 name[len] = 0;
875 pResult->columnNames[i] = MBStringFromUCS2String((UCS2CHAR *)name);
876 }
877 else
878 {
879 pResult->columnNames[i] = strdup("");
880 }
881 }
882
883 *pdwError = DBERR_SUCCESS;
884 }
885 else
886 {
887 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, sqlStatement, errorText);
888 // Free statement handle if query failed
889 SQLFreeHandle(SQL_HANDLE_STMT, sqlStatement);
890 }
891 }
892 else
893 {
894 *pdwError = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
895 }
896
897 if (pResult == NULL) // Unlock mutex if query has failed
898 {
899 MutexUnlock(pConn->mutexQuery);
900 }
901 return pResult;
902 }
903
904 /**
905 * Perform unbuffered SELECT query using prepared statement
906 */
907 extern "C" DBDRV_UNBUFFERED_RESULT EXPORT DrvSelectPreparedUnbuffered(DB2DRV_CONN *pConn, DB2DRV_STATEMENT *stmt, DWORD *pdwError, WCHAR *errorText)
908 {
909 DB2DRV_UNBUFFERED_QUERY_RESULT *pResult = NULL;
910
911 MutexLock(pConn->mutexQuery);
912 SQLRETURN rc = SQLExecute(stmt->handle);
913 if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO))
914 {
915 // Allocate result buffer and determine column info
916 pResult = (DB2DRV_UNBUFFERED_QUERY_RESULT *)malloc(sizeof(DB2DRV_UNBUFFERED_QUERY_RESULT));
917 pResult->sqlStatement = stmt->handle;
918 pResult->isPrepared = true;
919
920 short wNumCols;
921 SQLNumResultCols(pResult->sqlStatement, &wNumCols);
922 pResult->numColumns = wNumCols;
923 pResult->pConn = pConn;
924 pResult->noMoreRows = false;
925
926 // Get column names
927 pResult->columnNames = (char **)malloc(sizeof(char *) * pResult->numColumns);
928 for(int i = 0; i < pResult->numColumns; i++)
929 {
930 SQLWCHAR name[256];
931 SQLSMALLINT len;
932
933 rc = SQLColAttributeW(pResult->sqlStatement, (SQLSMALLINT)(i + 1), SQL_DESC_NAME, name, 256, &len, NULL);
934 if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO))
935 {
936 name[len] = 0;
937 pResult->columnNames[i] = MBStringFromUCS2String((UCS2CHAR *)name);
938 }
939 else
940 {
941 pResult->columnNames[i] = strdup("");
942 }
943 }
944
945 *pdwError = DBERR_SUCCESS;
946 }
947 else
948 {
949 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, stmt->handle, errorText);
950 }
951
952 if (pResult == NULL) // Unlock mutex if query has failed
953 MutexUnlock(pConn->mutexQuery);
954 return pResult;
955 }
956
957 /**
958 * Fetch next result line from asynchronous SELECT results
959 */
960 extern "C" bool EXPORT DrvFetch(DB2DRV_UNBUFFERED_QUERY_RESULT *pResult)
961 {
962 bool bResult = false;
963
964 if (pResult != NULL)
965 {
966 long iResult;
967
968 iResult = SQLFetch(pResult->sqlStatement);
969 bResult = ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO));
970 if (!bResult)
971 {
972 pResult->noMoreRows = true;
973 }
974 }
975 return bResult;
976 }
977
978 /**
979 * Get field length from unbuffered query result
980 */
981 extern "C" LONG EXPORT DrvGetFieldLengthUnbuffered(DB2DRV_UNBUFFERED_QUERY_RESULT *pResult, int iColumn)
982 {
983 LONG nLen = -1;
984
985 if (pResult != NULL)
986 {
987 if ((iColumn < pResult->numColumns) && (iColumn >= 0))
988 {
989 SQLLEN dataSize;
990 char temp[1];
991 long rc = SQLGetData(pResult->sqlStatement, (short)iColumn + 1, SQL_C_CHAR, temp, 0, &dataSize);
992 if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO))
993 {
994 nLen = (LONG)dataSize;
995 }
996 }
997 }
998 return nLen;
999 }
1000
1001 /**
1002 * Get field from current row in unbuffered query result
1003 */
1004 extern "C" NETXMS_WCHAR EXPORT *DrvGetFieldUnbuffered(DB2DRV_UNBUFFERED_QUERY_RESULT *pResult, int iColumn, NETXMS_WCHAR *pBuffer, int iBufSize)
1005 {
1006 SQLLEN iDataSize;
1007 long iResult;
1008
1009 // Check if we have valid result handle
1010 if (pResult == NULL)
1011 {
1012 return NULL;
1013 }
1014
1015 // Check if there are valid fetched row
1016 if (pResult->noMoreRows)
1017 {
1018 return NULL;
1019 }
1020
1021 if ((iColumn >= 0) && (iColumn < pResult->numColumns))
1022 {
1023 #if defined(_WIN32) || defined(UNICODE_UCS2)
1024 iResult = SQLGetData(pResult->sqlStatement, (short)iColumn + 1, SQL_C_WCHAR,
1025 pBuffer, iBufSize * sizeof(WCHAR), &iDataSize);
1026 #else
1027 SQLWCHAR *tempBuff = (SQLWCHAR *)malloc(iBufSize * sizeof(SQLWCHAR));
1028 iResult = SQLGetData(pResult->sqlStatement, (short)iColumn + 1, SQL_C_WCHAR,
1029 tempBuff, iBufSize * sizeof(SQLWCHAR), &iDataSize);
1030 ucs2_to_ucs4(tempBuff, -1, pBuffer, iBufSize);
1031 pBuffer[iBufSize - 1] = 0;
1032 free(tempBuff);
1033 #endif
1034 if (((iResult != SQL_SUCCESS) && (iResult != SQL_SUCCESS_WITH_INFO)) || (iDataSize == SQL_NULL_DATA))
1035 {
1036 pBuffer[0] = 0;
1037 }
1038 }
1039 else
1040 {
1041 pBuffer[0] = 0;
1042 }
1043 return pBuffer;
1044 }
1045
1046 /**
1047 * Get column count in async query result
1048 */
1049 extern "C" int EXPORT DrvGetColumnCountUnbuffered(DB2DRV_UNBUFFERED_QUERY_RESULT *pResult)
1050 {
1051 return (pResult != NULL) ? pResult->numColumns : 0;
1052 }
1053
1054 /**
1055 * Get column name in async query result
1056 */
1057 extern "C" const char EXPORT *DrvGetColumnNameUnbuffered(DB2DRV_UNBUFFERED_QUERY_RESULT *pResult, int column)
1058 {
1059 return ((pResult != NULL) && (column >= 0) && (column < pResult->numColumns)) ? pResult->columnNames[column] : NULL;
1060 }
1061
1062 /**
1063 * Destroy result of unbuffered query
1064 */
1065 extern "C" void EXPORT DrvFreeUnbufferedResult(DB2DRV_UNBUFFERED_QUERY_RESULT *pResult)
1066 {
1067 if (pResult == NULL)
1068 return;
1069
1070 if (pResult->isPrepared)
1071 SQLCloseCursor(pResult->sqlStatement);
1072 else
1073 SQLFreeHandle(SQL_HANDLE_STMT, pResult->sqlStatement);
1074 MutexUnlock(pResult->pConn->mutexQuery);
1075
1076 for(int i = 0; i < pResult->numColumns; i++)
1077 {
1078 free(pResult->columnNames[i]);
1079 }
1080 free(pResult->columnNames);
1081
1082 free(pResult);
1083 }
1084
1085 /**
1086 * Begin transaction
1087 */
1088 extern "C" DWORD EXPORT DrvBegin(DB2DRV_CONN *pConn)
1089 {
1090 SQLRETURN nRet;
1091 DWORD dwResult;
1092
1093 if (pConn == NULL)
1094 {
1095 return DBERR_INVALID_HANDLE;
1096 }
1097
1098 MutexLock(pConn->mutexQuery);
1099 nRet = SQLSetConnectAttr(pConn->sqlConn, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0);
1100 if ((nRet == SQL_SUCCESS) || (nRet == SQL_SUCCESS_WITH_INFO))
1101 {
1102 dwResult = DBERR_SUCCESS;
1103 }
1104 else
1105 {
1106 dwResult = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, NULL);
1107 }
1108 MutexUnlock(pConn->mutexQuery);
1109 return dwResult;
1110 }
1111
1112 /**
1113 * Commit transaction
1114 */
1115 extern "C" DWORD EXPORT DrvCommit(DB2DRV_CONN *pConn)
1116 {
1117 SQLRETURN nRet;
1118
1119 if (pConn == NULL)
1120 {
1121 return DBERR_INVALID_HANDLE;
1122 }
1123
1124 MutexLock(pConn->mutexQuery);
1125 nRet = SQLEndTran(SQL_HANDLE_DBC, pConn->sqlConn, SQL_COMMIT);
1126 SQLSetConnectAttr(pConn->sqlConn, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
1127 MutexUnlock(pConn->mutexQuery);
1128 return ((nRet == SQL_SUCCESS) || (nRet == SQL_SUCCESS_WITH_INFO)) ? DBERR_SUCCESS : DBERR_OTHER_ERROR;
1129 }
1130
1131 /**
1132 * Rollback transaction
1133 */
1134 extern "C" DWORD EXPORT DrvRollback(DB2DRV_CONN *pConn)
1135 {
1136 SQLRETURN nRet;
1137
1138 if (pConn == NULL)
1139 {
1140 return DBERR_INVALID_HANDLE;
1141 }
1142
1143 MutexLock(pConn->mutexQuery);
1144 nRet = SQLEndTran(SQL_HANDLE_DBC, pConn->sqlConn, SQL_ROLLBACK);
1145 SQLSetConnectAttr(pConn->sqlConn, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
1146 MutexUnlock(pConn->mutexQuery);
1147 return ((nRet == SQL_SUCCESS) || (nRet == SQL_SUCCESS_WITH_INFO)) ? DBERR_SUCCESS : DBERR_OTHER_ERROR;
1148 }
1149
1150
1151 /**
1152 * Check if table exist
1153 */
1154 extern "C" int EXPORT DrvIsTableExist(DB2DRV_CONN *pConn, const NETXMS_WCHAR *name)
1155 {
1156 WCHAR query[256];
1157 swprintf(query, 256, L"SELECT count(*) FROM sysibm.systables WHERE type='T' AND upper(name)=upper('%ls')", name);
1158 DWORD error;
1159 WCHAR errorText[DBDRV_MAX_ERROR_TEXT];
1160 int rc = DBIsTableExist_Failure;
1161 DB2DRV_QUERY_RESULT *hResult = (DB2DRV_QUERY_RESULT *)DrvSelect(pConn, query, &error, errorText);
1162 if (hResult != NULL)
1163 {
1164 WCHAR buffer[64] = L"";
1165 DrvGetField(hResult, 0, 0, buffer, 64);
1166 rc = (wcstol(buffer, NULL, 10) > 0) ? DBIsTableExist_Found : DBIsTableExist_NotFound;
1167 DrvFreeResult(hResult);
1168 }
1169 return rc;
1170 }
1171
1172 #ifdef _WIN32
1173
1174 /**
1175 * DLL Entry point
1176 */
1177 BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
1178 {
1179 if (dwReason == DLL_PROCESS_ATTACH)
1180 {
1181 DisableThreadLibraryCalls(hInstance);
1182 }
1183 return TRUE;
1184 }
1185
1186 #endif