First commit
[anna.git] / source / dbms.oracle / OutputBind.cpp
1 // ANNA - Anna is Not 'N' Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // https://bitbucket.org/testillano/anna
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 #include <oci.h>
38 #include <orl.h>
39
40 #include <anna/config/defines.hpp>
41 #include <anna/core/tracing/Logger.hpp>
42 #include <anna/core/DataBlock.hpp>
43
44 #include <anna/dbms/Float.hpp>
45 #include <anna/dbms/ShortBlock.hpp>
46 #include <anna/dbms/LongBlock.hpp>
47 #include <anna/dbms/String.hpp>
48 #include <anna/dbms/Date.hpp>
49 #include <anna/dbms/TimeStamp.hpp>
50
51 #include <anna/dbms.oracle/oracle.hpp>
52
53 using namespace anna;
54 using namespace std;
55
56 OutputBind::OutputBind(const char* name, dbms::Data& data) :
57   dbms::OutputBind(name, data),
58   BaseBind(data),
59   a_ociDefine(NULL) {
60 }
61
62 OutputBind::~OutputBind() {
63 }
64
65 void OutputBind::prepare(dbms::Statement* dbmsStatement, dbms::Connection* connection, const int pos)
66 throw(dbms::DatabaseException) {
67   if(a_ociDefine != NULL)
68     return;
69
70   Database& database(static_cast <Database&>(dbmsStatement->getDatabase()));
71   OCIError* error = database.getErrorHandler();
72   Statement* statement(static_cast <Statement*>(dbmsStatement));
73   dbms::Data& data = anna::dbms::Bind::getData();
74   oci_param ociparam = getOCIParam(database, static_cast <oracle::Connection*>(connection), data);
75
76   if(data.isNulleable() == false) {
77     anna_dbms_oracle_check(
78       OCIDefineByPos(
79         *statement, &a_ociDefine, error, pos, ociparam.buffer, ociparam.maxLength, ociparam.type,
80         0, ociparam.length, 0, OCI_DEFAULT
81       ),
82       error
83     );
84   } else {
85     anna_dbms_oracle_check(
86       OCIDefineByPos(
87         *statement, &a_ociDefine, error, pos, ociparam.buffer, ociparam.maxLength, ociparam.type,
88         &a_nullIndicator, ociparam.length, 0, OCI_DEFAULT
89       ),
90       error
91     );
92   }
93
94   LOGDEBUG(
95     std::string msg("anna::dbms::oracle::OutputBind::prepare | ");
96     msg += asString();
97     msg += " | Sentence: ";
98     msg += statement->getName();
99     msg += " | Position: ";
100     msg += functions::asString(pos);
101     Logger::debug(msg, ANNA_FILE_LOCATION)
102   );
103 }
104
105 //-------------------------------------------------------------------------------
106 // Transfiere la informacin obtenida desde Oracle. Todos los comprobados en �te
107 // m�odo son parametros que modificados al ejecuta la sentencia Oracle debido cmo
108 // hemos invocamo al m�odo OCIDefineByPos
109 //
110 // Todo esto se podr� haber hecho en la anna::dbms::DataBlock pero exigir�
111 // definir una clase distinta para cada RDBMS. Creo que los Binds particulares de
112 // cada base de datos se ocupen toda la complejidad de convertir los datos.
113 //
114 // (1) Truco para fijar el contenido y la longitud actual.
115 //-------------------------------------------------------------------------------
116 void OutputBind::decode() const
117 throw(RuntimeException) {
118   OutputBind* _this = const_cast <OutputBind*>(this);
119   char* str;
120   Data& data = _this->getData();
121   data.setNull(a_nullIndicator < 0);
122
123   switch(data.getType()) {
124   case Data::Type::String:
125     str = (char*) data.getBuffer();
126
127     if(data.isNull() == true)
128       *str = 0;
129     else
130       dbms::String::strip(str);
131
132     break;
133   case Data::Type::Float:
134     decodeFloat(data);
135     break;
136   case Data::Type::ShortBlock:
137     decodeShortBlock(data);
138     break;
139   case Data::Type::LongBlock:
140     decodeLongBlock(data);
141     break;
142   case Data::Type::Date:
143   case Data::Type::TimeStamp:
144
145     try {
146       decodeDate(data);
147     } catch(DatabaseException& edb) {
148       throw RuntimeException(edb);
149     }
150
151     break;
152   }
153 }
154
155 void OutputBind::decodeFloat(dbms::Data& data) const
156 throw(RuntimeException) {
157   dbms::Float& _float = static_cast <dbms::Float&>(data);
158
159   if(data.isNull() == true) {
160     _float = 0.0;
161     return;
162   }
163
164   char* _data = (char*) a_ofb->getData();
165   const char decimalPoint = oracle::Database::getDecimalPoint();
166
167   if(decimalPoint != 0) {
168     char* point = anna_strchr(_data, decimalPoint);
169
170     if(point != NULL)
171       *point = '.';
172   }
173
174   sscanf(_data, _float.getFormat(), (float*) _float.getBuffer());
175 }
176
177 void OutputBind::decodeShortBlock(dbms::Data& data) const
178 throw(RuntimeException) {
179   const anna::DataBlock& constdbms(static_cast <dbms::ShortBlock&>(data).getValue());
180   anna::DataBlock& dataBlock(const_cast <anna::DataBlock&>(constdbms));
181
182   if(data.isNull() == true) {
183     dataBlock.clear();
184     return;
185   }
186
187   const char* src = a_ofb->getData();
188
189   char* dest = (char*) dataBlock.getData();
190
191   unsigned char hex;
192
193   int j = 0;
194
195   for(register int i = 1; i < a_length; i += 2, j ++) {
196     hex = asByte(src [i - 1]) << 4;
197     hex |= asByte(src [i]);
198     dest [j] = hex;
199   }
200
201   dataBlock.clear();
202   dataBlock.allocate(j);     // (1)
203 }
204
205 //--------------------------------------------------------------------------------------------
206 // (1) Offset = 1 => primer caracter.
207 //--------------------------------------------------------------------------------------------
208 void OutputBind::decodeLongBlock(dbms::Data& data) const
209 throw(RuntimeException) {
210   const anna::DataBlock& constdbms(static_cast <dbms::LongBlock&>(data).getValue());
211   anna::DataBlock& dataBlock(const_cast <anna::DataBlock&>(constdbms));
212   dataBlock.clear();
213
214   if(data.isNull() == true)
215     return;
216
217   ub1* buffer;
218   ub4 maxLength;
219   ub4 length;
220   sword ret;
221   bool stop = false;
222   ub4 offset = 1;                                       // (1)
223
224   try {
225     buffer = (ub1*) a_ofb->getData();
226     maxLength = a_ofb->getSize();
227     length = 0;
228
229     do {
230       ret = OCILobRead(a_blob.context, a_blob.error, a_blob.handle, &length, offset, buffer, maxLength, 0, 0, 0, SQLCS_IMPLICIT);
231
232       switch(ret) {
233       case OCI_SUCCESS:
234         dataBlock += anna::DataBlock((const char*) buffer, length, false);
235         stop = true;
236         break;
237       case OCI_NEED_DATA:
238         dataBlock += anna::DataBlock((const char*) buffer, length, false);
239         offset += length;
240         break;
241       default:
242         throw dbms::DatabaseException(oracle::ResultCode(ret, a_blob.error),  ANNA_FILE_LOCATION);
243       }
244     } while(stop == false);
245   } catch(dbms::DatabaseException& edbms) {
246     throw RuntimeException(edbms);
247   }
248 }
249
250 void OutputBind::do_write(const dbms::LongBlock& data) const
251 throw(RuntimeException, dbms::DatabaseException) {
252   const anna::DataBlock& dataBlock = data.getValue();
253
254   if(a_blob.handle == NULL) {
255     string msg("anna::dbms::oracle::OutputBind::do_write | ");
256     msg += data.asString();
257     msg += " | BLOB must be loaded before modification";
258     throw RuntimeException(msg, ANNA_FILE_LOCATION);
259   }
260
261   ub1* buffer = (ub1*) dataBlock.getData();
262   ub4 length = dataBlock.getSize();
263   anna_dbms_oracle_check(
264     OCILobWrite(a_blob.context, a_blob.error, a_blob.handle, &length, (ub4) 1, buffer, length, OCI_ONE_PIECE, 0, 0, 0, SQLCS_IMPLICIT),
265     a_blob.error
266   );
267 }
268
269 void OutputBind::decodeDate(dbms::Data& data) const
270 throw(RuntimeException, dbms::DatabaseException) {
271   if(data.isNull() == true)
272     return;
273
274   Date& date = static_cast <Date&>(data);
275   sb2 year;
276   ub1 month, day;
277   anna_dbms_oracle_check(
278     OCIDateTimeGetDate(a_datetime.env, a_datetime.error, a_datetime.handle, &year, &month, &day),
279     a_datetime.error
280   );
281   date.setYear(year);
282   date.setMonth(month);
283   date.setDay(day);
284   ub1 hour, min, sec;
285   ub4 fsec;
286   sword status = OCIDateTimeGetTime(a_datetime.env, a_datetime.error, a_datetime.handle, &hour, &min, &sec, &fsec);
287
288   if(status == OCI_SUCCESS) {
289     date.setHour(hour);
290     date.setMinute(min);
291     date.setSecond(sec);
292
293     if(data.getType() == Data::Type::TimeStamp)
294       static_cast <dbms::TimeStamp&>(data).setFractionalSecond(fsec / 1000);
295   } else {
296     date.setHour(0);
297     date.setMinute(0);
298     date.setSecond(0);
299
300     if(data.getType() == Data::Type::TimeStamp)
301       static_cast <dbms::TimeStamp&>(data).setFractionalSecond(0);
302   }
303 }