OSDN Git Service

Add MS7619SE
[uclinux-h8/uClinux-dist.git] / user / gnugk / gksql_firebird.cxx
1 /*
2  * gksql_firebird.cxx
3  *
4  * Firebird/Interbase driver module for GnuGk
5  *
6  * Copyright (c) 2006, Michal Zygmuntowicz
7  *
8  * This work is published under the GNU Public License (GPL)
9  * see file COPYING for details.
10  * We also explicitely grant the right to link this code
11  * with the OpenH323 library.
12  *
13  * $Log: gksql_firebird.cxx,v $
14  * Revision 1.5  2007/03/13 18:39:43  willamowius
15  * compile fix for PWLib 1.11.3CVS
16  *
17  * Revision 1.4  2006/08/08 12:24:36  zvision
18  * Escape quote characters in query strings
19  *
20  * Revision 1.3  2006/07/06 15:25:13  willamowius
21  * set all deleted pointers to NULL (most probably more than needed)
22  *
23  * Revision 1.2  2006/06/08 07:38:42  willamowius
24  * compile fixes for gcc 3.3.x
25  *
26  * Revision 1.1  2006/06/02 09:21:34  zvision
27  * Firebird SQL driver
28  *
29  */
30 #if HAS_FIREBIRD
31
32 #if defined(_WIN32) && (_MSC_VER <= 1200)
33 #pragma warning(disable:4786) // warning about too long debug symbol off
34 #pragma warning(disable:4284)
35 #endif
36
37 #include <ptlib.h>
38 #include <cmath>
39 #include <ibase.h>
40 #include "gksql.h"
41
42 namespace {
43 PString XSQLVARToPString(XSQLVAR *sqlvar)
44 {
45         if (sqlvar->sqltype & 1)
46                 if (*(sqlvar->sqlind) == -1)
47                         return PString();
48
49         switch (sqlvar->sqltype & ~1L) {
50         case SQL_TEXT:
51                 return PString(sqlvar->sqldata, sqlvar->sqllen);
52         case SQL_SHORT:
53                 return sqlvar->sqlscale < 0 
54                         ? PString(PString::Decimal, *(int*)(sqlvar->sqldata) * pow(10.0, sqlvar->sqlscale), abs(sqlvar->sqlscale)) 
55                         : PString(*(short*)(sqlvar->sqldata));
56         case SQL_LONG:
57                 return sqlvar->sqlscale < 0
58                         ? PString(PString::Decimal, *(long*)(sqlvar->sqldata) * pow(10.0, sqlvar->sqlscale), abs(sqlvar->sqlscale)) 
59                         : PString(*(long*)(sqlvar->sqldata));
60         case SQL_DOUBLE:
61                 return sqlvar->sqlscale < 0 
62                         ? PString(PString::Decimal, *(double*)(sqlvar->sqldata) * pow(10.0, sqlvar->sqlscale), abs(sqlvar->sqlscale))
63                         : PString(PString::Printf, "%f", *(double*)(sqlvar->sqldata));
64         case SQL_INT64:
65                 return sqlvar->sqlscale < 0 
66                         ? PString(PString::Decimal, *(ISC_INT64*)(sqlvar->sqldata) * pow(10.0, sqlvar->sqlscale), abs(sqlvar->sqlscale))
67                         : PString(*(ISC_INT64*)(sqlvar->sqldata));
68         case SQL_FLOAT:
69                 return PString(PString::Printf, "%f", (double)*(float*)(sqlvar->sqldata));
70         case SQL_VARYING:
71                 return PString(sqlvar->sqldata + 2, *(short*)(sqlvar->sqldata));
72         default:
73                 return PString();
74         }
75 }
76 } // end of namespace
77
78 /** Class that encapsulates SQL query result for Firebird backend.
79         It does not provide any multithread safety, so should be accessed
80         from a single thread at time.
81 */
82 class GkIBSQLResult : public GkSQLResult
83 {
84 public:
85         /// Build the result from SELECT type query
86         GkIBSQLResult(
87                 /// transaction handle
88                 isc_tr_handle tr,
89                 /// statement handle
90                 isc_stmt_handle stmt,
91                 /// SELECT type query result
92                 XSQLDA* selectResult
93                 );
94
95         /// Build the empty     result and store query execution error information
96         GkIBSQLResult(
97                 /// Firebird specific error code
98                 unsigned int errorCode,
99                 /// PostgreSQL specific error message text
100                 const char* errorMsg,
101                 /// transaction handle
102                 isc_tr_handle tr = NULL,
103                 /// statement handle
104                 isc_stmt_handle stmt = NULL,
105                 /// SELECT type query result
106                 XSQLDA* selectResult = NULL
107                 );
108         
109         virtual ~GkIBSQLResult();
110         
111         /** @return
112             Backend specific error message, if the query failed.
113         */      
114         virtual PString GetErrorMessage();
115         
116         /** @return
117             Backend specific error code, if the query failed.
118         */      
119         virtual long GetErrorCode();
120         
121         /** @return
122             True if rows can be fetched in random access order, false if
123             rows have to be fethed sequentially and can be retrieved only once.
124         */
125         virtual bool HasRandomAccess();
126
127         /** Fetch a single row from the result set. After each row is fetched,
128             cursor position is moved to a next row.
129                 
130             @return
131             True if the row has been fetched, false if no more rows are available.
132         */
133         virtual bool FetchRow(
134                 /// array to be filled with string representations of the row fields
135                 PStringArray& result
136                 );
137         virtual bool FetchRow(
138                 /// array to be filled with string representations of the row fields
139                 ResultRow& result
140                 );
141
142         /** @return
143             True if the column at the index #fieldOffset# is NULL in the row 
144             fetched most recently.
145         */
146         virtual bool IsNullField(
147                 /// index of the column to check
148                 long fieldOffset
149                 );
150                         
151         /** Fetch a single row from the result set. This function requires
152                 that the backend supports random row access.
153                 
154             @return
155             True if the row has been fetched, false if a row at the given offset
156                 does not exists or SQL backend does not support random row access.
157         */
158         virtual bool FetchRow(
159                 /// array to be filled with string representations of the row fields
160                 PStringArray& result,
161                 /// index (0 based) of the row to fetch
162                 long rowOffset
163                 );
164         virtual bool FetchRow(
165                 /// array to be filled with string representations of the row fields
166                 ResultRow& result,
167                 /// index (0 based) of the row to fetch
168                 long rowOffset
169                 );
170                 
171         /** @return
172             True if the column at the index #fieldOffset# is NULL in the row 
173             at the specified index.
174         */
175         virtual bool IsNullField(
176                 /// index of the column to check
177                 long fieldOffset,
178                 /// index (0 based) of the row to check
179                 long rowOffset
180                 );
181
182 private:
183         GkIBSQLResult();
184         GkIBSQLResult(const GkIBSQLResult&);
185         GkIBSQLResult& operator=(const GkIBSQLResult&);
186         
187 protected:
188         /// query result for SELECT type queries
189         XSQLDA* m_sqlResult;
190         /// transaction handle
191         isc_tr_handle m_tr;
192         /// statement handle
193         isc_stmt_handle m_stmt;
194         /// the most recent row returned by fetch operation
195         int m_sqlRow;
196         /// Firebird specific error code (if the query failed)
197         unsigned int m_errorCode;
198         /// Firebird specific error message text (if the query failed)
199         PString m_errorMessage;
200 };
201
202 /// Firebird/Interbase backend connection implementation.
203 class GkIBSQLConnection : public GkSQLConnection
204 {
205 public:
206         /// Build a new Firebird connection object
207         GkIBSQLConnection(
208                 /// name to use in the log
209                 const char* name = "Firebird"
210                 );
211         
212         virtual ~GkIBSQLConnection();
213
214 protected:
215         class IBSQLConnWrapper : public GkSQLConnection::SQLConnWrapper
216         {
217         public:
218                 IBSQLConnWrapper(
219                         /// unique identifier for this connection
220                         int id,
221                         /// host:port this connection is made to
222                         const PString& host,
223                         /// Firebird connection object
224                         isc_db_handle conn
225                         ) : SQLConnWrapper(id, host), m_conn(conn) {}
226
227                 virtual ~IBSQLConnWrapper();
228
229         private:
230                 IBSQLConnWrapper();
231                 IBSQLConnWrapper(const IBSQLConnWrapper&);
232                 IBSQLConnWrapper& operator=(const IBSQLConnWrapper&);
233
234         public:
235                 isc_db_handle m_conn;
236         };
237
238         /** Create a new SQL connection using parameters stored in this object.
239             When the connection is to be closed, the object is simply deleted
240             using delete operator.
241             
242             @return
243             NULL if database connection could not be established 
244             or an object of IBSQLConnWrapper class.
245         */
246         virtual SQLConnPtr CreateNewConnection(
247                 /// unique identifier for this connection
248                 int id
249                 );
250         
251         /** Execute the query using specified SQL connection.
252
253                 @return
254                 Query execution result.
255         */
256         virtual GkSQLResult* ExecuteQuery(
257                 /// SQL connection to use for query execution
258                 SQLConnPtr conn,
259                 /// query string
260                 const char* queryStr,
261                 /// maximum time (ms) for the query execution, -1 means infinite
262                 long timeout = -1
263                 );
264                 
265         /** Escape any special characters in the string, so it can be used in a SQL query.
266
267                 @return
268                 Escaped string.
269         */
270         virtual PString EscapeString(
271                 /// SQL connection to get escaping parameters from
272                 SQLConnPtr conn,
273                 /// string to be escaped
274                 const char* str
275                 );
276
277 private:
278         GkIBSQLConnection(const GkIBSQLConnection&);
279         GkIBSQLConnection& operator=(const GkIBSQLConnection&);
280 };
281
282
283 GkIBSQLResult::GkIBSQLResult(
284         /// transaction handle
285         isc_tr_handle tr,
286         /// statement handle
287         isc_stmt_handle stmt,
288         /// SELECT type query result
289         XSQLDA* selectResult
290         ) 
291         : GkSQLResult(false), m_sqlResult(selectResult), m_tr(tr), m_stmt(stmt), m_sqlRow(-1),
292         m_errorCode(0)
293 {
294         if (m_sqlResult != NULL) {
295                 m_numRows = 100000;
296                 m_numFields = m_sqlResult->sqld;
297                 for (int i = 0; i < m_sqlResult->sqld; ++i) {
298                         m_sqlResult->sqlvar[i].sqlind = new short;
299                         if ((m_sqlResult->sqlvar[i].sqltype & ~1L) == SQL_VARYING)
300                                 m_sqlResult->sqlvar[i].sqldata = new char[m_sqlResult->sqlvar[i].sqllen + 2];
301                         else
302                                 m_sqlResult->sqlvar[i].sqldata = new char[m_sqlResult->sqlvar[i].sqllen];
303                 }
304         } else
305                 m_queryError = true;
306                 
307         m_selectType = (m_numFields != 0);
308 }
309
310 GkIBSQLResult::GkIBSQLResult(
311         /// Firebird specific error code
312         unsigned int errorCode,
313         /// Firebird specific error message text
314         const char* errorMsg,
315         /// transaction handle
316         isc_tr_handle tr,
317         /// statement handle
318         isc_stmt_handle stmt,
319         /// SELECT type query result
320         XSQLDA* selectResult
321         ) 
322         : GkSQLResult(true), m_sqlResult(selectResult), m_tr(tr), m_stmt(stmt), m_sqlRow(-1),
323         m_errorCode(errorCode), m_errorMessage(errorMsg)
324 {
325 }
326
327 GkIBSQLResult::~GkIBSQLResult()
328 {
329         ISC_STATUS status[20];
330         
331         if (m_stmt != NULL)
332                 isc_dsql_free_statement(status, &m_stmt, DSQL_drop);
333         if (m_sqlResult != NULL) {
334                 for (int i = 0; i < m_sqlResult->sqld; ++i) {
335                         if (m_sqlResult->sqlvar[i].sqldata != NULL) {
336                                 delete [] m_sqlResult->sqlvar[i].sqldata;
337                                 m_sqlResult->sqlvar[i].sqldata = NULL;
338                         }
339                         if (m_sqlResult->sqlvar[i].sqlind != NULL) {
340                                 delete m_sqlResult->sqlvar[i].sqlind;
341                                 m_sqlResult->sqlvar[i].sqlind = NULL;
342                         }
343                 }
344                 delete [] reinterpret_cast<char*>(m_sqlResult);
345                 m_sqlResult = NULL;
346         }
347         if (m_tr != NULL)
348                 if (m_queryError)
349                         isc_rollback_transaction(status, &m_tr);
350                 else
351                         isc_commit_transaction(status, &m_tr);
352 }
353
354 bool GkIBSQLResult::HasRandomAccess()
355 {
356         return false;
357 }
358
359 PString GkIBSQLResult::GetErrorMessage()
360 {
361         return m_errorMessage;
362 }
363         
364 long GkIBSQLResult::GetErrorCode()
365 {
366         return m_errorCode;
367 }
368
369 bool GkIBSQLResult::FetchRow(
370         /// array to be filled with string representations of the row fields
371         PStringArray& result
372         )
373 {
374         if (m_sqlResult == NULL || m_numRows <= 0)
375                 return false;
376         
377         if (m_sqlRow < 0)
378                 m_sqlRow = 0;
379                 
380         if (m_sqlRow >= m_numRows)
381                 return false;
382         
383         ISC_STATUS retval;
384         ISC_STATUS status[20];  
385         retval = isc_dsql_fetch(status, &m_stmt, 1, m_sqlResult);
386         if (status[0] == 1 && status[1] != 0) {
387                 m_numRows = m_sqlRow;
388                 if (retval != 100) {
389                         long errcode = isc_sqlcode(status);
390                         char errormsg[512];
391                         if (errcode == -999) {
392                                 errcode = status[1];
393                                 long *pvector = status;
394                                 errormsg[isc_interprete(errormsg, &pvector)] = 0;
395                         } else {
396                                 strcpy(errormsg, "SQL:");
397                                 isc_sql_interprete(static_cast<short>(errcode), errormsg + 4, 512 - 4); 
398                         }
399                         PTRACE(2, "Firebird\tFailed to fetch query row (" << errcode
400                                 << "): " << errormsg
401                                 );
402                 }
403                 return false;
404         }
405                 
406         result.SetSize(m_sqlResult->sqld);
407         
408         for (PINDEX i = 0; i < m_sqlResult->sqld; ++i)
409                 result[i] = XSQLVARToPString(&(m_sqlResult->sqlvar[i]));
410         
411         m_sqlRow++;
412         
413         return true;
414 }
415
416 bool GkIBSQLResult::FetchRow(
417         /// array to be filled with string representations of the row fields
418         ResultRow& result
419         )
420 {
421         if (m_sqlResult == NULL || m_numRows <= 0)
422                 return false;
423         
424         if (m_sqlRow < 0)
425                 m_sqlRow = 0;
426                 
427         if (m_sqlRow >= m_numRows)
428                 return false;
429         ISC_STATUS retval;      
430         ISC_STATUS status[20];  
431         retval = isc_dsql_fetch(status, &m_stmt, 1, m_sqlResult);
432         if (status[0] == 1 && status[1] != 0) {
433                 m_numRows = m_sqlRow;
434                 if (retval != 100) {
435                         long errcode = isc_sqlcode(status);
436                         char errormsg[512];
437                         if (errcode == -999) {
438                                 errcode = status[1];
439                                 long *pvector = status;
440                                 errormsg[isc_interprete(errormsg, &pvector)] = 0;
441                         } else {
442                                 strcpy(errormsg, "SQL:");
443                                 isc_sql_interprete(static_cast<short>(errcode), errormsg + 4, 512 - 4); 
444                         }
445                         PTRACE(2, "Firebird\tFailed to fetch query row (" << errcode
446                                 << "): " << errormsg
447                                 );
448                 }
449                 return false;
450         }
451
452         result.resize(m_numFields);
453         
454         for (PINDEX i = 0; i < m_numFields; i++) {
455                 result[i].first = XSQLVARToPString(&(m_sqlResult->sqlvar[i]));
456                 result[i].second = PString(m_sqlResult->sqlvar[i].aliasname, m_sqlResult->sqlvar[i].aliasname_length);
457         }
458         
459         m_sqlRow++;
460         
461         return true;
462 }
463
464 bool GkIBSQLResult::IsNullField(
465         /// index of the column to check
466         long fieldOffset
467         )
468 {
469         return m_sqlResult == NULL || m_sqlRow < 0 || m_sqlRow >= m_numRows
470                 || fieldOffset < 0 || fieldOffset >= m_numFields
471                 || ((m_sqlResult->sqlvar[fieldOffset].sqltype & 1) && *(m_sqlResult->sqlvar[fieldOffset].sqlind) == -1);
472 }
473
474 bool GkIBSQLResult::FetchRow(
475         /// array to be filled with string representations of the row fields
476         PStringArray& result,
477         /// index (0 based) of the row to fetch
478         long rowOffset
479         )
480 {
481         return false;
482 }
483
484 bool GkIBSQLResult::FetchRow(
485         /// array to be filled with string representations of the row fields
486         ResultRow& result,
487         /// index (0 based) of the row to fetch
488         long rowOffset
489         )
490 {
491         return false;
492 }
493
494 bool GkIBSQLResult::IsNullField(
495         /// index of the column to check
496         long fieldOffset,
497         /// index (0 based) of the row to check
498         long rowOffset
499         )
500 {
501         return false;
502 }
503
504
505 GkIBSQLConnection::GkIBSQLConnection(
506         /// name to use in the log
507         const char* name
508         ) : GkSQLConnection(name)
509 {
510 }
511         
512 GkIBSQLConnection::~GkIBSQLConnection()
513 {
514 }
515
516 GkIBSQLConnection::IBSQLConnWrapper::~IBSQLConnWrapper()
517 {
518         ISC_STATUS status[20];
519         isc_detach_database(status, &m_conn);
520 }
521
522 GkSQLConnection::SQLConnPtr GkIBSQLConnection::CreateNewConnection(
523         /// unique identifier for this connection
524         int id
525         )
526 {
527         unsigned dpb_offset = 0;
528         std::vector<char> dpb(1);
529         
530         dpb[dpb_offset++] = isc_dpb_version1;
531         
532         if (!m_username) {
533                 dpb.resize(dpb.size() + 2 + m_username.GetLength());
534                 dpb[dpb_offset++] = isc_dpb_user_name;
535                 dpb[dpb_offset++] = m_username.GetLength();
536                 memcpy(&(dpb[dpb_offset]), (const char*)m_username, m_username.GetLength());
537                 dpb_offset += m_username.GetLength();
538         }
539
540         if (!m_password) {      
541                 dpb.resize(dpb.size() + 2 + m_password.GetLength());
542                 dpb[dpb_offset++] = isc_dpb_password;
543                 dpb[dpb_offset++] = m_password.GetLength();
544                 memcpy(&(dpb[dpb_offset]), (const char*)m_password, m_password.GetLength());
545                 dpb_offset += m_password.GetLength();
546         }
547         
548         ISC_STATUS status[20];
549         isc_db_handle conn = NULL;
550         std::string dbname = m_database;
551         
552         if (!m_host) {
553                 dbname.insert(0, ":");
554                 dbname.insert(0, (const char *)m_host);
555         }
556         
557         isc_attach_database(status, 0, const_cast<char*>(dbname.c_str()), &conn, dpb_offset, &(dpb[0]));
558         if (status[0] == 1 && status[1] != 0) {
559                 long *pvector = status;
560                 char errormsg[512];
561                 errormsg[isc_interprete(errormsg, &pvector)] = 0;
562                 PTRACE(2, GetName() << "\tFirebird connection to " << m_username << '@' << dbname 
563                         << " failed (isc_attach_database failed): " << errormsg
564                         );
565                 return NULL;
566         }       
567         
568         PTRACE(5, GetName() << "\tFirebird connection to " << m_username << '@' << dbname
569                 << " established successfully"
570                 );
571         return new IBSQLConnWrapper(id, dbname, conn);
572 }
573
574 GkSQLResult* GkIBSQLConnection::ExecuteQuery(
575         /// SQL connection to use for query execution
576         GkSQLConnection::SQLConnPtr con,
577         /// query string
578         const char* queryStr,
579         /// maximum time (ms) for the query execution, -1 means infinite
580         long /*timeout*/
581         )
582 {
583         isc_db_handle conn = ((IBSQLConnWrapper*)con)->m_conn;
584
585         char errormsg[512];
586         ISC_STATUS status[20];
587         isc_tr_handle tr = NULL;
588         isc_stmt_handle stmt = NULL;
589         
590         isc_start_transaction(status, &tr, 1, &conn, 0, NULL);
591         if (status[0] == 1 && status[1] != 0) {
592                 long *pvector = status;
593                 char errormsg[512];
594                 errormsg[isc_interprete(errormsg, &pvector)] = 0;
595                 return new GkIBSQLResult(status[1], errormsg);
596         }
597         
598         isc_dsql_allocate_statement(status, &conn, &stmt);
599         if (status[0] == 1 && status[1] != 0) {
600                 long errorcode = isc_sqlcode(status);
601                 if (errorcode == -999) {
602                         errorcode = status[1];
603                         long *pvector = status;
604                         errormsg[isc_interprete(errormsg, &pvector)] = 0;
605                 } else {
606                         strcpy(errormsg, "SQL:");
607                         isc_sql_interprete(static_cast<short>(errorcode), errormsg, 512 - 4);
608                 }
609                 return new GkIBSQLResult(errorcode, errormsg, tr);
610         }
611
612         int numcols = 1;
613         XSQLDA *result = reinterpret_cast<XSQLDA*>(new char[XSQLDA_LENGTH(numcols)]);
614         memset(result, 0, XSQLDA_LENGTH(numcols));
615         result->version = SQLDA_VERSION1;
616         result->sqln = numcols;
617         
618         isc_dsql_prepare(status, &tr, &stmt, 0, const_cast<char*>(queryStr), SQL_DIALECT_CURRENT, result);
619         if (status[0] == 1 && status[1] != 0) {
620                 long errorcode = isc_sqlcode(status);
621                 if (errorcode == -999) {
622                         errorcode = status[1];
623                         long *pvector = status;
624                         errormsg[isc_interprete(errormsg, &pvector)] = 0;
625                 } else {
626                         strcpy(errormsg, "SQL:");
627                         isc_sql_interprete(static_cast<short>(errorcode), errormsg, 512 - 4);
628                 }
629                 return new GkIBSQLResult(errorcode, errormsg, tr, stmt);
630         }
631         
632         if (result->sqld > result->sqln) {
633                 numcols = result->sqld;
634                 delete [] reinterpret_cast<char*>(result);
635                 result = reinterpret_cast<XSQLDA*>(new char[XSQLDA_LENGTH(numcols)]);
636                 memset(result, 0, XSQLDA_LENGTH(numcols));
637                 result->version = SQLDA_VERSION1;
638                 result->sqln = numcols;
639         
640                 isc_dsql_describe(status, &stmt, SQLDA_VERSION1, result);
641                 if (status[0] == 1 && status[1] != 0) {
642                         long errorcode = isc_sqlcode(status);
643                         if (errorcode == -999) {
644                                 errorcode = status[1];
645                                 long *pvector = status;
646                                 errormsg[isc_interprete(errormsg, &pvector)] = 0;
647                         } else {
648                                 strcpy(errormsg, "SQL:");
649                                 isc_sql_interprete(static_cast<short>(errorcode), errormsg, 512 - 4);
650                         }
651                         delete [] reinterpret_cast<char*>(result);
652                         result = NULL;
653                         return new GkIBSQLResult(errorcode, errormsg, tr, stmt);
654                 }
655         }
656
657         isc_dsql_execute(status, &tr, &stmt, SQLDA_VERSION1, NULL);
658         if (status[0] == 1 && status[1] != 0) {
659                 long errorcode = isc_sqlcode(status);
660                 if (errorcode == -999) {
661                         errorcode = status[1];
662                         long *pvector = status;
663                         errormsg[isc_interprete(errormsg, &pvector)] = 0;
664                 } else {
665                         strcpy(errormsg, "SQL:");
666                         isc_sql_interprete(static_cast<short>(errorcode), errormsg, 512 - 4);
667                 }
668                 delete [] reinterpret_cast<char*>(result);
669                 result = NULL;
670                 return new GkIBSQLResult(errorcode, errormsg, tr, stmt);
671         }
672
673         return new GkIBSQLResult(tr, stmt, result);
674 }
675
676 PString GkIBSQLConnection::EscapeString(
677         /// SQL connection to get escaping parameters from
678         SQLConnPtr /*conn*/,
679         /// string to be escaped
680         const char* str
681         )
682 {
683         PString s(str);
684         
685         s.Replace("'", "''", TRUE);
686                         
687         return s;
688 }
689
690 namespace {
691         GkSQLCreator<GkIBSQLConnection> IBSQLCreator("Firebird");
692 }
693
694 #endif /* HAS_FIREBIRD */