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