Browse Source

Merge commit '286f5cf9e60df25c409df07e1033478b62ddbe55' into master

Yao Wei Tjong 姚伟忠 9 years ago
parent
commit
72549e108d

+ 50 - 0
Source/ThirdParty/nanodbc/CHANGELOG.md

@@ -1,3 +1,53 @@
+# v2.12.4
+
+Resolves a possible crash with `SQLDescribeParam()`. In Progress OpenEdge 11 driver setting the
+nullableptr argument to null causes a crash. This does not affect SQLite or MySQL drivers.
+
+Thanks to [@AndrewJD79](https://github.com/AndrewJD79) for finding and diagnosing the issue!
+
+# v2.12.3
+
+Unicode: Resolves a major issue with BLOB datatype handling for BINARY and TEXT columns.
+
+# v2.12.2
+
+Resolves a major issue with BLOB datatype handling for BINARY and TEXT columns.
+
+# v2.12.1
+
+Resolves a Travis-CI build issue.
+
+# v2.12.0
+
+Major work undertaken by Mateusz Łoskot provides new features and a host of bug fixes throughout.
+Refactoring work moves nanodbc away from platform dependent `wchar_t` in favor of `char16_t` or in the
+case of iODBC with unicode build enabled, `char32_t`. Boost.Test dropped in this version, in favor of Catch.
+
+## New Features
+
+- Converts usages of `wstring` and `wchar_t` to `u16string` and `char16_t`.
+- Enable iODBC + Unicode support with `u32string` types.
+- Add example program `table_schema.cpp`.
+- Add `dbms_name()` and `dbms_version()` methods to `connection` class.
+
+## Testing
+
+- Migrates tests from Boost.Test to Catch framework.
+- Enables unicode tests on travis-ci.
+- Syncs `Dockerfile` and `Vagrantfile`; adds quick usage docs for vagrant.
+- Switch Dockerfile over to `ubuntu:precise` (default).
+- Improve `odbc_test.cpp` to cope with DBMS variations.
+
+## Bug Fixes
+
+- Fix compiler warnings while building with VS2015.
+- Add missing optional `schema_name` parameter to usage info.
+- Workaround for VS2015 bug in `std::codecvt` for `char16_t`.
+- Fix retrieval of variable-length data in parts.
+- Fix `catalog::columns::is_nullable()` to handle valid `NULL`.
+- Fix check of total of characters required to display `SQL_DATE`.
+- Fix `SELECT` result sorting with `NULL` values involved.
+
 # v2.11.3
 
 - Fixes segmentation fault issue with unixODBC on Linux systems.

+ 48 - 22
Source/ThirdParty/nanodbc/README.md

@@ -4,9 +4,9 @@ A small C++ wrapper for the native C ODBC API. Please see the [online documentat
 
 | Version | Description |
 |:--- |:--- |
-| `release` | [![release](https://travis-ci.org/lexicalunit/nanodbc.svg?branch=release)](https://travis-ci.org/lexicalunit/nanodbc) Most recent release that's been deemed "stable". Always prefer to use this version if you can! |
-| `latest` | [![latest](https://travis-ci.org/lexicalunit/nanodbc.svg?branch=latest)](https://travis-ci.org/lexicalunit/nanodbc) Latest release; still needs testing to be deemed "stable". **[See all available releases.](https://github.com/lexicalunit/nanodbc/releases)** |
-| `master`  | [![master](https://travis-ci.org/lexicalunit/nanodbc.svg?branch=master)](https://travis-ci.org/lexicalunit/nanodbc) Contains the latest development code, not yet ready for a release. |
+| `release` | [![release](https://travis-ci.org/lexicalunit/nanodbc.svg?branch=release)](https://travis-ci.org/lexicalunit/nanodbc) Most recent published version that's deemed "stable". Review the [changelog notes](CHANGELOG.md) to see if this version is right for you. |
+| `latest` | [![latest](https://travis-ci.org/lexicalunit/nanodbc.svg?branch=latest)](https://travis-ci.org/lexicalunit/nanodbc) Latest published version; please use this version if CI tests are all passing. **[See all available versions.](https://github.com/lexicalunit/nanodbc/releases)** |
+| `master`  | [![master](https://travis-ci.org/lexicalunit/nanodbc.svg?branch=master)](https://travis-ci.org/lexicalunit/nanodbc) Contains the latest development code, not yet ready for a published version. |
 | `v2.x.x`  | Targets C++14+. All future development will build upon this version. |
 | `v1.x.x`  | Supports C++03 and optionally C++11. *There is no longer any support for this version.* |
 
@@ -14,21 +14,23 @@ A small C++ wrapper for the native C ODBC API. Please see the [online documentat
 
 Nanodbc is intentionally small enough that you can drag and drop the header and implementation files into your project and run with it. For those that want it, I have also provided [CMake](http://www.cmake.org/) files which build a library object, or build and run the included unit tests. The CMake files will also support out of source builds.
 
-Building unit tests requires [Boost.Test](http://www.boost.org/doc/libs/release/libs/test/). To build the tests you will also need to have either unixODBC or iODBC installed and discoverable by CMake. This is easy on OS X where you can use [Homebrew](http://brew.sh/) to install unixODBC with `brew install unixodbc`, or use the system provided iODBC if you have OS X 10.9 or earlier. Also note that you can install Boost via Homebrew as well, which is super convenient!
+Unit tests use the [Catch](https://github.com/philsquared/Catch) test framework, and CMake will automatically fetch the latest version of Catch for you at build time. To build the tests you will also need to have either unixODBC or iODBC installed and discoverable by CMake. This is easy on OS X where you can use [Homebrew](http://brew.sh/) to install unixODBC with `brew install unixodbc`, or use the system provided iODBC if you have OS X 10.9 or earlier.
 
 The unit tests attempt to connect to a [SQLite](https://www.sqlite.org/) database, so you will have to have that and a SQLite ODBC driver installed. At the time of this writing, there happens to be a nice [SQLite ODBC driver](http://www.ch-werner.de/sqliteodbc/) available from Christian Werner's website, also available via Homebrew as `sqliteobdc`! The tests expect to find a data source named `sqlite` on *nix systems and `SQLite3 ODBC Driver` on Windows systems. For example, your `odbcinst.ini` file on OS X must have a section like the following.
 
 ```
 [sqlite]
 Description             = SQLite3 ODBC Driver
-Driver                  = /usr/lib/libsqlite3odbc-0.93.dylib
 Setup                   = /usr/lib/libsqlite3odbc-0.93.dylib
+Driver                  = /usr/lib/libsqlite3odbc-0.93.dylib
 Threading               = 2
 ```
 
 ## Example Build Process
 
-It's most convenient to create a build directory for an out of source build, but this isn't required. After you've used cmake to generate your Makefiles, `make nanodbc` will build your shared object. `make check` will build and run the unit tests. If the unit tests fail, please don't hesitate to report it by creating an issue [with your detailed test log](http://stackoverflow.com/questions/5709914/using-cmake-how-do-i-get-verbose-output-from-ctest)! You can also install nanodbc to your system using `make install`.
+It's most convenient to create a build directory for an out of source build, but this isn't required. After you've used cmake to generate your Makefiles, `make nanodbc` will build your shared object. `make check` will build and run the unit tests. You can also install nanodbc to your system using `make install`.
+
+If the unit tests fail, please don't hesitate to [**report it**](https://github.com/lexicalunit/nanodbc/issues/new) by creating an issue with your detailed test log (prepend your `make` command with `env CTEST_OUTPUT_ON_FAILURE=1 ` to enable verbose output please).
 
 ```shell
 cd path/to/nanodbc/repository
@@ -40,6 +42,7 @@ make nanodbc # creates shared library
 make tests # builds the unit tests
 make test # runs the unit tests
 make check # builds and then runs unit tests
+make examples # builds all the example programs
 make install # installs nanodbc.h and shared library
 ```
 
@@ -49,15 +52,21 @@ The following build options are available via CMake. If you are not using CMake
 
 | CMake Option                     | Possible Values  | Default       | Details |
 | ------------------------------------- | --------------------- | ------------- | ------- |
-| `D NANODBC_USE_UNICODE=...`          | `OFF` or `ON`         | `OFF`         | Enables full unicode support. `nanodbc::string` becomes `std::wstring`. |
-| `D NANODBC_HANDLE_NODATA_BUG=...`    | `OFF` or `ON`         | `OFF`         | Provided to resolve issue [#33](https://github.com/lexicalunit/nanodbc/issues/33), details [in this commit](https://github.com/lexicalunit/nanodbc/commit/918d73cdf12d5903098381344eecde8e7d5d896e). |
-| `D NANODBC_USE_BOOST_CONVERT=...`    | `OFF` or `ON`         | `OFF`         | Provided as workaround to issue [#44](https://github.com/lexicalunit/nanodbc/issues/44). |
-| `D NANODBC_STATIC=...`               | `OFF` or `ON`         | `OFF`         | Enables building a static library, otherwise the build process produces a shared library. |
-| `D NANODBC_INSTALL=...`              | `OFF` or `ON`         | `ON`          | Enables install target. |
-| `D NANODBC_EXAMPLES=...`             | `OFF` or `ON`         | `ON`          | Enables building of examples. |
-| `D NANODBC_TEST=...`                 | `OFF` or `ON`         | `ON`          | Enables tests target (alias `check`). |
+| `-D NANODBC_USE_UNICODE=...`          | `OFF` or `ON`         | `OFF`         | Enables full unicode support. `nanodbc::string` becomes `std::u16string` or `std::u32string`. |
+| `-D NANODBC_HANDLE_NODATA_BUG=...`    | `OFF` or `ON`         | `OFF`         | Provided to resolve issue [#33](https://github.com/lexicalunit/nanodbc/issues/33), details [in this commit](https://github.com/lexicalunit/nanodbc/commit/918d73cdf12d5903098381344eecde8e7d5d896e). |
+| `-D NANODBC_USE_BOOST_CONVERT=...`    | `OFF` or `ON`         | `OFF`         | Provided as workaround to issue [#44](https://github.com/lexicalunit/nanodbc/issues/44). |
+| `-D NANODBC_STATIC=...`               | `OFF` or `ON`         | `OFF`         | Enables building a static library, otherwise the build process produces a shared library. |
+| `-D NANODBC_INSTALL=...`              | `OFF` or `ON`         | `ON`          | Enables install target. |
+| `-D NANODBC_EXAMPLES=...`             | `OFF` or `ON`         | `ON`          | Enables building of examples. |
+| `-D NANODBC_TEST=...`                 | `OFF` or `ON`         | `ON`          | Enables tests target (alias `check`). |
 | `-D NANODBC_ENABLE_LIBCXX=...`        | `OFF` or `ON`         | `ON`          | Enables usage of libc++ if found on the system. |
-| `‑D NANODBC_ODBC_VERSION=...`         | `SQL_OV_ODBC3[...]`   | See Details   | **[Optional]** Sets the ODBC version macro for nanodbc to use. Default is `SQL_OV_ODBC3_80` if available, otherwise `SQL_OV_ODBC3`. |
+| `-D NANODBC_ODBC_VERSION=...`         | `SQL_OV_ODBC3[...]`   | See Details   | **[Optional]** Sets the ODBC version macro for nanodbc to use. Default is `SQL_OV_ODBC3_80` if available, otherwise `SQL_OV_ODBC3`. |
+
+## Note About iODBC
+
+Under Windows `sizeof(wchar_t) == sizeof(SQLWCHAR) == 2`, yet on Unix systems `sizeof(wchar_t) == 4`. On unixODBC, `sizeof(SQLWCHAR) == 2` while on iODBC, `sizeof(SQLWCHAR) == sizeof(wchar_t) == 4`. This leads to incompatible ABIs between applications and drivers. If building against iODBC and the build option `NANODBC_USE_UNICODE` is `ON`, then `nanodbc::string_type` will be `std::u32string`. In **ALL** other cases it will be `std::u16string`.
+
+Continuous integration tests run on [Travis-CI](https://travis-ci.org/). The build platform does not make available a Unicode-enabled iODBC driver. As such there is no guarantee that tests will pass in entirety on a system using iODBC. My recommendation is to use unixODBC. If you must use iODBC, consider _disabling_ unicode mode to avoid `wchar_t` issues.
 
 ---
 
@@ -80,7 +89,7 @@ To do this manually instead, use the following steps — for example a minor
 
 ### Release Process
 
-Release nanodbc with the `scripts/release.sh` script. All this script does is push out the `master` branch to the `release` branch, indicating that a new "stable" version of nanodbc exists. To do so manually, execute `git push -f origin master:release`. **Only do this for releases that have been deemed "stable" based on suitable criteria.**
+Release nanodbc with the `scripts/release.sh` script. All this script does is push out the `master` branch to the `release` branch, indicating that a new "stable" published version of nanodbc exists. To do so manually, execute `git push -f origin master:release`. **Caution: Do this for versions deemed "stable" based on suitable criteria.**
 
 ## Source Level Documentation
 
@@ -92,23 +101,38 @@ Source level documentation provided via [GitHub's gh-pages](https://help.github.
 4. `make commit` Adds and commits any updated documentation.
 5. `git push origin gh-pages` Deploys the changes to github.
 
-Building documentation and gh-pages requires the use of [Doxygen](www.doxygen.org) and [jekyll](https://jekyllrb.com/). See the `Makefile` on the `gh-pages` branch of nanodbc for more details.
+Building documentation and gh-pages requires the use of [Doxygen](www.doxygen.org) and [jekyll](https://jekyllrb.com/). See the [`Makefile` on the `gh-pages` branch](https://github.com/lexicalunit/nanodbc/blob/gh-pages/Makefile) for more details.
 
-## Testing Environments
+## Quick Setup for Testing or Development Environments
 
-To get up and running with nanodbc as quickly as possible consider using the provided Dockerfile or Vagrantfile. For example, to spin up a [docker](https://www.docker.com/) container suitable for testing and development of nanodbc:
+To get up and running with nanodbc as fast as possible consider using the provided [Dockerfile](Dockerfile) or [Vagrantfile](Vagrantfile). For example, to spin up a [docker](https://www.docker.com/) container suitable for testing and development of nanodbc:
 
 ```shell
 $ cd /path/to/nanodbc
 $ docker build -t nanodbc .
 $ docker run -v "$(pwd)":"/opt/$(basename $(pwd))" -it nanodbc /bin/bash
-root@hash:/# mkdir -p /opt/nanodbc/docker_build && cd /opt/nanodbc/docker_build
-root@hash:/opt/nanodbc/docker_build# cmake -DNANODBC_USE_BOOST_CONVERT=YES ..
-root@hash:/opt/nanodbc/docker_build# make nanodbc
+root@hash:/# mkdir -p /opt/nanodbc/build && cd /opt/nanodbc/build
+root@hash:/opt/nanodbc/build# cmake ..
+root@hash:/opt/nanodbc/build# make nanodbc
+```
+
+Or, to build and ssh into a [vagrant](https://www.vagrantup.com/) VM (using VirtualBox for example) use:
+
+```shell
+$ cd /path/to/nanodbc
+$ vagrant up
+$ vagrant ssh
+vagrant@vagrant-ubuntu-precise-64:~$ git clone https://github.com/lexicalunit/nanodbc.git
+vagrant@vagrant-ubuntu-precise-64:~$ mkdir -p nanodbc/build && cd nanodbc/build
+vagrant@vagrant-ubuntu-precise-64:~$ CXX=g++-5 cmake ..
+vagrant@vagrant-ubuntu-precise-64:~$ make nanodbc
 ```
 
 ## Future work
 
+### Good to Have / Want Someday
+
+- Refactor unit tests to follow BDD pattern.
 - Update codebase to use more C++14 idioms and patterns.
 - Write more tests with the goal to have much higher code coverage.
 - More tests for a large variety of drivers. Include performance tests.
@@ -116,4 +140,6 @@ root@hash:/opt/nanodbc/docker_build# make nanodbc
 - Improve documentation: The main website and API docs should be more responsive.
 - Provide more examples in documentation, more details, and point out any gotchas.
 - Refactor code to remove the need for the `NANODBC_HANDLE_NODATA_BUG` option.
-- Fill out the Contributing section of this readme with more helpful information. Maybe a getting started section?
+- Versioned generated source level API documentation for `release` and `latest`. For each major and minor published versions too?
+- Windows CI tests; use [Appveyor](https://www.appveyor.com/)? Alternatives?
+- Add "HOWTO Build" documentation for Windows, OS X, and Linux.

+ 1 - 1
Source/ThirdParty/nanodbc/VERSION

@@ -1 +1 @@
-2.11.3
+2.12.4

+ 229 - 143
Source/ThirdParty/nanodbc/src/nanodbc.cpp

@@ -13,7 +13,6 @@
 #include <ctime>
 #include <iomanip>
 #include <map>
-#include <sstream>
 
 #ifndef __clang__
     #include <cstdint>
@@ -21,8 +20,8 @@
 
 // User may redefine NANODBC_ASSERT macro in nanodbc.h
 #ifndef NANODBC_ASSERT
-#include <cassert>
-#define NANODBC_ASSERT(expr) assert(expr)
+    #include <cassert>
+    #define NANODBC_ASSERT(expr) assert(expr)
 #endif
 
 #ifdef NANODBC_USE_BOOST_CONVERT
@@ -73,47 +72,36 @@
 //  "Y88888P"  888  888 888  "Y8888P "Y88P"   "Y88888  "Y8888
 // MARK: Unicode -
 
-#if defined(_MSC_VER)
-    #ifdef NANODBC_USE_UNICODE
-        #define NANODBC_TEXT(s) L ## s
-        #define NANODBC_SNPRINTF std::swprintf
-        #define NANODBC_STRFTIME std::wcsftime
-        #define NANODBC_STRLEN std::wcslen
-        #define NANADBC_STRNCMP std::wcsncmp
-        #define NANODBC_UNICODE(f) f ## W
-        #define NANODBC_SQLCHAR SQLWCHAR
+#ifdef NANODBC_USE_UNICODE
+    #ifdef NANODBC_USE_IODBC_WIDE_STRINGS
+        #define NANODBC_TEXT(s) U ## s
     #else
-        #define NANODBC_TEXT(s) s
-        #define NANODBC_SNPRINTF(buffer, count, format, ...) _snprintf_s(buffer, count, _TRUNCATE, format, __VA_ARGS__)
-        #define NANODBC_STRFTIME std::strftime
-        #define NANODBC_STRLEN std::strlen
-        #define NANADBC_STRNCMP std::strncmp
-        #define NANODBC_UNICODE(f) f
-        #define NANODBC_SQLCHAR SQLCHAR
+        #define NANODBC_TEXT(s) u ## s
+    #endif
+    #define NANODBC_FUNC(f) f ## W
+    #define NANODBC_SQLCHAR SQLWCHAR
+#else
+    #define NANODBC_TEXT(s) s
+    #define NANODBC_FUNC(f) f
+    #define NANODBC_SQLCHAR SQLCHAR
+#endif
+
+#ifdef NANODBC_USE_IODBC_WIDE_STRINGS
+    typedef std::u32string wide_string_type;
+    #define NANODBC_CODECVT_TYPE std::codecvt_utf8
+#else
+    typedef std::u16string wide_string_type;
+    #define NANODBC_CODECVT_TYPE std::codecvt_utf8_utf16
+#endif
+typedef wide_string_type::value_type wide_char_t;
 
+#if defined(_MSC_VER)
+    #ifndef NANODBC_USE_UNICODE
         // Disable unicode in sqlucode.h on Windows when NANODBC_USE_UNICODE
         // is not defined. This is required because unicode is enabled by
         // default on many Windows systems.
         #define SQL_NOUNICODEMAP
     #endif
-#else
-    #ifdef NANODBC_USE_UNICODE
-        #define NANODBC_TEXT(s) L ## s
-        #define NANODBC_SNPRINTF std::swprintf
-        #define NANODBC_STRFTIME std::wcsftime
-        #define NANODBC_STRLEN std::wcslen
-        #define NANADBC_STRNCMP std::wcsncmp
-        #define NANODBC_UNICODE(f) f ## W
-        #define NANODBC_SQLCHAR SQLWCHAR
-    #else
-        #define NANODBC_TEXT(s) s
-        #define NANODBC_SNPRINTF std::snprintf
-        #define NANODBC_STRFTIME std::strftime
-        #define NANODBC_STRLEN std::strlen
-        #define NANADBC_STRNCMP std::strncmp
-        #define NANODBC_UNICODE(f) f
-        #define NANODBC_SQLCHAR SQLCHAR
-    #endif
 #endif
 
 //  .d88888b.  8888888b.  888888b.    .d8888b.       888b     d888
@@ -214,28 +202,41 @@ namespace
         return i;
     }
 
-    inline void convert(const std::wstring& in, std::string& out)
+    inline void convert(const wide_string_type& in, std::string& out)
     {
         #ifdef NANODBC_USE_BOOST_CONVERT
             using boost::locale::conv::utf_to_utf;
             out = utf_to_utf<char>(in.c_str(), in.c_str() + in.size());
         #else
-            out = std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(in);
+            #if defined(_MSC_VER) && (_MSC_VER == 1900)
+                // Workaround for confirmed bug in VS2015.
+                // See: https://social.msdn.microsoft.com/Forums/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error
+                auto p = reinterpret_cast<wide_char_t const*>(in.data());
+                out = std::wstring_convert<NANODBC_CODECVT_TYPE<wide_char_t>, wide_char_t>().to_bytes(p, p + in.size());
+            #else
+                out = std::wstring_convert<NANODBC_CODECVT_TYPE<wide_char_t>, wide_char_t>().to_bytes(in);
+            #endif
         #endif
     }
 
     #ifdef NANODBC_USE_UNICODE
-        inline void convert(const std::string& in, std::wstring& out)
+        inline void convert(const std::string& in, wide_string_type& out)
         {
             #ifdef NANODBC_USE_BOOST_CONVERT
                 using boost::locale::conv::utf_to_utf;
-                out = utf_to_utf<wchar_t>(in.c_str(), in.c_str() + in.size());
+                out = utf_to_utf<wide_char_t>(in.c_str(), in.c_str() + in.size());
+            #elif defined(_MSC_VER) && (_MSC_VER == 1900)
+                // Workaround for confirmed bug in VS2015.
+                // See: https://social.msdn.microsoft.com/Forums/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error
+                auto s = std::wstring_convert<NANODBC_CODECVT_TYPE<wide_char_t>, wide_char_t>().from_bytes(in);
+                auto p = reinterpret_cast<wide_char_t const*>(s.data());
+                out.assign(p, p + s.size());
             #else
-                out = std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(in);
+                out = std::wstring_convert<NANODBC_CODECVT_TYPE<wide_char_t>, wide_char_t>().from_bytes(in);
             #endif
         }
 
-        inline void convert(const std::wstring& in, std::wstring & out)
+        inline void convert(const wide_string_type& in, wide_string_type& out)
         {
             out = in;
         }
@@ -268,7 +269,7 @@ namespace
         do
         {
             NANODBC_CALL_RC(
-                NANODBC_UNICODE(SQLGetDiagRec)
+                NANODBC_FUNC(SQLGetDiagRec)
                 , rc
                 , handle_type
                 , handle
@@ -286,7 +287,7 @@ namespace
                 break;
 
             NANODBC_CALL_RC(
-                NANODBC_UNICODE(SQLGetDiagRec)
+                NANODBC_FUNC(SQLGetDiagRec)
                 , rc
                 , handle_type
                 , handle
@@ -786,7 +787,7 @@ public:
         #endif
 
         NANODBC_CALL_RC(
-            NANODBC_UNICODE(SQLConnect)
+            NANODBC_FUNC(SQLConnect)
             , rc
             , conn_
             , (NANODBC_SQLCHAR*)dsn.c_str(), SQL_NTS
@@ -838,7 +839,7 @@ public:
         NANODBC_SQLCHAR dsn[1024];
         SQLSMALLINT dsn_size = 0;
         NANODBC_CALL_RC(
-            NANODBC_UNICODE(SQLDriverConnect)
+            NANODBC_FUNC(SQLDriverConnect)
             , rc
             , conn_
             , 0
@@ -888,13 +889,49 @@ public:
         return env_;
     }
 
+    string_type dbms_name() const
+    {
+        NANODBC_SQLCHAR name[255] = { 0 };
+        SQLSMALLINT length(0);
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLGetInfo)
+            , rc
+            , conn_
+            , SQL_DBMS_NAME
+            , name
+            , sizeof(name) / sizeof(NANODBC_SQLCHAR)
+            , &length);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+        return string_type(&name[0], &name[strarrlen(name)]);
+    }
+
+    string_type dbms_version() const
+    {
+        NANODBC_SQLCHAR version[255] = { 0 };
+        SQLSMALLINT length(0);
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLGetInfo)
+            , rc
+            , conn_
+            , SQL_DBMS_VER
+            , version
+            , sizeof(version) / sizeof(NANODBC_SQLCHAR)
+            , &length);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+        return string_type(&version[0], &version[strarrlen(version)]);
+    }
+
     string_type driver_name() const
     {
         NANODBC_SQLCHAR name[1024];
         SQLSMALLINT length;
         RETCODE rc;
         NANODBC_CALL_RC(
-            NANODBC_UNICODE(SQLGetInfo)
+            NANODBC_FUNC(SQLGetInfo)
             , rc
             , conn_
             , SQL_DRIVER_NAME
@@ -916,7 +953,7 @@ public:
         SQLSMALLINT length(0);
         RETCODE rc;
         NANODBC_CALL_RC(
-            NANODBC_UNICODE(SQLGetInfo)
+            NANODBC_FUNC(SQLGetInfo)
             , rc
             , conn_
             , SQL_DATABASE_NAME
@@ -934,7 +971,7 @@ public:
         SQLINTEGER length(0);
         RETCODE rc;
         NANODBC_CALL_RC(
-            NANODBC_UNICODE(SQLGetConnectAttr)
+            NANODBC_FUNC(SQLGetConnectAttr)
             , rc
             , conn_
             , SQL_ATTR_CURRENT_CATALOG
@@ -1247,7 +1284,7 @@ public:
 
         RETCODE rc;
         NANODBC_CALL_RC(
-            NANODBC_UNICODE(SQLPrepare)
+            NANODBC_FUNC(SQLPrepare)
             , rc
             , stmt_
             , (NANODBC_SQLCHAR*)query.c_str()
@@ -1392,7 +1429,7 @@ public:
         this->timeout(timeout);
 
         NANODBC_CALL_RC(
-            NANODBC_UNICODE(SQLExecDirect)
+            NANODBC_FUNC(SQLExecDirect)
             , rc
             , stmt_
             , (NANODBC_SQLCHAR*)query.c_str()
@@ -1471,7 +1508,7 @@ public:
 
         RETCODE rc;
         NANODBC_CALL_RC(
-            NANODBC_UNICODE(SQLProcedureColumns)
+            NANODBC_FUNC(SQLProcedureColumns)
             , rc
             , stmt_
             , (NANODBC_SQLCHAR*)(catalog.empty() ? NULL : catalog.c_str())
@@ -1530,6 +1567,7 @@ public:
     {
         RETCODE rc;
         SQLSMALLINT data_type;
+        SQLSMALLINT nullable;
         SQLULEN parameter_size;
         NANODBC_CALL_RC(
             SQLDescribeParam
@@ -1539,7 +1577,7 @@ public:
             , &data_type
             , &parameter_size
             , 0
-            , 0);
+            , &nullable);
         if(!success(rc))
             NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
         NANODBC_ASSERT(parameter_size <= static_cast<SQLULEN>(std::numeric_limits<unsigned long>::max()));
@@ -1579,6 +1617,7 @@ public:
         , SQLSMALLINT& scale)
     {
         RETCODE rc;
+        SQLSMALLINT nullable;
         NANODBC_CALL_RC(
             SQLDescribeParam
             , rc
@@ -1587,7 +1626,7 @@ public:
             , &data_type
             , &parameter_size
             , &scale
-            , 0);
+            , &nullable);
         if(!success(rc))
             NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
 
@@ -1799,12 +1838,23 @@ void statement::statement_impl::bind_strings(
 
     if(null_sentry)
     {
-        const string_type rhs(null_sentry);
         for(std::size_t i = 0; i < elements; ++i)
         {
-            const string_type lhs(values + i * length, values + (i + 1) * length);
-            if(NANADBC_STRNCMP(lhs.c_str(), rhs.c_str(), length))
-                bind_len_or_null_[param][i] = parameter_size;
+            const string_type s_lhs(values + i * length, values + (i + 1) * length);
+            const string_type s_rhs(null_sentry);
+            #if NANODBC_USE_UNICODE
+                std::string narrow_lhs;
+                narrow_lhs.reserve(s_lhs.size());
+                convert(s_lhs, narrow_lhs);
+                std::string narrow_rhs;
+                narrow_rhs.reserve(s_rhs.size());
+                convert(s_rhs, narrow_lhs);
+                if(std::strncmp(narrow_lhs.c_str(), narrow_rhs.c_str(), length))
+                    bind_len_or_null_[param][i] = parameter_size;
+            #else
+                if(std::strncmp(s_lhs.c_str(), s_rhs.c_str(), length))
+                    bind_len_or_null_[param][i] = parameter_size;
+            #endif
         }
     }
     else if(nulls)
@@ -2255,7 +2305,7 @@ private:
         for(SQLSMALLINT i = 0; i < n_columns; ++i)
         {
             NANODBC_CALL_RC(
-                NANODBC_UNICODE(SQLDescribeCol)
+                NANODBC_FUNC(SQLDescribeCol)
                 , rc
                 , stmt_.native_statement_handle()
                 , i + 1
@@ -2353,9 +2403,6 @@ private:
                     break;
                 case SQL_BINARY:
                 case SQL_VARBINARY:
-                    col.ctype_ = SQL_C_BINARY;
-                    col.clen_ = col.sqlsize_ + sizeof(NANODBC_SQLCHAR);
-                    break;
                 case SQL_LONGVARBINARY:
                     col.ctype_ = SQL_C_BINARY;
                     col.blob_ = true;
@@ -2422,11 +2469,11 @@ inline void result::result_impl::get_ref_impl<date>(short column, date& result)
     switch(col.ctype_)
     {
         case SQL_C_DATE:
-            result = *((date*)(col.pdata_ + rowset_position_ * col.clen_));
+            result = *reinterpret_cast<date*>(col.pdata_ + rowset_position_ * col.clen_);
             return;
         case SQL_C_TIMESTAMP:
         {
-            timestamp stamp = *( (timestamp*)( col.pdata_ + rowset_position_ * col.clen_ ) );
+            timestamp stamp = *reinterpret_cast<timestamp*>(col.pdata_ + rowset_position_ * col.clen_ );
             date d = { stamp.year, stamp.month, stamp.day };
             result = d;
             return;
@@ -2443,13 +2490,13 @@ inline void result::result_impl::get_ref_impl<timestamp>(short column, timestamp
     {
         case SQL_C_DATE:
         {
-            date d = *((date*)(col.pdata_ + rowset_position_ * col.clen_));
+            date d = *reinterpret_cast<date*>(col.pdata_ + rowset_position_ * col.clen_);
             timestamp stamp = { d.year, d.month, d.day, 0, 0, 0, 0 };
             result = stamp;
             return;
         }
         case SQL_C_TIMESTAMP:
-            result = *((timestamp*)(col.pdata_ + rowset_position_ * col.clen_));
+            result = *reinterpret_cast<timestamp*>(col.pdata_ + rowset_position_ * col.clen_);
             return;
     }
     throw type_incompatible_error();
@@ -2464,33 +2511,37 @@ inline void result::result_impl::get_ref_impl<string_type>(short column, string_
     switch(col.ctype_)
     {
         case SQL_C_CHAR:
+        case SQL_C_BINARY:
         {
             if(col.blob_)
             {
-                // Input is always std::string, while output may be std::string or std::wstring
-                std::stringstream ss;
-                char buff[1024] = {0};
-                std::size_t buff_size = sizeof(buff);
+                // Input is always std::string, while output may be std::string or wide_string_type
+                std::string out;
                 SQLLEN ValueLenOrInd;
                 SQLRETURN rc;
                 void* handle = native_statement_handle();
                 do
                 {
+                    char buffer[1024] = {0};
+                    const std::size_t buffer_size = sizeof(buffer);
                     NANODBC_CALL_RC(
                         SQLGetData
                         , rc
                         , handle            // StatementHandle
                         , column + 1        // Col_or_Param_Num
-                        , SQL_C_CHAR        // TargetType
-                        , buff              // TargetValuePtr
-                        , buff_size         // BufferLength
+                        , col.ctype_        // TargetType
+                        , buffer            // TargetValuePtr
+                        , buffer_size - 1   // BufferLength
                         , &ValueLenOrInd);  // StrLen_or_IndPtr
                     if(ValueLenOrInd > 0)
-                        ss << buff;
+                        out.append(buffer);
                     else if(ValueLenOrInd == SQL_NULL_DATA)
                         *col.cbdata_ = (SQLINTEGER) SQL_NULL_DATA;
-                } while(rc > 0);
-                convert(ss.str(), result);
+                    // Sequence of successful calls is:
+                    // SQL_NO_DATA or SQL_SUCCESS_WITH_INFO followed by SQL_SUCCESS.
+                } while(rc == SQL_SUCCESS_WITH_INFO);
+                if (rc == SQL_SUCCESS || rc == SQL_NO_DATA)
+                    convert(out, result);
             }
             else
             {
@@ -2505,48 +2556,48 @@ inline void result::result_impl::get_ref_impl<string_type>(short column, string_
         {
             if(col.blob_)
             {
-                // Input is always std::wstring, output might be std::string or std::wstring.
+                // Input is always wide_string_type, output might be std::string or wide_string_type.
                 // Use a string builder to build the output string.
-                std::wstringstream ss;
-                wchar_t buffer[512] = {0};
-                std::size_t buffer_size = sizeof(buffer);
+                wide_string_type out;
                 SQLLEN ValueLenOrInd;
                 SQLRETURN rc;
                 void* handle = native_statement_handle();
                 do
                 {
+                    wide_char_t buffer[512] = {0};
+                    const std::size_t buffer_size = sizeof(buffer);
                     NANODBC_CALL_RC(
                         SQLGetData
                         , rc
                         , handle            // StatementHandle
                         , column + 1        // Col_or_Param_Num
-                        , SQL_C_WCHAR       // TargetType
+                        , col.ctype_        // TargetType
                         , buffer            // TargetValuePtr
-                        , buffer_size       // BufferLength
+                        , buffer_size - 1   // BufferLength
                         , &ValueLenOrInd);  // StrLen_or_IndPtr
                     if(ValueLenOrInd > 0)
-                        ss << buffer;
+                        out.append(buffer);
                     else if(ValueLenOrInd == SQL_NULL_DATA)
                         *col.cbdata_ = (SQLINTEGER) SQL_NULL_DATA;
-                } while(rc > 0);
-                convert(ss.str(), result);
+                    // Sequence of successful calls is:
+                    // SQL_NO_DATA or SQL_SUCCESS_WITH_INFO followed by SQL_SUCCESS.
+                } while(rc == SQL_SUCCESS_WITH_INFO);
+                if (rc == SQL_SUCCESS || rc == SQL_NO_DATA)
+                    convert(out, result);
             }
             else
             {
                 // Type is unicode in the database, convert if necessary
                 const SQLWCHAR* s = reinterpret_cast<SQLWCHAR*>(col.pdata_ + rowset_position_ * col.clen_);
                 const string_type::size_type str_size = *col.cbdata_ / sizeof(SQLWCHAR);
-                std::wstring temp(s, s + str_size);
+                wide_string_type temp(s, s + str_size);
                 convert(temp, result);
             }
             return;
         }
 
         case SQL_C_GUID:
-        case SQL_C_BINARY:
         {
-            if(col.blob_)
-                throw std::runtime_error("blob not implemented yet");
             const char* s = col.pdata_ + rowset_position_ * col.clen_;
             result.assign(s, s + column_size);
             return;
@@ -2554,81 +2605,106 @@ inline void result::result_impl::get_ref_impl<string_type>(short column, string_
 
         case SQL_C_LONG:
         {
-           result.resize(column_size);
-           if(NANODBC_SNPRINTF(
-                    const_cast<string_type::value_type*>(result.data())
-                    , column_size
-                    , NANODBC_TEXT("%d")
-                    , *(int32_t*)(col.pdata_ + rowset_position_ * col.clen_)) == -1)
-               throw type_incompatible_error();
-           result.resize(NANODBC_STRLEN(result.c_str()));
-           return;
+            std::string buffer;
+            buffer.reserve(column_size + 1); // ensure terminating null
+            buffer.resize(buffer.capacity());
+            using std::fill;
+            fill(buffer.begin(), buffer.end(), '\0');
+            const wide_char_t data = *reinterpret_cast<wide_char_t*>(col.pdata_ + rowset_position_ * col.clen_);
+            const int bytes = std::snprintf(const_cast<char*>(buffer.data()), column_size, "%d", data);
+            if(bytes == -1)
+                throw type_incompatible_error();
+            else if((SQLULEN)bytes < column_size)
+                buffer.resize(bytes);
+            buffer.resize(std::strlen(buffer.data())); // drop any trailing nulls
+            result.reserve(buffer.size() * sizeof(string_type::value_type));
+            convert(buffer, result);
+            return;
         }
 
         case SQL_C_SBIGINT:
         {
             using namespace std; // in case intmax_t is in namespace std
-            result.resize(column_size);
-            if(NANODBC_SNPRINTF(
-                    const_cast<string_type::value_type*>(result.data())
-                    , column_size
-                    , NANODBC_TEXT("%jd")
-                    , (intmax_t) *(int64_t*)(col.pdata_ + rowset_position_ * col.clen_)) == -1)
+            std::string buffer;
+            buffer.reserve(column_size + 1); // ensure terminating null
+            buffer.resize(buffer.capacity());
+            using std::fill;
+            fill(buffer.begin(), buffer.end(), '\0');
+            const intmax_t data = (intmax_t)*reinterpret_cast<int64_t*>(col.pdata_ + rowset_position_ * col.clen_);
+            const int bytes = std::snprintf(const_cast<char*>(buffer.data()), column_size, "%jd", data);
+            if(bytes == -1)
                 throw type_incompatible_error();
-            result.resize(NANODBC_STRLEN(result.c_str()));
+            else if((SQLULEN)bytes < column_size)
+                buffer.resize(bytes);
+            buffer.resize(std::strlen(buffer.data())); // drop any trailing nulls
+            result.reserve(buffer.size() * sizeof(string_type::value_type));
+            convert(buffer, result);
             return;
         }
 
         case SQL_C_FLOAT:
         {
-            result.resize(column_size);
-            if(NANODBC_SNPRINTF(
-                    const_cast<string_type::value_type*>(result.data())
-                    , column_size
-                    , NANODBC_TEXT("%f")
-                    , *(float*)(col.pdata_ + rowset_position_ * col.clen_)) == -1)
+            std::string buffer;
+            buffer.reserve(column_size + 1); // ensure terminating null
+            buffer.resize(buffer.capacity());
+            using std::fill;
+            fill(buffer.begin(), buffer.end(), '\0');
+            const float data = *reinterpret_cast<float*>(col.pdata_ + rowset_position_ * col.clen_);
+            const int bytes = std::snprintf(const_cast<char*>(buffer.data()), column_size, "%f", data);
+            if(bytes == -1)
                 throw type_incompatible_error();
-            result.resize(NANODBC_STRLEN(result.c_str()));
+            else if((SQLULEN)bytes < column_size)
+                buffer.resize(bytes);
+            buffer.resize(std::strlen(buffer.data())); // drop any trailing nulls
+            result.reserve(buffer.size() * sizeof(string_type::value_type));
+            convert(buffer, result);
             return;
         }
 
         case SQL_C_DOUBLE:
         {
-            result.resize(column_size + 2);     // account for decimal mark and sign
-            if(NANODBC_SNPRINTF(
-                const_cast<string_type::value_type*>(result.data())
-                , column_size + 2
-                , NANODBC_TEXT("%.*lf")         // restrict the number of digits
+            std::string buffer;
+            const SQLULEN width = column_size + 2; // account for decimal mark and sign
+            buffer.reserve(width + 1); // ensure terminating null
+            buffer.resize(buffer.capacity());
+            using std::fill;
+            fill(buffer.begin(), buffer.end(), '\0');
+            const double data = *reinterpret_cast<double*>(col.pdata_ + rowset_position_ * col.clen_);
+            const int bytes = std::snprintf(
+                const_cast<char*>(buffer.data())
+                , width
+                , "%.*lf"                       // restrict the number of digits
                 , col.scale_                    // number of digits after the decimal point
-                , *(double*)(col.pdata_ + rowset_position_ * col.clen_)) == -1)
-            throw type_incompatible_error();
-            result.resize(NANODBC_STRLEN(result.c_str()));
+                , data);
+            if(bytes == -1)
+                throw type_incompatible_error();
+            else if((SQLULEN)bytes < column_size)
+                buffer.resize(bytes);
+            buffer.resize(std::strlen(buffer.data())); // drop any trailing nulls
+            result.reserve(buffer.size() * sizeof(string_type::value_type));
+            convert(buffer, result);
             return;
         }
 
         case SQL_C_DATE:
         {
-            date d = *((date*)(col.pdata_ + rowset_position_ * col.clen_));
+            const date d = *reinterpret_cast<date*>(col.pdata_ + rowset_position_ * col.clen_);
             std::tm st = { 0 };
             st.tm_year = d.year - 1900;
             st.tm_mon = d.month - 1;
             st.tm_mday = d.day;
             char* old_lc_time = std::setlocale(LC_TIME, NULL);
             std::setlocale(LC_TIME, "");
-            string_type::value_type date_str[512];
-            NANODBC_STRFTIME(
-                date_str
-                , sizeof(date_str) / sizeof(string_type::value_type)
-                , NANODBC_TEXT("%Y-%m-%d")
-                , &st);
+            char date_str[512];
+            std::strftime(date_str, sizeof(date_str), "%Y-%m-%d", &st);
             std::setlocale(LC_TIME, old_lc_time);
-            result.assign(date_str);
+            convert(date_str, result);
             return;
         }
 
         case SQL_C_TIMESTAMP:
         {
-            timestamp stamp = *((timestamp*)(col.pdata_ + rowset_position_ * col.clen_));
+            const timestamp stamp = *reinterpret_cast<timestamp*>(col.pdata_ + rowset_position_ * col.clen_);
             std::tm st = { 0 };
             st.tm_year = stamp.year - 1900;
             st.tm_mon = stamp.month - 1;
@@ -2638,15 +2714,11 @@ inline void result::result_impl::get_ref_impl<string_type>(short column, string_
             st.tm_sec = stamp.sec;
             char* old_lc_time = std::setlocale(LC_TIME, NULL);
             std::setlocale(LC_TIME, "");
-            string_type::value_type date_str[512];
-            NANODBC_STRFTIME(
-                date_str
-                , sizeof(date_str) / sizeof(string_type::value_type)
-                , NANODBC_TEXT("%Y-%m-%d %H:%M:%S %z")
-                , &st);
+            char date_str[512];
+            std::strftime(date_str, sizeof(date_str), "%Y-%m-%d %H:%M:%S %z", &st);
             std::setlocale(LC_TIME, old_lc_time);
-           result.assign(date_str);
-           return;
+            convert(date_str, result);
+            return;
         }
     }
     throw type_incompatible_error();
@@ -2859,6 +2931,16 @@ void* connection::native_env_handle() const
     return impl_->native_env_handle();
 }
 
+string_type connection::dbms_name() const
+{
+    return impl_->dbms_name();
+}
+
+string_type connection::dbms_version() const
+{
+    return impl_->dbms_version();
+}
+
 string_type connection::driver_name() const
 {
     return impl_->driver_name();
@@ -3475,8 +3557,12 @@ long catalog::columns::ordinal_position() const
 
 string_type catalog::columns::is_nullable() const
 {
-    // IS_NULLABLE is never NULL
-    return result_.get<string_type>(17);
+    // IS_NULLABLE might be NULL
+
+    // MSDN: This column returns a zero-length string if nullability is unknown.
+    //       ISO rules are followed to determine nullability.
+    //       An ISO SQL-compliant DBMS cannot return an empty string.
+    return result_.get<string_type>(17, string_type());
 }
 
 catalog::catalog(connection& conn)
@@ -3493,7 +3579,7 @@ catalog::tables catalog::find_tables(
     statement stmt(conn_);
     RETCODE rc;
     NANODBC_CALL_RC(
-        NANODBC_UNICODE(SQLTables)
+        NANODBC_FUNC(SQLTables)
         , rc
         , stmt.native_statement_handle()
         , (NANODBC_SQLCHAR*)(catalog.empty() ? NULL : catalog.c_str())
@@ -3520,7 +3606,7 @@ catalog::columns catalog::find_columns(
     statement stmt(conn_);
     RETCODE rc;
     NANODBC_CALL_RC(
-        NANODBC_UNICODE(SQLColumns)
+        NANODBC_FUNC(SQLColumns)
         , rc
         , stmt.native_statement_handle()
         , (NANODBC_SQLCHAR*)(catalog.empty() ? NULL : catalog.c_str())
@@ -3546,7 +3632,7 @@ catalog::primary_keys catalog::find_primary_keys(
     statement stmt(conn_);
     RETCODE rc;
     NANODBC_CALL_RC(
-        NANODBC_UNICODE(SQLPrimaryKeys)
+        NANODBC_FUNC(SQLPrimaryKeys)
         , rc
         , stmt.native_statement_handle()
         , (NANODBC_SQLCHAR*)(catalog.empty() ? NULL : catalog.c_str())

+ 26 - 12
Source/ThirdParty/nanodbc/src/nanodbc.h

@@ -112,14 +112,14 @@ namespace nanodbc
     //! \brief Assertion.
     //!
     //! By default, nanodbc uses C \c assert() for internal assertions.
-    //! User can override it by defining NANODBC_ASSERT(expr) macro
+    //! User can override it by defining \c NANODBC_ASSERT(expr) macro
     //! in the nanodbc.h file and customizing it as desired,
     //! before building the library.
     //!
     //! \code{.cpp}
     //! #ifdef _DEBUG
-    //! #include <crtdbg.h>
-    //! #define NANODBC_ASSERT _ASSERTE
+    //!     #include <crtdbg.h>
+    //!     #define NANODBC_ASSERT _ASSERTE
     //! #endif
     //! \endcode
     #define NANODBC_ASSERT(expression) assert(expression)
@@ -130,30 +130,34 @@ namespace nanodbc
 // You must explicitly request Unicode support by defining NANODBC_USE_UNICODE at compile time.
 #ifndef DOXYGEN
     #ifdef NANODBC_USE_UNICODE
-        typedef std::wstring string_type;
+        #ifdef NANODBC_USE_IODBC_WIDE_STRINGS
+            typedef std::u32string string_type;
+        #else
+            typedef std::u16string string_type;
+        #endif
     #else
         typedef std::string string_type;
     #endif // NANODBC_USE_UNICODE
 
     #if defined(_WIN64)
-        // LLP64 machine, Windows
+        // LLP64 machine: Windows
         typedef std::int64_t null_type;
     #elif !defined(_WIN64) && defined(__LP64__)
-        // LP64 machine, OS X or Linux
+        // LP64 machine: OS X or Linux
         typedef long null_type;
     #else
         // 32-bit machine
         typedef long null_type;
     #endif
 #else
-    //! string_type will be std::wstring if NANODBC_USE_UNICODE is defined, otherwise std::string.
+    //! \c string_type will be \c std::u16string or \c std::32string if \c NANODBC_USE_UNICODE is defined, otherwise \c std::string.
     typedef unspecified-type string_type;
-    //! null_type will be int64_t for 64-bit compilations, otherwise long.
+    //! \c null_type will be \c int64_t for 64-bit compilations, otherwise \c long.
     typedef unspecified-type null_type;
 #endif // DOXYGEN
 
 #if defined(_MSC_VER) && _MSC_VER <= 1800
-    // These versions of Visual C++ do not yet support noexcept or std::move.
+    // These versions of Visual C++ do not yet support \c noexcept or \c std::move.
     #define NANODBC_NOEXCEPT
     #define NANODBC_NO_MOVE_CTOR
 #else
@@ -176,10 +180,10 @@ namespace nanodbc
 //! \addtogroup exceptions Exception types
 //! \brief Possible error conditions.
 //!
-//! Specific errors such as type_incompatible_error, null_access_error, and index_range_error can arise
-//! from improper use of the nanodbc library. The general database_error is for all other situations
+//! Specific errors such as \c type_incompatible_error, \c null_access_error, and \c index_range_error can arise
+//! from improper use of the nanodbc library. The general \c database_error is for all other situations
 //! in which the ODBC driver or C API reports an error condition. The explanatory string for database_error
-//! will, if possible, contain a diagnostic message obtained from SQLGetDiagRec().
+//! will, if possible, contain a diagnostic message obtained from \c SQLGetDiagRec().
 //! @{
 
 //! \brief Type incompatible.
@@ -847,6 +851,16 @@ public:
     //! \brief Returns the native ODBC environment handle.
     void* native_env_handle() const;
 
+    //! \brief Returns name of the DBMS product.
+    //! Returns the ODBC information type SQL_DBMS_NAME of the DBMS product
+    //! accesssed by the driver via the current connection.
+    string_type dbms_name() const;
+
+    //! \brief Returns version of the DBMS product.
+    //! Returns the ODBC information type SQL_DBMS_VER of the DBMS product
+    //! accesssed by the driver via the current connection.
+    string_type dbms_version() const;
+
     //! \brief Returns the name of the ODBC driver.
     //! \throws database_error
     string_type driver_name() const;