fixed Windows build for DB drivers
[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 char s_driver[SQL_MAX_DSN_LENGTH + 1] = "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 char name[SQL_MAX_DSN_LENGTH + 1], attrs[1024];
183 SQLSMALLINT l1, l2;
184 rc = SQLDriversA(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 (!strcmp(name, "SQL Server Native Client 10.0") ||
188 !strcmp(name, "SQL Server Native Client 11.0"))
189 {
190 strcpy(s_driver, name);
191 break;
192 }
193 rc = SQLDriversA(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", s_driver, host, database);
252 }
253 else
254 {
255 snprintf(connectString, 1024, "DRIVER={%s};Server=%s;UID=%s;PWD=%s;Database=%s;APP=NetXMS", s_driver, host, login, password, database);
256 }
257 iResult = SQLDriverConnectA(pConn->sqlConn, NULL, (SQLCHAR *)connectString, SQL_NTS, NULL, 0, &outLen, SQL_DRIVER_NOPROMPT);
258
259 if ((iResult != SQL_SUCCESS) && (iResult != SQL_SUCCESS_WITH_INFO))
260 {
261 GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
262 goto connect_failure_2;
263 }
264
265 // Create mutex
266 pConn->mutexQuery = MutexCreate();
267
268 // Success
269 return (DBDRV_CONNECTION)pConn;
270
271 // Handle failures
272 connect_failure_2:
273 SQLFreeHandle(SQL_HANDLE_DBC, pConn->sqlConn);
274
275 connect_failure_1:
276 SQLFreeHandle(SQL_HANDLE_ENV, pConn->sqlEnv);
277
278 connect_failure_0:
279 free(pConn);
280 return NULL;
281 }
282
283 /**
284 * Disconnect from database
285 */
286 extern "C" void EXPORT DrvDisconnect(MSSQL_CONN *pConn)
287 {
288 MutexLock(pConn->mutexQuery);
289 MutexUnlock(pConn->mutexQuery);
290 SQLDisconnect(pConn->sqlConn);
291 SQLFreeHandle(SQL_HANDLE_DBC, pConn->sqlConn);
292 SQLFreeHandle(SQL_HANDLE_ENV, pConn->sqlEnv);
293 MutexDestroy(pConn->mutexQuery);
294 free(pConn);
295 }
296
297 /**
298 * Prepare statement
299 */
300 extern "C" DBDRV_STATEMENT EXPORT DrvPrepare(MSSQL_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
301 {
302 long iResult;
303 SQLHSTMT stmt;
304 MSSQL_STATEMENT *result;
305
306 MutexLock(pConn->mutexQuery);
307
308 // Allocate statement handle
309 iResult = SQLAllocHandle(SQL_HANDLE_STMT, pConn->sqlConn, &stmt);
310 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
311 {
312 // Prepare statement
313 iResult = SQLPrepareW(stmt, (SQLWCHAR *)pwszQuery, SQL_NTS);
314 if ((iResult == SQL_SUCCESS) ||
315 (iResult == SQL_SUCCESS_WITH_INFO))
316 {
317 result = (MSSQL_STATEMENT *)malloc(sizeof(MSSQL_STATEMENT));
318 result->handle = stmt;
319 result->buffers = new Array(0, 16, true);
320 result->connection = pConn;
321 *pdwError = DBERR_SUCCESS;
322 }
323 else
324 {
325 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, stmt, errorText);
326 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
327 result = NULL;
328 }
329 }
330 else
331 {
332 *pdwError = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
333 result = NULL;
334 }
335
336 MutexUnlock(pConn->mutexQuery);
337 return result;
338 }
339
340
341 //
342 // Bind parameter to statement
343 //
344
345 extern "C" void EXPORT DrvBind(MSSQL_STATEMENT *stmt, int pos, int sqlType, int cType, void *buffer, int allocType)
346 {
347 static SQLSMALLINT odbcSqlType[] = { SQL_VARCHAR, SQL_INTEGER, SQL_BIGINT, SQL_DOUBLE, SQL_LONGVARCHAR };
348 static SQLSMALLINT odbcCType[] = { SQL_C_WCHAR, SQL_C_SLONG, SQL_C_ULONG, SQL_C_SBIGINT, SQL_C_UBIGINT, SQL_C_DOUBLE };
349 static DWORD bufferSize[] = { 0, sizeof(LONG), sizeof(DWORD), sizeof(INT64), sizeof(QWORD), sizeof(double) };
350
351 int length = (cType == DB_CTYPE_STRING) ? ((int)wcslen((WCHAR *)buffer) + 1) : 0;
352
353 SQLPOINTER sqlBuffer;
354 switch(allocType)
355 {
356 case DB_BIND_STATIC:
357 sqlBuffer = buffer;
358 break;
359 case DB_BIND_DYNAMIC:
360 sqlBuffer = buffer;
361 stmt->buffers->add(sqlBuffer);
362 break;
363 case DB_BIND_TRANSIENT:
364 sqlBuffer = nx_memdup(buffer, (cType == DB_CTYPE_STRING) ? (DWORD)(length * sizeof(WCHAR)) : bufferSize[cType]);
365 stmt->buffers->add(sqlBuffer);
366 break;
367 default:
368 return; // Invalid call
369 }
370 SQLBindParameter(stmt->handle, pos, SQL_PARAM_INPUT, odbcCType[cType], odbcSqlType[sqlType],
371 (cType == DB_CTYPE_STRING) ? length : 0, 0, sqlBuffer, 0, NULL);
372 }
373
374
375 //
376 // Execute prepared statement
377 //
378
379 extern "C" DWORD EXPORT DrvExecute(MSSQL_CONN *pConn, MSSQL_STATEMENT *stmt, WCHAR *errorText)
380 {
381 DWORD dwResult;
382
383 MutexLock(pConn->mutexQuery);
384 long rc = SQLExecute(stmt->handle);
385 if ((rc == SQL_SUCCESS) ||
386 (rc == SQL_SUCCESS_WITH_INFO) ||
387 (rc == SQL_NO_DATA))
388 {
389 ClearPendingResults(stmt->handle);
390 dwResult = DBERR_SUCCESS;
391 }
392 else
393 {
394 dwResult = GetSQLErrorInfo(SQL_HANDLE_STMT, stmt->handle, errorText);
395 }
396 MutexUnlock(pConn->mutexQuery);
397 return dwResult;
398 }
399
400
401 //
402 // Destroy prepared statement
403 //
404
405 extern "C" void EXPORT DrvFreeStatement(MSSQL_STATEMENT *stmt)
406 {
407 if (stmt == NULL)
408 return;
409
410 MutexLock(stmt->connection->mutexQuery);
411 SQLFreeHandle(SQL_HANDLE_STMT, stmt->handle);
412 MutexUnlock(stmt->connection->mutexQuery);
413 delete stmt->buffers;
414 free(stmt);
415 }
416
417 /**
418 * Perform non-SELECT query
419 */
420 extern "C" DWORD EXPORT DrvQuery(MSSQL_CONN *pConn, WCHAR *pwszQuery, WCHAR *errorText)
421 {
422 long iResult;
423 DWORD dwResult;
424
425 MutexLock(pConn->mutexQuery);
426
427 // Allocate statement handle
428 SQLHSTMT sqlStatement;
429 iResult = SQLAllocHandle(SQL_HANDLE_STMT, pConn->sqlConn, &sqlStatement);
430 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
431 {
432 // Execute statement
433 iResult = SQLExecDirectW(sqlStatement, (SQLWCHAR *)pwszQuery, SQL_NTS);
434 if ((iResult == SQL_SUCCESS) ||
435 (iResult == SQL_SUCCESS_WITH_INFO) ||
436 (iResult == SQL_NO_DATA))
437 {
438 dwResult = DBERR_SUCCESS;
439 }
440 else
441 {
442 dwResult = GetSQLErrorInfo(SQL_HANDLE_STMT, sqlStatement, errorText);
443 }
444 SQLFreeHandle(SQL_HANDLE_STMT, sqlStatement);
445 }
446 else
447 {
448 dwResult = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
449 }
450
451 MutexUnlock(pConn->mutexQuery);
452 return dwResult;
453 }
454
455 /**
456 * Get complete field data
457 */
458 static WCHAR *GetFieldData(SQLHSTMT sqlStatement, short column)
459 {
460 WCHAR *result = NULL;
461 SQLLEN dataSize;
462 WCHAR buffer[256];
463 SQLRETURN rc = SQLGetData(sqlStatement, column, SQL_C_WCHAR, buffer, sizeof(buffer), &dataSize);
464 if (((rc == SQL_SUCCESS) || ((rc == SQL_SUCCESS_WITH_INFO) && (dataSize >= 0) && (dataSize <= (SQLLEN)(sizeof(buffer) - sizeof(WCHAR))))) && (dataSize != SQL_NULL_DATA))
465 {
466 result = wcsdup(buffer);
467 }
468 else if ((rc == SQL_SUCCESS_WITH_INFO) && (dataSize != SQL_NULL_DATA))
469 {
470 if (dataSize > (SQLLEN)(sizeof(buffer) - sizeof(WCHAR)))
471 {
472 WCHAR *temp = (WCHAR *)malloc(dataSize + sizeof(WCHAR));
473 memcpy(temp, buffer, sizeof(buffer));
474 rc = SQLGetData(sqlStatement, column, SQL_C_WCHAR, &temp[255], dataSize - 254 * sizeof(WCHAR), &dataSize);
475 if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO))
476 {
477 result = temp;
478 }
479 else
480 {
481 free(temp);
482 }
483 }
484 else if (dataSize == SQL_NO_TOTAL)
485 {
486 size_t tempSize = sizeof(buffer) * 4; // temporary buffer size in bytes
487 WCHAR *temp = (WCHAR *)malloc(tempSize);
488 memcpy(temp, buffer, sizeof(buffer));
489 size_t offset = sizeof(buffer) - sizeof(WCHAR); // offset in buffer in bytes
490 while(true)
491 {
492 SQLLEN readSize = tempSize - offset;
493 rc = SQLGetData(sqlStatement, column, SQL_C_WCHAR, (char *)temp + offset, readSize, &dataSize);
494 if ((rc == SQL_SUCCESS) || (rc == SQL_NO_DATA))
495 break;
496 if (dataSize == SQL_NO_TOTAL)
497 {
498 tempSize += sizeof(buffer) * 4;
499 }
500 else
501 {
502 tempSize += dataSize - readSize;
503 }
504 temp = (WCHAR *)realloc(temp, tempSize);
505 offset += readSize - sizeof(WCHAR);
506 }
507 result = temp;
508 }
509 }
510 return (result != NULL) ? result : wcsdup(L"");
511 }
512
513 /**
514 * Process results of SELECT query
515 */
516 static MSSQL_QUERY_RESULT *ProcessSelectResults(SQLHSTMT stmt)
517 {
518 // Allocate result buffer and determine column info
519 MSSQL_QUERY_RESULT *pResult = (MSSQL_QUERY_RESULT *)malloc(sizeof(MSSQL_QUERY_RESULT));
520 short wNumCols;
521 SQLNumResultCols(stmt, &wNumCols);
522 pResult->numColumns = wNumCols;
523 pResult->numRows = 0;
524 pResult->pValues = NULL;
525
526 // Get column names
527 pResult->columnNames = (char **)malloc(sizeof(char *) * pResult->numColumns);
528 for(int i = 0; i < (int)pResult->numColumns; i++)
529 {
530 char name[256];
531 SQLSMALLINT len;
532
533 SQLRETURN iResult = SQLColAttributeA(stmt, (SQLSMALLINT)(i + 1), SQL_DESC_NAME, name, 256, &len, NULL);
534 if ((iResult == SQL_SUCCESS) ||
535 (iResult == SQL_SUCCESS_WITH_INFO))
536 {
537 name[len] = 0;
538 pResult->columnNames[i] = strdup(name);
539 }
540 else
541 {
542 pResult->columnNames[i] = strdup("");
543 }
544 }
545
546 // Fetch all data
547 long iCurrValue = 0;
548 SQLRETURN iResult;
549 while(iResult = SQLFetch(stmt),
550 (iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
551 {
552 pResult->numRows++;
553 pResult->pValues = (WCHAR **)realloc(pResult->pValues,
554 sizeof(WCHAR *) * (pResult->numRows * pResult->numColumns));
555 for(int i = 1; i <= (int)pResult->numColumns; i++)
556 {
557 pResult->pValues[iCurrValue++] = GetFieldData(stmt, (short)i);
558 }
559 }
560
561 return pResult;
562 }
563
564 /**
565 * Perform SELECT query
566 */
567 extern "C" DBDRV_RESULT EXPORT DrvSelect(MSSQL_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
568 {
569 MSSQL_QUERY_RESULT *pResult = NULL;
570
571 MutexLock(pConn->mutexQuery);
572
573 // Allocate statement handle
574 SQLHSTMT sqlStatement;
575 SQLRETURN iResult = SQLAllocHandle(SQL_HANDLE_STMT, pConn->sqlConn, &sqlStatement);
576 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
577 {
578 // Execute statement
579 iResult = SQLExecDirectW(sqlStatement, (SQLWCHAR *)pwszQuery, SQL_NTS);
580 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
581 {
582 pResult = ProcessSelectResults(sqlStatement);
583 *pdwError = DBERR_SUCCESS;
584 }
585 else
586 {
587 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, sqlStatement, errorText);
588 }
589 SQLFreeHandle(SQL_HANDLE_STMT, sqlStatement);
590 }
591 else
592 {
593 *pdwError = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
594 }
595
596 MutexUnlock(pConn->mutexQuery);
597 return pResult;
598 }
599
600 /**
601 * Perform SELECT query using prepared statement
602 */
603 extern "C" DBDRV_RESULT EXPORT DrvSelectPrepared(MSSQL_CONN *pConn, MSSQL_STATEMENT *stmt, DWORD *pdwError, WCHAR *errorText)
604 {
605 MSSQL_QUERY_RESULT *pResult = NULL;
606
607 MutexLock(pConn->mutexQuery);
608 long rc = SQLExecute(stmt->handle);
609 if ((rc == SQL_SUCCESS) ||
610 (rc == SQL_SUCCESS_WITH_INFO))
611 {
612 pResult = ProcessSelectResults(stmt->handle);
613 *pdwError = DBERR_SUCCESS;
614 }
615 else
616 {
617 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, stmt->handle, errorText);
618 }
619 MutexUnlock(pConn->mutexQuery);
620 return pResult;
621 }
622
623 /**
624 * Get field length from result
625 */
626 extern "C" LONG EXPORT DrvGetFieldLength(MSSQL_QUERY_RESULT *pResult, int iRow, int iColumn)
627 {
628 LONG nLen = -1;
629
630 if (pResult != NULL)
631 {
632 if ((iRow < pResult->numRows) && (iRow >= 0) &&
633 (iColumn < pResult->numColumns) && (iColumn >= 0))
634 nLen = (LONG)wcslen(pResult->pValues[iRow * pResult->numColumns + iColumn]);
635 }
636 return nLen;
637 }
638
639 /**
640 * Get field value from result
641 */
642 extern "C" WCHAR EXPORT *DrvGetField(MSSQL_QUERY_RESULT *pResult, int iRow, int iColumn, WCHAR *pBuffer, int nBufSize)
643 {
644 WCHAR *pValue = NULL;
645
646 if (pResult != NULL)
647 {
648 if ((iRow < pResult->numRows) && (iRow >= 0) &&
649 (iColumn < pResult->numColumns) && (iColumn >= 0))
650 {
651 wcsncpy_s(pBuffer, nBufSize, pResult->pValues[iRow * pResult->numColumns + iColumn], _TRUNCATE);
652 pValue = pBuffer;
653 }
654 }
655 return pValue;
656 }
657
658 /**
659 * Get number of rows in result
660 */
661 extern "C" int EXPORT DrvGetNumRows(MSSQL_QUERY_RESULT *pResult)
662 {
663 return (pResult != NULL) ? pResult->numRows : 0;
664 }
665
666 /**
667 * Get column count in query result
668 */
669 extern "C" int EXPORT DrvGetColumnCount(MSSQL_QUERY_RESULT *pResult)
670 {
671 return (pResult != NULL) ? pResult->numColumns : 0;
672 }
673
674 /**
675 * Get column name in query result
676 */
677 extern "C" const char EXPORT *DrvGetColumnName(MSSQL_QUERY_RESULT *pResult, int column)
678 {
679 return ((pResult != NULL) && (column >= 0) && (column < pResult->numColumns)) ? pResult->columnNames[column] : NULL;
680 }
681
682 /**
683 * Free SELECT results
684 */
685 extern "C" void EXPORT DrvFreeResult(MSSQL_QUERY_RESULT *pResult)
686 {
687 if (pResult != NULL)
688 {
689 int i, iNumValues;
690
691 iNumValues = pResult->numColumns * pResult->numRows;
692 for(i = 0; i < iNumValues; i++)
693 safe_free(pResult->pValues[i]);
694 safe_free(pResult->pValues);
695
696 for(i = 0; i < pResult->numColumns; i++)
697 safe_free(pResult->columnNames[i]);
698 safe_free(pResult->columnNames);
699
700 free(pResult);
701 }
702 }
703
704 /**
705 * Perform unbuffered SELECT query
706 */
707 extern "C" DBDRV_UNBUFFERED_RESULT EXPORT DrvSelectUnbuffered(MSSQL_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
708 {
709 MSSQL_UNBUFFERED_QUERY_RESULT *pResult = NULL;
710
711 MutexLock(pConn->mutexQuery);
712
713 // Allocate statement handle
714 SQLHSTMT sqlStatement;
715 SQLRETURN iResult = SQLAllocHandle(SQL_HANDLE_STMT, pConn->sqlConn, &sqlStatement);
716 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
717 {
718 // Execute statement
719 iResult = SQLExecDirectW(sqlStatement, (SQLWCHAR *)pwszQuery, SQL_NTS);
720 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
721 {
722 // Allocate result buffer and determine column info
723 pResult = (MSSQL_UNBUFFERED_QUERY_RESULT *)malloc(sizeof(MSSQL_UNBUFFERED_QUERY_RESULT));
724 pResult->sqlStatement = sqlStatement;
725 pResult->isPrepared = false;
726
727 short wNumCols;
728 SQLNumResultCols(sqlStatement, &wNumCols);
729 pResult->numColumns = wNumCols;
730 pResult->pConn = pConn;
731 pResult->noMoreRows = false;
732 pResult->data = (WCHAR **)malloc(sizeof(WCHAR *) * pResult->numColumns);
733 memset(pResult->data, 0, sizeof(WCHAR *) * pResult->numColumns);
734
735 // Get column names
736 pResult->columnNames = (char **)malloc(sizeof(char *) * pResult->numColumns);
737 for(int i = 0; i < pResult->numColumns; i++)
738 {
739 char name[256];
740 SQLSMALLINT len;
741
742 iResult = SQLColAttributeA(sqlStatement, (SQLSMALLINT)(i + 1), SQL_DESC_NAME, name, 256, &len, NULL);
743 if ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO))
744 {
745 name[len] = 0;
746 pResult->columnNames[i] = strdup(name);
747 }
748 else
749 {
750 pResult->columnNames[i] = strdup("");
751 }
752 }
753
754 *pdwError = DBERR_SUCCESS;
755 }
756 else
757 {
758 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, sqlStatement, errorText);
759 // Free statement handle if query failed
760 SQLFreeHandle(SQL_HANDLE_STMT, sqlStatement);
761 }
762 }
763 else
764 {
765 *pdwError = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, errorText);
766 }
767
768 if (pResult == NULL) // Unlock mutex if query has failed
769 MutexUnlock(pConn->mutexQuery);
770 return pResult;
771 }
772
773 /**
774 * Perform unbuffered SELECT query using prepared statement
775 */
776 extern "C" DBDRV_UNBUFFERED_RESULT EXPORT DrvSelectPreparedUnbuffered(MSSQL_CONN *pConn, MSSQL_STATEMENT *stmt, DWORD *pdwError, WCHAR *errorText)
777 {
778 MSSQL_UNBUFFERED_QUERY_RESULT *pResult = NULL;
779
780 MutexLock(pConn->mutexQuery);
781 SQLRETURN rc = SQLExecute(stmt->handle);
782 if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO))
783 {
784 // Allocate result buffer and determine column info
785 pResult = (MSSQL_UNBUFFERED_QUERY_RESULT *)malloc(sizeof(MSSQL_UNBUFFERED_QUERY_RESULT));
786 pResult->sqlStatement = stmt->handle;
787 pResult->isPrepared = true;
788
789 short wNumCols;
790 SQLNumResultCols(pResult->sqlStatement, &wNumCols);
791 pResult->numColumns = wNumCols;
792 pResult->pConn = pConn;
793 pResult->noMoreRows = false;
794 pResult->data = (WCHAR **)malloc(sizeof(WCHAR *) * pResult->numColumns);
795 memset(pResult->data, 0, sizeof(WCHAR *) * pResult->numColumns);
796
797 // Get column names
798 pResult->columnNames = (char **)malloc(sizeof(char *) * pResult->numColumns);
799 for(int i = 0; i < pResult->numColumns; i++)
800 {
801 char name[256];
802 SQLSMALLINT len;
803
804 rc = SQLColAttributeA(pResult->sqlStatement, (SQLSMALLINT)(i + 1), SQL_DESC_NAME, name, 256, &len, NULL);
805 if ((rc == SQL_SUCCESS) || (rc == SQL_SUCCESS_WITH_INFO))
806 {
807 name[len] = 0;
808 pResult->columnNames[i] = strdup(name);
809 }
810 else
811 {
812 pResult->columnNames[i] = strdup("");
813 }
814 }
815 *pdwError = DBERR_SUCCESS;
816 }
817 else
818 {
819 *pdwError = GetSQLErrorInfo(SQL_HANDLE_STMT, stmt->handle, errorText);
820 }
821
822 if (pResult == NULL) // Unlock mutex if query has failed
823 MutexUnlock(pConn->mutexQuery);
824 return pResult;
825 }
826
827 /**
828 * Fetch next result line from unbuffered SELECT results
829 */
830 extern "C" bool EXPORT DrvFetch(MSSQL_UNBUFFERED_QUERY_RESULT *pResult)
831 {
832 bool bResult = false;
833
834 if (pResult != NULL)
835 {
836 long iResult;
837
838 iResult = SQLFetch(pResult->sqlStatement);
839 bResult = ((iResult == SQL_SUCCESS) || (iResult == SQL_SUCCESS_WITH_INFO));
840 if (bResult)
841 {
842 for(int i = 0; i < pResult->numColumns; i++)
843 {
844 free(pResult->data[i]);
845 pResult->data[i] = GetFieldData(pResult->sqlStatement, (short)i + 1);
846 }
847 }
848 else
849 {
850 pResult->noMoreRows = true;
851 }
852 }
853 return bResult;
854 }
855
856 /**
857 * Get field length from unbuffered query result
858 */
859 extern "C" LONG EXPORT DrvGetFieldLengthUnbuffered(MSSQL_UNBUFFERED_QUERY_RESULT *pResult, int iColumn)
860 {
861 LONG nLen = -1;
862 if (pResult != NULL)
863 {
864 if ((iColumn < pResult->numColumns) && (iColumn >= 0))
865 nLen = (pResult->data[iColumn] != NULL) ? (LONG)wcslen(pResult->data[iColumn]) : 0;
866 }
867 return nLen;
868 }
869
870 /**
871 * Get field from current row in unbuffered query result
872 */
873 extern "C" WCHAR EXPORT *DrvGetFieldUnbuffered(MSSQL_UNBUFFERED_QUERY_RESULT *pResult, int iColumn, WCHAR *pBuffer, int iBufSize)
874 {
875 // Check if we have valid result handle
876 if (pResult == NULL)
877 return NULL;
878
879 // Check if there are valid fetched row
880 if (pResult->noMoreRows)
881 return NULL;
882
883 if ((iColumn >= 0) && (iColumn < pResult->numColumns))
884 {
885 if (pResult->data[iColumn] != NULL)
886 {
887 wcsncpy(pBuffer, pResult->data[iColumn], iBufSize);
888 pBuffer[iBufSize - 1] = 0;
889 }
890 else
891 {
892 pBuffer[0] = 0;
893 }
894 }
895 else
896 {
897 pBuffer[0] = 0;
898 }
899 return pBuffer;
900 }
901
902 /**
903 * Get column count in unbuffered query result
904 */
905 extern "C" int EXPORT DrvGetColumnCountUnbuffered(MSSQL_UNBUFFERED_QUERY_RESULT *pResult)
906 {
907 return (pResult != NULL) ? pResult->numColumns : 0;
908 }
909
910 /**
911 * Get column name in unbuffered query result
912 */
913 extern "C" const char EXPORT *DrvGetColumnNameUnbuffered(MSSQL_UNBUFFERED_QUERY_RESULT *pResult, int column)
914 {
915 return ((pResult != NULL) && (column >= 0) && (column < pResult->numColumns)) ? pResult->columnNames[column] : NULL;
916 }
917
918 /**
919 * Destroy result of unbuffered query
920 */
921 extern "C" void EXPORT DrvFreeUnbufferedResult(MSSQL_UNBUFFERED_QUERY_RESULT *pResult)
922 {
923 if (pResult == NULL)
924 return;
925
926 if (pResult->isPrepared)
927 SQLCloseCursor(pResult->sqlStatement);
928 else
929 SQLFreeHandle(SQL_HANDLE_STMT, pResult->sqlStatement);
930 MutexUnlock(pResult->pConn->mutexQuery);
931 for(int i = 0; i < pResult->numColumns; i++)
932 {
933 free(pResult->data[i]);
934 free(pResult->columnNames[i]);
935 }
936 free(pResult->data);
937 free(pResult->columnNames);
938 free(pResult);
939 }
940
941 /**
942 * Begin transaction
943 */
944 extern "C" DWORD EXPORT DrvBegin(MSSQL_CONN *pConn)
945 {
946 SQLRETURN nRet;
947 DWORD dwResult;
948
949 if (pConn == NULL)
950 return DBERR_INVALID_HANDLE;
951
952 MutexLock(pConn->mutexQuery);
953 nRet = SQLSetConnectAttr(pConn->sqlConn, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0);
954 if ((nRet == SQL_SUCCESS) || (nRet == SQL_SUCCESS_WITH_INFO))
955 {
956 dwResult = DBERR_SUCCESS;
957 }
958 else
959 {
960 dwResult = GetSQLErrorInfo(SQL_HANDLE_DBC, pConn->sqlConn, NULL);
961 }
962 MutexUnlock(pConn->mutexQuery);
963 return dwResult;
964 }
965
966 /**
967 * Commit transaction
968 */
969 extern "C" DWORD EXPORT DrvCommit(MSSQL_CONN *pConn)
970 {
971 SQLRETURN nRet;
972
973 if (pConn == NULL)
974 return DBERR_INVALID_HANDLE;
975
976 MutexLock(pConn->mutexQuery);
977 nRet = SQLEndTran(SQL_HANDLE_DBC, pConn->sqlConn, SQL_COMMIT);
978 SQLSetConnectAttr(pConn->sqlConn, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
979 MutexUnlock(pConn->mutexQuery);
980 return ((nRet == SQL_SUCCESS) || (nRet == SQL_SUCCESS_WITH_INFO)) ? DBERR_SUCCESS : DBERR_OTHER_ERROR;
981 }
982
983 /**
984 * Rollback transaction
985 */
986 extern "C" DWORD EXPORT DrvRollback(MSSQL_CONN *pConn)
987 {
988 SQLRETURN nRet;
989
990 if (pConn == NULL)
991 return DBERR_INVALID_HANDLE;
992
993 MutexLock(pConn->mutexQuery);
994 nRet = SQLEndTran(SQL_HANDLE_DBC, pConn->sqlConn, SQL_ROLLBACK);
995 SQLSetConnectAttr(pConn->sqlConn, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
996 MutexUnlock(pConn->mutexQuery);
997 return ((nRet == SQL_SUCCESS) || (nRet == SQL_SUCCESS_WITH_INFO)) ? DBERR_SUCCESS : DBERR_OTHER_ERROR;
998 }
999
1000 /**
1001 * Check if table exist
1002 */
1003 extern "C" int EXPORT DrvIsTableExist(MSSQL_CONN *pConn, const WCHAR *name)
1004 {
1005 WCHAR query[256];
1006 swprintf(query, 256, L"SELECT count(*) FROM sysobjects WHERE xtype='U' AND upper(name)=upper('%ls')", name);
1007 DWORD error;
1008 WCHAR errorText[DBDRV_MAX_ERROR_TEXT];
1009 int rc = DBIsTableExist_Failure;
1010 MSSQL_QUERY_RESULT *hResult = (MSSQL_QUERY_RESULT *)DrvSelect(pConn, query, &error, errorText);
1011 if (hResult != NULL)
1012 {
1013 WCHAR buffer[64] = L"";
1014 DrvGetField(hResult, 0, 0, buffer, 64);
1015 rc = (wcstol(buffer, NULL, 10) > 0) ? DBIsTableExist_Found : DBIsTableExist_NotFound;
1016 DrvFreeResult(hResult);
1017 }
1018 return rc;
1019 }
1020
1021 /**
1022 * DLL Entry point
1023 */
1024 bool WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
1025 {
1026 if (dwReason == DLL_PROCESS_ATTACH)
1027 DisableThreadLibraryCalls(hInstance);
1028 return true;
1029 }