implemented DB driver call DrvGetFieldUnbufferedUTF8 (for databases with native UTF...
[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), 0 };
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 if (cType == DB_CTYPE_UTF8_STRING)
344 {
345 b->buffer = (allocType == DB_BIND_DYNAMIC) ? buffer : strdup((char *)buffer);
346 hStmt->buffers->add(b->buffer);
347 b->buffer_length = (unsigned long)strlen((char *)b->buffer) + 1;
348 hStmt->lengthFields[pos - 1] = b->buffer_length - 1;
349 b->length = &hStmt->lengthFields[pos - 1];
350 b->buffer_type = MYSQL_TYPE_STRING;
351 }
352 else
353 {
354 switch(allocType)
355 {
356 case DB_BIND_STATIC:
357 b->buffer = buffer;
358 break;
359 case DB_BIND_DYNAMIC:
360 b->buffer = buffer;
361 hStmt->buffers->add(buffer);
362 break;
363 case DB_BIND_TRANSIENT:
364 b->buffer = nx_memdup(buffer, bufferSize[cType]);
365 hStmt->buffers->add(b->buffer);
366 break;
367 default:
368 return; // Invalid call
369 }
370
371 switch(cType)
372 {
373 case DB_CTYPE_UINT32:
374 b->is_unsigned = true;
375 /* no break */
376 case DB_CTYPE_INT32:
377 b->buffer_type = MYSQL_TYPE_LONG;
378 break;
379 case DB_CTYPE_UINT64:
380 b->is_unsigned = true;
381 /* no break */
382 case DB_CTYPE_INT64:
383 b->buffer_type = MYSQL_TYPE_LONGLONG;
384 break;
385 case DB_CTYPE_DOUBLE:
386 b->buffer_type = MYSQL_TYPE_DOUBLE;
387 break;
388 }
389 }
390 }
391
392 /**
393 * Execute prepared statement
394 */
395 extern "C" DWORD EXPORT DrvExecute(MYSQL_CONN *pConn, MYSQL_STATEMENT *hStmt, WCHAR *errorText)
396 {
397 DWORD dwResult;
398
399 MutexLock(pConn->mutexQueryLock);
400
401 if (mysql_stmt_bind_param(hStmt->statement, hStmt->bindings) == 0)
402 {
403 if (mysql_stmt_execute(hStmt->statement) == 0)
404 {
405 dwResult = DBERR_SUCCESS;
406 }
407 else
408 {
409 int nErr = mysql_errno(pConn->pMySQL);
410 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR)
411 {
412 dwResult = DBERR_CONNECTION_LOST;
413 }
414 else
415 {
416 dwResult = DBERR_OTHER_ERROR;
417 }
418 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
419 }
420 }
421 else
422 {
423 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
424 dwResult = DBERR_OTHER_ERROR;
425 }
426
427 MutexUnlock(pConn->mutexQueryLock);
428 return dwResult;
429 }
430
431 /**
432 * Destroy prepared statement
433 */
434 extern "C" void EXPORT DrvFreeStatement(MYSQL_STATEMENT *hStmt)
435 {
436 if (hStmt == NULL)
437 return;
438
439 MutexLock(hStmt->connection->mutexQueryLock);
440 mysql_stmt_close(hStmt->statement);
441 MutexUnlock(hStmt->connection->mutexQueryLock);
442 delete hStmt->buffers;
443 safe_free(hStmt->bindings);
444 safe_free(hStmt->lengthFields);
445 free(hStmt);
446 }
447
448 /**
449 * Perform actual non-SELECT query
450 */
451 static DWORD DrvQueryInternal(MYSQL_CONN *pConn, const char *pszQuery, WCHAR *errorText)
452 {
453 DWORD dwRet = DBERR_INVALID_HANDLE;
454
455 MutexLock(pConn->mutexQueryLock);
456 if (mysql_query(pConn->pMySQL, pszQuery) == 0)
457 {
458 dwRet = DBERR_SUCCESS;
459 if (errorText != NULL)
460 *errorText = 0;
461 }
462 else
463 {
464 int nErr = mysql_errno(pConn->pMySQL);
465 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR) // CR_SERVER_GONE_ERROR - ???
466 {
467 dwRet = DBERR_CONNECTION_LOST;
468 }
469 else
470 {
471 dwRet = DBERR_OTHER_ERROR;
472 }
473 UpdateErrorMessage(mysql_error(pConn->pMySQL), errorText);
474 }
475
476 MutexUnlock(pConn->mutexQueryLock);
477 return dwRet;
478 }
479
480 /**
481 * Perform non-SELECT query
482 */
483 extern "C" DWORD EXPORT DrvQuery(MYSQL_CONN *pConn, WCHAR *pwszQuery, WCHAR *errorText)
484 {
485 DWORD dwRet;
486 char *pszQueryUTF8;
487
488 pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
489 dwRet = DrvQueryInternal(pConn, pszQueryUTF8, errorText);
490 free(pszQueryUTF8);
491 return dwRet;
492 }
493
494 /**
495 * Perform SELECT query
496 */
497 extern "C" DBDRV_RESULT EXPORT DrvSelect(MYSQL_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
498 {
499 MYSQL_RESULT *result = NULL;
500 char *pszQueryUTF8;
501
502 if (pConn == NULL)
503 {
504 *pdwError = DBERR_INVALID_HANDLE;
505 return NULL;
506 }
507
508 pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
509 MutexLock(pConn->mutexQueryLock);
510 if (mysql_query(pConn->pMySQL, pszQueryUTF8) == 0)
511 {
512 result = (MYSQL_RESULT *)malloc(sizeof(MYSQL_RESULT));
513 result->connection = pConn;
514 result->isPreparedStatement = false;
515 result->resultSet = mysql_store_result(pConn->pMySQL);
516 *pdwError = DBERR_SUCCESS;
517 if (errorText != NULL)
518 *errorText = 0;
519 }
520 else
521 {
522 int nErr = mysql_errno(pConn->pMySQL);
523 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR) // CR_SERVER_GONE_ERROR - ???
524 {
525 *pdwError = DBERR_CONNECTION_LOST;
526 }
527 else
528 {
529 *pdwError = DBERR_OTHER_ERROR;
530 }
531 UpdateErrorMessage(mysql_error(pConn->pMySQL), errorText);
532 }
533
534 MutexUnlock(pConn->mutexQueryLock);
535 free(pszQueryUTF8);
536 return result;
537 }
538
539 /**
540 * Perform SELECT query using prepared statement
541 */
542 extern "C" DBDRV_RESULT EXPORT DrvSelectPrepared(MYSQL_CONN *pConn, MYSQL_STATEMENT *hStmt, DWORD *pdwError, WCHAR *errorText)
543 {
544 MYSQL_RESULT *result = NULL;
545
546 if (pConn == NULL)
547 {
548 *pdwError = DBERR_INVALID_HANDLE;
549 return NULL;
550 }
551
552 MutexLock(pConn->mutexQueryLock);
553
554 if (mysql_stmt_bind_param(hStmt->statement, hStmt->bindings) == 0)
555 {
556 if (mysql_stmt_execute(hStmt->statement) == 0)
557 {
558 result = (MYSQL_RESULT *)malloc(sizeof(MYSQL_RESULT));
559 result->connection = pConn;
560 result->isPreparedStatement = true;
561 result->statement = hStmt->statement;
562 result->resultSet = mysql_stmt_result_metadata(hStmt->statement);
563 if (result->resultSet != NULL)
564 {
565 result->numColumns = mysql_num_fields(result->resultSet);
566
567 result->lengthFields = (unsigned long *)malloc(sizeof(unsigned long) * result->numColumns);
568 memset(result->lengthFields, 0, sizeof(unsigned long) * result->numColumns);
569
570 result->bindings = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * result->numColumns);
571 memset(result->bindings, 0, sizeof(MYSQL_BIND) * result->numColumns);
572 for(int i = 0; i < result->numColumns; i++)
573 {
574 result->bindings[i].buffer_type = MYSQL_TYPE_STRING;
575 result->bindings[i].length = &result->lengthFields[i];
576 }
577
578 mysql_stmt_bind_result(hStmt->statement, result->bindings);
579
580 if (mysql_stmt_store_result(hStmt->statement) == 0)
581 {
582 result->numRows = (int)mysql_stmt_num_rows(hStmt->statement);
583 result->currentRow = -1;
584 *pdwError = DBERR_SUCCESS;
585 }
586 else
587 {
588 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
589 *pdwError = DBERR_OTHER_ERROR;
590 mysql_free_result(result->resultSet);
591 free(result->bindings);
592 free(result->lengthFields);
593 free(result);
594 result = NULL;
595 }
596 }
597 else
598 {
599 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
600 *pdwError = DBERR_OTHER_ERROR;
601 free(result);
602 result = NULL;
603 }
604 }
605 else
606 {
607 int nErr = mysql_errno(pConn->pMySQL);
608 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR)
609 {
610 *pdwError = DBERR_CONNECTION_LOST;
611 }
612 else
613 {
614 *pdwError = DBERR_OTHER_ERROR;
615 }
616 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
617 }
618 }
619 else
620 {
621 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
622 *pdwError = DBERR_OTHER_ERROR;
623 }
624
625 MutexUnlock(pConn->mutexQueryLock);
626 return result;
627 }
628
629 /**
630 * Get field length from result
631 */
632 extern "C" LONG EXPORT DrvGetFieldLength(MYSQL_RESULT *hResult, int iRow, int iColumn)
633 {
634 if (hResult->isPreparedStatement)
635 {
636 if ((iRow < 0) || (iRow >= hResult->numRows) ||
637 (iColumn < 0) || (iColumn >= hResult->numColumns))
638 return -1;
639
640 if (hResult->currentRow != iRow)
641 {
642 MutexLock(hResult->connection->mutexQueryLock);
643 mysql_stmt_data_seek(hResult->statement, iRow);
644 mysql_stmt_fetch(hResult->statement);
645 hResult->currentRow = iRow;
646 MutexUnlock(hResult->connection->mutexQueryLock);
647 }
648 return (LONG)hResult->lengthFields[iColumn];
649 }
650 else
651 {
652 mysql_data_seek(hResult->resultSet, iRow);
653 MYSQL_ROW row = mysql_fetch_row(hResult->resultSet);
654 return (row == NULL) ? (LONG)-1 : ((row[iColumn] == NULL) ? -1 : (LONG)strlen(row[iColumn]));
655 }
656 }
657
658 /**
659 * Get field value from result - UNICODE and UTF8 implementation
660 */
661 static void *GetFieldInternal(MYSQL_RESULT *hResult, int iRow, int iColumn, void *pBuffer, int nBufSize, bool utf8)
662 {
663 void *pRet = NULL;
664
665 if (hResult->isPreparedStatement)
666 {
667 if ((iRow < 0) || (iRow >= hResult->numRows) ||
668 (iColumn < 0) || (iColumn >= hResult->numColumns))
669 return NULL;
670
671 MutexLock(hResult->connection->mutexQueryLock);
672 if (hResult->currentRow != iRow)
673 {
674 mysql_stmt_data_seek(hResult->statement, iRow);
675 mysql_stmt_fetch(hResult->statement);
676 hResult->currentRow = iRow;
677 }
678
679 MYSQL_BIND b;
680 unsigned long l = 0;
681 my_bool isNull;
682
683 memset(&b, 0, sizeof(MYSQL_BIND));
684 #if HAVE_ALLOCA
685 b.buffer = alloca(hResult->lengthFields[iColumn] + 1);
686 #else
687 b.buffer = malloc(hResult->lengthFields[iColumn] + 1);
688 #endif
689 b.buffer_length = hResult->lengthFields[iColumn] + 1;
690 b.buffer_type = MYSQL_TYPE_STRING;
691 b.length = &l;
692 b.is_null = &isNull;
693 int rc = mysql_stmt_fetch_column(hResult->statement, &b, iColumn, 0);
694 if (rc == 0)
695 {
696 if (!isNull)
697 {
698 ((char *)b.buffer)[l] = 0;
699 if (utf8)
700 {
701 strncpy((char *)pBuffer, (char *)b.buffer, nBufSize);
702 ((char *)pBuffer)[nBufSize - 1] = 0;
703 }
704 else
705 {
706 MultiByteToWideChar(CP_UTF8, 0, (char *)b.buffer, -1, (WCHAR *)pBuffer, nBufSize);
707 ((WCHAR *)pBuffer)[nBufSize - 1] = 0;
708 }
709 }
710 else
711 {
712 if (utf8)
713 *((char *)pBuffer) = 0;
714 else
715 *((WCHAR *)pBuffer) = 0;
716 }
717 pRet = pBuffer;
718 }
719 MutexUnlock(hResult->connection->mutexQueryLock);
720 #if !HAVE_ALLOCA
721 free(b.buffer);
722 #endif
723 }
724 else
725 {
726 mysql_data_seek(hResult->resultSet, iRow);
727 MYSQL_ROW row = mysql_fetch_row(hResult->resultSet);
728 if (row != NULL)
729 {
730 if (row[iColumn] != NULL)
731 {
732 if (utf8)
733 {
734 strncpy((char *)pBuffer, row[iColumn], nBufSize);
735 ((char *)pBuffer)[nBufSize - 1] = 0;
736 }
737 else
738 {
739 MultiByteToWideChar(CP_UTF8, 0, row[iColumn], -1, (WCHAR *)pBuffer, nBufSize);
740 ((WCHAR *)pBuffer)[nBufSize - 1] = 0;
741 }
742 pRet = pBuffer;
743 }
744 }
745 }
746 return pRet;
747 }
748
749 /**
750 * Get field value from result
751 */
752 extern "C" WCHAR EXPORT *DrvGetField(MYSQL_RESULT *hResult, int iRow, int iColumn, WCHAR *pBuffer, int nBufSize)
753 {
754 return (WCHAR *)GetFieldInternal(hResult, iRow, iColumn, pBuffer, nBufSize, false);
755 }
756
757 /**
758 * Get field value from result as UTF8 string
759 */
760 extern "C" char EXPORT *DrvGetFieldUTF8(MYSQL_RESULT *hResult, int iRow, int iColumn, char *pBuffer, int nBufSize)
761 {
762 return (char *)GetFieldInternal(hResult, iRow, iColumn, pBuffer, nBufSize, true);
763 }
764
765 /**
766 * Get number of rows in result
767 */
768 extern "C" int EXPORT DrvGetNumRows(MYSQL_RESULT *hResult)
769 {
770 return (hResult != NULL) ? (int)(hResult->isPreparedStatement ? hResult->numRows : mysql_num_rows(hResult->resultSet)) : 0;
771 }
772
773 /**
774 * Get column count in query result
775 */
776 extern "C" int EXPORT DrvGetColumnCount(MYSQL_RESULT *hResult)
777 {
778 return (hResult != NULL) ? (int)mysql_num_fields(hResult->resultSet) : 0;
779 }
780
781 /**
782 * Get column name in query result
783 */
784 extern "C" const char EXPORT *DrvGetColumnName(MYSQL_RESULT *hResult, int column)
785 {
786 MYSQL_FIELD *field;
787
788 if (hResult == NULL)
789 return NULL;
790
791 field = mysql_fetch_field_direct(hResult->resultSet, column);
792 return (field != NULL) ? field->name : NULL;
793 }
794
795 /**
796 * Free SELECT results
797 */
798 extern "C" void EXPORT DrvFreeResult(MYSQL_RESULT *hResult)
799 {
800 if (hResult == NULL)
801 return;
802
803 if (hResult->isPreparedStatement)
804 {
805 safe_free(hResult->bindings);
806 safe_free(hResult->lengthFields);
807 }
808
809 mysql_free_result(hResult->resultSet);
810 free(hResult);
811 }
812
813 /**
814 * Perform unbuffered SELECT query
815 */
816 extern "C" DBDRV_UNBUFFERED_RESULT EXPORT DrvSelectUnbuffered(MYSQL_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
817 {
818 MYSQL_UNBUFFERED_RESULT *pResult = NULL;
819 char *pszQueryUTF8;
820
821 if (pConn == NULL)
822 {
823 *pdwError = DBERR_INVALID_HANDLE;
824 return NULL;
825 }
826
827 pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
828 MutexLock(pConn->mutexQueryLock);
829 if (mysql_query(pConn->pMySQL, pszQueryUTF8) == 0)
830 {
831 pResult = (MYSQL_UNBUFFERED_RESULT *)malloc(sizeof(MYSQL_UNBUFFERED_RESULT));
832 pResult->connection = pConn;
833 pResult->isPreparedStatement = false;
834 pResult->resultSet = mysql_use_result(pConn->pMySQL);
835 if (pResult->resultSet != NULL)
836 {
837 pResult->noMoreRows = false;
838 pResult->numColumns = mysql_num_fields(pResult->resultSet);
839 pResult->pCurrRow = NULL;
840 pResult->lengthFields = (unsigned long *)malloc(sizeof(unsigned long) * pResult->numColumns);
841 pResult->bindings = NULL;
842 }
843 else
844 {
845 free(pResult);
846 pResult = NULL;
847 }
848
849 *pdwError = DBERR_SUCCESS;
850 if (errorText != NULL)
851 *errorText = 0;
852 }
853 else
854 {
855 int nErr = mysql_errno(pConn->pMySQL);
856 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR) // CR_SERVER_GONE_ERROR - ???
857 {
858 *pdwError = DBERR_CONNECTION_LOST;
859 }
860 else
861 {
862 *pdwError = DBERR_OTHER_ERROR;
863 }
864
865 if (errorText != NULL)
866 {
867 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, mysql_error(pConn->pMySQL), -1, errorText, DBDRV_MAX_ERROR_TEXT);
868 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
869 RemoveTrailingCRLFW(errorText);
870 }
871 }
872
873 if (pResult == NULL)
874 {
875 MutexUnlock(pConn->mutexQueryLock);
876 }
877 free(pszQueryUTF8);
878
879 return pResult;
880 }
881
882 /**
883 * Perform unbuffered SELECT query using prepared statement
884 */
885 extern "C" DBDRV_RESULT EXPORT DrvSelectPreparedUnbuffered(MYSQL_CONN *pConn, MYSQL_STATEMENT *hStmt, DWORD *pdwError, WCHAR *errorText)
886 {
887 MYSQL_UNBUFFERED_RESULT *result = NULL;
888
889 MutexLock(pConn->mutexQueryLock);
890
891 if (mysql_stmt_bind_param(hStmt->statement, hStmt->bindings) == 0)
892 {
893 if (mysql_stmt_execute(hStmt->statement) == 0)
894 {
895 result = (MYSQL_UNBUFFERED_RESULT *)malloc(sizeof(MYSQL_UNBUFFERED_RESULT));
896 result->connection = pConn;
897 result->isPreparedStatement = true;
898 result->statement = hStmt->statement;
899 result->resultSet = mysql_stmt_result_metadata(hStmt->statement);
900 if (result->resultSet != NULL)
901 {
902 result->noMoreRows = false;
903 result->numColumns = mysql_num_fields(result->resultSet);
904 result->pCurrRow = NULL;
905
906 result->lengthFields = (unsigned long *)malloc(sizeof(unsigned long) * result->numColumns);
907 memset(result->lengthFields, 0, sizeof(unsigned long) * result->numColumns);
908
909 result->bindings = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * result->numColumns);
910 memset(result->bindings, 0, sizeof(MYSQL_BIND) * result->numColumns);
911 for(int i = 0; i < result->numColumns; i++)
912 {
913 result->bindings[i].buffer_type = MYSQL_TYPE_STRING;
914 result->bindings[i].length = &result->lengthFields[i];
915 }
916
917 mysql_stmt_bind_result(hStmt->statement, result->bindings);
918 *pdwError = DBERR_SUCCESS;
919 }
920 else
921 {
922 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
923 *pdwError = DBERR_OTHER_ERROR;
924 free(result);
925 result = NULL;
926 }
927 }
928 else
929 {
930 int nErr = mysql_errno(pConn->pMySQL);
931 if (nErr == CR_SERVER_LOST || nErr == CR_CONNECTION_ERROR || nErr == CR_SERVER_GONE_ERROR)
932 {
933 *pdwError = DBERR_CONNECTION_LOST;
934 }
935 else
936 {
937 *pdwError = DBERR_OTHER_ERROR;
938 }
939 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
940 }
941 }
942 else
943 {
944 UpdateErrorMessage(mysql_stmt_error(hStmt->statement), errorText);
945 *pdwError = DBERR_OTHER_ERROR;
946 }
947
948 if (result == NULL)
949 {
950 MutexUnlock(pConn->mutexQueryLock);
951 }
952 return result;
953 }
954
955 /**
956 * Fetch next result line from asynchronous SELECT results
957 */
958 extern "C" bool EXPORT DrvFetch(MYSQL_UNBUFFERED_RESULT *result)
959 {
960 if ((result == NULL) || (result->noMoreRows))
961 return false;
962
963 bool success = true;
964
965 if (result->isPreparedStatement)
966 {
967 int rc = mysql_stmt_fetch(result->statement);
968 if ((rc != 0) && (rc != MYSQL_DATA_TRUNCATED))
969 {
970 result->noMoreRows = true;
971 success = false;
972 MutexUnlock(result->connection->mutexQueryLock);
973 }
974 }
975 else
976 {
977 // Try to fetch next row from server
978 result->pCurrRow = mysql_fetch_row(result->resultSet);
979 if (result->pCurrRow == NULL)
980 {
981 result->noMoreRows = true;
982 success = false;
983 MutexUnlock(result->connection->mutexQueryLock);
984 }
985 else
986 {
987 unsigned long *pLen;
988
989 // Get column lengths for current row
990 pLen = mysql_fetch_lengths(result->resultSet);
991 if (pLen != NULL)
992 {
993 memcpy(result->lengthFields, pLen, sizeof(unsigned long) * result->numColumns);
994 }
995 else
996 {
997 memset(result->lengthFields, 0, sizeof(unsigned long) * result->numColumns);
998 }
999 }
1000 }
1001 return success;
1002 }
1003
1004 /**
1005 * Get field length from async query result result
1006 */
1007 extern "C" LONG EXPORT DrvGetFieldLengthUnbuffered(MYSQL_UNBUFFERED_RESULT *hResult, int iColumn)
1008 {
1009 // Check if we have valid result handle
1010 if (hResult == NULL)
1011 return 0;
1012
1013 // Check if there are valid fetched row
1014 if (hResult->noMoreRows || ((hResult->pCurrRow == NULL) && !hResult->isPreparedStatement))
1015 return 0;
1016
1017 // Check if column number is valid
1018 if ((iColumn < 0) || (iColumn >= hResult->numColumns))
1019 return 0;
1020
1021 return hResult->lengthFields[iColumn];
1022 }
1023
1024 /**
1025 * Get field from current row in async query result - common implementation for wide char and UTF-8
1026 */
1027 static void *GetFieldUnbufferedInternal(MYSQL_UNBUFFERED_RESULT *hResult, int iColumn, void *pBuffer, int iBufSize, bool utf8)
1028 {
1029 // Check if we have valid result handle
1030 if (hResult == NULL)
1031 return NULL;
1032
1033 // Check if there are valid fetched row
1034 if ((hResult->noMoreRows) || ((hResult->pCurrRow == NULL) && !hResult->isPreparedStatement))
1035 return NULL;
1036
1037 // Check if column number is valid
1038 if ((iColumn < 0) || (iColumn >= hResult->numColumns))
1039 return NULL;
1040
1041 // Now get column data
1042 void *value = NULL;
1043 if (hResult->isPreparedStatement)
1044 {
1045 MYSQL_BIND b;
1046 unsigned long l = 0;
1047 my_bool isNull;
1048
1049 memset(&b, 0, sizeof(MYSQL_BIND));
1050 #if HAVE_ALLOCA
1051 b.buffer = alloca(hResult->lengthFields[iColumn] + 1);
1052 #else
1053 b.buffer = malloc(hResult->lengthFields[iColumn] + 1);
1054 #endif
1055 b.buffer_length = hResult->lengthFields[iColumn] + 1;
1056 b.buffer_type = MYSQL_TYPE_STRING;
1057 b.length = &l;
1058 b.is_null = &isNull;
1059 int rc = mysql_stmt_fetch_column(hResult->statement, &b, iColumn, 0);
1060 if (rc == 0)
1061 {
1062 if (!isNull)
1063 {
1064 ((char *)b.buffer)[l] = 0;
1065 if (utf8)
1066 {
1067 strncpy((char *)pBuffer, (char *)b.buffer, iBufSize);
1068 ((char *)pBuffer)[iBufSize - 1] = 0;
1069 }
1070 else
1071 {
1072 MultiByteToWideChar(CP_UTF8, 0, (char *)b.buffer, -1, (WCHAR *)pBuffer, iBufSize);
1073 ((WCHAR *)pBuffer)[iBufSize - 1] = 0;
1074 }
1075 }
1076 else
1077 {
1078 if (utf8)
1079 *((char *)pBuffer) = 0;
1080 else
1081 *((WCHAR *)pBuffer) = 0;
1082 }
1083 value = pBuffer;
1084 }
1085 #if !HAVE_ALLOCA
1086 free(b.buffer);
1087 #endif
1088 }
1089 else
1090 {
1091 int iLen = min((int)hResult->lengthFields[iColumn], iBufSize - 1);
1092 if (iLen > 0)
1093 {
1094 if (utf8)
1095 {
1096 memcpy(pBuffer, hResult->pCurrRow[iColumn], iLen);
1097 ((char *)pBuffer)[iLen] = 0;
1098 }
1099 else
1100 {
1101 MultiByteToWideChar(CP_UTF8, 0, hResult->pCurrRow[iColumn], iLen, (WCHAR *)pBuffer, iBufSize);
1102 ((WCHAR *)pBuffer)[iLen] = 0;
1103 }
1104 }
1105 else
1106 {
1107 if (utf8)
1108 *((char *)pBuffer) = 0;
1109 else
1110 *((WCHAR *)pBuffer) = 0;
1111 }
1112 value = pBuffer;
1113 }
1114 return value;
1115 }
1116
1117 /**
1118 * Get field from current row in async query result
1119 */
1120 extern "C" WCHAR EXPORT *DrvGetFieldUnbuffered(MYSQL_UNBUFFERED_RESULT *hResult, int iColumn, WCHAR *pBuffer, int iBufSize)
1121 {
1122 return (WCHAR *)GetFieldUnbufferedInternal(hResult, iColumn, pBuffer, iBufSize, false);
1123 }
1124
1125 /**
1126 * Get field from current row in async query result
1127 */
1128 extern "C" char EXPORT *DrvGetFieldUnbufferedUTF8(MYSQL_UNBUFFERED_RESULT *hResult, int iColumn, char *pBuffer, int iBufSize)
1129 {
1130 return (char *)GetFieldUnbufferedInternal(hResult, iColumn, pBuffer, iBufSize, true);
1131 }
1132
1133 /**
1134 * Get column count in async query result
1135 */
1136 extern "C" int EXPORT DrvGetColumnCountUnbuffered(MYSQL_UNBUFFERED_RESULT *hResult)
1137 {
1138 return (hResult != NULL) ? hResult->numColumns : 0;
1139 }
1140
1141 /**
1142 * Get column name in async query result
1143 */
1144 extern "C" const char EXPORT *DrvGetColumnNameUnbuffered(MYSQL_UNBUFFERED_RESULT *hResult, int column)
1145 {
1146 MYSQL_FIELD *field;
1147
1148 if ((hResult == NULL) || (hResult->resultSet == NULL))
1149 return NULL;
1150
1151 field = mysql_fetch_field_direct(hResult->resultSet, column);
1152 return (field != NULL) ? field->name : NULL;
1153 }
1154
1155 /**
1156 * Destroy result of async query
1157 */
1158 extern "C" void EXPORT DrvFreeUnbufferedResult(MYSQL_UNBUFFERED_RESULT *hResult)
1159 {
1160 if (hResult == NULL)
1161 return;
1162
1163 // Check if all result rows fetched
1164 if (!hResult->noMoreRows)
1165 {
1166 // Fetch remaining rows
1167 if (!hResult->isPreparedStatement)
1168 {
1169 while(mysql_fetch_row(hResult->resultSet) != NULL);
1170 }
1171
1172 // Now we are ready for next query, so unlock query mutex
1173 MutexUnlock(hResult->connection->mutexQueryLock);
1174 }
1175
1176 // Free allocated memory
1177 mysql_free_result(hResult->resultSet);
1178 free(hResult->lengthFields);
1179 free(hResult->bindings);
1180 free(hResult);
1181 }
1182
1183 /**
1184 * Begin transaction
1185 */
1186 extern "C" DWORD EXPORT DrvBegin(MYSQL_CONN *pConn)
1187 {
1188 return DrvQueryInternal(pConn, "BEGIN", NULL);
1189 }
1190
1191 /**
1192 * Commit transaction
1193 */
1194 extern "C" DWORD EXPORT DrvCommit(MYSQL_CONN *pConn)
1195 {
1196 return DrvQueryInternal(pConn, "COMMIT", NULL);
1197 }
1198
1199 /**
1200 * Rollback transaction
1201 */
1202 extern "C" DWORD EXPORT DrvRollback(MYSQL_CONN *pConn)
1203 {
1204 return DrvQueryInternal(pConn, "ROLLBACK", NULL);
1205 }
1206
1207 /**
1208 * Check if table exist
1209 */
1210 extern "C" int EXPORT DrvIsTableExist(MYSQL_CONN *pConn, const WCHAR *name)
1211 {
1212 WCHAR query[256], lname[256];
1213 wcsncpy(lname, name, 256);
1214 wcslwr(lname);
1215 swprintf(query, 256, L"SHOW TABLES LIKE '%ls'", lname);
1216 DWORD error;
1217 WCHAR errorText[DBDRV_MAX_ERROR_TEXT];
1218 int rc = DBIsTableExist_Failure;
1219 MYSQL_RESULT *hResult = (MYSQL_RESULT *)DrvSelect(pConn, query, &error, errorText);
1220 if (hResult != NULL)
1221 {
1222 rc = (DrvGetNumRows(hResult) > 0) ? DBIsTableExist_Found : DBIsTableExist_NotFound;
1223 DrvFreeResult(hResult);
1224 }
1225 return rc;
1226 }
1227
1228 #ifdef _WIN32
1229
1230 /**
1231 * DLL Entry point
1232 */
1233 bool WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
1234 {
1235 if (dwReason == DLL_PROCESS_ATTACH)
1236 DisableThreadLibraryCalls(hInstance);
1237 return true;
1238 }
1239
1240 #endif