libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
ODBCWrapper.cpp
Go to the documentation of this file.
1 /******************************************************************************
2 ** Copyright © 2002 by J.M.McGuiness, coder@hussar.me.uk
3 **
4 ** This library is free software; you can redistribute it and/or
5 ** modify it under the terms of the GNU Lesser General Public
6 ** License as published by the Free Software Foundation; either
7 ** version 2.1 of the License, or (at your option) any later version.
8 **
9 ** This library is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ** Lesser General Public License for more details.
13 **
14 ** You should have received a copy of the GNU Lesser General Public
15 ** License along with this library; if not, write to the Free Software
16 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 
19 #include"ODBCWrapper.hpp"
20 #include<iostream>
21 #include<memory>
22 #include<ostream>
23 #include<sstream>
24 
25 using namespace libjmmcg;
26 using namespace NTUtils::Database::ODBC;
27 
28 namespace {
29 
30 const SQLUINTEGER rs_count_timeout=60; // Seconds.
31 const TCHAR timeout_sql_state[]=_T("HYT0");
32 
33 class ODBCErrInfo {
34 public:
35  const SQLTCHAR * const SqlState;
36  const SQLTCHAR * const Msg;
37  const SQLINTEGER NativeError;
38 
39  inline ODBCErrInfo(const SQLTCHAR * const a, const SQLTCHAR * const b, const SQLINTEGER c) : SqlState(a), Msg(b), NativeError(c) {};
40  inline ~ODBCErrInfo(void) {};
41  friend inline tostream & __fastcall operator<<(tostream &o, const ODBCErrInfo i) {o<<_T("State: '")<<reinterpret_cast<const TCHAR * const>(i.SqlState)<<_T("'\nMessage: '")<<reinterpret_cast<const TCHAR * const>(i.Msg)<<_T("'\nNative error number: ")<<i.NativeError<<std::endl;return o;};
42 
43 private:
44  inline ODBCErrInfo(void) : SqlState(NULL), Msg(NULL), NativeError(NULL) {};
45 };
46 
47 }
48 
49 inline
50 Database::ODBC::Exceptions::ODBCExceptionErr::ODBCExceptionErr(const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : std::exception() {
51  m_info<<_T("ODBC error details:\n");
52  bool timed_out;
53  m_info<<GetODBCERRInfoTimeout(hstmt, type, timed_out)<<std::endl;
54  if (timed_out) {
55  m_info<<_T("Timed out.\n");
56  }
57  m_info<<_T("ODBC error raw code:")<<err<<std::endl;
58 }
59 
60 inline
61 Database::ODBC::Exceptions::ODBCExceptionErr::~ODBCExceptionErr(void) {
62  m_info<<_T("ODBC Wrapper error.\n");
63 }
64 
65 inline
66 const TCHAR * __fastcall Database::ODBC::Exceptions::ODBCExceptionErr::what(void) {
67  tstring str(m_info.str());
68  str_hack=std::auto_ptr<TCHAR>(new TCHAR[str.size()+sizeof(TCHAR)]);
69  memcpy(str_hack.get(), str.c_str(), sizeof(TCHAR)*str.size());
70  return str_hack.get();
71 }
72 
73 inline
74 tstring __fastcall Database::ODBC::Exceptions::ODBCExceptionErr::GetODBCERRInfoTimeout(const SQLHANDLE * const hstmt, const SQLSMALLINT type, bool &timed_out) {
75  SQLTCHAR SqlState[6], Msg[SQL_MAX_MESSAGE_LENGTH];
76  SQLINTEGER NativeError;
77  SQLSMALLINT i=1, MsgLen;
78  SQLRETURN rc2;
79  tstringstream ss;
80  timed_out=false;
81  while ((rc2=SQLGetDiagRec(type, *hstmt, i, SqlState, &NativeError, Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) {
82  const tstring state(reinterpret_cast<TCHAR *>(SqlState));
83  if (state.find(timeout_sql_state)!=tstring::npos) {
84  timed_out=true;
85  }
86  ss<<ODBCErrInfo(SqlState, Msg, NativeError);
87  i++;
88  }
89  return ss.str();
90 }
91 
92 inline
93 Database::ODBC::Exceptions::EnvironmentErr::EnvironmentErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : ODBCExceptionErr(hstmt, type, err) {
94  m_info<<_T("Environment error. Details:\n");
95  m_info<<str<<std::endl;
96 }
97 
98 inline
99 Database::ODBC::Exceptions::EnvironmentErr::~EnvironmentErr(void) {
100 }
101 
102 inline
103 Database::ODBC::Exceptions::ConnectionErr::ConnectionErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : ODBCExceptionErr(hstmt, type, err) {
104  m_info<<_T("Connection error. Details:\n");
105  m_info<<str<<std::endl;
106 }
107 
108 inline
109 Database::ODBC::Exceptions::ConnectionErr::ConnectionErr(const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : ODBCExceptionErr(hstmt, type, err) {
110  m_info<<_T("Connection error. Details:\n");
111  m_info<<str<<std::endl;
112  m_info<<_T("SQL statement:\n");
113  m_info<<sql<<std::endl;
114 }
115 
116 inline
117 Database::ODBC::Exceptions::ConnectionErr::~ConnectionErr(void) {
118 }
119 
120 inline
121 Database::ODBC::Exceptions::RecordsetBaseErr::RecordsetBaseErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : ODBCExceptionErr(hstmt, type, err) {
122  m_info<<_T("Record set error. Details:\n");
123  m_info<<str<<std::endl;
124 }
125 
126 inline
127 Database::ODBC::Exceptions::RecordsetBaseErr::RecordsetBaseErr(const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : ODBCExceptionErr(hstmt, type, err) {
128  m_info<<_T("Record set error. Details:\n");
129  m_info<<str<<std::endl;
130  m_info<<_T("SQL statement:\n");
131  m_info<<sql<<std::endl;
132 }
133 
134 inline
135 Database::ODBC::Exceptions::RecordsetBaseErr::~RecordsetBaseErr(void) {
136  m_info<<_T("Read-only record set error.\n");
137 }
138 
139 inline
140 Database::ODBC::Exceptions::RORecordsetBaseErr::RORecordsetBaseErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : RecordsetBaseErr(str, hstmt, type, err) {
141  m_info<<_T("Read-only record set error.\n");
142 }
143 
144 inline
145 Database::ODBC::Exceptions::RORecordsetBaseErr::RORecordsetBaseErr(const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : RecordsetBaseErr(str, sql, hstmt, type, err) {
146 }
147 
148 inline
149 Database::ODBC::Exceptions::RORecordsetBaseErr::RORecordsetBaseErr(const SQLUSMALLINT col_num, const type_info &data, const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : RecordsetBaseErr(str, hstmt, type, err) {
150  m_info<<_T("Read-only record set error. Parameters:\n");
151  m_info<<_T("Column number: ")<<col_num<<std::endl;
152  m_info<<_T("Parameter type: '")<<data.name()<<_T("'")<<std::endl;
153 }
154 
155 inline
156 Database::ODBC::Exceptions::RORecordsetBaseErr::RORecordsetBaseErr(const SQLUSMALLINT col_num, const type_info &data, const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : RecordsetBaseErr(str, sql, hstmt, type, err) {
157  m_info<<_T("Read-only record set error. Parameters:\n");
158  m_info<<_T("Column number: ")<<col_num<<std::endl;
159  m_info<<_T("Parameter type: '")<<data.name()<<_T("'")<<std::endl;
160 }
161 
162 inline
163 Database::ODBC::Exceptions::RORecordsetBaseErr::~RORecordsetBaseErr(void) {
164  m_info<<_T("Write-only connection error.\n");
165 }
166 
167 inline
168 Database::ODBC::Exceptions::WOConnectionBaseErr::WOConnectionBaseErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : RORecordsetBaseErr(str, hstmt, type, err) {
169  m_info<<_T("Write-only connection error.\n");
170 }
171 
172 inline
173 Database::ODBC::Exceptions::WOConnectionBaseErr::WOConnectionBaseErr(const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : RORecordsetBaseErr(str, sql, hstmt, type, err) {
174  m_info<<_T("Write-only connection error.\n");
175 }
176 
177 inline
178 Database::ODBC::Exceptions::WOConnectionBaseErr::WOConnectionBaseErr(const SQLUSMALLINT col_num, const type_info &data, const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : RORecordsetBaseErr(col_num, data, str, hstmt, type, err) {
179  m_info<<_T("Write-only connection error.\n");
180 }
181 
182 inline
183 Database::ODBC::Exceptions::WOConnectionBaseErr::WOConnectionBaseErr(const SQLUSMALLINT col_num, const type_info &data, const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err) : RORecordsetBaseErr(col_num, data, str, sql, hstmt, type, err) {
184  m_info<<_T("Write-only connection error.\n");
185 }
186 
187 inline
188 Database::ODBC::Exceptions::WOConnectionBaseErr::~WOConnectionBaseErr(void) {
189 }
190 
191 inline
192 Environment::Environment(const bool read_only, const SQLUINTEGER timeout) : henv(NULL), hdbc(NULL) {
193  SQLRETURN ret=SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
194  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
195  throw Database::ODBC::Exceptions::EnvironmentErr(_T("Failed to allocate ODBC handle."), &henv, SQL_HANDLE_ENV, ret);
196  }
197  ret=SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, reinterpret_cast<SQLPOINTER>(SQL_OV_ODBC3), 0);
198  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
199  throw Database::ODBC::Exceptions::EnvironmentErr(_T("Failed to set ODBC version behaviour as v3.x on the environment handle."), &henv, SQL_HANDLE_ENV, ret);
200  }
201  ret=SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
202  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
203  throw Database::ODBC::Exceptions::EnvironmentErr(_T("Failed to allocate data base connection handle."), &henv, SQL_HANDLE_ENV, ret);
204  }
205  ret=SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, reinterpret_cast<SQLPOINTER>(timeout), 0);
206  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
207  throw Database::ODBC::Exceptions::EnvironmentErr(_T("Failed to set the login timeout on the data base connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
208  }
209  ret=SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(read_only ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), 0);
210  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
211  throw Database::ODBC::Exceptions::EnvironmentErr(_T("Failed to set the auto commit feature on the data base connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
212  }
213  ret=SQLSetConnectAttr(hdbc, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast<SQLPOINTER>(SQL_CP_ONE_PER_HENV), 0);
214  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
215  throw Database::ODBC::Exceptions::EnvironmentErr(_T("Failed to enable connection pooling on the data base connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
216  }
217  ret=SQLSetConnectAttr(hdbc, SQL_ATTR_CP_MATCH, reinterpret_cast<SQLPOINTER>(SQL_CP_STRICT_MATCH), 0);
218  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
219  throw Database::ODBC::Exceptions::EnvironmentErr(_T("Failed to set the connection pooling matching algorithm on the data base connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
220  }
221  ret=SQLSetConnectAttr(hdbc, SQL_ATTR_ACCESS_MODE, reinterpret_cast<SQLPOINTER>(read_only ? SQL_MODE_READ_ONLY : SQL_MODE_READ_WRITE), 0);
222  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
223  throw Database::ODBC::Exceptions::EnvironmentErr(_T("Failed to set the read/write attributes on the data base connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
224  }
225  ret=SQLSetConnectAttr(hdbc, SQL_ATTR_TXN_ISOLATION, reinterpret_cast<SQLPOINTER>(read_only ? SQL_TXN_READ_UNCOMMITTED : SQL_TXN_SERIALIZABLE), 0);
226  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
227  throw Database::ODBC::Exceptions::EnvironmentErr(_T("Failed to set the transation isolation level on the data base connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
228  }
229 }
230 
231 inline
232 Environment::~Environment(void) {
233  if (hdbc) {
234  SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
235  }
236  if (henv) {
237  SQLFreeHandle(SQL_HANDLE_ENV, henv);
238  }
239 }
240 
241 
242 inline
243 Connection::Connection(const tstring &cnx, const bool read_only, const SQLUINTEGER timeout) : Environment(read_only, timeout), stmt_timeout(timeout), hstmt(NULL) {
244  const SQLSMALLINT cnx_buff_sz=1024;
245  SQLTCHAR cnx_buff[cnx_buff_sz];
246  SQLSMALLINT StringLength2Ptr;
247  const SQLRETURN ret=SQLDriverConnect(hdbc, NULL, const_cast<SQLTCHAR *>(cnx.c_str()), SQL_NTS, cnx_buff, cnx_buff_sz, &StringLength2Ptr, SQL_DRIVER_COMPLETE);
248  cnx_str=cnx_buff;
249  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
250  throw Database::ODBC::Exceptions::ConnectionErr(_T("Failed to connect to the supplied DSN."), &hdbc, SQL_HANDLE_DBC, ret);
251  }
252  AllocateStmt();
253 }
254 
255 inline
256 Connection::~Connection(void) {
257  if (hstmt) {
258  SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
259  }
260  SQLDisconnect(hdbc);
261 }
262 
263 inline
264 void __fastcall Connection::execute(const tstring &sql) {
265  const SQLRETURN ret=SQLExecDirect(hstmt, const_cast<SQLTCHAR *>(sql.c_str()), SQL_NTS);
266  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
267  throw Database::ODBC::Exceptions::RecordsetBaseErr(_T("Failed to execute the SQL statement on the statement handle."), sql, &hstmt, SQL_HANDLE_STMT, ret);
268  }
269 }
270 
271 inline
272 void __fastcall Connection::AllocateStmt(void) {
273  SQLRETURN ret=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
274  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
275  throw Database::ODBC::Exceptions::ConnectionErr(_T("Failed to allocate the SQL statement handle on the data base handle."), &hdbc, SQL_HANDLE_DBC, ret);
276  }
277  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_CONCURRENCY, reinterpret_cast<SQLPOINTER>(SQL_CONCUR_READ_ONLY), 0);
278  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
279  throw Database::ODBC::Exceptions::ConnectionErr(_T("Failed to set the concurrency attribute as a forward-only cursor on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
280  }
281  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_TYPE, reinterpret_cast<SQLPOINTER>(SQL_CURSOR_FORWARD_ONLY), 0);
282  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
283  throw Database::ODBC::Exceptions::ConnectionErr(_T("Failed to set the cursor attribute as a forward-only cursor on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
284  }
285  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_SCROLLABLE, reinterpret_cast<SQLPOINTER>(SQL_NONSCROLLABLE), 0);
286  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
287  throw Database::ODBC::Exceptions::ConnectionErr(_T("Failed to set the non-scollable cursor attribute on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
288  }
289  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>(stmt_timeout), 0);
290  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
291  throw Database::ODBC::Exceptions::ConnectionErr(_T("Failed to set the query time-out attribute on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
292  }
293  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_RETRIEVE_DATA, reinterpret_cast<SQLPOINTER>(SQL_RD_ON), 0);
294  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
295  throw Database::ODBC::Exceptions::ConnectionErr(_T("Failed to set the data retrieval attribute on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
296  }
297  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_NOSCAN, reinterpret_cast<SQLPOINTER>(SQL_NOSCAN_ON), 0);
298  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
299  throw Database::ODBC::Exceptions::ConnectionErr(_T("Failed to turn off DBMS-specific scanning on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
300  }
301 }
302 
303 inline
304 RecordsetBase::RecordsetBase(const tstring &cnx, const bool read_only, const SQLUINTEGER timeout) : Connection(cnx, read_only, timeout), allocated_cursor(false), row_count(-1) {
305 }
306 
307 inline
308 RecordsetBase::~RecordsetBase(void) {
309  if (allocated_cursor) {
310  SQLCloseCursor(hstmt);
311  }
312 }
313 
314 inline
315 void __fastcall RecordsetBase::execute(const tstring &sql) {
316  Connection::execute(sql);
317  allocated_cursor=true;
318  row_count=-1;
319 }
320 
321 inline
322 void __fastcall RecordsetBase::RowsAffected(void) {
323  const SQLRETURN ret=SQLRowCount(hstmt, &row_count);
324  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
325  throw Database::ODBC::Exceptions::RecordsetBaseErr(_T("Failed to get the row count from the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
326  }
327 }
328 
329 inline
330 RORecordsetBase::RORecordsetBase(const tstring &cnx, const SQLUINTEGER timeout) : RecordsetBase(cnx, true, timeout), no_more_data(true) {
331 }
332 
333 inline
334 RORecordsetBase::~RORecordsetBase(void) {
335 }
336 
337 inline
338 void __fastcall RORecordsetBase::execute(const tstring &sql) {
339  RecordsetBase::execute(sql);
340  sql_statement=sql;
341  const SQLRETURN ret=SQLNumResultCols(hstmt, &num_cols);
342  assert(num_cols>0);
343  if (!num_cols || (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO)) {
344  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to get the number of columns returned by the SQL statement from the statement handle."), sql, &hstmt, SQL_HANDLE_STMT, ret);
345  }
346  column_names.clear();
347 }
348 
349 inline
350 SQLINTEGER __fastcall RORecordsetBase::count(void) {
351  if (row_count==-1) {
352  // Whack up the timeout, as I've seen it sometimes time out if the db server is busy.
353  RORecordset rs(cnx_string(), rs_count_timeout);
354  // Need to name the column, otherwise the "field_cast(...)" doesn't work because the index
355  // is poo.
356  rs.execute(_T("select count(*) as pants from (") + sql_statement + _T(") as tmp_cnt_table"));
357  rs.item(1, row_count);
358  }
359  return RecordsetBase::count();
360 }
361 
362 // Avoid passing the map across a dll boundary, otherwise you get silly memory errors when accessing it...
363 // Hence no "inline".
364 SQLSMALLINT __fastcall RORecordsetBase::GetColumnIdx(const tstring &col_name) {
365  return column_names[col_name];
366 }
367 
368 inline
369 RORecordset::RORecordset(const tstring &cnx, const SQLUINTEGER timeout) : RORecordsetBase(cnx, timeout) {
370 }
371 
372 inline
373 RORecordset::~RORecordset(void) {
374 }
375 
376 inline
377 void __fastcall RORecordset::execute(const tstring &sql) {
378  RORecordsetBase::execute(sql);
379  SQLSMALLINT i=1;
380  SQLTCHAR ColumnName[MAX_PATH];
381  SQLSMALLINT NameLengthPtr;
382  SQLSMALLINT DataTypePtr;
383  SQLUINTEGER ColumnSizePtr;
384  SQLSMALLINT DecimalDigitsPtr;
385  SQLSMALLINT NullablePtr;
386  do {
387  const SQLRETURN ret=SQLDescribeCol(hstmt, i, ColumnName, MAX_PATH, &NameLengthPtr, &DataTypePtr, &ColumnSizePtr, &DecimalDigitsPtr, &NullablePtr);
388  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
389  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to get the required SQL column details from the statement handle."), sql, &hstmt, SQL_HANDLE_STMT, ret);
390  }
391  column_names[ColumnName]=i;
392  } while (++i<=NumColumns());
393  assert(NumColumns()==column_names.size());
394  no_more_data=false;
395  moveNext();
396 }
397 
398 inline
399 void __fastcall RORecordset::moveNext(void) {
400  const SQLRETURN ret=SQLFetch(hstmt);
401  if (ret==SQL_NO_DATA) {
402  no_more_data=true;
403  } else if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
404  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to fetch the next data on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
405  }
406 }
407 
408 inline
409 ROBulkRecordset::ROBulkRecordset(const tstring &cnx, const SQLUINTEGER row_arr_sz, const SQLUINTEGER timeout) : RORecordsetBase(cnx, timeout), row_buffer_size(row_arr_sz) {
410 }
411 
412 inline
413 ROBulkRecordset::~ROBulkRecordset(void) {
414 }
415 
416 inline
417 void __fastcall ROBulkRecordset::execute(const tstring &sql) {
418  SQLRETURN ret=SQLFreeStmt(hstmt, SQL_UNBIND);
419  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
420  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to unbind any old row data blocks from the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
421  }
422  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_RETRIEVE_DATA, reinterpret_cast<SQLPOINTER>(SQL_RD_OFF), 0);
423  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
424  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to set the data retrieval attribute on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
425  }
426  RORecordsetBase::execute(sql);
427  cols_sizes.clear();
428  SQLSMALLINT i=1;
429  SQLTCHAR ColumnName[MAX_PATH];
430  SQLSMALLINT NameLengthPtr;
431  SQLSMALLINT DataTypePtr;
432  SQLUINTEGER ColumnSizePtr;
433  SQLSMALLINT DecimalDigitsPtr;
434  SQLSMALLINT NullablePtr;
435  TotalColumnSizePtr=0;
436  size_t offset=0;
437  do {
438  ret=SQLDescribeCol(hstmt, i, ColumnName, MAX_PATH, &NameLengthPtr, &DataTypePtr, &ColumnSizePtr, &DecimalDigitsPtr, &NullablePtr);
439  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
440  tostringstream ss;
441  ss<<_T("Failed to get the required ")<<i<<_T("-th SQL column details from the statement handle.");
442  throw Database::ODBC::Exceptions::RORecordsetBaseErr(ss.str(), sql, &hstmt, SQL_HANDLE_STMT, ret);
443  }
444  column_names[ColumnName]=i;
445  // Seems to under-estimate the size of strings that are returned by 2 bytes....
446  const size_t size=ColumnSizePtr+2*sizeof(TCHAR);
447  cols_sizes.push_back(std::pair<size_t, size_t>(size, offset));
448  // Remember to add on a bit for the the element status...
449  offset+=size+sizeof(SQLINTEGER);
450  } while (++i<=NumColumns());
451  TotalColumnSizePtr=offset;
452  assert(NumColumns()==cols_sizes.size());
453  assert(NumColumns()==column_names.size());
454  SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
455  AllocateStmt();
456  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_RETRIEVE_DATA, reinterpret_cast<SQLPOINTER>(SQL_RD_ON), 0);
457  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
458  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to set the data retrieval attribute on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
459  }
460  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE, reinterpret_cast<SQLPOINTER>(TotalColumnSizePtr), 0);
461  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
462  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to bind the row data block to the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
463  }
464  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, reinterpret_cast<SQLPOINTER>(row_buffer_size), 0);
465  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
466  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to set the number of rows in the row data block attribute in the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
467  }
468  AllocateDataBlock();
469  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_STATUS_PTR, RowStatusArray.get(), 0);
470  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
471  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to bind the rows status array to the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
472  }
473  ret=SQLSetStmtAttr(hstmt, SQL_ATTR_ROWS_FETCHED_PTR, &NumRowsFetched, 0);
474  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
475  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to bind the number of rows filled variable to the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
476  }
477  i=0;
478  do {
479  ret=SQLBindCol(
480  hstmt,
481  i+1,
482  SQL_C_DEFAULT,
483  rows_buffer.get()+cols_sizes[i].second,
484  cols_sizes[i].first,
485  reinterpret_cast<SQLINTEGER *>(
486  rows_buffer.get()+
487  (
488  i<(cols_sizes.size()-1) ?
489  cols_sizes[i+1].second
490  :
491  TotalColumnSizePtr
492  )
493  -sizeof(SQLINTEGER)
494  )
495  );
496  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
497  tostringstream ss;
498  ss<<_T("Failed to bind the ")<<i<<_T("-th element from a row in the row data block to the statement handle.");
499  throw Database::ODBC::Exceptions::RORecordsetBaseErr(ss.str(), &hstmt, SQL_HANDLE_STMT, ret);
500  }
501  } while (++i<cols_sizes.size());
502  RecordsetBase::execute(sql);
503  row_buffer_pos=0;
504  no_more_data=false;
505  RawMoveNext();
506 }
507 
508 inline
509 void __fastcall ROBulkRecordset::moveNext(void) {
510  ++row_buffer_pos;
511  if (row_buffer_pos>=NumRowsFetched) {
512  row_buffer_pos=0;
513  RawMoveNext();
514  }
515 }
516 
517 inline
518 void __fastcall ROBulkRecordset::RawMoveNext(void) {
519  const SQLRETURN ret=SQLFetch(hstmt);
520  assert(NumRowsFetched>=0);
521  if (ret==SQL_NO_DATA) {
522  assert(NumRowsFetched==0);
523  no_more_data=true;
524  return;
525  } else if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
526  throw Database::ODBC::Exceptions::RORecordsetBaseErr(_T("Failed to fetch the next block of data on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
527  }
528  assert(NumRowsFetched>0);
529 }
530 
531 inline
532 void __fastcall ROBulkRecordset::AllocateDataBlock(void) {
533  assert(TotalColumnSizePtr);
534  assert(row_buffer_size);
535  rows_buffer=std::auto_ptr<RowBufferElementType>(new RowBufferElementType[TotalColumnSizePtr*row_buffer_size]);
536  RowStatusArray=std::auto_ptr<SQLUSMALLINT>(new SQLUSMALLINT[row_buffer_size]);
537 }
538 
539 inline
540 const ROBulkRecordset::RowBufferElementType * const __fastcall ROBulkRecordset::GetRowElement(const SQLUSMALLINT col_num) const {
541  return rows_buffer.get()+row_buffer_pos*TotalColumnSizePtr+cols_sizes[col_num-1].second;
542 }
543 
544 inline
545 WOConnectionBase::WOConnectionBase(const tstring &cnx, const SQLUINTEGER timeout) : RecordsetBase(cnx, false, timeout), in_transaction(false) {
546  SQLUSMALLINT tx_level;
547  SQLSMALLINT StringLengthPtr;
548  SQLRETURN ret=SQLGetInfo(hdbc, SQL_TXN_CAPABLE, reinterpret_cast<SQLPOINTER>(&tx_level), sizeof(SQLUSMALLINT), &StringLengthPtr);
549  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
550  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Failed to find transaction support information on the connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
551  }
552  if (tx_level==SQL_TC_NONE) {
553  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Transactions (which this applicationrequires) are not supported at all on the data base connection driver."), &hdbc, SQL_HANDLE_DBC, ret);
554  }
555  SQLTCHAR many_txs[]=_T(" \0");
556  ret=SQLGetInfo(hdbc, SQL_MULTIPLE_ACTIVE_TXN, reinterpret_cast<SQLPOINTER>(&many_txs), sizeof(many_txs), &StringLengthPtr);
557  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
558  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Failed to find multiple-transaction support information on the data base connection driver."), &hdbc, SQL_HANDLE_DBC, ret);
559  }
560  if (many_txs[0]!='Y') {
561  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Multiple transactions are not supported at all on the data base connection driver."), &hdbc, SQL_HANDLE_DBC, ret);
562  }
563 }
564 
565 inline
566 WOConnectionBase::~WOConnectionBase(void) {
567  if (in_transaction) {
568  rollback();
569  }
570 }
571 
572 inline
573 void __fastcall WOConnectionBase::commit(void) {
574  const SQLRETURN ret=SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);
575  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
576  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Failed to commit a transaction on the connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
577  }
578  in_transaction=false;
579 }
580 
581 inline
582 void __fastcall WOConnectionBase::rollback(void) {
583  const SQLRETURN ret=SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_ROLLBACK);
584  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
585  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Failed to rollback a transaction on the connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
586  }
587  in_transaction=false;
588 }
589 
590 inline
591 SQLINTEGER __fastcall WOConnectionBase::count(void) {
592  RecordsetBase::RowsAffected();
593  return RecordsetBase::count();
594 }
595 
596 inline
597 bool __fastcall WOConnectionBase::in_trans(void) const {
598  return in_transaction;
599 }
600 
601 // Avoid passing the map across a dll boundary, otherwise you get silly memory errors when accessing it...
602 // Hence no "inline".
603 void __fastcall WOConnectionBase::AddExecData(const SQLUSMALLINT col_num, const str_data_at_exec_type &data) {
604  exec_data[col_num]=data;
605 }
606 
607 // Avoid passing the map across a dll boundary, otherwise you get silly memory errors when accessing it...
608 // Hence no "inline".
609 WOConnectionBase::str_data_at_exec_type & __fastcall WOConnectionBase::ExecData(const SQLUSMALLINT col_num) {
610  return exec_data[col_num];
611 }
612 
613 inline
614 WOConnection::WOConnection(const tstring &cnx, const SQLUINTEGER timeout) : WOConnectionBase(cnx, timeout) {
615 }
616 
617 inline
618 WOConnection::~WOConnection(void) {
619 }
620 
621 inline
622 void __fastcall WOConnection::execute(const tstring &sql) {
623  assert(!in_transaction);
624 /* if (in_transaction) {
625  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Already in a transaction on the connection handle - it must be committed first."), sql, &hdbc, SQL_HANDLE_DBC, SQL_ERROR);
626  }
627 */WOConnectionBase::execute(sql);
628  in_transaction=true;
629 }
630 
631 inline
632 WOBulkConnection::WOBulkConnection(const tstring &cnx, const SQLUINTEGER row_arr_sz, const SQLUINTEGER timeout) : WOConnectionBase(cnx, timeout), row_buffer_size(row_arr_sz), prepared(false) {
633  SQLUSMALLINT property;
634  SQLSMALLINT StringLengthPtr;
635  SQLRETURN ret=SQLGetInfo(hdbc, SQL_CURSOR_COMMIT_BEHAVIOR, reinterpret_cast<SQLPOINTER>(&property), sizeof(SQLUSMALLINT), &StringLengthPtr);
636  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
637  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Failed to get the prepared statement behaviour after commits on the connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
638  }
639  sp_behaviour.commit=(property!=SQL_CB_DELETE);
640  ret=SQLGetInfo(hdbc, SQL_CURSOR_ROLLBACK_BEHAVIOR, reinterpret_cast<SQLPOINTER>(&property), sizeof(SQLUSMALLINT), &StringLengthPtr);
641  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
642  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Failed to get the prepared statement behaviour after rollbacks on the connection handle."), &hdbc, SQL_HANDLE_DBC, ret);
643  }
644  sp_behaviour.rollback=(property!=SQL_CB_DELETE);
645 }
646 
647 inline
648 WOBulkConnection::~WOBulkConnection(void) {
649 }
650 
651 inline
652 void __fastcall WOBulkConnection::prepare(const tstring &sql) {
653  assert(!in_transaction);
654  if (in_transaction) {
655  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Already in a transaction on the connection handle - it must be committed first."), sql_statement, &hdbc, SQL_HANDLE_DBC, SQL_ERROR);
656  }
657  if (prepared) {
658  const SQLRETURN ret=SQLFreeStmt(hstmt, SQL_RESET_PARAMS);
659  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
660  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Failed to unbind the previously bound paramaters on the SQL statement on the statement handle."), sql_statement, &hstmt, SQL_HANDLE_STMT, ret);
661  }
662  }
663  if (sql!=sql_statement){
664  prepared=false;
665  }
666  if (!prepared) {
667  sql_statement=sql;
668  SQLRETURN ret=SQLPrepare(hstmt, const_cast<SQLTCHAR *>(sql.c_str()), SQL_NTS);
669  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
670  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Failed to prepare the SQL statement on the statement handle."), sql_statement, &hstmt, SQL_HANDLE_STMT, ret);
671  }
672  prepared=true;
673  }
674  assert(prepared);
675 }
676 
677 inline
678 void __fastcall WOBulkConnection::execute(void) {
679  assert(prepared);
680  assert(!in_transaction);
681  if (in_transaction) {
682  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Already in a transaction on the connection handle - it must be committed first."), sql_statement, &hdbc, SQL_HANDLE_DBC, SQL_ERROR);
683  }
684  SQLRETURN ret=SQLExecute(hstmt);
685  if (ret==SQL_NEED_DATA) {
686  SQLPOINTER pToken;
687  while ((ret=SQLParamData(hstmt, &pToken))==SQL_NEED_DATA) {
688  ret=SQLPutData(hstmt, exec_data[reinterpret_cast<SQLUSMALLINT>(pToken)].str->begin(), SQL_NTS);
689  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
690  throw Database::ODBC::Exceptions::WOConnectionBaseErr(reinterpret_cast<SQLUSMALLINT>(pToken), typeid(*(exec_data[reinterpret_cast<SQLUSMALLINT>(pToken)].str)), _T("Failed to bung some data into the prepared, but hungry SQL statement on the statement handle."), sql_statement, &hstmt, SQL_HANDLE_STMT, ret);
691  }
692  }
693  }
694  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
695  throw Database::ODBC::Exceptions::WOConnectionBaseErr(_T("Failed to execute the prepared SQL statement on the statement handle."), sql_statement, &hstmt, SQL_HANDLE_STMT, ret);
696  }
697  in_transaction=true;
698 }
699 
700 inline
701 void __fastcall WOBulkConnection::commit(void) {
702  WOConnectionBase::commit();
703  if (!sp_behaviour.commit) {
704  prepared=false;
705  prepare(sql_statement);
706  }
707 }
708 
709 inline
710 void __fastcall WOBulkConnection::rollback(void) {
711  WOConnectionBase::rollback();
712  if (!sp_behaviour.rollback) {
713  prepared=false;
714  prepare(sql_statement);
715  }
716 }