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