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