OpenCog Framework  Branch: master, revision 6f0b7fc776b08468cf1b74aa9db028f387b4f0c0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
odbcxx.cc
Go to the documentation of this file.
1 /*
2  * FUNCTION:
3  * ODBC driver -- developed/tested with unixodbc http://www.unixodbc.org
4  *
5  * ODBC is basically brain-damaged, and so is this driver.
6  * The problem is that ODBC forces you to guess how many columns there
7  * are in your reply, and etc. which we don't know a-priori. Also
8  * makes VARCHAR difficult (impossible ??) to support correctly!
9  * Blame it on SQLBindCol(), which is a terrible idea. @#$%^ Microsoft.
10  *
11  * Threading:
12  * ----------
13  * This class is thread-enabled but not thread-safe. Two threads should
14  * not try to use one instance of this class at the same time. Each
15  * thread should construct it's own instance of this class. This class
16  * uses no globals.
17  *
18  * HISTORY:
19  * Copyright (c) 2002,2008 Linas Vepstas <linas@linas.org>
20  * created by Linas Vepstas March 2002
21  * ported to C++ March 2008
22  *
23  * LICENSE:
24  * This program is free software; you can redistribute it and/or modify
25  * it under the terms of the GNU Affero General Public License v3 as
26  * published by the Free Software Foundation and including the exceptions
27  * at http://opencog.org/wiki/Licenses
28  *
29  * This program is distributed in the hope that it will be useful,
30  * but WITHOUT ANY WARRANTY; without even the implied warranty of
31  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32  * GNU General Public License for more details.
33  *
34  * You should have received a copy of the GNU Affero General Public License
35  * along with this program; if not, write to:
36  * Free Software Foundation, Inc.,
37  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
38  */
39 
40 #ifdef HAVE_SQL_STORAGE
41 
42 #include "odbcxx.h"
43 
44 #include <stack>
45 #include <string>
46 
47 #include <sql.h>
48 #include <sqlext.h>
49 #include <stdio.h>
50 
51 #include <opencog/util/platform.h>
52 
53 // cheesy hack for missing PERR
54 #define PERR printf
55 
56 /* =========================================================== */
57 
58 #define PRINT_SQLERR(HTYPE,HAN) \
59 { \
60  char sql_stat[10]; \
61  SQLSMALLINT msglen; \
62  SQLINTEGER err; \
63  char msg[200]; \
64  \
65  SQLGetDiagRec(HTYPE, HAN, 1, (SQLCHAR *) sql_stat, \
66  &err, (SQLCHAR*) msg, sizeof(msg), &msglen); \
67  PERR("(%ld) %s\n", (long int) err, msg); \
68 }
69 
70 /* =========================================================== */
71 
72 ODBCConnection::ODBCConnection(const char * _dbname,
73  const char * _username,
74  const char * _authentication)
75 {
76  SQLRETURN rc;
77 
78  is_connected = false;
79 
80  sql_hdbc = NULL;
81  sql_henv = NULL;
82 
83  if (NULL == _dbname)
84  {
85  PERR("No DB specified");
86  return;
87  }
88 
89  /* Allocate environment handle */
90  rc = SQLAllocEnv(&sql_henv);
91  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
92  {
93  PERR("Can't SQLAllocEnv, rc=%d", rc);
94  return;
95  }
96 
97  /* Set the ODBC version */
98  rc = SQLSetEnvAttr(sql_henv, SQL_ATTR_ODBC_VERSION,
99  (void*)SQL_OV_ODBC3, 0);
100  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
101  {
102  PERR("Can't SQLSetEnv, rc=%d", rc);
103  PRINT_SQLERR (SQL_HANDLE_ENV, sql_henv);
104  SQLFreeHandle(SQL_HANDLE_ENV, sql_henv);
105  sql_henv = NULL;
106  return;
107  }
108 
109  /* Allocate the connection handle */
110  rc = SQLAllocConnect(sql_henv, &sql_hdbc);
111  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
112  {
113  PERR ("Can't SQLAllocConnect handle rc=%d", rc);
114  PRINT_SQLERR (SQL_HANDLE_ENV, sql_henv);
115  SQLFreeHandle(SQL_HANDLE_ENV, sql_henv);
116  sql_henv = NULL;
117  return;
118  }
119 
120  /* set the timeout to 5 seconds ?? hack alert fixme */
121  // SQLSetConnectAttr(sql_hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
122 
123  if (NULL == _authentication) _authentication = "";
124  rc = SQLConnect(sql_hdbc,
125  (SQLCHAR*) _dbname, SQL_NTS,
126  (SQLCHAR*) _username, SQL_NTS,
127  (SQLCHAR*) _authentication, SQL_NTS);
128 
129  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
130  {
131  PERR ("Can't perform SQLConnect rc=%d", rc);
132  PRINT_SQLERR (SQL_HANDLE_DBC, sql_hdbc);
133  SQLFreeHandle(SQL_HANDLE_DBC, sql_hdbc);
134  SQLFreeHandle(SQL_HANDLE_ENV, sql_henv);
135  sql_henv = NULL;
136  sql_hdbc = NULL;
137  return;
138  }
139 
140  dbname = _dbname;
141  username = _username;
142  is_connected = true;
143 }
144 
145 /* =========================================================== */
146 
148 {
149  if (sql_hdbc)
150  {
151  SQLDisconnect(sql_hdbc);
152  SQLFreeHandle(SQL_HANDLE_DBC, sql_hdbc);
153  sql_hdbc = NULL;
154  }
155 
156  if (sql_henv)
157  {
158  SQLFreeHandle(SQL_HANDLE_ENV, sql_henv);
159  sql_henv = NULL;
160  }
161 
162  while (!free_pool.empty())
163  {
164  ODBCRecordSet *rs = free_pool.top();
165  delete rs;
166  free_pool.pop();
167  }
168 }
169 
170 bool ODBCConnection::connected (void) const
171 {
172  return is_connected;
173 }
174 
175 /* =========================================================== */
176 #define DEFAULT_NUM_COLS 50
177 
179 {
180  ODBCRecordSet *rs;
181  if (!free_pool.empty())
182  {
183  rs = free_pool.top();
184  free_pool.pop();
185  rs->ncols = -1;
186  }
187  else
188  {
189  rs = new ODBCRecordSet(this);
190  }
191 
192  rs->alloc_and_bind_cols(DEFAULT_NUM_COLS);
193 
194  return rs;
195 }
196 
197 /* =========================================================== */
198 
199 void ODBCConnection::extract_error(const char *msg)
200 {
201  SQLRETURN ret;
202  SQLINTEGER i = 0;
203  SQLINTEGER native;
204  SQLCHAR state[7];
205  SQLCHAR text[256];
206  SQLSMALLINT len;
207  do
208  {
209  ret = SQLGetDiagRec(SQL_HANDLE_ENV, sql_henv, ++i, state, &native, text,
210  sizeof(text), &len);
211  if (SQL_SUCCEEDED(ret))
212  PERR("\t%s : %d : %d : %s\n", state, i, native, text);
213  } while (ret == SQL_SUCCESS);
214 }
215 
216 /* =========================================================== */
217 
219 ODBCConnection::exec(const char * buff)
220 {
221  ODBCRecordSet *rs;
222  SQLRETURN rc;
223 
224  if (!is_connected) return NULL;
225 
226  rs = get_record_set();
227  if (!rs) return NULL;
228 
229  rc = SQLExecDirect(rs->sql_hstmt, (SQLCHAR *)buff, SQL_NTS);
230 
231  /* If query returned no data, its not necessarily an error:
232  * its simply "no data", that's all.
233  */
234  if (SQL_NO_DATA == rc)
235  {
236  rs->release();
237  return NULL;
238  }
239 
240  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
241  {
242  PERR ("Can't perform query rc=%d ", rc);
243  PRINT_SQLERR (SQL_HANDLE_STMT, rs->sql_hstmt);
244  rs->release();
245  PERR ("\tQuery was: %s\n", buff);
246  extract_error("exec");
247  return NULL;
248  }
249 
250  /* Use numbr of columns to indicate that the query hasn't
251  * given results yet. */
252  rs->ncols = -1;
253  return rs;
254 }
255 
256 /* =========================================================== */
257 
258 #define DEFAULT_COLUMN_NAME_SIZE 121
259 #define DEFAULT_VARCHAR_SIZE 4040
260 
261 void
263 {
264  // IMPORTANT! MUST NOT BE ON STACK!! Else stack corruption will result.
265  // The ODBC driver really wants to write a return value here!
266  static SQLLEN bogus;
267 
268  SQLRETURN rc;
269  int i;
270 
271  if (new_ncols > arrsize)
272  {
273  if (column_labels)
274  {
275  for (i=0; i<arrsize; i++)
276  {
277  if (column_labels[i])
278  {
279  delete[] column_labels[i];
280  }
281  }
282  delete[] column_labels;
283  }
284  if (column_datatype) delete[] column_datatype;
285 
286  if (values)
287  {
288  for (i=0; i<arrsize; i++)
289  {
290  if (values[i])
291  {
292  delete[] values[i];
293  }
294  }
295  delete[] values;
296  }
297  if (vsizes) delete[] vsizes;
298 
299  column_labels = new char*[new_ncols];
300  column_datatype = new int[new_ncols];
301  values = new char*[new_ncols];
302  vsizes = new int[new_ncols];
303 
304  /* intialize */
305  for (i = 0; i<new_ncols; i++)
306  {
307  column_labels[i] = NULL;
308  column_datatype[i] = 0;
309  values[i] = NULL;
310  vsizes[i] = 0;
311  }
312 
313  arrsize = new_ncols;
314  }
315 
316  rc = SQLAllocStmt (conn->sql_hdbc, &sql_hstmt);
317  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
318  {
319  PERR("Can't allocate statement handle, rc=%d", rc);
320  PRINT_SQLERR (SQL_HANDLE_STMT, sql_hstmt);
321  /* oops memory leak */
322  return;
323  }
324 
325  /* Initialize the newly realloc'ed entries */
326  for (i=0; i<new_ncols; i++)
327  {
328  column_datatype[i] = 0;
329 
330  if (NULL == column_labels[i])
331  {
332  column_labels[i] = new char[DEFAULT_COLUMN_NAME_SIZE];
333  column_labels[i][0] = 0;
334  }
335  if (NULL == values[i])
336  {
337  values[i] = new char[DEFAULT_VARCHAR_SIZE];
338  vsizes[i] = DEFAULT_VARCHAR_SIZE;
339  values[i][0] = 0;
340  }
341  rc = SQLBindCol(sql_hstmt, i+1, SQL_C_CHAR,
342  values[i], vsizes[i], &bogus);
343  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
344  {
345  PERR ("Can't bind col=%d rc=%d", i, rc);
346  PRINT_SQLERR (SQL_HANDLE_STMT, sql_hstmt);
347  return;
348  }
349  }
350 }
351 
352 /* =========================================================== */
353 /* pseudo-private routine */
354 
355 
357 {
358  // If _conn is null, then this is null, too.
359  if (NULL == _conn) return;
360 
361  conn = _conn;
362  ncols = -1;
363  arrsize = 0;
364  column_labels = NULL;
365  column_datatype = NULL;
366  values = NULL;
367  vsizes = NULL;
368  sql_hstmt = NULL;
369 }
370 
371 /* =========================================================== */
372 
373 void
375 {
376  // 'this' is typically null when an error occurred, or if no data
377  // was returned. We don't want a call to 'release()' to crash for
378  // these cases ... we want to just keep going like normal.
379  if (!this) return;
380 
381  // Avoid accidental double-release
382  if (NULL == sql_hstmt) return;
383 
384  // SQLFreeStmt(sql_hstmt, SQL_UNBIND);
385  // SQLFreeStmt(sql_hstmt, SQL_CLOSE);
386  SQLFreeHandle(SQL_HANDLE_STMT, sql_hstmt);
387  sql_hstmt = NULL;
388 
389  conn->free_pool.push(this);
390 }
391 
392 /* =========================================================== */
393 
395 {
396  release(); // shouldn't be needed ... but just in case.
397 
398  conn = NULL;
399 
400  for (int i=0; i<arrsize; i++)
401  {
402  delete[] column_labels[i];
403  delete[] values[i];
404  }
405  delete[] column_labels;
406  column_labels = NULL;
407 
408  delete[] column_datatype;
409  column_datatype = NULL;
410 
411  delete[] values;
412  values = NULL;
413 
414  delete[] vsizes;
415  vsizes = NULL;
416 }
417 
418 /* =========================================================== */
419 
420 void
422 {
423  SQLSMALLINT _ncols;
424  SQLRETURN rc;
425  int i;
426 
427  if (0 <= ncols) return;
428 
429  /* If number of columns is negative, then we haven't
430  * gotten any results back yet. Start by getting the
431  * column labels.
432  */
433 
434  rc = SQLNumResultCols(sql_hstmt, &_ncols);
435  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
436  {
437  PERR ("Can't get num columns rc=%d", rc);
438  PRINT_SQLERR (SQL_HANDLE_STMT, sql_hstmt);
439  return;
440  }
441 
442  if (_ncols > arrsize)
443  {
444  PERR( "screwed not enough columns !! ");
445  _ncols = arrsize;
446  }
447 
448  for (i=0; i<_ncols; i++)
449  {
450  char namebuff[300];
451  SQLSMALLINT namelen;
452  SQLULEN column_size;
453  SQLSMALLINT datatype;
454  SQLSMALLINT decimal_digits;
455  SQLSMALLINT nullable;
456 
457  rc = SQLDescribeCol (sql_hstmt, i+1,
458  (SQLCHAR *) namebuff, 299, &namelen,
459  &datatype, &column_size, &decimal_digits, &nullable);
460  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
461  {
462  PERR ("Can't describe col rc=%d", rc);
463  PRINT_SQLERR (SQL_HANDLE_STMT, sql_hstmt);
464  return;
465  }
466 
467  namebuff[namelen] = 0x0;
468  // PINFO ("column %d has name\'%s\'", i, namebuff);
469 
470  strncpy(column_labels[i], namebuff, DEFAULT_COLUMN_NAME_SIZE);
471  column_labels[i][DEFAULT_COLUMN_NAME_SIZE-1] = 0;
472  column_datatype[i] = datatype;
473  }
474 
475  ncols = _ncols;
476 }
477 
478 /* =========================================================== */
479 
480 void
482 {
483  if (!this) return;
484  SQL_POSITION_TO(sql_hstmt, 0);
485 }
486 
487 
488 int
490 {
491  if (!this) return 0;
492 
493  SQLRETURN rc = SQLFetch(sql_hstmt);
494 
495  /* no more data */
496  if (SQL_NO_DATA == rc) return 0;
497  if (SQL_NULL_DATA == rc) return 0;
498 
499  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
500  {
501  PERR ("Can't fetch row rc=%d", rc);
502  PRINT_SQLERR (SQL_HANDLE_STMT, sql_hstmt);
503  return 0;
504  }
505 
506  return 1;
507 }
508 
509 /* =========================================================== */
510 
511 int
512 ODBCRecordSet::get_col_by_name (const char * fieldname)
513 {
514  int i;
515  const char * fp;
516 
517  /* lookup the column number based on the column name */
518  for (i=0; i<ncols; i++)
519  {
520  if (!strcasecmp (fieldname, column_labels[i])) return i;
521  }
522 
523  /* oops. Try removing the table name if possible */
524  fp = strrchr (fieldname, '.');
525  if (!fp) return -1;
526  fp ++;
527 
528  for (i=0; i<ncols; i++)
529  {
530  if (!strcasecmp (fp, column_labels[i])) return i;
531  }
532 
533  return -1;
534 }
535 
536 const char *
537 ODBCRecordSet::get_value(const char * fieldname)
538 {
539  if (!this) return NULL;
540  int column;
541 
542  /* If number of columns is negative, then we haven't
543  * gotten any results back yet. Start by getting the
544  * column labels.
545  */
546  if (0 > ncols)
547  {
549  }
550 
551  column = get_col_by_name (fieldname);
552  if (0 > column) return NULL;
553 
554  // LEAVE ("(rs=%p, fieldname=%s) {val=\'%s\'}", rs, fieldname, rs->values[column]);
555  return values[column];
556 }
557 
558 /* =========================================================== */
559 
560 #ifdef UNIT_TEST_EXAMPLE
561 
562 class Ola
563 {
564  public:
565  ODBCRecordSet *rs;
566  bool column_cb(const char *colname, const char * colvalue)
567  {
568  printf ("%s = %s\n", colname, colvalue);
569  return false;
570  }
571  bool row_cb(void)
572  {
573  printf ("---- New row found ----\n");
574  rs->foreach_column(&Ola::column_cb, this);
575  return false;
576  }
577 };
578 
579 int main ()
580 {
581  ODBCConnection *conn;
582  conn = new ODBCConnection("opencog", "linas", NULL);
583 
584  ODBCRecordSet *rs;
585 
586 // #define MAKE_SOME_DATA
587 #ifdef MAKE_SOME_DATA
588  rs = conn->exec(
589  "INSERT INTO Atoms VALUES (3,1,0.5, 0.5, 'umm');");
590 #endif
591 
592  // One way of doing things
593  rs = conn->exec("SELECT * FROM Atoms;");
594  while (rs->fetch_row())
595  {
596  const char * n = rs->get_value("name");
597  printf ("found column with value %s\n", n);
598  }
599  rs->release();
600 
601  // Another way of doing things
602  Ola *ola = new Ola();
603  rs = conn->exec("SELECT * FROM Atoms;");
604  while (rs->fetch_row())
605  {
606  printf("--- method 2 has row:\n");
607  rs->foreach_column(&Ola::column_cb, ola);
608  }
609  rs->release();
610 
611  // A third way of doing things
612  ola->rs = rs;
613  rs = conn->exec("SELECT * FROM Atoms;");
614  rs->foreach_row(&Ola::row_cb, ola);
615  rs->release();
616 
617  delete conn;
618 
619  return 0;
620 }
621 
622 #endif /* UNIT_TEST_EXAMPLE */
623 
624 /* =========================================================== */
625 
626 #if OLD_UNPORTED_C_CODE
627 
628 /* =========================================================== */
629 
630 DuiDBRecordSet *
631 dui_odbc_connection_tables (DuiDBConnection *dbc)
632 {
633  DuiODBCConnection *conn = (DuiODBCConnection *) dbc;
634  DuiODBCRecordSet *rs;
635  SQLRETURN rc;
636 
637  ENTER ("(conn=%p)", conn);
638  if (!conn) return NULL;
639 
640  rs = dui_odbc_recordset_new (conn);
641  if (!rs) return NULL;
642 
643  rc = SQLTables (rs->sql_hstmt,NULL, 0, NULL, 0, NULL,0, NULL, 0);
644 
645  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
646  {
647  PERR ("Can't perform query rc=%d", rc);
648  PRINT_SQLERR (SQL_HANDLE_STMT, rs->sql_hstmt);
649  dui_odbc_recordset_release (&rs->recset);
650  return NULL;
651  }
652 
653  LEAVE ("(conn=%p)", conn);
654  /* Use numbr of columns to indicate that the query hasn't
655  * given results yet. */
656  rs->ncols = -1;
657  return &rs->recset;
658 }
659 
660 /* =========================================================== */
661 
662 DuiDBRecordSet *
663 dui_odbc_connection_table_columns (DuiDBConnection *dbc,
664  const char * tablename)
665 {
666  DuiODBCConnection *conn = (DuiODBCConnection *) dbc;
667  DuiODBCRecordSet *rs;
668  SQLRETURN rc;
669 
670  ENTER ("(conn=%p, table=%s)", conn, tablename);
671  if (!conn || !tablename) return NULL;
672 
673  rs = dui_odbc_recordset_new (conn);
674  if (!rs) return NULL;
675 
676  rc = SQLColumns (rs->sql_hstmt,NULL, 0, NULL, 0,
677  (char *) tablename, SQL_NTS, NULL, 0);
678 
679  if ((SQL_SUCCESS != rc) and (SQL_SUCCESS_WITH_INFO != rc))
680  {
681  PERR ("Can't perform query rc=%d", rc);
682  PRINT_SQLERR (SQL_HANDLE_STMT, rs->sql_hstmt);
683  dui_odbc_recordset_release (&rs->recset);
684  return NULL;
685  }
686 
687  LEAVE ("(conn=%p, table=%s)", conn, tablename);
688  /* Use number of columns to indicate that the query hasn't
689  * given results yet. */
690  rs->ncols = -1;
691  return &rs->recset;
692 }
693 
694 #endif /* OLD_UNPORTED_C_CODE */
695 
696 #endif /* HAVE_SQL_STORAGE */
697 /* ============================= END OF FILE ================= */
698 
ODBCRecordSet * exec(const char *)
int ncols
Definition: odbcxx.h:75
int arrsize
Definition: odbcxx.h:76
friend class ODBCRecordSet
Definition: odbcxx.h:45
bool is_connected
Definition: odbcxx.h:49
int get_col_by_name(const char *)
ODBCConnection(const char *dbname, const char *username, const char *authentication)
SQLHENV sql_henv
Definition: odbcxx.h:50
int * vsizes
Definition: odbcxx.h:80
SQLHSTMT sql_hstmt
Definition: odbcxx.h:73
std::stack< ODBCRecordSet * > free_pool
Definition: odbcxx.h:52
char ** column_labels
Definition: odbcxx.h:77
ODBCRecordSet(ODBCConnection *)
void get_column_labels(void)
void alloc_and_bind_cols(int ncols)
const char * get_value(const char *fieldname)
bool foreach_row(bool(T::*cb)(void), T *data)
Definition: odbcxx.h:102
int * column_datatype
Definition: odbcxx.h:78
std::string username
Definition: odbcxx.h:48
char ** values
Definition: odbcxx.h:79
std::string dbname
Definition: odbcxx.h:47
ODBCConnection * conn
Definition: odbcxx.h:72
bool connected(void) const
int fetch_row(void)
SQLHDBC sql_hdbc
Definition: odbcxx.h:51
bool foreach_column(bool(T::*cb)(const char *, const char *), T *data)
Definition: odbcxx.h:114
void release(void)
void extract_error(const char *)
void rewind(void)
ODBCRecordSet * get_record_set(void)