1 /*
2 * Copyright (C) 2010-2013 Volodymyr Tarasenko <tvntsr@yahoo.com>
3 * 2010 Sergey Pavlov <sergey.pavlov@gmail.com>
4 * 2010 PortaOne Inc.
5 * Copyright (C) Tildeslash Ltd. All rights reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25
26 #include "Config.h"
27 #include "Thread.h"
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <string.h>
33
34 #include "OracleAdapter.h"
35 #include "StringBuffer.h"
36 #include "ConnectionDelegate.h"
37
38
39 /**
40 * Implementation of the Connection/Delegate interface for oracle.
41 *
42 * @file
43 */
44
45
46 /* ----------------------------------------------------------- Definitions */
47
48
49 #define ERB_SIZE 152
50 #define ORACLE_TRANSACTION_PERIOD 10
51 #define T ConnectionDelegate_T
52 struct T {
53 Connection_T delegator;
54 OCIEnv* env;
55 OCIError* err;
56 OCISvcCtx* svc;
57 OCISession* usr;
58 OCIServer* srv;
59 OCITrans* txnhp;
60 char erb[ERB_SIZE];
61 int maxRows;
62 int timeout;
63 int countdown;
64 sword lastError;
65 ub4 rowsChanged;
66 StringBuffer_T sb;
67 Thread_T watchdog;
68 char running;
69 };
70 extern const struct Rop_T oraclerops;
71 extern const struct Pop_T oraclepops;
72
73
74 /* ------------------------------------------------------- Private methods */
75
76
_getErrorDescription(T C)77 static const char *_getErrorDescription(T C) {
78 sb4 errcode;
79 switch (C->lastError)
80 {
81 case OCI_SUCCESS:
82 return "";
83 case OCI_SUCCESS_WITH_INFO:
84 return "Info - OCI_SUCCESS_WITH_INFO";
85 break;
86 case OCI_NEED_DATA:
87 return "Error - OCI_NEED_DATA";
88 break;
89 case OCI_NO_DATA:
90 return "Error - OCI_NODATA";
91 break;
92 case OCI_ERROR:
93 (void) OCIErrorGet(C->err, 1, NULL, &errcode, C->erb, (ub4)ERB_SIZE, OCI_HTYPE_ERROR);
94 return C->erb;
95 break;
96 case OCI_INVALID_HANDLE:
97 return "Error - OCI_INVALID_HANDLE";
98 break;
99 case OCI_STILL_EXECUTING:
100 return "Error - OCI_STILL_EXECUTE";
101 break;
102 case OCI_CONTINUE:
103 return "Error - OCI_CONTINUE";
104 break;
105 default:
106 break;
107 }
108 return C->erb;
109 }
110
111
_doConnect(T C,char ** error)112 static bool _doConnect(T C, char** error) {
113 #define ERROR(e) do {*error = Str_dup(e); return false;} while (0)
114 #define ORAERROR(e) do{ *error = Str_dup(_getErrorDescription(e)); return false;} while(0)
115 URL_T url = Connection_getURL(C->delegator);
116 const char *servicename, *username, *password;
117 const char *host = URL_getHost(url);
118 int port = URL_getPort(url);
119 if (! (username = URL_getUser(url)))
120 if (! (username = URL_getParameter(url, "user")))
121 ERROR("no username specified in URL");
122 if (! (password = URL_getPassword(url)))
123 if (! (password = URL_getParameter(url, "password")))
124 ERROR("no password specified in URL");
125 if (! (servicename = URL_getPath(url)))
126 ERROR("no Service Name specified in URL");
127 ++servicename;
128 /* Create a thread-safe OCI environment with N' substitution turned on. */
129 if (OCIEnvCreate(&C->env, OCI_THREADED | OCI_OBJECT | OCI_NCHAR_LITERAL_REPLACE_ON, 0, 0, 0, 0, 0, 0))
130 ERROR("Create a OCI environment failed");
131 /* allocate an error handle */
132 if (OCI_SUCCESS != OCIHandleAlloc(C->env, (dvoid**)&C->err, OCI_HTYPE_ERROR, 0, 0))
133 ERROR("Allocating error handler failed");
134 /* server contexts */
135 if (OCI_SUCCESS != OCIHandleAlloc(C->env, (dvoid**)&C->srv, OCI_HTYPE_SERVER, 0, 0))
136 ERROR("Allocating server context failed");
137 /* allocate a service handle */
138 if (OCI_SUCCESS != OCIHandleAlloc(C->env, (dvoid**)&C->svc, OCI_HTYPE_SVCCTX, 0, 0))
139 ERROR("Allocating service handle failed");
140 StringBuffer_clear(C->sb);
141 /* Oracle connect string is on the form: //host[:port]/service name */
142 if (host) {
143 StringBuffer_append(C->sb, "//%s", host);
144 if (port > 0)
145 StringBuffer_append(C->sb, ":%d", port);
146 StringBuffer_append(C->sb, "/%s", servicename);
147 } else /* Or just service name */
148 StringBuffer_append(C->sb, "%s", servicename);
149 // Set Connection ResultSet fetch size if found in URL
150 const char *fetchSize = URL_getParameter(url, "fetch-size");
151 if (fetchSize) {
152 int rows = Str_parseInt(fetchSize);
153 if (rows < 1)
154 ERROR("invalid fetch-size");
155 Connection_setFetchSize(C->delegator, rows);
156 }
157 /* Create a server context */
158 C->lastError = OCIServerAttach(C->srv, C->err, StringBuffer_toString(C->sb), StringBuffer_length(C->sb), OCI_DEFAULT);
159 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
160 ORAERROR(C);
161 /* Set attribute server context in the service context */
162 C->lastError = OCIAttrSet(C->svc, OCI_HTYPE_SVCCTX, C->srv, 0, OCI_ATTR_SERVER, C->err);
163 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
164 ORAERROR(C);
165 C->lastError = OCIHandleAlloc(C->env, (void**)&C->usr, OCI_HTYPE_SESSION, 0, NULL);
166 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
167 ORAERROR(C);
168 C->lastError = OCIAttrSet(C->usr, OCI_HTYPE_SESSION, (dvoid *)username, (int)strlen(username), OCI_ATTR_USERNAME, C->err);
169 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
170 ORAERROR(C);
171 C->lastError = OCIAttrSet(C->usr, OCI_HTYPE_SESSION, (dvoid *)password, (int)strlen(password), OCI_ATTR_PASSWORD, C->err);
172 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
173 ORAERROR(C);
174 ub4 sessionFlags = OCI_DEFAULT;
175 if (IS(URL_getParameter(url, "sysdba"), "true")) {
176 sessionFlags |= OCI_SYSDBA;
177 }
178 C->lastError = OCISessionBegin(C->svc, C->err, C->usr, OCI_CRED_RDBMS, sessionFlags);
179 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
180 ORAERROR(C);
181 OCIAttrSet(C->svc, OCI_HTYPE_SVCCTX, C->usr, 0, OCI_ATTR_SESSION, C->err);
182 return true;
183 }
184
185
WATCHDOG(watchdog,T)186 WATCHDOG(watchdog, T)
187
188
189 /* -------------------------------------------------------- Delegate Methods */
190
191
192 static const char *_getLastError(T C) {
193 return _getErrorDescription(C);
194 }
195
_free(T * C)196 static void _free(T* C) {
197 assert(C && *C);
198 if ((*C)->svc) {
199 OCISessionEnd((*C)->svc, (*C)->err, (*C)->usr, OCI_DEFAULT);
200 (*C)->svc = NULL;
201 }
202 if ((*C)->srv)
203 OCIServerDetach((*C)->srv, (*C)->err, OCI_DEFAULT);
204 if ((*C)->env)
205 OCIHandleFree((*C)->env, OCI_HTYPE_ENV);
206 StringBuffer_free(&((*C)->sb));
207 if ((*C)->watchdog)
208 Thread_join((*C)->watchdog);
209 FREE(*C);
210 }
211
212
_new(Connection_T delegator,char ** error)213 static T _new(Connection_T delegator, char **error) {
214 T C;
215 assert(delegator);
216 assert(error);
217 NEW(C);
218 C->delegator = delegator;
219 C->sb = StringBuffer_create(STRLEN);
220 if (! _doConnect(C, error)) {
221 _free(&C);
222 return NULL;
223 }
224 C->txnhp = NULL;
225 C->running = false;
226 return C;
227 }
228
229
_ping(T C)230 static bool _ping(T C) {
231 assert(C);
232 C->lastError = OCIPing(C->svc, C->err, OCI_DEFAULT);
233 return (C->lastError == OCI_SUCCESS);
234 }
235
236
_setQueryTimeout(T C,int ms)237 static void _setQueryTimeout(T C, int ms) {
238 assert(C);
239 assert(ms >= 0);
240 C->timeout = ms;
241 if (ms > 0) {
242 if (!C->watchdog) {
243 Thread_create(C->watchdog, watchdog, C);
244 }
245 } else {
246 if (C->watchdog) {
247 OCISvcCtx* t = C->svc;
248 C->svc = NULL;
249 Thread_join(C->watchdog);
250 C->svc = t;
251 C->watchdog = NULL;
252 }
253 }
254 }
255
256
_beginTransaction(T C)257 static bool _beginTransaction(T C) {
258 assert(C);
259 if (C->txnhp == NULL) /* Allocate handler only once, if it is necessary */
260 {
261 /* allocate transaction handle and set it in the service handle */
262 C->lastError = OCIHandleAlloc(C->env, (void **)&C->txnhp, OCI_HTYPE_TRANS, 0, 0);
263 if (C->lastError != OCI_SUCCESS)
264 return false;
265 OCIAttrSet(C->svc, OCI_HTYPE_SVCCTX, (void *)C->txnhp, 0, OCI_ATTR_TRANS, C->err);
266 }
267 C->lastError = OCITransStart (C->svc, C->err, ORACLE_TRANSACTION_PERIOD, OCI_TRANS_NEW);
268 return (C->lastError == OCI_SUCCESS);
269 }
270
271
_commit(T C)272 static bool _commit(T C) {
273 assert(C);
274 C->lastError = OCITransCommit(C->svc, C->err, OCI_DEFAULT);
275 return C->lastError == OCI_SUCCESS;
276 }
277
278
_rollback(T C)279 static bool _rollback(T C) {
280 assert(C);
281 C->lastError = OCITransRollback(C->svc, C->err, OCI_DEFAULT);
282 return C->lastError == OCI_SUCCESS;
283 }
284
285
_lastRowId(T C)286 static long long _lastRowId(T C) {
287 /*:FIXME:*/
288 /*
289 Oracle's RowID can be mapped on string only
290 so, currently I leave it unimplemented
291 */
292
293 /* OCIRowid* rowid; */
294 /* OCIDescriptorAlloc((dvoid *)C->env, */
295 /* (dvoid **)&rowid, */
296 /* (ub4) OCI_DTYPE_ROWID, */
297 /* (size_t) 0, (dvoid **) 0); */
298
299 /* if (OCIAttrGet (select_p, */
300 /* OCI_HTYPE_STMT, */
301 /* &rowid, /\* get the current rowid *\/ */
302 /* 0, */
303 /* OCI_ATTR_ROWID, */
304 /* errhp)) */
305 /* { */
306 /* printf ("Getting the Rowid failed \n"); */
307 /* return (OCI_ERROR); */
308 /* } */
309
310 /* OCIDescriptorFree(rowid, OCI_DTYPE_ROWID); */
311 DEBUG("OracleConnection_lastRowId: Not implemented yet");
312 return -1;
313 }
314
315
_rowsChanged(T C)316 static long long _rowsChanged(T C) {
317 assert(C);
318 return C->rowsChanged;
319 }
320
321
_execute(T C,const char * sql,va_list ap)322 static bool _execute(T C, const char *sql, va_list ap) {
323 OCIStmt* stmtp;
324 va_list ap_copy;
325 assert(C);
326 C->rowsChanged = 0;
327 va_copy(ap_copy, ap);
328 StringBuffer_vset(C->sb, sql, ap_copy);
329 va_end(ap_copy);
330 StringBuffer_trim(C->sb);
331 /* Build statement */
332 C->lastError = OCIHandleAlloc(C->env, (void **)&stmtp, OCI_HTYPE_STMT, 0, NULL);
333 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
334 return false;
335 C->lastError = OCIStmtPrepare(stmtp, C->err, StringBuffer_toString(C->sb), StringBuffer_length(C->sb), OCI_NTV_SYNTAX, OCI_DEFAULT);
336 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO) {
337 OCIHandleFree(stmtp, OCI_HTYPE_STMT);
338 return false;
339 }
340 /* Execute */
341 if (C->timeout > 0) {
342 C->countdown = C->timeout;
343 C->running = true;
344 }
345 C->lastError = OCIStmtExecute(C->svc, stmtp, C->err, 1, 0, NULL, NULL, OCI_DEFAULT);
346 C->running = false;
347 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO) {
348 ub4 parmcnt = 0;
349 OCIAttrGet(stmtp, OCI_HTYPE_STMT, &parmcnt, NULL, OCI_ATTR_PARSE_ERROR_OFFSET, C->err);
350 DEBUG("Error occured in StmtExecute %d (%s), offset is %d\n", C->lastError, _getLastError(C), parmcnt);
351 OCIHandleFree(stmtp, OCI_HTYPE_STMT);
352 return false;
353 }
354 C->lastError = OCIAttrGet(stmtp, OCI_HTYPE_STMT, &C->rowsChanged, 0, OCI_ATTR_ROW_COUNT, C->err);
355 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
356 DEBUG("OracleConnection_execute: Error in OCIAttrGet %d (%s)\n", C->lastError, _getLastError(C));
357 OCIHandleFree(stmtp, OCI_HTYPE_STMT);
358 return C->lastError == OCI_SUCCESS;
359 }
360
361
_executeQuery(T C,const char * sql,va_list ap)362 static ResultSet_T _executeQuery(T C, const char *sql, va_list ap) {
363 OCIStmt* stmtp;
364 va_list ap_copy;
365 assert(C);
366 C->rowsChanged = 0;
367 va_copy(ap_copy, ap);
368 StringBuffer_vset(C->sb, sql, ap_copy);
369 va_end(ap_copy);
370 StringBuffer_trim(C->sb);
371 /* Build statement */
372 C->lastError = OCIHandleAlloc(C->env, (void **)&stmtp, OCI_HTYPE_STMT, 0, NULL);
373 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
374 return NULL;
375 C->lastError = OCIStmtPrepare(stmtp, C->err, StringBuffer_toString(C->sb), StringBuffer_length(C->sb), OCI_NTV_SYNTAX, OCI_DEFAULT);
376 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO) {
377 OCIHandleFree(stmtp, OCI_HTYPE_STMT);
378 return NULL;
379 }
380 /* Execute and create Result Set */
381 if (C->timeout > 0) {
382 C->countdown = C->timeout;
383 C->running = true;
384 }
385 C->lastError = OCIStmtExecute(C->svc, stmtp, C->err, 0, 0, NULL, NULL, OCI_DEFAULT);
386 C->running = false;
387 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO) {
388 ub4 parmcnt = 0;
389 OCIAttrGet(stmtp, OCI_HTYPE_STMT, &parmcnt, NULL, OCI_ATTR_PARSE_ERROR_OFFSET, C->err);
390 DEBUG("Error occured in StmtExecute %d (%s), offset is %d\n", C->lastError, _getLastError(C), parmcnt);
391 OCIHandleFree(stmtp, OCI_HTYPE_STMT);
392 return NULL;
393 }
394 C->lastError = OCIAttrGet(stmtp, OCI_HTYPE_STMT, &C->rowsChanged, 0, OCI_ATTR_ROW_COUNT, C->err);
395 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
396 DEBUG("OracleConnection_execute: Error in OCIAttrGet %d (%s)\n", C->lastError, _getLastError(C));
397 return ResultSet_new(OracleResultSet_new(C->delegator, stmtp, C->env, C->usr, C->err, C->svc, true), (Rop_T)&oraclerops);
398 }
399
400
_prepareStatement(T C,const char * sql,va_list ap)401 static PreparedStatement_T _prepareStatement(T C, const char *sql, va_list ap) {
402 OCIStmt *stmtp;
403 va_list ap_copy;
404 assert(C);
405 va_copy(ap_copy, ap);
406 StringBuffer_vset(C->sb, sql, ap_copy);
407 va_end(ap_copy);
408 StringBuffer_trim(C->sb);
409 StringBuffer_prepare4oracle(C->sb);
410 /* Build statement */
411 C->lastError = OCIHandleAlloc(C->env, (void **)&stmtp, OCI_HTYPE_STMT, 0, 0);
412 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO)
413 return NULL;
414 C->lastError = OCIStmtPrepare(stmtp, C->err, StringBuffer_toString(C->sb), StringBuffer_length(C->sb), OCI_NTV_SYNTAX, OCI_DEFAULT);
415 if (C->lastError != OCI_SUCCESS && C->lastError != OCI_SUCCESS_WITH_INFO) {
416 OCIHandleFree(stmtp, OCI_HTYPE_STMT);
417 return NULL;
418 }
419 return PreparedStatement_new(OraclePreparedStatement_new(C->delegator, stmtp, C->env, C->usr, C->err, C->svc), (Pop_T)&oraclepops);
420 }
421
422
423 /* ------------------------------------------------------------------------- */
424
425
426 const struct Cop_T oraclesqlcops = {
427 .name = "oracle",
428 .new = _new,
429 .free = _free,
430 .ping = _ping,
431 .setQueryTimeout = _setQueryTimeout,
432 .beginTransaction = _beginTransaction,
433 .commit = _commit,
434 .rollback = _rollback,
435 .lastRowId = _lastRowId,
436 .rowsChanged = _rowsChanged,
437 .execute = _execute,
438 .executeQuery = _executeQuery,
439 .prepareStatement = _prepareStatement,
440 .getLastError = _getLastError
441 };
442