fixed memory leak in Oracle driver
[public/netxms.git] / src / db / dbdrv / oracle / oracle.cpp
CommitLineData
5039dede
AK
1/*
2** Oracle Database Driver
792038b9 3** Copyright (C) 2007-2016 Victor Kirhenshtein
5039dede
AK
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: oracle.cpp
20**
21**/
22
23#include "oracledrv.h"
24
f3c30cf5 25DECLARE_DRIVER_HEADER("ORACLE")
5039dede 26
9b4a2a0e 27static DWORD DrvQueryInternal(ORACLE_CONN *pConn, const WCHAR *pwszQuery, WCHAR *errorText);
5d255340 28
d1c1c522
VK
29/**
30 * Prepare string for using in SQL query - enclose in quotes and escape as needed
31 */
a3166a07
VK
32extern "C" WCHAR EXPORT *DrvPrepareStringW(const WCHAR *str)
33{
34 int len = (int)wcslen(str) + 3; // + two quotes and \0 at the end
35 int bufferSize = len + 128;
36 WCHAR *out = (WCHAR *)malloc(bufferSize * sizeof(WCHAR));
37 out[0] = L'\'';
38
39 const WCHAR *src = str;
40 int outPos;
41 for(outPos = 1; *src != 0; src++)
42 {
43 if (*src == L'\'')
44 {
45 len++;
46 if (len >= bufferSize)
47 {
48 bufferSize += 128;
49 out = (WCHAR *)realloc(out, bufferSize * sizeof(WCHAR));
50 }
51 out[outPos++] = L'\'';
52 out[outPos++] = L'\'';
53 }
54 else
55 {
56 out[outPos++] = *src;
57 }
58 }
59 out[outPos++] = L'\'';
60 out[outPos++] = 0;
61
62 return out;
63}
64
74d4ba34
VK
65/**
66 * Prepare string for using in SQL query - enclose in quotes and escape as needed
67 */
a3166a07 68extern "C" char EXPORT *DrvPrepareStringA(const char *str)
643c9dcb 69{
20ee0bf4 70 int len = (int)strlen(str) + 3; // + two quotes and \0 at the end
643c9dcb 71 int bufferSize = len + 128;
a3166a07
VK
72 char *out = (char *)malloc(bufferSize);
73 out[0] = '\'';
643c9dcb 74
a3166a07 75 const char *src = str;
643c9dcb 76 int outPos;
f694a7bc 77 for(outPos = 1; *src != 0; src++)
643c9dcb 78 {
a3166a07 79 if (*src == '\'')
643c9dcb
VK
80 {
81 len++;
82 if (len >= bufferSize)
83 {
84 bufferSize += 128;
a3166a07 85 out = (char *)realloc(out, bufferSize);
643c9dcb 86 }
a3166a07
VK
87 out[outPos++] = '\'';
88 out[outPos++] = '\'';
643c9dcb
VK
89 }
90 else
91 {
92 out[outPos++] = *src;
93 }
94 }
a3166a07 95 out[outPos++] = '\'';
643c9dcb
VK
96 out[outPos++] = 0;
97
98 return out;
99}
100
d1c1c522
VK
101/**
102 * Initialize driver
103 */
2df047f4 104extern "C" bool EXPORT DrvInit(const char *cmdLine)
5039dede 105{
750d59f2 106 return true;
5039dede
AK
107}
108
d1c1c522
VK
109/**
110 * Unload handler
111 */
08b214c6 112extern "C" void EXPORT DrvUnload()
5039dede
AK
113{
114 OCITerminate(OCI_DEFAULT);
115}
116
96e87c92
VK
117/**
118 * Get error text from error handle
119 */
526ae8b8 120static void GetErrorFromHandle(OCIError *handle, sb4 *errorCode, WCHAR *errorText)
5039dede 121{
a3166a07 122#if UNICODE_UCS2
526ae8b8 123 OCIErrorGet(handle, 1, NULL, errorCode, (text *)errorText, DBDRV_MAX_ERROR_TEXT, OCI_HTYPE_ERROR);
465b3f2d 124 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
5039dede 125#else
39945910 126 UCS2CHAR buffer[DBDRV_MAX_ERROR_TEXT];
5039dede 127
526ae8b8 128 OCIErrorGet(handle, 1, NULL, errorCode, (text *)buffer, DBDRV_MAX_ERROR_TEXT, OCI_HTYPE_ERROR);
5039dede 129 buffer[DBDRV_MAX_ERROR_TEXT - 1] = 0;
465b3f2d
VK
130 ucs2_to_ucs4(buffer, ucs2_strlen(buffer) + 1, errorText, DBDRV_MAX_ERROR_TEXT);
131 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
5039dede 132#endif
465b3f2d
VK
133 RemoveTrailingCRLFW(errorText);
134}
135
96e87c92
VK
136/**
137 * Set last error text
138 */
526ae8b8 139static void SetLastError(ORACLE_CONN *pConn)
465b3f2d 140{
526ae8b8 141 GetErrorFromHandle(pConn->handleError, &pConn->lastErrorCode, pConn->lastErrorText);
5039dede
AK
142}
143
96e87c92
VK
144/**
145 * Check if last error was caused by lost connection to server
146 */
526ae8b8 147static DWORD IsConnectionError(ORACLE_CONN *conn)
5039dede
AK
148{
149 ub4 nStatus = 0;
526ae8b8 150 OCIAttrGet(conn->handleServer, OCI_HTYPE_SERVER, &nStatus, NULL, OCI_ATTR_SERVER_STATUS, conn->handleError);
5039dede
AK
151 return (nStatus == OCI_SERVER_NOT_CONNECTED) ? DBERR_CONNECTION_LOST : DBERR_OTHER_ERROR;
152}
153
d1c1c522
VK
154/**
155 * Destroy query result
156 */
5039dede
AK
157static void DestroyQueryResult(ORACLE_RESULT *pResult)
158{
159 int i, nCount;
160
161 nCount = pResult->nCols * pResult->nRows;
162 for(i = 0; i < nCount; i++)
792038b9
VK
163 free(pResult->pData[i]);
164 free(pResult->pData);
2a8e6a7b
VK
165
166 for(i = 0; i < pResult->nCols; i++)
792038b9
VK
167 free(pResult->columnNames[i]);
168 free(pResult->columnNames);
2a8e6a7b 169
5039dede
AK
170 free(pResult);
171}
172
d1c1c522
VK
173/**
174 * Connect to database
175 */
f3c30cf5
VK
176extern "C" DBDRV_CONNECTION EXPORT DrvConnect(const char *host, const char *login, const char *password,
177 const char *database, const char *schema, WCHAR *errorText)
5039dede
AK
178{
179 ORACLE_CONN *pConn;
39945910 180 UCS2CHAR *pwszStr;
5039dede 181
5039dede
AK
182 pConn = (ORACLE_CONN *)malloc(sizeof(ORACLE_CONN));
183 if (pConn != NULL)
184 {
2a8e6a7b
VK
185 memset(pConn, 0, sizeof(ORACLE_CONN));
186
5039dede
AK
187 if (OCIEnvNlsCreate(&pConn->handleEnv, OCI_THREADED | OCI_NCHAR_LITERAL_REPLACE_OFF,
188 NULL, NULL, NULL, NULL, 0, NULL, OCI_UTF16ID, OCI_UTF16ID) == OCI_SUCCESS)
189 {
190 OCIHandleAlloc(pConn->handleEnv, (void **)&pConn->handleError, OCI_HTYPE_ERROR, 0, NULL);
191 OCIHandleAlloc(pConn->handleEnv, (void **)&pConn->handleServer, OCI_HTYPE_SERVER, 0, NULL);
f3c30cf5 192 pwszStr = UCS2StringFromMBString(host);
5039dede 193 if (OCIServerAttach(pConn->handleServer, pConn->handleError,
39945910 194 (text *)pwszStr, (sb4)ucs2_strlen(pwszStr) * sizeof(UCS2CHAR), OCI_DEFAULT) == OCI_SUCCESS)
5039dede
AK
195 {
196 free(pwszStr);
197
198 // Initialize service handle
199 OCIHandleAlloc(pConn->handleEnv, (void **)&pConn->handleService, OCI_HTYPE_SVCCTX, 0, NULL);
200 OCIAttrSet(pConn->handleService, OCI_HTYPE_SVCCTX, pConn->handleServer, 0, OCI_ATTR_SERVER, pConn->handleError);
201
202 // Initialize session handle
203 OCIHandleAlloc(pConn->handleEnv, (void **)&pConn->handleSession, OCI_HTYPE_SESSION, 0, NULL);
f3c30cf5 204 pwszStr = UCS2StringFromMBString(login);
5039dede 205 OCIAttrSet(pConn->handleSession, OCI_HTYPE_SESSION, pwszStr,
39945910 206 (ub4)ucs2_strlen(pwszStr) * sizeof(UCS2CHAR), OCI_ATTR_USERNAME, pConn->handleError);
5039dede 207 free(pwszStr);
f3c30cf5 208 pwszStr = UCS2StringFromMBString(password);
5039dede 209 OCIAttrSet(pConn->handleSession, OCI_HTYPE_SESSION, pwszStr,
39945910 210 (ub4)ucs2_strlen(pwszStr) * sizeof(UCS2CHAR), OCI_ATTR_PASSWORD, pConn->handleError);
5039dede
AK
211
212 // Authenticate
213 if (OCISessionBegin(pConn->handleService, pConn->handleError,
8228010e 214 pConn->handleSession, OCI_CRED_RDBMS, OCI_STMT_CACHE) == OCI_SUCCESS)
5039dede 215 {
acc231ba 216 OCIAttrSet(pConn->handleService, OCI_HTYPE_SVCCTX, pConn->handleSession, 0, OCI_ATTR_SESSION, pConn->handleError);
5039dede
AK
217 pConn->mutexQueryLock = MutexCreate();
218 pConn->nTransLevel = 0;
526ae8b8
VK
219 pConn->lastErrorCode = 0;
220 pConn->lastErrorText[0] = 0;
85dfb586 221 pConn->prefetchLimit = 10;
f3c30cf5
VK
222
223 if ((schema != NULL) && (schema[0] != 0))
224 {
225 free(pwszStr);
226 pwszStr = UCS2StringFromMBString(schema);
227 OCIAttrSet(pConn->handleSession, OCI_HTYPE_SESSION, pwszStr,
228 (ub4)ucs2_strlen(pwszStr) * sizeof(UCS2CHAR), OCI_ATTR_CURRENT_SCHEMA, pConn->handleError);
229 }
5d255340
VK
230
231 // Setup session
9b4a2a0e 232 DrvQueryInternal(pConn, L"ALTER SESSION SET NLS_LANGUAGE='AMERICAN' NLS_NUMERIC_CHARACTERS='.,'", NULL);
2df047f4
VK
233
234 UCS2CHAR version[1024];
235 if (OCIServerVersion(pConn->handleService, pConn->handleError, (OraText *)version, sizeof(version), OCI_HTYPE_SVCCTX) == OCI_SUCCESS)
236 {
237#if UNICODE_UCS4
238 WCHAR *wver = UCS4StringFromUCS2String(version);
239 nxlog_debug(5, _T("ORACLE: connected to %s"), wver);
240 free(wver);
241#else
242 nxlog_debug(5, _T("ORACLE: connected to %s"), version);
243#endif
244 }
5039dede
AK
245 }
246 else
247 {
526ae8b8 248 GetErrorFromHandle(pConn->handleError, &pConn->lastErrorCode, errorText);
acc231ba 249 OCIServerDetach(pConn->handleServer, pConn->handleError, OCI_DEFAULT);
5039dede
AK
250 OCIHandleFree(pConn->handleEnv, OCI_HTYPE_ENV);
251 free(pConn);
252 pConn = NULL;
253 }
254 }
255 else
256 {
526ae8b8 257 GetErrorFromHandle(pConn->handleError, &pConn->lastErrorCode, errorText);
5039dede
AK
258 OCIHandleFree(pConn->handleEnv, OCI_HTYPE_ENV);
259 free(pConn);
260 pConn = NULL;
261 }
262 free(pwszStr);
263 }
264 else
265 {
465b3f2d 266 wcscpy(errorText, L"Cannot allocate environment handle");
5039dede
AK
267 free(pConn);
268 pConn = NULL;
269 }
270 }
465b3f2d
VK
271 else
272 {
273 wcscpy(errorText, L"Memory allocation error");
274 }
5039dede 275
b8c1ec69 276 return (DBDRV_CONNECTION)pConn;
5039dede
AK
277}
278
74d4ba34
VK
279/**
280 * Disconnect from database
281 */
5039dede
AK
282extern "C" void EXPORT DrvDisconnect(ORACLE_CONN *pConn)
283{
acc231ba
VK
284 if (pConn == NULL)
285 return;
286
287 OCISessionEnd(pConn->handleService, pConn->handleError, NULL, OCI_DEFAULT);
288 OCIServerDetach(pConn->handleServer, pConn->handleError, OCI_DEFAULT);
289 OCIHandleFree(pConn->handleEnv, OCI_HTYPE_ENV);
290 MutexDestroy(pConn->mutexQueryLock);
291 free(pConn);
5039dede
AK
292}
293
85dfb586
VK
294/**
295 * Set prefetch limit
296 */
297extern "C" void EXPORT DrvSetPrefetchLimit(ORACLE_CONN *pConn, int limit)
298{
299 if (pConn != NULL)
300 pConn->prefetchLimit = limit;
301}
302
74d4ba34
VK
303/**
304 * Convert query from NetXMS portable format to native Oracle format
305 */
47a89886
VK
306static UCS2CHAR *ConvertQuery(WCHAR *query)
307{
308#if UNICODE_UCS4
309 UCS2CHAR *srcQuery = UCS2StringFromUCS4String(query);
310#else
311 UCS2CHAR *srcQuery = query;
312#endif
313 int count = NumCharsW(query, L'?');
314 if (count == 0)
315 {
316#if UNICODE_UCS4
317 return srcQuery;
318#else
319 return wcsdup(query);
320#endif
321 }
322
987533db 323 UCS2CHAR *dstQuery = (UCS2CHAR *)malloc((ucs2_strlen(srcQuery) + count * 3 + 1) * sizeof(UCS2CHAR));
47a89886
VK
324 bool inString = false;
325 int pos = 1;
326 UCS2CHAR *src, *dst;
327 for(src = srcQuery, dst = dstQuery; *src != 0; src++)
328 {
329 switch(*src)
330 {
331 case '\'':
332 *dst++ = *src;
333 inString = !inString;
334 break;
335 case '\\':
336 *dst++ = *src++;
337 *dst++ = *src;
338 break;
339 case '?':
340 if (inString)
341 {
342 *dst++ = '?';
343 }
344 else
345 {
346 *dst++ = ':';
347 if (pos < 10)
348 {
349 *dst++ = pos + '0';
350 }
987533db 351 else if (pos < 100)
47a89886
VK
352 {
353 *dst++ = pos / 10 + '0';
354 *dst++ = pos % 10 + '0';
355 }
987533db
VK
356 else
357 {
358 *dst++ = pos / 100 + '0';
359 *dst++ = (pos % 100) / 10 + '0';
360 *dst++ = pos % 10 + '0';
361 }
47a89886
VK
362 pos++;
363 }
364 break;
365 default:
366 *dst++ = *src;
367 break;
368 }
369 }
370 *dst = 0;
371#if UNICODE_UCS4
372 free(srcQuery);
373#endif
374 return dstQuery;
375}
376
96e87c92
VK
377/**
378 * Prepare statement
379 */
de1d708f 380extern "C" ORACLE_STATEMENT EXPORT *DrvPrepare(ORACLE_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
ec19ded8
VK
381{
382 ORACLE_STATEMENT *stmt = NULL;
383 OCIStmt *handleStmt;
384
47a89886 385 UCS2CHAR *ucs2Query = ConvertQuery(pwszQuery);
ec19ded8 386
c17f6cbc 387 MutexLock(pConn->mutexQueryLock);
ec19ded8
VK
388 if (OCIStmtPrepare2(pConn->handleService, &handleStmt, pConn->handleError, (text *)ucs2Query,
389 (ub4)ucs2_strlen(ucs2Query) * sizeof(UCS2CHAR), NULL, 0, OCI_NTV_SYNTAX, OCI_DEFAULT) == OCI_SUCCESS)
390 {
391 stmt = (ORACLE_STATEMENT *)malloc(sizeof(ORACLE_STATEMENT));
392 stmt->connection = pConn;
393 stmt->handleStmt = handleStmt;
47a89886 394 stmt->bindings = new Array(8, 8, false);
66c11d49 395 stmt->batchBindings = NULL;
47a89886 396 stmt->buffers = new Array(8, 8, true);
d0b5d358 397 stmt->batchMode = false;
66c11d49 398 stmt->batchSize = 0;
47a89886 399 OCIHandleAlloc(pConn->handleEnv, (void **)&stmt->handleError, OCI_HTYPE_ERROR, 0, NULL);
de1d708f 400 *pdwError = DBERR_SUCCESS;
ec19ded8
VK
401 }
402 else
403 {
526ae8b8 404 SetLastError(pConn);
de1d708f 405 *pdwError = IsConnectionError(pConn);
ec19ded8
VK
406 }
407
408 if (errorText != NULL)
409 {
526ae8b8 410 wcsncpy(errorText, pConn->lastErrorText, DBDRV_MAX_ERROR_TEXT);
ec19ded8
VK
411 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
412 }
413 MutexUnlock(pConn->mutexQueryLock);
414
ec19ded8 415 free(ucs2Query);
ec19ded8
VK
416 return stmt;
417}
418
91eefa56 419/**
66c11d49 420 * Open batch
91eefa56 421 */
66c11d49
VK
422extern "C" bool EXPORT DrvOpenBatch(ORACLE_STATEMENT *stmt)
423{
424 stmt->buffers->clear();
425 if (stmt->batchBindings != NULL)
426 stmt->batchBindings->clear();
427 else
428 stmt->batchBindings = new ObjectArray<OracleBatchBind>(16, 16, true);
429 stmt->batchMode = true;
430 stmt->batchSize = 0;
431 return true;
432}
433
434/**
435 * Start next batch row
436 */
437extern "C" void EXPORT DrvNextBatchRow(ORACLE_STATEMENT *stmt)
ec19ded8 438{
66c11d49
VK
439 if (!stmt->batchMode)
440 return;
441
442 for(int i = 0; i < stmt->batchBindings->size(); i++)
443 {
444 OracleBatchBind *bind = stmt->batchBindings->get(i);
445 if (bind != NULL)
446 bind->addRow();
447 }
448 stmt->batchSize++;
449}
450
451/**
452 * Buffer sizes for different C types
453 */
454static DWORD s_bufferSize[] = { 0, sizeof(LONG), sizeof(DWORD), sizeof(INT64), sizeof(QWORD), sizeof(double) };
455
456/**
457 * Corresponding Oracle types for C types
458 */
459static ub2 s_oracleType[] = { SQLT_STR, SQLT_INT, SQLT_UIN, SQLT_INT, SQLT_UIN, SQLT_FLT };
47a89886 460
66c11d49
VK
461/**
462 * Bind parameter to statement - normal mode (non-batch)
463 */
464static void BindNormal(ORACLE_STATEMENT *stmt, int pos, int sqlType, int cType, void *buffer, int allocType)
465{
47a89886
VK
466 OCIBind *handleBind = (OCIBind *)stmt->bindings->get(pos - 1);
467 void *sqlBuffer;
511bea22 468 switch(cType)
47a89886 469 {
511bea22 470 case DB_CTYPE_STRING:
47a89886 471#if UNICODE_UCS4
511bea22 472 sqlBuffer = UCS2StringFromUCS4String((WCHAR *)buffer);
66c11d49 473 stmt->buffers->set(pos - 1, sqlBuffer);
636e0c72 474 if (allocType == DB_BIND_DYNAMIC)
511bea22
VK
475 free(buffer);
476#else
66c11d49 477 if (allocType == DB_BIND_TRANSIENT)
511bea22
VK
478 {
479 sqlBuffer = wcsdup((WCHAR *)buffer);
66c11d49 480 stmt->buffers->set(pos - 1, sqlBuffer);
511bea22
VK
481 }
482 else
483 {
47a89886 484 sqlBuffer = buffer;
511bea22
VK
485 if (allocType == DB_BIND_DYNAMIC)
486 stmt->buffers->set(pos - 1, sqlBuffer);
487 }
488#endif
66c11d49
VK
489 OCIBindByPos(stmt->handleStmt, &handleBind, stmt->handleError, pos, sqlBuffer,
490 ((sb4)ucs2_strlen((UCS2CHAR *)sqlBuffer) + 1) * sizeof(UCS2CHAR),
491 (sqlType == DB_SQLTYPE_TEXT) ? SQLT_LNG : SQLT_STR,
492 NULL, NULL, NULL, 0, NULL, OCI_DEFAULT);
511bea22
VK
493 break;
494 case DB_CTYPE_INT64: // OCI prior to 11.2 cannot bind 64 bit integers
96dd17a0 495 sqlBuffer = malloc(sizeof(OCINumber));
6492e559 496 stmt->buffers->set(pos - 1, sqlBuffer);
96dd17a0
VK
497 OCINumberFromInt(stmt->handleError, buffer, sizeof(INT64), OCI_NUMBER_SIGNED, (OCINumber *)sqlBuffer);
498 OCIBindByPos(stmt->handleStmt, &handleBind, stmt->handleError, pos, sqlBuffer, sizeof(OCINumber),
499 SQLT_VNU, NULL, NULL, NULL, 0, NULL, OCI_DEFAULT);
511bea22
VK
500 if (allocType == DB_BIND_DYNAMIC)
501 free(buffer);
502 break;
503 case DB_CTYPE_UINT64: // OCI prior to 11.2 cannot bind 64 bit integers
96dd17a0 504 sqlBuffer = malloc(sizeof(OCINumber));
6492e559 505 stmt->buffers->set(pos - 1, sqlBuffer);
96dd17a0
VK
506 OCINumberFromInt(stmt->handleError, buffer, sizeof(INT64), OCI_NUMBER_UNSIGNED, (OCINumber *)sqlBuffer);
507 OCIBindByPos(stmt->handleStmt, &handleBind, stmt->handleError, pos, sqlBuffer, sizeof(OCINumber),
508 SQLT_VNU, NULL, NULL, NULL, 0, NULL, OCI_DEFAULT);
509 if (allocType == DB_BIND_DYNAMIC)
510 free(buffer);
511bea22
VK
511 break;
512 default:
66c11d49
VK
513 switch(allocType)
514 {
515 case DB_BIND_STATIC:
516 sqlBuffer = buffer;
517 break;
518 case DB_BIND_DYNAMIC:
519 sqlBuffer = buffer;
520 stmt->buffers->set(pos - 1, buffer);
521 break;
522 case DB_BIND_TRANSIENT:
523 sqlBuffer = nx_memdup(buffer, s_bufferSize[cType]);
524 stmt->buffers->set(pos - 1, sqlBuffer);
525 break;
526 default:
527 return; // Invalid call
528 }
529 OCIBindByPos(stmt->handleStmt, &handleBind, stmt->handleError, pos, sqlBuffer, s_bufferSize[cType],
530 s_oracleType[cType], NULL, NULL, NULL, 0, NULL, OCI_DEFAULT);
531 break;
532 }
533
534 stmt->bindings->set(pos - 1, handleBind);
535}
536
537/**
538 * Batch bind - constructor
539 */
540OracleBatchBind::OracleBatchBind(int cType, int sqlType)
541{
542 m_cType = cType;
543 m_size = 0;
544 m_allocated = 256;
545 if ((cType == DB_CTYPE_STRING) || (cType == DB_CTYPE_INT64) || (cType == DB_CTYPE_UINT64))
546 {
547 m_elementSize = sizeof(UCS2CHAR);
548 m_string = true;
549 m_oraType = (sqlType == DB_SQLTYPE_TEXT) ? SQLT_LNG : SQLT_STR;
550 m_data = NULL;
551 m_strings = (UCS2CHAR **)calloc(m_allocated, sizeof(UCS2CHAR *));
552 }
553 else
554 {
555 m_elementSize = s_bufferSize[cType];
556 m_string = false;
557 m_oraType = s_oracleType[cType];
558 m_data = calloc(m_allocated, m_elementSize);
559 m_strings = NULL;
560 }
561}
562
563/**
564 * Batch bind - destructor
565 */
566OracleBatchBind::~OracleBatchBind()
567{
568 if (m_strings != NULL)
569 {
570 for(int i = 0; i < m_size; i++)
571 safe_free(m_strings[i]);
572 free(m_strings);
573 }
574 safe_free(m_data);
575}
576
577/**
578 * Batch bind - add row
579 */
580void OracleBatchBind::addRow()
581{
582 if (m_size == m_allocated)
583 {
584 m_allocated += 256;
585 if (m_string)
586 {
587 m_strings = (UCS2CHAR **)realloc(m_strings, m_allocated * sizeof(UCS2CHAR *));
588 memset(m_strings + m_size, 0, (m_allocated - m_size) * sizeof(UCS2CHAR *));
589 }
590 else
591 {
592 m_data = realloc(m_data, m_allocated * m_elementSize);
593 memset((char *)m_data + m_size * m_elementSize, 0, (m_allocated - m_size) * m_elementSize);
594 }
595 }
596 if (m_size > 0)
597 {
598 // clone last element
599 if (m_string)
600 {
601 UCS2CHAR *p = m_strings[m_size - 1];
602 m_strings[m_size] = (p != NULL) ? ucs2_strdup(p) : NULL;
603 }
604 else
605 {
606 memcpy((char *)m_data + m_size * m_elementSize, (char *)m_data + (m_size - 1) * m_elementSize, m_elementSize);
607 }
608 }
609 m_size++;
610}
611
612/**
613 * Batch bind - set value
614 */
615void OracleBatchBind::set(void *value)
616{
617 if (m_string)
618 {
619 safe_free(m_strings[m_size - 1]);
620 m_strings[m_size - 1] = (UCS2CHAR *)value;
621 if (value != NULL)
622 {
623 int l = (int)(ucs2_strlen((UCS2CHAR *)value) + 1) * sizeof(UCS2CHAR);
624 if (l > m_elementSize)
625 m_elementSize = l;
626 }
627 }
628 else
629 {
630 memcpy((char *)m_data + (m_size - 1) * m_elementSize, value, m_elementSize);
631 }
632}
633
634/**
635 * Get data for OCI bind
636 */
637void *OracleBatchBind::getData()
638{
639 if (!m_string)
640 return m_data;
641
642 safe_free(m_data);
643 m_data = calloc(m_size, m_elementSize);
644 char *p = (char *)m_data;
645 for(int i = 0; i < m_size; i++)
646 {
647 if (m_strings[i] == NULL)
648 continue;
649 memcpy(p, m_strings[i], ucs2_strlen(m_strings[i]) * sizeof(UCS2CHAR));
650 p += m_elementSize;
651 }
652 return m_data;
653}
654
655/**
656 * Bind parameter to statement - batch mode
657 */
658static void BindBatch(ORACLE_STATEMENT *stmt, int pos, int sqlType, int cType, void *buffer, int allocType)
659{
660 if (stmt->batchSize == 0)
661 return; // no batch rows added yet
662
663 OracleBatchBind *bind = stmt->batchBindings->get(pos - 1);
664 if (bind == NULL)
665 {
666 bind = new OracleBatchBind(cType, sqlType);
667 stmt->batchBindings->set(pos - 1, bind);
668 for(int i = 0; i < stmt->batchSize; i++)
669 bind->addRow();
670 }
671
672 if (bind->getCType() != cType)
673 return;
674
675 void *sqlBuffer;
676 switch(bind->getCType())
677 {
678 case DB_CTYPE_STRING:
679#if UNICODE_UCS4
680 sqlBuffer = UCS2StringFromUCS4String((WCHAR *)buffer);
681 if (allocType == DB_BIND_DYNAMIC)
682 free(buffer);
683#else
684 if (allocType == DB_BIND_DYNAMIC)
685 {
686 sqlBuffer = buffer;
687 }
688 else
511bea22 689 {
66c11d49 690 sqlBuffer = wcsdup((WCHAR *)buffer);
511bea22 691 }
66c11d49
VK
692#endif
693 bind->set(sqlBuffer);
694 break;
695 case DB_CTYPE_INT64: // OCI prior to 11.2 cannot bind 64 bit integers
696#ifdef UNICODE_UCS2
697 sqlBuffer = malloc(64 * sizeof(WCHAR));
698 swprintf((WCHAR *)sqlBuffer, 64, INT64_FMTW, *((INT64 *)buffer));
699#else
700 {
701 char temp[64];
702 snprintf(temp, 64, INT64_FMTA, *((INT64 *)buffer));
703 sqlBuffer = UCS2StringFromMBString(temp);
704 }
705#endif
706 bind->set(sqlBuffer);
707 if (allocType == DB_BIND_DYNAMIC)
708 free(buffer);
709 break;
710 case DB_CTYPE_UINT64: // OCI prior to 11.2 cannot bind 64 bit integers
711#ifdef UNICODE_UCS2
712 sqlBuffer = malloc(64 * sizeof(WCHAR));
713 swprintf((WCHAR *)sqlBuffer, 64, UINT64_FMTW, *((QWORD *)buffer));
714#else
715 {
716 char temp[64];
717 snprintf(temp, 64, UINT64_FMTA, *((QWORD *)buffer));
718 sqlBuffer = UCS2StringFromMBString(temp);
719 }
720#endif
721 bind->set(sqlBuffer);
722 if (allocType == DB_BIND_DYNAMIC)
723 free(buffer);
724 break;
725 default:
726 bind->set(buffer);
727 if (allocType == DB_BIND_DYNAMIC)
728 free(buffer);
511bea22 729 break;
47a89886 730 }
66c11d49
VK
731}
732
733/**
734 * Bind parameter to statement
735 */
736extern "C" void EXPORT DrvBind(ORACLE_STATEMENT *stmt, int pos, int sqlType, int cType, void *buffer, int allocType)
737{
738 if (stmt->batchMode)
739 BindBatch(stmt, pos, sqlType, cType, buffer, allocType);
740 else
741 BindNormal(stmt, pos, sqlType, cType, buffer, allocType);
ec19ded8
VK
742}
743
a9671f4b
VK
744/**
745 * Execute prepared non-select statement
746 */
ec19ded8
VK
747extern "C" DWORD EXPORT DrvExecute(ORACLE_CONN *pConn, ORACLE_STATEMENT *stmt, WCHAR *errorText)
748{
749 DWORD dwResult;
750
66c11d49
VK
751 if (stmt->batchMode)
752 {
24bc6f13
VK
753 if (stmt->batchSize == 0)
754 {
755 stmt->batchMode = false;
756 stmt->batchBindings->clear();
757 return DBERR_SUCCESS; // empty batch
758 }
759
66c11d49
VK
760 for(int i = 0; i < stmt->batchBindings->size(); i++)
761 {
762 OracleBatchBind *b = stmt->batchBindings->get(i);
763 if (b == NULL)
764 continue;
765
766 OCIBind *handleBind = NULL;
767 OCIBindByPos(stmt->handleStmt, &handleBind, stmt->handleError, i + 1, b->getData(),
768 b->getElementSize(), b->getOraType(), NULL, NULL, NULL, 0, NULL, OCI_DEFAULT);
769 }
770 }
771
c17f6cbc 772 MutexLock(pConn->mutexQueryLock);
66c11d49
VK
773 if (OCIStmtExecute(pConn->handleService, stmt->handleStmt, pConn->handleError,
774 stmt->batchMode ? stmt->batchSize : 1, 0, NULL, NULL,
ec19ded8
VK
775 (pConn->nTransLevel == 0) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT) == OCI_SUCCESS)
776 {
777 dwResult = DBERR_SUCCESS;
778 }
779 else
780 {
526ae8b8 781 SetLastError(pConn);
ec19ded8
VK
782 dwResult = IsConnectionError(pConn);
783 }
784
785 if (errorText != NULL)
786 {
526ae8b8 787 wcsncpy(errorText, pConn->lastErrorText, DBDRV_MAX_ERROR_TEXT);
ec19ded8
VK
788 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
789 }
790 MutexUnlock(pConn->mutexQueryLock);
66c11d49
VK
791
792 if (stmt->batchMode)
793 {
794 stmt->batchMode = false;
795 stmt->batchBindings->clear();
796 }
797
ec19ded8
VK
798 return dwResult;
799}
800
d1c1c522
VK
801/**
802 * Destroy prepared statement
803 */
ec19ded8
VK
804extern "C" void EXPORT DrvFreeStatement(ORACLE_STATEMENT *stmt)
805{
806 if (stmt == NULL)
807 return;
808
c17f6cbc 809 MutexLock(stmt->connection->mutexQueryLock);
47a89886
VK
810 OCIStmtRelease(stmt->handleStmt, stmt->handleError, NULL, 0, OCI_DEFAULT);
811 OCIHandleFree(stmt->handleError, OCI_HTYPE_ERROR);
ec19ded8 812 MutexUnlock(stmt->connection->mutexQueryLock);
47a89886 813 delete stmt->bindings;
66c11d49 814 delete stmt->batchBindings;
47a89886 815 delete stmt->buffers;
ec19ded8
VK
816 free(stmt);
817}
818
69bb7f47
VK
819/**
820 * Perform non-SELECT query
821 */
9b4a2a0e 822static DWORD DrvQueryInternal(ORACLE_CONN *pConn, const WCHAR *pwszQuery, WCHAR *errorText)
5039dede
AK
823{
824 OCIStmt *handleStmt;
825 DWORD dwResult;
826
39945910
VK
827#if UNICODE_UCS4
828 UCS2CHAR *ucs2Query = UCS2StringFromUCS4String(pwszQuery);
829#else
51d31f95 830 const UCS2CHAR *ucs2Query = pwszQuery;
39945910
VK
831#endif
832
c17f6cbc 833 MutexLock(pConn->mutexQueryLock);
6f7c88fb
VK
834 if (OCIStmtPrepare2(pConn->handleService, &handleStmt, pConn->handleError, (text *)ucs2Query,
835 (ub4)ucs2_strlen(ucs2Query) * sizeof(UCS2CHAR), NULL, 0, OCI_NTV_SYNTAX, OCI_DEFAULT) == OCI_SUCCESS)
5039dede
AK
836 {
837 if (OCIStmtExecute(pConn->handleService, handleStmt, pConn->handleError, 1, 0, NULL, NULL,
838 (pConn->nTransLevel == 0) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT) == OCI_SUCCESS)
839 {
840 dwResult = DBERR_SUCCESS;
841 }
842 else
843 {
526ae8b8 844 SetLastError(pConn);
5039dede
AK
845 dwResult = IsConnectionError(pConn);
846 }
6f7c88fb 847 OCIStmtRelease(handleStmt, pConn->handleError, NULL, 0, OCI_DEFAULT);
5039dede
AK
848 }
849 else
850 {
526ae8b8 851 SetLastError(pConn);
5039dede
AK
852 dwResult = IsConnectionError(pConn);
853 }
5039dede 854 if (errorText != NULL)
a3166a07 855 {
526ae8b8 856 wcsncpy(errorText, pConn->lastErrorText, DBDRV_MAX_ERROR_TEXT);
a3166a07
VK
857 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
858 }
5039dede 859 MutexUnlock(pConn->mutexQueryLock);
39945910
VK
860
861#if UNICODE_UCS4
862 free(ucs2Query);
863#endif
5039dede 864 return dwResult;
9b4a2a0e
VK
865}
866
867/**
868 * Perform non-SELECT query - entry point
869 */
870extern "C" DWORD EXPORT DrvQuery(ORACLE_CONN *conn, const WCHAR *query, WCHAR *errorText)
871{
872 return DrvQueryInternal(conn, query, errorText);
5039dede
AK
873}
874
d1c1c522
VK
875/**
876 * Process SELECT results
877 */
ec19ded8 878static ORACLE_RESULT *ProcessQueryResults(ORACLE_CONN *pConn, OCIStmt *handleStmt, DWORD *pdwError)
5039dede 879{
5039dede
AK
880 OCIParam *handleParam;
881 OCIDefine *handleDefine;
882 ub4 nCount;
883 ub2 nWidth;
884 sword nStatus;
885 ORACLE_FETCH_BUFFER *pBuffers;
2a8e6a7b 886 text *colName;
5039dede 887
ec19ded8
VK
888 ORACLE_RESULT *pResult = (ORACLE_RESULT *)malloc(sizeof(ORACLE_RESULT));
889 pResult->nRows = 0;
890 pResult->pData = NULL;
891 pResult->columnNames = NULL;
892 OCIAttrGet(handleStmt, OCI_HTYPE_STMT, &nCount, NULL, OCI_ATTR_PARAM_COUNT, pConn->handleError);
893 pResult->nCols = nCount;
894 if (pResult->nCols > 0)
5039dede 895 {
ec19ded8 896 // Prepare receive buffers and fetch column names
acec59f5
VK
897 pResult->columnNames = (char **)calloc(pResult->nCols, sizeof(char *));
898 pBuffers = (ORACLE_FETCH_BUFFER *)calloc(pResult->nCols, sizeof(ORACLE_FETCH_BUFFER));
ec19ded8 899 for(int i = 0; i < pResult->nCols; i++)
5039dede 900 {
ec19ded8
VK
901 if ((nStatus = OCIParamGet(handleStmt, OCI_HTYPE_STMT, pConn->handleError,
902 (void **)&handleParam, (ub4)(i + 1))) == OCI_SUCCESS)
5039dede 903 {
ec19ded8
VK
904 // Column name
905 if (OCIAttrGet(handleParam, OCI_DTYPE_PARAM, &colName, &nCount, OCI_ATTR_NAME, pConn->handleError) == OCI_SUCCESS)
5039dede 906 {
ec19ded8
VK
907 // We are in UTF-16 mode, so OCIAttrGet will return UTF-16 strings
908 pResult->columnNames[i] = MBStringFromUCS2String((UCS2CHAR *)colName);
909 }
910 else
911 {
912 pResult->columnNames[i] = strdup("");
5039dede
AK
913 }
914
ec19ded8 915 // Data size
0fb0952d
VK
916 ub2 type = 0;
917 OCIAttrGet(handleParam, OCI_DTYPE_PARAM, &type, NULL, OCI_ATTR_DATA_TYPE, pConn->handleError);
918 if (type == OCI_TYPECODE_CLOB)
919 {
920 pBuffers[i].pData = NULL;
921 OCIDescriptorAlloc(pConn->handleEnv, (void **)&pBuffers[i].lobLocator, OCI_DTYPE_LOB, 0, NULL);
922 handleDefine = NULL;
923 nStatus = OCIDefineByPos(handleStmt, &handleDefine, pConn->handleError, i + 1,
924 &pBuffers[i].lobLocator, 0, SQLT_CLOB, &pBuffers[i].isNull,
925 NULL, NULL, OCI_DEFAULT);
926 }
927 else
928 {
929 pBuffers[i].lobLocator = NULL;
930 OCIAttrGet(handleParam, OCI_DTYPE_PARAM, &nWidth, NULL, OCI_ATTR_DATA_SIZE, pConn->handleError);
931 pBuffers[i].pData = (UCS2CHAR *)malloc((nWidth + 31) * sizeof(UCS2CHAR));
932 handleDefine = NULL;
933 nStatus = OCIDefineByPos(handleStmt, &handleDefine, pConn->handleError, i + 1,
934 pBuffers[i].pData, (nWidth + 31) * sizeof(UCS2CHAR),
935 SQLT_CHR, &pBuffers[i].isNull, &pBuffers[i].nLength,
936 &pBuffers[i].nCode, OCI_DEFAULT);
937 }
938 if (nStatus != OCI_SUCCESS)
939 {
526ae8b8 940 SetLastError(pConn);
0fb0952d
VK
941 *pdwError = IsConnectionError(pConn);
942 }
ec19ded8
VK
943 OCIDescriptorFree(handleParam, OCI_DTYPE_PARAM);
944 }
945 else
946 {
526ae8b8 947 SetLastError(pConn);
ec19ded8
VK
948 *pdwError = IsConnectionError(pConn);
949 }
950 }
951
952 // Fetch data
953 if (nStatus == OCI_SUCCESS)
954 {
955 int nPos = 0;
956 while(1)
957 {
958 nStatus = OCIStmtFetch2(handleStmt, pConn->handleError, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);
959 if (nStatus == OCI_NO_DATA)
960 {
961 *pdwError = DBERR_SUCCESS; // EOF
962 break;
963 }
964 if ((nStatus != OCI_SUCCESS) && (nStatus != OCI_SUCCESS_WITH_INFO))
965 {
526ae8b8 966 SetLastError(pConn);
ec19ded8
VK
967 *pdwError = IsConnectionError(pConn);
968 break;
969 }
970
971 // New row
972 pResult->nRows++;
973 pResult->pData = (WCHAR **)realloc(pResult->pData, sizeof(WCHAR *) * pResult->nCols * pResult->nRows);
974 for(int i = 0; i < pResult->nCols; i++)
975 {
976 if (pBuffers[i].isNull)
977 {
978 pResult->pData[nPos] = (WCHAR *)nx_memdup("\0\0\0", sizeof(WCHAR));
979 }
0fb0952d
VK
980 else if (pBuffers[i].lobLocator != NULL)
981 {
982 ub4 length = 0;
983 ub4 amount = length;
984 OCILobGetLength(pConn->handleService, pConn->handleError, pBuffers[i].lobLocator, &length);
985 pResult->pData[nPos] = (WCHAR *)malloc((length + 1) * sizeof(WCHAR));
986#if UNICODE_UCS4
987 UCS2CHAR *ucs2buffer = (UCS2CHAR *)malloc(sizeof(UCS2CHAR) * length);
988 OCILobRead(pConn->handleService, pConn->handleError, pBuffers[i].lobLocator, &amount, 1,
989 ucs2buffer, length * sizeof(UCS2CHAR), NULL, NULL, OCI_UCS2ID, SQLCS_IMPLICIT);
990 ucs2_to_ucs4(ucs2buffer, length, pResult->pData[nPos], length + 1);
991 free(ucs2buffer);
992#else
993 OCILobRead(pConn->handleService, pConn->handleError, pBuffers[i].lobLocator, &amount, 1,
994 pResult->pData[nPos], (length + 1) * sizeof(WCHAR), NULL, NULL, OCI_UCS2ID, SQLCS_IMPLICIT);
995#endif
996 pResult->pData[nPos][length] = 0;
997 }
ec19ded8 998 else
5039dede 999 {
ec19ded8
VK
1000 int length = pBuffers[i].nLength / sizeof(UCS2CHAR);
1001 pResult->pData[nPos] = (WCHAR *)malloc((length + 1) * sizeof(WCHAR));
39945910 1002#if UNICODE_UCS4
ec19ded8 1003 ucs2_to_ucs4(pBuffers[i].pData, length, pResult->pData[nPos], length + 1);
39945910 1004#else
ec19ded8 1005 memcpy(pResult->pData[nPos], pBuffers[i].pData, pBuffers[i].nLength);
39945910 1006#endif
ec19ded8 1007 pResult->pData[nPos][length] = 0;
5039dede 1008 }
ec19ded8 1009 nPos++;
5039dede 1010 }
ec19ded8
VK
1011 }
1012 }
5039dede 1013
ec19ded8
VK
1014 // Cleanup
1015 for(int i = 0; i < pResult->nCols; i++)
0fb0952d 1016 {
ec19ded8 1017 safe_free(pBuffers[i].pData);
0fb0952d
VK
1018 if (pBuffers[i].lobLocator != NULL)
1019 {
1020 OCIDescriptorFree(pBuffers[i].lobLocator, OCI_DTYPE_LOB);
1021 }
1022 }
ec19ded8 1023 free(pBuffers);
5039dede 1024
ec19ded8
VK
1025 // Destroy results in case of error
1026 if (*pdwError != DBERR_SUCCESS)
1027 {
1028 DestroyQueryResult(pResult);
1029 pResult = NULL;
1030 }
1031 }
1032
1033 return pResult;
1034}
1035
d1c1c522
VK
1036/**
1037 * Perform SELECT query
1038 */
ec19ded8
VK
1039extern "C" DBDRV_RESULT EXPORT DrvSelect(ORACLE_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
1040{
1041 ORACLE_RESULT *pResult = NULL;
1042 OCIStmt *handleStmt;
1043
1044#if UNICODE_UCS4
1045 UCS2CHAR *ucs2Query = UCS2StringFromUCS4String(pwszQuery);
1046#else
1047 UCS2CHAR *ucs2Query = pwszQuery;
1048#endif
1049
c17f6cbc 1050 MutexLock(pConn->mutexQueryLock);
ec19ded8
VK
1051 if (OCIStmtPrepare2(pConn->handleService, &handleStmt, pConn->handleError, (text *)ucs2Query,
1052 (ub4)ucs2_strlen(ucs2Query) * sizeof(UCS2CHAR), NULL, 0, OCI_NTV_SYNTAX, OCI_DEFAULT) == OCI_SUCCESS)
1053 {
ede6a257 1054 OCIAttrSet(handleStmt, OCI_HTYPE_STMT, &pConn->prefetchLimit, 0, OCI_ATTR_PREFETCH_ROWS, pConn->handleError);
ec19ded8
VK
1055 if (OCIStmtExecute(pConn->handleService, handleStmt, pConn->handleError,
1056 0, 0, NULL, NULL, (pConn->nTransLevel == 0) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT) == OCI_SUCCESS)
1057 {
1058 pResult = ProcessQueryResults(pConn, handleStmt, pdwError);
5039dede
AK
1059 }
1060 else
1061 {
526ae8b8 1062 SetLastError(pConn);
5039dede
AK
1063 *pdwError = IsConnectionError(pConn);
1064 }
6f7c88fb 1065 OCIStmtRelease(handleStmt, pConn->handleError, NULL, 0, OCI_DEFAULT);
5039dede
AK
1066 }
1067 else
1068 {
526ae8b8 1069 SetLastError(pConn);
5039dede
AK
1070 *pdwError = IsConnectionError(pConn);
1071 }
5039dede 1072 if (errorText != NULL)
a3166a07 1073 {
526ae8b8 1074 wcsncpy(errorText, pConn->lastErrorText, DBDRV_MAX_ERROR_TEXT);
a3166a07
VK
1075 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
1076 }
5039dede 1077 MutexUnlock(pConn->mutexQueryLock);
39945910
VK
1078
1079#if UNICODE_UCS4
1080 free(ucs2Query);
1081#endif
5039dede
AK
1082 return pResult;
1083}
1084
d1c1c522
VK
1085/**
1086 * Perform SELECT query using prepared statement
1087 */
ec19ded8
VK
1088extern "C" DBDRV_RESULT EXPORT DrvSelectPrepared(ORACLE_CONN *pConn, ORACLE_STATEMENT *stmt, DWORD *pdwError, WCHAR *errorText)
1089{
1090 ORACLE_RESULT *pResult = NULL;
1091
c17f6cbc 1092 MutexLock(pConn->mutexQueryLock);
bf600f06 1093 OCIAttrSet(stmt->handleStmt, OCI_HTYPE_STMT, &pConn->prefetchLimit, 0, OCI_ATTR_PREFETCH_ROWS, pConn->handleError);
ec19ded8
VK
1094 if (OCIStmtExecute(pConn->handleService, stmt->handleStmt, pConn->handleError,
1095 0, 0, NULL, NULL, (pConn->nTransLevel == 0) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT) == OCI_SUCCESS)
1096 {
1097 pResult = ProcessQueryResults(pConn, stmt->handleStmt, pdwError);
1098 }
1099 else
1100 {
526ae8b8 1101 SetLastError(pConn);
ec19ded8
VK
1102 *pdwError = IsConnectionError(pConn);
1103 }
1104
1105 if (errorText != NULL)
1106 {
526ae8b8 1107 wcsncpy(errorText, pConn->lastErrorText, DBDRV_MAX_ERROR_TEXT);
ec19ded8
VK
1108 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
1109 }
1110 MutexUnlock(pConn->mutexQueryLock);
1111
1112 return pResult;
1113}
1114
d1c1c522
VK
1115/**
1116 * Get field length from result
1117 */
5039dede
AK
1118extern "C" LONG EXPORT DrvGetFieldLength(ORACLE_RESULT *pResult, int nRow, int nColumn)
1119{
1120 if (pResult == NULL)
07c9f3b0 1121 return -1;
5039dede
AK
1122
1123 if ((nRow >= 0) && (nRow < pResult->nRows) &&
1124 (nColumn >= 0) && (nColumn < pResult->nCols))
caac6e38 1125 return (LONG)wcslen(pResult->pData[pResult->nCols * nRow + nColumn]);
5039dede 1126
07c9f3b0 1127 return -1;
5039dede
AK
1128}
1129
d1c1c522
VK
1130/**
1131 * Get field value from result
1132 */
5039dede
AK
1133extern "C" WCHAR EXPORT *DrvGetField(ORACLE_RESULT *pResult, int nRow, int nColumn,
1134 WCHAR *pBuffer, int nBufLen)
1135{
1136 WCHAR *pValue = NULL;
1137
1138 if (pResult != NULL)
1139 {
1140 if ((nRow < pResult->nRows) && (nRow >= 0) &&
1141 (nColumn < pResult->nCols) && (nColumn >= 0))
1142 {
5502f4aa
VK
1143#ifdef _WIN32
1144 wcsncpy_s(pBuffer, nBufLen, pResult->pData[nRow * pResult->nCols + nColumn], _TRUNCATE);
1145#else
5039dede
AK
1146 wcsncpy(pBuffer, pResult->pData[nRow * pResult->nCols + nColumn], nBufLen);
1147 pBuffer[nBufLen - 1] = 0;
5502f4aa 1148#endif
5039dede
AK
1149 pValue = pBuffer;
1150 }
1151 }
1152 return pValue;
1153}
1154
0fb0952d
VK
1155/**
1156 * Get number of rows in result
1157 */
5039dede
AK
1158extern "C" int EXPORT DrvGetNumRows(ORACLE_RESULT *pResult)
1159{
1160 return (pResult != NULL) ? pResult->nRows : 0;
1161}
1162
0fb0952d
VK
1163/**
1164 * Get column count in query result
1165 */
2a8e6a7b
VK
1166extern "C" int EXPORT DrvGetColumnCount(ORACLE_RESULT *pResult)
1167{
1168 return (pResult != NULL) ? pResult->nCols : 0;
1169}
1170
0fb0952d
VK
1171/**
1172 * Get column name in query result
1173 */
2a8e6a7b
VK
1174extern "C" const char EXPORT *DrvGetColumnName(ORACLE_RESULT *pResult, int column)
1175{
1176 return ((pResult != NULL) && (column >= 0) && (column < pResult->nCols)) ? pResult->columnNames[column] : NULL;
1177}
1178
d1c1c522
VK
1179/**
1180 * Free SELECT results
1181 */
5039dede
AK
1182extern "C" void EXPORT DrvFreeResult(ORACLE_RESULT *pResult)
1183{
1184 if (pResult != NULL)
1185 DestroyQueryResult(pResult);
1186}
1187
792038b9
VK
1188/**
1189 * Destroy unbuffered query result
1190 */
1191static void DestroyUnbufferedQueryResult(ORACLE_UNBUFFERED_RESULT *result, bool freeStatement)
1192{
1193 int i;
1194
1195 if (freeStatement)
1196 OCIStmtRelease(result->handleStmt, result->connection->handleError, NULL, 0, OCI_DEFAULT);
1197
1198 for(i = 0; i < result->nCols; i++)
1199 {
1200 free(result->pBuffers[i].pData);
1201 if (result->pBuffers[i].lobLocator != NULL)
1202 {
1203 OCIDescriptorFree(result->pBuffers[i].lobLocator, OCI_DTYPE_LOB);
1204 }
1205 }
1206 free(result->pBuffers);
1207
1208 for(i = 0; i < result->nCols; i++)
1209 free(result->columnNames[i]);
1210 free(result->columnNames);
1211
1212 free(result);
1213}
1214
d1c1c522 1215/**
f17cf019 1216 * Process results of unbuffered query execution (prepare for fetching results)
d1c1c522 1217 */
f17cf019 1218static ORACLE_UNBUFFERED_RESULT *ProcessUnbufferedQueryResults(ORACLE_CONN *pConn, OCIStmt *handleStmt, DWORD *pdwError)
5039dede 1219{
f17cf019
VK
1220 ORACLE_UNBUFFERED_RESULT *result = (ORACLE_UNBUFFERED_RESULT *)malloc(sizeof(ORACLE_UNBUFFERED_RESULT));
1221 result->handleStmt = handleStmt;
1222 result->connection = pConn;
1223
1224 ub4 nCount;
1225 OCIAttrGet(result->handleStmt, OCI_HTYPE_STMT, &nCount, NULL, OCI_ATTR_PARAM_COUNT, pConn->handleError);
1226 result->nCols = nCount;
1227 if (result->nCols > 0)
1228 {
1229 // Prepare receive buffers and fetch column names
792038b9
VK
1230 result->columnNames = (char **)calloc(result->nCols, sizeof(char *));
1231 result->pBuffers = (ORACLE_FETCH_BUFFER *)calloc(result->nCols, sizeof(ORACLE_FETCH_BUFFER));
f17cf019
VK
1232 for(int i = 0; i < result->nCols; i++)
1233 {
1234 OCIParam *handleParam;
1235
1236 result->pBuffers[i].isNull = 1; // Mark all columns as NULL initially
1237 if (OCIParamGet(result->handleStmt, OCI_HTYPE_STMT, pConn->handleError,
1238 (void **)&handleParam, (ub4)(i + 1)) == OCI_SUCCESS)
1239 {
1240 // Column name
1241 text *colName;
1242 if (OCIAttrGet(handleParam, OCI_DTYPE_PARAM, &colName, &nCount, OCI_ATTR_NAME, pConn->handleError) == OCI_SUCCESS)
1243 {
1244 // We are in UTF-16 mode, so OCIAttrGet will return UTF-16 strings
1245 result->columnNames[i] = MBStringFromUCS2String((UCS2CHAR *)colName);
1246 }
1247 else
1248 {
1249 result->columnNames[i] = strdup("");
1250 }
1251
1252 // Data size
1253 sword nStatus;
1254 ub2 type = 0;
1255 OCIAttrGet(handleParam, OCI_DTYPE_PARAM, &type, NULL, OCI_ATTR_DATA_TYPE, pConn->handleError);
1256 OCIDefine *handleDefine;
1257 if (type == OCI_TYPECODE_CLOB)
1258 {
1259 result->pBuffers[i].pData = NULL;
1260 OCIDescriptorAlloc(pConn->handleEnv, (void **)&result->pBuffers[i].lobLocator, OCI_DTYPE_LOB, 0, NULL);
1261 handleDefine = NULL;
1262 nStatus = OCIDefineByPos(result->handleStmt, &handleDefine, pConn->handleError, i + 1,
1263 &result->pBuffers[i].lobLocator, 0, SQLT_CLOB, &result->pBuffers[i].isNull,
1264 NULL, NULL, OCI_DEFAULT);
1265 }
1266 else
1267 {
1268 ub2 nWidth;
1269 result->pBuffers[i].lobLocator = NULL;
1270 OCIAttrGet(handleParam, OCI_DTYPE_PARAM, &nWidth, NULL, OCI_ATTR_DATA_SIZE, pConn->handleError);
1271 result->pBuffers[i].pData = (UCS2CHAR *)malloc((nWidth + 31) * sizeof(UCS2CHAR));
1272 handleDefine = NULL;
1273 nStatus = OCIDefineByPos(result->handleStmt, &handleDefine, pConn->handleError, i + 1,
1274 result->pBuffers[i].pData, (nWidth + 31) * sizeof(UCS2CHAR),
1275 SQLT_CHR, &result->pBuffers[i].isNull, &result->pBuffers[i].nLength,
1276 &result->pBuffers[i].nCode, OCI_DEFAULT);
1277 }
792038b9 1278 OCIDescriptorFree(handleParam, OCI_DTYPE_PARAM);
f17cf019
VK
1279 if (nStatus == OCI_SUCCESS)
1280 {
1281 *pdwError = DBERR_SUCCESS;
1282 }
1283 else
1284 {
1285 SetLastError(pConn);
1286 *pdwError = IsConnectionError(pConn);
792038b9
VK
1287 DestroyUnbufferedQueryResult(result, false);
1288 result = NULL;
1289 break;
f17cf019 1290 }
f17cf019
VK
1291 }
1292 else
1293 {
1294 SetLastError(pConn);
1295 *pdwError = IsConnectionError(pConn);
792038b9 1296 DestroyUnbufferedQueryResult(result, false);
f17cf019
VK
1297 result = NULL;
1298 break;
1299 }
1300 }
1301 }
1302 else
1303 {
1304 free(result);
1305 result = NULL;
1306 }
1307
1308 return result;
1309}
1310
1311/**
1312 * Perform unbuffered SELECT query
1313 */
1314extern "C" DBDRV_UNBUFFERED_RESULT EXPORT DrvSelectUnbuffered(ORACLE_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, WCHAR *errorText)
1315{
1316 ORACLE_UNBUFFERED_RESULT *result = NULL;
5039dede 1317
1d3c6b50
VK
1318#if UNICODE_UCS4
1319 UCS2CHAR *ucs2Query = UCS2StringFromUCS4String(pwszQuery);
1320#else
1321 UCS2CHAR *ucs2Query = pwszQuery;
1322#endif
1323
c17f6cbc 1324 MutexLock(pConn->mutexQueryLock);
5039dede 1325
f17cf019
VK
1326 OCIStmt *handleStmt;
1327 if (OCIStmtPrepare2(pConn->handleService, &handleStmt, pConn->handleError, (text *)ucs2Query,
6f7c88fb 1328 (ub4)ucs2_strlen(ucs2Query) * sizeof(UCS2CHAR), NULL, 0, OCI_NTV_SYNTAX, OCI_DEFAULT) == OCI_SUCCESS)
5039dede 1329 {
f17cf019
VK
1330 OCIAttrSet(handleStmt, OCI_HTYPE_STMT, &pConn->prefetchLimit, 0, OCI_ATTR_PREFETCH_ROWS, pConn->handleError);
1331 if (OCIStmtExecute(pConn->handleService, handleStmt, pConn->handleError,
1f9abbf3 1332 0, 0, NULL, NULL, (pConn->nTransLevel == 0) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT) == OCI_SUCCESS)
5039dede 1333 {
f17cf019 1334 result = ProcessUnbufferedQueryResults(pConn, handleStmt, pdwError);
5039dede
AK
1335 }
1336 else
1337 {
526ae8b8 1338 SetLastError(pConn);
5039dede
AK
1339 *pdwError = IsConnectionError(pConn);
1340 }
1341 }
1342 else
1343 {
526ae8b8 1344 SetLastError(pConn);
5039dede
AK
1345 *pdwError = IsConnectionError(pConn);
1346 }
1347
1d3c6b50
VK
1348#if UNICODE_UCS4
1349 free(ucs2Query);
1350#endif
1351
f17cf019
VK
1352 if ((*pdwError == DBERR_SUCCESS) && (result != NULL))
1353 return result;
5039dede
AK
1354
1355 // On failure, unlock query mutex and do cleanup
f17cf019 1356 OCIStmtRelease(handleStmt, pConn->handleError, NULL, 0, OCI_DEFAULT);
5039dede 1357 if (errorText != NULL)
a3166a07 1358 {
526ae8b8 1359 wcsncpy(errorText, pConn->lastErrorText, DBDRV_MAX_ERROR_TEXT);
a3166a07
VK
1360 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
1361 }
5039dede
AK
1362 MutexUnlock(pConn->mutexQueryLock);
1363 return NULL;
1364}
1365
0fb0952d 1366/**
f17cf019 1367 * Perform SELECT query using prepared statement
0fb0952d 1368 */
f17cf019
VK
1369extern "C" DBDRV_UNBUFFERED_RESULT EXPORT DrvSelectPreparedUnbuffered(ORACLE_CONN *pConn, ORACLE_STATEMENT *stmt, DWORD *pdwError, WCHAR *errorText)
1370{
1371 ORACLE_UNBUFFERED_RESULT *result = NULL;
1372
1373 MutexLock(pConn->mutexQueryLock);
1374
1375 OCIAttrSet(stmt->handleStmt, OCI_HTYPE_STMT, &pConn->prefetchLimit, 0, OCI_ATTR_PREFETCH_ROWS, pConn->handleError);
1376 if (OCIStmtExecute(pConn->handleService, stmt->handleStmt, pConn->handleError,
1377 0, 0, NULL, NULL, (pConn->nTransLevel == 0) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT) == OCI_SUCCESS)
1378 {
1379 result = ProcessUnbufferedQueryResults(pConn, stmt->handleStmt, pdwError);
1380 }
1381 else
1382 {
1383 SetLastError(pConn);
1384 *pdwError = IsConnectionError(pConn);
1385 }
1386
1387 if ((*pdwError == DBERR_SUCCESS) && (result != NULL))
1388 return result;
1389
1390 // On failure, unlock query mutex and do cleanup
1391 if (errorText != NULL)
1392 {
1393 wcsncpy(errorText, pConn->lastErrorText, DBDRV_MAX_ERROR_TEXT);
1394 errorText[DBDRV_MAX_ERROR_TEXT - 1] = 0;
1395 }
1396 MutexUnlock(pConn->mutexQueryLock);
1397 return NULL;
1398}
1399
1400/**
1401 * Fetch next result line from unbuffered SELECT results
1402 */
1403extern "C" bool EXPORT DrvFetch(ORACLE_UNBUFFERED_RESULT *result)
5039dede 1404{
750d59f2 1405 bool success;
f694a7bc 1406
f17cf019 1407 if (result == NULL)
750d59f2 1408 return false;
f694a7bc 1409
f17cf019 1410 sword rc = OCIStmtFetch2(result->handleStmt, result->connection->handleError, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);
f694a7bc
VK
1411 if ((rc == OCI_SUCCESS) || (rc == OCI_SUCCESS_WITH_INFO))
1412 {
750d59f2 1413 success = true;
f694a7bc
VK
1414 }
1415 else
1416 {
f17cf019 1417 SetLastError(result->connection);
750d59f2 1418 success = false;
f694a7bc
VK
1419 }
1420 return success;
5039dede
AK
1421}
1422
0fb0952d 1423/**
f17cf019 1424 * Get field length from current row in unbuffered query result
0fb0952d 1425 */
f17cf019 1426extern "C" LONG EXPORT DrvGetFieldLengthUnbuffered(ORACLE_UNBUFFERED_RESULT *result, int nColumn)
61f032f5 1427{
f17cf019 1428 if (result == NULL)
61f032f5
VK
1429 return 0;
1430
f17cf019 1431 if ((nColumn < 0) || (nColumn >= result->nCols))
61f032f5
VK
1432 return 0;
1433
f17cf019 1434 if (result->pBuffers[nColumn].isNull)
61f032f5
VK
1435 return 0;
1436
f17cf019 1437 if (result->pBuffers[nColumn].lobLocator != NULL)
0fb0952d
VK
1438 {
1439 ub4 length = 0;
f17cf019 1440 OCILobGetLength(result->connection->handleService, result->connection->handleError, result->pBuffers[nColumn].lobLocator, &length);
0fb0952d
VK
1441 return (LONG)length;
1442 }
1443
f17cf019 1444 return (LONG)(result->pBuffers[nColumn].nLength / sizeof(UCS2CHAR));
61f032f5
VK
1445}
1446
0fb0952d 1447/**
f17cf019 1448 * Get field from current row in unbuffered query result
0fb0952d 1449 */
f17cf019 1450extern "C" WCHAR EXPORT *DrvGetFieldUnbuffered(ORACLE_UNBUFFERED_RESULT *result, int nColumn, WCHAR *pBuffer, int nBufSize)
5039dede
AK
1451{
1452 int nLen;
1453
f17cf019 1454 if (result == NULL)
5039dede
AK
1455 return NULL;
1456
f17cf019 1457 if ((nColumn < 0) || (nColumn >= result->nCols))
5039dede
AK
1458 return NULL;
1459
f17cf019 1460 if (result->pBuffers[nColumn].isNull)
5039dede
AK
1461 {
1462 *pBuffer = 0;
1463 }
f17cf019 1464 else if (result->pBuffers[nColumn].lobLocator != NULL)
0fb0952d
VK
1465 {
1466 ub4 length = 0;
f17cf019 1467 OCILobGetLength(result->connection->handleService, result->connection->handleError, result->pBuffers[nColumn].lobLocator, &length);
0fb0952d 1468
383c8f2a 1469 nLen = min(nBufSize - 1, (int)length);
0fb0952d
VK
1470 ub4 amount = nLen;
1471#if UNICODE_UCS4
1472 UCS2CHAR *ucs2buffer = (UCS2CHAR *)malloc(nLen * sizeof(UCS2CHAR));
f17cf019 1473 OCILobRead(result->connection->handleService, result->connection->handleError, result->pBuffers[nColumn].lobLocator, &amount, 1,
0fb0952d
VK
1474 ucs2buffer, nLen * sizeof(UCS2CHAR), NULL, NULL, OCI_UCS2ID, SQLCS_IMPLICIT);
1475 ucs2_to_ucs4(ucs2buffer, nLen, pBuffer, nLen);
1476 free(ucs2buffer);
1477#else
f17cf019 1478 OCILobRead(result->connection->handleService, result->connection->handleError, result->pBuffers[nColumn].lobLocator, &amount, 1,
0fb0952d
VK
1479 pBuffer, nBufSize * sizeof(WCHAR), NULL, NULL, OCI_UCS2ID, SQLCS_IMPLICIT);
1480#endif
1481 pBuffer[nLen] = 0;
1482 }
5039dede
AK
1483 else
1484 {
f17cf019 1485 nLen = min(nBufSize - 1, ((int)(result->pBuffers[nColumn].nLength / sizeof(UCS2CHAR))));
39945910 1486#if UNICODE_UCS4
f17cf019 1487 ucs2_to_ucs4(result->pBuffers[nColumn].pData, nLen, pBuffer, nLen + 1);
39945910 1488#else
f17cf019 1489 memcpy(pBuffer, result->pBuffers[nColumn].pData, nLen * sizeof(WCHAR));
39945910 1490#endif
5039dede
AK
1491 pBuffer[nLen] = 0;
1492 }
1493
1494 return pBuffer;
1495}
1496
0fb0952d 1497/**
f17cf019 1498 * Get column count in unbuffered query result
0fb0952d 1499 */
f17cf019 1500extern "C" int EXPORT DrvGetColumnCountUnbuffered(ORACLE_UNBUFFERED_RESULT *result)
2a8e6a7b 1501{
f17cf019 1502 return (result != NULL) ? result->nCols : 0;
2a8e6a7b
VK
1503}
1504
0fb0952d 1505/**
f17cf019 1506 * Get column name in unbuffered query result
0fb0952d 1507 */
f17cf019 1508extern "C" const char EXPORT *DrvGetColumnNameUnbuffered(ORACLE_UNBUFFERED_RESULT *result, int column)
2a8e6a7b 1509{
f17cf019 1510 return ((result != NULL) && (column >= 0) && (column < result->nCols)) ? result->columnNames[column] : NULL;
2a8e6a7b
VK
1511}
1512
0fb0952d 1513/**
f17cf019 1514 * Destroy result of unbuffered query
0fb0952d 1515 */
f17cf019 1516extern "C" void EXPORT DrvFreeUnbufferedResult(ORACLE_UNBUFFERED_RESULT *result)
5039dede 1517{
f17cf019 1518 if (result == NULL)
5039dede
AK
1519 return;
1520
792038b9
VK
1521 MUTEX mutex = result->connection->mutexQueryLock;
1522 DestroyUnbufferedQueryResult(result, true);
1523 MutexUnlock(mutex);
5039dede
AK
1524}
1525
0fb0952d
VK
1526/**
1527 * Begin transaction
1528 */
5039dede
AK
1529extern "C" DWORD EXPORT DrvBegin(ORACLE_CONN *pConn)
1530{
5039dede 1531 if (pConn == NULL)
1d3c6b50 1532 return DBERR_INVALID_HANDLE;
5039dede 1533
c17f6cbc 1534 MutexLock(pConn->mutexQueryLock);
1f9abbf3 1535 pConn->nTransLevel++;
5039dede 1536 MutexUnlock(pConn->mutexQueryLock);
1f9abbf3 1537 return DBERR_SUCCESS;
5039dede
AK
1538}
1539
0fb0952d
VK
1540/**
1541 * Commit transaction
1542 */
5039dede
AK
1543extern "C" DWORD EXPORT DrvCommit(ORACLE_CONN *pConn)
1544{
1d3c6b50 1545 DWORD dwResult;
5039dede
AK
1546
1547 if (pConn == NULL)
1d3c6b50 1548 return DBERR_INVALID_HANDLE;
5039dede 1549
c17f6cbc 1550 MutexLock(pConn->mutexQueryLock);
5039dede
AK
1551 if (pConn->nTransLevel > 0)
1552 {
1553 if (OCITransCommit(pConn->handleService, pConn->handleError, OCI_DEFAULT) == OCI_SUCCESS)
1554 {
1555 dwResult = DBERR_SUCCESS;
1f9abbf3 1556 pConn->nTransLevel = 0;
5039dede
AK
1557 }
1558 else
1559 {
526ae8b8 1560 SetLastError(pConn);
5039dede
AK
1561 dwResult = IsConnectionError(pConn);
1562 }
1563 }
1564 else
1565 {
1566 dwResult = DBERR_SUCCESS;
1567 }
1568 MutexUnlock(pConn->mutexQueryLock);
1d3c6b50 1569 return dwResult;
5039dede
AK
1570}
1571
0fb0952d
VK
1572/**
1573 * Rollback transaction
1574 */
5039dede
AK
1575extern "C" DWORD EXPORT DrvRollback(ORACLE_CONN *pConn)
1576{
1d3c6b50 1577 DWORD dwResult;
5039dede
AK
1578
1579 if (pConn == NULL)
1d3c6b50 1580 return DBERR_INVALID_HANDLE;
5039dede 1581
c17f6cbc 1582 MutexLock(pConn->mutexQueryLock);
5039dede
AK
1583 if (pConn->nTransLevel > 0)
1584 {
1585 if (OCITransRollback(pConn->handleService, pConn->handleError, OCI_DEFAULT) == OCI_SUCCESS)
1586 {
1587 dwResult = DBERR_SUCCESS;
1f9abbf3 1588 pConn->nTransLevel = 0;
5039dede
AK
1589 }
1590 else
1591 {
526ae8b8 1592 SetLastError(pConn);
5039dede
AK
1593 dwResult = IsConnectionError(pConn);
1594 }
1595 }
1596 else
1597 {
1598 dwResult = DBERR_SUCCESS;
1599 }
1600 MutexUnlock(pConn->mutexQueryLock);
1d3c6b50 1601 return dwResult;
5039dede
AK
1602}
1603
daf3c104
VK
1604/**
1605 * Check if table exist
1606 */
1607extern "C" int EXPORT DrvIsTableExist(ORACLE_CONN *pConn, const WCHAR *name)
1608{
1609 WCHAR query[256];
fdf1ed50 1610 swprintf(query, 256, L"SELECT count(*) FROM user_tables WHERE table_name=upper('%ls')", name);
daf3c104
VK
1611 DWORD error;
1612 WCHAR errorText[DBDRV_MAX_ERROR_TEXT];
1613 int rc = DBIsTableExist_Failure;
1614 ORACLE_RESULT *hResult = (ORACLE_RESULT *)DrvSelect(pConn, query, &error, errorText);
1615 if (hResult != NULL)
1616 {
1617 WCHAR buffer[64] = L"";
1618 DrvGetField(hResult, 0, 0, buffer, 64);
1619 rc = (wcstol(buffer, NULL, 10) > 0) ? DBIsTableExist_Found : DBIsTableExist_NotFound;
1620 DrvFreeResult(hResult);
1621 }
1622 return rc;
1623}
1624
5039dede
AK
1625#ifdef _WIN32
1626
5d255340
VK
1627/**
1628 * DLL Entry point
1629 */
750d59f2 1630bool WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
5039dede 1631{
1d3c6b50
VK
1632 if (dwReason == DLL_PROCESS_ATTACH)
1633 DisableThreadLibraryCalls(hInstance);
750d59f2 1634 return true;
5039dede
AK
1635}
1636
1637#endif