libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
ODBCWrapper.hpp
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 #pragma once
20 
21 // 4284 return type for 'X::operator ->' is 'Y *' (ie; not a UDT or reference to a UDT. Will produce errors if applied using infix notation)
22 #pragma warning(disable:4284)
23 // 4786 identifier was truncated to '255' characters in the browser information
24 #pragma warning(disable:4786)
25 // 4710 function '...' not inlined
26 #pragma warning(disable:4710)
27 // 4514 unreferenced inline function has been removed
28 #pragma warning(disable:4514)
29 
30 #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
31 
32 #ifdef ODBC_EXPORTS
33 #define ODBC_DECLSPEC __declspec(dllexport)
34 #define ODBC_EXTERN
35 #else
36 #define ODBC_DECLSPEC __declspec(dllimport)
37 #define ODBC_EXTERN extern
38 #endif
39 
40 #include<windows.h>
41 #include<typeinfo.h>
42 #include<sql.h>
43 #include<sqlext.h>
44 // This header file is not really required, but I leave it here as a reminder that it exists
45 // and is strictly part of the ODBC API. Basically it declares bcp functionality, and
46 // distributed server functionality. I don't include it to save compilation time.
47 //#include <odbcss.h>
48 #include<tchar.h>
49 #include<cassert>
50 #include<map>
51 #include<string>
52 #include<sstream>
53 #include<typeinfo.h>
54 #include<vector>
55 
56 namespace jmmcg {
57 
61 
62  // Why like this? Because otherwise we'd have the code too far to the right.
63  namespace NTUtils { namespace Database {
64 
65  /**
66  For details on the ODBC SDK see the following link in the MSDN (assuming the Jan 2000
67  MSDN is installed):
68 
69  "mk:@MSITStore:D:\devtools\Microsoft%20Visual%20Studio\MSDN\2000JAN\1033"
70 
71  To see a comparison of ADO/OLE DB/ODBC see (M$ keeps moving these around...):
72 
73  <a href="http://msdn.microsoft.com/library/psdk/sql/highprog.htm"/>
74 
75  Or:
76 
77  ADO: <a href="http://msdn.microsoft.com/library/en-us/architec/8_ar_ad_4zqn.asp?frame=true"/>
78  Excerpt from page:
79  "...ADO is the API most recommended for general-purpose data access to SQL Server for these reasons:
80  - ADO is easy to learn and program.
81  - ADO has the feature set required by most general-purpose applications.
82  - ADO enables programmers to quickly produce robust applications..."
83 
84  ODBC: <a href="http://msdn.microsoft.com/library/en-us/architec/8_ar_ad_86lf.asp?frame=true"/>
85  Excerpt from page:
86  "...ODBC can be used in tools, utilities, or system level development needing either top performance or access to SQL Server features. These features include:
87  - A set of bulk copy functions based on the earlier DB-Library bulk copy functions.
88  - Extensions to the ODBC diagnostic functions and records to get SQL Server-specific information from messages and errors.
89  - A set of functions that exposes catalog information from the linked servers used in SQL Server distributed queries.
90  - Various driver-specific attributes and connection string keywords to control SQL Server-specific behaviors."
91 
92  Useage details:
93  ===============
94 
95  This ODBC wrapper requires a minimum of ODBC version 3 header files/libs/dlls.
96  They must support the following, to quote the MSDN:
97 
98  "ODBC aligns with the following specifications and standards that deal with the
99  Call-Level Interface (CLI). (The ODBC features are a superset of each of these
100  standards.)
101 
102  - The X/Open CAE Specification "Data Management: SQL Call-Level Interface (CLI)"
103  - ISO/IEC 9075-3:1995 (E) Call-Level Interface (SQL/CLI)
104 
105  As a result of this alignment, the following are true:
106 
107  - An application written to the X/Open and ISO CLI specifications will work with
108  an ODBC 3.x driver or a standards-compliant driver when it is compiled with the
109  ODBC 3.x header files and linked with ODBC 3.x libraries, and when it gains
110  access to the driver through the ODBC 3.x Driver Manager.
111  - A driver written to the X/Open and ISO CLI specifications will work with an
112  ODBC 3.x application or a standards-compliant application when it is compiled
113  with the ODBC 3.x header files and linked with ODBC 3.x libraries, and when the
114  application gains access to the driver through the ODBC 3.x Driver Manager.
115  (For more information, see "Standards-Compliant Applications and Drivers
116  <odbcstandards_compliant_applications_and_drivers.htm>" in Chapter 17,
117  "Programming Considerations.")"
118 
119  Further specific details of this layer follow:
120  ==============================================
121 
122  1. You must have the "Platform SDK" installed. (Specifically the ODBC SDK in the
123  "Data Access Services\MDAC SDK" books.) You can download the MDAC SDK from the M$
124  web site at: "http://www.microsoft.com/Data/download.htm". The MDAC SDK contains
125  just the ADO/OLE DB/ODBC SDKs, a sub-set of the Platform SDK.
126 
127  2. To run executables created with this code you must install the required ODBC drivers.
128  These must conform to the above specification. To get the correct ODBC to SQL server
129  drivers, which are included in MDAC there are two ways:
130 
131  a) Install them using "mdac_typ.exe" available from:
132 
133  <a href="http://www.microsoft.com/data/download.htm"/>
134 
135  The minimum version to install is "2.1.2.4202.3 (GA)". Note that this installs:
136 
137  - ADO v 2.10.4202.1
138  - ODBC v 3.510.4202.0
139 
140  Also note that as of MDAC v2.6, ODBC support (amongst others) is no longer included.
141 
142  b) MDAC v2.5 is shipped with Windows 2000 & Windows XP. This page:
143 
144  "http://support.microsoft.com/support/kb/articles/Q271/9/08.ASP"
145 
146  Search for article id "Q271908". I quote from that page:
147 
148  "The ODBC Desktop Drivers and Visual FoxPro ODBC driver, which no longer ship
149  with MDAC 2.6, are shipped with the Microsoft Windows 2000 operating system
150  (which contains MDAC 2.5). Microsoft Windows Millennium Edition (Me) also ships
151  with MDAC 2.5.
152 
153  Microsoft Windows XP also contains the ODBC Desktop Drivers and Visual FoxPro
154  Driver. For Microsoft Windows NT and Microsoft Windows 9x, MDAC 2.5 or an
155  earlier version will need to be installed to obtain these drivers."
156 
157 
158  Amongst other things. (See the release manifest at:
159  <a href="http://www.microsoft.com/data/MDAC21info/MDAC21sp2manifest.htm"/>.)
160 
161  3. The exceptions all inherit from the "ODBCExceptionErr" class, which can be used as
162  a filter to catch all exceptions thrown by this layer.
163 
164  4. The "Environment" class is pretty useless, apart from the fact that it hides a
165  lot of the messy allocation and deallocation details of accessing the ODBC drivers.
166  This class is derived from to be actually useful, despite the fact that it is
167  concrete. Note that this class checks to ensure the ODBC layer is the correct version.
168  Which must be ODBC version 3.
169 
170  5. The "Connection" class wraps a few more messy ODBC details regarding setting
171  various connection based attributes for read only or write-only connections.
172  Again this class is only really useful if derived from. By default this class enables
173  connection pooling. Note that DBMS-specific translation on SQL strings is turned off
174  by this class for speed. Hence only ANSI SQL is allowed.
175 
176  6. The "RORecordset" class. This is a fairly trivial class that gets the data from the
177  data base one element at a time. It was created mainly to be able to implement the
178  loathed "count()" more memory efficiently than using the "ROBulkRecordset". Tests reveal
179  that this class is over 10% slower than the "ROBulkRecordset" class for a given SQL
180  statement. So basically only use it if you want a single row with a single column
181  as the result of the SQL. For any other case don't use it, use it's big brother.
182 
183  7. The "ROBulkRecordset" class. Now we're getting to the meat of this layer.
184  This class is for read only (i.e. no SQL "update", "insert" or "delete" statements)
185  data base manipulations. It attempts to mimick a real recordset, at which it is only
186  possibly partially successful. It does not require an ODBC connection to be in scope
187  as it creates and manages its own. The reference to "bulk" in the name means that
188  the class has the ability to pre-fetch a pre-defined quantity of rows from the data
189  base at a time. (Calls to "moveNext()" only get more data from the data base when the
190  internal buffer is exhausted.) In general it has been found that optimium values for
191  the internal buffer size are either "1" (one) or >100. Some testing may be required
192  to determine the exact value, which will be dependant upon the process, the data base
193  server, the network, and the dynamic profiles of these.
194 
195  8. The "WOConnection" class. This is used to write data to the data base. It is implemented as
196  write-only, but this is not a limitation of ODBC, only the wrapper. The "count()" function
197  returns the number of records affected by the update. (Note well the comments about
198  "count()" in "RecordsetBase".) Also it is perfectly acceptable to have multiple
199  "WOConnection"s in scope with the same data base connection string. Further it is acceptable
200  to have multiple transactions (they are wrapped within the "WOConnection" class) active
201  at any one time, even on the same table. But obviously not the same row... The
202  "WOConnection" class checks to ensure that multiple transaction support is supported by the
203  data base driver. (This is part of the ODBC v2.0 standard.)
204 
205  9. The "WOBulkConnection" class. This is also used to write data to the data base. It prepares
206  the SQL statement before execution. (Hence the "execute()" implementation does not take an
207  argument.) Also you can optionally bind the arguments the SQL needs, if it is parameterised.
208  Note that if you bind a "tstring" this MUST be sized correctly before binding. i.e. the
209  variable must be declared as "tstring param(50, '\0');" where "50" is at least the size
210  of the string in the data base, and '\0' is the value the string is initialised to. ('\0', or
211  NULL is the fastest value to use.) Otherwise you'll get exceptions thrown at run-time when
212  committing the SQL. Note that this class does similar multiple transaction support checks,
213  etc, as per the "WOConnection" class.
214 
215  Performance vs. ADO:
216  ====================
217 
218  In all cases the core loop has been designed to be such that the rate determining step is the
219  data base activity. Also all values are quoted to within 10% error (average deviation).
220  In all read cases an entire row of the calls table was returned.
221 
222  1. "RORecordset": ~50% faster than ADO.
223  2. "ROBulkRecordset":
224  - 1 row: 3x faster.
225  - 10 rows: 20% slower.
226  - 100 rows: same speed as ADO.
227  - 1000 rows: 3x faster.
228  - >1000 rows: varies depending upon returned data. If the data is the full row of a calls
229  table, as 1000. If the data is an arbitrary number of non-nullable "long"s,
230  up to 100x faster. (i.e. so fast the data base is no longer the limiting
231  factor, but the memory copies to get the data out of the recordset.)
232  The constructor for the "ROBulkRecordset" can take a argument of the number of rows it is
233  to fetch at a time. By default this is set to "1" (one), as reasonable & fast default that
234  can only be beaten by careful tweaking.
235  3. "WOConnection": ~50% faster than ADO.
236  4. "WOBulkConnection": About the same speed as "WOConnection" for simple SQL. Untested for more
237  complex SQL.
238  5. Indexing the elements in a row. Testing has revealed that indexing an element of a
239  row by the name is around 40% slower than using the *correct* column number. If your SQL
240  returns a single column, plainly this can be indexed by number without causing a dependancy
241  on the data base table design. (And is recommended.) (Specifically the order in which rows
242  are returned.) If more than one column is returned and indexing by column number is used,
243  a dependancy on the data base table design is introduced, which may not be acceptable.
244 
245  As a matter of interest the ODBC layer (thus ADO) cannot transfer a single character into
246  an SQL table with a column of type "char" size 1. It has to make a string out of this. Thus
247  it would be better to use a "tinyint" of size 1, precision 3 and cast it. (This is equivalent
248  to the C data type "char". (Also the ODBC layer has to do a lot more messing around with
249  passing the data too, which makes it even worse.)
250 
251  Future Improvements:
252  ====================
253 
254  1. Write a prepared "ROBulkRecordset" layer & test its performance.
255  2. Improve the error reporting to give even more information.
256  3. Implement "item(... , tstring &...)".
257  4. Implement "item(...)" for nullable columns. Currently if a nullable element that is "NULL"
258  is returned, an ODBC exception of "SQLState 22002: Indicator variable required but not
259  supplied" is thrown. This is because the "StrLen_or_IndPtr" parameter is set to "NULL", as
260  it is unused.
261  5. Resolve the use of maps across a dll boundary, as really slows the stuff down. (Compiler
262  bugs again prevent me from doing this...)
263  6. More...?
264 
265  */
266  namespace ODBC {
267 
268  namespace Exceptions {
269 
270  // I *know* that officially you shouldn't derive from "std::exception",
271  // (because the destructor is not virtual) but it is just *sooo* handy
272  // to derive from it, so that a "catch (std::exception &err) {..."
273  // can be done to catch these too. Anyway exceptions *should* be in a
274  // hierarchy. Nyyyerrr. It's my code & I'll do what I want!
276  public:
277  virtual inline ~ODBCExceptionErr(void);
278 
279  // I'd really like this to be a "const" member function, but, alas,
280  // another limitation of "std::exception" is that this function is
281  // declared as non-const....
282  inline const TCHAR * __fastcall what(void) noexcept(true);
283 
284  protected:
287 
288  inline ODBCExceptionErr(const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
289 
290  private:
291  static inline tstring __fastcall GetODBCERRInfoTimeout(const SQLHANDLE * const hstmt, const SQLSMALLINT type, bool &timed_out);
292  };
293 
295  public:
296  inline EnvironmentErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
297  inline ~EnvironmentErr(void);
298 
299  };
300 
302  public:
303  inline ConnectionErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
304  inline ConnectionErr(const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
305  inline ~ConnectionErr(void);
306  };
307 
309  public:
310  inline RecordsetBaseErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
311  inline RecordsetBaseErr(const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
312  inline ~RecordsetBaseErr(void);
313  };
314 
316  public:
317  inline RORecordsetBaseErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
318  inline RORecordsetBaseErr(const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
319  inline RORecordsetBaseErr(const SQLUSMALLINT col_num, const type_info &data, const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
320  inline 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);
321  inline ~RORecordsetBaseErr(void);
322  };
323 
325  public:
326  inline WOConnectionBaseErr(const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
327  inline WOConnectionBaseErr(const tstring &str, const tstring &sql, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
328  inline WOConnectionBaseErr(const SQLUSMALLINT col_num, const type_info &data,const tstring &str, const SQLHANDLE * const hstmt, const SQLSMALLINT type, const SQLRETURN err);
329  inline 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);
330  inline ~WOConnectionBaseErr(void);
331  };
332 
333  }
334 
336  public:
337  inline Environment(const bool read_only = true, const SQLUINTEGER timeout=15);
338  virtual inline ~Environment(void) noexcept(true);
339 
340  protected:
342 
343  private:
344  SQLHENV henv;
345 
346  // Prevent copying or assignment.
347  inline Environment(const Environment &) noexcept(true) {assert(false);}
348  inline Environment & __fastcall operator=(const Environment &) noexcept(true) {assert(false);return *this;}
349  };
350 
352  public:
353  inline Connection(const tstring &cnx, const bool read_only, const SQLUINTEGER timeout=15);
354  inline ~Connection(void) noexcept(true);
355  inline const tstring &__fastcall cnx_string() const noexcept(true) {return cnx_str;}
356  virtual inline void __fastcall execute(const tstring &sql);
357  virtual inline void __fastcall execute(const tstringstream &sql) {execute(sql.str());}
358 
359  protected:
362 
363  inline void __fastcall AllocateStmt();
364 
365  private:
367 
368  // Prevent assignment.
369  inline Connection & __fastcall operator=(const Connection &) noexcept(true) {assert(false);return *this;}
370 
371  };
372 
374  public:
375  inline RecordsetBase(const tstring &cnx, const bool read_only, const SQLUINTEGER timeout=15);
376  inline ~RecordsetBase() noexcept(true);
377  inline void __fastcall execute(const tstring &sql);
378  // Return the number of rows affected by the previously executed SQL.
379  // The return value of this is either:
380  // 1. "-1" signifying that the count could not be obtained (possibly due to limitiations
381  // imposed by the driver, cursor, or SQL statement.
382  // 2. An interger >=0, signifying the number of rows affected by the SQL statement.
383  // (Which may be non-obvious due to the way the ODBC drivers handle compound SQL
384  // statements, e.g. "select * from table1;select * from table2".)
385  // Using this function with "select"s SQL statements implies re-executing the SQL
386  // statemenet (suitably mangled), see comments below.
387  // Using it with "insert/update/delete" SQL statemenets before the statement is executed
388  // is meaningless (and will return "-1"). After the statement is executed it will return
389  // a (possibly different) value - see above.
390  // NOTE: Non-const to allow later overriding of this function. This is because the
391  // later implementations of "count()" imply doing tricky things with "row_count". Which
392  // therefore makes the object non-const. O.k. "row_count" could be "mutable", but in
393  // this case as we are doing quite a bit of work in the "count()" function, and I don't
394  // like it (the "count()" function), it isn't "mutable".
395  virtual inline SQLINTEGER __fastcall count() noexcept(true) {return row_count;}
396 
397  protected:
399 
400  inline void __fastcall RowsAffected();
401 
402  private:
403  bool allocated_cursor;
404 
405  // Prevent assignment.
406  inline RecordsetBase & __fastcall operator=(const RecordsetBase &) noexcept(true) {assert(false);return *this;}
407  };
408 
409  // The base read-only recordset object.
411  public:
413  inline ~RORecordsetBase(void) noexcept(true);
414  inline const tstring & __fastcall sql_string(void) const noexcept(true) {return sql_statement;}
415  inline void __fastcall execute(const tstring &sql);
416  virtual inline void __fastcall moveNext(void) = 0;
417  // Testing shows that implementing this function here gives around a 2% speed improvement,
418  // depite claiming to be "inline"d...
419  inline bool __fastcall eof() const noexcept(true) {return no_more_data;}
420  // NOTE:
421  // This function is really horrid. Why?
422  // 1. Tests have shown that SQL executed after a count suffers a performance loss of around 10%, even
423  // excluding the time it takes to do a count. (!)
424  // 2. The returned value is not guaranteed to be accurate. Why? The rows aren't locked, so if someone
425  // deletes a load of rows after doing a count, the value *will* be inaccurate. So don't use it as
426  // a critical counter to count over all of the values in an ODBC result set, otherwise you may.get
427  // unpredicatable results.
428  // 3. You can't use this count with a SQL statement that includes "order by". It *will* break and throw
429  // an exception. e.g. "select id from types order by id" is a *total* no-no.
430  inline SQLINTEGER __fastcall count();
431  // Avoid passing the map across a dll boundary, otherwise you get silly memory errors when accessing it...
432  // Hence no "inline".
433  SQLSMALLINT __fastcall GetColumnIdx(const tstring &col_name) noexcept(true);
434 
435  protected:
438 
439  // Testing shows that implementing this function here gives around a 2% speed improvement,
440  // depite claiming to be "inline"d...
441  inline SQLSMALLINT __fastcall NumColumns(void) const noexcept(true) {return num_cols;}
442 
443  private:
446 
447  // Prevent assignment.
448  inline RORecordsetBase & __fastcall operator=(const RORecordsetBase &) noexcept(true) {assert(false);return *this;}
449  };
450 
451  // A plain-old boring implementation that doesn't use anything clever like bulk row caching.
452  // It is perfectly acceptable to use the same "RORecordset" with different SQL statements
453  // passed in by "execute(...)".
455  public:
456  inline RORecordset(const tstring &cnx, const SQLUINTEGER timeout=15);
457  inline ~RORecordset(void) noexcept(true);
458  inline void __fastcall execute(const tstring &sql);
459  inline void __fastcall moveNext(void);
460 
461  template<class T> inline void __fastcall item(const SQLUSMALLINT col_num, T &data) const {
462  assert(col_num>=1);
463  if (eof()) {
464  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Unexpected EOF in the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
465  }
466  const SQLRETURN ret=SQLGetData(hstmt, col_num, SQL_C_DEFAULT, &data, sizeof(T), NULL);
467  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
468  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Failed to get the data from the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, ret);
469  }
470  }
471  template<> inline void __fastcall item(const SQLUSMALLINT col_num, tstring &data) const {
472  assert(col_num>=1);
473  if (eof()) {
474  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Unexpected EOF in the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
475  }
476  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Getting 'tstrings' from the statement handle is unsupported, use an array of 'char' or 'wchar' that is big enough instead."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
477  const SQLRETURN ret=SQLGetData(hstmt, col_num, SQL_C_CHAR, data.begin(), data.size(), NULL);
478  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
479  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Failed to get the data from the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, ret);
480  }
481  }
482 
483  template<> inline void __fastcall item(const SQLUSMALLINT col_num, long &data) const {
484  assert(col_num>=1);
485  if (eof()) {
486  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Unexpected EOF in the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
487  }
488  const SQLRETURN ret=SQLGetData(hstmt, col_num, SQL_C_SLONG, &data, sizeof(long), NULL);
489  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
490  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Failed to get the data from the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, ret);
491  }
492  }
493 
494  template<> inline void __fastcall item(const SQLUSMALLINT col_num, char &data) const {
495  assert(col_num>=1);
496  if (eof()) {
497  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Unexpected EOF in the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
498  }
499  // Use 'SQL_C_DEFAULT' so that this function will work for any 8-bit sized SQL type.
500  const SQLRETURN ret=SQLGetData(hstmt, col_num, SQL_C_CHAR, &data, sizeof(char), NULL);
501  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
502  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Failed to get the data from the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, ret);
503  }
504  }
505 
506  template<class T> inline void __fastcall item(const tstring &col_name, T &data) const {
508  }
509 
510  private:
511  void operator=(const RORecordset &)=delete;
512  };
513 
514  // This class declares an object that can retrieve row(s) of data from a data base.
515  // It doesn't merely get the individual column of a row at a time.
516  // Note that is specifically read-only, so only supports SQL "select" statements.
517  // Any SQL that has "update", "delete" or "insert" in it will cause the object to
518  // throw an error condition. Use the write record set object for when you want to modify
519  // the data base.
520  // It is perfectly acceptable to use the same "RORecordset" with different SQL statements
521  // passed in by "execute(...)".
523  public:
524  inline ROBulkRecordset(const tstring &cnx, const SQLUINTEGER row_arr_sz=1, const SQLUINTEGER timeout=15);
525  inline ~ROBulkRecordset() noexcept(true);
526  inline void __fastcall execute(const tstring &sql);
527  inline void __fastcall moveNext();
528  inline SQLUINTEGER __fastcall NumRowsInDataBlock(void) const noexcept(true) {return row_buffer_size;}
529 
530  template<class T> inline void __fastcall item(const SQLUSMALLINT col_num, T &data) const {
531  assert(col_num>=1);
532  if (eof()) {
533  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Unexpected EOF in the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
534  }
535  if (RowStatusArray.get()[row_buffer_pos]==SQL_ROW_SUCCESS || c.RowStatusArray.get()[row_buffer_pos]==SQL_ROW_SUCCESS_WITH_INFO) {
536  memcpy(&data, GetRowElement(col_num), sizeof(T));
537  }
538  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Failed to get the data from the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
539  }
540 
541  template<> inline void __fastcall item(const SQLUSMALLINT col_num, tstring &data) const {
542  assert(col_num>=1);
543  if (eof()) {
544  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Unexpected EOF in the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
545  }
546  if (RowStatusArray.get()[row_buffer_pos]==SQL_ROW_SUCCESS || RowStatusArray.get()[row_buffer_pos]==SQL_ROW_SUCCESS_WITH_INFO) {
547  data=reinterpret_cast<const TCHAR * const>(GetRowElement(col_num));
548  }
549  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Failed to get the data from the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
550  }
551 
552  template<> inline void __fastcall item(const SQLUSMALLINT col_num, long &data) const {
553  assert(col_num>=1);
554  if (eof()) {
555  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Unexpected EOF in the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
556  }
557  if (RowStatusArray.get()[row_buffer_pos]==SQL_ROW_SUCCESS || RowStatusArray.get()[row_buffer_pos]==SQL_ROW_SUCCESS_WITH_INFO) {
558  data=*reinterpret_cast<const long * const>(GetRowElement(col_num));
559  }
560  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Failed to get the data from the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
561  }
562 
563  template<> inline void __fastcall item(const SQLUSMALLINT col_num, char &data) const {
564  assert(col_num>=1);
565  if (eof()) {
566  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Unexpected EOF in the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
567  }
568  if (RowStatusArray.get()[row_buffer_pos]==SQL_ROW_SUCCESS || RowStatusArray.get()[row_buffer_pos]==SQL_ROW_SUCCESS_WITH_INFO) {
569  data=*reinterpret_cast<const char * const>(GetRowElement(col_num));
570  }
571  throw Database::ODBC::Exceptions::RORecordsetBaseErr(col_num, typeid(data), _T("Failed to get the data from the recordset."), sql_string(), &hstmt, SQL_HANDLE_STMT, SQL_ERROR);
572  }
573 
574  template<class T> inline void __fastcall item(const tstring &col_name, T &data) const {
576  }
577 
578  protected:
579  typedef unsigned char RowBufferElementType;
580 
581  inline void __fastcall AllocateDataBlock();
582 
583  private:
591 
592  inline void __fastcall RawMoveNext();
593  inline const RowBufferElementType * const __fastcall GetRowElement(const SQLUSMALLINT col_num) const noexcept(true);
594 
595  void operator=(const ROBulkRecordset &)=delete;
596  };
597 
599  public:
600  inline WOConnectionBase(const tstring &cnx, const SQLUINTEGER timeout=15);
601  inline ~WOConnectionBase(void) noexcept(true);
602  virtual inline void __fastcall commit();
603  virtual inline void __fastcall rollback();
604  inline SQLINTEGER __fastcall count();
605  inline bool __fastcall in_trans() const noexcept(true);
606 
607  template<class T> inline void __fastcall BindParam(const SQLUSMALLINT col_num, const SQLSMALLINT inp_or_out, T &data) {
608  assert(col_num>=1);
610  hstmt,
611  col_num,
612  inp_or_out,
613  SQL_C_DEFAULT,
614  0,
615  0,
616  0,
617  &data,
618  sizeof(T),
619  NULL
620  );
621  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
622  throw Database::ODBC::Exceptions::WOConnectionBase(col_num, typeid(data), _T("Failed to bind the parameter to the column on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
623  }
624  }
625 
626  template<> inline void __fastcall BindParam(const SQLUSMALLINT col_num, const SQLSMALLINT inp_or_out, long &data) {
627  assert(col_num>=1);
629  hstmt,
630  col_num,
631  inp_or_out,
632  SQL_C_SLONG,
633  SQL_INTEGER,
634  0,
635  0,
636  &data,
637  sizeof(long),
638  NULL
639  );
640  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
641  throw Database::ODBC::Exceptions::WOConnectionBaseErr(col_num, typeid(data), _T("Failed to bind the parameter to the column on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
642  }
643  }
644 
645  template<> inline void __fastcall BindParam(const SQLUSMALLINT col_num, const SQLSMALLINT inp_or_out, tstring &data) {
646  assert(col_num>=1);
648  &data,
649  SQL_LEN_DATA_AT_EXEC(data.size())
650  };
653  hstmt,
654  col_num,
655  inp_or_out,
656  SQL_C_CHAR,
657  SQL_CHAR,
658  data.size(),
659  0,
660  reinterpret_cast<SQLPOINTER>(col_num),
661  data.size(),
663  );
664  if (ret!=SQL_SUCCESS && ret!=SQL_SUCCESS_WITH_INFO) {
665  throw Database::ODBC::Exceptions::WOConnectionBaseErr(col_num, typeid(data), _T("Failed to bind the parameter to the column on the statement handle."), &hstmt, SQL_HANDLE_STMT, ret);
666  }
667  }
668 
669  protected:
673  };
676 
677  private:
678  // Avoid passing the map across a dll boundary, otherwise you get silly memory errors when accessing it...
679  // Hence no "inline".
680  void __fastcall AddExecData(const SQLUSMALLINT col_num, const str_data_at_exec_type &data);
681  // Avoid passing the map across a dll boundary, otherwise you get silly memory errors when accessing it...
682  // Hence no "inline".
684 
685  void operator=(const WOConnectionBase &)=delete;
686  };
687 
689  public:
690  inline DBTransaction(WOConnectionBase &cnx) noexcept(true) : wo_cnx(cnx) {
691  }
693  if (wo_cnx.in_trans()) {
694  wo_cnx.rollback();
695  }
696  }
697 
698  inline void __fastcall commit(void) throw (Database::ODBC::Exceptions::ODBCExceptionErr *) {
699  // If the "wo_cnx.commit()" is successful, "wo_cnx.in_transaction()" will return false.
700  wo_cnx.commit();
701  }
702 
703  private:
704  WOConnectionBase &wo_cnx;
705 
706  DBTransaction(const DBTransaction &)=delete;
707  void operator=(const DBTransaction &)=delete;
708  };
709 
711  public:
712  inline WOConnection(const tstring &cnx, const SQLUINTEGER timeout=15);
713  inline ~WOConnection() noexcept(true);
714  inline void __fastcall execute(const tstring &sql);
715 
716  private:
717  void operator=(const WOConnection &)=delete;
718  };
719 
721  public:
722  inline WOBulkConnection(const tstring &cnx, const SQLUINTEGER row_arr_sz=1, const SQLUINTEGER timeout=15);
723  inline ~WOBulkConnection() noexcept(true);
724  inline const tstring & __fastcall sql_string() const noexcept(true) {return sql_statement;}
725  inline void __fastcall execute();
726  inline void __fastcall commit();
727  inline void __fastcall rollback();
728  inline void __fastcall prepare(const tstring &sql);
729 
730  private:
731  struct sp_behaviour_type {
732  bool commit;
733  bool rollback;
734  };
737  bool prepared;
739 
740  // Ensure that the base class "execute(...)" cannot be called, as it is deprecated in this class.
741  inline void __fastcall execute(const tstring &) {}
742 
743  void operator=(const WOBulkConnection &)=delete;
744  };
745 
746 } } } }
747 
748 #pragma warning(default:4284)