9bd19bfb6760ef2fae59b339d7d8c3dea60ea1e9
[public/netxms.git] / src / db / dbdrv / mysql / mysql.cpp
1 /*
2 ** MySQL Database Driver
3 ** Copyright (C) 2003-2015 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: mysql.cpp
20 **
21 **/
22
23 #include "mysqldrv.h"
24
25 DECLARE_DRIVER_HEADER("MYSQL")
26
27 /**
28 * Update error message from given source
29 */
30 static void UpdateErrorMessage(const char *source, WCHAR *errorText)
31 {
32 if (errorText != NULL)
33 {
34 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, source, -1, errorText, DBDRV_MAX_ERROR_TEXT);
35 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
36 RemoveTrailingCRLFW(errorText);
37 }
38 }
39
40 /**
41 * Update buffer length in DrvPrepareStringW
42 */
43 #define UPDATE_LENGTH \
44 len++; \
45 if (len >= bufferSize - 1) \
46 { \
47 bufferSize += 128; \
48 out = (WCHAR *)realloc(out, bufferSize * sizeof(WCHAR)); \
49 }
50
51 /**
52 * Prepare string for using in SQL query - enclose in quotes and escape as needed
53 * (wide string version)
54 */
55 extern "C" WCHAR EXPORT *DrvPrepareStringW(const WCHAR *str)
56 {
57 int len = (int)wcslen(str) + 3; // + two quotes and \0 at the end
58 int bufferSize = len + 128;
59 WCHAR *out = (WCHAR *)malloc(bufferSize * sizeof(WCHAR));
60 out[0] = _T('\'');
61
62 const WCHAR *src = str;
63 int outPos;
64 for(outPos = 1; *src != 0; src++)
65 {
66 switch(*src)
67 {
68 case L'\'':
69 out[outPos++] = L'\'';
70 out[outPos++] = L'\'';
71 UPDATE_LENGTH;
72 break;
73 case L'\r':
74 out[outPos++] = L'\\';
75 out[outPos++] = L'\r';
76 UPDATE_LENGTH;
77 break;
78 case L'\n':
79 out[outPos++] = L'\\';
80 out[outPos++] = L'\n';
81 UPDATE_LENGTH;
82 break;
83 case L'\b':
84 out[outPos++] = L'\\';
85 out[outPos++] = L'\b';
86 UPDATE_LENGTH;
87 break;
88 case L'\t':
89 out[outPos++] = L'\\';
90 out[outPos++] = L'\t';
91 UPDATE_LENGTH;
92 break;
93 case 26:
94 out[outPos++] = L'\\';
95 out[outPos++] = L'Z';
96 break;
97 case L'\\':
98 out[outPos++] = L'\\';
99 out[outPos++] = L'\\';
100 UPDATE_LENGTH;
101 break;
102 default:
103 out[outPos++] = *src;
104 break;
105 }
106 }
107 out[outPos++] = L'\'';
108 out[outPos++] = 0;
109
110 return out;
111 }
112
113 #undef UPDATE_LENGTH
114
115 /**
116 * Update buffer length in DrvPrepareStringA
117 */
118 #define UPDATE_LENGTH \
119 len++; \
120 if (len >= bufferSize - 1) \
121 { \
122 bufferSize += 128; \
123 out = (char *)realloc(out, bufferSize); \
124 }
125
126 /**
127 * Prepare string for using in SQL query - enclose in quotes and escape as needed
128 * (multibyte string version)
129 */
130 extern "C" char EXPORT *DrvPrepareStringA(const char *str)
131 {
132 int len = (int)strlen(str) + 3; // + two quotes and \0 at the end
133 int bufferSize = len + 128;
134 char *out = (char *)malloc(bufferSize);
135 out[0] = _T('\'');
136
137 const char *src = str;
138 int outPos;
139 for(outPos = 1; *src != 0; src++)
140 {
141 switch(*src)
142 {
143 case '\'':
144 out[outPos++] = '\'';
145 out[outPos++] = '\'';
146 UPDATE_LENGTH;
147 break;
148 case '\r':
149 out[outPos++] = '\\';
150 out[outPos++] = '\r';
151 UPDATE_LENGTH;
152 break;
153 case '\n':
154 out[outPos++] = '\\';
155 out[outPos++] = '\n';
156 UPDATE_LENGTH;
157 break;
158 case '\b':
159 out[outPos++] = '\\';
160 out[outPos++] = '\b';
161 UPDATE_LENGTH;
162 break;
163 case '\t':
164 out[outPos++] = '\\';
165 out[outPos++] = '\t';
166 UPDATE_LENGTH;
167 break;
168 case 26:
169 out[outPos++] = '\\';
170 out[outPos++] = 'Z';
171 break;
172 case '\\':
173 out[outPos++] = '\\';
174 out[outPos++] = '\\';
175 UPDATE_LENGTH;
176 break;
177 default:
178 out[outPos++] = *src;
179 break;
180 }
181 }
182 out[outPos++] = '\'';
183 out[outPos++] = 0;
184
185 return out;
186 }
187
188 #undef UPDATE_LENGTH
189
190 /**
191 * Initialize driver
192 */
193 extern "C" bool EXPORT DrvInit(const char *cmdLine)
194 {
195 return mysql_library_init(0, NULL, NULL) == 0;
196 }
197
198 /**
199 * Unload handler
200 */
201 extern "C" void EXPORT DrvUnload()
202 {
203 mysql_library_end();
204 }
205
206 /**
207 * Connect to database
208 */
209 extern "C" DBDRV_CONNECTION EXPORT DrvConnect(const char *szHost, const char *szLogin, const char *szPassword,
210 const char *szDatabase, const char *schema, WCHAR *errorText)
211 {
212 MYSQL *pMySQL;
213 MYSQL_CONN *pConn;
214 const char *pHost = szHost;
215 const char *pSocket = NULL;
216
217 pMySQL = mysql_init(NULL);
218 if (pMySQL == NULL)
219 {
220 wcscpy(errorText, L"Insufficient memory to allocate connection handle");
221 return NULL;
222 }
223
224 pSocket = strstr(szHost, "socket:");
225 if (pSocket != NULL)
226 {
227 pHost = NULL;
228 pSocket += 7;
229 }
230
231 if (!mysql_real_connect(
232 pMySQL, // MYSQL *
233 pHost, // host
234 szLogin[0] == 0 ? NULL : szLogin, // user
235 (szPassword[0] == 0 || szLogin[0] == 0) ? NULL : szPassword, // pass
236 szDatabase, // DB Name
237 0, // use default port
238 pSocket, // char * - unix socket
239 0 // flags
240 ))
241 {
242 UpdateErrorMessage(mysql_error(pMySQL), errorText);
243 mysql_close(pMySQL);
244 return NULL;
245 }
246
247 pConn = (MYSQL_CONN *)malloc(sizeof(MYSQL_CONN));
248 pConn->pMySQL = pMySQL;
249 pConn->mutexQueryLock = MutexCreate();
250
251 // Switch to UTF-8 encoding
252 mysql_set_character_set(pMySQL, "utf8");
253
254 return (DBDRV_CONNECTION)pConn;
255 }
256
257 /**
258 * Disconnect from database
259 */
260 extern "C" void EXPORT DrvDisconnect(MYSQL_CONN *pConn)
261 {
262 if (pConn != NULL)
263 {
264 mysql_close(pConn->pMySQL);
265 MutexDestroy(pConn->mutexQueryLock);
266 free(pConn);
267 }
268 }
269
270 /**
271 * Prepare statement
272 */
273 extern "C" DBDRV_STATEMENT EXPORT DrvPrepare(MYSQL_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
274 {
275 MYSQL_STATEMENT *result = NULL;
276
277 MutexLock(pConn->mutexQueryLock);
278 MYSQL_STMT *stmt = mysql_stmt_init(pConn->pMySQL);
279 if (stmt != NULL)
280 {
281 char *pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
282 int rc = mysql_stmt_prepare(stmt, pszQueryUTF8, (unsigned long)strlen(pszQueryUTF8));
283 if (rc == 0)
284 {
285 result = (MYSQL_STATEMENT *)malloc(sizeof(MYSQL_STATEMENT));
286 result->connection = pConn;
287 result->statement = stmt;
288 result->paramCount = (int)mysql_stmt_param_count(stmt);
289 result->bindings = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * result->paramCount);
290 memset(result->bindings, 0, sizeof(MYSQL_BIND) * result->paramCount);
291 result->lengthFields = (unsigned long *)malloc(sizeof(unsigned long) * result->paramCount);
292 memset(result->lengthFields, 0, sizeof(unsigned long) * result->paramCount);
293 result->buffers = new Array(result->paramCount, 16, true);
294 *pdwError = DBERR_SUCCESS;
295 }
296 else
297 {
298 int nErr = mysql_errno(pConn->pMySQL);
299 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR)
300 {
301 *pdwError = DBERR_CONNECTION_LOST;
302 }
303 else
304 {
305 *pdwError = DBERR_OTHER_ERROR;
306 }
307 UpdateErrorMessage(mysql_stmt_error(stmt), errorText);
308 mysql_stmt_close(stmt);
309 }
310 free(pszQueryUTF8);
311 }
312 else
313 {
314 *pdwError = DBERR_OTHER_ERROR;
315 UpdateErrorMessage("Call to mysql_stmt_init failed", errorText);
316 }
317 MutexUnlock(pConn->mutexQueryLock);
318 return result;
319 }
320
321 /**
322 * Bind parameter to prepared statement
323 */
324 extern "C" void EXPORT DrvBind(MYSQL_STATEMENT *hStmt, int pos, int sqlType, int cType, void *buffer, int allocType)
325 {
326 static size_t bufferSize[] = { 0, sizeof(INT32), sizeof(UINT32), sizeof(INT64), sizeof(UINT64), sizeof(double) };
327
328 if ((pos < 1) || (pos > hStmt->paramCount))
329 return;
330 MYSQL_BIND *b = &hStmt->bindings[pos - 1];
331
332 if (cType == DB_CTYPE_STRING)
333 {
334 b->buffer = UTF8StringFromWideString((WCHAR *)buffer);
335 hStmt->buffers->add(b->buffer);
336 if (allocType == DB_BIND_DYNAMIC)
337 free(buffer);
338 b->buffer_length = (unsigned long)strlen((char *)b->buffer) + 1;
339 hStmt->lengthFields[pos - 1] = b->buffer_length - 1;
340 b->length = &hStmt->lengthFields[pos - 1];
341 b->buffer_type = MYSQL_TYPE_STRING;
342 }
343 else
344 {
345 switch(allocType)
346 {
347 case DB_BIND_STATIC:
348 b->buffer = buffer;
349 break;
350 case DB_BIND_DYNAMIC:
351 b->buffer = buffer;
352 hStmt->buffers->add(buffer);
353 break;
354 case DB_BIND_TRANSIENT:
355 b->buffer = nx_memdup(buffer, bufferSize[cType]);
356 hStmt->buffers->add(b->buffer);
357 break;
358 default:
359 return; // Invalid call
360 }
361
362 switch(cType)
363 {
364 case DB_CTYPE_UINT32:
365 b->is_unsigned = true;
366 /* no break */
367 case DB_CTYPE_INT32:
368 b->buffer_type = MYSQL_TYPE_LONG;
369 break;
370 case DB_CTYPE_UINT64:
371 b->is_unsigned = true;
372 /* no break */
373 case DB_CTYPE_INT64:
374 b->buffer_type = MYSQL_TYPE_LONGLONG;
375 break;
376 case DB_CTYPE_DOUBLE:
377 b->buffer_type = MYSQL_TYPE_DOUBLE;
378 break;
379 }
380 }
381 }
382
383 /**
384 * Execute prepared statement
385 */
386 extern "C" DWORD EXPORT DrvExecute(MYSQL_CONN *pConn, MYSQL_STATEMENT *hStmt, WCHAR *errorText)
387 {
388 DWORD dwResult;
389
390 MutexLock(pConn->mutexQueryLock);
391
392 if (mysql_stmt_bind_param(hStmt->statement, hStmt->bindings) == 0)
393 {
394 if (mysql_stmt_execute(hStmt->statement) == 0)
395 {
396 dwResult = DBERR_SUCCESS;
397 }
398 else
399 {
400 int nErr = mysql_errno(pConn->pMySQL);
401 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR)
402 {
403 dwResult = DBERR_CONNECTION_LOST;
404 }
405 else
406 {
407 dwResult = DBERR_OTHER_ERROR;
408 }
409 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
410 }
411 }
412 else
413 {
414 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
415 dwResult = DBERR_OTHER_ERROR;
416 }
417
418 MutexUnlock(pConn->mutexQueryLock);
419 return dwResult;
420 }
421
422 /**
423 * Destroy prepared statement
424 */
425 extern "C" void EXPORT DrvFreeStatement(MYSQL_STATEMENT *hStmt)
426 {
427 if (hStmt == NULL)
428 return;
429
430 MutexLock(hStmt->connection->mutexQueryLock);
431 mysql_stmt_close(hStmt->statement);
432 MutexUnlock(hStmt->connection->mutexQueryLock);
433 delete hStmt->buffers;
434 safe_free(hStmt->bindings);
435 safe_free(hStmt->lengthFields);
436 free(hStmt);
437 }
438
439 /**
440 * Perform actual non-SELECT query
441 */
442 static DWORD DrvQueryInternal(MYSQL_CONN *pConn, const char *pszQuery, WCHAR *errorText)
443 {
444 DWORD dwRet = DBERR_INVALID_HANDLE;
445
446 MutexLock(pConn->mutexQueryLock);
447 if (mysql_query(pConn->pMySQL, pszQuery) == 0)
448 {
449 dwRet = DBERR_SUCCESS;
450 if (errorText != NULL)
451 *errorText = 0;
452 }
453 else
454 {
455 int nErr = mysql_errno(pConn->pMySQL);
456 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR) // CR_SERVER_GONE_ERROR - ???
457 {
458 dwRet = DBERR_CONNECTION_LOST;
459 }
460 else
461 {
462 dwRet = DBERR_OTHER_ERROR;
463 }
464 UpdateErrorMessage(mysql_error(pConn->pMySQL), errorText);
465 }
466
467 MutexUnlock(pConn->mutexQueryLock);
468 return dwRet;
469 }
470
471 /**
472 * Perform non-SELECT query
473 */
474 extern "C" DWORD EXPORT DrvQuery(MYSQL_CONN *pConn, WCHAR *pwszQuery, WCHAR *errorText)
475 {
476 DWORD dwRet;
477 char *pszQueryUTF8;
478
479 pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
480 dwRet = DrvQueryInternal(pConn, pszQueryUTF8, errorText);
481 free(pszQueryUTF8);
482 return dwRet;
483 }
484
485 /**
486 * Perform SELECT query
487 */
488 extern "C" DBDRV_RESULT EXPORT DrvSelect(MYSQL_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
489 {
490 MYSQL_RESULT *result = NULL;
491 char *pszQueryUTF8;
492
493 if (pConn == NULL)
494 {
495 *pdwError = DBERR_INVALID_HANDLE;
496 return NULL;
497 }
498
499 pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
500 MutexLock(pConn->mutexQueryLock);
501 if (mysql_query(pConn->pMySQL, pszQueryUTF8) == 0)
502 {
503 result = (MYSQL_RESULT *)malloc(sizeof(MYSQL_RESULT));
504 result->connection = pConn;
505 result->isPreparedStatement = false;
506 result->resultSet = mysql_store_result(pConn->pMySQL);
507 *pdwError = DBERR_SUCCESS;
508 if (errorText != NULL)
509 *errorText = 0;
510 }
511 else
512 {
513 int nErr = mysql_errno(pConn->pMySQL);
514 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR) // CR_SERVER_GONE_ERROR - ???
515 {
516 *pdwError = DBERR_CONNECTION_LOST;
517 }
518 else
519 {
520 *pdwError = DBERR_OTHER_ERROR;
521 }
522 UpdateErrorMessage(mysql_error(pConn->pMySQL), errorText);
523 }
524
525 MutexUnlock(pConn->mutexQueryLock);
526 free(pszQueryUTF8);
527 return result;
528 }
529
530 /**
531 * Perform SELECT query using prepared statement
532 */
533 extern "C" DBDRV_RESULT EXPORT DrvSelectPrepared(MYSQL_CONN *pConn, MYSQL_STATEMENT *hStmt, DWORD *pdwError, WCHAR *errorText)
534 {
535 MYSQL_RESULT *result = NULL;
536
537 if (pConn == NULL)
538 {
539 *pdwError = DBERR_INVALID_HANDLE;
540 return NULL;
541 }
542
543 MutexLock(pConn->mutexQueryLock);
544
545 if (mysql_stmt_bind_param(hStmt->statement, hStmt->bindings) == 0)
546 {
547 if (mysql_stmt_execute(hStmt->statement) == 0)
548 {
549 result = (MYSQL_RESULT *)malloc(sizeof(MYSQL_RESULT));
550 result->connection = pConn;
551 result->isPreparedStatement = true;
552 result->statement = hStmt->statement;
553 result->resultSet = mysql_stmt_result_metadata(hStmt->statement);
554 if (result->resultSet != NULL)
555 {
556 result->numColumns = mysql_num_fields(result->resultSet);
557
558 result->lengthFields = (unsigned long *)malloc(sizeof(unsigned long) * result->numColumns);
559 memset(result->lengthFields, 0, sizeof(unsigned long) * result->numColumns);
560
561 result->bindings = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * result->numColumns);
562 memset(result->bindings, 0, sizeof(MYSQL_BIND) * result->numColumns);
563 for(int i = 0; i < result->numColumns; i++)
564 {
565 result->bindings[i].buffer_type = MYSQL_TYPE_STRING;
566 result->bindings[i].length = &result->lengthFields[i];
567 }
568
569 mysql_stmt_bind_result(hStmt->statement, result->bindings);
570
571 if (mysql_stmt_store_result(hStmt->statement) == 0)
572 {
573 result->numRows = (int)mysql_stmt_num_rows(hStmt->statement);
574 result->currentRow = -1;
575 *pdwError = DBERR_SUCCESS;
576 }
577 else
578 {
579 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
580 *pdwError = DBERR_OTHER_ERROR;
581 mysql_free_result(result->resultSet);
582 free(result->bindings);
583 free(result->lengthFields);
584 free(result);
585 result = NULL;
586 }
587 }
588 else
589 {
590 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
591 *pdwError = DBERR_OTHER_ERROR;
592 free(result);
593 result = NULL;
594 }
595 }
596 else
597 {
598 int nErr = mysql_errno(pConn->pMySQL);
599 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR)
600 {
601 *pdwError = DBERR_CONNECTION_LOST;
602 }
603 else
604 {
605 *pdwError = DBERR_OTHER_ERROR;
606 }
607 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
608 }
609 }
610 else
611 {
612 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
613 *pdwError = DBERR_OTHER_ERROR;
614 }
615
616 MutexUnlock(pConn->mutexQueryLock);
617 return result;
618 }
619
620 /**
621 * Get field length from result
622 */
623 extern "C" LONG EXPORT DrvGetFieldLength(MYSQL_RESULT *hResult, int iRow, int iColumn)
624 {
625 if (hResult->isPreparedStatement)
626 {
627 if ((iRow < 0) || (iRow >= hResult->numRows) ||
628 (iColumn < 0) || (iColumn >= hResult->numColumns))
629 return -1;
630
631 if (hResult->currentRow != iRow)
632 {
633 MutexLock(hResult->connection->mutexQueryLock);
634 mysql_stmt_data_seek(hResult->statement, iRow);
635 mysql_stmt_fetch(hResult->statement);
636 hResult->currentRow = iRow;
637 MutexUnlock(hResult->connection->mutexQueryLock);
638 }
639 return (LONG)hResult->lengthFields[iColumn];
640 }
641 else
642 {
643 mysql_data_seek(hResult->resultSet, iRow);
644 MYSQL_ROW row = mysql_fetch_row(hResult->resultSet);
645 return (row == NULL) ? (LONG)-1 : ((row[iColumn] == NULL) ? -1 : (LONG)strlen(row[iColumn]));
646 }
647 }
648
649 /**
650 * Get field value from result - UNICODE and UTF8 implementation
651 */
652 static void *GetFieldInternal(MYSQL_RESULT *hResult, int iRow, int iColumn, void *pBuffer, int nBufSize, bool utf8)
653 {
654 void *pRet = NULL;
655
656 if (hResult->isPreparedStatement)
657 {
658 if ((iRow < 0) || (iRow >= hResult->numRows) ||
659 (iColumn < 0) || (iColumn >= hResult->numColumns))
660 return NULL;
661
662 MutexLock(hResult->connection->mutexQueryLock);
663 if (hResult->currentRow != iRow)
664 {
665 mysql_stmt_data_seek(hResult->statement, iRow);
666 mysql_stmt_fetch(hResult->statement);
667 hResult->currentRow = iRow;
668 }
669
670 MYSQL_BIND b;
671 unsigned long l = 0;
672 my_bool isNull;
673
674 memset(&b, 0, sizeof(MYSQL_BIND));
675 #if HAVE_ALLOCA
676 b.buffer = alloca(hResult->lengthFields[iColumn] + 1);
677 #else
678 b.buffer = malloc(hResult->lengthFields[iColumn] + 1);
679 #endif
680 b.buffer_length = hResult->lengthFields[iColumn] + 1;
681 b.buffer_type = MYSQL_TYPE_STRING;
682 b.length = &l;
683 b.is_null = &isNull;
684 int rc = mysql_stmt_fetch_column(hResult->statement, &b, iColumn, 0);
685 if (rc == 0)
686 {
687 if (!isNull)
688 {
689 ((char *)b.buffer)[l] = 0;
690 if (utf8)
691 {
692 strncpy((char *)pBuffer, (char *)b.buffer, nBufSize);
693 ((char *)pBuffer)[nBufSize - 1] = 0;
694 }
695 else
696 {
697 MultiByteToWideChar(CP_UTF8, 0, (char *)b.buffer, -1, (WCHAR *)pBuffer, nBufSize);
698 ((WCHAR *)pBuffer)[nBufSize - 1] = 0;
699 }
700 }
701 else
702 {
703 if (utf8)
704 *((char *)pBuffer) = 0;
705 else
706 *((WCHAR *)pBuffer) = 0;
707 }
708 pRet = pBuffer;
709 }
710 MutexUnlock(hResult->connection->mutexQueryLock);
711 #if !HAVE_ALLOCA
712 free(b.buffer);
713 #endif
714 }
715 else
716 {
717 mysql_data_seek(hResult->resultSet, iRow);
718 MYSQL_ROW row = mysql_fetch_row(hResult->resultSet);
719 if (row != NULL)
720 {
721 if (row[iColumn] != NULL)
722 {
723 if (utf8)
724 {
725 strncpy((char *)pBuffer, row[iColumn], nBufSize);
726 ((char *)pBuffer)[nBufSize - 1] = 0;
727 }
728 else
729 {
730 MultiByteToWideChar(CP_UTF8, 0, row[iColumn], -1, (WCHAR *)pBuffer, nBufSize);
731 ((WCHAR *)pBuffer)[nBufSize - 1] = 0;
732 }
733 pRet = pBuffer;
734 }
735 }
736 }
737 return pRet;
738 }
739
740 /**
741 * Get field value from result
742 */
743 extern "C" WCHAR EXPORT *DrvGetField(MYSQL_RESULT *hResult, int iRow, int iColumn, WCHAR *pBuffer, int nBufSize)
744 {
745 return (WCHAR *)GetFieldInternal(hResult, iRow, iColumn, pBuffer, nBufSize, false);
746 }
747
748 /**
749 * Get field value from result as UTF8 string
750 */
751 extern "C" char EXPORT *DrvGetFieldUTF8(MYSQL_RESULT *hResult, int iRow, int iColumn, char *pBuffer, int nBufSize)
752 {
753 return (char *)GetFieldInternal(hResult, iRow, iColumn, pBuffer, nBufSize, true);
754 }
755
756 /**
757 * Get number of rows in result
758 */
759 extern "C" int EXPORT DrvGetNumRows(MYSQL_RESULT *hResult)
760 {
761 return (hResult != NULL) ? (int)(hResult->isPreparedStatement ? hResult->numRows : mysql_num_rows(hResult->resultSet)) : 0;
762 }
763
764 /**
765 * Get column count in query result
766 */
767 extern "C" int EXPORT DrvGetColumnCount(MYSQL_RESULT *hResult)
768 {
769 return (hResult != NULL) ? (int)mysql_num_fields(hResult->resultSet) : 0;
770 }
771
772 /**
773 * Get column name in query result
774 */
775 extern "C" const char EXPORT *DrvGetColumnName(MYSQL_RESULT *hResult, int column)
776 {
777 MYSQL_FIELD *field;
778
779 if (hResult == NULL)
780 return NULL;
781
782 field = mysql_fetch_field_direct(hResult->resultSet, column);
783 return (field != NULL) ? field->name : NULL;
784 }
785
786 /**
787 * Free SELECT results
788 */
789 extern "C" void EXPORT DrvFreeResult(MYSQL_RESULT *hResult)
790 {
791 if (hResult == NULL)
792 return;
793
794 if (hResult->isPreparedStatement)
795 {
796 safe_free(hResult->bindings);
797 safe_free(hResult->lengthFields);
798 }
799
800 mysql_free_result(hResult->resultSet);
801 free(hResult);
802 }
803
804 /**
805 * Perform unbuffered SELECT query
806 */
807 extern "C" DBDRV_UNBUFFERED_RESULT EXPORT DrvSelectUnbuffered(MYSQL_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
808 {
809 MYSQL_UNBUFFERED_RESULT *pResult = NULL;
810 char *pszQueryUTF8;
811
812 if (pConn == NULL)
813 {
814 *pdwError = DBERR_INVALID_HANDLE;
815 return NULL;
816 }
817
818 pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
819 MutexLock(pConn->mutexQueryLock);
820 if (mysql_query(pConn->pMySQL, pszQueryUTF8) == 0)
821 {
822 pResult = (MYSQL_UNBUFFERED_RESULT *)malloc(sizeof(MYSQL_UNBUFFERED_RESULT));
823 pResult->connection = pConn;
824 pResult->isPreparedStatement = false;
825 pResult->resultSet = mysql_use_result(pConn->pMySQL);
826 if (pResult->resultSet != NULL)
827 {
828 pResult->noMoreRows = false;
829 pResult->numColumns = mysql_num_fields(pResult->resultSet);
830 pResult->pCurrRow = NULL;
831 pResult->lengthFields = (unsigned long *)malloc(sizeof(unsigned long) * pResult->numColumns);
832 pResult->bindings = NULL;
833 }
834 else
835 {
836 free(pResult);
837 pResult = NULL;
838 }
839
840 *pdwError = DBERR_SUCCESS;
841 if (errorText != NULL)
842 *errorText = 0;
843 }
844 else
845 {
846 int nErr = mysql_errno(pConn->pMySQL);
847 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR) // CR_SERVER_GONE_ERROR - ???
848 {
849 *pdwError = DBERR_CONNECTION_LOST;
850 }
851 else
852 {
853 *pdwError = DBERR_OTHER_ERROR;
854 }
855
856 if (errorText != NULL)
857 {
858 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, mysql_error(pConn->pMySQL), -1, errorText, DBDRV_MAX_ERROR_TEXT);
859 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
860 RemoveTrailingCRLFW(errorText);
861 }
862 }
863
864 if (pResult == NULL)
865 {
866 MutexUnlock(pConn->mutexQueryLock);
867 }
868 free(pszQueryUTF8);
869
870 return pResult;
871 }
872
873 /**
874 * Perform unbuffered SELECT query using prepared statement
875 */
876 extern "C" DBDRV_RESULT EXPORT DrvSelectPreparedUnbuffered(MYSQL_CONN *pConn, MYSQL_STATEMENT *hStmt, DWORD *pdwError, WCHAR *errorText)
877 {
878 MYSQL_UNBUFFERED_RESULT *result = NULL;
879
880 MutexLock(pConn->mutexQueryLock);
881
882 if (mysql_stmt_bind_param(hStmt->statement, hStmt->bindings) == 0)
883 {
884 if (mysql_stmt_execute(hStmt->statement) == 0)
885 {
886 result = (MYSQL_UNBUFFERED_RESULT *)malloc(sizeof(MYSQL_UNBUFFERED_RESULT));
887 result->connection = pConn;
888 result->isPreparedStatement = true;
889 result->statement = hStmt->statement;
890 result->resultSet = mysql_stmt_result_metadata(hStmt->statement);
891 if (result->resultSet != NULL)
892 {
893 result->noMoreRows = false;
894 result->numColumns = mysql_num_fields(result->resultSet);
895 result->pCurrRow = NULL;
896
897 result->lengthFields = (unsigned long *)malloc(sizeof(unsigned long) * result->numColumns);
898 memset(result->lengthFields, 0, sizeof(unsigned long) * result->numColumns);
899
900 result->bindings = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * result->numColumns);
901 memset(result->bindings, 0, sizeof(MYSQL_BIND) * result->numColumns);
902 for(int i = 0; i < result->numColumns; i++)
903 {
904 result->bindings[i].buffer_type = MYSQL_TYPE_STRING;
905 result->bindings[i].length = &result->lengthFields[i];
906 }
907
908 mysql_stmt_bind_result(hStmt->statement, result->bindings);
909 *pdwError = DBERR_SUCCESS;
910 }
911 else
912 {
913 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
914 *pdwError = DBERR_OTHER_ERROR;
915 free(result);
916 result = NULL;
917 }
918 }
919 else
920 {
921 int nErr = mysql_errno(pConn->pMySQL);
922 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR)
923 {
924 *pdwError = DBERR_CONNECTION_LOST;
925 }
926 else
927 {
928 *pdwError = DBERR_OTHER_ERROR;
929 }
930 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
931 }
932 }
933 else
934 {
935 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
936 *pdwError = DBERR_OTHER_ERROR;
937 }
938
939 if (result == NULL)
940 {
941 MutexUnlock(pConn->mutexQueryLock);
942 }
943 return result;
944 }
945
946 /**
947 * Fetch next result line from asynchronous SELECT results
948 */
949 extern "C" bool EXPORT DrvFetch(MYSQL_UNBUFFERED_RESULT *result)
950 {
951 if ((result == NULL) || (result->noMoreRows))
952 return false;
953
954 bool success = true;
955
956 if (result->isPreparedStatement)
957 {
958 int rc = mysql_stmt_fetch(result->statement);
959 if ((rc != 0) && (rc != MYSQL_DATA_TRUNCATED))
960 {
961 result->noMoreRows = true;
962 success = false;
963 MutexUnlock(result->connection->mutexQueryLock);
964 }
965 }
966 else
967 {
968 // Try to fetch next row from server
969 result->pCurrRow = mysql_fetch_row(result->resultSet);
970 if (result->pCurrRow == NULL)
971 {
972 result->noMoreRows = true;
973 success = false;
974 MutexUnlock(result->connection->mutexQueryLock);
975 }
976 else
977 {
978 unsigned long *pLen;
979
980 // Get column lengths for current row
981 pLen = mysql_fetch_lengths(result->resultSet);
982 if (pLen != NULL)
983 {
984 memcpy(result->lengthFields, pLen, sizeof(unsigned long) * result->numColumns);
985 }
986 else
987 {
988 memset(result->lengthFields, 0, sizeof(unsigned long) * result->numColumns);
989 }
990 }
991 }
992 return success;
993 }
994
995 /**
996 * Get field length from async query result result
997 */
998 extern "C" LONG EXPORT DrvGetFieldLengthUnbuffered(MYSQL_UNBUFFERED_RESULT *hResult, int iColumn)
999 {
1000 // Check if we have valid result handle
1001 if (hResult == NULL)
1002 return 0;
1003
1004 // Check if there are valid fetched row
1005 if (hResult->noMoreRows || ((hResult->pCurrRow == NULL) && !hResult->isPreparedStatement))
1006 return 0;
1007
1008 // Check if column number is valid
1009 if ((iColumn < 0) || (iColumn >= hResult->numColumns))
1010 return 0;
1011
1012 return hResult->lengthFields[iColumn];
1013 }
1014
1015 /**
1016 * Get field from current row in async query result
1017 */
1018 extern "C" WCHAR EXPORT *DrvGetFieldUnbuffered(MYSQL_UNBUFFERED_RESULT *hResult, int iColumn, WCHAR *pBuffer, int iBufSize)
1019 {
1020 // Check if we have valid result handle
1021 if (hResult == NULL)
1022 return NULL;
1023
1024 // Check if there are valid fetched row
1025 if ((hResult->noMoreRows) || ((hResult->pCurrRow == NULL) && !hResult->isPreparedStatement))
1026 return NULL;
1027
1028 // Check if column number is valid
1029 if ((iColumn < 0) || (iColumn >= hResult->numColumns))
1030 return NULL;
1031
1032 // Now get column data
1033 WCHAR *value = NULL;
1034 if (hResult->isPreparedStatement)
1035 {
1036 MYSQL_BIND b;
1037 unsigned long l = 0;
1038 my_bool isNull;
1039
1040 memset(&b, 0, sizeof(MYSQL_BIND));
1041 #if HAVE_ALLOCA
1042 b.buffer = alloca(hResult->lengthFields[iColumn] + 1);
1043 #else
1044 b.buffer = malloc(hResult->lengthFields[iColumn] + 1);
1045 #endif
1046 b.buffer_length = hResult->lengthFields[iColumn] + 1;
1047 b.buffer_type = MYSQL_TYPE_STRING;
1048 b.length = &l;
1049 b.is_null = &isNull;
1050 int rc = mysql_stmt_fetch_column(hResult->statement, &b, iColumn, 0);
1051 if (rc == 0)
1052 {
1053 if (!isNull)
1054 {
1055 ((char *)b.buffer)[l] = 0;
1056 MultiByteToWideChar(CP_UTF8, 0, (char *)b.buffer, -1, (WCHAR *)pBuffer, iBufSize);
1057 ((WCHAR *)pBuffer)[iBufSize - 1] = 0;
1058 }
1059 else
1060 {
1061 *((WCHAR *)pBuffer) = 0;
1062 }
1063 value = pBuffer;
1064 }
1065 #if !HAVE_ALLOCA
1066 free(b.buffer);
1067 #endif
1068 }
1069 else
1070 {
1071 int iLen = min((int)hResult->lengthFields[iColumn], iBufSize - 1);
1072 if (iLen > 0)
1073 {
1074 MultiByteToWideChar(CP_UTF8, 0, hResult->pCurrRow[iColumn], iLen, pBuffer, iBufSize);
1075 }
1076 pBuffer[iLen] = 0;
1077 value = pBuffer;
1078 }
1079 return value;
1080 }
1081
1082 /**
1083 * Get column count in async query result
1084 */
1085 extern "C" int EXPORT DrvGetColumnCountUnbuffered(MYSQL_UNBUFFERED_RESULT *hResult)
1086 {
1087 return (hResult != NULL) ? hResult->numColumns : 0;
1088 }
1089
1090 /**
1091 * Get column name in async query result
1092 */
1093 extern "C" const char EXPORT *DrvGetColumnNameUnbuffered(MYSQL_UNBUFFERED_RESULT *hResult, int column)
1094 {
1095 MYSQL_FIELD *field;
1096
1097 if ((hResult == NULL) || (hResult->resultSet == NULL))
1098 return NULL;
1099
1100 field = mysql_fetch_field_direct(hResult->resultSet, column);
1101 return (field != NULL) ? field->name : NULL;
1102 }
1103
1104 /**
1105 * Destroy result of async query
1106 */
1107 extern "C" void EXPORT DrvFreeUnbufferedResult(MYSQL_UNBUFFERED_RESULT *hResult)
1108 {
1109 if (hResult == NULL)
1110 return;
1111
1112 // Check if all result rows fetched
1113 if (!hResult->noMoreRows)
1114 {
1115 // Fetch remaining rows
1116 if (!hResult->isPreparedStatement)
1117 {
1118 while(mysql_fetch_row(hResult->resultSet) != NULL);
1119 }
1120
1121 // Now we are ready for next query, so unlock query mutex
1122 MutexUnlock(hResult->connection->mutexQueryLock);
1123 }
1124
1125 // Free allocated memory
1126 mysql_free_result(hResult->resultSet);
1127 free(hResult->lengthFields);
1128 free(hResult->bindings);
1129 free(hResult);
1130 }
1131
1132 /**
1133 * Begin transaction
1134 */
1135 extern "C" DWORD EXPORT DrvBegin(MYSQL_CONN *pConn)
1136 {
1137 return DrvQueryInternal(pConn, "BEGIN", NULL);
1138 }
1139
1140 /**
1141 * Commit transaction
1142 */
1143 extern "C" DWORD EXPORT DrvCommit(MYSQL_CONN *pConn)
1144 {
1145 return DrvQueryInternal(pConn, "COMMIT", NULL);
1146 }
1147
1148 /**
1149 * Rollback transaction
1150 */
1151 extern "C" DWORD EXPORT DrvRollback(MYSQL_CONN *pConn)
1152 {
1153 return DrvQueryInternal(pConn, "ROLLBACK", NULL);
1154 }
1155
1156 /**
1157 * Check if table exist
1158 */
1159 extern "C" int EXPORT DrvIsTableExist(MYSQL_CONN *pConn, const WCHAR *name)
1160 {
1161 WCHAR query[256], lname[256];
1162 wcsncpy(lname, name, 256);
1163 wcslwr(lname);
1164 swprintf(query, 256, L"SHOW TABLES LIKE '%ls'", lname);
1165 DWORD error;
1166 WCHAR errorText[DBDRV_MAX_ERROR_TEXT];
1167 int rc = DBIsTableExist_Failure;
1168 MYSQL_RESULT *hResult = (MYSQL_RESULT *)DrvSelect(pConn, query, &error, errorText);
1169 if (hResult != NULL)
1170 {
1171 rc = (DrvGetNumRows(hResult) > 0) ? DBIsTableExist_Found : DBIsTableExist_NotFound;
1172 DrvFreeResult(hResult);
1173 }
1174 return rc;
1175 }
1176
1177 #ifdef _WIN32
1178
1179 /**
1180 * DLL Entry point
1181 */
1182 bool WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
1183 {
1184 if (dwReason == DLL_PROCESS_ATTACH)
1185 DisableThreadLibraryCalls(hInstance);
1186 return true;
1187 }
1188
1189 #endif