Răsfoiți Sursa

Merge commit 'c5706e2c884636104906ea75dbf444ad82abf0f4' into master.

Yao Wei Tjong 姚伟忠 8 ani în urmă
părinte
comite
27d00d19c6

+ 1 - 1
Source/ThirdParty/nanodbc/CHANGELOG.md

@@ -198,7 +198,7 @@ case of iODBC with unicode build enabled, `char32_t`. Boost.Test dropped in this
 - Cleans up style; removes CPP11 macros and C++03 support cruft.
 - Silence warnings and untabify.
 - Works with Unicode (std::wstring as nanodbc::string_type)
-- Using Nanodbc with SQL Server Native Client works with nvarchar(max) and varchar(max) fields in Win32 and Win64.
+- Using nanodbc with SQL Server Native Client works with nvarchar(max) and varchar(max) fields in Win32 and Win64.
 
 # v1.0.0
 

+ 21 - 3
Source/ThirdParty/nanodbc/CMakeLists.txt

@@ -23,12 +23,30 @@
 # Define target name
 set (TARGET_NAME nanodbc)
 
+# nanodbc specific options
+option (NANODBC_DISABLE_ASYNC "Disable async features entirely" OFF)
+if (NANODBC_DISABLE_ASYNC)
+    add_definitions (-DNANODBC_DISABLE_ASYNC)
+endif ()
+option (NANODBC_ENABLE_UNICODE "Enable Unicode support" OFF)
+if (NANODBC_ENABLE_UNICODE)
+    add_definitions (-DNANODBC_ENABLE_UNICODE)
+    if (MSVC)
+        # Sets "Use Unicode Character Set" property in Visual Studio projects
+        add_definitions (-DUNICODE -D_UNICODE)
+    endif ()
+endif ()
+option (NANODBC_ENABLE_WORKAROUND_NODATA "Enable SQL_NO_DATA workaround (see Issue #33)" OFF)
+if (NANODBC_ENABLE_WORKAROUND_NODATA)
+  add_definitions (-DNANODBC_ENABLE_WORKAROUND_NODATA)
+endif ()
+
 # Define source files
-define_source_files (GLOB_CPP_PATTERNS src/*.cpp GLOB_H_PATTERNS src/*.h)
+define_source_files (GLOB_CPP_PATTERNS nanodbc/*.cpp GLOB_H_PATTERNS nanodbc/*.h)
 
 # Define dependency libs
 if (ODBC_INCLUDE_DIRS)
-    set (INCLUDE_DIRS ${ODBC_INCLUDE_DIRS})
+    set (INCLUDE_DIRS . ${ODBC_INCLUDE_DIRS})
 endif ()
 if (ODBC_DEFINES)
     add_definitions (${ODBC_DEFINES})
@@ -38,4 +56,4 @@ endif ()
 setup_library ()
 
 # Install headers for building and using the Urho3D library
-install_header_files (DIRECTORY src/ DESTINATION ${DEST_INCLUDE_DIR}/ThirdParty/nanodbc FILES_MATCHING PATTERN *.h)  # Note: the trailing slash is significant
+install_header_files (DIRECTORY nanodbc/ DESTINATION ${DEST_INCLUDE_DIR}/ThirdParty/nanodbc FILES_MATCHING PATTERN *.h)  # Note: the trailing slash is significant

+ 19 - 19
Source/ThirdParty/nanodbc/LICENSE

@@ -1,19 +1,19 @@
-The MIT License
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+The MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 230 - 51
Source/ThirdParty/nanodbc/README.md

@@ -1,22 +1,80 @@
-![nanodbc-banner](https://cloud.githubusercontent.com/assets/1903876/11858632/cc0e21e6-a428-11e5-9a84-39fa27984914.png)
+![nanodbc-banner][nanodbc-banner]
 
-A small C++ wrapper for the native C ODBC API. Please see the [online documentation](http://lexicalunit.github.com/nanodbc/) for user information, example usage, propaganda, and detailed source level documentation.
+A small C++ wrapper for the native C ODBC API. Please see the [online documentation][nanodbc] for
+user information, example usage, propaganda, and detailed source level documentation.
+
+[![GitHub release](https://img.shields.io/github/tag/lexicalunit/nanodbc.svg)](https://github.com/lexicalunit/nanodbc/releases)
+[![GitHub commits](https://img.shields.io/github/commits-since/lexicalunit/nanodbc/v2.12.4.svg?style=flat-square)](https://github.com/lexicalunit/nanodbc/releases/tag/v2.12.4)
+[![License](https://img.shields.io/github/license/lexicalunit/nanodbc.svg?style=flat-square)](https://github.com/lexicalunit/nanodbc/blob/master/LICENSE)
+
+[![Gitter](https://img.shields.io/gitter/room/lexicalunit/nanodbc.svg?style=flat-square)](https://gitter.im/nanodbc-help/Lobby)
+
+## Build Status
+
+| Branch |  Linux/OSX | Windows | Coverity |
+|:--- |:--- |:--- |:--- |
+| `master`  | [![master][travis-badge-master]][travis] | [![master][appveyor-badge]][appveyor] | [![coverity_scan][coverity-badge]][coverity] |
+| `latest` | [![latest][travis-badge-latest]][travis] |   |   |
+| `release` | [![release][travis-badge-release]][travis] |   |   |
+
+> **Note:** The Coverity status uses the [coverity_scan][nanodbc-coverity] branch. When `master`
+            has had a significant amount of work pushed to it, merge those changes into
+            `coverity_scan` as well to keep the status up to date.
+
+## Philosophy
+
+The native C API for working with ODBC is exorbitantly verbose, ridiculously complicated, and
+fantastically brittle. nanodbc addresses these frustrations! The goal for nanodbc is to make
+developers happy. Common tasks should be easy, requiring concise and simple code.
+
+The [latest C++ standards][cpp-std] and [best practices][cpp-core] are
+_enthusiastically_ incorporated to make the library as future-proof as possible. To accommodate
+users who can not use the latest and greatest, [semantic versioning][semver] and
+release notes will clarify required C++ features and/or standards for particular versions.
+
+### Design Decisions
+
+All complex objects in nanodbc follow the [pimpl (Pointer to IMPLementation)][pimpl] idiom to
+provide separation between interface and implementation, value semantics, and a clean `nanodbc.h` header file that includes nothing but standard C++ headers.
+
+nanodbc wraps ODBC code, providing a simpler way to do the same thing. We try to be as featureful
+as possible, but I can't guarantee you'll never have to write supporting ODBC code. Personally, I
+have never had to do so.
+
+Major features beyond what's already supported by ODBC are not within the scope of nanodbc. This is
+where the *nano* part of nanodbc becomes relevant: This library is _as minimal as possible_. That
+means no dependencies beyond standard C++ and typical ODBC headers. No features unsupported by
+existing ODBC API calls.
+
+## Branches
 
 | Version | Description |
 |:--- |:--- |
-| `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. |
+| `release` | 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 published version; please use this version if CI tests are all passing. **[See all available versions.][nanodbc-releases]** |
+| `master`  | 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.* |
 
 # Building
 
-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.
+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][cmake] files which build a library object, or build and run the included tests. The CMake
+files will also support out of source builds.
 
-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.
+Tests use the [Catch][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][brew]
+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.
+The tests attempt to connect to a [SQLite][sqlite] 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][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]
@@ -28,9 +86,14 @@ 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. 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 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).
+If the tests fail, please don't hesitate to [**report it**][nanodbc-new-issue] 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
@@ -39,73 +102,89 @@ cd build
 cmake [Build Options] ..
 make # creates shared library
 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 tests # builds the tests
+make test # runs the tests
+make check # builds and then runs tests
 make examples # builds all the example programs
 make install # installs nanodbc.h and shared library
 ```
 
 ## Build Options
 
-The following build options are available via CMake. If you are not using CMake to build nanodbc, you will need to set the corresponding `-D` compile define flags yourself. You will need to configure your build to use [boost](http://www.boost.org/) if you want to use the `NANODBC_USE_BOOST_CONVERT` option.
+The following build options are available via [CMake command-line option][cmake-docs] `-D`. If you
+are not using CMake to build nanodbc, you will need to set the corresponding `-D` compile define
+flags yourself.
 
-| CMake Option                     | Possible Values  | Default       | Details |
-| ------------------------------------- | --------------------- | ------------- | ------- |
-| `-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`. |
+All boolean options follow the CMake [OPTION][cmake-option] default value convention: if no initial value is provided, `OFF` is used.
 
-## Note About iODBC
+Use the standard CMake option `-DBUILD_SHARED_LIBS=ON` to build nanodbc as shared library.
 
-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`.
+If you need to use the `NANODBC_ENABLE_BOOST=ON` option, you will have to configure your environment to use [Boost][boost].
 
-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.
+| CMake Option                  | Possible Values | Details |
+| -----------------------------------| ---------------------| ------- |
+| `NANODBC_DISABLE_ASYNC`            | `OFF` or `ON`        | Disable all async features. May resolve build issues in older ODBC versions. |
+| `NANODBC_DISABLE_EXAMPLES`         | `OFF` or `ON`        | Do not build examples. |
+| `NANODBC_DISABLE_INSTALL`          | `OFF` or `ON`        | Do not generate install target. |
+| `NANODBC_DISABLE_LIBCXX`           | `OFF` or `ON`        | Do not use libc++, if available on the system. |
+| `NANODBC_DISABLE_TESTS`            | `OFF` or `ON`        | Do not build tests. |
+| `NANODBC_ENABLE_BOOST`             | `OFF` or `ON`        | Use Boost for Unicode string convertions (requires [Boost.Locale][boost-locale]). Workaround to issue [#44](https://github.com/lexicalunit/nanodbc/issues/44). |
+| `NANODBC_ENABLE_UNICODE`           | `OFF` or `ON`        | Enable Unicode support. `nanodbc::string` becomes `std::u16string` or `std::u32string`. |
+| `NANODBC_ENABLE_WORKAROUND_NODATA` | `OFF` or `ON`        | Enable `SQL_NO_DATA` workaround to issue [#33](https://github.com/lexicalunit/nanodbc/issues/33). |
+| `NANODBC_ODBC_VERSION`             | `SQL_OV_ODBC3[...]`  | Forces ODBC version to use. Default is `SQL_OV_ODBC3_80` if available, otherwise `SQL_OV_ODBC3`. |
 
----
+## Note About iODBC
 
-# Contributing
+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` will be `std::u32string`. In **ALL** other cases it will be `std::u16string`.
 
-## Publish and Release Process
+Continuous integration tests run on [Travis-CI][travis]. 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.
 
-Once your local `master` branch is ready for publishing (i.e. [semantic versioning](http://semver.org/)), use the `scripts/publish.sh` script. This script bumps the major, minor, or patch version, then updates the repository's `VERSION` file, adds a "Preparing" commit, and creates git tags appropriately. For example to make a minor update you would run `./scripts/publish.sh minor`.
+---
 
-> **Important:** Always update [`CHANGELOG.md`](CHANGELOG.md) with information about new changes, bug fixes, and features when making a new release. Use the `./scripts/changes.sh` script to aid in your composition of this document. The publish script itself will attempt to verify that the changelog file has been properly updated.
+# Contributing
 
-To do this manually instead, use the following steps — for example a minor update from `2.9.x` to `2.10.0`:
+## Code Style
 
-1. `echo "2.10.0" > VERSION`
-2. `git add VERSION`
-3. `git commit -m "Preparing 2.10.0 release."`
-4. `git tag -f "v2.10.0"`
-5. `git push -f origin "v2.10.0"`
-6. `git push -f origin master:latest`
+[`clang-format`][clang-format] handles all C++ code formatting for nanodbc. This utility is
+[brew-installable][brew] on OS X (`brew install clang-format`) and is available on all major
+platforms. See our `.clang-format` configuration file for details on the style. The script
+`utility/style.sh` formats all code in the repository automatically. To run `clang-format` on a
+single file use the following.
 
-### Release Process
+```shell
+$ clang-format -i /path/to/file
+```
 
-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.**
+**Please auto-format all code submitted in Pull Requests.**
 
 ## Source Level Documentation
 
-Source level documentation provided via [GitHub's gh-pages](https://help.github.com/articles/what-are-github-pages/) is available at [nanodbc.lexicalunit.com](http://lexicalunit.github.io/nanodbc/). To re-build and update it, preform the following steps from the root directory of the repository:
+Source level documentation provided via [GitHub's gh-pages][gh-pages] is available at
+[nanodbc.lexicalunit.com][nanodbc]. To re-build and update it, preform the following steps from the
+root directory of the repository:
 
-1. `git clone -b gh-pages [email protected]:lexicalunit/nanodbc.git doc` Necessary the first time, not subsequently.
+1. `git clone -b gh-pages [email protected]:lexicalunit/nanodbc.git doc` Necessary the first time,
+   not subsequently.
 2. `cd doc`
 3. `make` Generates updated documentation locally.
 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](https://github.com/lexicalunit/nanodbc/blob/gh-pages/Makefile) for more details.
+Building documentation and gh-pages requires the use of [Doxygen][doxygen] and
+[jekyll][jekyll]. See the [`Makefile` on the `gh-pages` branch][nanodbc-makefile] for more details.
 
 ## Quick Setup for Testing or Development Environments
 
-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:
+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][docker]
+container suitable for testing and development of nanodbc:
 
 ```shell
 $ cd /path/to/nanodbc
@@ -116,7 +195,7 @@ 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:
+Or, to build and ssh into a [vagrant][vagrant] VM (using VirtualBox for example) use:
 
 ```shell
 $ cd /path/to/nanodbc
@@ -128,18 +207,118 @@ vagrant@vagrant-ubuntu-precise-64:~$ CXX=g++-5 cmake ..
 vagrant@vagrant-ubuntu-precise-64:~$ make nanodbc
 ```
 
+## Tests
+
+One of important objectives is to maintain nanodbc covered with tests. New contributions
+submitted via Pull Requests must include corresponding tests. This is important to ensure
+the quality of new features.
+
+The good news is that adding tests is easy!
+
+The tests structure:
+
+- `tests/base_test_fixture.h` includes a set of common test cases.
+- `tests/<database>_test.cpp` is a source code for an independent test program that includes both,
+  common and database-specific test cases.
+
+To add new test case:
+
+1. In `tests/base_test_fixture.h` file, add a new test case method to `base_test_fixture`
+   class (e.g. `void my_feature_test()`).
+2. In each `tests/<database>_test.cpp` file, copy and paste the `TEST_CASE_METHOD` boilerplate,
+   updating name, tags, etc.
+
+If a feature requires a database-specific test case for each database, then skip the
+`tests/base_test_fixture.h` step and write a dedicated test case directly in
+`tests/<database>_test.cpp` file.
+
+## Publish and Release Process
+
+Once your local `master` branch is ready for publishing
+(i.e. [semantic versioning][semver]), use the `utility/publish.sh` script. This script
+bumps the major, minor, or patch version, then updates the repository's `VERSION` file, adds a
+"Preparing" commit, and creates git tags appropriately. For example to make a minor update you
+would run `./utility/publish.sh minor`.
+
+> **Important:** Always update [`CHANGELOG.md`](CHANGELOG.md) with information about new changes,
+                 bug fixes, and features when making a new release. Use the `./utility/changes.sh`
+                 script to aid in your composition of this document. The publish script itself will
+                 attempt to verify that the changelog file has been properly updated.
+
+To do this manually instead, use the following steps &mdash; for example a minor update from
+`2.9.x` to `2.10.0`:
+
+1. `echo "2.10.0" > VERSION`
+2. `git add VERSION`
+3. `git commit -m "Preparing 2.10.0 release."`
+4. `git tag -f "v2.10.0"`
+5. `git push -f origin "v2.10.0"`
+6. `git push -f origin master:latest`
+
+### Release Process
+
+Release nanodbc with the `utility/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.**
+
 ## Future work
 
 ### Good to Have / Want Someday
 
-- Refactor unit tests to follow BDD pattern.
+- Refactor 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.
 - Clean up `bind_*` family of functions, reduce any duplication.
 - 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.
-- 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?
+- Versioned generated source level API documentation for `release` and `latest`.
+  For each major and minor published versions too?
 - Add "HOWTO Build" documentation for Windows, OS X, and Linux.
+
+---
+
+[MIT][mit] &copy; [lexicalunit][author] and [contributors][contributors].
+
+[mit]:              http://opensource.org/licenses/MIT
+[author]:           http://github.com/lexicalunit
+[contributors]:     https://github.com/lexicalunit/nanodbc/graphs/contributors
+
+[nanodbc]:              http://lexicalunit.github.com/nanodbc/
+[nanodbc-banner]:       https://cloud.githubusercontent.com/assets/1903876/11858632/cc0e21e6-a428-11e5-9a84-39fa27984914.png
+[nanodbc-coverity]:     https://github.com/lexicalunit/nanodbc/tree/coverity_scan
+[nanodbc-makefile]:     https://github.com/lexicalunit/nanodbc/blob/gh-pages/Makefile
+[nanodbc-new-issue]:    https://github.com/lexicalunit/nanodbc/issues/new
+[nanodbc-releases]:     https://github.com/lexicalunit/nanodbc/releases
+
+[boost]:        http://www.boost.org/
+[boost-locale]: http://www.boost.org/doc/libs/release/libs/locale/
+[brew]:         http://brew.sh/
+[catch]:        https://github.com/philsquared/Catch
+[clang-format]: http://clang.llvm.org/docs/ClangFormat.html
+[cmake-docs]:   https://cmake.org/cmake/help/latest/manual/cmake.1.html
+[cmake]:        http://www.cmake.org/
+[cmake-option]: http://cmake.org/cmake/help/latest/command/option.html
+[cpp-core]:     https://github.com/isocpp/CppCoreGuidelines
+[cpp-std]:      https://isocpp.org/std/status
+[docker]:       https://www.docker.com/
+[doxygen]:      http://www.doxygen.org
+[gh-pages]:     https://help.github.com/articles/what-are-github-pages/
+[jekyll]:       https://jekyllrb.com/
+[pimpl]:        http://c2.com/cgi/wiki?PimplIdiom
+[semver]:       http://semver.org/
+[sqlite]:       https://www.sqlite.org/
+[sqliteodbc]:   http://www.ch-werner.de/sqliteodbc/
+[vagrant]:      https://www.vagrantup.com/
+
+[travis]:               https://travis-ci.org/lexicalunit/nanodbc
+[travis-badge-latest]:  https://travis-ci.org/lexicalunit/nanodbc.svg?branch=latest
+[travis-badge-master]:  https://travis-ci.org/lexicalunit/nanodbc.svg?branch=master
+[travis-badge-release]: https://travis-ci.org/lexicalunit/nanodbc.svg?branch=release
+
+[appveyor]:         https://ci.appveyor.com/project/lexicalunit/nanodbc?branch=master
+[appveyor-badge]:   https://ci.appveyor.com/api/projects/status/71nb7l794n3i8vdj/branch/master?svg=true
+
+[coverity]:         https://scan.coverity.com/projects/lexicalunit-nanodbc
+[coverity-badge]:   https://scan.coverity.com/projects/7437/badge.svg

+ 4809 - 0
Source/ThirdParty/nanodbc/nanodbc/nanodbc.cpp

@@ -0,0 +1,4809 @@
+/// \file nanodbc.cpp Implementation details.
+#ifndef DOXYGEN
+
+// ASCII art banners are helpful for code editors with a minimap display.
+// Generated with http://patorjk.com/software/taag/#p=display&v=0&f=Colossal
+
+#if defined(_MSC_VER)
+#if _MSC_VER <= 1800
+// silence spurious Visual C++ warnings
+#pragma warning(disable : 4244) // warning about integer conversion issues.
+#pragma warning(disable : 4312) // warning about 64-bit portability issues.
+#endif
+#pragma warning(disable : 4996) // warning about deprecated declaration
+#endif
+
+#include <nanodbc/nanodbc.h>
+
+#include <algorithm>
+#include <clocale>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <iomanip>
+#include <map>
+#include <type_traits>
+
+#ifndef __clang__
+#include <cstdint>
+#endif
+
+// User may redefine NANODBC_ASSERT macro in nanodbc/nanodbc.h
+#ifndef NANODBC_ASSERT
+#include <cassert>
+#define NANODBC_ASSERT(expr) assert(expr)
+#endif
+
+#ifdef NANODBC_ENABLE_BOOST
+#include <boost/locale/encoding_utf.hpp>
+#elif defined(__GNUC__) && (__GNUC__ < 5)
+#include <cwchar>
+#else
+#include <codecvt>
+#endif
+
+#ifdef __APPLE__
+// silence spurious OS X deprecation warnings
+#define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_6
+#endif
+
+#ifdef _WIN32
+// needs to be included above sql.h for windows
+#ifndef __MINGW32__
+#define NOMINMAX
+#endif
+#include <windows.h>
+#endif
+
+#include <sql.h>
+#include <sqlext.h>
+
+// Driver specific SQL data type defines.
+// Microsoft has -150 thru -199 reserved for Microsoft SQL Server Native Client driver usage.
+// Originally, defined in sqlncli.h (old SQL Server Native Client driver)
+// and msodbcsql.h (new Microsoft ODBC Driver for SQL Server)
+// See https://github.com/lexicalunit/nanodbc/issues/226
+#ifndef SQL_SS_VARIANT
+#define SQL_SS_VARIANT (-150)
+#endif
+#ifndef SQL_SS_XML
+#define SQL_SS_XML (-152)
+#endif
+#ifndef SQL_SS_TABLE
+#define SQL_SS_TABLE (-153)
+#endif
+#ifndef SQL_SS_TIME2
+#define SQL_SS_TIME2 (-154)
+#endif
+#ifndef SQL_SS_TIMESTAMPOFFSET
+#define SQL_SS_TIMESTAMPOFFSET (-155)
+#endif
+// Large CLR User-Defined Types (ODBC)
+// https://msdn.microsoft.com/en-us/library/bb677316.aspx
+// Essentially, UDT is a varbinary type with additional metadata.
+// Memory layout: SQLCHAR *(unsigned char *)
+// C data type:   SQL_C_BINARY
+// Value:         SQL_BINARY (-2)
+#ifndef SQL_SS_UDT
+#define SQL_SS_UDT (-151) // from sqlncli.h
+#endif
+
+#ifndef SQL_NVARCHAR
+#define SQL_NVARCHAR (-10)
+#endif
+
+// Default to ODBC version defined by NANODBC_ODBC_VERSION if provided.
+#ifndef NANODBC_ODBC_VERSION
+#ifdef SQL_OV_ODBC3_80
+// Otherwise, use ODBC v3.8 if it's available...
+#define NANODBC_ODBC_VERSION SQL_OV_ODBC3_80
+#else
+// or fallback to ODBC v3.x.
+#define NANODBC_ODBC_VERSION SQL_OV_ODBC3
+#endif
+#endif
+
+// clang-format off
+// 888     888          d8b                       888
+// 888     888          Y8P                       888
+// 888     888                                    888
+// 888     888 88888b.  888  .d8888b .d88b.   .d88888  .d88b.
+// 888     888 888 "88b 888 d88P"   d88""88b d88" 888 d8P  Y8b
+// 888     888 888  888 888 888     888  888 888  888 88888888
+// Y88b. .d88P 888  888 888 Y88b.   Y88..88P Y88b 888 Y8b.
+//  "Y88888P"  888  888 888  "Y8888P "Y88P"   "Y88888  "Y8888
+// MARK: Unicode -
+// clang-format on
+
+#ifdef NANODBC_ENABLE_UNICODE
+#define NANODBC_FUNC(f) f##W
+#define NANODBC_SQLCHAR SQLWCHAR
+#else
+#define NANODBC_FUNC(f) f
+#define NANODBC_SQLCHAR SQLCHAR
+#endif
+
+#ifdef NANODBC_USE_IODBC_WIDE_STRINGS
+typedef std::u32string wide_string;
+#define NANODBC_CODECVT_TYPE std::codecvt_utf8
+#else
+#ifdef _MSC_VER
+typedef std::wstring wide_string;
+#define NANODBC_CODECVT_TYPE std::codecvt_utf8_utf16
+#else
+typedef std::u16string wide_string;
+#define NANODBC_CODECVT_TYPE std::codecvt_utf8_utf16
+#endif
+#endif
+typedef wide_string::value_type wide_char_t;
+
+#if defined(_MSC_VER)
+#ifndef NANODBC_ENABLE_UNICODE
+// Disable unicode in sqlucode.h on Windows when NANODBC_ENABLE_UNICODE
+// is not defined. This is required because unicode is enabled by
+// default on many Windows systems.
+#define SQL_NOUNICODEMAP
+#endif
+#endif
+
+// clang-format off
+//  .d88888b.  8888888b.  888888b.    .d8888b.       888b     d888
+// d88P" "Y88b 888  "Y88b 888  "88b  d88P  Y88b      8888b   d8888
+// 888     888 888    888 888  .88P  888    888      88888b.d88888
+// 888     888 888    888 8888888K.  888             888Y88888P888  8888b.   .d8888b 888d888 .d88b.  .d8888b
+// 888     888 888    888 888  "Y88b 888             888 Y888P 888     "88b d88P"    888P"  d88""88b 88K
+// 888     888 888    888 888    888 888    888      888  Y8P  888 .d888888 888      888    888  888 "Y8888b.
+// Y88b. .d88P 888  .d88P 888   d88P Y88b  d88P      888   "   888 888  888 Y88b.    888    Y88..88P      X88
+//  "Y88888P"  8888888P"  8888888P"   "Y8888P"       888       888 "Y888888  "Y8888P 888     "Y88P"   88888P'
+// MARK: ODBC Macros -
+// clang-format on
+
+#define NANODBC_STRINGIZE_I(text) #text
+#define NANODBC_STRINGIZE(text) NANODBC_STRINGIZE_I(text)
+
+// By making all calls to ODBC functions through this macro, we can easily get
+// runtime debugging information of which ODBC functions are being called,
+// in what order, and with what parameters by defining NANODBC_ODBC_API_DEBUG.
+#ifdef NANODBC_ODBC_API_DEBUG
+#include <iostream>
+#define NANODBC_CALL_RC(FUNC, RC, ...)                                                             \
+    do                                                                                             \
+    {                                                                                              \
+        std::cerr << __FILE__                                                                      \
+            ":" NANODBC_STRINGIZE(__LINE__) " " NANODBC_STRINGIZE(FUNC) "(" #__VA_ARGS__ ")"       \
+                  << std::endl;                                                                    \
+        RC = FUNC(__VA_ARGS__);                                                                    \
+    } while (false) /**/
+#define NANODBC_CALL(FUNC, ...)                                                                    \
+    do                                                                                             \
+    {                                                                                              \
+        std::cerr << __FILE__                                                                      \
+            ":" NANODBC_STRINGIZE(__LINE__) " " NANODBC_STRINGIZE(FUNC) "(" #__VA_ARGS__ ")"       \
+                  << std::endl;                                                                    \
+        FUNC(__VA_ARGS__);                                                                         \
+    } while (false) /**/
+#else
+#define NANODBC_CALL_RC(FUNC, RC, ...) RC = FUNC(__VA_ARGS__)
+#define NANODBC_CALL(FUNC, ...) FUNC(__VA_ARGS__)
+#endif
+
+// clang-format off
+// 8888888888                                      888    888                        888 888 d8b
+// 888                                             888    888                        888 888 Y8P
+// 888                                             888    888                        888 888
+// 8888888    888d888 888d888 .d88b.  888d888      8888888888  8888b.  88888b.   .d88888 888 888 88888b.   .d88b.
+// 888        888P"   888P"  d88""88b 888P"        888    888     "88b 888 "88b d88" 888 888 888 888 "88b d88P"88b
+// 888        888     888    888  888 888          888    888 .d888888 888  888 888  888 888 888 888  888 888  888
+// 888        888     888    Y88..88P 888          888    888 888  888 888  888 Y88b 888 888 888 888  888 Y88b 888
+// 8888888888 888     888     "Y88P"  888          888    888 "Y888888 888  888  "Y88888 888 888 888  888  "Y88888
+//                                                                                                             888
+//                                                                                                        Y8b d88P
+//                                                                                                         "Y88P"
+// MARK: Error Handling -
+// clang-format on
+
+namespace
+{
+#ifdef NANODBC_ODBC_API_DEBUG
+inline std::string return_code(RETCODE rc)
+{
+    switch (rc)
+    {
+    case SQL_SUCCESS:
+        return "SQL_SUCCESS";
+    case SQL_SUCCESS_WITH_INFO:
+        return "SQL_SUCCESS_WITH_INFO";
+    case SQL_ERROR:
+        return "SQL_ERROR";
+    case SQL_INVALID_HANDLE:
+        return "SQL_INVALID_HANDLE";
+    case SQL_NO_DATA:
+        return "SQL_NO_DATA";
+    case SQL_NEED_DATA:
+        return "SQL_NEED_DATA";
+    case SQL_STILL_EXECUTING:
+        return "SQL_STILL_EXECUTING";
+    }
+    NANODBC_ASSERT(0);
+    return "unknown"; // should never make it here
+}
+#endif
+
+// Easy way to check if a return code signifies success.
+inline bool success(RETCODE rc)
+{
+#ifdef NANODBC_ODBC_API_DEBUG
+    std::cerr << "<-- rc: " << return_code(rc) << " | " << std::endl;
+#endif
+    return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO;
+}
+
+// Returns the array size.
+template <typename T, std::size_t N>
+inline std::size_t arrlen(T (&)[N])
+{
+    return N;
+}
+
+// Operates like strlen() on a character array.
+template <typename T, std::size_t N>
+inline std::size_t strarrlen(T (&a)[N])
+{
+    const T* s = &a[0];
+    std::size_t i = 0;
+    while (*s++ && i < N)
+        i++;
+    return i;
+}
+
+inline void convert(const wide_string& in, std::string& out)
+{
+#ifdef NANODBC_ENABLE_BOOST
+    using boost::locale::conv::utf_to_utf;
+    out = utf_to_utf<char>(in.c_str(), in.c_str() + in.size());
+#elif defined(__GNUC__) && (__GNUC__ < 5)
+    std::vector<wchar_t> characters(in.begin(), in.end());
+    const wchar_t * source = characters.data();
+    size_t size = wcsnrtombs(nullptr, &source, characters.size(), 0, nullptr);
+    if (size == std::string::npos)
+        throw std::range_error("UTF-16 -> UTF-8 conversion error");
+    out.resize(size);
+    wcsnrtombs(&out[0], &source, characters.size(), out.length(), nullptr);
+#elif defined(_MSC_VER) && (_MSC_VER >= 1900)
+    // Workaround for confirmed bug in VS2015 and VS2017 too
+    // See: https://connect.microsoft.com/VisualStudio/Feedback/Details/1403302
+    auto p = reinterpret_cast<unsigned short const*>(in.data());
+    out = std::wstring_convert<NANODBC_CODECVT_TYPE<unsigned short>, unsigned short>().to_bytes(
+        p, p + in.size());
+#else
+    out = std::wstring_convert<NANODBC_CODECVT_TYPE<wide_char_t>, wide_char_t>().to_bytes(in);
+#endif
+}
+
+#ifdef NANODBC_ENABLE_UNICODE
+inline void convert(const std::string& in, wide_string& out)
+{
+#ifdef NANODBC_ENABLE_BOOST
+    using boost::locale::conv::utf_to_utf;
+    out = utf_to_utf<wide_char_t>(in.c_str(), in.c_str() + in.size());
+#elif defined(__GNUC__) && (__GNUC__ < 5)
+    size_t size = mbsnrtowcs(nullptr, in.data(), in.length(), 0, nullptr);
+    if (size == std::string::npos)
+        throw std::range_error("UTF-8 -> UTF-16 conversion error");
+    std::vector<wchar_t> characters(size);
+    const char * source = in.data();
+    mbsnrtowcs(&characters[0], &source, in.length(), characters.size(), nullptr);
+    out = std::string(characters.begin(), characters.end());
+#elif defined(_MSC_VER) && (_MSC_VER >= 1900)
+    // Workaround for confirmed bug in VS2015 and VS2017 too
+    // See: https://connect.microsoft.com/VisualStudio/Feedback/Details/1403302
+    auto s =
+        std::wstring_convert<NANODBC_CODECVT_TYPE<unsigned short>, unsigned short>().from_bytes(in);
+    auto p = reinterpret_cast<wide_char_t const*>(s.data());
+    out.assign(p, p + s.size());
+#else
+    out = std::wstring_convert<NANODBC_CODECVT_TYPE<wide_char_t>, wide_char_t>().from_bytes(in);
+#endif
+}
+
+inline void convert(const wide_string& in, wide_string& out)
+{
+    out = in;
+}
+#else
+inline void convert(const std::string& in, std::string& out)
+{
+    out = in;
+}
+#endif
+
+// Attempts to get the most recent ODBC error as a string.
+// Always returns std::string, even in unicode mode.
+inline std::string
+recent_error(SQLHANDLE handle, SQLSMALLINT handle_type, long& native, std::string& state)
+{
+    nanodbc::string result;
+    std::string rvalue;
+    std::vector<NANODBC_SQLCHAR> sql_message(SQL_MAX_MESSAGE_LENGTH);
+    sql_message[0] = '\0';
+
+    SQLINTEGER i = 1;
+    SQLINTEGER native_error;
+    SQLSMALLINT total_bytes;
+    NANODBC_SQLCHAR sql_state[6];
+    RETCODE rc;
+
+    do
+    {
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLGetDiagRec),
+            rc,
+            handle_type,
+            handle,
+            (SQLSMALLINT)i,
+            sql_state,
+            &native_error,
+            0,
+            0,
+            &total_bytes);
+
+        if (success(rc) && total_bytes > 0)
+            sql_message.resize(static_cast<std::size_t>(total_bytes) + 1);
+
+        if (rc == SQL_NO_DATA)
+            break;
+
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLGetDiagRec),
+            rc,
+            handle_type,
+            handle,
+            (SQLSMALLINT)i,
+            sql_state,
+            &native_error,
+            sql_message.data(),
+            (SQLSMALLINT)sql_message.size(),
+            &total_bytes);
+
+        if (!success(rc))
+        {
+            convert(result, rvalue);
+            return rvalue;
+        }
+
+        if (!result.empty())
+            result += ' ';
+
+        result += nanodbc::string(sql_message.begin(), sql_message.end());
+        i++;
+
+// NOTE: unixODBC using PostgreSQL and SQLite drivers crash if you call SQLGetDiagRec()
+// more than once. So as a (terrible but the best possible) workaround just exit
+// this loop early on non-Windows systems.
+#ifndef _MSC_VER
+        break;
+#endif
+    } while (rc != SQL_NO_DATA);
+
+    convert(result, rvalue);
+    state = std::string(&sql_state[0], &sql_state[arrlen(sql_state) - 1]);
+    native = native_error;
+    std::string status = state;
+    status += ": ";
+    status += rvalue;
+
+    // some drivers insert \0 into error messages for unknown reasons
+    using std::replace;
+    replace(status.begin(), status.end(), '\0', ' ');
+
+    return status;
+}
+
+} // namespace
+
+namespace nanodbc
+{
+
+type_incompatible_error::type_incompatible_error()
+    : std::runtime_error("type incompatible")
+{
+}
+
+const char* type_incompatible_error::what() const NANODBC_NOEXCEPT
+{
+    return std::runtime_error::what();
+}
+
+null_access_error::null_access_error()
+    : std::runtime_error("null access")
+{
+}
+
+const char* null_access_error::what() const NANODBC_NOEXCEPT
+{
+    return std::runtime_error::what();
+}
+
+index_range_error::index_range_error()
+    : std::runtime_error("index out of range")
+{
+}
+
+const char* index_range_error::what() const NANODBC_NOEXCEPT
+{
+    return std::runtime_error::what();
+}
+
+programming_error::programming_error(const std::string& info)
+    : std::runtime_error(info.c_str())
+{
+}
+
+const char* programming_error::what() const NANODBC_NOEXCEPT
+{
+    return std::runtime_error::what();
+}
+
+database_error::database_error(void* handle, short handle_type, const std::string& info)
+    : std::runtime_error(info)
+    , native_error(0)
+    , sql_state("00000")
+{
+    message = std::string(std::runtime_error::what()) +
+              recent_error(handle, handle_type, native_error, sql_state);
+}
+
+const char* database_error::what() const NANODBC_NOEXCEPT
+{
+    return message.c_str();
+}
+
+const long database_error::native() const NANODBC_NOEXCEPT
+{
+    return native_error;
+}
+
+const std::string database_error::state() const NANODBC_NOEXCEPT
+{
+    return sql_state;
+}
+
+} // namespace nanodbc
+
+// Throwing exceptions using NANODBC_THROW_DATABASE_ERROR enables file name
+// and line numbers to be inserted into the error message. Useful for debugging.
+#define NANODBC_THROW_DATABASE_ERROR(handle, handle_type)                                          \
+    throw nanodbc::database_error(                                                                 \
+        handle, handle_type, __FILE__ ":" NANODBC_STRINGIZE(__LINE__) ": ") /**/
+
+// clang-format off
+// 8888888b.           888             d8b 888
+// 888  "Y88b          888             Y8P 888
+// 888    888          888                 888
+// 888    888  .d88b.  888888  8888b.  888 888 .d8888b
+// 888    888 d8P  Y8b 888        "88b 888 888 88K
+// 888    888 88888888 888    .d888888 888 888 "Y8888b.
+// 888  .d88P Y8b.     Y88b.  888  888 888 888      X88
+// 8888888P"   "Y8888   "Y888 "Y888888 888 888  88888P'
+// MARK: Details -
+// clang-format on
+
+#if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_STMT_EVENT) &&                       \
+    defined(SQL_API_SQLCOMPLETEASYNC)
+#define NANODBC_DO_ASYNC_IMPL
+#endif
+
+namespace
+{
+
+using namespace std; // if int64_t is in std namespace (in c++11)
+
+template <typename T>
+using is_integral8 = std::integral_constant<
+    bool,
+    std::is_integral<T>::value && sizeof(T) == 1 && !std::is_same<T, char>::value>;
+
+template <typename T>
+using is_integral16 = std::integral_constant<
+    bool,
+    std::is_integral<T>::value && sizeof(T) == 2 && !std::is_same<T, wchar_t>::value>;
+
+template <typename T>
+using is_integral32 = std::integral_constant<
+    bool,
+    std::is_integral<T>::value && sizeof(T) == 4 && !std::is_same<T, wchar_t>::value>;
+
+template <typename T>
+using is_integral64 = std::integral_constant<bool, std::is_integral<T>::value && sizeof(T) == 8>;
+
+// A utility for calculating the ctype from the given type T.
+// I essentially create a lookup table based on the MSDN ODBC documentation.
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms714556(v=vs.85).aspx
+template <class T, typename Enable = void>
+struct sql_ctype
+{
+};
+
+template <>
+struct sql_ctype<uint8_t>
+{
+    static const SQLSMALLINT value = SQL_C_BINARY;
+};
+
+template <typename T>
+struct sql_ctype<
+    T,
+    typename std::enable_if<is_integral16<T>::value && std::is_signed<T>::value>::type>
+{
+    static const SQLSMALLINT value = SQL_C_SSHORT;
+};
+
+template <typename T>
+struct sql_ctype<
+    T,
+    typename std::enable_if<is_integral16<T>::value && std::is_unsigned<T>::value>::type>
+{
+    static const SQLSMALLINT value = SQL_C_USHORT;
+};
+
+template <typename T>
+struct sql_ctype<
+    T,
+    typename std::enable_if<is_integral32<T>::value && std::is_signed<T>::value>::type>
+{
+    static const SQLSMALLINT value = SQL_C_SLONG;
+};
+
+template <typename T>
+struct sql_ctype<
+    T,
+    typename std::enable_if<is_integral32<T>::value && std::is_unsigned<T>::value>::type>
+{
+    static const SQLSMALLINT value = SQL_C_ULONG;
+};
+
+template <typename T>
+struct sql_ctype<
+    T,
+    typename std::enable_if<is_integral64<T>::value && std::is_signed<T>::value>::type>
+{
+    static const SQLSMALLINT value = SQL_C_SBIGINT;
+};
+
+template <typename T>
+struct sql_ctype<
+    T,
+    typename std::enable_if<is_integral64<T>::value && std::is_unsigned<T>::value>::type>
+{
+    static const SQLSMALLINT value = SQL_C_UBIGINT;
+};
+
+template <>
+struct sql_ctype<float>
+{
+    static const SQLSMALLINT value = SQL_C_FLOAT;
+};
+
+template <>
+struct sql_ctype<double>
+{
+    static const SQLSMALLINT value = SQL_C_DOUBLE;
+};
+
+template <>
+struct sql_ctype<nanodbc::string::value_type>
+{
+#ifdef NANODBC_ENABLE_UNICODE
+    static const SQLSMALLINT value = SQL_C_WCHAR;
+#else
+    static const SQLSMALLINT value = SQL_C_CHAR;
+#endif
+};
+
+template <>
+struct sql_ctype<nanodbc::string>
+{
+#ifdef NANODBC_ENABLE_UNICODE
+    static const SQLSMALLINT value = SQL_C_WCHAR;
+#else
+    static const SQLSMALLINT value = SQL_C_CHAR;
+#endif
+};
+
+template <>
+struct sql_ctype<nanodbc::date>
+{
+    static const SQLSMALLINT value = SQL_C_DATE;
+};
+
+template <>
+struct sql_ctype<nanodbc::time>
+{
+    static const SQLSMALLINT value = SQL_C_TIME;
+};
+
+template <>
+struct sql_ctype<nanodbc::timestamp>
+{
+    static const SQLSMALLINT value = SQL_C_TIMESTAMP;
+};
+
+// Encapsulates resources needed for column binding.
+class bound_column
+{
+public:
+    bound_column(const bound_column& rhs) = delete;
+    bound_column& operator=(bound_column rhs) = delete;
+
+    bound_column()
+        : name_()
+        , column_(0)
+        , sqltype_(0)
+        , sqlsize_(0)
+        , scale_(0)
+        , ctype_(0)
+        , clen_(0)
+        , blob_(false)
+        , cbdata_(0)
+        , pdata_(0)
+    {
+    }
+
+    ~bound_column()
+    {
+        delete[] cbdata_;
+        delete[] pdata_;
+    }
+
+public:
+    nanodbc::string name_;
+    short column_;
+    SQLSMALLINT sqltype_;
+    SQLULEN sqlsize_;
+    SQLSMALLINT scale_;
+    SQLSMALLINT ctype_;
+    SQLULEN clen_;
+    bool blob_;
+    nanodbc::null_type* cbdata_;
+    char* pdata_;
+};
+
+// Encapsulates properties of statement parameter.
+// Parameter corresponds to parameter marker associated with a prepared SQL statement.
+struct bound_parameter
+{
+    bound_parameter() = default;
+
+    SQLULEN size_ = 0;       // SQL data size of column or expression inbytes or characters
+    SQLUSMALLINT index_ = 0; // Zero-based index of parameter marker
+    SQLSMALLINT iotype_ = 0; // Input/Output type of parameter
+    SQLSMALLINT type_ = 0;   // SQL data type of parameter
+    SQLSMALLINT scale_ = 0;  // decimal digits of column or expression
+};
+
+// Encapsulates properties of buffer with data values bound to statement parameter.
+template <typename T>
+struct bound_buffer
+{
+    bound_buffer() = default;
+    bound_buffer(T const* values, std::size_t size, std::size_t value_size = 0)
+        : values_(values)
+        , size_(size)
+        , value_size_(value_size)
+    {
+    }
+
+    T const* values_ = nullptr;  // Pointer to buffer for parameter's data
+    std::size_t size_ = 0;       // Number of values (1 or length of array)
+    std::size_t value_size_ = 0; // Size of single value (max size). Zero, if ignored.
+};
+
+// Allocates the native ODBC handles.
+inline void allocate_environment_handle(SQLHENV& env)
+{
+    RETCODE rc;
+    NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
+
+    try
+    {
+        NANODBC_CALL_RC(
+            SQLSetEnvAttr,
+            rc,
+            env,
+            SQL_ATTR_ODBC_VERSION,
+            (SQLPOINTER)NANODBC_ODBC_VERSION,
+            SQL_IS_UINTEGER);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
+    }
+    catch (...)
+    {
+        NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env);
+        throw;
+    }
+}
+
+inline void allocate_handle(SQLHENV& env, SQLHDBC& conn)
+{
+    allocate_environment_handle(env);
+
+    try
+    {
+        NANODBC_ASSERT(env);
+        RETCODE rc;
+        NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_DBC, env, &conn);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
+    }
+    catch (...)
+    {
+        NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env);
+        throw;
+    }
+}
+
+} // namespace
+
+// clang-format off
+//  .d8888b.                                               888    d8b                             8888888                        888
+// d88P  Y88b                                              888    Y8P                               888                          888
+// 888    888                                              888                                      888                          888
+// 888         .d88b.  88888b.  88888b.   .d88b.   .d8888b 888888 888  .d88b.  88888b.              888   88888b.d88b.  88888b.  888
+// 888        d88""88b 888 "88b 888 "88b d8P  Y8b d88P"    888    888 d88""88b 888 "88b             888   888 "888 "88b 888 "88b 888
+// 888    888 888  888 888  888 888  888 88888888 888      888    888 888  888 888  888             888   888  888  888 888  888 888
+// Y88b  d88P Y88..88P 888  888 888  888 Y8b.     Y88b.    Y88b.  888 Y88..88P 888  888             888   888  888  888 888 d88P 888
+//  "Y8888P"   "Y88P"  888  888 888  888  "Y8888   "Y8888P  "Y888 888  "Y88P"  888  888           8888888 888  888  888 88888P"  888
+//                                                                                                                      888
+//                                                                                                                      888
+//                                                                                                                      888
+// MARK: Connection Impl -
+// clang-format on
+
+namespace nanodbc
+{
+
+class connection::connection_impl
+{
+public:
+    connection_impl(const connection_impl&) = delete;
+    connection_impl& operator=(const connection_impl&) = delete;
+
+    connection_impl()
+        : env_(0)
+        , conn_(0)
+        , connected_(false)
+        , transactions_(0)
+        , rollback_(false)
+    {
+        allocate_handle(env_, conn_);
+    }
+
+    connection_impl(const string& dsn, const string& user, const string& pass, long timeout)
+        : env_(0)
+        , conn_(0)
+        , connected_(false)
+        , transactions_(0)
+        , rollback_(false)
+    {
+        allocate_handle(env_, conn_);
+        try
+        {
+            connect(dsn, user, pass, timeout);
+        }
+        catch (...)
+        {
+            NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_DBC, conn_);
+            NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env_);
+            throw;
+        }
+    }
+
+    connection_impl(const string& connection_string, long timeout)
+        : env_(0)
+        , conn_(0)
+        , connected_(false)
+        , transactions_(0)
+        , rollback_(false)
+    {
+        allocate_handle(env_, conn_);
+        try
+        {
+            connect(connection_string, timeout);
+        }
+        catch (...)
+        {
+            NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_DBC, conn_);
+            NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env_);
+            throw;
+        }
+    }
+
+    ~connection_impl() NANODBC_NOEXCEPT
+    {
+        try
+        {
+            disconnect();
+        }
+        catch (...)
+        {
+            // ignore exceptions thrown during disconnect
+        }
+        NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_DBC, conn_);
+        NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_ENV, env_);
+    }
+
+#if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_DBC_EVENT)
+    void enable_async(void* event_handle)
+    {
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            SQLSetConnectAttr,
+            rc,
+            conn_,
+            SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE,
+            (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_ON,
+            SQL_IS_INTEGER);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+
+        NANODBC_CALL_RC(
+            SQLSetConnectAttr, rc, conn_, SQL_ATTR_ASYNC_DBC_EVENT, event_handle, SQL_IS_POINTER);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+    }
+
+    void async_complete()
+    {
+        RETCODE rc, arc;
+        NANODBC_CALL_RC(SQLCompleteAsync, rc, SQL_HANDLE_DBC, conn_, &arc);
+        if (!success(rc) || !success(arc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+
+        connected_ = true;
+
+        NANODBC_CALL_RC(
+            SQLSetConnectAttr,
+            rc,
+            conn_,
+            SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE,
+            (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_OFF,
+            SQL_IS_INTEGER);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+    }
+#endif // !NANODBC_DISABLE_ASYNC && SQL_ATTR_ASYNC_DBC_EVENT
+
+    RETCODE connect(
+        const string& dsn,
+        const string& user,
+        const string& pass,
+        long timeout,
+        void* event_handle = nullptr)
+    {
+        disconnect();
+
+        RETCODE rc;
+        NANODBC_CALL_RC(SQLFreeHandle, rc, SQL_HANDLE_DBC, conn_);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+
+        NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_DBC, env_, &conn_);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(env_, SQL_HANDLE_ENV);
+
+        NANODBC_CALL_RC(
+            SQLSetConnectAttr, rc, conn_, SQL_LOGIN_TIMEOUT, (SQLPOINTER)(std::intptr_t)timeout, 0);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+
+#if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_DBC_EVENT)
+        if (event_handle != nullptr)
+            enable_async(event_handle);
+#endif
+
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLConnect),
+            rc,
+            conn_,
+            (NANODBC_SQLCHAR*)dsn.c_str(),
+            SQL_NTS,
+            !user.empty() ? (NANODBC_SQLCHAR*)user.c_str() : 0,
+            SQL_NTS,
+            !pass.empty() ? (NANODBC_SQLCHAR*)pass.c_str() : 0,
+            SQL_NTS);
+        if (!success(rc) && (event_handle == nullptr || rc != SQL_STILL_EXECUTING))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+
+        connected_ = success(rc);
+
+        return rc;
+    }
+
+    RETCODE
+    connect(const string& connection_string, long timeout, void* event_handle = nullptr)
+    {
+        disconnect();
+
+        RETCODE rc;
+        NANODBC_CALL_RC(SQLFreeHandle, rc, SQL_HANDLE_DBC, conn_);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+
+        NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_DBC, env_, &conn_);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(env_, SQL_HANDLE_ENV);
+
+        NANODBC_CALL_RC(
+            SQLSetConnectAttr, rc, conn_, SQL_LOGIN_TIMEOUT, (SQLPOINTER)(std::intptr_t)timeout, 0);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+
+#if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_DBC_EVENT)
+        if (event_handle != nullptr)
+            enable_async(event_handle);
+#endif
+
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLDriverConnect),
+            rc,
+            conn_,
+            0,
+            (NANODBC_SQLCHAR*)connection_string.c_str(),
+            SQL_NTS,
+            nullptr,
+            0,
+            nullptr,
+            SQL_DRIVER_NOPROMPT);
+        if (!success(rc) && (event_handle == nullptr || rc != SQL_STILL_EXECUTING))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+
+        connected_ = success(rc);
+
+        return rc;
+    }
+
+    bool connected() const { return connected_; }
+
+    void disconnect()
+    {
+        if (connected())
+        {
+            RETCODE rc;
+            NANODBC_CALL_RC(SQLDisconnect, rc, conn_);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+        }
+        connected_ = false;
+    }
+
+    std::size_t transactions() const { return transactions_; }
+
+    void* native_dbc_handle() const { return conn_; }
+
+    void* native_env_handle() const { return env_; }
+
+    template <class T>
+    T get_info(short info_type) const
+    {
+        return get_info_impl<T>(info_type);
+    }
+    string dbms_name() const;
+
+    string dbms_version() const;
+
+    string driver_name() const;
+
+    string database_name() const;
+
+    string catalog_name() const
+    {
+        NANODBC_SQLCHAR name[SQL_MAX_OPTION_STRING_LENGTH] = {0};
+        SQLINTEGER length(0);
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLGetConnectAttr),
+            rc,
+            conn_,
+            SQL_ATTR_CURRENT_CATALOG,
+            name,
+            sizeof(name) / sizeof(NANODBC_SQLCHAR),
+            &length);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+        return string(&name[0], &name[strarrlen(name)]);
+    }
+
+    std::size_t ref_transaction() { return ++transactions_; }
+
+    std::size_t unref_transaction()
+    {
+        if (transactions_ > 0)
+            --transactions_;
+        return transactions_;
+    }
+
+    bool rollback() const { return rollback_; }
+
+    void rollback(bool onoff) { rollback_ = onoff; }
+
+private:
+    template <class T>
+    T get_info_impl(short info_type) const;
+
+    HENV env_;
+    HDBC conn_;
+    bool connected_;
+    std::size_t transactions_;
+    bool rollback_; // if true, this connection is marked for eventual transaction rollback
+};
+
+template <class T>
+T connection::connection_impl::get_info_impl(short info_type) const
+{
+    T value;
+    RETCODE rc;
+    NANODBC_CALL_RC(NANODBC_FUNC(SQLGetInfo), rc, conn_, info_type, &value, 0, nullptr);
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+    return value;
+}
+
+template <>
+string connection::connection_impl::get_info_impl<string>(short info_type) const
+{
+    NANODBC_SQLCHAR value[1024] = {0};
+    SQLSMALLINT length(0);
+    RETCODE rc;
+    NANODBC_CALL_RC(
+        NANODBC_FUNC(SQLGetInfo),
+        rc,
+        conn_,
+        info_type,
+        value,
+        sizeof(value) / sizeof(NANODBC_SQLCHAR),
+        &length);
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
+    return string(&value[0], &value[strarrlen(value)]);
+}
+
+string connection::connection_impl::dbms_name() const
+{
+    return get_info<string>(SQL_DBMS_NAME);
+}
+
+string connection::connection_impl::dbms_version() const
+{
+    return get_info<string>(SQL_DBMS_VER);
+}
+
+string connection::connection_impl::driver_name() const
+{
+    return get_info<string>(SQL_DRIVER_NAME);
+}
+
+string connection::connection_impl::database_name() const
+{
+    return get_info<string>(SQL_DATABASE_NAME);
+}
+
+template string connection::get_info(short info_type) const;
+template unsigned short connection::get_info(short info_type) const;
+template uint32_t connection::get_info(short info_type) const;
+template uint64_t connection::get_info(short info_type) const;
+
+} // namespace nanodbc
+
+// clang-format off
+// 88888888888                                                  888    d8b                             8888888                        888
+//     888                                                      888    Y8P                               888                          888
+//     888                                                      888                                      888                          888
+//     888  888d888 8888b.  88888b.  .d8888b   8888b.   .d8888b 888888 888  .d88b.  88888b.              888   88888b.d88b.  88888b.  888
+//     888  888P"      "88b 888 "88b 88K          "88b d88P"    888    888 d88""88b 888 "88b             888   888 "888 "88b 888 "88b 888
+//     888  888    .d888888 888  888 "Y8888b. .d888888 888      888    888 888  888 888  888             888   888  888  888 888  888 888
+//     888  888    888  888 888  888      X88 888  888 Y88b.    Y88b.  888 Y88..88P 888  888             888   888  888  888 888 d88P 888
+//     888  888    "Y888888 888  888  88888P' "Y888888  "Y8888P  "Y888 888  "Y88P"  888  888           8888888 888  888  888 88888P"  888
+//                                                                                                                           888
+//                                                                                                                           888
+//                                                                                                                           888
+// MARK: Transaction Impl -
+// clang-format on
+
+namespace nanodbc
+{
+
+class transaction::transaction_impl
+{
+public:
+    transaction_impl(const transaction_impl&) = delete;
+    transaction_impl& operator=(const transaction_impl&) = delete;
+
+    transaction_impl(const class connection& conn)
+        : conn_(conn)
+        , committed_(false)
+    {
+        if (conn_.transactions() == 0 && conn_.connected())
+        {
+            RETCODE rc;
+            NANODBC_CALL_RC(
+                SQLSetConnectAttr,
+                rc,
+                conn_.native_dbc_handle(),
+                SQL_ATTR_AUTOCOMMIT,
+                (SQLPOINTER)SQL_AUTOCOMMIT_OFF,
+                SQL_IS_UINTEGER);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(conn_.native_dbc_handle(), SQL_HANDLE_DBC);
+        }
+        conn_.ref_transaction();
+    }
+
+    ~transaction_impl() NANODBC_NOEXCEPT
+    {
+        if (!committed_)
+        {
+            conn_.rollback(true);
+            conn_.unref_transaction();
+        }
+
+        if (conn_.transactions() == 0 && conn_.connected())
+        {
+            if (conn_.rollback())
+            {
+                NANODBC_CALL(SQLEndTran, SQL_HANDLE_DBC, conn_.native_dbc_handle(), SQL_ROLLBACK);
+                conn_.rollback(false);
+            }
+
+            NANODBC_CALL(
+                SQLSetConnectAttr,
+                conn_.native_dbc_handle(),
+                SQL_ATTR_AUTOCOMMIT,
+                (SQLPOINTER)SQL_AUTOCOMMIT_ON,
+                SQL_IS_UINTEGER);
+        }
+    }
+
+    void commit()
+    {
+        if (committed_)
+            return;
+        committed_ = true;
+        if (conn_.unref_transaction() == 0 && conn_.connected())
+        {
+            RETCODE rc;
+            NANODBC_CALL_RC(SQLEndTran, rc, SQL_HANDLE_DBC, conn_.native_dbc_handle(), SQL_COMMIT);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(conn_.native_dbc_handle(), SQL_HANDLE_DBC);
+        }
+    }
+
+    void rollback() NANODBC_NOEXCEPT
+    {
+        if (committed_)
+            return;
+        conn_.rollback(true);
+    }
+
+    class connection& connection() { return conn_; }
+
+    const class connection& connection() const { return conn_; }
+
+private:
+    class connection conn_;
+    bool committed_;
+};
+
+} // namespace nanodbc
+
+// clang-format off
+//  .d8888b.  888             888                                            888              8888888                        888
+// d88P  Y88b 888             888                                            888                888                          888
+// Y88b.      888             888                                            888                888                          888
+//  "Y888b.   888888  8888b.  888888 .d88b.  88888b.d88b.   .d88b.  88888b.  888888             888   88888b.d88b.  88888b.  888
+//     "Y88b. 888        "88b 888   d8P  Y8b 888 "888 "88b d8P  Y8b 888 "88b 888                888   888 "888 "88b 888 "88b 888
+//       "888 888    .d888888 888   88888888 888  888  888 88888888 888  888 888                888   888  888  888 888  888 888
+// Y88b  d88P Y88b.  888  888 Y88b. Y8b.     888  888  888 Y8b.     888  888 Y88b.              888   888  888  888 888 d88P 888
+//  "Y8888P"   "Y888 "Y888888  "Y888 "Y8888  888  888  888  "Y8888  888  888  "Y888           8888888 888  888  888 88888P"  888
+//                                                                                                                  888
+//                                                                                                                  888
+//                                                                                                                  888
+// MARK: Statement Impl -
+// clang-format on
+
+namespace nanodbc
+{
+
+class statement::statement_impl
+{
+public:
+    statement_impl(const statement_impl&) = delete;
+    statement_impl& operator=(const statement_impl&) = delete;
+
+    statement_impl()
+        : stmt_(0)
+        , open_(false)
+        , conn_()
+        , bind_len_or_null_()
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        , async_(false)
+        , async_enabled_(false)
+        , async_event_(nullptr)
+#endif
+    {
+    }
+
+    statement_impl(class connection& conn)
+        : stmt_(0)
+        , open_(false)
+        , conn_()
+        , bind_len_or_null_()
+        , string_data_()
+        , binary_data_()
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        , async_(false)
+        , async_enabled_(false)
+        , async_event_(nullptr)
+#endif
+    {
+        open(conn);
+    }
+
+    statement_impl(class connection& conn, const string& query, long timeout)
+        : stmt_(0)
+        , open_(false)
+        , conn_()
+        , bind_len_or_null_()
+        , string_data_()
+        , binary_data_()
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        , async_(false)
+        , async_enabled_(false)
+        , async_event_(nullptr)
+#endif
+    {
+        prepare(conn, query, timeout);
+    }
+
+    ~statement_impl() NANODBC_NOEXCEPT
+    {
+        if (open() && connected())
+        {
+            NANODBC_CALL(SQLCancel, stmt_);
+            reset_parameters();
+            NANODBC_CALL(SQLFreeHandle, SQL_HANDLE_STMT, stmt_);
+        }
+    }
+
+    void open(class connection& conn)
+    {
+        close();
+        RETCODE rc;
+        NANODBC_CALL_RC(SQLAllocHandle, rc, SQL_HANDLE_STMT, conn.native_dbc_handle(), &stmt_);
+        open_ = success(rc);
+        if (!open_)
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+        conn_ = conn;
+    }
+
+    bool open() const { return open_; }
+
+    bool connected() const { return conn_.connected(); }
+
+    const class connection& connection() const { return conn_; }
+
+    class connection& connection() { return conn_; }
+
+    void* native_statement_handle() const { return stmt_; }
+
+    void close()
+    {
+        if (open() && connected())
+        {
+            RETCODE rc;
+            NANODBC_CALL_RC(SQLCancel, rc, stmt_);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+
+            reset_parameters();
+
+            NANODBC_CALL_RC(SQLFreeHandle, rc, SQL_HANDLE_STMT, stmt_);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+        }
+
+        open_ = false;
+        stmt_ = 0;
+    }
+
+    void cancel()
+    {
+        RETCODE rc;
+        NANODBC_CALL_RC(SQLCancel, rc, stmt_);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+    }
+
+    void prepare(class connection& conn, const string& query, long timeout)
+    {
+        open(conn);
+        prepare(query, timeout);
+    }
+
+    RETCODE prepare(const string& query, long timeout, void* event_handle = nullptr)
+    {
+        if (!open())
+            throw programming_error("statement has no associated open connection");
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        if (event_handle == nullptr)
+            disable_async();
+        else
+            enable_async(event_handle);
+#endif
+
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLPrepare),
+            rc,
+            stmt_,
+            (NANODBC_SQLCHAR*)query.c_str(),
+            (SQLINTEGER)query.size());
+        if (!success(rc) && rc != SQL_STILL_EXECUTING)
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+
+        this->timeout(timeout);
+
+        return rc;
+    }
+
+    void timeout(long timeout)
+    {
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            SQLSetStmtAttr,
+            rc,
+            stmt_,
+            SQL_ATTR_QUERY_TIMEOUT,
+            (SQLPOINTER)(std::intptr_t)timeout,
+            0);
+
+        // some drivers don't support timeout for statements,
+        // so only raise the error if a non-default timeout was requested.
+        if (!success(rc) && (timeout != 0))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+    }
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+    void enable_async(void* event_handle)
+    {
+        RETCODE rc;
+        if (!async_enabled_)
+        {
+            NANODBC_CALL_RC(
+                SQLSetStmtAttr,
+                rc,
+                stmt_,
+                SQL_ATTR_ASYNC_ENABLE,
+                (SQLPOINTER)SQL_ASYNC_ENABLE_ON,
+                SQL_IS_INTEGER);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+            async_enabled_ = true;
+        }
+
+        if (async_event_ != event_handle)
+        {
+            NANODBC_CALL_RC(
+                SQLSetStmtAttr, rc, stmt_, SQL_ATTR_ASYNC_STMT_EVENT, event_handle, SQL_IS_POINTER);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+            async_event_ = event_handle;
+        }
+    }
+
+    void disable_async() const
+    {
+        if (async_enabled_)
+        {
+            RETCODE rc;
+            NANODBC_CALL_RC(
+                SQLSetStmtAttr,
+                rc,
+                stmt_,
+                SQL_ATTR_ASYNC_ENABLE,
+                (SQLPOINTER)SQL_ASYNC_ENABLE_OFF,
+                SQL_IS_INTEGER);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+            async_enabled_ = false;
+        }
+    }
+
+    bool async_helper(RETCODE rc)
+    {
+        if (rc == SQL_STILL_EXECUTING)
+        {
+            async_ = true;
+            return true;
+        }
+        else if (success(rc))
+        {
+            async_ = false;
+            return false;
+        }
+        else
+        {
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+        }
+    }
+
+    bool async_prepare(const string& query, void* event_handle, long timeout)
+    {
+        return async_helper(prepare(query, timeout, event_handle));
+    }
+
+    bool async_execute_direct(
+        class connection& conn,
+        void* event_handle,
+        const string& query,
+        long batch_operations,
+        long timeout,
+        statement& statement)
+    {
+        return async_helper(
+            just_execute_direct(conn, query, batch_operations, timeout, statement, event_handle));
+    }
+
+    bool
+    async_execute(void* event_handle, long batch_operations, long timeout, statement& statement)
+    {
+        return async_helper(just_execute(batch_operations, timeout, statement, event_handle));
+    }
+
+    void call_complete_async()
+    {
+        if (async_)
+        {
+            RETCODE rc, arc;
+            NANODBC_CALL_RC(SQLCompleteAsync, rc, SQL_HANDLE_STMT, stmt_, &arc);
+            if (!success(rc) || !success(arc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+        }
+    }
+
+    result complete_execute(long batch_operations, statement& statement)
+    {
+        call_complete_async();
+
+        return result(statement, batch_operations);
+    }
+
+    void complete_prepare() { call_complete_async(); }
+
+#endif
+    result execute_direct(
+        class connection& conn,
+        const string& query,
+        long batch_operations,
+        long timeout,
+        statement& statement)
+    {
+#ifdef NANODBC_ENABLE_WORKAROUND_NODATA
+        const RETCODE rc = just_execute_direct(conn, query, batch_operations, timeout, statement);
+        if (rc == SQL_NO_DATA)
+            return result();
+#else
+        just_execute_direct(conn, query, batch_operations, timeout, statement);
+#endif
+        return result(statement, batch_operations);
+    }
+
+    RETCODE just_execute_direct(
+        class connection& conn,
+        const string& query,
+        long batch_operations,
+        long timeout,
+        statement&, // statement
+        void* event_handle = nullptr)
+    {
+        open(conn);
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        if (event_handle == nullptr)
+            disable_async();
+        else
+            enable_async(event_handle);
+#endif
+
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            SQLSetStmtAttr,
+            rc,
+            stmt_,
+            SQL_ATTR_PARAMSET_SIZE,
+            (SQLPOINTER)(std::intptr_t)batch_operations,
+            0);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+
+        this->timeout(timeout);
+
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLExecDirect), rc, stmt_, (NANODBC_SQLCHAR*)query.c_str(), SQL_NTS);
+        if (!success(rc) && rc != SQL_NO_DATA && rc != SQL_STILL_EXECUTING)
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+
+        return rc;
+    }
+
+    result execute(long batch_operations, long timeout, statement& statement)
+    {
+#ifdef NANODBC_ENABLE_WORKAROUND_NODATA
+        const RETCODE rc = just_execute(batch_operations, timeout, statement);
+        if (rc == SQL_NO_DATA)
+            return result();
+#else
+        just_execute(batch_operations, timeout, statement);
+#endif
+        return result(statement, batch_operations);
+    }
+
+    RETCODE just_execute(
+        long batch_operations,
+        long timeout,
+        statement& /*statement*/,
+        void* event_handle = nullptr)
+    {
+        RETCODE rc;
+
+        if (open())
+        {
+            // The ODBC cursor must be closed before subsequent executions, as described
+            // here
+            // http://msdn.microsoft.com/en-us/library/windows/desktop/ms713584%28v=vs.85%29.aspx
+            //
+            // However, we don't necessarily want to call SQLCloseCursor() because that
+            // will cause an invalid cursor state in the case that no cursor is currently open.
+            // A better solution is to use SQLFreeStmt() with the SQL_CLOSE option, which has
+            // the same effect without the undesired limitations.
+            NANODBC_CALL_RC(SQLFreeStmt, rc, stmt_, SQL_CLOSE);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+        }
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        if (event_handle == nullptr)
+            disable_async();
+        else
+            enable_async(event_handle);
+#endif
+
+        NANODBC_CALL_RC(
+            SQLSetStmtAttr,
+            rc,
+            stmt_,
+            SQL_ATTR_PARAMSET_SIZE,
+            (SQLPOINTER)(std::intptr_t)batch_operations,
+            0);
+        if (!success(rc) && rc != SQL_NO_DATA)
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+
+        this->timeout(timeout);
+
+        NANODBC_CALL_RC(SQLExecute, rc, stmt_);
+        if (!success(rc) && rc != SQL_NO_DATA && rc != SQL_STILL_EXECUTING)
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+
+        return rc;
+    }
+
+    result procedure_columns(
+        const string& catalog,
+        const string& schema,
+        const string& procedure,
+        const string& column,
+        statement& statement)
+    {
+        if (!open())
+            throw programming_error("statement has no associated open connection");
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        disable_async();
+#endif
+
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLProcedureColumns),
+            rc,
+            stmt_,
+            (NANODBC_SQLCHAR*)(catalog.empty() ? nullptr : catalog.c_str()),
+            (catalog.empty() ? 0 : SQL_NTS),
+            (NANODBC_SQLCHAR*)(schema.empty() ? nullptr : schema.c_str()),
+            (schema.empty() ? 0 : SQL_NTS),
+            (NANODBC_SQLCHAR*)procedure.c_str(),
+            SQL_NTS,
+            (NANODBC_SQLCHAR*)(column.empty() ? nullptr : column.c_str()),
+            (column.empty() ? 0 : SQL_NTS));
+
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+
+        return result(statement, 1);
+    }
+
+    long affected_rows() const
+    {
+        SQLLEN rows;
+        RETCODE rc;
+        NANODBC_CALL_RC(SQLRowCount, rc, stmt_, &rows);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+        NANODBC_ASSERT(rows <= static_cast<SQLLEN>(std::numeric_limits<long>::max()));
+        return static_cast<long>(rows);
+    }
+
+    short columns() const
+    {
+        SQLSMALLINT cols;
+        RETCODE rc;
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        disable_async();
+#endif
+
+        NANODBC_CALL_RC(SQLNumResultCols, rc, stmt_, &cols);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+        return cols;
+    }
+
+    void reset_parameters() NANODBC_NOEXCEPT { NANODBC_CALL(SQLFreeStmt, stmt_, SQL_RESET_PARAMS); }
+
+    short parameters() const
+    {
+        SQLSMALLINT params;
+        RETCODE rc;
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        disable_async();
+#endif
+
+        NANODBC_CALL_RC(SQLNumParams, rc, stmt_, &params);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+        return params;
+    }
+
+    unsigned long parameter_size(short param_index) const
+    {
+        RETCODE rc;
+        SQLSMALLINT data_type;
+        SQLSMALLINT nullable;
+        SQLULEN parameter_size;
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        disable_async();
+#endif
+
+        NANODBC_CALL_RC(
+            SQLDescribeParam,
+            rc,
+            stmt_,
+            param_index + 1,
+            &data_type,
+            &parameter_size,
+            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()));
+        return static_cast<unsigned long>(parameter_size);
+    }
+
+    static SQLSMALLINT param_type_from_direction(param_direction direction)
+    {
+        switch (direction)
+        {
+        case PARAM_IN:
+            return SQL_PARAM_INPUT;
+            break;
+        case PARAM_OUT:
+            return SQL_PARAM_OUTPUT;
+            break;
+        case PARAM_INOUT:
+            return SQL_PARAM_INPUT_OUTPUT;
+            break;
+        case PARAM_RETURN:
+            return SQL_PARAM_OUTPUT;
+            break;
+        default:
+            NANODBC_ASSERT(false);
+            throw programming_error("unrecognized param_direction value");
+        }
+    }
+
+    // initializes bind_len_or_null_ and gets information for bind
+    void prepare_bind(
+        short param_index,
+        std::size_t batch_size,
+        param_direction direction,
+        bound_parameter& param)
+    {
+        NANODBC_ASSERT(param_index >= 0);
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        disable_async();
+#endif
+
+        RETCODE rc;
+        SQLSMALLINT nullable; // unused
+        NANODBC_CALL_RC(
+            SQLDescribeParam,
+            rc,
+            stmt_,
+            param_index + 1,
+            &param.type_,
+            &param.size_,
+            &param.scale_,
+            &nullable);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+
+        param.index_ = param_index;
+        param.iotype_ = param_type_from_direction(direction);
+
+        if (!bind_len_or_null_.count(param_index))
+            bind_len_or_null_[param_index] = std::vector<null_type>();
+        std::vector<null_type>().swap(bind_len_or_null_[param_index]);
+
+        // ODBC weirdness: this must be at least 8 elements in size
+        const std::size_t indicator_size = batch_size > 8 ? batch_size : 8;
+        bind_len_or_null_[param_index].reserve(indicator_size);
+        bind_len_or_null_[param_index].assign(indicator_size, SQL_NULL_DATA);
+
+        NANODBC_ASSERT(param.index_ == param_index);
+        NANODBC_ASSERT(param.iotype_ > 0);
+    }
+
+    // calls actual ODBC bind parameter function
+    template <class T>
+    void bind_parameter(bound_parameter const& param, bound_buffer<T>& buffer)
+    {
+        auto const buffer_size = buffer.value_size_ > 0 ? buffer.value_size_ : param.size_;
+
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            SQLBindParameter,
+            rc,
+            stmt_,               // handle
+            param.index_ + 1,    // parameter number
+            param.iotype_,       // input or output type
+            sql_ctype<T>::value, // value type
+            param.type_,         // parameter type
+            param.size_,         // column size ignored for many types, but needed for strings
+            param.scale_,        // decimal digits
+            (SQLPOINTER)buffer.values_, // parameter value
+            buffer_size,                // buffer length
+            bind_len_or_null_[param.index_].data());
+
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+    }
+
+    template <class T>
+    void bind(
+        param_direction direction,
+        short param_index,
+        T const* values,
+        std::size_t batch_size,
+        bool const* nulls = nullptr,
+        T const* null_sentry = nullptr);
+
+    // handles multiple binary values
+    void bind(
+        param_direction direction,
+        short param_index,
+        std::vector<std::vector<uint8_t>> const& values,
+        bool const* nulls = nullptr,
+        uint8_t const* null_sentry = nullptr)
+    {
+        std::size_t batch_size = values.size();
+        bound_parameter param;
+        prepare_bind(param_index, batch_size, direction, param);
+
+        size_t max_length = 0;
+        for (std::size_t i = 0; i < batch_size; ++i)
+        {
+            max_length = std::max(values[i].size(), max_length);
+        }
+        binary_data_[param_index] = std::vector<uint8_t>(batch_size * max_length, 0);
+        for (std::size_t i = 0; i < batch_size; ++i)
+        {
+            std::copy(
+                values[i].begin(),
+                values[i].end(),
+                binary_data_[param_index].data() + (i * max_length));
+        }
+
+        if (null_sentry)
+        {
+            for (std::size_t i = 0; i < batch_size; ++i)
+                if (!std::equal(values[i].begin(), values[i].end(), null_sentry))
+                {
+                    bind_len_or_null_[param_index][i] = values[i].size();
+                }
+        }
+        else if (nulls)
+        {
+            for (std::size_t i = 0; i < batch_size; ++i)
+            {
+                if (!nulls[i])
+                    bind_len_or_null_[param_index][i] = values[i].size(); // null terminated
+            }
+        }
+        else
+        {
+            for (std::size_t i = 0; i < batch_size; ++i)
+            {
+                bind_len_or_null_[param_index][i] = values[i].size();
+            }
+        }
+        bound_buffer<uint8_t> buffer(binary_data_[param_index].data(), batch_size, max_length);
+        bind_parameter(param, buffer);
+    }
+
+    void bind_strings(
+        param_direction direction,
+        short param_index,
+        string::value_type const* values,
+        std::size_t value_size,
+        std::size_t batch_size,
+        bool const* nulls = nullptr,
+        string::value_type const* null_sentry = nullptr);
+
+    void bind_strings(
+        param_direction direction,
+        short param_index,
+        std::vector<string> const& values,
+        bool const* nulls = nullptr,
+        string::value_type const* null_sentry = nullptr);
+
+    // handles multiple null values
+    void bind_null(short param_index, std::size_t batch_size)
+    {
+        bound_parameter param;
+        prepare_bind(param_index, batch_size, PARAM_IN, param);
+
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            SQLBindParameter,
+            rc,
+            stmt_,
+            param.index_ + 1, // parameter number
+            param.iotype_,    // input or output typ,
+            SQL_C_CHAR,
+            param.type_, // parameter type
+            param.size_, // column size ignored for many types, but needed for string,
+            0,           // decimal digits
+            nullptr,     // null value
+            0,           // buffe length
+            bind_len_or_null_[param.index_].data());
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+    }
+
+    // comparator for null sentry values
+    template <class T>
+    bool equals(const T& lhs, const T& rhs)
+    {
+        return lhs == rhs;
+    }
+
+private:
+    HSTMT stmt_;
+    bool open_;
+    class connection conn_;
+    std::map<short, std::vector<null_type>> bind_len_or_null_;
+    std::map<short, std::vector<string::value_type>> string_data_;
+    std::map<short, std::vector<uint8_t>> binary_data_;
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+    bool async_;                 // true if statement is currently in SQL_STILL_EXECUTING mode
+    mutable bool async_enabled_; // true if statement currently has SQL_ATTR_ASYNC_ENABLE =
+                                 // SQL_ASYNC_ENABLE_ON
+    void* async_event_;          // currently active event handle for async notifications
+#endif
+};
+
+// Supports code like: query.bind(0, std_string.c_str())
+// In this case, we need to pass nullptr to the final parameter of SQLBindParameter().
+template <>
+void statement::statement_impl::bind_parameter<string::value_type>(
+    bound_parameter const& param,
+    bound_buffer<string::value_type>& buffer)
+{
+    auto const buffer_size = buffer.value_size_ > 0 ? buffer.value_size_ : param.size_;
+
+    RETCODE rc;
+    NANODBC_CALL_RC(
+        SQLBindParameter,
+        rc,
+        stmt_,                                // handle
+        param.index_ + 1,                     // parameter number
+        param.iotype_,                        // input or output type
+        sql_ctype<string::value_type>::value, // value type
+        param.type_,                          // parameter type
+        param.size_,                // column size ignored for many types, but needed for strings
+        param.scale_,               // decimal digits
+        (SQLPOINTER)buffer.values_, // parameter value
+        buffer_size,                // buffer length
+        (buffer.size_ <= 1 ? nullptr : bind_len_or_null_[param.index_].data()));
+
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
+}
+
+template <class T>
+void statement::statement_impl::bind(
+    param_direction direction,
+    short param_index,
+    T const* values,
+    std::size_t batch_size,
+    bool const* nulls /*= nullptr*/,
+    T const* null_sentry /*= nullptr*/)
+{
+    bound_parameter param;
+    prepare_bind(param_index, batch_size, direction, param);
+
+    if (nulls || null_sentry)
+    {
+        for (std::size_t i = 0; i < batch_size; ++i)
+            if ((null_sentry && !equals(values[i], *null_sentry)) || (nulls && !nulls[i]) || !nulls)
+                bind_len_or_null_[param_index][i] = param.size_;
+    }
+    else
+    {
+        for (std::size_t i = 0; i < batch_size; ++i)
+            bind_len_or_null_[param_index][i] = param.size_;
+    }
+
+    bound_buffer<T> buffer(values, batch_size);
+    bind_parameter(param, buffer);
+}
+
+void statement::statement_impl::bind_strings(
+    param_direction direction,
+    short param_index,
+    std::vector<string> const& values,
+    bool const* nulls /*= nullptr*/,
+    string::value_type const* null_sentry /*= nullptr*/)
+{
+
+    size_t const batch_size = values.size();
+    bound_parameter param;
+    prepare_bind(param_index, batch_size, direction, param);
+
+    size_t max_length = 0;
+    for (std::size_t i = 0; i < batch_size; ++i)
+    {
+        max_length = std::max(values[i].length(), max_length);
+    }
+    // add space for null terminator
+    ++max_length;
+
+    string_data_[param_index] = std::vector<string::value_type>(batch_size * max_length, 0);
+    for (std::size_t i = 0; i < batch_size; ++i)
+    {
+        std::copy(
+            values[i].begin(),
+            values[i].end(),
+            string_data_[param_index].data() + (i * max_length));
+    }
+    bind_strings(
+        direction,
+        param_index,
+        string_data_[param_index].data(),
+        max_length,
+        batch_size,
+        nulls,
+        null_sentry);
+}
+
+void statement::statement_impl::bind_strings(
+    param_direction direction,
+    short param_index,
+    string::value_type const* values,
+    std::size_t value_size,
+    std::size_t batch_size,
+    bool const* nulls /*= nullptr*/,
+    string::value_type const* null_sentry /*= nullptr*/)
+{
+    bound_parameter param;
+    prepare_bind(param_index, batch_size, direction, param);
+
+    if (null_sentry)
+    {
+        for (std::size_t i = 0; i < batch_size; ++i)
+        {
+            const string s_lhs(values + i * value_size, values + (i + 1) * value_size);
+            const string s_rhs(null_sentry);
+#if NANODBC_ENABLE_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_rhs);
+            if (std::strncmp(narrow_lhs.c_str(), narrow_rhs.c_str(), value_size) != 0)
+                bind_len_or_null_[param_index][i] = SQL_NTS;
+#else
+            if (std::strncmp(s_lhs.c_str(), s_rhs.c_str(), value_size) != 0)
+                bind_len_or_null_[param_index][i] = SQL_NTS;
+#endif
+        }
+    }
+    else if (nulls)
+    {
+        for (std::size_t i = 0; i < batch_size; ++i)
+        {
+            if (!nulls[i])
+                bind_len_or_null_[param_index][i] = SQL_NTS; // null terminated
+        }
+    }
+    else
+    {
+        for (std::size_t i = 0; i < batch_size; ++i)
+        {
+            bind_len_or_null_[param_index][i] = SQL_NTS;
+        }
+    }
+
+    auto const buffer_length = value_size * sizeof(string::value_type);
+    bound_buffer<string::value_type> buffer(values, batch_size, buffer_length);
+    bind_parameter(param, buffer);
+}
+
+template <>
+bool statement::statement_impl::equals(const date& lhs, const date& rhs)
+{
+    return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day;
+}
+
+template <>
+bool statement::statement_impl::equals(const time& lhs, const time& rhs)
+{
+    return lhs.hour == rhs.hour && lhs.min == rhs.min && lhs.sec == rhs.sec;
+}
+
+template <>
+bool statement::statement_impl::equals(const timestamp& lhs, const timestamp& rhs)
+{
+    return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day &&
+           lhs.hour == rhs.hour && lhs.min == rhs.min && lhs.sec == rhs.sec &&
+           lhs.fract == rhs.fract;
+}
+
+} // namespace nanodbc
+
+// clang-format off
+// 8888888b.                            888 888              8888888                        888
+// 888   Y88b                           888 888                888                          888
+// 888    888                           888 888                888                          888
+// 888   d88P .d88b.  .d8888b  888  888 888 888888             888   88888b.d88b.  88888b.  888
+// 8888888P" d8P  Y8b 88K      888  888 888 888                888   888 "888 "88b 888 "88b 888
+// 888 T88b  88888888 "Y8888b. 888  888 888 888                888   888  888  888 888  888 888
+// 888  T88b Y8b.          X88 Y88b 888 888 Y88b.              888   888  888  888 888 d88P 888
+// 888   T88b "Y8888   88888P'  "Y88888 888  "Y888           8888888 888  888  888 88888P"  888
+//                                                                                 888
+//                                                                                 888
+//                                                                                 888
+// MARK: Result Impl -
+// clang-format on
+
+namespace nanodbc
+{
+
+class result::result_impl
+{
+public:
+    result_impl(const result_impl&) = delete;
+    result_impl& operator=(const result_impl&) = delete;
+
+    result_impl(statement stmt, long rowset_size)
+        : stmt_(stmt)
+        , rowset_size_(rowset_size)
+        , row_count_(0)
+        , bound_columns_(0)
+        , bound_columns_size_(0)
+        , rowset_position_(0)
+        , bound_columns_by_name_()
+        , at_end_(false)
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        , async_(false)
+#endif
+    {
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            SQLSetStmtAttr,
+            rc,
+            stmt_.native_statement_handle(),
+            SQL_ATTR_ROW_ARRAY_SIZE,
+            (SQLPOINTER)(std::intptr_t)rowset_size_,
+            0);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+
+        NANODBC_CALL_RC(
+            SQLSetStmtAttr,
+            rc,
+            stmt_.native_statement_handle(),
+            SQL_ATTR_ROWS_FETCHED_PTR,
+            &row_count_,
+            0);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+
+        auto_bind();
+    }
+
+    ~result_impl() NANODBC_NOEXCEPT { cleanup_bound_columns(); }
+
+    void* native_statement_handle() const { return stmt_.native_statement_handle(); }
+
+    long rowset_size() const { return rowset_size_; }
+
+    long affected_rows() const { return stmt_.affected_rows(); }
+
+    long rows() const NANODBC_NOEXCEPT
+    {
+        NANODBC_ASSERT(row_count_ <= static_cast<SQLULEN>(std::numeric_limits<long>::max()));
+        return static_cast<long>(row_count_);
+    }
+
+    short columns() const { return stmt_.columns(); }
+
+    bool first()
+    {
+        rowset_position_ = 0;
+        return fetch(0, SQL_FETCH_FIRST);
+    }
+
+    bool last()
+    {
+        rowset_position_ = 0;
+        return fetch(0, SQL_FETCH_LAST);
+    }
+
+    bool next(void* event_handle = nullptr)
+    {
+        if (rows() && ++rowset_position_ < rowset_size_)
+            return rowset_position_ < rows();
+        rowset_position_ = 0;
+        return fetch(0, SQL_FETCH_NEXT, event_handle);
+    }
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+    bool async_next(void* event_handle)
+    {
+        async_ = next(event_handle);
+        return async_;
+    }
+
+    bool complete_next()
+    {
+        if (async_)
+        {
+            RETCODE rc, arc;
+            NANODBC_CALL_RC(
+                SQLCompleteAsync, rc, SQL_HANDLE_STMT, stmt_.native_statement_handle(), &arc);
+            if (arc == SQL_NO_DATA)
+            {
+                at_end_ = true;
+                return false;
+            }
+            if (!success(rc) || !success(arc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+            async_ = false;
+        }
+        return !at_end_;
+    }
+#endif
+
+    bool prior()
+    {
+        if (rows() && --rowset_position_ >= 0)
+            return true;
+        rowset_position_ = 0;
+        return fetch(0, SQL_FETCH_PRIOR);
+    }
+
+    bool move(long row)
+    {
+        rowset_position_ = 0;
+        return fetch(row, SQL_FETCH_ABSOLUTE);
+    }
+
+    bool skip(long rows)
+    {
+        rowset_position_ += rows;
+        if (this->rows() && rowset_position_ < rowset_size_)
+            return rowset_position_ < this->rows();
+        rowset_position_ = 0;
+        return fetch(rows, SQL_FETCH_RELATIVE);
+    }
+
+    unsigned long position() const
+    {
+        SQLULEN pos = 0; // necessary to initialize to 0
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            SQLGetStmtAttr,
+            rc,
+            stmt_.native_statement_handle(),
+            SQL_ATTR_ROW_NUMBER,
+            &pos,
+            SQL_IS_UINTEGER,
+            0);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+
+        // MSDN (https://msdn.microsoft.com/en-us/library/ms712631.aspx):
+        // If the number of the current row cannot be determined or
+        // there is no current row, the driver returns 0.
+        // Otherwise, valid row number is returned, starting at 1.
+        //
+        // NOTE: We try to address incorrect implementation in some drivers (e.g. SQLite ODBC)
+        // which instead of 0 return SQL_ROW_NUMBER_UNKNOWN(-2) .
+        if (pos == 0 || pos == static_cast<SQLULEN>(SQL_ROW_NUMBER_UNKNOWN))
+            return 0;
+
+        NANODBC_ASSERT(pos < static_cast<SQLULEN>(std::numeric_limits<unsigned long>::max()));
+        return static_cast<unsigned long>(pos) + rowset_position_;
+    }
+
+    bool at_end() const NANODBC_NOEXCEPT
+    {
+        if (at_end_)
+            return true;
+        SQLULEN pos = 0; // necessary to initialize to 0
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            SQLGetStmtAttr,
+            rc,
+            stmt_.native_statement_handle(),
+            SQL_ATTR_ROW_NUMBER,
+            &pos,
+            SQL_IS_UINTEGER,
+            0);
+        return (!success(rc) || rows() < 0 || pos - 1 > static_cast<unsigned long>(rows()));
+    }
+
+    bool is_null(short column) const
+    {
+        if (column >= bound_columns_size_)
+            throw index_range_error();
+        bound_column& col = bound_columns_[column];
+        if (rowset_position_ >= rows())
+            throw index_range_error();
+        return col.cbdata_[static_cast<size_t>(rowset_position_)] == SQL_NULL_DATA;
+    }
+
+    bool is_null(const string& column_name) const
+    {
+        const short column = this->column(column_name);
+        return is_null(column);
+    }
+
+    short column(const string& column_name) const
+    {
+        typedef std::map<string, bound_column*>::const_iterator iter;
+        iter i = bound_columns_by_name_.find(column_name);
+        if (i == bound_columns_by_name_.end())
+            throw index_range_error();
+        return i->second->column_;
+    }
+
+    string column_name(short column) const
+    {
+        if (column >= bound_columns_size_)
+            throw index_range_error();
+        return bound_columns_[column].name_;
+    }
+
+    long column_size(short column) const
+    {
+        if (column >= bound_columns_size_)
+            throw index_range_error();
+        bound_column& col = bound_columns_[column];
+        NANODBC_ASSERT(col.sqlsize_ <= static_cast<SQLULEN>(std::numeric_limits<long>::max()));
+        return static_cast<long>(col.sqlsize_);
+    }
+
+    int column_size(const string& column_name) const
+    {
+        const short column = this->column(column_name);
+        return column_size(column);
+    }
+
+    int column_decimal_digits(short column) const
+    {
+        if (column >= bound_columns_size_)
+            throw index_range_error();
+        bound_column& col = bound_columns_[column];
+        return col.scale_;
+    }
+
+    int column_decimal_digits(const string& column_name) const
+    {
+        const short column = this->column(column_name);
+        bound_column& col = bound_columns_[column];
+        return col.scale_;
+    }
+
+    int column_datatype(short column) const
+    {
+        if (column >= bound_columns_size_)
+            throw index_range_error();
+        bound_column& col = bound_columns_[column];
+        return col.sqltype_;
+    }
+
+    int column_datatype(const string& column_name) const
+    {
+        const short column = this->column(column_name);
+        bound_column& col = bound_columns_[column];
+        return col.sqltype_;
+    }
+
+    string column_datatype_name(short column) const
+    {
+        if (column >= bound_columns_size_)
+            throw index_range_error();
+
+        NANODBC_SQLCHAR type_name[256] = {0};
+        SQLSMALLINT len = 0; // total number of bytes
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            SQLColAttribute,
+            rc,
+            stmt_.native_statement_handle(),
+            column + 1,
+            SQL_DESC_TYPE_NAME,
+            type_name,
+            sizeof(type_name) / sizeof(NANODBC_SQLCHAR),
+            &len,
+            nullptr);
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+
+        NANODBC_ASSERT(len % sizeof(NANODBC_SQLCHAR) == 0);
+        len = len / sizeof(NANODBC_SQLCHAR);
+        return string(type_name, type_name + len);
+    }
+
+    string column_datatype_name(const string& column_name) const
+    {
+        return column_datatype_name(this->column(column_name));
+    }
+
+    int column_c_datatype(short column) const
+    {
+        if (column >= bound_columns_size_)
+            throw index_range_error();
+        bound_column& col = bound_columns_[column];
+        return col.ctype_;
+    }
+
+    int column_c_datatype(const string& column_name) const
+    {
+        const short column = this->column(column_name);
+        bound_column& col = bound_columns_[column];
+        return col.ctype_;
+    }
+
+    bool next_result()
+    {
+        RETCODE rc;
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        stmt_.disable_async();
+#endif
+
+        NANODBC_CALL_RC(SQLMoreResults, rc, stmt_.native_statement_handle());
+        if (rc == SQL_NO_DATA)
+            return false;
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+        auto_bind();
+        return true;
+    }
+
+    template <class T>
+    void get_ref(short column, T& result) const
+    {
+        if (column >= bound_columns_size_)
+            throw index_range_error();
+        if (is_null(column))
+            throw null_access_error();
+        get_ref_impl<T>(column, result);
+    }
+
+    template <class T>
+    void get_ref(short column, const T& fallback, T& result) const
+    {
+        if (column >= bound_columns_size_)
+            throw index_range_error();
+        if (is_null(column))
+        {
+            result = fallback;
+            return;
+        }
+        get_ref_impl<T>(column, result);
+    }
+
+    template <class T>
+    void get_ref(const string& column_name, T& result) const
+    {
+        const short column = this->column(column_name);
+        if (is_null(column))
+            throw null_access_error();
+        get_ref_impl<T>(column, result);
+    }
+
+    template <class T>
+    void get_ref(const string& column_name, const T& fallback, T& result) const
+    {
+        const short column = this->column(column_name);
+        if (is_null(column))
+        {
+            result = fallback;
+            return;
+        }
+        get_ref_impl<T>(column, result);
+    }
+
+    template <class T>
+    T get(short column) const
+    {
+        T result;
+        get_ref(column, result);
+        return result;
+    }
+
+    template <class T>
+    T get(short column, const T& fallback) const
+    {
+        T result;
+        get_ref(column, fallback, result);
+        return result;
+    }
+
+    template <class T>
+    T get(const string& column_name) const
+    {
+        T result;
+        get_ref(column_name, result);
+        return result;
+    }
+
+    template <class T>
+    T get(const string& column_name, const T& fallback) const
+    {
+        T result;
+        get_ref(column_name, fallback, result);
+        return result;
+    }
+
+private:
+    template <class T>
+    void get_ref_impl(short column, T& result) const;
+
+    void before_move() NANODBC_NOEXCEPT
+    {
+        for (short i = 0; i < bound_columns_size_; ++i)
+        {
+            bound_column& col = bound_columns_[i];
+            for (std::size_t j = 0; j < static_cast<size_t>(rowset_size_); ++j)
+                col.cbdata_[j] = 0;
+            if (col.blob_ && col.pdata_)
+                release_bound_resources(i);
+        }
+    }
+
+    void release_bound_resources(short column) NANODBC_NOEXCEPT
+    {
+        NANODBC_ASSERT(column < bound_columns_size_);
+        bound_column& col = bound_columns_[column];
+        delete[] col.pdata_;
+        col.pdata_ = 0;
+        col.clen_ = 0;
+    }
+
+    void cleanup_bound_columns() NANODBC_NOEXCEPT
+    {
+        before_move();
+        delete[] bound_columns_;
+        bound_columns_ = nullptr;
+        bound_columns_size_ = 0;
+        bound_columns_by_name_.clear();
+    }
+
+    // If event_handle is specified, fetch returns true iff the statement is still executing
+    bool fetch(long rows, SQLUSMALLINT orientation, void* event_handle = nullptr)
+    {
+        before_move();
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        if (event_handle == nullptr)
+            stmt_.disable_async();
+        else
+            stmt_.enable_async(event_handle);
+#endif // !NANODBC_DISABLE_ASYNC && SQL_ATTR_ASYNC_STMT_EVENT && SQL_API_SQLCOMPLETEASYNC
+
+        RETCODE rc;
+        NANODBC_CALL_RC(SQLFetchScroll, rc, stmt_.native_statement_handle(), orientation, rows);
+        if (rc == SQL_NO_DATA)
+        {
+            at_end_ = true;
+            return false;
+        }
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        if (event_handle != nullptr)
+            return rc == SQL_STILL_EXECUTING;
+#endif
+        if (!success(rc))
+            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+        return true;
+    }
+
+    void auto_bind()
+    {
+        cleanup_bound_columns();
+
+        const short n_columns = columns();
+        if (n_columns < 1)
+            return;
+
+        NANODBC_ASSERT(!bound_columns_);
+        NANODBC_ASSERT(!bound_columns_size_);
+        bound_columns_ = new bound_column[n_columns];
+        bound_columns_size_ = n_columns;
+
+        RETCODE rc;
+        NANODBC_SQLCHAR column_name[1024];
+        SQLSMALLINT sqltype = 0, scale = 0, nullable = 0, len = 0;
+        SQLULEN sqlsize = 0;
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+        stmt_.disable_async();
+#endif
+
+        for (SQLSMALLINT i = 0; i < n_columns; ++i)
+        {
+            NANODBC_CALL_RC(
+                NANODBC_FUNC(SQLDescribeCol),
+                rc,
+                stmt_.native_statement_handle(),
+                i + 1,
+                (NANODBC_SQLCHAR*)column_name,
+                sizeof(column_name) / sizeof(NANODBC_SQLCHAR),
+                &len,
+                &sqltype,
+                &sqlsize,
+                &scale,
+                &nullable);
+            if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+
+            // Adjust the sqlsize parameter in case of "unlimited" data (varchar(max),
+            // nvarchar(max)).
+            bool is_blob = false;
+
+            if (sqlsize == 0)
+            {
+                switch (sqltype)
+                {
+                case SQL_VARCHAR:
+                case SQL_WVARCHAR:
+                {
+                    // Divide in half, due to sqlsize being 32-bit in Win32 (and 64-bit in x64)
+                    // sqlsize = std::numeric_limits<int32_t>::max() / 2 - 1;
+                    is_blob = true;
+                }
+                }
+            }
+
+            bound_column& col = bound_columns_[i];
+            col.name_ = reinterpret_cast<string::value_type*>(column_name);
+            col.column_ = i;
+            col.sqltype_ = sqltype;
+            col.sqlsize_ = sqlsize;
+            col.scale_ = scale;
+            bound_columns_by_name_[col.name_] = &col;
+
+            using namespace std; // if int64_t is in std namespace (in c++11)
+            switch (col.sqltype_)
+            {
+            case SQL_BIT:
+            case SQL_TINYINT:
+            case SQL_SMALLINT:
+            case SQL_INTEGER:
+            case SQL_BIGINT:
+                col.ctype_ = SQL_C_SBIGINT;
+                col.clen_ = sizeof(int64_t);
+                break;
+            case SQL_DOUBLE:
+            case SQL_FLOAT:
+            case SQL_REAL:
+                col.ctype_ = SQL_C_DOUBLE;
+                col.clen_ = sizeof(double);
+                break;
+            case SQL_DECIMAL:
+            case SQL_NUMERIC:
+                col.ctype_ = SQL_C_CHAR;
+                // SQL column size defines number of digits without the decimal mark
+                // and without minus sign which may also occur.
+                // We need to adjust buffer length allow space for null-termination character
+                // as well as the fractional part separator and the minus sign.
+                col.clen_ = (col.sqlsize_ + 1 + 1 + 1) * sizeof(SQLCHAR);
+                break;
+            case SQL_DATE:
+            case SQL_TYPE_DATE:
+                col.ctype_ = SQL_C_DATE;
+                col.clen_ = sizeof(date);
+                break;
+            case SQL_TIME:
+            case SQL_TYPE_TIME:
+            case SQL_SS_TIME2:
+                col.ctype_ = SQL_C_TIME;
+                col.clen_ = sizeof(time);
+                break;
+            case SQL_TIMESTAMP:
+            case SQL_TYPE_TIMESTAMP:
+            case SQL_SS_TIMESTAMPOFFSET:
+                col.ctype_ = SQL_C_TIMESTAMP;
+                col.clen_ = sizeof(timestamp);
+                break;
+            case SQL_CHAR:
+            case SQL_VARCHAR:
+            case SQL_NVARCHAR:
+                col.ctype_ = SQL_C_CHAR;
+                col.clen_ = (col.sqlsize_ + 1) * sizeof(SQLCHAR);
+                if (is_blob)
+                {
+                    col.clen_ = 0;
+                    col.blob_ = true;
+                }
+                break;
+            case SQL_WCHAR:
+            case SQL_WVARCHAR:
+                col.ctype_ = SQL_C_WCHAR;
+                col.clen_ = (col.sqlsize_ + 1) * sizeof(SQLWCHAR);
+                if (is_blob)
+                {
+                    col.clen_ = 0;
+                    col.blob_ = true;
+                }
+                break;
+            case SQL_LONGVARCHAR:
+                col.ctype_ = SQL_C_CHAR;
+                col.blob_ = true;
+                col.clen_ = 0;
+                break;
+            case SQL_BINARY:
+            case SQL_VARBINARY:
+            case SQL_LONGVARBINARY:
+            case SQL_SS_UDT: // MSDN: Essentially, UDT is a varbinary type with additional metadata.
+                col.ctype_ = SQL_C_BINARY;
+                col.blob_ = true;
+                col.clen_ = 0;
+                break;
+            default:
+                col.ctype_ = sql_ctype<string>::value;
+                col.clen_ = 128;
+                break;
+            }
+        }
+
+        for (SQLSMALLINT i = 0; i < n_columns; ++i)
+        {
+            bound_column& col = bound_columns_[i];
+            col.cbdata_ = new null_type[static_cast<size_t>(rowset_size_)];
+            if (col.blob_)
+            {
+                NANODBC_CALL_RC(
+                    SQLBindCol,
+                    rc,
+                    stmt_.native_statement_handle(),
+                    i + 1,
+                    col.ctype_,
+                    0,
+                    0,
+                    col.cbdata_);
+                if (!success(rc))
+                    NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+            }
+            else
+            {
+                col.pdata_ = new char[rowset_size_ * col.clen_];
+                NANODBC_CALL_RC(
+                    SQLBindCol,
+                    rc,
+                    stmt_.native_statement_handle(),
+                    i + 1,        // ColumnNumber
+                    col.ctype_,   // TargetType
+                    col.pdata_,   // TargetValuePtr
+                    col.clen_,    // BufferLength
+                    col.cbdata_); // StrLen_or_Ind
+                if (!success(rc))
+                    NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+            }
+        }
+    }
+
+private:
+    statement stmt_;
+    const long rowset_size_;
+    SQLULEN row_count_;
+    bound_column* bound_columns_;
+    short bound_columns_size_;
+    long rowset_position_;
+    std::map<string, bound_column*> bound_columns_by_name_;
+    bool at_end_;
+#if defined(NANODBC_DO_ASYNC_IMPL)
+    bool async_; // true if statement is currently in SQL_STILL_EXECUTING mode
+#endif
+};
+
+template <>
+inline void result::result_impl::get_ref_impl<date>(short column, date& result) const
+{
+    bound_column& col = bound_columns_[column];
+    switch (col.ctype_)
+    {
+    case SQL_C_DATE:
+        result = *reinterpret_cast<date*>(col.pdata_ + rowset_position_ * col.clen_);
+        return;
+    case SQL_C_TIMESTAMP:
+    {
+        timestamp stamp = *reinterpret_cast<timestamp*>(col.pdata_ + rowset_position_ * col.clen_);
+        date d = {stamp.year, stamp.month, stamp.day};
+        result = d;
+        return;
+    }
+    }
+    throw type_incompatible_error();
+}
+
+template <>
+inline void result::result_impl::get_ref_impl<time>(short column, time& result) const
+{
+    bound_column& col = bound_columns_[column];
+    switch (col.ctype_)
+    {
+    case SQL_C_TIME:
+        result = *reinterpret_cast<time*>(col.pdata_ + rowset_position_ * col.clen_);
+        return;
+    case SQL_C_TIMESTAMP:
+    {
+        timestamp stamp = *reinterpret_cast<timestamp*>(col.pdata_ + rowset_position_ * col.clen_);
+        time t = {stamp.hour, stamp.min, stamp.sec};
+        result = t;
+        return;
+    }
+    }
+    throw type_incompatible_error();
+}
+
+template <>
+inline void result::result_impl::get_ref_impl<timestamp>(short column, timestamp& result) const
+{
+    bound_column& col = bound_columns_[column];
+    switch (col.ctype_)
+    {
+    case SQL_C_DATE:
+    {
+        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 = *reinterpret_cast<timestamp*>(col.pdata_ + rowset_position_ * col.clen_);
+        return;
+    }
+    throw type_incompatible_error();
+}
+
+template <>
+inline void result::result_impl::get_ref_impl<string>(short column, string& result) const
+{
+    bound_column& col = bound_columns_[column];
+    const SQLULEN column_size = col.sqlsize_;
+
+    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 wide_string
+            std::string out;
+            // The length of the data available to return, decreasing with subsequent SQLGetData
+            // calls.
+            // But, NOT the length of data returned into the buffer (apart from the final call).
+            SQLLEN ValueLenOrInd;
+            SQLRETURN rc;
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+            stmt_.disable_async();
+#endif
+
+            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
+                    col.ctype_,      // TargetType
+                    buffer,          // TargetValuePtr
+                    buffer_size,     // BufferLength
+                    &ValueLenOrInd); // StrLen_or_IndPtr
+                if (ValueLenOrInd > 0)
+                    out.append(
+                        buffer,
+                        std::min<std::size_t>(
+                            ValueLenOrInd,
+                            col.ctype_ == SQL_C_BINARY ? buffer_size : buffer_size - 1));
+                else if (ValueLenOrInd == SQL_NULL_DATA)
+                    col.cbdata_[static_cast<size_t>(rowset_position_)] = (SQLINTEGER)SQL_NULL_DATA;
+                // 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 if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+        }
+        else
+        {
+            const char* s = col.pdata_ + rowset_position_ * col.clen_;
+            const std::string::size_type str_size = std::strlen(s);
+            result.assign(s, s + str_size);
+        }
+        return;
+    }
+
+    case SQL_C_WCHAR:
+    {
+        if (col.blob_)
+        {
+            // Input is always wide_string, output might be std::string or wide_string.
+            // Use a string builder to build the output string.
+            wide_string out;
+            // The length of the data available to return, decreasing with subsequent SQLGetData
+            // calls.
+            // But, NOT the length of data returned into the buffer (apart from the final call).
+            SQLLEN ValueLenOrInd;
+            SQLRETURN rc;
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+            stmt_.disable_async();
+#endif
+
+            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
+                    col.ctype_,      // TargetType
+                    buffer,          // TargetValuePtr
+                    buffer_size,     // BufferLength
+                    &ValueLenOrInd); // StrLen_or_IndPtr
+                if (ValueLenOrInd > 0)
+                    out.append(
+                        buffer,
+                        std::min<std::size_t>(
+                            ValueLenOrInd / sizeof(wide_char_t),
+                            (buffer_size / sizeof(wide_char_t)) - 1));
+                else if (ValueLenOrInd == SQL_NULL_DATA)
+                    col.cbdata_[static_cast<std::size_t>(rowset_position_)] = (SQLINTEGER)SQL_NULL_DATA;
+                // 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 if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+            ;
+        }
+        else
+        {
+            // Type is unicode in the database, convert if necessary
+            const SQLWCHAR* s =
+                reinterpret_cast<SQLWCHAR*>(col.pdata_ + rowset_position_ * col.clen_);
+            const string::size_type str_size = col.cbdata_[static_cast<size_t>(rowset_position_)] / sizeof(SQLWCHAR);
+            wide_string temp(s, s + str_size);
+            convert(temp, result);
+        }
+        return;
+    }
+
+    case SQL_C_GUID:
+    {
+        const char* s = col.pdata_ + rowset_position_ * col.clen_;
+        result.assign(s, s + column_size);
+        return;
+    }
+
+    case SQL_C_LONG:
+    {
+        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 int32_t data = *reinterpret_cast<int32_t*>(col.pdata_ + rowset_position_ * col.clen_);
+        const int bytes =
+            std::snprintf(const_cast<char*>(buffer.data()), column_size + 1, "%d", data);
+        if (bytes == -1)
+            throw type_incompatible_error();
+        else if ((SQLULEN)bytes < column_size)
+            buffer.resize(static_cast<size_t>(bytes));
+        buffer.resize(std::strlen(buffer.data())); // drop any trailing nulls
+        result.reserve(buffer.size() * sizeof(string::value_type));
+        convert(buffer, result);
+        return;
+    }
+
+    case SQL_C_SBIGINT:
+    {
+        using namespace std; // in case intmax_t is in namespace std
+        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 + 1, "%jd", data);
+        if (bytes == -1)
+            throw type_incompatible_error();
+        else if ((SQLULEN)bytes < column_size)
+            buffer.resize(static_cast<size_t>(bytes));
+        buffer.resize(std::strlen(buffer.data())); // drop any trailing nulls
+        result.reserve(buffer.size() * sizeof(string::value_type));
+        convert(buffer, result);
+        return;
+    }
+
+    case SQL_C_FLOAT:
+    {
+        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 + 1, "%f", data);
+        if (bytes == -1)
+            throw type_incompatible_error();
+        else if ((SQLULEN)bytes < column_size)
+            buffer.resize(static_cast<std::size_t>(bytes));
+        buffer.resize(std::strlen(buffer.data())); // drop any trailing nulls
+        result.reserve(buffer.size() * sizeof(string::value_type));
+        convert(buffer, result);
+        return;
+    }
+
+    case SQL_C_DOUBLE:
+    {
+        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 + 1,
+            "%.*lf",    // restrict the number of digits
+            col.scale_, // number of digits after the decimal point
+            data);
+        if (bytes == -1)
+            throw type_incompatible_error();
+        else if ((SQLULEN)bytes < column_size)
+            buffer.resize(static_cast<size_t>(bytes));
+        buffer.resize(std::strlen(buffer.data())); // drop any trailing nulls
+        result.reserve(buffer.size() * sizeof(string::value_type));
+        convert(buffer, result);
+        return;
+    }
+
+    case SQL_C_DATE:
+    {
+        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, nullptr);
+        std::setlocale(LC_TIME, "");
+        char date_str[512];
+        std::strftime(date_str, sizeof(date_str), "%Y-%m-%d", &st);
+        std::setlocale(LC_TIME, old_lc_time);
+        convert(date_str, result);
+        return;
+    }
+
+    case SQL_C_TIME:
+    {
+        const time t = *reinterpret_cast<time*>(col.pdata_ + rowset_position_ * col.clen_);
+        std::tm st = {0};
+        st.tm_hour = t.hour;
+        st.tm_min = t.min;
+        st.tm_sec = t.sec;
+        char* old_lc_time = std::setlocale(LC_TIME, nullptr);
+        std::setlocale(LC_TIME, "");
+        char date_str[512];
+        std::strftime(date_str, sizeof(date_str), "%H:%M:%S", &st);
+        std::setlocale(LC_TIME, old_lc_time);
+        convert(date_str, result);
+        return;
+    }
+
+    case SQL_C_TIMESTAMP:
+    {
+        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;
+        st.tm_mday = stamp.day;
+        st.tm_hour = stamp.hour;
+        st.tm_min = stamp.min;
+        st.tm_sec = stamp.sec;
+        char* old_lc_time = std::setlocale(LC_TIME, nullptr);
+        std::setlocale(LC_TIME, "");
+        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);
+        convert(date_str, result);
+        return;
+    }
+    }
+    throw type_incompatible_error();
+}
+
+template <>
+inline void result::result_impl::get_ref_impl<std::vector<std::uint8_t>>(
+    short column,
+    std::vector<std::uint8_t>& result) const
+{
+    bound_column& col = bound_columns_[column];
+    const SQLULEN column_size = col.sqlsize_;
+
+    switch (col.ctype_)
+    {
+    case SQL_C_BINARY:
+    {
+        if (col.blob_)
+        {
+            // Input and output is always array of bytes.
+            std::vector<std::uint8_t> out;
+            std::uint8_t buffer[1024] = {0};
+            std::size_t const buffer_size = sizeof(buffer);
+            // The length of the data available to return, decreasing with subsequent SQLGetData
+            // calls.
+            // But, NOT the length of data returned into the buffer (apart from the final call).
+            SQLLEN ValueLenOrInd;
+            SQLRETURN rc;
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+            stmt_.disable_async();
+#endif
+
+            void* handle = native_statement_handle();
+            do
+            {
+                NANODBC_CALL_RC(
+                    SQLGetData,
+                    rc,
+                    handle,          // StatementHandle
+                    column + 1,      // Col_or_Param_Num
+                    SQL_C_BINARY,    // TargetType
+                    buffer,          // TargetValuePtr
+                    buffer_size,     // BufferLength
+                    &ValueLenOrInd); // StrLen_or_IndPtr
+                if (ValueLenOrInd > 0)
+                {
+                    auto const buffer_size_filled =
+                        std::min<std::size_t>(ValueLenOrInd, buffer_size);
+                    NANODBC_ASSERT(buffer_size_filled <= buffer_size);
+                    out.insert(std::end(out), buffer, buffer + buffer_size_filled);
+                }
+                else if (ValueLenOrInd == SQL_NULL_DATA)
+                    col.cbdata_[static_cast<size_t>(rowset_position_)] = (SQLINTEGER)SQL_NULL_DATA;
+                // 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)
+                result = std::move(out);
+            else if (!success(rc))
+                NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
+        }
+        else
+        {
+            // Read fixed-length binary data
+            const char* s = col.pdata_ + rowset_position_ * col.clen_;
+            result.assign(s, s + column_size);
+        }
+        return;
+    }
+    }
+    throw type_incompatible_error();
+}
+
+template <class T>
+void result::result_impl::get_ref_impl(short column, T& result) const
+{
+    bound_column& col = bound_columns_[column];
+    using namespace std; // if int64_t is in std namespace (in c++11)
+    const char* s = col.pdata_ + rowset_position_ * col.clen_;
+    switch (col.ctype_)
+    {
+    case SQL_C_CHAR:
+        result = (T) * (char*)(s);
+        return;
+    case SQL_C_SSHORT:
+        result = (T) * (short*)(s);
+        return;
+    case SQL_C_USHORT:
+        result = (T) * (unsigned short*)(s);
+        return;
+    case SQL_C_LONG:
+        result = (T) * (int32_t*)(s);
+        return;
+    case SQL_C_SLONG:
+        result = (T) * (int32_t*)(s);
+        return;
+    case SQL_C_ULONG:
+        result = (T) * (uint32_t*)(s);
+        return;
+    case SQL_C_FLOAT:
+        result = (T) * (float*)(s);
+        return;
+    case SQL_C_DOUBLE:
+        result = (T) * (double*)(s);
+        return;
+    case SQL_C_SBIGINT:
+        result = (T) * (int64_t*)(s);
+        return;
+    case SQL_C_UBIGINT:
+        result = (T) * (uint64_t*)(s);
+        return;
+    }
+    throw type_incompatible_error();
+}
+
+} // namespace nanodbc
+
+// clang-format off
+// 8888888888                            8888888888                         888    d8b
+// 888                                   888                                888    Y8P
+// 888                                   888                                888
+// 8888888 888d888 .d88b.   .d88b.       8888888 888  888 88888b.   .d8888b 888888 888  .d88b.  88888b.  .d8888b
+// 888     888P"  d8P  Y8b d8P  Y8b      888     888  888 888 "88b d88P"    888    888 d88""88b 888 "88b 88K
+// 888     888    88888888 88888888      888     888  888 888  888 888      888    888 888  888 888  888 "Y8888b.
+// 888     888    Y8b.     Y8b.          888     Y88b 888 888  888 Y88b.    Y88b.  888 Y88..88P 888  888      X88
+// 888     888     "Y8888   "Y8888       888      "Y88888 888  888  "Y8888P  "Y888 888  "Y88P"  888  888  88888P'
+// MARK: Free Functions -
+// clang-format on
+
+namespace nanodbc
+{
+
+std::list<driver> list_drivers()
+{
+    NANODBC_SQLCHAR descr[1024] = {0};
+    NANODBC_SQLCHAR attrs[1024] = {0};
+    SQLSMALLINT descr_len_ret{0};
+    SQLSMALLINT attrs_len_ret{0};
+    SQLUSMALLINT direction{SQL_FETCH_FIRST};
+
+    HENV env{0};
+    allocate_environment_handle(env);
+
+    std::list<driver> drivers;
+    RETCODE rc{SQL_SUCCESS};
+    do
+    {
+        NANODBC_ASSERT(env);
+        NANODBC_CALL_RC(
+            NANODBC_FUNC(SQLDrivers),
+            rc,
+            env,
+            direction,                               // EnvironmentHandle
+            descr,                                   // DriverDescription
+            sizeof(descr) / sizeof(NANODBC_SQLCHAR), // BufferLength1
+            &descr_len_ret,                          // DescriptionLengthPtr
+            attrs,                                   // DriverAttributes
+            sizeof(attrs) / sizeof(NANODBC_SQLCHAR), // BufferLength2
+            &attrs_len_ret);                         // AttributesLengthPtr
+
+        if (rc == SQL_SUCCESS)
+        {
+            using char_type = string::value_type;
+            static_assert(
+                sizeof(NANODBC_SQLCHAR) == sizeof(char_type),
+                "incompatible SQLCHAR and string::value_type");
+
+            driver drv;
+            drv.name = string(&descr[0], &descr[strarrlen(descr)]);
+
+            // Split "Key1=Value1\0Key2=Value2\0\0" into list of key-value pairs
+            auto beg = &attrs[0];
+            auto const end = &attrs[attrs_len_ret];
+            auto pair_end = end;
+            while ((pair_end = std::find(beg, end, NANODBC_TEXT('\0'))) != end)
+            {
+                auto const eq_pos = std::find(beg, pair_end, NANODBC_TEXT('='));
+                if (eq_pos == end)
+                    break;
+
+                driver::attribute attr{{beg, eq_pos}, {eq_pos + 1, pair_end}};
+                drv.attributes.push_back(std::move(attr));
+                beg = pair_end + 1;
+            }
+
+            drivers.push_back(std::move(drv));
+
+            direction = SQL_FETCH_NEXT;
+        }
+        else
+        {
+            if (rc != SQL_NO_DATA)
+                NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
+        }
+    } while (success(rc));
+
+    return drivers;
+}
+
+result execute(connection& conn, const string& query, long batch_operations, long timeout)
+{
+    class statement statement;
+    return statement.execute_direct(conn, query, batch_operations, timeout);
+}
+
+void just_execute(connection& conn, const string& query, long batch_operations, long timeout)
+{
+    class statement statement;
+    statement.just_execute_direct(conn, query, batch_operations, timeout);
+}
+
+result execute(statement& stmt, long batch_operations)
+{
+    return stmt.execute(batch_operations);
+}
+
+void just_execute(statement& stmt, long batch_operations)
+{
+    return stmt.just_execute(batch_operations);
+}
+
+result transact(statement& stmt, long batch_operations)
+{
+    class transaction transaction(stmt.connection());
+    result rvalue = stmt.execute(batch_operations);
+    transaction.commit();
+    return rvalue;
+}
+
+void just_transact(statement& stmt, long batch_operations)
+{
+    class transaction transaction(stmt.connection());
+    stmt.just_execute(batch_operations);
+    transaction.commit();
+}
+
+void prepare(statement& stmt, const string& query, long timeout)
+{
+    stmt.prepare(stmt.connection(), query, timeout);
+}
+
+} // namespace nanodbc
+
+// clang-format off
+//  .d8888b.                                               888    d8b                             8888888888                 888
+// d88P  Y88b                                              888    Y8P                             888                        888
+// 888    888                                              888                                    888                        888
+// 888         .d88b.  88888b.  88888b.   .d88b.   .d8888b 888888 888  .d88b.  88888b.            8888888 888  888  888  .d88888
+// 888        d88""88b 888 "88b 888 "88b d8P  Y8b d88P"    888    888 d88""88b 888 "88b           888     888  888  888 d88" 888
+// 888    888 888  888 888  888 888  888 88888888 888      888    888 888  888 888  888           888     888  888  888 888  888
+// Y88b  d88P Y88..88P 888  888 888  888 Y8b.     Y88b.    Y88b.  888 Y88..88P 888  888           888     Y88b 888 d88P Y88b 888
+//  "Y8888P"   "Y88P"  888  888 888  888  "Y8888   "Y8888P  "Y888 888  "Y88P"  888  888           888      "Y8888888P"   "Y88888
+// MARK: Connection Fwd -
+// clang-format on
+
+namespace nanodbc
+{
+
+connection::connection()
+    : impl_(new connection_impl())
+{
+}
+
+connection::connection(const connection& rhs)
+    : impl_(rhs.impl_)
+{
+}
+
+#ifndef NANODBC_NO_MOVE_CTOR
+connection::connection(connection&& rhs) NANODBC_NOEXCEPT : impl_(std::move(rhs.impl_))
+{
+}
+#endif
+
+connection& connection::operator=(connection rhs)
+{
+    swap(rhs);
+    return *this;
+}
+
+void connection::swap(connection& rhs) NANODBC_NOEXCEPT
+{
+    using std::swap;
+    swap(impl_, rhs.impl_);
+}
+
+connection::connection(const string& dsn, const string& user, const string& pass, long timeout)
+    : impl_(new connection_impl(dsn, user, pass, timeout))
+{
+}
+
+connection::connection(const string& connection_string, long timeout)
+    : impl_(new connection_impl(connection_string, timeout))
+{
+}
+
+connection::~connection() NANODBC_NOEXCEPT
+{
+}
+
+void connection::connect(const string& dsn, const string& user, const string& pass, long timeout)
+{
+    impl_->connect(dsn, user, pass, timeout);
+}
+
+void connection::connect(const string& connection_string, long timeout)
+{
+    impl_->connect(connection_string, timeout);
+}
+
+#if !defined(NANODBC_DISABLE_ASYNC) && defined(SQL_ATTR_ASYNC_DBC_EVENT)
+bool connection::async_connect(
+    const string& dsn,
+    const string& user,
+    const string& pass,
+    void* event_handle,
+    long timeout)
+{
+    return impl_->connect(dsn, user, pass, timeout, event_handle) == SQL_STILL_EXECUTING;
+}
+
+bool connection::async_connect(const string& connection_string, void* event_handle, long timeout)
+{
+    return impl_->connect(connection_string, timeout, event_handle) == SQL_STILL_EXECUTING;
+}
+
+void connection::async_complete()
+{
+    impl_->async_complete();
+}
+#endif // !NANODBC_DISABLE_ASYNC && SQL_ATTR_ASYNC_DBC_EVENT
+
+bool connection::connected() const
+{
+    return impl_->connected();
+}
+
+void connection::disconnect()
+{
+    impl_->disconnect();
+}
+
+std::size_t connection::transactions() const
+{
+    return impl_->transactions();
+}
+
+template <class T>
+T connection::get_info(short info_type) const
+{
+    return impl_->get_info<T>(info_type);
+}
+
+void* connection::native_dbc_handle() const
+{
+    return impl_->native_dbc_handle();
+}
+
+void* connection::native_env_handle() const
+{
+    return impl_->native_env_handle();
+}
+
+string connection::dbms_name() const
+{
+    return impl_->dbms_name();
+}
+
+string connection::dbms_version() const
+{
+    return impl_->dbms_version();
+}
+
+string connection::driver_name() const
+{
+    return impl_->driver_name();
+}
+
+string connection::database_name() const
+{
+    return impl_->database_name();
+}
+
+string connection::catalog_name() const
+{
+    return impl_->catalog_name();
+}
+
+std::size_t connection::ref_transaction()
+{
+    return impl_->ref_transaction();
+}
+
+std::size_t connection::unref_transaction()
+{
+    return impl_->unref_transaction();
+}
+
+bool connection::rollback() const
+{
+    return impl_->rollback();
+}
+
+void connection::rollback(bool onoff)
+{
+    impl_->rollback(onoff);
+}
+
+} // namespace nanodbc
+
+// clang-format off
+// 88888888888                                                  888    d8b                             8888888888                 888
+//     888                                                      888    Y8P                             888                        888
+//     888                                                      888                                    888                        888
+//     888  888d888 8888b.  88888b.  .d8888b   8888b.   .d8888b 888888 888  .d88b.  88888b.            8888888 888  888  888  .d88888 .d8888b
+//     888  888P"      "88b 888 "88b 88K          "88b d88P"    888    888 d88""88b 888 "88b           888     888  888  888 d88" 888 88K
+//     888  888    .d888888 888  888 "Y8888b. .d888888 888      888    888 888  888 888  888           888     888  888  888 888  888 "Y8888b.
+//     888  888    888  888 888  888      X88 888  888 Y88b.    Y88b.  888 Y88..88P 888  888           888     Y88b 888 d88P Y88b 888      X88
+//     888  888    "Y888888 888  888  88888P' "Y888888  "Y8888P  "Y888 888  "Y88P"  888  888           888      "Y8888888P"   "Y88888  88888P'
+// MARK: Transaction Fwd -
+// clang-format on
+
+namespace nanodbc
+{
+
+transaction::transaction(const class connection& conn)
+    : impl_(new transaction_impl(conn))
+{
+}
+
+transaction::transaction(const transaction& rhs)
+    : impl_(rhs.impl_)
+{
+}
+
+#ifndef NANODBC_NO_MOVE_CTOR
+transaction::transaction(transaction&& rhs) NANODBC_NOEXCEPT : impl_(std::move(rhs.impl_))
+{
+}
+#endif
+
+transaction& transaction::operator=(transaction rhs)
+{
+    swap(rhs);
+    return *this;
+}
+
+void transaction::swap(transaction& rhs) NANODBC_NOEXCEPT
+{
+    using std::swap;
+    swap(impl_, rhs.impl_);
+}
+
+transaction::~transaction() NANODBC_NOEXCEPT
+{
+}
+
+void transaction::commit()
+{
+    impl_->commit();
+}
+
+void transaction::rollback() NANODBC_NOEXCEPT
+{
+    impl_->rollback();
+}
+
+class connection& transaction::connection()
+{
+    return impl_->connection();
+}
+
+const class connection& transaction::connection() const
+{
+    return impl_->connection();
+}
+
+transaction::operator class connection&()
+{
+    return impl_->connection();
+}
+
+transaction::operator const class connection&() const
+{
+    return impl_->connection();
+}
+
+} // namespace nanodbc
+
+// clang-format off
+//  .d8888b.  888             888                                            888              8888888888                 888
+// d88P  Y88b 888             888                                            888              888                        888
+// Y88b.      888             888                                            888              888                        888
+//  "Y888b.   888888  8888b.  888888 .d88b.  88888b.d88b.   .d88b.  88888b.  888888           8888888 888  888  888  .d88888
+//     "Y88b. 888        "88b 888   d8P  Y8b 888 "888 "88b d8P  Y8b 888 "88b 888              888     888  888  888 d88" 888
+//       "888 888    .d888888 888   88888888 888  888  888 88888888 888  888 888              888     888  888  888 888  888
+// Y88b  d88P Y88b.  888  888 Y88b. Y8b.     888  888  888 Y8b.     888  888 Y88b.            888     Y88b 888 d88P Y88b 888
+//  "Y8888P"   "Y888 "Y888888  "Y888 "Y8888  888  888  888  "Y8888  888  888  "Y888           888      "Y8888888P"   "Y88888
+// MARK: Statement Fwd -
+// clang-format on
+
+namespace nanodbc
+{
+
+statement::statement()
+    : impl_(new statement_impl())
+{
+}
+
+statement::statement(class connection& conn)
+    : impl_(new statement_impl(conn))
+{
+}
+
+#ifndef NANODBC_NO_MOVE_CTOR
+statement::statement(statement&& rhs) NANODBC_NOEXCEPT : impl_(std::move(rhs.impl_))
+{
+}
+#endif
+
+statement::statement(class connection& conn, const string& query, long timeout)
+    : impl_(new statement_impl(conn, query, timeout))
+{
+}
+
+statement::statement(const statement& rhs)
+    : impl_(rhs.impl_)
+{
+}
+
+statement& statement::operator=(statement rhs)
+{
+    swap(rhs);
+    return *this;
+}
+
+void statement::swap(statement& rhs) NANODBC_NOEXCEPT
+{
+    using std::swap;
+    swap(impl_, rhs.impl_);
+}
+
+statement::~statement() NANODBC_NOEXCEPT
+{
+}
+
+void statement::open(class connection& conn)
+{
+    impl_->open(conn);
+}
+
+bool statement::open() const
+{
+    return impl_->open();
+}
+
+bool statement::connected() const
+{
+    return impl_->connected();
+}
+
+const class connection& statement::connection() const
+{
+    return impl_->connection();
+}
+
+class connection& statement::connection()
+{
+    return impl_->connection();
+}
+
+void* statement::native_statement_handle() const
+{
+    return impl_->native_statement_handle();
+}
+
+void statement::close()
+{
+    impl_->close();
+}
+
+void statement::cancel()
+{
+    impl_->cancel();
+}
+
+void statement::prepare(class connection& conn, const string& query, long timeout)
+{
+    impl_->prepare(conn, query, timeout);
+}
+
+void statement::prepare(const string& query, long timeout)
+{
+    impl_->prepare(query, timeout);
+}
+
+void statement::timeout(long timeout)
+{
+    impl_->timeout(timeout);
+}
+
+result statement::execute_direct(
+    class connection& conn,
+    const string& query,
+    long batch_operations,
+    long timeout)
+{
+    return impl_->execute_direct(conn, query, batch_operations, timeout, *this);
+}
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+bool statement::async_prepare(const string& query, void* event_handle, long timeout)
+{
+    return impl_->async_prepare(query, event_handle, timeout);
+}
+
+bool statement::async_execute_direct(
+    class connection& conn,
+    void* event_handle,
+    const string& query,
+    long batch_operations,
+    long timeout)
+{
+    return impl_->async_execute_direct(conn, event_handle, query, batch_operations, timeout, *this);
+}
+
+bool statement::async_execute(void* event_handle, long batch_operations, long timeout)
+{
+    return impl_->async_execute(event_handle, batch_operations, timeout, *this);
+}
+
+void statement::complete_prepare()
+{
+    return impl_->complete_prepare();
+}
+
+result statement::complete_execute(long batch_operations)
+{
+    return impl_->complete_execute(batch_operations, *this);
+}
+
+result statement::async_complete(long batch_operations)
+{
+    return impl_->complete_execute(batch_operations, *this);
+}
+
+void statement::enable_async(void* event_handle)
+{
+    impl_->enable_async(event_handle);
+}
+
+void statement::disable_async() const
+{
+    impl_->disable_async();
+}
+#endif
+
+void statement::just_execute_direct(
+    class connection& conn,
+    const string& query,
+    long batch_operations,
+    long timeout)
+{
+    impl_->just_execute_direct(conn, query, batch_operations, timeout, *this);
+}
+
+result statement::execute(long batch_operations, long timeout)
+{
+    return impl_->execute(batch_operations, timeout, *this);
+}
+
+void statement::just_execute(long batch_operations, long timeout)
+{
+    impl_->just_execute(batch_operations, timeout, *this);
+}
+
+result statement::procedure_columns(
+    const string& catalog,
+    const string& schema,
+    const string& procedure,
+    const string& column)
+{
+    return impl_->procedure_columns(catalog, schema, procedure, column, *this);
+}
+
+long statement::affected_rows() const
+{
+    return impl_->affected_rows();
+}
+
+short statement::columns() const
+{
+    return impl_->columns();
+}
+
+short statement::parameters() const
+{
+    return impl_->parameters();
+}
+
+void statement::reset_parameters() NANODBC_NOEXCEPT
+{
+    impl_->reset_parameters();
+}
+
+unsigned long statement::parameter_size(short param_index) const
+{
+    return impl_->parameter_size(param_index);
+}
+
+// We need to instantiate each form of bind() for each of our supported data types.
+#define NANODBC_INSTANTIATE_BINDS(type)                                                            \
+    template void statement::bind(short, const type*, param_direction);              /* 1-ary */   \
+    template void statement::bind(short, const type*, std::size_t, param_direction); /* n-ary */   \
+    template void statement::bind(                                                                 \
+        short, const type*, std::size_t, const type*, param_direction); /* n-ary, sentry */        \
+    template void statement::bind(                                                                 \
+        short, const type*, std::size_t, const bool*, param_direction) /* n-ary, flags */
+
+// The following are the only supported instantiations of statement::bind().
+NANODBC_INSTANTIATE_BINDS(string::value_type);
+NANODBC_INSTANTIATE_BINDS(short);
+NANODBC_INSTANTIATE_BINDS(unsigned short);
+NANODBC_INSTANTIATE_BINDS(int);
+NANODBC_INSTANTIATE_BINDS(unsigned int);
+NANODBC_INSTANTIATE_BINDS(long int);
+NANODBC_INSTANTIATE_BINDS(unsigned long int);
+NANODBC_INSTANTIATE_BINDS(long long);
+NANODBC_INSTANTIATE_BINDS(unsigned long long);
+NANODBC_INSTANTIATE_BINDS(float);
+NANODBC_INSTANTIATE_BINDS(double);
+NANODBC_INSTANTIATE_BINDS(date);
+NANODBC_INSTANTIATE_BINDS(time);
+NANODBC_INSTANTIATE_BINDS(timestamp);
+
+#undef NANODBC_INSTANTIATE_BINDS
+
+template <class T>
+void statement::bind(short param_index, const T* value, param_direction direction)
+{
+    impl_->bind(direction, param_index, value, 1);
+}
+
+template <class T>
+void statement::bind(
+    short param_index,
+    T const* values,
+    std::size_t batch_size,
+    param_direction direction)
+{
+    impl_->bind(direction, param_index, values, batch_size);
+}
+
+template <class T>
+void statement::bind(
+    short param_index,
+    T const* values,
+    std::size_t batch_size,
+    T const* null_sentry,
+    param_direction direction)
+{
+    impl_->bind(direction, param_index, values, batch_size, nullptr, null_sentry);
+}
+
+template <class T>
+void statement::bind(
+    short param_index,
+    T const* values,
+    std::size_t batch_size,
+    bool const* nulls,
+    param_direction direction)
+{
+    impl_->bind(direction, param_index, values, batch_size, nulls);
+}
+
+void statement::bind(
+    short param_index,
+    std::vector<std::vector<uint8_t>> const& values,
+    param_direction direction)
+{
+    impl_->bind(direction, param_index, values);
+}
+
+void statement::bind(
+    short param_index,
+    std::vector<std::vector<uint8_t>> const& values,
+    bool const* nulls,
+    param_direction direction)
+{
+    impl_->bind(direction, param_index, values, nulls);
+}
+
+void statement::bind(
+    short param_index,
+    std::vector<std::vector<uint8_t>> const& values,
+    uint8_t const* null_sentry,
+    param_direction direction)
+{
+    impl_->bind(direction, param_index, values, nullptr, null_sentry);
+}
+
+void statement::bind_strings(
+    short param_index,
+    std::vector<string> const& values,
+    param_direction direction)
+{
+    impl_->bind_strings(direction, param_index, values);
+}
+
+void statement::bind_strings(
+    short param_index,
+    string::value_type const* values,
+    std::size_t value_size,
+    std::size_t batch_size,
+    param_direction direction)
+{
+    impl_->bind_strings(direction, param_index, values, value_size, batch_size);
+}
+
+void statement::bind_strings(
+    short param_index,
+    string::value_type const* values,
+    std::size_t value_size,
+    std::size_t batch_size,
+    string::value_type const* null_sentry,
+    param_direction direction)
+{
+    impl_->bind_strings(
+        direction, param_index, values, value_size, batch_size, nullptr, null_sentry);
+}
+
+void statement::bind_strings(
+    short param_index,
+    string::value_type const* values,
+    std::size_t value_size,
+    std::size_t batch_size,
+    bool const* nulls,
+    param_direction direction)
+{
+    impl_->bind_strings(direction, param_index, values, value_size, batch_size, nulls);
+}
+
+void statement::bind_strings(
+    short param_index,
+    std::vector<string> const& values,
+    string::value_type const* null_sentry,
+    param_direction direction)
+{
+    impl_->bind_strings(direction, param_index, values, nullptr, null_sentry);
+}
+
+void statement::bind_strings(
+    short param_index,
+    std::vector<string> const& values,
+    bool const* nulls,
+    param_direction direction)
+{
+    impl_->bind_strings(direction, param_index, values, nulls);
+}
+
+void statement::bind_null(short param_index, std::size_t batch_size)
+{
+    impl_->bind_null(param_index, batch_size);
+}
+
+} // namespace nanodbc
+
+namespace nanodbc
+{
+
+catalog::tables::tables(result& find_result)
+    : result_(find_result)
+{
+}
+
+bool catalog::tables::next()
+{
+    return result_.next();
+}
+
+string catalog::tables::table_catalog() const
+{
+    // TABLE_CAT might be NULL
+    return result_.get<string>(0, string());
+}
+
+string catalog::tables::table_schema() const
+{
+    // TABLE_SCHEM might be NULL
+    return result_.get<string>(1, string());
+}
+
+string catalog::tables::table_name() const
+{
+    // TABLE_NAME column is never NULL
+    return result_.get<string>(2);
+}
+
+string catalog::tables::table_type() const
+{
+    // TABLE_TYPE column is never NULL
+    return result_.get<string>(3);
+}
+
+string catalog::tables::table_remarks() const
+{
+    // REMARKS might be NULL
+    return result_.get<string>(4, string());
+}
+
+catalog::table_privileges::table_privileges(result& find_result)
+    : result_(find_result)
+{
+}
+
+bool catalog::table_privileges::next()
+{
+    return result_.next();
+}
+
+string catalog::table_privileges::table_catalog() const
+{
+    // TABLE_CAT might be NULL
+    return result_.get<string>(0, string());
+}
+
+string catalog::table_privileges::table_schema() const
+{
+    // TABLE_SCHEM might be NULL
+    return result_.get<string>(1, string());
+}
+
+string catalog::table_privileges::table_name() const
+{
+    // TABLE_NAME column is never NULL
+    return result_.get<string>(2);
+}
+
+string catalog::table_privileges::grantor() const
+{
+    // GRANTOR might be NULL
+    return result_.get<string>(3, string());
+}
+
+string catalog::table_privileges::grantee() const
+{
+    // GRANTEE column is never NULL
+    return result_.get<string>(4);
+}
+
+string catalog::table_privileges::privilege() const
+{
+    // PRIVILEGE column is never NULL
+    return result_.get<string>(5);
+}
+
+string catalog::table_privileges::is_grantable() const
+{
+    // IS_GRANTABLE might be NULL
+    return result_.get<string>(6, string());
+}
+
+catalog::primary_keys::primary_keys(result& find_result)
+    : result_(find_result)
+{
+}
+
+bool catalog::primary_keys::next()
+{
+    return result_.next();
+}
+
+string catalog::primary_keys::table_catalog() const
+{
+    // TABLE_CAT might be NULL
+    return result_.get<string>(0, string());
+}
+
+string catalog::primary_keys::table_schema() const
+{
+    // TABLE_SCHEM might be NULL
+    return result_.get<string>(1, string());
+}
+
+string catalog::primary_keys::table_name() const
+{
+    // TABLE_NAME is never NULL
+    return result_.get<string>(2);
+}
+
+string catalog::primary_keys::column_name() const
+{
+    // COLUMN_NAME is never NULL
+    return result_.get<string>(3);
+}
+
+short catalog::primary_keys::column_number() const
+{
+    // KEY_SEQ is never NULL
+    return result_.get<short>(4);
+}
+
+string catalog::primary_keys::primary_key_name() const
+{
+    // PK_NAME might be NULL
+    return result_.get<string>(5);
+}
+
+catalog::columns::columns(result& find_result)
+    : result_(find_result)
+{
+}
+
+bool catalog::columns::next()
+{
+    return result_.next();
+}
+
+string catalog::columns::table_catalog() const
+{
+    // TABLE_CAT might be NULL
+    return result_.get<string>(0, string());
+}
+
+string catalog::columns::table_schema() const
+{
+    // TABLE_SCHEM might be NULL
+    return result_.get<string>(1, string());
+}
+
+string catalog::columns::table_name() const
+{
+    // TABLE_NAME is never NULL
+    return result_.get<string>(2);
+}
+
+string catalog::columns::column_name() const
+{
+    // COLUMN_NAME is never NULL
+    return result_.get<string>(3);
+}
+
+short catalog::columns::data_type() const
+{
+    // DATA_TYPE is never NULL
+    return result_.get<short>(4);
+}
+
+string catalog::columns::type_name() const
+{
+    // TYPE_NAME is never NULL
+    return result_.get<string>(5);
+}
+
+long catalog::columns::column_size() const
+{
+    // COLUMN_SIZE
+    return result_.get<long>(6);
+}
+
+long catalog::columns::buffer_length() const
+{
+    // BUFFER_LENGTH
+    return result_.get<long>(7);
+}
+
+short catalog::columns::decimal_digits() const
+{
+    // DECIMAL_DIGITS might be NULL
+    return result_.get<short>(8, 0);
+}
+
+short catalog::columns::numeric_precision_radix() const
+{
+    // NUM_PREC_RADIX might be NULL
+    return result_.get<short>(9, 0);
+}
+
+short catalog::columns::nullable() const
+{
+    // NULLABLE is never NULL
+    return result_.get<short>(10);
+}
+
+string catalog::columns::remarks() const
+{
+    // REMARKS might be NULL
+    return result_.get<string>(11, string());
+}
+
+string catalog::columns::column_default() const
+{
+    // COLUMN_DEF might be NULL, if no default value is specified
+    return result_.get<string>(12, string());
+}
+
+short catalog::columns::sql_data_type() const
+{
+    // SQL_DATA_TYPE is never NULL
+    return result_.get<short>(13);
+}
+
+short catalog::columns::sql_datetime_subtype() const
+{
+    // SQL_DATETIME_SUB might be NULL
+    return result_.get<short>(14, 0);
+}
+
+long catalog::columns::char_octet_length() const
+{
+    // CHAR_OCTET_LENGTH might be NULL
+    return result_.get<long>(15, 0);
+}
+
+long catalog::columns::ordinal_position() const
+{
+    // ORDINAL_POSITION is never NULL
+    return result_.get<long>(16);
+}
+
+string catalog::columns::is_nullable() const
+{
+    // IS_NULLABLE might be NULL.
+    return result_.get<string>(17, string());
+}
+
+catalog::catalog(connection& conn)
+    : conn_(conn)
+{
+}
+
+catalog::tables catalog::find_tables(
+    const string& table,
+    const string& type,
+    const string& schema,
+    const string& catalog)
+{
+    // Passing a null pointer to a search pattern argument does not
+    // constrain the search for that argument; that is, a null pointer and
+    // the search pattern % (any characters) are equivalent.
+    // However, a zero-length search pattern - that is, a valid pointer to
+    // a string of length zero - matches only the empty string ("").
+    // See https://msdn.microsoft.com/en-us/library/ms710171.aspx
+
+    statement stmt(conn_);
+    RETCODE rc;
+    NANODBC_CALL_RC(
+        NANODBC_FUNC(SQLTables),
+        rc,
+        stmt.native_statement_handle(),
+        (NANODBC_SQLCHAR*)(catalog.empty() ? nullptr : catalog.c_str()),
+        (catalog.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(schema.empty() ? nullptr : schema.c_str()),
+        (schema.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(table.empty() ? nullptr : table.c_str()),
+        (table.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(type.empty() ? nullptr : type.c_str()),
+        (type.empty() ? 0 : SQL_NTS));
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT);
+
+    result find_result(stmt, 1);
+    return catalog::tables(find_result);
+}
+
+catalog::table_privileges
+catalog::find_table_privileges(const string& catalog, const string& table, const string& schema)
+{
+    // Passing a null pointer to a search pattern argument does not
+    // constrain the search for that argument; that is, a null pointer and
+    // the search pattern % (any characters) are equivalent.
+    // However, a zero-length search pattern - that is, a valid pointer to
+    // a string of length zero - matches only the empty string ("").
+    // See https://msdn.microsoft.com/en-us/library/ms710171.aspx
+
+    statement stmt(conn_);
+    RETCODE rc;
+    NANODBC_CALL_RC(
+        NANODBC_FUNC(SQLTablePrivileges),
+        rc,
+        stmt.native_statement_handle(),
+        (NANODBC_SQLCHAR*)(catalog.empty() ? nullptr : catalog.c_str()),
+        (catalog.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(schema.empty() ? nullptr : schema.c_str()),
+        (schema.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(table.empty() ? nullptr : table.c_str()),
+        (table.empty() ? 0 : SQL_NTS));
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT);
+
+    result find_result(stmt, 1);
+    return catalog::table_privileges(find_result);
+}
+
+catalog::columns catalog::find_columns(
+    const string& column,
+    const string& table,
+    const string& schema,
+    const string& catalog)
+{
+    statement stmt(conn_);
+    RETCODE rc;
+    NANODBC_CALL_RC(
+        NANODBC_FUNC(SQLColumns),
+        rc,
+        stmt.native_statement_handle(),
+        (NANODBC_SQLCHAR*)(catalog.empty() ? nullptr : catalog.c_str()),
+        (catalog.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(schema.empty() ? nullptr : schema.c_str()),
+        (schema.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(table.empty() ? nullptr : table.c_str()),
+        (table.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(column.empty() ? nullptr : column.c_str()),
+        (column.empty() ? 0 : SQL_NTS));
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT);
+
+    result find_result(stmt, 1);
+    return catalog::columns(find_result);
+}
+
+catalog::primary_keys
+catalog::find_primary_keys(const string& table, const string& schema, const string& catalog)
+{
+    statement stmt(conn_);
+    RETCODE rc;
+    NANODBC_CALL_RC(
+        NANODBC_FUNC(SQLPrimaryKeys),
+        rc,
+        stmt.native_statement_handle(),
+        (NANODBC_SQLCHAR*)(catalog.empty() ? nullptr : catalog.c_str()),
+        (catalog.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(schema.empty() ? nullptr : schema.c_str()),
+        (schema.empty() ? 0 : SQL_NTS),
+        (NANODBC_SQLCHAR*)(table.empty() ? nullptr : table.c_str()),
+        (table.empty() ? 0 : SQL_NTS));
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT);
+
+    result find_result(stmt, 1);
+    return catalog::primary_keys(find_result);
+}
+
+std::list<string> catalog::list_catalogs()
+{
+    // Special case for list of catalogs only:
+    // all the other arguments must match empty string (""),
+    // otherwise pattern-based lookup is performed returning
+    // Cartesian product of catalogs, tables and schemas.
+    statement stmt(conn_);
+    RETCODE rc;
+    NANODBC_CALL_RC(
+        NANODBC_FUNC(SQLTables),
+        rc,
+        stmt.native_statement_handle(),
+        (NANODBC_SQLCHAR*)SQL_ALL_CATALOGS,
+        1,
+        (NANODBC_SQLCHAR*)NANODBC_TEXT(""),
+        0,
+        (NANODBC_SQLCHAR*)NANODBC_TEXT(""),
+        0,
+        (NANODBC_SQLCHAR*)NANODBC_TEXT(""),
+        0);
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT);
+
+    result find_result(stmt, 1);
+    catalog::tables catalogs(find_result);
+
+    std::list<string> names;
+    while (catalogs.next())
+        names.push_back(catalogs.table_catalog());
+    return names;
+}
+
+std::list<string> catalog::list_schemas()
+{
+    // Special case for list of schemas:
+    // all the other arguments must match empty string (""),
+    // otherwise pattern-based lookup is performed returning
+    // Cartesian product of catalogs, tables and schemas.
+    statement stmt(conn_);
+    RETCODE rc;
+    NANODBC_CALL_RC(
+        NANODBC_FUNC(SQLTables),
+        rc,
+        stmt.native_statement_handle(),
+        (NANODBC_SQLCHAR*)NANODBC_TEXT(""),
+        0,
+        (NANODBC_SQLCHAR*)SQL_ALL_SCHEMAS,
+        1,
+        (NANODBC_SQLCHAR*)NANODBC_TEXT(""),
+        0,
+        (NANODBC_SQLCHAR*)NANODBC_TEXT(""),
+        0);
+    if (!success(rc))
+        NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT);
+
+    result find_result(stmt, 1);
+    catalog::tables schemas(find_result);
+
+    std::list<string> names;
+    while (schemas.next())
+        names.push_back(schemas.table_schema());
+    return names;
+}
+
+} // namespace nanodbc
+
+// clang-format off
+// 8888888b.                            888 888              8888888888                 888
+// 888   Y88b                           888 888              888                        888
+// 888    888                           888 888              888                        888
+// 888   d88P .d88b.  .d8888b  888  888 888 888888           8888888 888  888  888  .d88888
+// 8888888P" d8P  Y8b 88K      888  888 888 888              888     888  888  888 d88" 888
+// 888 T88b  88888888 "Y8888b. 888  888 888 888              888     888  888  888 888  888
+// 888  T88b Y8b.          X88 Y88b 888 888 Y88b.            888     Y88b 888 d88P Y88b 888
+// 888   T88b "Y8888   88888P'  "Y88888 888  "Y888           888      "Y8888888P"   "Y88888
+// MARK: Result Fwd -
+// clang-format on
+
+namespace nanodbc
+{
+
+result::result()
+    : impl_()
+{
+}
+
+result::~result() NANODBC_NOEXCEPT
+{
+}
+
+result::result(statement stmt, long rowset_size)
+    : impl_(new result_impl(stmt, rowset_size))
+{
+}
+
+#ifndef NANODBC_NO_MOVE_CTOR
+result::result(result&& rhs) NANODBC_NOEXCEPT : impl_(std::move(rhs.impl_))
+{
+}
+#endif
+
+result::result(const result& rhs)
+    : impl_(rhs.impl_)
+{
+}
+
+result& result::operator=(result rhs)
+{
+    swap(rhs);
+    return *this;
+}
+
+void result::swap(result& rhs) NANODBC_NOEXCEPT
+{
+    using std::swap;
+    swap(impl_, rhs.impl_);
+}
+
+void* result::native_statement_handle() const
+{
+    return impl_->native_statement_handle();
+}
+
+long result::rowset_size() const NANODBC_NOEXCEPT
+{
+    return impl_->rowset_size();
+}
+
+long result::affected_rows() const
+{
+    return impl_->affected_rows();
+}
+
+long result::rows() const NANODBC_NOEXCEPT
+{
+    return impl_->rows();
+}
+
+short result::columns() const
+{
+    return impl_->columns();
+}
+
+bool result::first()
+{
+    return impl_->first();
+}
+
+bool result::last()
+{
+    return impl_->last();
+}
+
+bool result::next()
+{
+    return impl_->next();
+}
+
+#if defined(NANODBC_DO_ASYNC_IMPL)
+bool result::async_next(void* event_handle)
+{
+    return impl_->async_next(event_handle);
+}
+
+bool result::complete_next()
+{
+    return impl_->complete_next();
+}
+#endif
+
+bool result::prior()
+{
+    return impl_->prior();
+}
+
+bool result::move(long row)
+{
+    return impl_->move(row);
+}
+
+bool result::skip(long rows)
+{
+    return impl_->skip(rows);
+}
+
+unsigned long result::position() const
+{
+    return impl_->position();
+}
+
+bool result::at_end() const NANODBC_NOEXCEPT
+{
+    return impl_->at_end();
+}
+
+bool result::is_null(short column) const
+{
+    return impl_->is_null(column);
+}
+
+bool result::is_null(const string& column_name) const
+{
+    return impl_->is_null(column_name);
+}
+
+short result::column(const string& column_name) const
+{
+    return impl_->column(column_name);
+}
+
+string result::column_name(short column) const
+{
+    return impl_->column_name(column);
+}
+
+long result::column_size(short column) const
+{
+    return impl_->column_size(column);
+}
+
+long result::column_size(const string& column_name) const
+{
+    return impl_->column_size(column_name);
+}
+
+int result::column_decimal_digits(short column) const
+{
+    return impl_->column_decimal_digits(column);
+}
+
+int result::column_decimal_digits(const string& column_name) const
+{
+    return impl_->column_decimal_digits(column_name);
+}
+
+int result::column_datatype(short column) const
+{
+    return impl_->column_datatype(column);
+}
+
+int result::column_datatype(const string& column_name) const
+{
+    return impl_->column_datatype(column_name);
+}
+
+string result::column_datatype_name(short column) const
+{
+    return impl_->column_datatype_name(column);
+}
+
+string result::column_datatype_name(const string& column_name) const
+{
+    return impl_->column_datatype_name(column_name);
+}
+
+int result::column_c_datatype(short column) const
+{
+    return impl_->column_c_datatype(column);
+}
+
+int result::column_c_datatype(const string& column_name) const
+{
+    return impl_->column_c_datatype(column_name);
+}
+
+bool result::next_result()
+{
+    return impl_->next_result();
+}
+
+template <class T>
+void result::get_ref(short column, T& result) const
+{
+    return impl_->get_ref<T>(column, result);
+}
+
+template <class T>
+void result::get_ref(short column, const T& fallback, T& result) const
+{
+    return impl_->get_ref<T>(column, fallback, result);
+}
+
+template <class T>
+void result::get_ref(const string& column_name, T& result) const
+{
+    return impl_->get_ref<T>(column_name, result);
+}
+
+template <class T>
+void result::get_ref(const string& column_name, const T& fallback, T& result) const
+{
+    return impl_->get_ref<T>(column_name, fallback, result);
+}
+
+template <class T>
+T result::get(short column) const
+{
+    return impl_->get<T>(column);
+}
+
+template <class T>
+T result::get(short column, const T& fallback) const
+{
+    return impl_->get<T>(column, fallback);
+}
+
+template <class T>
+T result::get(const string& column_name) const
+{
+    return impl_->get<T>(column_name);
+}
+
+template <class T>
+T result::get(const string& column_name, const T& fallback) const
+{
+    return impl_->get<T>(column_name, fallback);
+}
+
+result::operator bool() const
+{
+    return static_cast<bool>(impl_);
+}
+
+// The following are the only supported instantiations of result::get_ref().
+template void result::get_ref(short, string::value_type&) const;
+template void result::get_ref(short, short&) const;
+template void result::get_ref(short, unsigned short&) const;
+template void result::get_ref(short, int&) const;
+template void result::get_ref(short, unsigned int&) const;
+template void result::get_ref(short, long int&) const;
+template void result::get_ref(short, unsigned long int&) const;
+template void result::get_ref(short, long long int&) const;
+template void result::get_ref(short, unsigned long long int&) const;
+template void result::get_ref(short, float&) const;
+template void result::get_ref(short, double&) const;
+template void result::get_ref(short, string&) const;
+template void result::get_ref(short, date&) const;
+template void result::get_ref(short, time&) const;
+template void result::get_ref(short, timestamp&) const;
+template void result::get_ref(short, std::vector<std::uint8_t>&) const;
+
+template void result::get_ref(const string&, string::value_type&) const;
+template void result::get_ref(const string&, short&) const;
+template void result::get_ref(const string&, unsigned short&) const;
+template void result::get_ref(const string&, int&) const;
+template void result::get_ref(const string&, unsigned int&) const;
+template void result::get_ref(const string&, long int&) const;
+template void result::get_ref(const string&, unsigned long int&) const;
+template void result::get_ref(const string&, long long int&) const;
+template void result::get_ref(const string&, unsigned long long int&) const;
+template void result::get_ref(const string&, float&) const;
+template void result::get_ref(const string&, double&) const;
+template void result::get_ref(const string&, string&) const;
+template void result::get_ref(const string&, date&) const;
+template void result::get_ref(const string&, time&) const;
+template void result::get_ref(const string&, timestamp&) const;
+template void result::get_ref(const string&, std::vector<std::uint8_t>&) const;
+
+// The following are the only supported instantiations of result::get_ref() with fallback.
+template void result::get_ref(short, const string::value_type&, string::value_type&) const;
+template void result::get_ref(short, const short&, short&) const;
+template void result::get_ref(short, const unsigned short&, unsigned short&) const;
+template void result::get_ref(short, const int&, int&) const;
+template void result::get_ref(short, const unsigned int&, unsigned int&) const;
+template void result::get_ref(short, const long int&, long int&) const;
+template void result::get_ref(short, const unsigned long int&, unsigned long int&) const;
+template void result::get_ref(short, const long long int&, long long int&) const;
+template void result::get_ref(short, const unsigned long long int&, unsigned long long int&) const;
+template void result::get_ref(short, const float&, float&) const;
+template void result::get_ref(short, const double&, double&) const;
+template void result::get_ref(short, const string&, string&) const;
+template void result::get_ref(short, const date&, date&) const;
+template void result::get_ref(short, const time&, time&) const;
+template void result::get_ref(short, const timestamp&, timestamp&) const;
+template void
+result::get_ref(short, const std::vector<std::uint8_t>&, std::vector<std::uint8_t>&) const;
+
+template void result::get_ref(const string&, const string::value_type&, string::value_type&) const;
+template void result::get_ref(const string&, const short&, short&) const;
+template void result::get_ref(const string&, const unsigned short&, unsigned short&) const;
+template void result::get_ref(const string&, const int&, int&) const;
+template void result::get_ref(const string&, const unsigned int&, unsigned int&) const;
+template void result::get_ref(const string&, const long int&, long int&) const;
+template void result::get_ref(const string&, const unsigned long int&, unsigned long int&) const;
+template void result::get_ref(const string&, const long long int&, long long int&) const;
+template void
+result::get_ref(const string&, const unsigned long long int&, unsigned long long int&) const;
+template void result::get_ref(const string&, const float&, float&) const;
+template void result::get_ref(const string&, const double&, double&) const;
+template void result::get_ref(const string&, const string&, string&) const;
+template void result::get_ref(const string&, const date&, date&) const;
+template void result::get_ref(const string&, const time&, time&) const;
+template void result::get_ref(const string&, const timestamp&, timestamp&) const;
+template void
+result::get_ref(const string&, const std::vector<std::uint8_t>&, std::vector<std::uint8_t>&) const;
+
+// The following are the only supported instantiations of result::get().
+template string::value_type result::get(short) const;
+template short result::get(short) const;
+template unsigned short result::get(short) const;
+template int result::get(short) const;
+template unsigned int result::get(short) const;
+template long int result::get(short) const;
+template unsigned long int result::get(short) const;
+template long long int result::get(short) const;
+template unsigned long long int result::get(short) const;
+template float result::get(short) const;
+template double result::get(short) const;
+template string result::get(short) const;
+template date result::get(short) const;
+template time result::get(short) const;
+template timestamp result::get(short) const;
+template std::vector<std::uint8_t> result::get(short) const;
+
+template string::value_type result::get(const string&) const;
+template short result::get(const string&) const;
+template unsigned short result::get(const string&) const;
+template int result::get(const string&) const;
+template unsigned int result::get(const string&) const;
+template long int result::get(const string&) const;
+template unsigned long int result::get(const string&) const;
+template long long int result::get(const string&) const;
+template unsigned long long int result::get(const string&) const;
+template float result::get(const string&) const;
+template double result::get(const string&) const;
+template string result::get(const string&) const;
+template date result::get(const string&) const;
+template time result::get(const string&) const;
+template timestamp result::get(const string&) const;
+template std::vector<std::uint8_t> result::get(const string&) const;
+
+// The following are the only supported instantiations of result::get() with fallback.
+template string::value_type result::get(short, const string::value_type&) const;
+template short result::get(short, const short&) const;
+template unsigned short result::get(short, const unsigned short&) const;
+template int result::get(short, const int&) const;
+template unsigned int result::get(short, const unsigned int&) const;
+template long int result::get(short, const long int&) const;
+template unsigned long int result::get(short, const unsigned long int&) const;
+template long long int result::get(short, const long long int&) const;
+template unsigned long long int result::get(short, const unsigned long long int&) const;
+template float result::get(short, const float&) const;
+template double result::get(short, const double&) const;
+template string result::get(short, const string&) const;
+template date result::get(short, const date&) const;
+template time result::get(short, const time&) const;
+template timestamp result::get(short, const timestamp&) const;
+template std::vector<std::uint8_t> result::get(short, const std::vector<std::uint8_t>&) const;
+
+template string::value_type result::get(const string&, const string::value_type&) const;
+template short result::get(const string&, const short&) const;
+template unsigned short result::get(const string&, const unsigned short&) const;
+template int result::get(const string&, const int&) const;
+template unsigned int result::get(const string&, const unsigned int&) const;
+template long int result::get(const string&, const long int&) const;
+template unsigned long int result::get(const string&, const unsigned long int&) const;
+template long long int result::get(const string&, const long long int&) const;
+template unsigned long long int result::get(const string&, const unsigned long long int&) const;
+template float result::get(const string&, const float&) const;
+template double result::get(const string&, const double&) const;
+template string result::get(const string&, const string&) const;
+template date result::get(const string&, const date&) const;
+template time result::get(const string&, const time&) const;
+template timestamp result::get(const string&, const timestamp&) const;
+template std::vector<std::uint8_t>
+result::get(const string&, const std::vector<std::uint8_t>&) const;
+
+} // namespace nanodbc
+
+#undef NANODBC_THROW_DATABASE_ERROR
+#undef NANODBC_STRINGIZE
+#undef NANODBC_STRINGIZE_I
+#undef NANODBC_CALL_RC
+#undef NANODBC_CALL
+
+#endif // DOXYGEN

+ 1822 - 0
Source/ThirdParty/nanodbc/nanodbc/nanodbc.h

@@ -0,0 +1,1822 @@
+/// \file nanodbc.h The entirety of nanodbc can be found within this file and nanodbc.cpp.
+
+/// \mainpage
+///
+/// \section synopsis Synopsis
+/// This library provides a wrapper API for the native ODBC API. It aims to do everything ODBC does,
+/// but with a \b much nicer interface. Anything it doesn't (yet) do can be done by retrieving the
+/// native ODBC handles and dropping down to straight ODBC C API code.
+/// For more propaganda, please see the <a href="http://lexicalunit.github.com/nanodbc/">project
+/// homepage</a>.
+///
+/// \section toc Table of Contents
+/// - \ref license "License"
+/// - \ref credits "Credits"
+/// - Source level documentation:
+///     - \ref nanodbc "nanodbc namespace"
+///     - \ref exceptions
+///     - \ref utility
+///     - \ref mainc
+///     - \ref mainf
+///     - \ref binding
+///     - \ref bind_multi
+///     - \ref bind_strings
+///
+/// \section license License
+/// <div class="license">
+/// Copyright (C) 2013 lexicalunit <[email protected]>
+///
+/// The MIT License
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a copy
+/// of this software and associated documentation files (the "Software"), to deal
+/// in the Software without restriction, including without limitation the rights
+/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+/// copies of the Software, and to permit persons to whom the Software is
+/// furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+/// THE SOFTWARE.
+/// </div>
+///
+/// \section credits Credits
+/// <div class="license">
+/// Much of the code in this file was originally derived from TinyODBC.
+/// TinyODBC is hosted at http://code.google.com/p/tiodbc/
+/// Copyright (C) 2008 SqUe [email protected]
+/// License: The MIT License
+///
+/// The idea for using RAII for transactions was inspired by SimpleDB: C++ ODBC database API,
+/// however the code in nanodbc is original and not derived from SimpleDB. Therefore
+/// the LGPL license under which SimpleDB is distributed does NOT apply to nanodbc.
+/// SimpleDB is hosted at http://simpledb.sourceforge.net
+/// Copyright (C) 2006 Eminence Technology Pty Ltd
+/// Copyright (C) 2008-2010,2012 Russell Kliese [email protected]
+/// License: GNU Lesser General Public version 2.1
+///
+/// Some improvements and features are based on The Python ODBC Library.
+/// The Python ODBC Library is hosted at http://code.google.com/p/pyodbc/
+/// License: The MIT License
+///
+/// Implementation of column binding inspired by Nick E. Geht's source code posted to on CodeGuru.
+/// GSODBC hosted at http://www.codeguru.com/mfc_database/gsodbc.html
+/// Copyright (C) 2002 Nick E. Geht
+/// License: Perpetual license to reproduce, distribute, adapt, perform, display, and sublicense.
+/// See http://www.codeguru.com/submission-guidelines.php for details.
+/// </div>
+
+#ifndef NANODBC_H
+#define NANODBC_H
+
+#include <cstddef>
+#include <functional>
+#include <list>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#ifndef __clang__
+#include <cstdint>
+#endif
+
+/// \brief The entirety of nanodbc can be found within this one namespace.
+///
+/// \note This library does not make any exception safety guarantees, but should work just fine with
+///       a threading enabled ODBC driver. If you want to use nanodbc objects in threads I recommend
+///       each thread keep their own connection to the database. Otherwise you must synchronize any
+///       access to nanodbc objects.
+namespace nanodbc
+{
+
+// clang-format off
+//  .d8888b.                     .d888 d8b                                   888    d8b
+// d88P  Y88b                   d88P"  Y8P                                   888    Y8P
+// 888    888                   888                                          888
+// 888         .d88b.  88888b.  888888 888  .d88b.  888  888 888d888 8888b.  888888 888  .d88b.  88888b.
+// 888        d88""88b 888 "88b 888    888 d88P"88b 888  888 888P"      "88b 888    888 d88""88b 888 "88b
+// 888    888 888  888 888  888 888    888 888  888 888  888 888    .d888888 888    888 888  888 888  888
+// Y88b  d88P Y88..88P 888  888 888    888 Y88b 888 Y88b 888 888    888  888 Y88b.  888 Y88..88P 888  888
+//  "Y8888P"   "Y88P"  888  888 888    888  "Y88888  "Y88888 888    "Y888888  "Y888 888  "Y88P"  888  888
+//                                              888
+//                                         Y8b d88P
+//                                          "Y88P"
+// MARK: Configuration -
+// clang-format on
+
+/// \addtogroup macros Macros
+/// \brief Macros that nanodbc uses, can be overriden by users.
+///
+/// @{
+
+#ifdef DOXYGEN
+/// \def NANODBC_ASSERT(expression)
+/// \brief Assertion.
+///
+/// By default, nanodbc uses C \c assert() for internal assertions.
+/// 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
+/// #endif
+/// \endcode
+#define NANODBC_ASSERT(expression) assert(expression)
+#endif
+
+/// @}
+
+// You must explicitly request Unicode support by defining NANODBC_ENABLE_UNICODE at compile time.
+#ifndef DOXYGEN
+#ifdef NANODBC_ENABLE_UNICODE
+#ifdef NANODBC_USE_IODBC_WIDE_STRINGS
+#define NANODBC_TEXT(s) U##s
+typedef std::u32string string;
+#else
+#ifdef _MSC_VER
+typedef std::wstring string;
+#define NANODBC_TEXT(s) L##s
+#else
+typedef std::u16string string;
+#define NANODBC_TEXT(s) u##s
+#endif
+#endif
+#else
+typedef std::string string;
+#define NANODBC_TEXT(s) s
+#endif
+
+#if defined(_WIN64)
+// LLP64 machine: Windows
+typedef std::int64_t null_type;
+#elif !defined(_WIN64) && defined(__LP64__)
+// LP64 machine: OS X or Linux
+typedef long null_type;
+#else
+// 32-bit machine
+typedef long null_type;
+#endif
+#else
+/// \def NANODBC_TEXT(s)
+/// \brief Creates a string literal of the type corresponding to `nanodbc::string`.
+///
+/// By default, the macro maps to an unprefixed string literal.
+/// If building with options NANODBC_ENABLE_UNICODE=ON and
+/// NANODBC_USE_IODBC_WIDE_STRINGS=ON specified, then it prefixes a literal with U"...".
+/// If only NANODBC_ENABLE_UNICODE=ON is specified, then:
+///   * If building with Visual Studio, then the macro prefixes a literal with L"...".
+///   * Otherwise, it prefixes a literal with u"...".
+#define NANODBC_TEXT(s) s
+
+/// \c string will be \c std::u16string or \c std::32string if \c NANODBC_ENABLE_UNICODE
+/// defined.
+///
+/// Otherwise it will be \c std::string.
+typedef unspecified - type string;
+/// \c null_type will be \c int64_t for 64-bit compilations, otherwise \c long.
+typedef unspecified - type null_type;
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER <= 1800
+// These versions of Visual C++ do not yet support \c noexcept or \c std::move.
+#define NANODBC_NOEXCEPT
+#define NANODBC_NO_MOVE_CTOR
+#else
+#define NANODBC_NOEXCEPT noexcept
+#endif
+
+#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
+// [[deprecated]] is only available in C++14
+#define NANODBC_DEPRECATED [[deprecated]]
+#else
+#ifdef __GNUC__
+#define NANODBC_DEPRECATED __attribute__((deprecated))
+#elif defined(_MSC_VER)
+#define NANODBC_DEPRECATED __declspec(deprecated)
+#else
+#define NANODBC_DEPRECATED
+#endif
+#endif
+
+// clang-format off
+// 8888888888                                      888    888                        888 888 d8b
+// 888                                             888    888                        888 888 Y8P
+// 888                                             888    888                        888 888
+// 8888888    888d888 888d888 .d88b.  888d888      8888888888  8888b.  88888b.   .d88888 888 888 88888b.   .d88b.
+// 888        888P"   888P"  d88""88b 888P"        888    888     "88b 888 "88b d88" 888 888 888 888 "88b d88P"88b
+// 888        888     888    888  888 888          888    888 .d888888 888  888 888  888 888 888 888  888 888  888
+// 888        888     888    Y88..88P 888          888    888 888  888 888  888 Y88b 888 888 888 888  888 Y88b 888
+// 8888888888 888     888     "Y88P"  888          888    888 "Y888888 888  888  "Y88888 888 888 888  888  "Y88888
+//                                                                                                             888
+//                                                                                                        Y8b d88P
+//                                                                                                         "Y88P"
+// MARK: Error Handling -
+// clang-format on
+
+/// \addtogroup exceptions Exception types
+/// \brief Possible error conditions.
+///
+/// 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 \c SQLGetDiagRec().
+/// @{
+
+/// \brief Type incompatible.
+/// \see exceptions
+class type_incompatible_error : public std::runtime_error
+{
+public:
+    type_incompatible_error();
+    const char* what() const NANODBC_NOEXCEPT;
+};
+
+/// \brief Accessed null data.
+/// \see exceptions
+class null_access_error : public std::runtime_error
+{
+public:
+    null_access_error();
+    const char* what() const NANODBC_NOEXCEPT;
+};
+
+/// \brief Index out of range.
+/// \see exceptions
+class index_range_error : public std::runtime_error
+{
+public:
+    index_range_error();
+    const char* what() const NANODBC_NOEXCEPT;
+};
+
+/// \brief Programming logic error.
+/// \see exceptions
+class programming_error : public std::runtime_error
+{
+public:
+    explicit programming_error(const std::string& info);
+    const char* what() const NANODBC_NOEXCEPT;
+};
+
+/// \brief General database error.
+/// \see exceptions
+class database_error : public std::runtime_error
+{
+public:
+    /// \brief Creates runtime_error with message about last ODBC error.
+    /// \param handle The native ODBC statement or connection handle.
+    /// \param handle_type The native ODBC handle type code for the given handle.
+    /// \param info Additional info that will be appended to the beginning of the error message.
+    database_error(void* handle, short handle_type, const std::string& info = "");
+    const char* what() const NANODBC_NOEXCEPT;
+    const long native() const NANODBC_NOEXCEPT;
+    const std::string state() const NANODBC_NOEXCEPT;
+
+private:
+    long native_error;
+    std::string sql_state;
+    std::string message;
+};
+
+/// @}
+
+// clang-format off
+// 888     888 888    d8b 888 d8b 888    d8b
+// 888     888 888    Y8P 888 Y8P 888    Y8P
+// 888     888 888        888     888
+// 888     888 888888 888 888 888 888888 888  .d88b.  .d8888b
+// 888     888 888    888 888 888 888    888 d8P  Y8b 88K
+// 888     888 888    888 888 888 888    888 88888888 "Y8888b.
+// Y88b. .d88P Y88b.  888 888 888 Y88b.  888 Y8b.          X88
+//  "Y88888P"   "Y888 888 888 888  "Y888 888  "Y8888   88888P'
+// MARK: Utilities -
+// clang-format on
+
+/// \addtogroup utility Utilities
+/// \brief Additional nanodbc utility classes and functions.
+///
+/// \{
+
+/// \brief A type for representing date data.
+struct date
+{
+    std::int16_t year;  ///< Year [0-inf).
+    std::int16_t month; ///< Month of the year [1-12].
+    std::int16_t day;   ///< Day of the month [1-31].
+};
+
+/// \brief A type for representing time data.
+struct time
+{
+    std::int16_t hour; ///< Hours since midnight [0-23].
+    std::int16_t min;  ///< Minutes after the hour [0-59].
+    std::int16_t sec;  ///< Seconds after the minute.
+};
+
+/// \brief A type for representing timestamp data.
+struct timestamp
+{
+    std::int16_t year;  ///< Year [0-inf).
+    std::int16_t month; ///< Month of the year [1-12].
+    std::int16_t day;   ///< Day of the month [1-31].
+    std::int16_t hour;  ///< Hours since midnight [0-23].
+    std::int16_t min;   ///< Minutes after the hour [0-59].
+    std::int16_t sec;   ///< Seconds after the minute.
+    std::int32_t fract; ///< Fractional seconds.
+};
+
+/// \}
+
+/// \addtogroup mainc Main classes
+/// \brief Main nanodbc classes.
+///
+/// @{
+
+// clang-format off
+// 88888888888                                                  888    d8b
+//     888                                                      888    Y8P
+//     888                                                      888
+//     888  888d888 8888b.  88888b.  .d8888b   8888b.   .d8888b 888888 888  .d88b.  88888b.
+//     888  888P"      "88b 888 "88b 88K          "88b d88P"    888    888 d88""88b 888 "88b
+//     888  888    .d888888 888  888 "Y8888b. .d888888 888      888    888 888  888 888  888
+//     888  888    888  888 888  888      X88 888  888 Y88b.    Y88b.  888 Y88..88P 888  888
+//     888  888    "Y888888 888  888  88888P' "Y888888  "Y8888P  "Y888 888  "Y88P"  888  888
+// MARK: Transaction -
+// clang-format on
+
+/// \brief A resource for managing transaction commits and rollbacks.
+/// \attention You will want to use transactions if you are doing batch operations because it will
+///            prevent auto commits from occurring after each individual operation is executed.
+class transaction
+{
+public:
+    /// \brief Begin a transaction on the given connection object.
+    /// \post Operations that modify the database must now be committed before taking effect.
+    /// \throws database_error
+    explicit transaction(const class connection& conn);
+
+    /// Copy constructor.
+    transaction(const transaction& rhs);
+
+#ifndef NANODBC_NO_MOVE_CTOR
+    /// Move constructor.
+    transaction(transaction&& rhs) NANODBC_NOEXCEPT;
+#endif
+
+    /// Assignment.
+    transaction& operator=(transaction rhs);
+
+    /// Member swap.
+    void swap(transaction& rhs) NANODBC_NOEXCEPT;
+
+    /// \brief If this transaction has not been committed, will will rollback any modifying ops.
+    ~transaction() NANODBC_NOEXCEPT;
+
+    /// \brief Commits transaction immediately.
+    /// \throws database_error
+    void commit();
+
+    /// \brief Marks this transaction for rollback.
+    void rollback() NANODBC_NOEXCEPT;
+
+    /// Returns the connection object.
+    class connection& connection();
+
+    /// Returns the connection object.
+    const class connection& connection() const;
+
+    /// Returns the connection object.
+    operator class connection&();
+
+    /// Returns the connection object.
+    operator const class connection&() const;
+
+private:
+    class transaction_impl;
+    friend class nanodbc::connection;
+
+private:
+    std::shared_ptr<transaction_impl> impl_;
+};
+
+// clang-format off
+//  .d8888b.  888             888                                            888
+// d88P  Y88b 888             888                                            888
+// Y88b.      888             888                                            888
+//  "Y888b.   888888  8888b.  888888 .d88b.  88888b.d88b.   .d88b.  88888b.  888888
+//     "Y88b. 888        "88b 888   d8P  Y8b 888 "888 "88b d8P  Y8b 888 "88b 888
+//       "888 888    .d888888 888   88888888 888  888  888 88888888 888  888 888
+// Y88b  d88P Y88b.  888  888 Y88b. Y8b.     888  888  888 Y8b.     888  888 Y88b.
+//  "Y8888P"   "Y888 "Y888888  "Y888 "Y8888  888  888  888  "Y8888  888  888  "Y888
+// MARK: Statement -
+// clang-format on
+
+/// \brief Represents a statement on the database.
+class statement
+{
+public:
+    /// \brief Provides support for retrieving output/return parameters.
+    /// \see binding
+    enum param_direction
+    {
+        PARAM_IN,    ///< Binding an input parameter.
+        PARAM_OUT,   ///< Binding an output parameter.
+        PARAM_INOUT, ///< Binding an input/output parameter.
+        PARAM_RETURN ///< Binding a return parameter.
+    };
+
+public:
+    /// \brief Creates a new un-prepared statement.
+    /// \see execute(), just_execute(), execute_direct(), just_execute_direct(), open(), prepare()
+    statement();
+
+    /// \brief Constructs a statement object and associates it to the given connection.
+    /// \param conn The connection to use.
+    /// \see open(), prepare()
+    explicit statement(class connection& conn);
+
+    /// \brief Constructs and prepares a statement using the given connection and query.
+    /// \param conn The connection to use.
+    /// \param query The SQL query statement.
+    /// \param timeout The number in seconds before query timeout. Default: 0 meaning no timeout.
+    /// \see execute(), just_execute(), execute_direct(), just_execute_direct(), open(), prepare()
+    statement(class connection& conn, const string& query, long timeout = 0);
+
+    /// \brief Copy constructor.
+    statement(const statement& rhs);
+
+#ifndef NANODBC_NO_MOVE_CTOR
+    /// \brief Move constructor.
+    statement(statement&& rhs) NANODBC_NOEXCEPT;
+#endif
+
+    /// \brief Assignment.
+    statement& operator=(statement rhs);
+
+    /// \brief Member swap.
+    void swap(statement& rhs) NANODBC_NOEXCEPT;
+
+    /// \brief Closes the statement.
+    /// \see close()
+    ~statement() NANODBC_NOEXCEPT;
+
+    /// \brief Creates a statement for the given connection.
+    /// \param conn The connection where the statement will be executed.
+    /// \throws database_error
+    void open(class connection& conn);
+
+    /// \brief Returns true if connection is open.
+    bool open() const;
+
+    /// \brief Returns true if connected to the database.
+    bool connected() const;
+
+    /// \brief Returns the associated connection object if any.
+    class connection& connection();
+
+    /// \brief Returns the associated connection object if any.
+    const class connection& connection() const;
+
+    /// \brief Returns the native ODBC statement handle.
+    void* native_statement_handle() const;
+
+    /// \brief Closes the statement and frees all associated resources.
+    void close();
+
+    /// \brief Cancels execution of the statement.
+    /// \throws database_error
+    void cancel();
+
+    /// \brief Opens and prepares the given statement to execute on the given connection.
+    /// \param conn The connection where the statement will be executed.
+    /// \param query The SQL query that will be executed.
+    /// \param timeout The number in seconds before query timeout. Default 0 meaning no timeout.
+    /// \see open()
+    /// \throws database_error
+    void prepare(class connection& conn, const string& query, long timeout = 0);
+
+    /// \brief Prepares the given statement to execute its associated connection.
+    /// \note If the statement is not open throws programming_error.
+    /// \param query The SQL query that will be executed.
+    /// \param timeout The number in seconds before query timeout. Default 0 meaning no timeout.
+    /// \see open()
+    /// \throws database_error, programming_error
+    void prepare(const string& query, long timeout = 0);
+
+    /// \brief Sets the number in seconds before query timeout. Default is 0 indicating no timeout.
+    /// \throws database_error
+    void timeout(long timeout = 0);
+
+    /// \brief Opens, prepares, and executes the given query directly on the given connection.
+    /// \param conn The connection where the statement will be executed.
+    /// \param query The SQL query that will be executed.
+    /// \param batch_operations Numbers of rows to fetch per rowset, or the number of batch
+    ///        parameters to process.
+    /// \param timeout The number in seconds before query timeout. Default 0 meaning no timeout.
+    /// \return A result set object.
+    /// \attention You will want to use transactions if you are doing batch operations because it
+    ///            will prevent auto commits occurring after each individual operation is executed.
+    /// \see open(), prepare(), execute(), result, transaction
+    class result execute_direct(
+        class connection& conn,
+        const string& query,
+        long batch_operations = 1,
+        long timeout = 0);
+
+#if !defined(NANODBC_DISABLE_ASYNC)
+    /// \brief Prepare the given statement, in asynchronous mode.
+    /// \note If the statement is not open throws programming_error.
+    ///
+    /// This method will only be available if nanodbc is built against ODBC headers and library that
+    /// supports asynchronous mode. Such that the identifiers `SQL_ATTR_ASYNC_STMT_EVENT` and
+    /// `SQLCompleteAsync` are extant. Otherwise this method will be defined, but not implemented.
+    ///
+    /// Asynchronous features can be disabled entirely by defining `NANODBC_DISABLE_ASYNC` when
+    /// building nanodbc.
+    ///
+    /// \param event_handle The event handle the caller will wait before calling complete_prepare.
+    /// \param query The SQL query that will be prepared.
+    /// \param timeout The number in seconds before query timeout. Default 0 meaning no timeout.
+    /// \throws database_error
+    /// \return Boolean: true if the event handle needs to be awaited, false is result is ready now.
+    /// \see complete_prepare()
+    bool async_prepare(const string& query, void* event_handle, long timeout = 0);
+
+    /// \brief Completes a previously initiated asynchronous query preparation.
+    ///
+    /// This method will only be available if nanodbc is built against ODBC headers and library that
+    /// supports asynchronous mode. Such that the identifiers `SQL_ATTR_ASYNC_STMT_EVENT` and
+    /// `SQLCompleteAsync` are extant. Otherwise this method will be defined, but not implemented.
+    ///
+    /// Asynchronous features can be disabled entirely by defining `NANODBC_DISABLE_ASYNC` when
+    /// building nanodbc.
+    ///
+    /// \throws database_error
+    /// \see async_prepare()
+    void complete_prepare();
+
+    /// \brief Opens, prepares, and executes query directly on the given connection, in async mode.
+    ///
+    /// This method will only be available if nanodbc is built against ODBC headers and library that
+    /// supports asynchronous mode. Such that the identifiers `SQL_ATTR_ASYNC_STMT_EVENT` and
+    /// `SQLCompleteAsync` are extant. Otherwise this method will be defined, but not implemented.
+    ///
+    /// Asynchronous features can be disabled entirely by defining `NANODBC_DISABLE_ASYNC` when
+    /// building nanodbc.
+    ///
+    /// \param conn The connection where the statement will be executed.
+    /// \param event_handle The event handle the caller will wait before calling complete_execute.
+    /// \param query The SQL query that will be executed.
+    /// \param batch_operations Rows to fetch per rowset or number of batch parameters to process.
+    /// \param timeout The number in seconds before query timeout. Default 0 meaning no timeout.
+    /// \throws database_error
+    /// \return Boolean: true if event handle needs to be awaited, false if result ready now.
+    /// \attention You will want to use transactions if you are doing batch operations because it
+    ///            will prevent auto commits after each individual operation is executed.
+    /// \see complete_execute(), open(), prepare(), execute(), result, transaction
+    bool async_execute_direct(
+        class connection& conn,
+        void* event_handle,
+        const string& query,
+        long batch_operations = 1,
+        long timeout = 0);
+
+    /// \brief Execute the previously prepared query now, in asynchronous mode.
+    ///
+    /// This method will only be available if nanodbc is built against ODBC headers and library that
+    /// supports asynchronous mode. Such that the identifiers `SQL_ATTR_ASYNC_STMT_EVENT` and
+    /// `SQLCompleteAsync` are extant. Otherwise this method will be defined, but not implemented.
+    ///
+    /// Asynchronous features can be disabled entirely by defining `NANODBC_DISABLE_ASYNC` when
+    /// building nanodbc.
+    ///
+    /// \param event_handle The event handle the caller will wait before calling complete_execute.
+    /// \param batch_operations Rows to fetch per rowset or number of batch parameters to process.
+    /// \param timeout The number in seconds before query timeout. Default 0 meaning no timeout.
+    /// \throws database_error
+    /// \return Boolean: true if event handle needs to be awaited, false if result is ready now.
+    /// \attention You will want to use transactions if you are doing batch operations because it
+    ///            will prevent auto commits after each individual operation is executed.
+    /// \see complete_execute(), open(), prepare(), result, transaction
+    bool async_execute(void* event_handle, long batch_operations = 1, long timeout = 0);
+
+    /// \brief Completes a previously initiated asynchronous query execution, returning the result.
+    ///
+    /// This method will only be available if nanodbc is built against ODBC headers and library that
+    /// supports asynchronous mode. Such that the identifiers `SQL_ATTR_ASYNC_STMT_EVENT` and
+    /// `SQLCompleteAsync` are extant. Otherwise this method will be defined, but not implemented.
+    ///
+    /// Asynchronous features can be disabled entirely by defining `NANODBC_DISABLE_ASYNC` when
+    /// building nanodbc.
+    ///
+    /// \throws database_error
+    /// \return A result set object.
+    /// \param batch_operations Rows to fetch per rowset or number of batch parameters to process.
+    /// \see async_execute(), async_execute_direct()
+    class result complete_execute(long batch_operations = 1);
+
+    /// \brief Completes a previously initiated asynchronous query execution, returning the result.
+    ///
+    /// \deprecated Use complete_execute instead.
+    NANODBC_DEPRECATED class result async_complete(long batch_operations = 1);
+
+    /// undocumented - for internal use only (used from result_impl)
+    void enable_async(void* event_handle);
+
+    /// undocumented - for internal use only (used from result_impl)
+    void disable_async() const;
+#endif
+
+    /// \brief Execute the previously prepared query now without constructing result object.
+    /// \param conn The connection where the statement will be executed.
+    /// \param query The SQL query that will be executed.
+    /// \param batch_operations Rows to fetch per rowset, or number of batch parameters to process.
+    /// \param timeout Seconds before query timeout. Default is 0 indicating no timeout.
+    /// \throws database_error
+    /// \return A result set object.
+    /// \attention You will want to use transactions if you are doing batch operations because it
+    ///            will prevent auto commits after each individual operation is executed.
+    /// \see open(), prepare(), execute(), execute_direct(), result, transaction
+    void just_execute_direct(
+        class connection& conn,
+        const string& query,
+        long batch_operations = 1,
+        long timeout = 0);
+
+    /// \brief Execute the previously prepared query now.
+    /// \param batch_operations Rows to fetch per rowset, or number of batch parameters to process.
+    /// \param timeout The number in seconds before query timeout. Default 0 meaning no timeout.
+    /// \throws database_error
+    /// \return A result set object.
+    /// \attention You will want to use transactions if you are doing batch operations because it
+    ///            will prevent auto commits after each individual operation is executed.
+    /// \see open(), prepare(), result, transaction
+    class result execute(long batch_operations = 1, long timeout = 0);
+
+    /// \brief Execute the previously prepared query now without constructing result object.
+    /// \param batch_operations Rows to fetch per rowset, or number of batch parameters to process.
+    /// \param timeout The number in seconds before query timeout. Default 0 meaning no timeout.
+    /// \throws database_error
+    /// \return A result set object.
+    /// \attention You will want to use transactions if you are doing batch operations because it
+    ///            will prevent auto commits after each individual operation is executed.
+    /// \see open(), prepare(), execute(), result, transaction
+    void just_execute(long batch_operations = 1, long timeout = 0);
+
+    /// \brief Returns the input and output paramters of the specified stored procedure.
+    /// \param catalog The catalog name of the procedure.
+    /// \param schema Pattern to use for schema names.
+    /// \param procedure The name of the procedure.
+    /// \param column Pattern to use for column names.
+    /// \throws database_error
+    /// \return A result set object.
+    class result procedure_columns(
+        const string& catalog,
+        const string& schema,
+        const string& procedure,
+        const string& column);
+
+    /// \brief Returns rows affected by the request or -1 if affected rows is not available.
+    /// \throws database_error
+    long affected_rows() const;
+
+    /// \brief Returns the number of columns in a result set.
+    /// \throws database_error
+    short columns() const;
+
+    /// \brief Resets all currently bound parameters.
+    void reset_parameters() NANODBC_NOEXCEPT;
+
+    /// \brief Returns the number of parameters in the statement.
+    /// \throws database_error
+    short parameters() const;
+
+    /// \brief Returns parameter size for indicated parameter placeholder in a prepared statement.
+    unsigned long parameter_size(short param_index) const;
+
+    /// \addtogroup binding Binding parameters
+    /// \brief These functions are used to bind values to ODBC parameters.
+    ///
+    /// @{
+
+    /// \brief Binds given value to given parameter placeholder number in the prepared statement.
+    ///
+    /// If your prepared SQL query has any ? placeholders, this is how you bind values to them.
+    /// Placeholder numbers count from left to right and are 0-indexed.
+    ///
+    /// It is NOT possible to use these functions for batch operations as number of elements is not
+    /// specified here.
+    ///
+    /// \param param_index Zero-based index of parameter marker (placeholder position).
+    /// \param value Value to substitute into placeholder.
+    /// \param direction ODBC parameter direction.
+    /// \throws database_error
+    template <class T>
+    void bind(short param_index, T const* value, param_direction direction = PARAM_IN);
+
+    /// \addtogroup bind_multi Binding multiple non-string values
+    /// \brief Binds given values to given parameter placeholder number in the prepared statement.
+    ///
+    /// If your prepared SQL query has any parameter markers, ? (question  mark) placeholders,
+    /// this is how you bind values to them.
+    /// Parameter markers are numbered using Zero-based index from left to right.
+    ///
+    /// It is possible to use these functions for batch operations.
+    ///
+    /// \param param_index Zero-based index of parameter marker (placeholder position).
+    /// \param values Values to substitute into placeholder.
+    /// \param batch_size The number of values being bound.
+    /// \param null_sentry Value which should represent a null value.
+    /// \param nulls Flags for values that should be set to a null value.
+    /// \param param_direciton ODBC parameter direction.
+    /// \throws database_error
+    ///
+    /// @{
+
+    /// \brief Binds multiple values.
+    /// \see bind_multi
+    template <class T>
+    void bind(
+        short param_index,
+        T const* values,
+        std::size_t batch_size,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple values.
+    /// \see bind_multi
+    template <class T>
+    void bind(
+        short param_index,
+        T const* values,
+        std::size_t batch_size,
+        T const* null_sentry,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple values.
+    /// \see bind_multi
+    template <class T>
+    void bind(
+        short param_index,
+        T const* values,
+        std::size_t batch_size,
+        bool const* nulls,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple values.
+    /// \see bind_multi
+    void bind(
+        short param_index,
+        std::vector<std::vector<uint8_t>> const& values,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple values.
+    /// \see bind_multi
+    void bind(
+        short param_index,
+        std::vector<std::vector<uint8_t>> const& values,
+        bool const* nulls,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple values.
+    /// \see bind_multi
+    void bind(
+        short param_index,
+        std::vector<std::vector<uint8_t>> const& values,
+        uint8_t const* null_sentry,
+        param_direction direction = PARAM_IN);
+
+    /// @}
+
+    /// \addtogroup bind_strings Binding multiple string values
+    /// \brief Binds given string values to parameter marker in prepared statement.
+    ///
+    /// If your prepared SQL query has any parameter markers, ? (question  mark) placeholders,
+    /// this is how you bind values to them.
+    /// Parameter markers are numbered using Zero-based index from left to right.
+    ///
+    /// It is possible to use these functions for batch operations.
+    ///
+    /// \param param_index Zero-based index of parameter marker (placeholder position).
+    /// \param values Array of values to substitute into parameter placeholders.
+    /// \param value_size Maximum length of string value in array.
+    /// \param batch_size Number of string values to bind. Otherwise template parameter BatchSize is
+    /// taken as the number of values.
+    /// \param null_sentry Value which should represent a null value.
+    /// \param nulls Flags for values that should be set to a null value.
+    /// \param param_direciton ODBC parameter direction.
+    /// \throws database_error
+    ///
+    /// @{
+
+    /// \brief Binds multiple string values.
+    /// \see bind_strings
+    void bind_strings(
+        short param_index,
+        string::value_type const* values,
+        std::size_t value_size,
+        std::size_t batch_size,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple string values.
+    ///
+    /// Size of the values vector indicates number of values to bind.
+    /// Longest string in the array determines maximum length of individual value.
+    ///
+    /// \see bind_strings
+    void bind_strings(
+        short param_index,
+        std::vector<string> const& values,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple string values.
+    /// \see bind_strings
+    template <std::size_t BatchSize, std::size_t ValueSize>
+    void bind_strings(
+        short param_index,
+        string::value_type const (&values)[BatchSize][ValueSize],
+        param_direction direction = PARAM_IN)
+    {
+        auto param_values = reinterpret_cast<string::value_type const*>(values);
+        bind_strings(param_index, param_values, ValueSize, BatchSize, direction);
+    }
+
+    /// \brief Binds multiple string values.
+    /// \see bind_strings
+    void bind_strings(
+        short param_index,
+        string::value_type const* values,
+        std::size_t value_size,
+        std::size_t batch_size,
+        string::value_type const* null_sentry,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple string values.
+    /// \see bind_strings
+    void bind_strings(
+        short param_index,
+        std::vector<string> const& values,
+        string::value_type const* null_sentry,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple string values.
+    /// \see bind_strings
+    template <std::size_t BatchSize, std::size_t ValueSize>
+    void bind_strings(
+        short param_index,
+        string::value_type const (&values)[BatchSize][ValueSize],
+        string::value_type const* null_sentry,
+        param_direction direction = PARAM_IN)
+    {
+        auto param_values = reinterpret_cast<string::value_type const*>(values);
+        bind_strings(param_index, param_values, ValueSize, BatchSize, null_sentry, direction);
+    }
+
+    /// \brief Binds multiple string values.
+    /// \see bind_strings
+    void bind_strings(
+        short param_index,
+        string::value_type const* values,
+        std::size_t value_size,
+        std::size_t batch_size,
+        bool const* nulls,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple string values.
+    /// \see bind_strings
+    void bind_strings(
+        short param_index,
+        std::vector<string> const& values,
+        bool const* nulls,
+        param_direction direction = PARAM_IN);
+
+    /// \brief Binds multiple string values.
+    /// \see bind_strings
+    template <std::size_t BatchSize, std::size_t ValueSize>
+    void bind_strings(
+        short param_index,
+        string::value_type const (&values)[BatchSize][ValueSize],
+        bool const* nulls,
+        param_direction direction = PARAM_IN)
+    {
+        auto param_values = reinterpret_cast<string::value_type const*>(values);
+        bind_strings(param_index, param_values, ValueSize, BatchSize, nulls, direction);
+    }
+
+    /// @}
+
+    /// \brief Binds null values to the parameter placeholder number in the prepared statement.
+    ///
+    /// If your prepared SQL query has any parameter markers, ? (question  mark) placeholders,
+    /// this is how you bind values to them.
+    /// Parameter markers are numbered using Zero-based index from left to right.
+    ///
+    /// It is possible to use this function for batch operations.
+    ///
+    /// \param param_index Zero-based index of parameter marker (placeholder position).
+    /// \param batch_size The number of elements being bound.
+    /// \throws database_error
+    void bind_null(short param_index, std::size_t batch_size = 1);
+
+    /// @}
+
+private:
+    typedef std::function<bool(std::size_t)> null_predicate_type;
+
+private:
+    class statement_impl;
+    friend class nanodbc::result;
+
+private:
+    std::shared_ptr<statement_impl> impl_;
+};
+
+// clang-format off
+//  .d8888b.                                               888    d8b
+// d88P  Y88b                                              888    Y8P
+// 888    888                                              888
+// 888         .d88b.  88888b.  88888b.   .d88b.   .d8888b 888888 888  .d88b.  88888b.
+// 888        d88""88b 888 "88b 888 "88b d8P  Y8b d88P"    888    888 d88""88b 888 "88b
+// 888    888 888  888 888  888 888  888 88888888 888      888    888 888  888 888  888
+// Y88b  d88P Y88..88P 888  888 888  888 Y8b.     Y88b.    Y88b.  888 Y88..88P 888  888
+//  "Y8888P"   "Y88P"  888  888 888  888  "Y8888   "Y8888P  "Y888 888  "Y88P"  888  888
+// MARK: Connection -
+// clang-format on
+
+/// \brief Manages and encapsulates ODBC resources such as the connection and environment handles.
+class connection
+{
+public:
+    /// \brief Create new connection object, initially not connected.
+    connection();
+
+    /// Copy constructor.
+    connection(const connection& rhs);
+
+#ifndef NANODBC_NO_MOVE_CTOR
+    /// Move constructor.
+    connection(connection&& rhs) NANODBC_NOEXCEPT;
+#endif
+
+    /// Assignment.
+    connection& operator=(connection rhs);
+
+    /// Member swap.
+    void swap(connection&) NANODBC_NOEXCEPT;
+
+    /// \brief Create new connection object and immediately connect to the given data source.
+    /// \param dsn The name of the data source.
+    /// \param user The username for authenticating to the data source.
+    /// \param pass The password for authenticating to the data source.
+    /// \param timeout Seconds before connection timeout. Default 0 meaning no timeout.
+    /// \throws database_error
+    /// \see connected(), connect()
+    connection(const string& dsn, const string& user, const string& pass, long timeout = 0);
+
+    /// \brief Create new connection object and immediately connect using the given connection
+    /// string.
+    /// \param connection_string The connection string for establishing a connection.
+    /// \param timeout Seconds before connection timeout. Default is 0 indicating no timeout.
+    /// \throws database_error
+    /// \see connected(), connect()
+    connection(const string& connection_string, long timeout = 0);
+
+    /// \brief Automatically disconnects from the database and frees all associated resources.
+    ///
+    /// Will not throw even if disconnecting causes some kind of error and raises an exception.
+    /// If you explicitly need to know if disconnect() succeeds, call it directly.
+    ~connection() NANODBC_NOEXCEPT;
+
+    /// \brief Connect to the given data source.
+    /// \param dsn The name of the data source.
+    /// \param user The username for authenticating to the data source.
+    /// \param pass The password for authenticating to the data source.
+    /// \param timeout Seconds before connection timeout. Default is 0 indicating no timeout.
+    /// \throws database_error
+    /// \see connected()
+    void connect(const string& dsn, const string& user, const string& pass, long timeout = 0);
+
+    /// \brief Connect using the given connection string.
+    /// \param connection_string The connection string for establishing a connection.
+    /// \param timeout Seconds before connection timeout. Default is 0 indicating no timeout.
+    /// \throws database_error
+    /// \see connected()
+    void connect(const string& connection_string, long timeout = 0);
+
+#if !defined(NANODBC_DISABLE_ASYNC)
+    /// \brief Initiate an asynchronous connection operation to the given data source.
+    ///
+    /// This method will only be available if nanodbc is built against ODBC headers and library that
+    /// supports asynchronous mode. Such that the identifiers `SQL_ATTR_ASYNC_DBC_EVENT` and
+    /// `SQLCompleteAsync` are extant. Otherwise this method will be defined, but not implemented.
+    ///
+    /// Asynchronous features can be disabled entierly by defining `NANODBC_DISABLE_ASYNC` when
+    /// building nanodbc.
+    ///
+    /// \param dsn The name of the data source.
+    /// \param user The username for authenticating to the data source.
+    /// \param pass The password for authenticating to the data source.
+    /// \param event_handle The event handle the caller will wait before calling async_complete.
+    /// \param timeout Seconds before connection timeout. Default is 0 indicating no timeout.
+    /// \throws database_error
+    /// \return Boolean: true if event handle needs to be awaited, false if connection is ready now.
+    /// \see connected()
+    bool async_connect(
+        const string& dsn,
+        const string& user,
+        const string& pass,
+        void* event_handle,
+        long timeout = 0);
+
+    /// \brief Initiate an asynchronous connection operation using the given connection string.
+    ///
+    /// This method will only be available if nanodbc is built against ODBC headers and library that
+    /// supports asynchronous mode. Such that the identifiers `SQL_ATTR_ASYNC_DBC_EVENT` and
+    /// `SQLCompleteAsync` are extant. Otherwise this method will be defined, but not implemented.
+    ///
+    /// Asynchronous features can be disabled entierly by defining `NANODBC_DISABLE_ASYNC` when
+    /// building nanodbc.
+    ///
+    /// \param connection_string The connection string for establishing a connection.
+    /// \param event_handle Event handle the caller will wait before calling async_complete.
+    /// \param timeout Seconds before connection timeout. Default is 0 indicating no timeout.
+    /// \throws database_error
+    /// \return Boolean: true if event handle needs to be awaited, false if connection is ready now.
+    /// \see connected()
+    bool async_connect(const string& connection_string, void* event_handle, long timeout = 0);
+
+    /// \brief Completes a previously initiated asynchronous connection operation.
+    ///
+    /// Asynchronous features can be disabled entierly by defining `NANODBC_DISABLE_ASYNC` when
+    /// building nanodbc.
+    void async_complete();
+#endif
+
+    /// \brief Returns true if connected to the database.
+    bool connected() const;
+
+    /// \brief Disconnects from the database, but maintains environment and handle resources.
+    void disconnect();
+
+    /// \brief Returns the number of transactions currently held for this connection.
+    std::size_t transactions() const;
+
+    /// \brief Returns the native ODBC database connection handle.
+    void* native_dbc_handle() const;
+
+    /// \brief Returns the native ODBC environment handle.
+    void* native_env_handle() const;
+
+    /// \brief Returns information from the ODBC connection as a string or fixed-size value.
+    /// The general information about the driver and data source associated
+    /// with a connection is obtained using `SQLGetInfo` function.
+    template <class T>
+    T get_info(short info_type) 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 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 dbms_version() const;
+
+    /// \brief Returns the name of the ODBC driver.
+    /// \throws database_error
+    string driver_name() const;
+
+    /// \brief Returns the name of the currently connected database.
+    /// Returns the current SQL_DATABASE_NAME information value associated with the connection.
+    string database_name() const;
+
+    /// \brief Returns the name of the current catalog.
+    /// Returns the current setting of the connection attribute SQL_ATTR_CURRENT_CATALOG.
+    string catalog_name() const;
+
+private:
+    std::size_t ref_transaction();
+    std::size_t unref_transaction();
+    bool rollback() const;
+    void rollback(bool onoff);
+
+private:
+    class connection_impl;
+    friend class nanodbc::transaction::transaction_impl;
+
+private:
+    std::shared_ptr<connection_impl> impl_;
+};
+
+// clang-format off
+// 8888888b.                            888 888
+// 888   Y88b                           888 888
+// 888    888                           888 888
+// 888   d88P .d88b.  .d8888b  888  888 888 888888
+// 8888888P" d8P  Y8b 88K      888  888 888 888
+// 888 T88b  88888888 "Y8888b. 888  888 888 888
+// 888  T88b Y8b.          X88 Y88b 888 888 Y88b.
+// 888   T88b "Y8888   88888P'  "Y88888 888  "Y888
+// MARK: Result -
+// clang-format on
+
+class catalog;
+
+/// \brief A resource for managing result sets from statement execution.
+///
+/// \see statement::execute(), statement::execute_direct()
+/// \note result objects may be copied, however all copies will refer to the same result set.
+class result
+{
+public:
+    /// \brief Empty result set.
+    result();
+
+    /// \brief Free result set.
+    ~result() NANODBC_NOEXCEPT;
+
+    /// \brief Copy constructor.
+    result(const result& rhs);
+
+#ifndef NANODBC_NO_MOVE_CTOR
+    /// \brief Move constructor.
+    result(result&& rhs) NANODBC_NOEXCEPT;
+#endif
+
+    /// \brief Assignment.
+    result& operator=(result rhs);
+
+    /// \brief Member swap.
+    void swap(result& rhs) NANODBC_NOEXCEPT;
+
+    /// \brief Returns the native ODBC statement handle.
+    void* native_statement_handle() const;
+
+    /// \brief The rowset size for this result set.
+    long rowset_size() const NANODBC_NOEXCEPT;
+
+    /// \brief Number of affected rows by the request or -1 if the affected rows is not available.
+    /// \throws database_error
+    long affected_rows() const;
+
+    /// \brief Rows in the current rowset or 0 if the number of rows is not available.
+    long rows() const NANODBC_NOEXCEPT;
+
+    /// \brief Returns the number of columns in a result set.
+    /// \throws database_error
+    short columns() const;
+
+    /// \brief Fetches the first row in the current result set.
+    /// \return true if there are more results or false otherwise.
+    /// \throws database_error
+    bool first();
+
+    /// \brief Fetches the last row in the current result set.
+    /// \return true if there are more results or false otherwise.
+    /// \throws database_error
+    bool last();
+
+    /// \brief Fetches the next row in the current result set.
+    /// \return true if there are more results or false otherwise.
+    /// \throws database_error
+    bool next();
+
+#if !defined(NANODBC_DISABLE_ASYNC)
+    /// \brief Initiates an asynchronous fetch of the next row in the current result set.
+    /// \return true if the caller needs to wait for the event to be signalled, false if
+    ///         complete_next() can be called immediately.
+    /// \throws database_error
+    bool async_next(void* event_handle);
+
+    /// \brief Completes a previously-initiated async fetch for next row in the current result set.
+    /// \return true if there are more results or false otherwise.
+    /// \throws database_error
+    bool complete_next();
+#endif
+
+    /// \brief Fetches the prior row in the current result set.
+    /// \return true if there are more results or false otherwise.
+    /// \throws database_error
+    bool prior();
+
+    /// \brief Moves to and fetches the specified row in the current result set.
+    /// \return true if there are results or false otherwise.
+    /// \throws database_error
+    bool move(long row);
+
+    /// \brief Skips a number of rows and then fetches the resulting row in the current result set.
+    /// \return true if there are results or false otherwise.
+    /// \throws database_error
+    bool skip(long rows);
+
+    /// \brief Returns the row position in the current result set.
+    unsigned long position() const;
+
+    /// \brief Returns true if there are no more results in the current result set.
+    bool at_end() const NANODBC_NOEXCEPT;
+
+    /// \brief Gets data from the given column of the current rowset.
+    ///
+    /// Columns are numbered from left to right and 0-indexed.
+    /// \param column position.
+    /// \param result The column's value will be written to this parameter.
+    /// \throws database_error, index_range_error, type_incompatible_error, null_access_error
+    template <class T>
+    void get_ref(short column, T& result) const;
+
+    /// \brief Gets data from the given column of the current rowset.
+    ///
+    /// If the data is null, fallback is returned instead.
+    ///
+    /// Columns are numbered from left to right and 0-indexed.
+    /// \param column position.
+    /// \param fallback if value is null, return fallback instead.
+    /// \param result The column's value will be written to this parameter.
+    /// \throws database_error, index_range_error, type_incompatible_error
+    template <class T>
+    void get_ref(short column, const T& fallback, T& result) const;
+
+    /// \brief Gets data from the given column by name of the current rowset.
+    ///
+    /// \param column_name column's name.
+    /// \param result The column's value will be written to this parameter.
+    /// \throws database_error, index_range_error, type_incompatible_error, null_access_error
+    template <class T>
+    void get_ref(const string& column_name, T& result) const;
+
+    /// \brief Gets data from the given column by name of the current rowset.
+    ///
+    /// If the data is null, fallback is returned instead.
+    ///
+    /// \param column_name column's name.
+    /// \param fallback if value is null, return fallback instead.
+    /// \param result The column's value will be written to this parameter.
+    /// \throws database_error, index_range_error, type_incompatible_error
+    template <class T>
+    void get_ref(const string& column_name, const T& fallback, T& result) const;
+
+    /// \brief Gets data from the given column of the current rowset.
+    ///
+    /// Columns are numbered from left to right and 0-indexed.
+    /// \param column position.
+    /// \throws database_error, index_range_error, type_incompatible_error, null_access_error
+    template <class T>
+    T get(short column) const;
+
+    /// \brief Gets data from the given column of the current rowset.
+    ///
+    /// If the data is null, fallback is returned instead.
+    ///
+    /// Columns are numbered from left to right and 0-indexed.
+    /// \param column position.
+    /// \param fallback if value is null, return fallback instead.
+    /// \throws database_error, index_range_error, type_incompatible_error
+    template <class T>
+    T get(short column, const T& fallback) const;
+
+    /// \brief Gets data from the given column by name of the current rowset.
+    ///
+    /// \param column_name column's name.
+    /// \throws database_error, index_range_error, type_incompatible_error, null_access_error
+    template <class T>
+    T get(const string& column_name) const;
+
+    /// \brief Gets data from the given column by name of the current rowset.
+    ///
+    /// If the data is null, fallback is returned instead.
+    ///
+    /// \param column_name column's name.
+    /// \param fallback if value is null, return fallback instead.
+    /// \throws database_error, index_range_error, type_incompatible_error
+    template <class T>
+    T get(const string& column_name, const T& fallback) const;
+
+    /// \brief Returns true if and only if the given column of the current rowset is null.
+    ///
+    /// There is a bug/limitation in ODBC drivers for SQL Server (and possibly others)
+    /// which causes SQLBindCol() to never write SQL_NOT_NULL to the length/indicator
+    /// buffer unless you also bind the data column. nanodbc's is_null() will return
+    /// correct values for (n)varchar(max) columns when you ensure that SQLGetData()
+    /// has been called for that column (i.e. after get() or get_ref() is called).
+    ///
+    /// Columns are numbered from left to right and 0-indexed.
+    /// \see get(), get_ref()
+    /// \param column position.
+    /// \throws database_error, index_range_error
+    bool is_null(short column) const;
+
+    /// \brief Returns true if and only if the given column by name of the current rowset is null.
+    ///
+    /// See is_null(short column) for details on a bug/limitation of some ODBC drivers.
+    /// \see is_null()
+    /// \param column_name column's name.
+    /// \throws database_error, index_range_error
+    bool is_null(const string& column_name) const;
+
+    /// \brief Returns the column number of the specified column name.
+    ///
+    /// Columns are numbered from left to right and 0-indexed.
+    /// \param column_name column's name.
+    /// \throws index_range_error
+    short column(const string& column_name) const;
+
+    /// \brief Returns the name of the specified column.
+    ///
+    /// Columns are numbered from left to right and 0-indexed.
+    /// \param column position.
+    /// \throws index_range_error
+    string column_name(short column) const;
+
+    /// \brief Returns the size of the specified column.
+    ///
+    /// Columns are numbered from left to right and 0-indexed.
+    /// \param column position.
+    /// \throws index_range_error
+    long column_size(short column) const;
+
+    /// \brief Returns the size of the specified column by name.
+    long column_size(const string& column_name) const;
+
+    /// \brief Returns the number of decimal digits of the specified column.
+    ///
+    /// Applies to exact numeric types (scale), datetime and interval types (prcision).
+    /// If the number cannot be determined or is not applicable, drivers typically return 0.
+    ///
+    /// Columns are numbered from left to right and 0-indexed.
+    /// \param column position.
+    /// \throws index_range_error
+    int column_decimal_digits(short column) const;
+
+    /// \brief Returns the number of decimal digits of the specified column by name.
+    int column_decimal_digits(const string& column_name) const;
+
+    /// \brief Returns a identifying integer value representing the SQL type of this column.
+    int column_datatype(short column) const;
+
+    /// \brief Returns a identifying integer value representing the SQL type of this column by name.
+    int column_datatype(const string& column_name) const;
+
+    /// \brief Returns data source dependent data type name of this column.
+    ///
+    /// The function calls SQLCoLAttribute with the field attribute SQL_DESC_TYPE_NAME to
+    /// obtain the data type name.
+    /// If the type is unknown, an empty string is returned.
+    /// \note Unlike other column metadata functions (eg. column_datatype()),
+    /// this function cost is an extra ODBC API call.
+    string column_datatype_name(short column) const;
+
+    /// \brief Returns data source dependent data type name of this column by name.
+    ///
+    /// The function calls SQLCoLAttribute with the field attribute SQL_DESC_TYPE_NAME to
+    /// obtain the data type name.
+    /// If the type is unknown, an empty string is returned.
+    /// \note Unlike other column metadata functions (eg. column_datatype()),
+    /// this function cost is an extra ODBC API call.
+    string column_datatype_name(const string& column_name) const;
+
+    /// \brief Returns a identifying integer value representing the C type of this column.
+    int column_c_datatype(short column) const;
+
+    /// \brief Returns a identifying integer value representing the C type of this column by name.
+    int column_c_datatype(const string& column_name) const;
+
+    /// \brief Returns the next result, e.g. when stored procedure returns multiple result sets.
+    bool next_result();
+
+    /// \brief If and only if result object is valid, returns true.
+    explicit operator bool() const;
+
+private:
+    result(statement statement, long rowset_size);
+
+private:
+    class result_impl;
+    friend class nanodbc::statement::statement_impl;
+    friend class nanodbc::catalog;
+
+private:
+    std::shared_ptr<result_impl> impl_;
+};
+
+/// \brief Single pass input iterator that accesses successive rows in the attached result set.
+class result_iterator
+{
+public:
+    typedef std::input_iterator_tag iterator_category; ///< Category of iterator.
+    typedef result value_type;                         ///< Values returned by iterator access.
+    typedef result* pointer;                           ///< Pointer to iteration values.
+    typedef result& reference;                         ///< Reference to iteration values.
+    typedef std::ptrdiff_t difference_type;            ///< Iterator difference.
+
+    /// Default iterator; an empty result set.
+    result_iterator() = default;
+
+    /// Create result iterator for a given result set.
+    explicit result_iterator(result& r)
+        : result_(r)
+    {
+        ++(*this);
+    }
+
+    /// Dereference.
+    reference operator*() { return result_; }
+
+    /// Access through dereference.
+    pointer operator->()
+    {
+        if (!result_)
+            throw std::runtime_error("result is empty");
+        return &(operator*());
+    }
+
+    /// Iteration.
+    result_iterator& operator++()
+    {
+        try
+        {
+            if (!result_.next())
+                result_ = result();
+        }
+        catch (...)
+        {
+            result_ = result();
+        }
+        return *this;
+    }
+
+    /// Iteration.
+    result_iterator operator++(int)
+    {
+        result_iterator tmp(*this);
+        ++(*this);
+        return tmp;
+    }
+
+    /// Iterators are equal if they a tied to the same native statemnt handle, or both empty.
+    bool operator==(result_iterator const& rhs) const
+    {
+        if (result_ && rhs.result_)
+            return result_.native_statement_handle() == rhs.result_.native_statement_handle();
+        else
+            return !result_ && !rhs.result_;
+    }
+
+    /// Iterators are not equal if they have different native statemnt handles.
+    bool operator!=(result_iterator const& rhs) const { return !(*this == rhs); }
+
+private:
+    result result_;
+};
+
+/// \brief Returns an iterator to the beginning of the given result set.
+inline result_iterator begin(result& r)
+{
+    return result_iterator(r);
+}
+
+/// \brief Returns an iterator to the end of a result set.
+///
+/// The default-constructed `nanodbc::result_iterator` is known as the end-of-result iterator.
+/// When a valid `nanodbc::result_iterator` reaches the end of the underlying result set,
+/// it becomes equal to the end-of-result iterator.
+/// Dereferencing or incrementing it further is undefined.
+inline result_iterator end(result& /*r*/)
+{
+    return result_iterator();
+}
+
+// clang-format off
+//
+//  .d8888b.           888             888
+// d88P  Y88b          888             888
+// 888    888          888             888
+// 888         8888b.  888888  8888b.  888  .d88b.   .d88b.
+// 888            "88b 888        "88b 888 d88""88b d88P"88b
+// 888    888 .d888888 888    .d888888 888 888  888 888  888
+// Y88b  d88P 888  888 Y88b.  888  888 888 Y88..88P Y88b 888
+//  "Y8888P"  "Y888888  "Y888 "Y888888 888  "Y88P"   "Y88888
+//                                                      888
+//                                                 Y8b d88P
+//                                                  "Y88P"
+// MARK: Catalog -
+// clang-format on
+
+/// \brief A resource for get catalog information from connected data source.
+///
+/// Queries are performed using the Catalog Functions in ODBC.
+/// All provided operations are convenient wrappers around the ODBC API
+/// The original ODBC behaviour should not be affected by any added processing.
+class catalog
+{
+public:
+    /// \brief Result set for a list of tables in the data source.
+    class tables
+    {
+    public:
+        bool next();                  ///< Move to the next result in the result set.
+        string table_catalog() const; ///< Fetch table catalog.
+        string table_schema() const;  ///< Fetch table schema.
+        string table_name() const;    ///< Fetch table name.
+        string table_type() const;    ///< Fetch table type.
+        string table_remarks() const; ///< Fetch table remarks.
+
+    private:
+        friend class nanodbc::catalog;
+        tables(result& find_result);
+        result result_;
+    };
+
+    /// \brief Result set for a list of columns in one or more tables.
+    class columns
+    {
+    public:
+        bool next();                           ///< Move to the next result in the result set.
+        string table_catalog() const;          ///< Fetch table catalog.
+        string table_schema() const;           ///< Fetch table schema.
+        string table_name() const;             ///< Fetch table name.
+        string column_name() const;            ///< Fetch column name.
+        short data_type() const;               ///< Fetch column data type.
+        string type_name() const;              ///< Fetch column type name.
+        long column_size() const;              ///< Fetch column size.
+        long buffer_length() const;            ///< Fetch buffer length.
+        short decimal_digits() const;          ///< Fetch decimal digits.
+        short numeric_precision_radix() const; ///< Fetch numeric precission.
+        short nullable() const;                ///< True iff column is nullable.
+        string remarks() const;                ///< Fetch column remarks.
+        string column_default() const;         ///< Fetch column's default.
+        short sql_data_type() const;           ///< Fetch column's SQL data type.
+        short sql_datetime_subtype() const;    ///< Fetch datetime subtype of column.
+        long char_octet_length() const;        ///< Fetch char octet length.
+
+        /// \brief Ordinal position of the column in the table.
+        /// The first column in the table is number 1.
+        /// Returns ORDINAL_POSITION column value in result set returned by SQLColumns.
+        long ordinal_position() const;
+
+        /// \brief Fetch column is-nullable information.
+        ///
+        /// \note 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.
+        string is_nullable() const;
+
+    private:
+        friend class nanodbc::catalog;
+        columns(result& find_result);
+        result result_;
+    };
+
+    /// \brief Result set for a list of columns that compose the primary key of a single table.
+    class primary_keys
+    {
+    public:
+        bool next();                  ///< Move to the next result in the result set.
+        string table_catalog() const; ///< Fetch table catalog.
+        string table_schema() const;  ///< Fetch table schema.
+        string table_name() const;    ///< Fetch table name.
+        string column_name() const;   ///< Fetch column name.
+
+        /// \brief Column sequence number in the key (starting with 1).
+        /// Returns valye of KEY_SEQ column in result set returned by SQLPrimaryKeys.
+        short column_number() const;
+
+        /// \brief Primary key name.
+        /// NULL if not applicable to the data source.
+        /// Returns valye of PK_NAME column in result set returned by SQLPrimaryKeys.
+        string primary_key_name() const;
+
+    private:
+        friend class nanodbc::catalog;
+        primary_keys(result& find_result);
+        result result_;
+    };
+
+    /// \brief Result set for a list of tables and the privileges associated with each table.
+    class table_privileges
+    {
+    public:
+        bool next();                  ///< Move to the next result in the result set
+        string table_catalog() const; ///< Fetch table catalog.
+        string table_schema() const;  ///< Fetch table schema.
+        string table_name() const;    ///< Fetch table name.
+        string grantor() const;       ///< Fetch name of user who granted the privilege.
+        string grantee() const;       ///< Fetch name of user whom the privilege was granted.
+        string privilege() const;     ///< Fetch the table privilege.
+        /// Fetch indicator whether the grantee is permitted to grant the privilege to other users.
+        string is_grantable() const;
+
+    private:
+        friend class nanodbc::catalog;
+        table_privileges(result& find_result);
+        result result_;
+    };
+
+    /// \brief Creates catalog operating on database accessible through the specified connection.
+    explicit catalog(connection& conn);
+
+    /// \brief Creates result set with catalogs, schemas, tables, or table types.
+    ///
+    /// Tables information is obtained by executing `SQLTable` function within
+    /// scope of the connected database accessible with the specified connection.
+    /// Since this function is implemented in terms of the `SQLTable`s, it returns
+    /// result set ordered by TABLE_TYPE, TABLE_CAT, TABLE_SCHEM, and TABLE_NAME.
+    ///
+    /// All arguments are treated as the Pattern Value Arguments.
+    /// Empty string argument is equivalent to passing the search pattern '%'.
+    catalog::tables find_tables(
+        const string& table = string(),
+        const string& type = string(),
+        const string& schema = string(),
+        const string& catalog = string());
+
+    /// \brief Creates result set with tables and the privileges associated with each table.
+    /// Tables information is obtained by executing `SQLTablePrivileges` function within
+    /// scope of the connected database accessible with the specified connection.
+    /// Since this function is implemented in terms of the `SQLTablePrivileges`s, it returns
+    /// result set ordered by TABLE_CAT, TABLE_SCHEM, TABLE_NAME, PRIVILEGE, and GRANTEE.
+    ///
+    /// \param catalog The table catalog. It cannot contain a string search pattern.
+    /// \param schema String search pattern for schema names, treated as the Pattern Value
+    /// Arguments.
+    /// \param table String search pattern for table names, treated as the Pattern Value Arguments.
+    ///
+    /// \note Due to the fact catalog cannot is not the Pattern Value Argument,
+    ///       order of parameters is different than in the other catalog look-up functions.
+    catalog::table_privileges find_table_privileges(
+        const string& catalog,
+        const string& table = string(),
+        const string& schema = string());
+
+    /// \brief Creates result set with columns in one or more tables.
+    ///
+    /// Columns information is obtained by executing `SQLColumns` function within
+    /// scope of the connected database accessible with the specified connection.
+    /// Since this function is implemented in terms of the `SQLColumns`, it returns
+    /// result set ordered by TABLE_CAT, TABLE_SCHEM, TABLE_NAME, and ORDINAL_POSITION.
+    ///
+    /// All arguments are treated as the Pattern Value Arguments.
+    /// Empty string argument is equivalent to passing the search pattern '%'.
+    catalog::columns find_columns(
+        const string& column = string(),
+        const string& table = string(),
+        const string& schema = string(),
+        const string& catalog = string());
+
+    /// \brief Creates result set with columns that compose the primary key of a single table.
+    ///
+    /// Returns result set with column names that make up the primary key for a table.
+    /// The primary key information is obtained by executing `SQLPrimaryKey` function within
+    /// scope of the connected database accessible with the specified connection.
+    ///
+    /// All arguments are treated as the Pattern Value Arguments.
+    /// Empty string argument is equivalent to passing the search pattern '%'.
+    catalog::primary_keys find_primary_keys(
+        const string& table,
+        const string& schema = string(),
+        const string& catalog = string());
+
+    /// \brief Returns names of all catalogs (or databases) available in connected data source.
+    ///
+    /// Executes `SQLTable` function with `SQL_ALL_CATALOG` as catalog search pattern.
+    std::list<string> list_catalogs();
+
+    /// \brief Returns names of all schemas available in connected data source.
+    ///
+    /// Executes `SQLTable` function with `SQL_ALL_SCHEMAS` as schema search pattern.
+    std::list<string> list_schemas();
+
+private:
+    connection conn_;
+};
+
+/// @}
+
+// clang-format off
+// 8888888888                            8888888888                         888    d8b
+// 888                                   888                                888    Y8P
+// 888                                   888                                888
+// 8888888 888d888 .d88b.   .d88b.       8888888 888  888 88888b.   .d8888b 888888 888  .d88b.  88888b.  .d8888b
+// 888     888P"  d8P  Y8b d8P  Y8b      888     888  888 888 "88b d88P"    888    888 d88""88b 888 "88b 88K
+// 888     888    88888888 88888888      888     888  888 888  888 888      888    888 888  888 888  888 "Y8888b.
+// 888     888    Y8b.     Y8b.          888     Y88b 888 888  888 Y88b.    Y88b.  888 Y88..88P 888  888      X88
+// 888     888     "Y8888   "Y8888       888      "Y88888 888  888  "Y8888P  "Y888 888  "Y88P"  888  888  88888P'
+// MARK: Free Functions -
+// clang-format on
+
+/// \addtogroup mainf Free Functions
+/// \brief Convenience functions.
+///
+/// @{
+
+/// \brief Information on a configured ODBC driver.
+struct driver
+{
+    /// \brief Driver attributes.
+    struct attribute
+    {
+        nanodbc::string keyword; ///< Driver keyword attribute.
+        nanodbc::string value;   ///< Driver attribute value.
+    };
+
+    nanodbc::string name;            ///< Driver name.
+    std::list<attribute> attributes; ///< List of driver attributes.
+};
+
+/// \brief Returns a list of ODBC drivers on your system.
+std::list<driver> list_drivers();
+
+/// \brief Immediately opens, prepares, and executes the given query directly on the given
+/// connection.
+/// \param conn The connection where the statement will be executed.
+/// \param query The SQL query that will be executed.
+/// \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters
+/// to process.
+/// \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
+/// \return A result set object.
+/// \attention You will want to use transactions if you are doing batch operations because it will
+///            prevent auto commits from occurring after each individual operation is executed.
+/// \see open(), prepare(), execute(), result, transaction
+result execute(connection& conn, const string& query, long batch_operations = 1, long timeout = 0);
+
+/// \brief Opens, prepares, and executes query directly without creating result object.
+/// \param conn The connection where the statement will be executed.
+/// \param query The SQL query that will be executed.
+/// \param batch_operations Rows to fetch per rowset, or number of batch parameters to process.
+/// \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
+/// \return A result set object.
+/// \attention You will want to use transactions if you are doing batch operations because it will
+///            prevent auto commits from occurring after each individual operation is executed.
+/// \see open(), prepare(), execute(), result, transaction
+void just_execute(
+    connection& conn,
+    const string& query,
+    long batch_operations = 1,
+    long timeout = 0);
+
+/// \brief Execute the previously prepared query now.
+/// \param stmt The prepared statement that will be executed.
+/// \param batch_operations Rows to fetch per rowset, or the number of batch parameters to process.
+/// \throws database_error
+/// \return A result set object.
+/// \attention You will want to use transactions if you are doing batch operations because it will
+///            prevent auto commits from occurring after each individual operation is executed.
+/// \see open(), prepare(), execute(), result
+result execute(statement& stmt, long batch_operations = 1);
+
+/// \brief Execute the previously prepared query now and without creating result object.
+/// \param stmt The prepared statement that will be executed.
+/// \param batch_operations Rows to fetch per rowset, or the number of batch parameters to process.
+/// \throws database_error
+/// \return A result set object.
+/// \attention You will want to use transactions if you are doing batch operations because it will
+///            prevent auto commits from occurring after each individual operation is executed.
+/// \see open(), prepare(), execute(), result
+void just_execute(statement& stmt, long batch_operations = 1);
+
+/// \brief Execute the previously prepared query now.
+///
+/// Executes within the context of a transaction object, commits directly after execution.
+/// \param stmt The prepared statement that will be executed in batch.
+/// \param batch_operations Rows to fetch per rowset, or the number of batch parameters to process.
+/// \throws database_error
+/// \return A result set object.
+/// \see open(), prepare(), execute(), result, transaction
+result transact(statement& stmt, long batch_operations);
+
+/// \brief Execute the previously prepared query now and without creating result object.
+///
+/// Executes within the context of a transaction object, commits directly after execution.
+/// \param stmt The prepared statement that will be executed in batch.
+/// \param batch_operations Rows to fetch per rowset, or the number of batch parameters to process.
+/// \throws database_error
+/// \return A result set object.
+/// \see open(), prepare(), execute(), result, transaction
+void just_transact(statement& stmt, long batch_operations);
+
+/// \brief Prepares the given statement to execute on it associated connection.
+///
+/// If the statement is not open throws programming_error.
+/// \param stmt The prepared statement that will be executed in batch.
+/// \param query The SQL query that will be executed.
+/// \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
+/// \see open()
+/// \throws database_error, programming_error
+void prepare(statement& stmt, const string& query, long timeout = 0);
+
+/// @}
+
+} // namespace nanodbc
+
+#endif

+ 0 - 4003
Source/ThirdParty/nanodbc/src/nanodbc.cpp

@@ -1,4003 +0,0 @@
-//! \file nanodbc.cpp Implementation details.
-#ifndef DOXYGEN
-
-// ASCII art banners are helpful for code editors with a minimap display.
-// Generated with http://patorjk.com/software/taag/#p=display&v=0&f=Colossal
-
-#include "nanodbc.h"
-
-#include <algorithm>
-#include <clocale>
-#include <cstdio>
-#include <cstring>
-#include <ctime>
-#include <iomanip>
-#include <map>
-
-#ifndef __clang__
-    #include <cstdint>
-#endif
-
-// User may redefine NANODBC_ASSERT macro in nanodbc.h
-#ifndef NANODBC_ASSERT
-    #include <cassert>
-    #define NANODBC_ASSERT(expr) assert(expr)
-#endif
-
-#ifdef NANODBC_USE_BOOST_CONVERT
-    #include <boost/locale/encoding_utf.hpp>
-#else
-    #include <codecvt>
-#endif
-
-#if defined(_MSC_VER) && _MSC_VER <= 1800
-    // silence spurious Visual C++ warnings
-    #pragma warning(disable:4244) // warning about integer conversion issues.
-    #pragma warning(disable:4312) // warning about 64-bit portability issues.
-    #pragma warning(disable:4996) // warning about snprintf() deprecated.
-#endif
-
-#ifdef __APPLE__
-    // silence spurious OS X deprecation warnings
-    #define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_6
-#endif
-
-#ifdef _WIN32
-    // needs to be included above sql.h for windows
-    #define NOMINMAX
-    #include <windows.h>
-#endif
-
-#include <sql.h>
-#include <sqlext.h>
-
-// Default to ODBC version defined by NANODBC_ODBC_VERSION if provided.
-#ifndef NANODBC_ODBC_VERSION
-    #ifdef SQL_OV_ODBC3_80
-        // Otherwise, use ODBC v3.8 if it's available...
-        #define NANODBC_ODBC_VERSION SQL_OV_ODBC3_80
-    #else
-        // or fallback to ODBC v3.x.
-        #define NANODBC_ODBC_VERSION SQL_OV_ODBC3
-    #endif
-#endif
-
-// 888     888          d8b                       888
-// 888     888          Y8P                       888
-// 888     888                                    888
-// 888     888 88888b.  888  .d8888b .d88b.   .d88888  .d88b.
-// 888     888 888 "88b 888 d88P"   d88""88b d88" 888 d8P  Y8b
-// 888     888 888  888 888 888     888  888 888  888 88888888
-// Y88b. .d88P 888  888 888 Y88b.   Y88..88P Y88b 888 Y8b.
-//  "Y88888P"  888  888 888  "Y8888P "Y88P"   "Y88888  "Y8888
-// MARK: Unicode -
-
-#ifdef NANODBC_USE_UNICODE
-    #ifdef NANODBC_USE_IODBC_WIDE_STRINGS
-        #define NANODBC_TEXT(s) U ## s
-    #else
-        #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
-#endif
-
-//  .d88888b.  8888888b.  888888b.    .d8888b.       888b     d888
-// d88P" "Y88b 888  "Y88b 888  "88b  d88P  Y88b      8888b   d8888
-// 888     888 888    888 888  .88P  888    888      88888b.d88888
-// 888     888 888    888 8888888K.  888             888Y88888P888  8888b.   .d8888b 888d888 .d88b.  .d8888b
-// 888     888 888    888 888  "Y88b 888             888 Y888P 888     "88b d88P"    888P"  d88""88b 88K
-// 888     888 888    888 888    888 888    888      888  Y8P  888 .d888888 888      888    888  888 "Y8888b.
-// Y88b. .d88P 888  .d88P 888   d88P Y88b  d88P      888   "   888 888  888 Y88b.    888    Y88..88P      X88
-//  "Y88888P"  8888888P"  8888888P"   "Y8888P"       888       888 "Y888888  "Y8888P 888     "Y88P"   88888P'
-// MARK: ODBC Macros -
-
-#define NANODBC_STRINGIZE_I(text) #text
-#define NANODBC_STRINGIZE(text) NANODBC_STRINGIZE_I(text)
-
-// By making all calls to ODBC functions through this macro, we can easily get
-// runtime debugging information of which ODBC functions are being called,
-// in what order, and with what parameters by defining NANODBC_ODBC_API_DEBUG.
-#ifdef NANODBC_ODBC_API_DEBUG
-    #include <iostream>
-    #define NANODBC_CALL_RC(FUNC, RC, ...)                                    \
-        do                                                                    \
-        {                                                                     \
-            std::cerr << __FILE__ ":" NANODBC_STRINGIZE(__LINE__) " "         \
-                NANODBC_STRINGIZE(FUNC) "(" #__VA_ARGS__ ")" << std::endl;    \
-            RC = FUNC(__VA_ARGS__);                                           \
-        } while(false)                                                        \
-        /**/
-    #define NANODBC_CALL(FUNC, ...)                                           \
-        do                                                                    \
-        {                                                                     \
-            std::cerr << __FILE__ ":" NANODBC_STRINGIZE(__LINE__) " "         \
-                NANODBC_STRINGIZE(FUNC) "(" #__VA_ARGS__ ")" << std::endl;    \
-            FUNC(__VA_ARGS__);                                                \
-        } while(false)                                                        \
-        /**/
-#else
-    #define NANODBC_CALL_RC(FUNC, RC, ...) RC = FUNC(__VA_ARGS__)
-    #define NANODBC_CALL(FUNC, ...) FUNC(__VA_ARGS__)
-#endif
-
-// 8888888888                                      888    888                        888 888 d8b
-// 888                                             888    888                        888 888 Y8P
-// 888                                             888    888                        888 888
-// 8888888    888d888 888d888 .d88b.  888d888      8888888888  8888b.  88888b.   .d88888 888 888 88888b.   .d88b.
-// 888        888P"   888P"  d88""88b 888P"        888    888     "88b 888 "88b d88" 888 888 888 888 "88b d88P"88b
-// 888        888     888    888  888 888          888    888 .d888888 888  888 888  888 888 888 888  888 888  888
-// 888        888     888    Y88..88P 888          888    888 888  888 888  888 Y88b 888 888 888 888  888 Y88b 888
-// 8888888888 888     888     "Y88P"  888          888    888 "Y888888 888  888  "Y88888 888 888 888  888  "Y88888
-//                                                                                                             888
-//                                                                                                        Y8b d88P
-//                                                                                                         "Y88P"
-// MARK: Error Handling -
-
-namespace
-{
-    #ifdef NANODBC_ODBC_API_DEBUG
-        inline nanodbc::string_type return_code(RETCODE rc)
-        {
-            switch(rc)
-            {
-                case SQL_SUCCESS: return NANODBC_TEXT("SQL_SUCCESS");
-                case SQL_SUCCESS_WITH_INFO: return NANODBC_TEXT("SQL_SUCCESS_WITH_INFO");
-                case SQL_ERROR: return NANODBC_TEXT("SQL_ERROR");
-                case SQL_INVALID_HANDLE: return NANODBC_TEXT("SQL_INVALID_HANDLE");
-                case SQL_NO_DATA: return NANODBC_TEXT("SQL_NO_DATA");
-                case SQL_NEED_DATA: return NANODBC_TEXT("SQL_NEED_DATA");
-                case SQL_STILL_EXECUTING: return NANODBC_TEXT("SQL_STILL_EXECUTING");
-            }
-            NANODBC_ASSERT(0);
-            return "unknown"; // should never make it here
-        }
-    #endif
-
-    // Easy way to check if a return code signifies success.
-    inline bool success(RETCODE rc)
-    {
-        #ifdef NANODBC_ODBC_API_DEBUG
-            std::cerr << "<-- rc: " << return_code(rc) << " | ";
-        #endif
-        return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO;
-    }
-
-    // Returns the array size.
-    template<typename T, std::size_t N>
-    inline std::size_t arrlen(T(&)[N])
-    {
-        return N;
-    }
-
-    // Operates like strlen() on a character array.
-    template<typename T, std::size_t N>
-    inline std::size_t strarrlen(T(&a)[N])
-    {
-        const T* s = &a[0];
-        std::size_t i = 0;
-        while(*s++ && i < N) i++;
-        return i;
-    }
-
-    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
-            #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
-                #ifdef NANODBC_USE_IODBC_WIDE_STRINGS
-                    auto p = reinterpret_cast<int32_t const*>(in.data());
-                    out = std::wstring_convert<NANODBC_CODECVT_TYPE<int32_t>, int32_t>().to_bytes(p, p + in.size());
-                #else
-                    auto p = reinterpret_cast<int16_t const*>(in.data());
-                    out = std::wstring_convert<NANODBC_CODECVT_TYPE<int16_t>, int16_t>().to_bytes(p, p + in.size());
-                #endif
-            #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, wide_string_type& out)
-        {
-            #ifdef NANODBC_USE_BOOST_CONVERT
-                using boost::locale::conv::utf_to_utf;
-                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
-                #ifdef NANODBC_USE_IODBC_WIDE_STRINGS
-                    auto s = std::wstring_convert<NANODBC_CODECVT_TYPE<int32_t>, int32_t>().from_bytes(in);
-                    auto p = reinterpret_cast<int32_t const*>(s.data());
-                #else
-                    auto s = std::wstring_convert<NANODBC_CODECVT_TYPE<int16_t>, int16_t>().from_bytes(in);
-                    auto p = reinterpret_cast<int16_t const*>(s.data());
-                #endif
-                out.assign(p, p + s.size());
-            #else
-                out = std::wstring_convert<NANODBC_CODECVT_TYPE<wide_char_t>, wide_char_t>().from_bytes(in);
-            #endif
-        }
-
-        inline void convert(const wide_string_type& in, wide_string_type& out)
-        {
-            out = in;
-        }
-    #else
-        inline void convert(const std::string& in, std::string& out)
-        {
-            out = in;
-        }
-    #endif
-
-    // Attempts to get the most recent ODBC error as a string.
-    // Always returns std::string, even in unicode mode.
-    inline std::string recent_error(
-        SQLHANDLE handle
-        , SQLSMALLINT handle_type
-        , long &native
-        , std::string &state)
-    {
-        nanodbc::string_type result;
-        std::string rvalue;
-        std::vector<NANODBC_SQLCHAR> sql_message(SQL_MAX_MESSAGE_LENGTH);
-        sql_message[0] = '\0';
-
-        SQLINTEGER i = 1;
-        SQLINTEGER native_error;
-        SQLSMALLINT total_bytes;
-        NANODBC_SQLCHAR sql_state[6];
-        RETCODE rc;
-
-        do
-        {
-            NANODBC_CALL_RC(
-                NANODBC_FUNC(SQLGetDiagRec)
-                , rc
-                , handle_type
-                , handle
-                , (SQLSMALLINT)i
-                , sql_state
-                , &native_error
-                , 0
-                , 0
-                , &total_bytes);
-
-            if(success(rc) && total_bytes > 0)
-                sql_message.resize(total_bytes + 1);
-
-            if(rc == SQL_NO_DATA)
-                break;
-
-            NANODBC_CALL_RC(
-                NANODBC_FUNC(SQLGetDiagRec)
-                , rc
-                , handle_type
-                , handle
-                , (SQLSMALLINT)i
-                , sql_state
-                , &native_error
-                , sql_message.data()
-                , (SQLSMALLINT)sql_message.size()
-                , &total_bytes);
-
-            if(!success(rc))
-            {
-                convert(result, rvalue);
-                return rvalue;
-            }
-
-            if(!result.empty())
-                result += ' ';
-
-            result += nanodbc::string_type(sql_message.begin(), sql_message.end());
-            i++;
-
-            // NOTE: unixODBC using PostgreSQL and SQLite drivers crash if you call SQLGetDiagRec()
-            // more than once. So as a (terrible but the best possible) workaround just exit
-            // this loop early on non-Windows systems.
-            #ifndef _MSC_VER
-                break;
-            #endif
-        } while(rc != SQL_NO_DATA);
-
-        convert(result, rvalue);
-        state = std::string(&sql_state[0], &sql_state[arrlen(sql_state) - 1]);
-        native = native_error;
-        std::string status = state;
-        status += ": ";
-        status += rvalue;
-
-        // some drivers insert \0 into error messages for unknown reasons
-        using std::replace;
-        replace(status.begin(), status.end(), '\0', ' ');
-
-        return status;
-    }
-} // namespace
-
-namespace nanodbc
-{
-    type_incompatible_error::type_incompatible_error()
-    : std::runtime_error("type incompatible") { }
-
-    const char* type_incompatible_error::what() const NANODBC_NOEXCEPT
-    {
-        return std::runtime_error::what();
-    }
-
-    null_access_error::null_access_error()
-    : std::runtime_error("null access") { }
-
-    const char* null_access_error::what() const NANODBC_NOEXCEPT
-    {
-        return std::runtime_error::what();
-    }
-
-    index_range_error::index_range_error()
-    : std::runtime_error("index out of range") { }
-
-    const char* index_range_error::what() const NANODBC_NOEXCEPT
-    {
-        return std::runtime_error::what();
-    }
-
-    programming_error::programming_error(const std::string& info)
-    : std::runtime_error(info.c_str()) { }
-
-    const char* programming_error::what() const NANODBC_NOEXCEPT
-    {
-        return std::runtime_error::what();
-    }
-
-    database_error::database_error(void* handle, short handle_type, const std::string& info)
-    : std::runtime_error(info), native_error(0), sql_state("00000")
-    {
-        message = std::string(std::runtime_error::what()) + recent_error(handle, handle_type, native_error, sql_state);
-    }
-
-    const char* database_error::what() const NANODBC_NOEXCEPT
-    {
-        return message.c_str();
-    }
-
-    const long database_error::native() const NANODBC_NOEXCEPT
-    {
-        return native_error;
-    }
-
-    const std::string database_error::state() const NANODBC_NOEXCEPT
-    {
-        return sql_state;
-    }
-
-} // namespace nanodbc
-
-// Throwing exceptions using NANODBC_THROW_DATABASE_ERROR enables file name
-// and line numbers to be inserted into the error message. Useful for debugging.
-#define NANODBC_THROW_DATABASE_ERROR(handle, handle_type)                     \
-    throw nanodbc::database_error(                                            \
-        handle                                                                \
-        , handle_type                                                         \
-        , __FILE__ ":" NANODBC_STRINGIZE(__LINE__) ": ")                      \
-    /**/
-
-// 8888888b.           888             d8b 888
-// 888  "Y88b          888             Y8P 888
-// 888    888          888                 888
-// 888    888  .d88b.  888888  8888b.  888 888 .d8888b
-// 888    888 d8P  Y8b 888        "88b 888 888 88K
-// 888    888 88888888 888    .d888888 888 888 "Y8888b.
-// 888  .d88P Y8b.     Y88b.  888  888 888 888      X88
-// 8888888P"   "Y8888   "Y888 "Y888888 888 888  88888P'
-// MARK: Details -
-
-namespace
-{
-    using namespace std; // if int64_t is in std namespace (in c++11)
-
-    // A utility for calculating the ctype from the given type T.
-    // I essentially create a lookup table based on the MSDN ODBC documentation.
-    // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms714556(v=vs.85).aspx
-    template<class T>
-    struct sql_ctype { };
-
-    template<>
-    struct sql_ctype<nanodbc::string_type::value_type>
-    {
-        #ifdef NANODBC_USE_UNICODE
-            static const SQLSMALLINT value = SQL_C_WCHAR;
-        #else
-            static const SQLSMALLINT value = SQL_C_CHAR;
-        #endif
-    };
-
-    template<>
-    struct sql_ctype<short>
-    {
-        static const SQLSMALLINT value = SQL_C_SSHORT;
-    };
-
-    template<>
-    struct sql_ctype<unsigned short>
-    {
-        static const SQLSMALLINT value = SQL_C_USHORT;
-    };
-
-    template<>
-    struct sql_ctype<int32_t>
-    {
-        static const SQLSMALLINT value = SQL_C_SLONG;
-    };
-
-    template<>
-    struct sql_ctype<uint32_t>
-    {
-        static const SQLSMALLINT value = SQL_C_ULONG;
-    };
-
-    template<>
-    struct sql_ctype<int64_t>
-    {
-        static const SQLSMALLINT value = SQL_C_SBIGINT;
-    };
-
-    template<>
-    struct sql_ctype<uint64_t>
-    {
-        static const SQLSMALLINT value = SQL_C_UBIGINT;
-    };
-
-    template<>
-    struct sql_ctype<float>
-    {
-        static const SQLSMALLINT value = SQL_C_FLOAT;
-    };
-
-    template<>
-    struct sql_ctype<double>
-    {
-        static const SQLSMALLINT value = SQL_C_DOUBLE;
-    };
-
-    template<>
-    struct sql_ctype<nanodbc::string_type>
-    {
-        #ifdef NANODBC_USE_UNICODE
-            static const SQLSMALLINT value = SQL_C_WCHAR;
-        #else
-            static const SQLSMALLINT value = SQL_C_CHAR;
-        #endif
-    };
-
-    template<>
-    struct sql_ctype<nanodbc::date>
-    {
-        static const SQLSMALLINT value = SQL_C_DATE;
-    };
-
-    template<>
-    struct sql_ctype<nanodbc::timestamp>
-    {
-        static const SQLSMALLINT value = SQL_C_TIMESTAMP;
-    };
-
-    // Encapsulates resources needed for column binding.
-    class bound_column
-    {
-    public:
-        bound_column(const bound_column& rhs) =delete;
-        bound_column& operator=(bound_column rhs) =delete;
-
-        bound_column()
-        : name_()
-        , column_(0)
-        , sqltype_(0)
-        , sqlsize_(0)
-        , scale_(0)
-        , ctype_(0)
-        , clen_(0)
-        , blob_(false)
-        , cbdata_(0)
-        , pdata_(0)
-        {
-
-        }
-
-        ~bound_column()
-        {
-            delete[] cbdata_;
-            delete[] pdata_;
-        }
-
-    public:
-        nanodbc::string_type name_;
-        short column_;
-        SQLSMALLINT sqltype_;
-        SQLULEN sqlsize_;
-        SQLSMALLINT scale_;
-        SQLSMALLINT ctype_;
-        SQLULEN clen_;
-        bool blob_;
-        nanodbc::null_type* cbdata_;
-        char* pdata_;
-    };
-
-    // Allocates the native ODBC handles.
-    inline void allocate_handle(SQLHENV& env, SQLHDBC& conn)
-    {
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLAllocHandle
-            , rc
-            , SQL_HANDLE_ENV
-            , SQL_NULL_HANDLE
-            , &env);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
-
-        try
-        {
-            NANODBC_CALL_RC(
-                SQLSetEnvAttr
-                , rc
-                , env
-                , SQL_ATTR_ODBC_VERSION
-                , (SQLPOINTER)NANODBC_ODBC_VERSION
-                , SQL_IS_UINTEGER);
-            if(!success(rc))
-                NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
-
-            NANODBC_CALL_RC(
-                SQLAllocHandle
-                , rc
-                , SQL_HANDLE_DBC
-                , env
-                , &conn);
-            if(!success(rc))
-                NANODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
-        }
-        catch(...)
-        {
-            NANODBC_CALL(
-                SQLFreeHandle
-                , SQL_HANDLE_ENV
-                , env);
-            throw;
-        }
-    }
-} // namespace
-
-//  .d8888b.                                               888    d8b                             8888888                        888
-// d88P  Y88b                                              888    Y8P                               888                          888
-// 888    888                                              888                                      888                          888
-// 888         .d88b.  88888b.  88888b.   .d88b.   .d8888b 888888 888  .d88b.  88888b.              888   88888b.d88b.  88888b.  888
-// 888        d88""88b 888 "88b 888 "88b d8P  Y8b d88P"    888    888 d88""88b 888 "88b             888   888 "888 "88b 888 "88b 888
-// 888    888 888  888 888  888 888  888 88888888 888      888    888 888  888 888  888             888   888  888  888 888  888 888
-// Y88b  d88P Y88..88P 888  888 888  888 Y8b.     Y88b.    Y88b.  888 Y88..88P 888  888             888   888  888  888 888 d88P 888
-//  "Y8888P"   "Y88P"  888  888 888  888  "Y8888   "Y8888P  "Y888 888  "Y88P"  888  888           8888888 888  888  888 88888P"  888
-//                                                                                                                      888
-//                                                                                                                      888
-//                                                                                                                      888
-// MARK: Connection Impl -
-
-namespace nanodbc
-{
-
-class connection::connection_impl
-{
-public:
-    connection_impl(const connection_impl&) =delete;
-    connection_impl& operator=(const connection_impl&) =delete;
-
-    connection_impl()
-    : env_(0)
-    , conn_(0)
-    , connected_(false)
-    , transactions_(0)
-    , rollback_(false)
-    {
-        allocate_handle(env_, conn_);
-    }
-
-    connection_impl(
-        const string_type& dsn
-        , const string_type& user
-        , const string_type& pass
-        , long timeout)
-    : env_(0)
-    , conn_(0)
-    , connected_(false)
-    , transactions_(0)
-    , rollback_(false)
-    {
-        allocate_handle(env_, conn_);
-        try
-        {
-            connect(dsn, user, pass, timeout);
-        }
-        catch(...)
-        {
-            NANODBC_CALL(
-                SQLFreeHandle
-                , SQL_HANDLE_DBC
-                , conn_);
-            NANODBC_CALL(
-                SQLFreeHandle
-                , SQL_HANDLE_ENV
-                , env_);
-            throw;
-        }
-    }
-
-    connection_impl(const string_type& connection_string, long timeout)
-    : env_(0)
-    , conn_(0)
-    , connected_(false)
-    , transactions_(0)
-    , rollback_(false)
-    {
-        allocate_handle(env_, conn_);
-        try
-        {
-            connect(connection_string, timeout);
-        }
-        catch(...)
-        {
-            NANODBC_CALL(
-                SQLFreeHandle
-                , SQL_HANDLE_DBC
-                , conn_);
-            NANODBC_CALL(
-                SQLFreeHandle
-                , SQL_HANDLE_ENV
-                , env_);
-            throw;
-        }
-    }
-
-    ~connection_impl() NANODBC_NOEXCEPT
-    {
-        try
-        {
-            disconnect();
-        }
-        catch(...)
-        {
-            // ignore exceptions thrown during disconnect
-        }
-        NANODBC_CALL(
-            SQLFreeHandle
-            , SQL_HANDLE_DBC
-            , conn_);
-        NANODBC_CALL(
-            SQLFreeHandle
-            , SQL_HANDLE_ENV
-            , env_);
-    }
-
-#ifdef SQL_ATTR_ASYNC_DBC_EVENT
-    void enable_async(void* event_handle)
-    {
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLSetConnectAttr
-            , rc
-            , conn_
-            , SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE
-            , (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_ON
-            , SQL_IS_INTEGER);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-
-        NANODBC_CALL_RC(
-            SQLSetConnectAttr
-            , rc
-            , conn_
-            , SQL_ATTR_ASYNC_DBC_EVENT
-            , event_handle
-            , SQL_IS_POINTER);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-    }
-
-    void async_complete()
-    {
-        RETCODE rc, arc;
-        NANODBC_CALL_RC(
-            SQLCompleteAsync
-            , rc
-            , SQL_HANDLE_DBC
-            , conn_
-            , &arc);
-        if(!success(rc) || !success(arc))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-
-        NANODBC_CALL_RC(
-            SQLSetConnectAttr
-            , rc
-            , conn_
-            , SQL_ATTR_ASYNC_ENABLE
-            , (SQLPOINTER)SQL_ASYNC_ENABLE_OFF
-            , SQL_IS_INTEGER);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-
-        connected_ = success(rc);
-    }
-#endif // SQL_ATTR_ASYNC_DBC_EVENT
-
-    void connect(
-        const string_type& dsn
-        , const string_type& user
-        , const string_type& pass
-        , long timeout
-        , void* event_handle = NULL)
-    {
-        disconnect();
-
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLFreeHandle
-            , rc
-            , SQL_HANDLE_DBC
-            , conn_);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-
-        NANODBC_CALL_RC(
-            SQLAllocHandle
-            , rc
-            , SQL_HANDLE_DBC
-            , env_
-            , &conn_);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(env_, SQL_HANDLE_ENV);
-
-        NANODBC_CALL_RC(
-            SQLSetConnectAttr
-            , rc
-            , conn_
-            , SQL_LOGIN_TIMEOUT
-            , (SQLPOINTER)(std::intptr_t)timeout
-            , 0);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-
-        #ifdef SQL_ATTR_ASYNC_DBC_EVENT
-            if(event_handle != NULL)
-                enable_async(event_handle);
-        #endif
-
-        NANODBC_CALL_RC(
-            NANODBC_FUNC(SQLConnect)
-            , rc
-            , conn_
-            , (NANODBC_SQLCHAR*)dsn.c_str(), SQL_NTS
-            , !user.empty() ? (NANODBC_SQLCHAR*)user.c_str() : 0, SQL_NTS
-            , !pass.empty() ? (NANODBC_SQLCHAR*)pass.c_str() : 0, SQL_NTS);
-        if(!success(rc) && (event_handle == NULL || rc != SQL_STILL_EXECUTING))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-
-        connected_ = success(rc);
-    }
-
-    void connect(const string_type& connection_string, long timeout, void* event_handle = NULL)
-    {
-        disconnect();
-
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLFreeHandle
-            , rc
-            , SQL_HANDLE_DBC
-            , conn_);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-
-        NANODBC_CALL_RC(
-            SQLAllocHandle
-            , rc
-            , SQL_HANDLE_DBC
-            , env_
-            , &conn_);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(env_, SQL_HANDLE_ENV);
-
-        NANODBC_CALL_RC(
-            SQLSetConnectAttr
-            , rc
-            , conn_
-            , SQL_LOGIN_TIMEOUT
-            , (SQLPOINTER)(std::intptr_t)timeout
-            , 0);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-
-        #ifdef SQL_ATTR_ASYNC_DBC_EVENT
-            if(event_handle != NULL)
-                enable_async(event_handle);
-        #endif
-
-        NANODBC_SQLCHAR dsn[1024];
-        SQLSMALLINT dsn_size = 0;
-        NANODBC_CALL_RC(
-            NANODBC_FUNC(SQLDriverConnect)
-            , rc
-            , conn_
-            , 0
-            , (NANODBC_SQLCHAR*)connection_string.c_str(), SQL_NTS
-            , dsn
-            , sizeof(dsn) / sizeof(NANODBC_SQLCHAR)
-            , &dsn_size
-            , SQL_DRIVER_NOPROMPT);
-        if(!success(rc) && (event_handle == NULL || rc != SQL_STILL_EXECUTING))
-            NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-
-        connected_ = success(rc);
-    }
-
-    bool connected() const
-    {
-        return connected_;
-    }
-
-    void disconnect()
-    {
-        if(connected())
-        {
-            RETCODE rc;
-            NANODBC_CALL_RC(
-                SQLDisconnect
-                , rc
-                , conn_);
-            if(!success(rc))
-                NANODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
-        }
-        connected_ = false;
-    }
-
-    std::size_t transactions() const
-    {
-        return transactions_;
-    }
-
-    void* native_dbc_handle() const
-    {
-        return conn_;
-    }
-
-    void* native_env_handle() const
-    {
-        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_FUNC(SQLGetInfo)
-            , rc
-            , conn_
-            , SQL_DRIVER_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 database_name() const
-    {
-        // FIXME: Allocate buffer of dynamic size as drivers do not agree on universal size
-        // MySQL driver limits MAX_NAME_LEN=255
-        // PostgreSQL driver MAX_INFO_STIRNG=128
-        // MFC CDatabase allocates buffer dynamically.
-        NANODBC_SQLCHAR name[255] = { 0 };
-        SQLSMALLINT length(0);
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            NANODBC_FUNC(SQLGetInfo)
-            , rc
-            , conn_
-            , SQL_DATABASE_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 catalog_name() const
-    {
-        NANODBC_SQLCHAR name[SQL_MAX_OPTION_STRING_LENGTH] = { 0 };
-        SQLINTEGER length(0);
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            NANODBC_FUNC(SQLGetConnectAttr)
-            , rc
-            , conn_
-            , SQL_ATTR_CURRENT_CATALOG
-            , 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)]);
-    }
-
-
-    std::size_t ref_transaction()
-    {
-        return --transactions_;
-    }
-
-    std::size_t unref_transaction()
-    {
-        return ++transactions_;
-    }
-
-    bool rollback() const
-    {
-        return rollback_;
-    }
-
-    void rollback(bool onoff)
-    {
-        rollback_ = onoff;
-    }
-
-private:
-    HENV env_;
-    HDBC conn_;
-    bool connected_;
-    std::size_t transactions_;
-    bool rollback_; // if true, this connection is marked for eventual transaction rollback
-};
-
-} // namespace nanodbc
-
-// 88888888888                                                  888    d8b                             8888888                        888
-//     888                                                      888    Y8P                               888                          888
-//     888                                                      888                                      888                          888
-//     888  888d888 8888b.  88888b.  .d8888b   8888b.   .d8888b 888888 888  .d88b.  88888b.              888   88888b.d88b.  88888b.  888
-//     888  888P"      "88b 888 "88b 88K          "88b d88P"    888    888 d88""88b 888 "88b             888   888 "888 "88b 888 "88b 888
-//     888  888    .d888888 888  888 "Y8888b. .d888888 888      888    888 888  888 888  888             888   888  888  888 888  888 888
-//     888  888    888  888 888  888      X88 888  888 Y88b.    Y88b.  888 Y88..88P 888  888             888   888  888  888 888 d88P 888
-//     888  888    "Y888888 888  888  88888P' "Y888888  "Y8888P  "Y888 888  "Y88P"  888  888           8888888 888  888  888 88888P"  888
-//                                                                                                                           888
-//                                                                                                                           888
-//                                                                                                                           888
-// MARK: Transaction Impl -
-
-namespace nanodbc
-{
-
-class transaction::transaction_impl
-{
-public:
-    transaction_impl(const transaction_impl&) =delete;
-    transaction_impl& operator=(const transaction_impl&) =delete;
-
-    transaction_impl(const class connection& conn)
-    : conn_(conn)
-    , committed_(false)
-    {
-        if(conn_.transactions() == 0 && conn_.connected())
-        {
-            RETCODE rc;
-            NANODBC_CALL_RC(
-                SQLSetConnectAttr
-                , rc
-                , conn_.native_dbc_handle()
-                , SQL_ATTR_AUTOCOMMIT
-                , (SQLPOINTER)SQL_AUTOCOMMIT_OFF
-                , SQL_IS_UINTEGER);
-            if(!success(rc))
-                NANODBC_THROW_DATABASE_ERROR(conn_.native_dbc_handle(), SQL_HANDLE_DBC);
-        }
-        conn_.ref_transaction();
-    }
-
-    ~transaction_impl() NANODBC_NOEXCEPT
-    {
-        if(!committed_)
-        {
-            conn_.rollback(true);
-            conn_.unref_transaction();
-        }
-
-        if(conn_.transactions() == 0 && conn_.connected())
-        {
-            if(conn_.rollback())
-            {
-                NANODBC_CALL(
-                    SQLEndTran
-                    , SQL_HANDLE_DBC
-                    , conn_.native_dbc_handle()
-                    , SQL_ROLLBACK);
-                conn_.rollback(false);
-            }
-
-            NANODBC_CALL(
-                SQLSetConnectAttr
-                , conn_.native_dbc_handle()
-                , SQL_ATTR_AUTOCOMMIT
-                , (SQLPOINTER)SQL_AUTOCOMMIT_ON
-                , SQL_IS_UINTEGER);
-        }
-    }
-
-    void commit()
-    {
-        if(committed_)
-            return;
-        committed_ = true;
-        if(conn_.unref_transaction() == 0 && conn_.connected())
-        {
-            RETCODE rc;
-            NANODBC_CALL_RC(
-                SQLEndTran
-                , rc
-                , SQL_HANDLE_DBC
-                , conn_.native_dbc_handle()
-                , SQL_COMMIT);
-            if(!success(rc))
-                NANODBC_THROW_DATABASE_ERROR(conn_.native_dbc_handle(), SQL_HANDLE_DBC);
-        }
-    }
-
-    void rollback() NANODBC_NOEXCEPT
-    {
-        if(committed_)
-            return;
-        conn_.rollback(true);
-    }
-
-    class connection& connection()
-    {
-        return conn_;
-    }
-
-    const class connection& connection() const
-    {
-        return conn_;
-    }
-
-private:
-    class connection conn_;
-    bool committed_;
-};
-
-} // namespace nanodbc
-
-//  .d8888b.  888             888                                            888              8888888                        888
-// d88P  Y88b 888             888                                            888                888                          888
-// Y88b.      888             888                                            888                888                          888
-//  "Y888b.   888888  8888b.  888888 .d88b.  88888b.d88b.   .d88b.  88888b.  888888             888   88888b.d88b.  88888b.  888
-//     "Y88b. 888        "88b 888   d8P  Y8b 888 "888 "88b d8P  Y8b 888 "88b 888                888   888 "888 "88b 888 "88b 888
-//       "888 888    .d888888 888   88888888 888  888  888 88888888 888  888 888                888   888  888  888 888  888 888
-// Y88b  d88P Y88b.  888  888 Y88b. Y8b.     888  888  888 Y8b.     888  888 Y88b.              888   888  888  888 888 d88P 888
-//  "Y8888P"   "Y888 "Y888888  "Y888 "Y8888  888  888  888  "Y8888  888  888  "Y888           8888888 888  888  888 88888P"  888
-//                                                                                                                  888
-//                                                                                                                  888
-//                                                                                                                  888
-// MARK: Statement Impl -
-
-namespace nanodbc
-{
-
-class statement::statement_impl
-{
-public:
-    statement_impl(const statement_impl&) =delete;
-    statement_impl& operator=(const statement_impl&) =delete;
-
-    statement_impl()
-    : stmt_(0)
-    , open_(false)
-    , conn_()
-    , bind_len_or_null_()
-    {
-
-    }
-
-    statement_impl(class connection& conn)
-    : stmt_(0)
-    , open_(false)
-    , conn_()
-    , bind_len_or_null_()
-    {
-        open(conn);
-    }
-
-    statement_impl(class connection& conn, const string_type& query, long timeout)
-    : stmt_(0)
-    , open_(false)
-    , conn_()
-    , bind_len_or_null_()
-    {
-        prepare(conn, query, timeout);
-    }
-
-    ~statement_impl() NANODBC_NOEXCEPT
-    {
-        if(open() && connected())
-        {
-            NANODBC_CALL(
-                SQLCancel
-                , stmt_);
-            reset_parameters();
-            NANODBC_CALL(
-                SQLFreeHandle
-                , SQL_HANDLE_STMT
-                , stmt_);
-        }
-    }
-
-    void open(class connection& conn)
-    {
-        close();
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLAllocHandle
-            , rc
-            , SQL_HANDLE_STMT
-            , conn.native_dbc_handle()
-            , &stmt_);
-        open_ = success(rc);
-        if(!open_)
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-        conn_ = conn;
-    }
-
-    bool open() const
-    {
-        return open_;
-    }
-
-    bool connected() const
-    {
-        return conn_.connected();
-    }
-
-    const class connection& connection() const
-    {
-        return conn_;
-    }
-
-    class connection& connection()
-    {
-        return conn_;
-    }
-
-    void* native_statement_handle() const
-    {
-        return stmt_;
-    }
-
-    void close()
-    {
-        if(open() && connected())
-        {
-            RETCODE rc;
-            NANODBC_CALL_RC(
-                SQLCancel
-                , rc
-                , stmt_);
-            if(!success(rc))
-                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-            reset_parameters();
-
-            NANODBC_CALL_RC(
-                SQLFreeHandle
-                , rc
-                , SQL_HANDLE_STMT
-                , stmt_);
-            if(!success(rc))
-                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-        }
-
-        open_ = false;
-        stmt_ = 0;
-    }
-
-    void cancel()
-    {
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLCancel
-            , rc
-            , stmt_);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-    }
-
-    void prepare(class connection& conn, const string_type& query, long timeout)
-    {
-        open(conn);
-        prepare(query, timeout);
-    }
-
-    void prepare(const string_type& query, long timeout)
-    {
-        if(!open())
-            throw programming_error("statement has no associated open connection");
-
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            NANODBC_FUNC(SQLPrepare)
-            , rc
-            , stmt_
-            , (NANODBC_SQLCHAR*)query.c_str()
-            , (SQLINTEGER)query.size());
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        this->timeout(timeout);
-    }
-
-    void timeout(long timeout)
-    {
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLSetStmtAttr
-            , rc
-            , stmt_
-            , SQL_ATTR_QUERY_TIMEOUT
-            , (SQLPOINTER)(std::intptr_t)timeout,
-             0);
-
-        // some drivers don't support timeout for statements,
-        // so only raise the error if a non-default timeout was requested.
-        if(!success(rc) && (timeout != 0))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-    }
-
-#if defined(SQL_ATTR_ASYNC_STMT_EVENT) && defined(SQL_API_SQLCOMPLETEASYNC)
-    void enable_async(void* event_handle)
-    {
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLSetStmtAttr
-            , rc
-            , stmt_
-            , SQL_ATTR_ASYNC_ENABLE
-            , (SQLPOINTER)SQL_ASYNC_ENABLE_ON
-            , SQL_IS_INTEGER);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        NANODBC_CALL_RC(
-            SQLSetStmtAttr
-            , rc
-            , stmt_
-            , SQL_ATTR_ASYNC_STMT_EVENT
-            , event_handle
-            , SQL_IS_POINTER);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-    }
-
-    void async_execute_direct(
-        class connection& conn
-        , void* event_handle
-        , const string_type& query
-        , long batch_operations
-        , long timeout
-        , statement& statement)
-    {
-        RETCODE rc = just_execute_direct(
-            conn
-            , query
-            , batch_operations
-            , timeout
-            , statement
-            , event_handle);
-
-        if(rc != SQL_STILL_EXECUTING)
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-    }
-
-    result async_complete(long batch_operations, statement& statement)
-    {
-        RETCODE rc, arc;
-        NANODBC_CALL_RC(
-            SQLCompleteAsync
-            , rc
-            , SQL_HANDLE_STMT
-            , stmt_
-            , &arc);
-        if(!success(rc) || !success(arc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        NANODBC_CALL_RC(
-            SQLSetStmtAttr
-            , rc
-            , stmt_
-            , SQL_ATTR_ASYNC_ENABLE
-            , (SQLPOINTER)SQL_ASYNC_ENABLE_OFF
-            , SQL_IS_INTEGER);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        return result(statement, batch_operations);
-    }
-#endif // SQL_ATTR_ASYNC_STMT_EVENT && SQL_API_SQLCOMPLETEASYNC
-
-    result execute_direct(
-        class connection& conn
-        , const string_type& query
-        , long batch_operations
-        , long timeout
-        , statement& statement)
-    {
-        #ifdef NANODBC_HANDLE_NODATA_BUG
-            const RETCODE rc = just_execute_direct(conn, query, batch_operations, timeout, statement);
-            if(rc == SQL_NO_DATA)
-                return result();
-        #else
-            just_execute_direct(conn, query, batch_operations, timeout, statement);
-        #endif
-        return result(statement, batch_operations);
-    }
-
-    RETCODE just_execute_direct(
-        class connection& conn
-        , const string_type& query
-        , long batch_operations
-        , long timeout
-        , statement& /*statement*/
-        , void* event_handle = NULL)
-    {
-        open(conn);
-
-        #if defined(SQL_ATTR_ASYNC_STMT_EVENT) && defined(SQL_API_SQLCOMPLETEASYNC)
-            if(event_handle != NULL)
-                enable_async(event_handle);
-        #endif
-
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLSetStmtAttr
-            , rc
-            , stmt_
-            , SQL_ATTR_PARAMSET_SIZE
-            , (SQLPOINTER)(std::intptr_t)batch_operations
-            , 0);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        this->timeout(timeout);
-
-        NANODBC_CALL_RC(
-            NANODBC_FUNC(SQLExecDirect)
-            , rc
-            , stmt_
-            , (NANODBC_SQLCHAR*)query.c_str()
-            , SQL_NTS);
-        if(!success(rc) && rc != SQL_NO_DATA && rc != SQL_STILL_EXECUTING)
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        return rc;
-    }
-
-    result execute(long batch_operations, long timeout, statement& statement)
-    {
-        #ifdef NANODBC_HANDLE_NODATA_BUG
-            const RETCODE rc = just_execute(batch_operations, timeout, statement);
-            if(rc == SQL_NO_DATA)
-                return result();
-        #else
-            just_execute(batch_operations, timeout, statement);
-        #endif
-        return result(statement, batch_operations);
-    }
-
-    RETCODE just_execute(long batch_operations, long timeout, statement& /*statement*/)
-    {
-        RETCODE rc;
-
-        if(open())
-        {
-            // The ODBC cursor must be closed before subsequent executions, as described
-            // here http://msdn.microsoft.com/en-us/library/windows/desktop/ms713584%28v=vs.85%29.aspx
-            //
-            // However, we don't necessarily want to call SQLCloseCursor() because that
-            // will cause an invalid cursor state in the case that no cursor is currently open.
-            // A better solution is to use SQLFreeStmt() with the SQL_CLOSE option, which has
-            // the same effect without the undesired limitations.
-            NANODBC_CALL_RC(
-                SQLFreeStmt
-                , rc
-                , stmt_
-                , SQL_CLOSE);
-            if(!success(rc))
-                NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-        }
-
-        NANODBC_CALL_RC(
-            SQLSetStmtAttr
-            , rc
-            , stmt_
-            , SQL_ATTR_PARAMSET_SIZE
-            , (SQLPOINTER)(std::intptr_t)batch_operations
-            , 0);
-        if(!success(rc) && rc != SQL_NO_DATA)
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        this->timeout(timeout);
-
-        NANODBC_CALL_RC(
-            SQLExecute
-            , rc
-            , stmt_);
-        if(!success(rc) && rc != SQL_NO_DATA)
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        return rc;
-    }
-
-    result procedure_columns(
-        const string_type& catalog
-        , const string_type& schema
-        , const string_type& procedure
-        , const string_type& column
-        , statement& statement)
-    {
-        if(!open())
-            throw programming_error("statement has no associated open connection");
-
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            NANODBC_FUNC(SQLProcedureColumns)
-            , rc
-            , stmt_
-            , (NANODBC_SQLCHAR*)(catalog.empty() ? NULL : catalog.c_str())
-            , (catalog.empty() ? 0 : SQL_NTS)
-            , (NANODBC_SQLCHAR*)(schema.empty() ? NULL : schema.c_str())
-            , (schema.empty() ? 0 : SQL_NTS)
-            , (NANODBC_SQLCHAR*)procedure.c_str()
-            , SQL_NTS
-            , (NANODBC_SQLCHAR*)(column.empty() ? NULL : column.c_str())
-            , (column.empty() ? 0 : SQL_NTS));
-
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        return result(statement, 1);
-    }
-
-    long affected_rows() const
-    {
-        SQLLEN rows;
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLRowCount
-            , rc
-            , stmt_
-            , &rows);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-        NANODBC_ASSERT(rows <= static_cast<SQLLEN>(std::numeric_limits<long>::max()));
-        return static_cast<long>(rows);
-    }
-
-    short columns() const
-    {
-        SQLSMALLINT cols;
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLNumResultCols
-            , rc
-            , stmt_
-            , &cols);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-        return cols;
-    }
-
-    void reset_parameters() NANODBC_NOEXCEPT
-    {
-        NANODBC_CALL(
-            SQLFreeStmt
-            , stmt_
-            , SQL_RESET_PARAMS);
-    }
-
-    unsigned long parameter_size(short param) const
-    {
-        RETCODE rc;
-        SQLSMALLINT data_type;
-        SQLSMALLINT nullable;
-        SQLULEN parameter_size;
-        NANODBC_CALL_RC(
-            SQLDescribeParam
-            , rc
-            , stmt_
-            , param + 1
-            , &data_type
-            , &parameter_size
-            , 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()));
-        return static_cast<unsigned long>(parameter_size);
-    }
-
-    static SQLSMALLINT param_type_from_direction(param_direction direction)
-    {
-        switch(direction)
-        {
-            case PARAM_IN:
-                return SQL_PARAM_INPUT;
-                break;
-            case PARAM_OUT:
-                return SQL_PARAM_OUTPUT;
-                break;
-            case PARAM_INOUT:
-                return SQL_PARAM_INPUT_OUTPUT;
-                break;
-            case PARAM_RETURN:
-                return SQL_PARAM_OUTPUT;
-                break;
-            default:
-                NANODBC_ASSERT(false);
-                throw programming_error("unrecognized param_direction value");
-        }
-    }
-
-    // initializes bind_len_or_null_ and gets information for bind
-    void prepare_bind(
-        short param
-        , std::size_t elements
-        , param_direction direction
-        , SQLSMALLINT& data_type
-        , SQLSMALLINT& param_type
-        , SQLULEN& parameter_size
-        , SQLSMALLINT& scale)
-    {
-        RETCODE rc;
-        SQLSMALLINT nullable;
-        NANODBC_CALL_RC(
-            SQLDescribeParam
-            , rc
-            , stmt_
-            , param + 1
-            , &data_type
-            , &parameter_size
-            , &scale
-            , &nullable);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-
-        param_type = param_type_from_direction(direction);
-
-        if(!bind_len_or_null_.count(param))
-            bind_len_or_null_[param] = std::vector<null_type>();
-        std::vector<null_type>().swap(bind_len_or_null_[param]);
-
-        // ODBC weirdness: this must be at least 8 elements in size
-        const std::size_t indicator_size = elements > 8 ? elements : 8;
-
-        bind_len_or_null_[param].reserve(indicator_size);
-        bind_len_or_null_[param].assign(indicator_size, SQL_NULL_DATA);
-    }
-
-    // calls actual ODBC bind parameter function
-    template<class T>
-    void bind_parameter(
-        short param
-        , const T* data
-        , std::size_t /*elements*/
-        , SQLSMALLINT data_type
-        , SQLSMALLINT param_type
-        , SQLULEN parameter_size
-        , SQLSMALLINT scale)
-    {
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLBindParameter
-            , rc
-            , stmt_ // handle
-            , param + 1 // parameter number
-            , param_type // input or output type
-            , sql_ctype<T>::value // value type
-            , data_type // parameter type
-            , parameter_size // column size ignored for many types, but needed for strings
-            , scale // decimal digits
-            , (SQLPOINTER)data // parameter value
-            , parameter_size // buffer length
-            , bind_len_or_null_[param].data());
-
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-    }
-
-    // handles a single value (possibly a single string value), or multiple non-string values
-    template<class T>
-    void bind(short param, const T* values, std::size_t elements, param_direction direction);
-
-    // handles multiple string values
-    void bind_strings(
-        short param
-        , const string_type::value_type* values
-        , std::size_t /*length*/
-        , std::size_t elements
-        , param_direction direction)
-    {
-        bind(param, values, elements, direction);
-    }
-
-    // handles multiple null values
-    void bind_null(short param, std::size_t elements)
-    {
-        SQLSMALLINT data_type;
-        SQLSMALLINT param_type;
-        SQLULEN parameter_size;
-        SQLSMALLINT scale;
-        prepare_bind(param, elements, PARAM_IN, data_type, param_type, parameter_size, scale);
-
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLBindParameter
-            , rc
-            , stmt_
-            , param + 1
-            , param_type
-            , SQL_C_CHAR
-            , data_type
-            , parameter_size // column size ignored for many types, but needed for strings
-            , 0
-            , (SQLPOINTER)0 // null value
-            , 0 // parameter_size
-            , bind_len_or_null_[param].data());
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-    }
-
-    // comparator for null sentry values
-    template<class T>
-    bool equals(const T& lhs, const T& rhs)
-    {
-        return lhs == rhs;
-    }
-
-    // handles multiple non-string values with a null sentry
-    template<class T>
-    void bind(
-        short param
-        , const T* values
-        , std::size_t elements
-        , const bool* nulls
-        , const T* null_sentry
-        , param_direction direction);
-
-    // handles multiple string values
-    void bind_strings(
-        short param
-        , const string_type::value_type* values
-        , std::size_t length
-        , std::size_t elements
-        , const bool* nulls
-        , const string_type::value_type* null_sentry
-        , param_direction direction);
-
-private:
-    HSTMT stmt_;
-    bool open_;
-    class connection conn_;
-    std::map<short, std::vector<null_type> > bind_len_or_null_;
-};
-
-// Supports code like: query.bind(0, std_string.c_str())
-// In this case, we need to pass NULL to the final parameter of SQLBindParameter().
-template<>
-void statement::statement_impl::bind_parameter<string_type::value_type>(
-    short param
-    , const string_type::value_type* data
-    , std::size_t elements
-    , SQLSMALLINT data_type
-    , SQLSMALLINT param_type
-    , SQLULEN parameter_size
-    , SQLSMALLINT scale)
-{
-    RETCODE rc;
-    NANODBC_CALL_RC(
-        SQLBindParameter
-        , rc
-        , stmt_ // handle
-        , param + 1 // parameter number
-        , param_type // input or output type
-        , sql_ctype<string_type::value_type>::value // value type
-        , data_type // parameter type
-        , parameter_size // column size ignored for many types, but needed for strings
-        , scale // decimal digits
-        , (SQLPOINTER)data // parameter value
-        , parameter_size // buffer length
-        , (elements <= 1 ? NULL : bind_len_or_null_[param].data()));
-
-    if(!success(rc))
-        NANODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
-}
-
-template<class T>
-void statement::statement_impl::bind(
-    short param
-    , const T* values
-    , std::size_t elements
-    , param_direction direction)
-{
-    SQLSMALLINT data_type;
-    SQLSMALLINT param_type;
-    SQLULEN parameter_size;
-    SQLSMALLINT scale;
-    prepare_bind(param, elements, direction, data_type, param_type, parameter_size, scale);
-
-    for(std::size_t i = 0; i < elements; ++i)
-        bind_len_or_null_[param][i] = parameter_size;
-
-    bind_parameter(param, values, elements, data_type, param_type, parameter_size, scale);
-}
-
-template<class T>
-void statement::statement_impl::bind(
-    short param
-    , const T* values
-    , std::size_t elements
-    , const bool* nulls
-    , const T* null_sentry
-    , param_direction direction)
-{
-    SQLSMALLINT data_type;
-    SQLSMALLINT param_type;
-    SQLULEN parameter_size;
-    SQLSMALLINT scale;
-    prepare_bind(param, elements, direction, data_type, param_type, parameter_size, scale);
-
-    for(std::size_t i = 0; i < elements; ++i)
-        if((null_sentry && !equals(values[i], *null_sentry)) || (nulls && !nulls[i]))
-            bind_len_or_null_[param][i] = parameter_size;
-
-    bind_parameter(param, values, elements, data_type, param_type, parameter_size, scale);
-}
-
-void statement::statement_impl::bind_strings(
-    short param
-    , const string_type::value_type* values
-    , std::size_t length
-    , std::size_t elements
-    , const bool* nulls
-    , const string_type::value_type* null_sentry
-    , param_direction direction)
-{
-    SQLSMALLINT data_type;
-    SQLSMALLINT param_type;
-    SQLULEN parameter_size;
-    SQLSMALLINT scale;
-    prepare_bind(param, elements, direction, data_type, param_type, parameter_size, scale);
-
-    if(null_sentry)
-    {
-        for(std::size_t i = 0; i < elements; ++i)
-        {
-            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)
-    {
-        for(std::size_t i = 0; i < elements; ++i)
-        {
-            if(!nulls[i])
-                bind_len_or_null_[param][i] = SQL_NTS; // null terminated
-        }
-    }
-
-    bind_parameter(param, values, elements, data_type, param_type, parameter_size, scale);
-}
-
-template<>
-bool statement::statement_impl::equals(const date& lhs, const date& rhs)
-{
-    return lhs.year == rhs.year
-        && lhs.month == rhs.month
-        && lhs.day == rhs.day;
-}
-
-template<>
-bool statement::statement_impl::equals(const timestamp& lhs, const timestamp& rhs)
-{
-    return lhs.year == rhs.year
-        && lhs.month == rhs.month
-        && lhs.day == rhs.day
-        && lhs.hour == rhs.hour
-        && lhs.min == rhs.min
-        && lhs.sec == rhs.sec
-        && lhs.fract == rhs.fract;
-}
-
-} // namespace nanodbc
-
-// 8888888b.                            888 888              8888888                        888
-// 888   Y88b                           888 888                888                          888
-// 888    888                           888 888                888                          888
-// 888   d88P .d88b.  .d8888b  888  888 888 888888             888   88888b.d88b.  88888b.  888
-// 8888888P" d8P  Y8b 88K      888  888 888 888                888   888 "888 "88b 888 "88b 888
-// 888 T88b  88888888 "Y8888b. 888  888 888 888                888   888  888  888 888  888 888
-// 888  T88b Y8b.          X88 Y88b 888 888 Y88b.              888   888  888  888 888 d88P 888
-// 888   T88b "Y8888   88888P'  "Y88888 888  "Y888           8888888 888  888  888 88888P"  888
-//                                                                                 888
-//                                                                                 888
-//                                                                                 888
-// MARK: Result Impl -
-
-namespace nanodbc
-{
-
-class result::result_impl
-{
-public:
-    result_impl(const result_impl&) =delete;
-    result_impl& operator=(const result_impl&) =delete;
-
-    result_impl(statement stmt, long rowset_size)
-    : stmt_(stmt)
-    , rowset_size_(rowset_size)
-    , row_count_(0)
-    , bound_columns_(0)
-    , bound_columns_size_(0)
-    , rowset_position_(0)
-    , bound_columns_by_name_()
-    , at_end_(false)
-    {
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLSetStmtAttr
-            , rc
-            , stmt_.native_statement_handle()
-            , SQL_ATTR_ROW_ARRAY_SIZE
-            , (SQLPOINTER)(std::intptr_t)rowset_size_
-            , 0);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
-
-        NANODBC_CALL_RC(
-            SQLSetStmtAttr
-            , rc
-            , stmt_.native_statement_handle()
-            , SQL_ATTR_ROWS_FETCHED_PTR
-            , &row_count_
-            , 0);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
-
-        auto_bind();
-    }
-
-    ~result_impl() NANODBC_NOEXCEPT
-    {
-        cleanup_bound_columns();
-    }
-
-    void* native_statement_handle() const
-    {
-        return stmt_.native_statement_handle();
-    }
-
-    long rowset_size() const
-    {
-        return rowset_size_;
-    }
-
-    long affected_rows() const
-    {
-        return stmt_.affected_rows();
-    }
-
-    long rows() const NANODBC_NOEXCEPT
-    {
-        NANODBC_ASSERT(row_count_ <= static_cast<SQLULEN>(std::numeric_limits<long>::max()));
-        return static_cast<long>(row_count_);
-    }
-
-    short columns() const
-    {
-        return stmt_.columns();
-    }
-
-    bool first()
-    {
-        rowset_position_ = 0;
-        return fetch(0, SQL_FETCH_FIRST);
-    }
-
-    bool last()
-    {
-        rowset_position_ = 0;
-        return fetch(0, SQL_FETCH_LAST);
-    }
-
-    bool next()
-    {
-        if(rows() && ++rowset_position_ < rowset_size_)
-            return rowset_position_ < rows();
-        rowset_position_ = 0;
-        return fetch(0, SQL_FETCH_NEXT);
-    }
-
-    bool prior()
-    {
-        if(rows() && --rowset_position_ >= 0)
-            return true;
-        rowset_position_ = 0;
-        return fetch(0, SQL_FETCH_PRIOR);
-    }
-
-    bool move(long row)
-    {
-        rowset_position_ = 0;
-        return fetch(row, SQL_FETCH_ABSOLUTE);
-    }
-
-    bool skip(long rows)
-    {
-        rowset_position_ += rows;
-        if(this->rows() && rowset_position_ < rowset_size_)
-            return rowset_position_ < this->rows();
-        rowset_position_ = 0;
-        return fetch(rows, SQL_FETCH_RELATIVE);
-    }
-
-    unsigned long position() const
-    {
-        SQLULEN pos = 0; // necessary to initialize to 0
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLGetStmtAttr
-            , rc
-            , stmt_.native_statement_handle()
-            , SQL_ATTR_ROW_NUMBER
-            , &pos
-            , SQL_IS_UINTEGER
-            , 0);
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
-
-
-        // MSDN (https://msdn.microsoft.com/en-us/library/ms712631.aspx):
-        // If the number of the current row cannot be determined or
-        // there is no current row, the driver returns 0.
-        // Otherwise, valid row number is returned, starting at 1.
-        //
-        // NOTE: We try to address incorrect implementation in some drivers (e.g. SQLite ODBC)
-        // which instead of 0 return SQL_ROW_NUMBER_UNKNOWN(-2) .
-        if (pos == 0 || pos == static_cast<SQLULEN>(SQL_ROW_NUMBER_UNKNOWN))
-            return 0;
-
-        NANODBC_ASSERT(pos <= static_cast<SQLULEN>(std::numeric_limits<unsigned long>::max()));
-        return static_cast<unsigned long>(pos) + rowset_position_;
-    }
-
-    bool end() const NANODBC_NOEXCEPT
-    {
-        if(at_end_)
-            return true;
-        SQLULEN pos = 0; // necessary to initialize to 0
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLGetStmtAttr
-            , rc
-            , stmt_.native_statement_handle()
-            , SQL_ATTR_ROW_NUMBER
-            , &pos
-            , SQL_IS_UINTEGER
-            , 0);
-        return (!success(rc) || rows() < 0 || pos - 1 > static_cast<unsigned long>(rows()));
-    }
-
-    bool is_null(short column) const
-    {
-        if(column >= bound_columns_size_)
-            throw index_range_error();
-        bound_column& col = bound_columns_[column];
-        if(rowset_position_ >= rows())
-            throw index_range_error();
-        return col.cbdata_[rowset_position_] == SQL_NULL_DATA;
-    }
-
-    bool is_null(const string_type& column_name) const
-    {
-        const short column = this->column(column_name);
-        return is_null(column);
-    }
-
-    string_type column_name(short column) const
-    {
-        if(column >= bound_columns_size_)
-            throw index_range_error();
-        return bound_columns_[column].name_;
-    }
-
-    long column_size(short column) const
-    {
-        if(column >= bound_columns_size_)
-            throw index_range_error();
-        bound_column& col = bound_columns_[column];
-        NANODBC_ASSERT(col.sqlsize_ <= static_cast<SQLULEN>(std::numeric_limits<long>::max()));
-        return static_cast<long>(col.sqlsize_);
-    }
-
-    short column(const string_type& column_name) const
-    {
-        typedef std::map<string_type, bound_column*>::const_iterator iter;
-        iter i = bound_columns_by_name_.find(column_name);
-        if(i == bound_columns_by_name_.end())
-            throw index_range_error();
-        return i->second->column_;
-    }
-
-    int column_datatype(short column) const
-    {
-        if(column >= bound_columns_size_)
-            throw index_range_error();
-        bound_column& col = bound_columns_[column];
-        return col.sqltype_;
-    }
-
-    int column_datatype(const string_type& column_name) const
-    {
-        const short column = this->column(column_name);
-        bound_column& col = bound_columns_[column];
-        return col.sqltype_;
-    }
-
-    int column_c_datatype(short column) const
-    {
-        if(column >= bound_columns_size_)
-            throw index_range_error();
-        bound_column& col = bound_columns_[column];
-        return col.ctype_;
-    }
-
-    int column_c_datatype(const string_type& column_name) const
-    {
-        const short column = this->column(column_name);
-        bound_column& col = bound_columns_[column];
-        return col.ctype_;
-    }
-
-    bool next_result()
-    {
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLMoreResults
-            , rc
-            , stmt_.native_statement_handle());
-        if(rc == SQL_NO_DATA)
-            return false;
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
-        auto_bind();
-        return true;
-    }
-
-    template<class T>
-    void get_ref(short column, T& result) const
-    {
-        if(column >= bound_columns_size_)
-            throw index_range_error();
-        if(is_null(column))
-            throw null_access_error();
-        get_ref_impl<T>(column, result);
-    }
-
-    template<class T>
-    void get_ref(short column, const T& fallback, T& result) const
-    {
-        if(column >= bound_columns_size_)
-            throw index_range_error();
-        if(is_null(column))
-        {
-            result = fallback;
-            return;
-        }
-        get_ref_impl<T>(column, result);
-    }
-
-    template<class T>
-    void get_ref(const string_type& column_name, T& result) const
-    {
-        const short column = this->column(column_name);
-        if(is_null(column))
-            throw null_access_error();
-        get_ref_impl<T>(column, result);
-    }
-
-    template<class T>
-    void get_ref(const string_type& column_name, const T& fallback, T& result) const
-    {
-        const short column = this->column(column_name);
-        if(is_null(column))
-        {
-            result = fallback;
-            return;
-        }
-        get_ref_impl<T>(column, result);
-    }
-
-    template<class T>
-    T get(short column) const
-    {
-        T result;
-        get_ref(column, result);
-        return result;
-    }
-
-    template<class T>
-    T get(short column, const T& fallback) const
-    {
-        T result;
-        get_ref(column, fallback, result);
-        return result;
-    }
-
-    template<class T>
-    T get(const string_type& column_name) const
-    {
-        T result;
-        get_ref(column_name, result);
-        return result;
-    }
-
-    template<class T>
-    T get(const string_type& column_name, const T& fallback) const
-    {
-        T result;
-        get_ref(column_name, fallback, result);
-        return result;
-    }
-
-private:
-    template<class T>
-    void get_ref_impl(short column, T& result) const;
-
-    void before_move() NANODBC_NOEXCEPT
-    {
-        for(short i = 0; i < bound_columns_size_; ++i)
-        {
-            bound_column& col = bound_columns_[i];
-            for(long j = 0; j < rowset_size_; ++j)
-                col.cbdata_[j] = 0;
-            if(col.blob_ && col.pdata_)
-                release_bound_resources(i);
-        }
-    }
-
-    void release_bound_resources(short column) NANODBC_NOEXCEPT
-    {
-        NANODBC_ASSERT(column < bound_columns_size_);
-        bound_column& col = bound_columns_[column];
-        delete[] col.pdata_;
-        col.pdata_ = 0;
-        col.clen_ = 0;
-    }
-
-    void cleanup_bound_columns() NANODBC_NOEXCEPT
-    {
-        before_move();
-        delete[] bound_columns_;
-        bound_columns_ = NULL;
-        bound_columns_size_ = 0;
-        bound_columns_by_name_.clear();
-    }
-
-    bool fetch(long rows, SQLUSMALLINT orientation)
-    {
-        before_move();
-        RETCODE rc;
-        NANODBC_CALL_RC(
-            SQLFetchScroll
-            , rc
-            , stmt_.native_statement_handle()
-            , orientation
-            , rows);
-        if(rc == SQL_NO_DATA)
-        {
-            at_end_ = true;
-            return false;
-        }
-        if(!success(rc))
-            NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
-        return true;
-    }
-
-    void auto_bind()
-    {
-        cleanup_bound_columns();
-
-        const short n_columns = columns();
-        if(n_columns < 1)
-            return;
-
-        NANODBC_ASSERT(!bound_columns_);
-        NANODBC_ASSERT(!bound_columns_size_);
-        bound_columns_ = new bound_column[n_columns];
-        bound_columns_size_ = n_columns;
-
-        RETCODE rc;
-        NANODBC_SQLCHAR column_name[1024];
-        SQLSMALLINT sqltype, scale, nullable, len;
-        SQLULEN sqlsize;
-
-        for(SQLSMALLINT i = 0; i < n_columns; ++i)
-        {
-            NANODBC_CALL_RC(
-                NANODBC_FUNC(SQLDescribeCol)
-                , rc
-                , stmt_.native_statement_handle()
-                , i + 1
-                , (NANODBC_SQLCHAR*)column_name
-                , sizeof(column_name)/sizeof(NANODBC_SQLCHAR)
-                , &len
-                , &sqltype
-                , &sqlsize
-                , &scale
-                , &nullable);
-            if(!success(rc))
-                NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
-
-            // Adjust the sqlsize parameter in case of "unlimited" data (varchar(max), nvarchar(max)).
-            bool is_blob = false;
-
-            if(sqlsize == 0)
-            {
-                switch (sqltype)
-                {
-                    case SQL_VARCHAR:
-                    case SQL_WVARCHAR:
-                    {
-                        // Divide in half, due to sqlsize being 32-bit in Win32 (and 64-bit in x64)
-                        //sqlsize = std::numeric_limits<int32_t>::max() / 2 - 1;
-                        is_blob = true;
-                    }
-                }
-            }
-
-            bound_column& col = bound_columns_[i];
-            col.name_ = reinterpret_cast<string_type::value_type*>(column_name);
-            col.column_ = i;
-            col.sqltype_ = sqltype;
-            col.sqlsize_ = sqlsize;
-            col.scale_ = scale;
-            bound_columns_by_name_[col.name_] = &col;
-
-            using namespace std; // if int64_t is in std namespace (in c++11)
-            switch(col.sqltype_)
-            {
-                case SQL_BIT:
-                case SQL_TINYINT:
-                case SQL_SMALLINT:
-                case SQL_INTEGER:
-                    col.ctype_ = SQL_C_LONG;
-                    col.clen_ = sizeof(int32_t);
-                    break;
-                case SQL_BIGINT:
-                    col.ctype_ = SQL_C_SBIGINT;
-                    col.clen_ = sizeof(int64_t);
-                    break;
-                case SQL_DOUBLE:
-                case SQL_FLOAT:
-                case SQL_DECIMAL:
-                case SQL_REAL:
-                case SQL_NUMERIC:
-                    col.ctype_ = SQL_C_DOUBLE;
-                    col.clen_ = sizeof(double);
-                    break;
-                case SQL_DATE:
-                case SQL_TYPE_DATE:
-                    col.ctype_ = SQL_C_DATE;
-                    col.clen_ = sizeof(date);
-                    break;
-                case SQL_TIMESTAMP:
-                case SQL_TYPE_TIMESTAMP:
-                    col.ctype_ = SQL_C_TIMESTAMP;
-                    col.clen_ = sizeof(timestamp);
-                    break;
-                case SQL_CHAR:
-                case SQL_VARCHAR:
-                    col.ctype_ = SQL_C_CHAR;
-                    col.clen_ = (col.sqlsize_ + 1) * sizeof(SQLCHAR);
-                    if(is_blob)
-                    {
-                        col.clen_ = 0;
-                        col.blob_ = true;
-                    }
-                    break;
-                case SQL_WCHAR:
-                case SQL_WVARCHAR:
-                    col.ctype_ = SQL_C_WCHAR;
-                    col.clen_ = (col.sqlsize_ + 1) * sizeof(SQLWCHAR);
-                    if(is_blob)
-                    {
-                        col.clen_ = 0;
-                        col.blob_ = true;
-                    }
-                    break;
-                case SQL_LONGVARCHAR:
-                    col.ctype_ = SQL_C_CHAR;
-                    col.blob_ = true;
-                    col.clen_ = 0;
-                    break;
-                case SQL_BINARY:
-                case SQL_VARBINARY:
-                case SQL_LONGVARBINARY:
-                    col.ctype_ = SQL_C_BINARY;
-                    col.blob_ = true;
-                    col.clen_ = 0;
-                    break;
-                default:
-                    col.ctype_ = sql_ctype<string_type>::value;
-                    col.clen_ = 128;
-                    break;
-            }
-        }
-
-        for(SQLSMALLINT i = 0; i < n_columns; ++i)
-        {
-            bound_column& col = bound_columns_[i];
-            col.cbdata_ = new null_type[rowset_size_];
-            if(col.blob_)
-            {
-                NANODBC_CALL_RC(
-                    SQLBindCol
-                    , rc
-                    , stmt_.native_statement_handle()
-                    , i + 1
-                    , col.ctype_
-                    , 0
-                    , 0
-                    , col.cbdata_);
-                if(!success(rc))
-                    NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
-            }
-            else
-            {
-                col.pdata_ = new char[rowset_size_ * col.clen_];
-                NANODBC_CALL_RC(
-                    SQLBindCol
-                    , rc
-                    , stmt_.native_statement_handle()
-                    , i + 1         // ColumnNumber
-                    , col.ctype_    // TargetType
-                    , col.pdata_    // TargetValuePtr
-                    , col.clen_     // BufferLength
-                    , col.cbdata_); // StrLen_or_Ind
-                if(!success(rc))
-                    NANODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
-            }
-        }
-    }
-
-private:
-    statement stmt_;
-    const long rowset_size_;
-    SQLULEN row_count_;
-    bound_column* bound_columns_;
-    short bound_columns_size_;
-    long rowset_position_;
-    std::map<string_type, bound_column*> bound_columns_by_name_;
-    bool at_end_;
-};
-
-template<>
-inline void result::result_impl::get_ref_impl<date>(short column, date& result) const
-{
-    bound_column& col = bound_columns_[column];
-    switch(col.ctype_)
-    {
-        case SQL_C_DATE:
-            result = *reinterpret_cast<date*>(col.pdata_ + rowset_position_ * col.clen_);
-            return;
-        case SQL_C_TIMESTAMP:
-        {
-            timestamp stamp = *reinterpret_cast<timestamp*>(col.pdata_ + rowset_position_ * col.clen_ );
-            date d = { stamp.year, stamp.month, stamp.day };
-            result = d;
-            return;
-        }
-    }
-    throw type_incompatible_error();
-}
-
-template<>
-inline void result::result_impl::get_ref_impl<timestamp>(short column, timestamp& result) const
-{
-    bound_column& col = bound_columns_[column];
-    switch(col.ctype_)
-    {
-        case SQL_C_DATE:
-        {
-            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 = *reinterpret_cast<timestamp*>(col.pdata_ + rowset_position_ * col.clen_);
-            return;
-    }
-    throw type_incompatible_error();
-}
-
-template<>
-inline void result::result_impl::get_ref_impl<string_type>(short column, string_type& result) const
-{
-    bound_column& col = bound_columns_[column];
-    const SQLULEN column_size = col.sqlsize_;
-
-    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 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
-                        , col.ctype_        // TargetType
-                        , buffer            // TargetValuePtr
-                        , buffer_size - 1   // BufferLength
-                        , &ValueLenOrInd);  // StrLen_or_IndPtr
-                    if(ValueLenOrInd > 0)
-                        out.append(buffer);
-                    else if(ValueLenOrInd == SQL_NULL_DATA)
-                        *col.cbdata_ = (SQLINTEGER) SQL_NULL_DATA;
-                    // 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
-            {
-                const char* s = col.pdata_ + rowset_position_ * col.clen_;
-                const std::string::size_type str_size = std::strlen(s);
-                result.assign(s, s + str_size);
-            }
-            return;
-        }
-
-        case SQL_C_WCHAR:
-        {
-            if(col.blob_)
-            {
-                // Input is always wide_string_type, output might be std::string or wide_string_type.
-                // Use a string builder to build the output string.
-                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
-                        , col.ctype_        // TargetType
-                        , buffer            // TargetValuePtr
-                        , buffer_size - 1   // BufferLength
-                        , &ValueLenOrInd);  // StrLen_or_IndPtr
-                    if(ValueLenOrInd > 0)
-                        out.append(buffer);
-                    else if(ValueLenOrInd == SQL_NULL_DATA)
-                        *col.cbdata_ = (SQLINTEGER) SQL_NULL_DATA;
-                    // 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);
-                wide_string_type temp(s, s + str_size);
-                convert(temp, result);
-            }
-            return;
-        }
-
-        case SQL_C_GUID:
-        {
-            const char* s = col.pdata_ + rowset_position_ * col.clen_;
-            result.assign(s, s + column_size);
-            return;
-        }
-
-        case SQL_C_LONG:
-        {
-            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
-            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();
-            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:
-        {
-            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();
-            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:
-        {
-            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
-                , 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:
-        {
-            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, "");
-            char date_str[512];
-            std::strftime(date_str, sizeof(date_str), "%Y-%m-%d", &st);
-            std::setlocale(LC_TIME, old_lc_time);
-            convert(date_str, result);
-            return;
-        }
-
-        case SQL_C_TIMESTAMP:
-        {
-            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;
-            st.tm_mday = stamp.day;
-            st.tm_hour = stamp.hour;
-            st.tm_min = stamp.min;
-            st.tm_sec = stamp.sec;
-            char* old_lc_time = std::setlocale(LC_TIME, NULL);
-            std::setlocale(LC_TIME, "");
-            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);
-            convert(date_str, result);
-            return;
-        }
-    }
-    throw type_incompatible_error();
-}
-
-template<class T>
-void result::result_impl::get_ref_impl(short column, T& result) const
-{
-    bound_column& col = bound_columns_[column];
-    using namespace std; // if int64_t is in std namespace (in c++11)
-    const char* s = col.pdata_ + rowset_position_ * col.clen_;
-    switch(col.ctype_)
-    {
-        case SQL_C_CHAR: result = (T)*(char*)(s); return;
-        case SQL_C_SSHORT: result = (T)*(short*)(s); return;
-        case SQL_C_USHORT: result = (T)*(unsigned short*)(s); return;
-        case SQL_C_LONG: result = (T)*(int32_t*)(s); return;
-        case SQL_C_SLONG: result = (T)*(int32_t*)(s); return;
-        case SQL_C_ULONG: result = (T)*(uint32_t*)(s); return;
-        case SQL_C_FLOAT: result = (T)*(float*)(s); return;
-        case SQL_C_DOUBLE: result = (T)*(double*)(s); return;
-        case SQL_C_SBIGINT: result = (T)*(int64_t*)(s); return;
-        case SQL_C_UBIGINT: result = (T)*(uint64_t*)(s); return;
-    }
-    throw type_incompatible_error();
-}
-
-} // namespace nanodbc
-
-// 8888888888                            8888888888                         888    d8b
-// 888                                   888                                888    Y8P
-// 888                                   888                                888
-// 8888888 888d888 .d88b.   .d88b.       8888888 888  888 88888b.   .d8888b 888888 888  .d88b.  88888b.  .d8888b
-// 888     888P"  d8P  Y8b d8P  Y8b      888     888  888 888 "88b d88P"    888    888 d88""88b 888 "88b 88K
-// 888     888    88888888 88888888      888     888  888 888  888 888      888    888 888  888 888  888 "Y8888b.
-// 888     888    Y8b.     Y8b.          888     Y88b 888 888  888 Y88b.    Y88b.  888 Y88..88P 888  888      X88
-// 888     888     "Y8888   "Y8888       888      "Y88888 888  888  "Y8888P  "Y888 888  "Y88P"  888  888  88888P'
-// MARK: Free Functions -
-
-namespace nanodbc
-{
-
-result execute(connection& conn, const string_type& query, long batch_operations, long timeout)
-{
-    class statement statement;
-    return statement.execute_direct(conn, query, batch_operations, timeout);
-}
-
-void just_execute(connection& conn, const string_type& query, long batch_operations, long timeout) {
-    class statement statement;
-    statement.just_execute_direct(conn, query, batch_operations, timeout);
-}
-
-result execute(statement& stmt, long batch_operations)
-{
-    return stmt.execute(batch_operations);
-}
-
-void just_execute(statement& stmt, long batch_operations)
-{
-    return stmt.just_execute(batch_operations);
-}
-
-result transact(statement& stmt, long batch_operations)
-{
-    class transaction transaction(stmt.connection());
-    result rvalue = stmt.execute(batch_operations);
-    transaction.commit();
-    return rvalue;
-}
-
-void just_transact(statement& stmt, long batch_operations)
-{
-    class transaction transaction(stmt.connection());
-    stmt.just_execute(batch_operations);
-    transaction.commit();
-}
-
-void prepare(statement& stmt, const string_type& query, long timeout)
-{
-    stmt.prepare(stmt.connection(), query, timeout);
-}
-
-} // namespace nanodbc
-
-//  .d8888b.                                               888    d8b                             8888888888                 888
-// d88P  Y88b                                              888    Y8P                             888                        888
-// 888    888                                              888                                    888                        888
-// 888         .d88b.  88888b.  88888b.   .d88b.   .d8888b 888888 888  .d88b.  88888b.            8888888 888  888  888  .d88888
-// 888        d88""88b 888 "88b 888 "88b d8P  Y8b d88P"    888    888 d88""88b 888 "88b           888     888  888  888 d88" 888
-// 888    888 888  888 888  888 888  888 88888888 888      888    888 888  888 888  888           888     888  888  888 888  888
-// Y88b  d88P Y88..88P 888  888 888  888 Y8b.     Y88b.    Y88b.  888 Y88..88P 888  888           888     Y88b 888 d88P Y88b 888
-//  "Y8888P"   "Y88P"  888  888 888  888  "Y8888   "Y8888P  "Y888 888  "Y88P"  888  888           888      "Y8888888P"   "Y88888
-// MARK: Connection Fwd -
-
-namespace nanodbc
-{
-
-connection::connection()
-: impl_(new connection_impl())
-{
-
-}
-
-connection::connection(const connection& rhs)
-: impl_(rhs.impl_)
-{
-
-}
-
-#ifndef NANODBC_NO_MOVE_CTOR
-    connection::connection(connection&& rhs) NANODBC_NOEXCEPT
-    : impl_(std::move(rhs.impl_))
-    {
-
-    }
-#endif
-
-connection& connection::operator=(connection rhs)
-{
-    swap(rhs);
-    return *this;
-}
-
-void connection::swap(connection& rhs) NANODBC_NOEXCEPT
-{
-    using std::swap;
-    swap(impl_, rhs.impl_);
-}
-
-connection::connection(
-    const string_type& dsn
-    , const string_type& user
-    , const string_type& pass
-    , long timeout)
-: impl_(new connection_impl(dsn, user, pass, timeout))
-{
-
-}
-
-connection::connection(const string_type& connection_string, long timeout)
-: impl_(new connection_impl(connection_string, timeout))
-{
-
-}
-
-connection::~connection() NANODBC_NOEXCEPT
-{
-
-}
-
-void connection::connect(
-    const string_type& dsn
-    , const string_type& user
-    , const string_type& pass
-    , long timeout)
-{
-    impl_->connect(dsn, user, pass, timeout);
-}
-
-void connection::connect(const string_type& connection_string, long timeout)
-{
-    impl_->connect(connection_string, timeout);
-}
-
-#ifdef SQL_ATTR_ASYNC_DBC_EVENT
-void connection::async_connect(
-    const string_type& dsn
-    , const string_type& user
-    , const string_type& pass
-    , void* event_handle
-    , long timeout)
-{
-    impl_->connect(dsn, user, pass, timeout, event_handle);
-}
-
-void connection::async_connect(const string_type& connection_string, void* event_handle, long timeout)
-{
-    impl_->connect(connection_string, timeout, event_handle);
-}
-
-void connection::async_complete()
-{
-    impl_->async_complete();
-}
-#endif // SQL_ATTR_ASYNC_DBC_EVENT
-
-bool connection::connected() const
-{
-    return impl_->connected();
-}
-
-void connection::disconnect()
-{
-    impl_->disconnect();
-}
-
-std::size_t connection::transactions() const
-{
-    return impl_->transactions();
-}
-
-void* connection::native_dbc_handle() const
-{
-    return impl_->native_dbc_handle();
-}
-
-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();
-}
-
-string_type connection::database_name() const
-{
-    return impl_->database_name();
-}
-
-string_type connection::catalog_name() const
-{
-    return impl_->catalog_name();
-}
-
-std::size_t connection::ref_transaction()
-{
-    return impl_->ref_transaction();
-}
-
-std::size_t connection::unref_transaction()
-{
-    return impl_->unref_transaction();
-}
-
-bool connection::rollback() const
-{
-    return impl_->rollback();
-}
-
-void connection::rollback(bool onoff)
-{
-    impl_->rollback(onoff);
-}
-
-} // namespace nanodbc
-
-// 88888888888                                                  888    d8b                             8888888888                 888
-//     888                                                      888    Y8P                             888                        888
-//     888                                                      888                                    888                        888
-//     888  888d888 8888b.  88888b.  .d8888b   8888b.   .d8888b 888888 888  .d88b.  88888b.            8888888 888  888  888  .d88888 .d8888b
-//     888  888P"      "88b 888 "88b 88K          "88b d88P"    888    888 d88""88b 888 "88b           888     888  888  888 d88" 888 88K
-//     888  888    .d888888 888  888 "Y8888b. .d888888 888      888    888 888  888 888  888           888     888  888  888 888  888 "Y8888b.
-//     888  888    888  888 888  888      X88 888  888 Y88b.    Y88b.  888 Y88..88P 888  888           888     Y88b 888 d88P Y88b 888      X88
-//     888  888    "Y888888 888  888  88888P' "Y888888  "Y8888P  "Y888 888  "Y88P"  888  888           888      "Y8888888P"   "Y88888  88888P'
-// MARK: Transaction Fwd -
-
-namespace nanodbc
-{
-
-transaction::transaction(const class connection& conn)
-: impl_(new transaction_impl(conn))
-{
-
-}
-
-transaction::transaction(const transaction& rhs)
-: impl_(rhs.impl_)
-{
-
-}
-
-#ifndef NANODBC_NO_MOVE_CTOR
-    transaction::transaction(transaction&& rhs) NANODBC_NOEXCEPT
-    : impl_(std::move(rhs.impl_))
-    {
-
-    }
-#endif
-
-transaction& transaction::operator=(transaction rhs)
-{
-    swap(rhs);
-    return *this;
-}
-
-void transaction::swap(transaction& rhs) NANODBC_NOEXCEPT
-{
-    using std::swap;
-    swap(impl_, rhs.impl_);
-}
-
-transaction::~transaction() NANODBC_NOEXCEPT
-{
-
-}
-
-void transaction::commit()
-{
-    impl_->commit();
-}
-
-void transaction::rollback() NANODBC_NOEXCEPT
-{
-    impl_->rollback();
-}
-
-class connection& transaction::connection()
-{
-    return impl_->connection();
-}
-
-const class connection& transaction::connection() const
-{
-    return impl_->connection();
-}
-
-transaction::operator class connection&()
-{
-    return impl_->connection();
-}
-
-transaction::operator const class connection&() const
-{
-    return impl_->connection();
-}
-
-} // namespace nanodbc
-
-//  .d8888b.  888             888                                            888              8888888888                 888
-// d88P  Y88b 888             888                                            888              888                        888
-// Y88b.      888             888                                            888              888                        888
-//  "Y888b.   888888  8888b.  888888 .d88b.  88888b.d88b.   .d88b.  88888b.  888888           8888888 888  888  888  .d88888
-//     "Y88b. 888        "88b 888   d8P  Y8b 888 "888 "88b d8P  Y8b 888 "88b 888              888     888  888  888 d88" 888
-//       "888 888    .d888888 888   88888888 888  888  888 88888888 888  888 888              888     888  888  888 888  888
-// Y88b  d88P Y88b.  888  888 Y88b. Y8b.     888  888  888 Y8b.     888  888 Y88b.            888     Y88b 888 d88P Y88b 888
-//  "Y8888P"   "Y888 "Y888888  "Y888 "Y8888  888  888  888  "Y8888  888  888  "Y888           888      "Y8888888P"   "Y88888
-// MARK: Statement Fwd -
-
-namespace nanodbc
-{
-
-statement::statement()
-: impl_(new statement_impl())
-{
-
-}
-
-statement::statement(class connection& conn)
-: impl_(new statement_impl(conn))
-{
-
-}
-
-#ifndef NANODBC_NO_MOVE_CTOR
-    statement::statement(statement&& rhs) NANODBC_NOEXCEPT
-    : impl_(std::move(rhs.impl_))
-    {
-
-    }
-#endif
-
-statement::statement(class connection& conn, const string_type& query, long timeout)
-: impl_(new statement_impl(conn, query, timeout))
-{
-
-}
-
-statement::statement(const statement& rhs)
-: impl_(rhs.impl_)
-{
-
-}
-
-statement& statement::operator=(statement rhs)
-{
-    swap(rhs);
-    return *this;
-}
-
-void statement::swap(statement& rhs) NANODBC_NOEXCEPT
-{
-    using std::swap;
-    swap(impl_, rhs.impl_);
-}
-
-statement::~statement() NANODBC_NOEXCEPT
-{
-
-}
-
-void statement::open(class connection& conn)
-{
-    impl_->open(conn);
-}
-
-bool statement::open() const
-{
-    return impl_->open();
-}
-
-bool statement::connected() const
-{
-    return impl_->connected();
-}
-
-const class connection& statement::connection() const
-{
-    return impl_->connection();
-}
-
-class connection& statement::connection()
-{
-    return impl_->connection();
-}
-
-void* statement::native_statement_handle() const
-{
-    return impl_->native_statement_handle();
-}
-
-void statement::close()
-{
-    impl_->close();
-}
-
-void statement::cancel()
-{
-    impl_->cancel();
-}
-
-void statement::prepare(class connection& conn, const string_type& query, long timeout)
-{
-    impl_->prepare(conn, query, timeout);
-}
-
-void statement::prepare(const string_type& query, long timeout)
-{
-    impl_->prepare(query, timeout);
-}
-
-void statement::timeout(long timeout)
-{
-    impl_->timeout(timeout);
-}
-
-result statement::execute_direct(
-    class connection& conn
-    , const string_type& query
-    , long batch_operations
-    , long timeout)
-{
-    return impl_->execute_direct(conn, query, batch_operations, timeout, *this);
-}
-
-#if defined(SQL_ATTR_ASYNC_STMT_EVENT) && defined(SQL_API_SQLCOMPLETEASYNC)
-    void statement::async_execute_direct(
-        class connection& conn
-        , void* event_handle
-        , const string_type& query
-        , long batch_operations
-        , long timeout)
-    {
-        impl_->async_execute_direct(conn, event_handle, query, batch_operations, timeout, *this);
-    }
-
-    result statement::async_complete(long batch_operations)
-    {
-        return impl_->async_complete(batch_operations, *this);
-    }
-#endif
-
-void statement::just_execute_direct(
-    class connection& conn
-    , const string_type& query
-    , long batch_operations
-    , long timeout)
-{
-    impl_->just_execute_direct(conn, query, batch_operations, timeout, *this);
-}
-
-result statement::execute(long batch_operations, long timeout)
-{
-    return impl_->execute(batch_operations, timeout, *this);
-}
-
-void statement::just_execute(long batch_operations, long timeout)
-{
-    impl_->just_execute(batch_operations, timeout, *this);
-}
-
-result statement::procedure_columns(
-    const string_type& catalog
-    , const string_type& schema
-    , const string_type& procedure
-    , const string_type& column)
-{
-    return impl_->procedure_columns(catalog, schema, procedure, column, *this);
-}
-
-long statement::affected_rows() const
-{
-    return impl_->affected_rows();
-}
-
-short statement::columns() const
-{
-    return impl_->columns();
-}
-
-void statement::reset_parameters() NANODBC_NOEXCEPT
-{
-    impl_->reset_parameters();
-}
-
-unsigned long statement::parameter_size(short param) const
-{
-    return impl_->parameter_size(param);
-}
-
-// We need to instantiate each form of bind() for each of our supported data types.
-#define NANODBC_INSTANTIATE_BINDS(type)                                                                                 \
-    template void statement::bind(short, const type*, param_direction); /* 1-ary */                                     \
-    template void statement::bind(short, const type*, std::size_t, param_direction); /* n-ary */                        \
-    template void statement::bind(short, const type*, std::size_t, const type*, param_direction); /* n-ary, sentry */   \
-    template void statement::bind(short, const type*, std::size_t, const bool*, param_direction) /* n-ary, flags */     \
-    /**/
-
-// The following are the only supported instantiations of statement::bind().
-NANODBC_INSTANTIATE_BINDS(string_type::value_type);
-NANODBC_INSTANTIATE_BINDS(short);
-NANODBC_INSTANTIATE_BINDS(unsigned short);
-NANODBC_INSTANTIATE_BINDS(int32_t);
-NANODBC_INSTANTIATE_BINDS(uint32_t);
-NANODBC_INSTANTIATE_BINDS(int64_t);
-NANODBC_INSTANTIATE_BINDS(uint64_t);
-NANODBC_INSTANTIATE_BINDS(float);
-NANODBC_INSTANTIATE_BINDS(double);
-NANODBC_INSTANTIATE_BINDS(date);
-NANODBC_INSTANTIATE_BINDS(timestamp);
-
-#undef NANODBC_INSTANTIATE_BINDS
-
-template<class T>
-void statement::bind(short param, const T* value, param_direction direction)
-{
-    impl_->bind(param, value, 1, direction);
-}
-
-template<class T>
-void statement::bind(short param, const T* values, std::size_t elements, param_direction direction)
-{
-    impl_->bind(param, values, elements, direction);
-}
-
-template<class T>
-void statement::bind(
-    short param
-    , const T* values
-    , std::size_t elements
-    , const T* null_sentry
-    , param_direction direction)
-{
-    impl_->bind(param, values, elements, 0, null_sentry, direction);
-}
-
-template<class T>
-void statement::bind(
-    short param
-    , const T* values
-    , std::size_t elements
-    , const bool* nulls
-    , param_direction direction)
-{
-    impl_->bind(param, values, elements, nulls, (T*)0, direction);
-}
-
-void statement::bind_strings(
-    short param
-    , const string_type::value_type* values
-    , std::size_t length
-    , std::size_t elements
-    , param_direction direction)
-{
-    impl_->bind_strings(param, values, length, elements, direction);
-}
-
-void statement::bind_strings(
-    short param
-    , const string_type::value_type* values
-    , std::size_t length
-    , std::size_t elements
-    , const string_type::value_type* null_sentry
-    , param_direction direction)
-{
-    impl_->bind_strings(param, values, length, elements, (bool*)0, null_sentry, direction);
-}
-
-void statement::bind_strings(
-    short param
-    , const string_type::value_type* values
-    , std::size_t length
-    , std::size_t elements
-    , const bool* nulls
-    , param_direction direction)
-{
-    impl_->bind_strings(
-        param
-        , values
-        , length
-        , elements
-        , nulls
-        , (string_type::value_type*)0
-        , direction);
-}
-
-void statement::bind_null(short param, std::size_t elements)
-{
-    impl_->bind_null(param, elements);
-}
-
-} // namespace nanodbc
-
-namespace nanodbc
-{
-
-catalog::tables::tables(result& find_result)
-: result_(find_result)
-{
-}
-
-bool catalog::tables::next()
-{
-    return result_.next();
-}
-
-string_type catalog::tables::table_catalog() const
-{
-    // TABLE_CAT might be NULL
-    return result_.get<string_type>(0, string_type());
-}
-
-string_type catalog::tables::table_schema() const
-{
-    // TABLE_SCHEM might be NULL
-    return result_.get<string_type>(1, string_type());
-}
-
-string_type catalog::tables::table_name() const
-{
-    // TABLE_NAME column is never NULL
-    return result_.get<string_type>(2);
-}
-
-string_type catalog::tables::table_type() const
-{
-    // TABLE_TYPE column is never NULL
-    return result_.get<string_type>(3);
-}
-
-string_type catalog::tables::table_remarks() const
-{
-    // REMARKS might be NULL
-    return result_.get<string_type>(4, string_type());
-}
-
-catalog::primary_keys::primary_keys(result& find_result)
-: result_(find_result)
-{
-}
-
-bool catalog::primary_keys::next()
-{
-    return result_.next();
-}
-
-string_type catalog::primary_keys::table_catalog() const
-{
-    // TABLE_CAT might be NULL
-    return result_.get<string_type>(0, string_type());
-}
-
-string_type catalog::primary_keys::table_schema() const
-{
-    // TABLE_SCHEM might be NULL
-    return result_.get<string_type>(1, string_type());
-}
-
-string_type catalog::primary_keys::table_name() const
-{
-    // TABLE_NAME is never NULL
-    return result_.get<string_type>(2);
-}
-
-string_type catalog::primary_keys::column_name() const
-{
-    // COLUMN_NAME is never NULL
-    return result_.get<string_type>(3);
-}
-
-short catalog::primary_keys::column_number() const
-{
-    // KEY_SEQ is never NULL
-    return result_.get<short>(4);
-}
-
-string_type catalog::primary_keys::primary_key_name() const
-{
-    // PK_NAME might be NULL
-    return result_.get<string_type>(5);
-}
-
-catalog::columns::columns(result& find_result)
-: result_(find_result)
-{
-}
-
-bool catalog::columns::next()
-{
-    return result_.next();
-}
-
-string_type catalog::columns::table_catalog() const
-{
-    // TABLE_CAT might be NULL
-    return result_.get<string_type>(0, string_type());
-}
-
-string_type catalog::columns::table_schema() const
-{
-    // TABLE_SCHEM might be NULL
-    return result_.get<string_type>(1, string_type());
-}
-
-string_type catalog::columns::table_name() const
-{
-    // TABLE_NAME is never NULL
-    return result_.get<string_type>(2);
-}
-
-string_type catalog::columns::column_name() const
-{
-    // COLUMN_NAME is never NULL
-    return result_.get<string_type>(3);
-}
-
-short catalog::columns::data_type() const
-{
-    // DATA_TYPE is never NULL
-    return result_.get<short>(4);
-}
-
-string_type catalog::columns::type_name() const
-{
-    // TYPE_NAME is never NULL
-    return result_.get<string_type>(5);
-}
-
-long catalog::columns::column_size() const
-{
-    // COLUMN_SIZE
-    return result_.get<long>(6);
-}
-
-long catalog::columns::buffer_length() const
-{
-    // BUFFER_LENGTH
-    return result_.get<long>(7);
-}
-
-short catalog::columns::decimal_digits() const
-{
-    // DECIMAL_DIGITS might be NULL
-    return result_.get<short>(8, 0);
-}
-
-short catalog::columns::numeric_precision_radix() const
-{
-    // NUM_PREC_RADIX might be NULL
-    return result_.get<short>(9, 0);
-}
-
-short catalog::columns::nullable() const
-{
-    // NULLABLE is never NULL
-    return result_.get<short>(10);
-}
-
-string_type catalog::columns::remarks() const
-{
-    // REMARKS might be NULL
-    return result_.get<string_type>(11, string_type());
-}
-
-string_type catalog::columns::column_default() const
-{
-    // COLUMN_DEF might be NULL, if no default value is specified.
-    return result_.get<string_type>(12, string_type());
-}
-
-short catalog::columns::sql_data_type() const
-{
-    // SQL_DATA_TYPE is never NULL
-    return result_.get<short>(13);
-}
-
-short catalog::columns::sql_datetime_subtype() const
-{
-    // SQL_DATETIME_SUB might be NULL
-    return result_.get<short>(14, 0);
-}
-
-long catalog::columns::char_octed_length() const
-{
-    // CHAR_OCTET_LENGTH might be nULL
-    return result_.get<long>(15, 0);
-}
-
-long catalog::columns::ordinal_position() const
-{
-    // ORDINAL_POSITION is never NULL
-    return result_.get<long>(16);
-}
-
-string_type catalog::columns::is_nullable() const
-{
-    // 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)
-: conn_(conn)
-{
-}
-
-catalog::tables catalog::find_tables(
-    const string_type& table
-  , const string_type& type
-  , const string_type& schema
-  , const string_type& catalog)
-{
-    statement stmt(conn_);
-    RETCODE rc;
-    NANODBC_CALL_RC(
-        NANODBC_FUNC(SQLTables)
-        , rc
-        , stmt.native_statement_handle()
-        , (NANODBC_SQLCHAR*)(catalog.empty() ? NULL : catalog.c_str())
-        , (catalog.empty() ? 0 : SQL_NTS)
-        , (NANODBC_SQLCHAR*)(schema.empty() ? NULL : schema.c_str())
-        , (schema.empty() ? 0 : SQL_NTS)
-        , (NANODBC_SQLCHAR*)(table.empty() ? NULL : table.c_str())
-        , (table.empty() ? 0 : SQL_NTS)
-        , (NANODBC_SQLCHAR*)(type.empty() ? NULL : type.c_str())
-        , (type.empty() ? 0 : SQL_NTS));
-    if(!success(rc))
-        NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT);
-
-    result find_result(stmt, 1);
-    return catalog::tables(find_result);
-}
-
-catalog::columns catalog::find_columns(
-        const string_type& column
-      , const string_type& table
-      , const string_type& schema
-      , const string_type& catalog)
-{
-    statement stmt(conn_);
-    RETCODE rc;
-    NANODBC_CALL_RC(
-        NANODBC_FUNC(SQLColumns)
-        , rc
-        , stmt.native_statement_handle()
-        , (NANODBC_SQLCHAR*)(catalog.empty() ? NULL : catalog.c_str())
-        , (catalog.empty() ? 0 : SQL_NTS)
-        , (NANODBC_SQLCHAR*)(schema.empty() ? NULL : schema.c_str())
-        , (schema.empty() ? 0 : SQL_NTS)
-        , (NANODBC_SQLCHAR*)(table.empty() ? NULL : table.c_str())
-        , (table.empty() ? 0 : SQL_NTS)
-        , (NANODBC_SQLCHAR*)(column.empty() ? NULL : column.c_str())
-        , (column.empty() ? 0 : SQL_NTS));
-    if(!success(rc))
-        NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT);
-
-    result find_result(stmt, 1);
-    return catalog::columns(find_result);
-}
-
-catalog::primary_keys catalog::find_primary_keys(
-      const string_type& table
-    , const string_type& schema
-    , const string_type& catalog)
-{
-    statement stmt(conn_);
-    RETCODE rc;
-    NANODBC_CALL_RC(
-        NANODBC_FUNC(SQLPrimaryKeys)
-        , rc
-        , stmt.native_statement_handle()
-        , (NANODBC_SQLCHAR*)(catalog.empty() ? NULL : catalog.c_str())
-        , (catalog.empty() ? 0 : SQL_NTS)
-        , (NANODBC_SQLCHAR*)(schema.empty() ? NULL : schema.c_str())
-        , (schema.empty() ? 0 : SQL_NTS)
-        , (NANODBC_SQLCHAR*)(table.empty() ? NULL : table.c_str())
-        , (table.empty() ? 0 : SQL_NTS));
-    if(!success(rc))
-        NANODBC_THROW_DATABASE_ERROR(stmt.native_statement_handle(), SQL_HANDLE_STMT);
-
-    result find_result(stmt, 1);
-    return catalog::primary_keys(find_result);
-}
-
-} // namespace nanodbc
-
-// 8888888b.                            888 888              8888888888                 888
-// 888   Y88b                           888 888              888                        888
-// 888    888                           888 888              888                        888
-// 888   d88P .d88b.  .d8888b  888  888 888 888888           8888888 888  888  888  .d88888
-// 8888888P" d8P  Y8b 88K      888  888 888 888              888     888  888  888 d88" 888
-// 888 T88b  88888888 "Y8888b. 888  888 888 888              888     888  888  888 888  888
-// 888  T88b Y8b.          X88 Y88b 888 888 Y88b.            888     Y88b 888 d88P Y88b 888
-// 888   T88b "Y8888   88888P'  "Y88888 888  "Y888           888      "Y8888888P"   "Y88888
-// MARK: Result Fwd -
-
-namespace nanodbc
-{
-
-result::result()
-: impl_()
-{
-
-}
-
-result::~result() NANODBC_NOEXCEPT
-{
-
-}
-
-result::result(statement stmt, long rowset_size)
-: impl_(new result_impl(stmt, rowset_size))
-{
-
-}
-
-#ifndef NANODBC_NO_MOVE_CTOR
-    result::result(result&& rhs) NANODBC_NOEXCEPT
-    : impl_(std::move(rhs.impl_))
-    {
-
-    }
-#endif
-
-result::result(const result& rhs)
-: impl_(rhs.impl_)
-{
-
-}
-
-result& result::operator=(result rhs)
-{
-    swap(rhs);
-    return *this;
-}
-
-void result::swap(result& rhs) NANODBC_NOEXCEPT
-{
-    using std::swap;
-    swap(impl_, rhs.impl_);
-}
-
-void* result::native_statement_handle() const
-{
-    return impl_->native_statement_handle();
-}
-
-long result::rowset_size() const NANODBC_NOEXCEPT
-{
-    return impl_->rowset_size();
-}
-
-long result::affected_rows() const
-{
-    return impl_->affected_rows();
-}
-
-long result::rows() const NANODBC_NOEXCEPT
-{
-    return impl_->rows();
-}
-
-short result::columns() const
-{
-    return impl_->columns();
-}
-
-bool result::first()
-{
-    return impl_->first();
-}
-
-bool result::last()
-{
-    return impl_->last();
-}
-
-bool result::next()
-{
-    return impl_->next();
-}
-
-bool result::prior()
-{
-    return impl_->prior();
-}
-
-bool result::move(long row)
-{
-    return impl_->move(row);
-}
-
-bool result::skip(long rows)
-{
-    return impl_->skip(rows);
-}
-
-unsigned long result::position() const
-{
-    return impl_->position();
-}
-
-bool result::end() const NANODBC_NOEXCEPT
-{
-    return impl_->end();
-}
-
-bool result::is_null(short column) const
-{
-    return impl_->is_null(column);
-}
-
-bool result::is_null(const string_type& column_name) const
-{
-    return impl_->is_null(column_name);
-}
-
-string_type result::column_name(short column) const
-{
-    return impl_->column_name(column);
-}
-
-long result::column_size(short column) const
-{
-    return impl_->column_size(column);
-}
-
-short result::column(const string_type& column_name) const
-{
-    return impl_->column(column_name);
-}
-
-int result::column_datatype(short column) const
-{
-    return impl_->column_datatype(column);
-}
-
-int result::column_datatype(const string_type& column_name) const
-{
-    return impl_->column_datatype(column_name);
-}
-
-int result::column_c_datatype(short column) const
-{
-    return impl_->column_c_datatype(column);
-}
-
-int result::column_c_datatype(const string_type& column_name) const
-{
-    return impl_->column_c_datatype(column_name);
-}
-
-bool result::next_result()
-{
-    return impl_->next_result();
-}
-
-template<class T>
-void result::get_ref(short column, T& result) const
-{
-    return impl_->get_ref<T>(column, result);
-}
-
-template<class T>
-void result::get_ref(short column, const T& fallback, T& result) const
-{
-    return impl_->get_ref<T>(column, fallback, result);
-}
-
-template<class T>
-void result::get_ref(const string_type& column_name, T& result) const
-{
-    return impl_->get_ref<T>(column_name, result);
-}
-
-template<class T>
-void result::get_ref(const string_type& column_name, const T& fallback, T& result) const
-{
-    return impl_->get_ref<T>(column_name, fallback, result);
-}
-
-template<class T>
-T result::get(short column) const
-{
-    return impl_->get<T>(column);
-}
-
-template<class T>
-T result::get(short column, const T& fallback) const
-{
-    return impl_->get<T>(column, fallback);
-}
-
-template<class T>
-T result::get(const string_type& column_name) const
-{
-    return impl_->get<T>(column_name);
-}
-
-template<class T>
-T result::get(const string_type& column_name, const T& fallback) const
-{
-    return impl_->get<T>(column_name, fallback);
-}
-
-result::operator bool() const
-{
-    return static_cast<bool>(impl_);
-}
-
-// The following are the only supported instantiations of result::get_ref().
-template void result::get_ref(short, string_type::value_type&) const;
-template void result::get_ref(short, short&) const;
-template void result::get_ref(short, unsigned short&) const;
-template void result::get_ref(short, int32_t&) const;
-template void result::get_ref(short, uint32_t&) const;
-template void result::get_ref(short, int64_t&) const;
-template void result::get_ref(short, uint64_t&) const;
-template void result::get_ref(short, float&) const;
-template void result::get_ref(short, double&) const;
-template void result::get_ref(short, string_type&) const;
-template void result::get_ref(short, date&) const;
-template void result::get_ref(short, timestamp&) const;
-
-template void result::get_ref(const string_type&, string_type::value_type&) const;
-template void result::get_ref(const string_type&, short&) const;
-template void result::get_ref(const string_type&, unsigned short&) const;
-template void result::get_ref(const string_type&, int32_t&) const;
-template void result::get_ref(const string_type&, uint32_t&) const;
-template void result::get_ref(const string_type&, int64_t&) const;
-template void result::get_ref(const string_type&, uint64_t&) const;
-template void result::get_ref(const string_type&, float&) const;
-template void result::get_ref(const string_type&, double&) const;
-template void result::get_ref(const string_type&, string_type&) const;
-template void result::get_ref(const string_type&, date&) const;
-template void result::get_ref(const string_type&, timestamp&) const;
-
-// The following are the only supported instantiations of result::get_ref() with fallback.
-template void result::get_ref(short, const string_type::value_type&, string_type::value_type&) const;
-template void result::get_ref(short, const short&, short&) const;
-template void result::get_ref(short, const unsigned short&, unsigned short&) const;
-template void result::get_ref(short, const int32_t&, int32_t&) const;
-template void result::get_ref(short, const uint32_t&, uint32_t&) const;
-template void result::get_ref(short, const int64_t&, int64_t&) const;
-template void result::get_ref(short, const uint64_t&, uint64_t&) const;
-template void result::get_ref(short, const float&, float&) const;
-template void result::get_ref(short, const double&, double&) const;
-template void result::get_ref(short, const string_type&, string_type&) const;
-template void result::get_ref(short, const date&, date&) const;
-template void result::get_ref(short, const timestamp&, timestamp&) const;
-
-template void result::get_ref(const string_type&, const string_type::value_type&, string_type::value_type&) const;
-template void result::get_ref(const string_type&, const short&, short&) const;
-template void result::get_ref(const string_type&, const unsigned short&, unsigned short&) const;
-template void result::get_ref(const string_type&, const int32_t&, int32_t&) const;
-template void result::get_ref(const string_type&, const uint32_t&, uint32_t&) const;
-template void result::get_ref(const string_type&, const int64_t&, int64_t&) const;
-template void result::get_ref(const string_type&, const uint64_t&, uint64_t&) const;
-template void result::get_ref(const string_type&, const float&, float&) const;
-template void result::get_ref(const string_type&, const double&, double&) const;
-template void result::get_ref(const string_type&, const string_type&, string_type&) const;
-template void result::get_ref(const string_type&, const date&, date&) const;
-template void result::get_ref(const string_type&, const timestamp&, timestamp&) const;
-
-// The following are the only supported instantiations of result::get().
-template string_type::value_type result::get(short) const;
-template short result::get(short) const;
-template unsigned short result::get(short) const;
-template int32_t result::get(short) const;
-template uint32_t result::get(short) const;
-template int64_t result::get(short) const;
-template uint64_t result::get(short) const;
-template float result::get(short) const;
-template double result::get(short) const;
-template string_type result::get(short) const;
-template date result::get(short) const;
-template timestamp result::get(short) const;
-
-template string_type::value_type result::get(const string_type&) const;
-template short result::get(const string_type&) const;
-template unsigned short result::get(const string_type&) const;
-template int32_t result::get(const string_type&) const;
-template uint32_t result::get(const string_type&) const;
-template int64_t result::get(const string_type&) const;
-template uint64_t result::get(const string_type&) const;
-template float result::get(const string_type&) const;
-template double result::get(const string_type&) const;
-template string_type result::get(const string_type&) const;
-template date result::get(const string_type&) const;
-template timestamp result::get(const string_type&) const;
-
-// The following are the only supported instantiations of result::get() with fallback.
-template string_type::value_type result::get(short, const string_type::value_type&) const;
-template short result::get(short, const short&) const;
-template unsigned short result::get(short, const unsigned short&) const;
-template int32_t result::get(short, const int32_t&) const;
-template uint32_t result::get(short, const uint32_t&) const;
-template int64_t result::get(short, const int64_t&) const;
-template uint64_t result::get(short, const uint64_t&) const;
-template float result::get(short, const float&) const;
-template double result::get(short, const double&) const;
-template string_type result::get(short, const string_type&) const;
-template date result::get(short, const date&) const;
-template timestamp result::get(short, const timestamp&) const;
-
-template string_type::value_type result::get(const string_type&, const string_type::value_type&) const;
-template short result::get(const string_type&, const short&) const;
-template unsigned short result::get(const string_type&, const unsigned short&) const;
-template int32_t result::get(const string_type&, const int32_t&) const;
-template uint32_t result::get(const string_type&, const uint32_t&) const;
-template int64_t result::get(const string_type&, const int64_t&) const;
-template uint64_t result::get(const string_type&, const uint64_t&) const;
-template float result::get(const string_type&, const float&) const;
-template double result::get(const string_type&, const double&) const;
-template string_type result::get(const string_type&, const string_type&) const;
-template date result::get(const string_type&, const date&) const;
-template timestamp result::get(const string_type&, const timestamp&) const;
-
-} // namespace nanodbc
-
-#undef NANODBC_THROW_DATABASE_ERROR
-#undef NANODBC_STRINGIZE
-#undef NANODBC_STRINGIZE_I
-#undef NANODBC_CALL_RC
-#undef NANODBC_CALL
-
-#endif // DOXYGEN

+ 0 - 1391
Source/ThirdParty/nanodbc/src/nanodbc.h

@@ -1,1391 +0,0 @@
-//! \file nanodbc.h The entirety of nanodbc can be found within this file and nanodbc.cpp.
-
-//! \mainpage
-//!
-//! \section synopsis Synopsis
-//! This library provides a wrapper API for the native ODBC API. It aims to do everything ODBC does, but with a \b much nicer interface.
-//! Anything it doesn't (yet) do can be done by retrieving the native ODBC handles and dropping down to straight ODBC C API code.
-//! For more propaganda, please see the <a href="http://lexicalunit.github.com/nanodbc/">project homepage</a>.
-//!
-//! \section toc Table of Contents
-//! - \ref license "License"
-//! - \ref credits "Credits"
-//! - Source level documentation:
-//!     - \ref nanodbc "nanodbc namespace"
-//!     - \ref exceptions
-//!     - \ref utility
-//!     - \ref mainc
-//!     - \ref mainf
-//!     - \ref binding
-//!     - \ref bind_multi
-//!     - \ref bind_strings
-//!
-//! \section license License
-//! <div class="license">
-//! Copyright (C) 2013 lexicalunit <[email protected]>
-//!
-//! The MIT License
-//!
-//! Permission is hereby granted, free of charge, to any person obtaining a copy
-//! of this software and associated documentation files (the "Software"), to deal
-//! in the Software without restriction, including without limitation the rights
-//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-//! copies of the Software, and to permit persons to whom the Software is
-//! furnished to do so, subject to the following conditions:
-//!
-//! The above copyright notice and this permission notice shall be included in
-//! all copies or substantial portions of the Software.
-//!
-//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-//! THE SOFTWARE.
-//! </div>
-//!
-//! \section credits Credits
-//! <div class="license">
-//! Much of the code in this file was originally derived from TinyODBC.
-//! TinyODBC is hosted at http://code.google.com/p/tiodbc/
-//! Copyright (C) 2008 SqUe [email protected]
-//! License: The MIT License
-//!
-//! The idea for using RAII for transactions was inspired by SimpleDB: C++ ODBC database API,
-//! however the code in nanodbc is original and not derived from SimpleDB. Therefore
-//! the LGPL license under which SimpleDB is distributed does NOT apply to nanodbc.
-//! SimpleDB is hosted at http://simpledb.sourceforge.net
-//! Copyright (C) 2006 Eminence Technology Pty Ltd
-//! Copyright (C) 2008-2010,2012 Russell Kliese [email protected]
-//! License: GNU Lesser General Public version 2.1
-//!
-//! Some improvements and features are based on The Python ODBC Library.
-//! The Python ODBC Library is hosted at http://code.google.com/p/pyodbc/
-//! License: The MIT License
-//!
-//! Implementation of column binding inspired by Nick E. Geht's source code posted to on CodeGuru.
-//! GSODBC hosted at http://www.codeguru.com/mfc_database/gsodbc.html
-//! Copyright (C) 2002 Nick E. Geht
-//! License: Perpetual license to reproduce, distribute, adapt, perform, display, and sublicense.
-//! See http://www.codeguru.com/submission-guidelines.php for details.
-//! </div>
-
-#ifndef NANODBC_H
-#define NANODBC_H
-
-#include <functional>
-#include <memory>
-#include <stdexcept>
-#include <string>
-#include <vector>
-
-#ifndef __clang__
-    #include <cstdint>
-#endif
-
-//! \brief The entirety of nanodbc can be found within this one namespace.
-//! \note This library does not make any exception safety guarantees, but should work just fine with a threading enabled ODBC driver. If you want to use nanodbc objects in threads I recommend each thread keep their own connection to the database. Otherwise you must synchronize any access to nanodbc objects.
-namespace nanodbc
-{
-
-//  .d8888b.                     .d888 d8b                                   888    d8b
-// d88P  Y88b                   d88P"  Y8P                                   888    Y8P
-// 888    888                   888                                          888
-// 888         .d88b.  88888b.  888888 888  .d88b.  888  888 888d888 8888b.  888888 888  .d88b.  88888b.
-// 888        d88""88b 888 "88b 888    888 d88P"88b 888  888 888P"      "88b 888    888 d88""88b 888 "88b
-// 888    888 888  888 888  888 888    888 888  888 888  888 888    .d888888 888    888 888  888 888  888
-// Y88b  d88P Y88..88P 888  888 888    888 Y88b 888 Y88b 888 888    888  888 Y88b.  888 Y88..88P 888  888
-//  "Y8888P"   "Y88P"  888  888 888    888  "Y88888  "Y88888 888    "Y888888  "Y888 888  "Y88P"  888  888
-//                                              888
-//                                         Y8b d88P
-//                                          "Y88P"
-// MARK: Configuration -
-
-//! \addtogroup macros Macros
-//! \brief Macros that nanodbc uses, can be overriden by users.
-//!
-//! @{
-
-#ifdef DOXYGEN
-    //! \def NANODBC_ASSERT(expression)
-    //! \brief Assertion.
-    //!
-    //! By default, nanodbc uses C \c assert() for internal assertions.
-    //! 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
-    //! #endif
-    //! \endcode
-    #define NANODBC_ASSERT(expression) assert(expression)
-#endif
-
-//! @}
-
-// You must explicitly request Unicode support by defining NANODBC_USE_UNICODE at compile time.
-#ifndef DOXYGEN
-    #ifdef NANODBC_USE_UNICODE
-        #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
-        typedef std::int64_t null_type;
-    #elif !defined(_WIN64) && defined(__LP64__)
-        // LP64 machine: OS X or Linux
-        typedef long null_type;
-    #else
-        // 32-bit machine
-        typedef long null_type;
-    #endif
-#else
-    //! \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;
-    //! \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 \c noexcept or \c std::move.
-    #define NANODBC_NOEXCEPT
-    #define NANODBC_NO_MOVE_CTOR
-#else
-    #define NANODBC_NOEXCEPT noexcept
-#endif
-
-// 8888888888                                      888    888                        888 888 d8b
-// 888                                             888    888                        888 888 Y8P
-// 888                                             888    888                        888 888
-// 8888888    888d888 888d888 .d88b.  888d888      8888888888  8888b.  88888b.   .d88888 888 888 88888b.   .d88b.
-// 888        888P"   888P"  d88""88b 888P"        888    888     "88b 888 "88b d88" 888 888 888 888 "88b d88P"88b
-// 888        888     888    888  888 888          888    888 .d888888 888  888 888  888 888 888 888  888 888  888
-// 888        888     888    Y88..88P 888          888    888 888  888 888  888 Y88b 888 888 888 888  888 Y88b 888
-// 8888888888 888     888     "Y88P"  888          888    888 "Y888888 888  888  "Y88888 888 888 888  888  "Y88888
-//                                                                                                             888
-//                                                                                                        Y8b d88P
-//                                                                                                         "Y88P"
-// MARK: Error Handling -
-
-//! \addtogroup exceptions Exception types
-//! \brief Possible error conditions.
-//!
-//! 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 \c SQLGetDiagRec().
-//! @{
-
-//! \brief Type incompatible.
-//! \see exceptions
-class type_incompatible_error : public std::runtime_error
-{
-public:
-    type_incompatible_error();
-    const char* what() const NANODBC_NOEXCEPT;
-};
-
-//! \brief Accessed null data.
-//! \see exceptions
-class null_access_error : public std::runtime_error
-{
-public:
-    null_access_error();
-    const char* what() const NANODBC_NOEXCEPT;
-};
-
-//! \brief Index out of range.
-//! \see exceptions
-class index_range_error : public std::runtime_error
-{
-public:
-    index_range_error();
-    const char* what() const NANODBC_NOEXCEPT;
-};
-
-//! \brief Programming logic error.
-//! \see exceptions
-class programming_error : public std::runtime_error
-{
-public:
-    explicit programming_error(const std::string& info);
-    const char* what() const NANODBC_NOEXCEPT;
-};
-
-//! \brief General database error.
-//! \see exceptions
-class database_error : public std::runtime_error
-{
-public:
-    //! \brief Creates a runtime_error with a message describing the last ODBC error generated for the given handle and handle_type.
-    //! \param handle The native ODBC statement or connection handle.
-    //! \param handle_type The native ODBC handle type code for the given handle.
-    //! \param info Additional information that will be appended to the beginning of the error message.
-    database_error(void* handle, short handle_type, const std::string& info = "");
-    const char* what() const NANODBC_NOEXCEPT;
-    const long native() const NANODBC_NOEXCEPT;
-    const std::string state() const NANODBC_NOEXCEPT;
-private:
-    long native_error;
-    std::string sql_state;
-    std::string message;
-};
-
-//! @}
-
-// 888     888 888    d8b 888 d8b 888    d8b
-// 888     888 888    Y8P 888 Y8P 888    Y8P
-// 888     888 888        888     888
-// 888     888 888888 888 888 888 888888 888  .d88b.  .d8888b
-// 888     888 888    888 888 888 888    888 d8P  Y8b 88K
-// 888     888 888    888 888 888 888    888 88888888 "Y8888b.
-// Y88b. .d88P Y88b.  888 888 888 Y88b.  888 Y8b.          X88
-//  "Y88888P"   "Y888 888 888 888  "Y888 888  "Y8888   88888P'
-// MARK: Utilities -
-
-//! \addtogroup utility Utilities
-//! \brief Additional nanodbc utility classes and functions.
-//!
-//! \{
-
-//! \brief A type for representing date data.
-struct date
-{
-    std::int16_t year; //!< Year [0-inf).
-    std::int16_t month; //!< Month of the year [1-12].
-    std::int16_t day; //!< Day of the month [1-31].
-};
-
-//! \brief A type for representing timestamp data.
-struct timestamp
-{
-    std::int16_t year;   //!< Year [0-inf).
-    std::int16_t month;  //!< Month of the year [1-12].
-    std::int16_t day;    //!< Day of the month [1-31].
-    std::int16_t hour;   //!< Hours since midnight [0-23].
-    std::int16_t min;    //!< Minutes after the hour [0-59].
-    std::int16_t sec;    //!< Seconds after the minute.
-    std::int32_t fract;  //!< Fractional seconds.
-};
-
-//! \}
-
-//! \addtogroup mainc Main classes
-//! \brief Main nanodbc classes.
-//!
-//! @{
-
-// 88888888888                                                  888    d8b
-//     888                                                      888    Y8P
-//     888                                                      888
-//     888  888d888 8888b.  88888b.  .d8888b   8888b.   .d8888b 888888 888  .d88b.  88888b.
-//     888  888P"      "88b 888 "88b 88K          "88b d88P"    888    888 d88""88b 888 "88b
-//     888  888    .d888888 888  888 "Y8888b. .d888888 888      888    888 888  888 888  888
-//     888  888    888  888 888  888      X88 888  888 Y88b.    Y88b.  888 Y88..88P 888  888
-//     888  888    "Y888888 888  888  88888P' "Y888888  "Y8888P  "Y888 888  "Y88P"  888  888
-// MARK: Transaction -
-
-//! \brief A resource for managing transaction commits and rollbacks.
-//!
-//! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-class transaction
-{
-public:
-    //! \brief Begin a transaction on the given connection object.
-    //! \post Operations that modify the database must now be committed before taking effect.
-    //! \throws database_error
-    explicit transaction(const class connection& conn);
-
-    //! Copy constructor.
-    transaction(const transaction& rhs);
-
-    #ifndef NANODBC_NO_MOVE_CTOR
-        //! Move constructor.
-        transaction(transaction&& rhs) NANODBC_NOEXCEPT;
-    #endif
-
-    //! Assignment.
-    transaction& operator=(transaction rhs);
-
-    //! Member swap.
-    void swap(transaction& rhs) NANODBC_NOEXCEPT;
-
-    //! \brief If this transaction has not been committed, will will rollback any modifying operations.
-    ~transaction() NANODBC_NOEXCEPT;
-
-    //! \brief Marks this transaction for commit.
-    //! \throws database_error
-    void commit();
-
-    //! \brief Marks this transaction for rollback.
-    void rollback() NANODBC_NOEXCEPT;
-
-    //! Returns the connection object.
-    class connection& connection();
-
-    //! Returns the connection object.
-    const class connection& connection() const;
-
-    //! Returns the connection object.
-    operator class connection&();
-
-    //! Returns the connection object.
-    operator const class connection&() const;
-
-private:
-    class transaction_impl;
-    friend class nanodbc::connection;
-
-private:
-    std::shared_ptr<transaction_impl> impl_;
-};
-
-//  .d8888b.  888             888                                            888
-// d88P  Y88b 888             888                                            888
-// Y88b.      888             888                                            888
-//  "Y888b.   888888  8888b.  888888 .d88b.  88888b.d88b.   .d88b.  88888b.  888888
-//     "Y88b. 888        "88b 888   d8P  Y8b 888 "888 "88b d8P  Y8b 888 "88b 888
-//       "888 888    .d888888 888   88888888 888  888  888 88888888 888  888 888
-// Y88b  d88P Y88b.  888  888 Y88b. Y8b.     888  888  888 Y8b.     888  888 Y88b.
-//  "Y8888P"   "Y888 "Y888888  "Y888 "Y8888  888  888  888  "Y8888  888  888  "Y888
-// MARK: Statement -
-
-//! \brief Represents a statement on the database.
-class statement
-{
-public:
-    //! \brief Provides support for retrieving output/return parameters.
-    //! \see binding
-    enum param_direction
-    {
-        PARAM_IN //!< Binding an input parameter.
-        , PARAM_OUT //!< Binding an output parameter.
-        , PARAM_INOUT //!< Binding an input/output parameter.
-        , PARAM_RETURN //!< Binding a return parameter.
-    };
-
-public:
-    //! \brief Creates a new un-prepared statement.
-    //! \see execute(), just_execute(), execute_direct(), just_execute_direct(), open(), prepare()
-    statement();
-
-    //! \brief Constructs a statement object and associates it to the given connection.
-    //! \param conn The connection to use.
-    //! \see open(), prepare()
-    explicit statement(class connection& conn);
-
-    //! \brief Constructs and prepares a statement using the given connection and query.
-    //! \param conn The connection to use.
-    //! \param query The SQL query statement.
-    //! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-    //! \see execute(), just_execute(), execute_direct(), just_execute_direct(), open(), prepare()
-    statement(class connection& conn, const string_type& query, long timeout = 0);
-
-    //! Copy constructor.
-    statement(const statement& rhs);
-
-    #ifndef NANODBC_NO_MOVE_CTOR
-        //! Move constructor.
-        statement(statement&& rhs) NANODBC_NOEXCEPT;
-    #endif
-
-    //! Assignment.
-    statement& operator=(statement rhs);
-
-    //! Member swap.
-    void swap(statement& rhs) NANODBC_NOEXCEPT;
-
-    //! \brief Closes the statement.
-    //! \see close()
-    ~statement() NANODBC_NOEXCEPT;
-
-    //! \brief Creates a statement for the given connection.
-    //! \param conn The connection where the statement will be executed.
-    //! \throws database_error
-    void open(class connection& conn);
-
-    //! \brief Returns true if connection is open.
-    bool open() const;
-
-    //! \brief Returns true if connected to the database.
-    bool connected() const;
-
-    //! \brief Returns the associated connection object if any.
-    class connection& connection();
-
-    //! \brief Returns the associated connection object if any.
-    const class connection& connection() const;
-
-    //! \brief Returns the native ODBC statement handle.
-    void* native_statement_handle() const;
-
-    //! \brief Closes the statement and frees all associated resources.
-    void close();
-
-    //! \brief Cancels execution of the statement.
-    //! \throws database_error
-    void cancel();
-
-    //! \brief Opens and prepares the given statement to execute on the given connection.
-    //! \param conn The connection where the statement will be executed.
-    //! \param query The SQL query that will be executed.
-    //! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-    //! \see open()
-    //! \throws database_error
-    void prepare(class connection& conn, const string_type& query, long timeout = 0);
-
-    //! \brief Prepares the given statement to execute its associated connection.
-    //! If the statement is not open throws programming_error.
-    //! \param query The SQL query that will be executed.
-    //! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-    //! \see open()
-    //! \throws database_error, programming_error
-    void prepare(const string_type& query, long timeout = 0);
-
-    //! \brief Sets the number in seconds before query timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    void timeout(long timeout = 0);
-
-    //! \brief Immediately opens, prepares, and executes the given query directly on the given connection.
-    //! \param conn The connection where the statement will be executed.
-    //! \param query The SQL query that will be executed.
-    //! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-    //! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-    //! \return A result set object.
-    //! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-    //! \see open(), prepare(), execute(), result, transaction
-    class result execute_direct(class connection& conn, const string_type& query, long batch_operations = 1, long timeout = 0);
-
-    //! \brief Immediately opens, prepares, and executes the given query directly on the given connection, in asynchronous mode.
-    //!
-    //! This method will only be available if nanodbc is built against ODBC headers and library that supports asynchronous mode.
-    //! Such that the identifiers `SQL_ATTR_ASYNC_STMT_EVENT` and `SQLCompleteAsync` are extant. Otherwise
-    //! this method will be defined, but not implemented.
-    //!
-    //! \param conn The connection where the statement will be executed.
-    //! \param event_handle The event handle for which the caller will wait before calling async_complete.
-    //! \param query The SQL query that will be executed.
-    //! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-    //! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-    //! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-    //! \see open(), prepare(), execute(), result, transaction
-    void async_execute_direct(class connection& conn, void* event_handle, const string_type& query, long batch_operations = 1, long timeout = 0);
-
-    //! \brief Completes a previously initiated asynchronous query operation, returning the result.
-    //!
-    //! This method will only be available if nanodbc is built against ODBC headers and library that supports asynchronous mode.
-    //! Such that the identifiers `SQL_ATTR_ASYNC_STMT_EVENT` and `SQLCompleteAsync` are extant. Otherwise
-    //! this method will be defined, but not implemented.
-    //!
-    //! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-    class result async_complete(long batch_operations = 1);
-
-    //! \brief Execute the previously prepared query now without constructing result object.
-    //! \param conn The connection where the statement will be executed.
-    //! \param query The SQL query that will be executed.
-    //! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-    //! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    //! \return A result set object.
-    //! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-    //! \see open(), prepare(), execute(), execute_direct(), result, transaction
-    void just_execute_direct(class connection& conn, const string_type& query, long batch_operations = 1, long timeout = 0);
-
-    //! \brief Execute the previously prepared query now.
-    //! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-    //! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    //! \return A result set object.
-    //! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-    //! \see open(), prepare(), result, transaction
-    class result execute(long batch_operations = 1, long timeout = 0);
-
-    //! \brief Execute the previously prepared query now without constructing result object.
-    //! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-    //! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    //! \return A result set object.
-    //! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-    //! \see open(), prepare(), execute(), result, transaction
-    void just_execute(long batch_operations = 1, long timeout = 0);
-
-    //! \brief Returns the input and output paramters of the specified stored procedure.
-    //! \param catalog The catalog name of the procedure.
-    //! \param schema Pattern to use for schema names.
-    //! \param procedure The name of the procedure.
-    //! \param column Pattern to use for column names.
-    //! \throws database_error
-    //! \return A result set object.
-    class result procedure_columns(const string_type& catalog, const string_type& schema, const string_type& procedure, const string_type& column);
-
-    //! \brief Returns the number of rows affected by the request or -1 if the number of affected rows is not available.
-    //! \throws database_error
-    long affected_rows() const;
-
-    //! \brief Returns the number of columns in a result set.
-    //! \throws database_error
-    short columns() const;
-
-    //! \brief Resets all currently bound parameters.
-    void reset_parameters() NANODBC_NOEXCEPT;
-
-    //! \brief Returns the parameter size for the indicated parameter placeholder within a prepared statement.
-    unsigned long parameter_size(short param) const;
-
-    //! \addtogroup binding Binding parameters
-    //! \brief These functions are used to bind values to ODBC parameters.
-    //!
-    //! @{
-
-    //! \brief Binds the given value to the given parameter placeholder number in the prepared statement.
-    //!
-    //! If your prepared SQL query has any ? placeholders, this is how you bind values to them.
-    //! Placeholder numbers count from left to right and are 0-indexed.
-    //!
-    //! It is NOT possible to use these functions for bulk operations as number of elements is not specified here.
-    //!
-    //! \param param Placeholder position.
-    //! \param value Value to substitute into placeholder.
-    //! \param direction ODBC parameter direction.
-    //! \throws database_error
-    template<class T>
-    void bind(short param, const T* value, param_direction direction = PARAM_IN);
-
-    //! \addtogroup bind_multi Binding multiple non-string values
-    //! \brief Binds the given values to the given parameter placeholder number in the prepared statement.
-    //!
-    //! If your prepared SQL query has any ? placeholders, this is how you bind values to them.
-    //! Placeholder numbers count from left to right and are 0-indexed.
-    //!
-    //! It is possible to use these functions for bulk operations.
-    //!
-    //! \param param Placeholder position.
-    //! \param values Values to substitute into placeholder.
-    //! \param elements The number of elements being bound.
-    //! \param null_sentry Value which should represent a null value.
-    //! \param nulls Flags for values that should be set to a null value.
-    //! \param param_direciton ODBC parameter direction.
-    //! \throws database_error
-    //!
-    //! @{
-
-    //! \brief Binds multiple values.
-    //! \see bind_multi
-    template<class T>
-    void bind(short param, const T* values, std::size_t elements, param_direction direction = PARAM_IN);
-
-    //! \brief Binds multiple values.
-    //! \see bind_multi
-    template<class T>
-    void bind(short param, const T* values, std::size_t elements, const T* null_sentry, param_direction direction = PARAM_IN);
-
-    //! \brief Binds multiple values.
-    //! \see bind_multi
-    template<class T>
-    void bind(short param, const T* values, std::size_t elements, const bool* nulls, param_direction direction = PARAM_IN);
-
-    //! @}
-
-    //! \addtogroup bind_strings Binding multiple string values
-    //! \brief Binds the given string values to the given parameter placeholder number in the prepared statement.
-    //!
-    //! If your prepared SQL query has any ? placeholders, this is how you bind values to them.
-    //! Placeholder numbers count from left to right and are 0-indexed.
-    //!
-    //! It is possible to use these functions for bulk operations.
-    //!
-    //! \param param Placeholder position.
-    //! \param values Values to substitute into placeholder.
-    //! \param length Maximum length of string elements.
-    //! \param elements The number of elements being bound. Otherwise the value N is taken as the number of elements.
-    //! \param null_sentry Value which should represent a null value.
-    //! \param nulls Flags for values that should be set to a null value.
-    //! \param param_direciton ODBC parameter direction.
-    //! \throws database_error
-    //!
-    //! @{
-
-    //! \brief Binds multiple string values.
-    //! \see bind_strings
-    void bind_strings(
-        short param
-        , const string_type::value_type* values
-        , std::size_t length
-        , std::size_t elements
-        , param_direction direction = PARAM_IN);
-
-    //! \brief Binds multiple string values.
-    //! \see bind_strings
-    template<std::size_t N, std::size_t M>
-    void bind_strings(
-        short param
-        , const string_type::value_type(&values)[N][M]
-        , param_direction direction = PARAM_IN)
-    {
-        bind_strings(
-            param
-            , reinterpret_cast<const string_type::value_type*>(values)
-            , M
-            , N
-            , direction);
-    }
-
-    //! \brief Binds multiple string values.
-    //! \see bind_strings
-    void bind_strings(
-        short param
-        , const string_type::value_type* values
-        , std::size_t length
-        , std::size_t elements
-        , const string_type::value_type* null_sentry
-        , param_direction direction = PARAM_IN);
-
-    //! \brief Binds multiple string values.
-    //! \see bind_strings
-    template<std::size_t N, std::size_t M>
-    void bind_strings(
-        short param
-        , const string_type::value_type(&values)[N][M]
-        , const string_type::value_type* null_sentry
-        , param_direction direction = PARAM_IN)
-    {
-        bind_strings(
-            param
-            , reinterpret_cast<const string_type::value_type*>(values)
-            , M
-            , N
-            , null_sentry
-            , direction);
-    }
-
-    //! \brief Binds multiple string values.
-    //! \see bind_strings
-    void bind_strings(
-        short param
-        , const string_type::value_type* values
-        , std::size_t length
-        , std::size_t elements
-        , const bool* nulls
-        , param_direction direction = PARAM_IN);
-
-    //! \brief Binds multiple string values.
-    //! \see bind_strings
-    template<std::size_t N, std::size_t M>
-    void bind_strings(
-        short param
-        , const string_type::value_type(&values)[N][M]
-        , const bool* nulls
-        , param_direction direction = PARAM_IN)
-    {
-        bind_strings(
-            param
-            , reinterpret_cast<const string_type::value_type*>(values)
-            , M
-            , N
-            , nulls
-            , direction);
-    }
-
-    //! @}
-
-    //! \brief Binds null values to the given parameter placeholder number in the prepared statement.
-    //!
-    //! If your prepared SQL query has any ? placeholders, this is how you bind values to them.
-    //! Placeholder numbers count from left to right and are 0-indexed.
-    //!
-    //! It is possible to use this function for bulk operations.
-    //!
-    //! \param param Placeholder position.
-    //! \param elements The number of elements being bound.
-    //! \throws database_error
-    void bind_null(short param, std::size_t elements = 1);
-
-    //! @}
-
-private:
-    typedef std::function<bool (std::size_t)> null_predicate_type;
-
-private:
-    class statement_impl;
-    friend class nanodbc::result;
-
-private:
-    std::shared_ptr<statement_impl> impl_;
-};
-
-//  .d8888b.                                               888    d8b
-// d88P  Y88b                                              888    Y8P
-// 888    888                                              888
-// 888         .d88b.  88888b.  88888b.   .d88b.   .d8888b 888888 888  .d88b.  88888b.
-// 888        d88""88b 888 "88b 888 "88b d8P  Y8b d88P"    888    888 d88""88b 888 "88b
-// 888    888 888  888 888  888 888  888 88888888 888      888    888 888  888 888  888
-// Y88b  d88P Y88..88P 888  888 888  888 Y8b.     Y88b.    Y88b.  888 Y88..88P 888  888
-//  "Y8888P"   "Y88P"  888  888 888  888  "Y8888   "Y8888P  "Y888 888  "Y88P"  888  888
-// MARK: Connection -
-
-//! \brief Manages and encapsulates ODBC resources such as the connection and environment handles.
-class connection
-{
-public:
-    //! \brief Create new connection object, initially not connected.
-    connection();
-
-    //! Copy constructor.
-    connection(const connection& rhs);
-
-    #ifndef NANODBC_NO_MOVE_CTOR
-        //! Move constructor.
-        connection(connection&& rhs) NANODBC_NOEXCEPT;
-    #endif
-
-    //! Assignment.
-    connection& operator=(connection rhs);
-
-    //! Member swap.
-    void swap(connection&) NANODBC_NOEXCEPT;
-
-    //! \brief Create new connection object and immediately connect to the given data source.
-    //! \param dsn The name of the data source.
-    //! \param user The username for authenticating to the data source.
-    //! \param pass The password for authenticating to the data source.
-    //! \param timeout The number in seconds before connection timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    //! \see connected(), connect()
-    connection(
-        const string_type& dsn
-        , const string_type& user
-        , const string_type& pass
-        , long timeout = 0);
-
-    //! \brief Create new connection object and immediately connect using the given connection string.
-    //! \param connection_string The connection string for establishing a connection.
-    //! \param timeout The number in seconds before connection timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    //! \see connected(), connect()
-    connection(const string_type& connection_string, long timeout = 0);
-
-    //! \brief Automatically disconnects from the database and frees all associated resources.
-    //!
-    //! Will not throw even if disconnecting causes some kind of error and raises an exception.
-    //! If you explicitly need to know if disconnect() succeeds, call it directly.
-    ~connection() NANODBC_NOEXCEPT;
-
-    //! \brief Connect to the given data source.
-    //! \param dsn The name of the data source.
-    //! \param user The username for authenticating to the data source.
-    //! \param pass The password for authenticating to the data source.
-    //! \param timeout The number in seconds before connection timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    //! \see connected()
-    void connect(
-        const string_type& dsn
-        , const string_type& user
-        , const string_type& pass
-        , long timeout = 0);
-
-    //! \brief Connect using the given connection string.
-    //! \param connection_string The connection string for establishing a connection.
-    //! \param timeout The number in seconds before connection timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    //! \see connected()
-    void connect(const string_type& connection_string, long timeout = 0);
-
-    //! \brief Initiate an asynchronous connection operation to the given data source.
-    //!
-    //! This method will only be available if nanodbc is built against ODBC headers and library that supports asynchronous mode.
-    //! Such that the identifiers `SQL_ATTR_ASYNC_DBC_EVENT` and `SQLCompleteAsync` are extant. Otherwise
-    //! this method will be defined, but not implemented.
-    //!
-    //! \param dsn The name of the data source.
-    //! \param user The username for authenticating to the data source.
-    //! \param pass The password for authenticating to the data source.
-    //! \param event_handle The event handle for which the caller will wait before calling async_complete.
-    //! \param timeout The number in seconds before connection timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    //! \see connected()
-    void async_connect(
-        const string_type& dsn
-        , const string_type& user
-        , const string_type& pass
-        , void* event_handle
-        , long timeout = 0);
-
-    //! \brief Initiate an asynchronous connection operation using the given connection string.
-    //!
-    //! This method will only be available if nanodbc is built against ODBC headers and library that supports asynchronous mode.
-    //! Such that the identifiers `SQL_ATTR_ASYNC_DBC_EVENT` and `SQLCompleteAsync` are extant. Otherwise
-    //! this method will be defined, but not implemented.
-    //!
-    //! \param connection_string The connection string for establishing a connection.
-    //! \param event_handle The event handle for which the caller will wait before calling async_complete.
-    //! \param timeout The number in seconds before connection timeout. Default is 0 indicating no timeout.
-    //! \throws database_error
-    //! \see connected()
-    void async_connect(const string_type& connection_string, void* event_handle, long timeout = 0);
-
-    //! \brief Completes a previously initiated asynchronous connection operation.
-    void async_complete();
-
-    //! \brief Returns true if connected to the database.
-    bool connected() const;
-
-    //! \brief Disconnects from the database, but maintains environment and handle resources.
-    void disconnect();
-
-    //! \brief Returns the number of transactions currently held for this connection.
-    std::size_t transactions() const;
-
-    //! \brief Returns the native ODBC database connection handle.
-    void* native_dbc_handle() const;
-
-    //! \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;
-
-    //! \brief Returns the name of the currently connected database.
-    //! Returns the current SQL_DATABASE_NAME information value associated with the connection.
-    string_type database_name() const;
-
-    //! \brief Returns the name of the current catalog.
-    //! Returns the current setting of the connection attribute SQL_ATTR_CURRENT_CATALOG.
-    string_type catalog_name() const;
-
-private:
-    std::size_t ref_transaction();
-    std::size_t unref_transaction();
-    bool rollback() const;
-    void rollback(bool onoff);
-
-private:
-    class connection_impl;
-    friend class nanodbc::transaction::transaction_impl;
-
-private:
-    std::shared_ptr<connection_impl> impl_;
-};
-
-// 8888888b.                            888 888
-// 888   Y88b                           888 888
-// 888    888                           888 888
-// 888   d88P .d88b.  .d8888b  888  888 888 888888
-// 8888888P" d8P  Y8b 88K      888  888 888 888
-// 888 T88b  88888888 "Y8888b. 888  888 888 888
-// 888  T88b Y8b.          X88 Y88b 888 888 Y88b.
-// 888   T88b "Y8888   88888P'  "Y88888 888  "Y888
-// MARK: Result -
-
-class catalog;
-
-//! \brief A resource for managing result sets from statement execution.
-//!
-//! \see statement::execute(), statement::execute_direct()
-//! \note result objects may be copied, however all copies will refer to the same underlying ODBC result set.
-class result
-{
-public:
-    //! Empty result set.
-    result();
-
-    //! Free result set.
-    ~result() NANODBC_NOEXCEPT;
-
-    //! Copy constructor.
-    result(const result& rhs);
-
-    #ifndef NANODBC_NO_MOVE_CTOR
-        //! Move constructor.
-        result(result&& rhs) NANODBC_NOEXCEPT;
-    #endif
-
-    //! Assignment.
-    result& operator=(result rhs);
-
-    //! Member swap.
-    void swap(result& rhs) NANODBC_NOEXCEPT;
-
-    //! \brief Returns the native ODBC statement handle.
-    void* native_statement_handle() const;
-
-    //! \brief The rowset size for this result set.
-    long rowset_size() const NANODBC_NOEXCEPT;
-
-    //! \brief Returns the number of rows affected by the request or -1 if the number of affected rows is not available.
-    //! \throws database_error
-    long affected_rows() const;
-
-    //! \brief Returns the number of rows in the current rowset or 0 if the number of rows is not available.
-    long rows() const NANODBC_NOEXCEPT;
-
-    //! \brief Returns the number of columns in a result set.
-    //! \throws database_error
-    short columns() const;
-
-    //! \brief Fetches the first row in the current result set.
-    //! \return true if there are more results or false otherwise.
-    //! \throws database_error
-    bool first();
-
-    //! \brief Fetches the last row in the current result set.
-    //! \return true if there are more results or false otherwise.
-    //! \throws database_error
-    bool last();
-
-    //! \brief Fetches the next row in the current result set.
-    //! \return true if there are more results or false otherwise.
-    //! \throws database_error
-    bool next();
-
-    //! \brief Fetches the prior row in the current result set.
-    //! \return true if there are more results or false otherwise.
-    //! \throws database_error
-    bool prior();
-
-    //! \brief Moves to and fetches the specified row in the current result set.
-    //! \return true if there are results or false otherwise.
-    //! \throws database_error
-    bool move(long row);
-
-    //! \brief Skips a number of rows and then fetches the resulting row in the current result set.
-    //! \return true if there are results or false otherwise.
-    //! \throws database_error
-    bool skip(long rows);
-
-    //! \brief Returns the row position in the current result set.
-    unsigned long position() const;
-
-    //! \brief Returns true if there are no more results in the current result set.
-    bool end() const NANODBC_NOEXCEPT;
-
-    //! \brief Gets data from the given column of the current rowset.
-    //!
-    //! Columns are numbered from left to right and 0-indexed.
-    //! \param column position.
-    //! \param result The column's value will be written to this parameter.
-    //! \throws database_error, index_range_error, type_incompatible_error, null_access_error
-    template<class T>
-    void get_ref(short column, T& result) const;
-
-    //! \brief Gets data from the given column of the current rowset.
-    //! If the data is null, fallback is returned instead.
-    //!
-    //! Columns are numbered from left to right and 0-indexed.
-    //! \param column position.
-    //! \param fallback if value is null, return fallback instead.
-    //! \param result The column's value will be written to this parameter.
-    //! \throws database_error, index_range_error, type_incompatible_error
-    template<class T>
-    void get_ref(short column, const T& fallback, T& result) const;
-
-    //! \brief Gets data from the given column by name of the current rowset.
-    //!
-    //! \param column_name column's name.
-    //! \param result The column's value will be written to this parameter.
-    //! \throws database_error, index_range_error, type_incompatible_error, null_access_error
-    template<class T>
-    void get_ref(const string_type& column_name, T& result) const;
-
-    //! \brief Gets data from the given column by name of the current rowset.
-    //! If the data is null, fallback is returned instead.
-    //!
-    //! \param column_name column's name.
-    //! \param fallback if value is null, return fallback instead.
-    //! \param result The column's value will be written to this parameter.
-    //! \throws database_error, index_range_error, type_incompatible_error
-    template<class T>
-    void get_ref(const string_type& column_name, const T& fallback, T& result) const;
-
-    //! \brief Gets data from the given column of the current rowset.
-    //!
-    //! Columns are numbered from left to right and 0-indexed.
-    //! \param column position.
-    //! \throws database_error, index_range_error, type_incompatible_error, null_access_error
-    template<class T>
-    T get(short column) const;
-
-    //! \brief Gets data from the given column of the current rowset.
-    //! If the data is null, fallback is returned instead.
-    //!
-    //! Columns are numbered from left to right and 0-indexed.
-    //! \param column position.
-    //! \param fallback if value is null, return fallback instead.
-    //! \throws database_error, index_range_error, type_incompatible_error
-    template<class T>
-    T get(short column, const T& fallback) const;
-
-    //! \brief Gets data from the given column by name of the current rowset.
-    //!
-    //! \param column_name column's name.
-    //! \throws database_error, index_range_error, type_incompatible_error, null_access_error
-    template<class T>
-    T get(const string_type& column_name) const;
-
-    //! \brief Gets data from the given column by name of the current rowset.
-    //! If the data is null, fallback is returned instead.
-    //!
-    //! \param column_name column's name.
-    //! \param fallback if value is null, return fallback instead.
-    //! \throws database_error, index_range_error, type_incompatible_error
-    template<class T>
-    T get(const string_type& column_name, const T& fallback) const;
-
-    //! \brief Returns true if and only if the given column of the current rowset is null.
-    //!
-    //! There is a bug/limitation in ODBC drivers for SQL Server (and possibly others)
-    //! which causes SQLBindCol() to never write SQL_NOT_NULL to the length/indicator
-    //! buffer unless you also bind the data column. Nanodbc's is_null() will return
-    //! correct values for (n)varchar(max) columns when you ensure that SQLGetData()
-    //! has been called for that column (i.e. after get() or get_ref() is called).
-    //!
-    //! Columns are numbered from left to right and 0-indexed.
-    //! \see get(), get_ref()
-    //! \param column position.
-    //! \throws database_error, index_range_error
-    bool is_null(short column) const;
-
-    //! \brief Returns true if and only if the given column by name of the current rowset is null.
-    //!
-    //! See is_null(short column) for details on a bug/limitation of some ODBC drivers.
-    //! \see is_null()
-    //! \param column_name column's name.
-    //! \throws database_error, index_range_error
-    bool is_null(const string_type& column_name) const;
-
-    //! \brief Returns the name of the specified column.
-    //!
-    //! Columns are numbered from left to right and 0-indexed.
-    //! \param column position.
-    //! \throws index_range_error
-    string_type column_name(short column) const;
-
-    //! \brief Returns the size of the specified column.
-    //!
-    //! Columns are numbered from left to right and 0-indexed.
-    //! \param column position.
-    //! \throws index_range_error
-    long column_size(short column) const;
-
-    //! \brief Returns the column number of the specified column name.
-    //!
-    //! Columns are numbered from left to right and 0-indexed.
-    //! \param column_name column's name.
-    //! \throws index_range_error
-    short column(const string_type& column_name) const;
-
-    //! Returns a identifying integer value representing the SQL type of this column.
-    int column_datatype(short column) const;
-
-    //! Returns a identifying integer value representing the SQL type of this column by name.
-    int column_datatype(const string_type& column_name) const;
-
-    //! Returns a identifying integer value representing the C type of this column.
-    int column_c_datatype(short column) const;
-
-    //! Returns a identifying integer value representing the C type of this column by name.
-    int column_c_datatype(const string_type& column_name) const;
-
-    //! Returns the next result, for example when stored procedure returns multiple result sets.
-    bool next_result();
-
-    //! If and only if result object is valid, returns true.
-    explicit operator bool() const;
-
-private:
-    result(statement statement, long rowset_size);
-
-private:
-    class result_impl;
-    friend class nanodbc::statement::statement_impl;
-    friend class nanodbc::catalog;
-
-private:
-    std::shared_ptr<result_impl> impl_;
-};
-
-
-//
-//  .d8888b.           888             888
-// d88P  Y88b          888             888
-// 888    888          888             888
-// 888         8888b.  888888  8888b.  888  .d88b.   .d88b.
-// 888            "88b 888        "88b 888 d88""88b d88P"88b
-// 888    888 .d888888 888    .d888888 888 888  888 888  888
-// Y88b  d88P 888  888 Y88b.  888  888 888 Y88..88P Y88b 888
-//  "Y8888P"  "Y888888  "Y888 "Y888888 888  "Y88P"   "Y88888
-//                                                      888
-//                                                 Y8b d88P
-//                                                  "Y88P"
-// MARK: Catalog -
-
-class catalog
-{
-public:
-
-    class tables
-    {
-    public:
-        bool next();
-        string_type table_catalog() const;
-        string_type table_schema() const;
-        string_type table_name() const;
-        string_type table_type() const;
-        string_type table_remarks() const;
-
-    private:
-        friend class nanodbc::catalog;
-        tables(result& find_result);
-        result result_;
-    };
-
-    class columns
-    {
-    public:
-
-        //! \brief
-        bool next();
-
-        //! \brief
-        string_type table_catalog() const;
-
-        //! \brief
-        string_type table_schema() const;
-
-        //! \brief
-        string_type table_name() const;
-
-        //! \brief
-        string_type column_name() const;
-
-        //! \brief
-        short data_type() const;
-
-        //! \brief
-        string_type type_name() const;
-
-        //! \brief
-        long column_size() const;
-
-        //! \brief
-        long buffer_length() const;
-
-        //! \brief
-        short decimal_digits() const;
-
-        //! \brief
-        short numeric_precision_radix() const;
-
-        //! \brief
-        short nullable() const;
-
-        //! \brief
-        string_type remarks() const;
-
-        //! \brief
-        string_type column_default() const;
-
-        //! \brief
-        short sql_data_type() const;
-
-        //! \brief
-        short sql_datetime_subtype() const;
-
-        //! \brief
-        long char_octed_length() const;
-
-        //! \brief Ordinal position of the column in the table.
-        //! The first column in the table is number 1.
-        //! Returns ORDINAL_POSITION column value in result set returned by SQLColumns.
-        long ordinal_position() const;
-
-        //! \brief
-        //! TODO: Translate "YES","NO", <empty> strings to IsNullable enum?
-        string_type is_nullable() const;
-
-    private:
-        friend class nanodbc::catalog;
-        columns(result& find_result);
-        result result_;
-    };
-
-    class primary_keys
-    {
-    public:
-        bool next();
-        string_type table_catalog() const;
-        string_type table_schema() const;
-        string_type table_name() const;
-        string_type column_name() const;
-
-        //! \brief Column sequence number in the key (starting with 1).
-        //! Returns valye of KEY_SEQ column in result set returned by SQLPrimaryKeys.
-        short column_number() const;
-
-        //! \brief Primary key name.
-        //! NULL if not applicable to the data source.
-        //! Returns valye of PK_NAME column in result set returned by SQLPrimaryKeys.
-        string_type primary_key_name() const;
-
-    private:
-        friend class nanodbc::catalog;
-        primary_keys(result& find_result);
-        result result_;
-    };
-
-    //! \brief Creates catalog operating on database accessible through the specified connection.
-    catalog(connection& conn);
-
-    //! \brief Creates result set with tables information.
-    //!
-    //! Tables information is obtained by executing SQLTable function within
-    //! scope of the connected database accessible with the specified connection.
-    //! Since this function is implemented in terms of the SQLTables, it returns
-    //! result set ordered by TABLE_TYPE, TABLE_CAT, TABLE_SCHEM, and TABLE_NAME.
-    catalog::tables find_tables(
-        const string_type& table = string_type()
-      , const string_type& type = string_type()
-      , const string_type& schema = string_type()
-      , const string_type& catalog = string_type());
-
-    //! \brief Creates result set with columns information in specified tables.
-    //!
-    //! Columns information is obtained by executing SQLColumns function within
-    //! scope of the connected database accessible with the specified connection.
-    //! Since this function is implemented in terms of the SQLColumns, it returns
-    //! result set ordered by TABLE_CAT, TABLE_SCHEM, TABLE_NAME, and ORDINAL_POSITION.
-    catalog::columns find_columns(
-        const string_type& column = string_type()
-      , const string_type& table = string_type()
-      , const string_type& schema = string_type()
-      , const string_type& catalog = string_type());
-
-    //! \brief Creates result set with primary key information.
-    //!
-    //! Returns result set with column names that make up the primary key for a table.
-    //! The primary key information is obtained by executing SQLPrimaryKey function within
-    //! scope of the connected database accessible with the specified connection.
-    catalog::primary_keys find_primary_keys(
-        const string_type& table
-      , const string_type& schema = string_type()
-      , const string_type& catalog = string_type());
-
-private:
-    connection conn_;
-};
-
-//! @}
-
-// 8888888888                            8888888888                         888    d8b
-// 888                                   888                                888    Y8P
-// 888                                   888                                888
-// 8888888 888d888 .d88b.   .d88b.       8888888 888  888 88888b.   .d8888b 888888 888  .d88b.  88888b.  .d8888b
-// 888     888P"  d8P  Y8b d8P  Y8b      888     888  888 888 "88b d88P"    888    888 d88""88b 888 "88b 88K
-// 888     888    88888888 88888888      888     888  888 888  888 888      888    888 888  888 888  888 "Y8888b.
-// 888     888    Y8b.     Y8b.          888     Y88b 888 888  888 Y88b.    Y88b.  888 Y88..88P 888  888      X88
-// 888     888     "Y8888   "Y8888       888      "Y88888 888  888  "Y8888P  "Y888 888  "Y88P"  888  888  88888P'
-// MARK: Free Functions -
-
-//! \addtogroup mainf Free Functions
-//! \brief Convenience functions.
-//!
-//! @{
-
-//! \brief Immediately opens, prepares, and executes the given query directly on the given connection.
-//! \param conn The connection where the statement will be executed.
-//! \param query The SQL query that will be executed.
-//! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-//! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-//! \return A result set object.
-//! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-//! \see open(), prepare(), execute(), result, transaction
-result execute(
-    connection& conn
-    , const string_type& query
-    , long batch_operations = 1
-    , long timeout = 0);
-
-//! \brief Immediately opens, prepares, and executes the given query directly on the given connection without creating result object.
-//! \param conn The connection where the statement will be executed.
-//! \param query The SQL query that will be executed.
-//! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-//! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-//! \return A result set object.
-//! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-//! \see open(), prepare(), execute(), result, transaction
-void just_execute(
-    connection& conn
-    , const string_type& query
-    , long batch_operations = 1
-    , long timeout = 0);
-
-//! \brief Execute the previously prepared query now.
-//! \param stmt The prepared statement that will be executed.
-//! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-//! \throws database_error
-//! \return A result set object.
-//! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-//! \see open(), prepare(), execute(), result
-result execute(statement& stmt, long batch_operations = 1);
-
-//! \brief Execute the previously prepared query now and without creating result object.
-//! \param stmt The prepared statement that will be executed.
-//! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-//! \throws database_error
-//! \return A result set object.
-//! \attention You will want to use transactions if you are doing batch operations because it will prevent auto commits from occurring after each individual operation is executed.
-//! \see open(), prepare(), execute(), result
-void just_execute(statement& stmt, long batch_operations = 1);
-
-//! \brief Execute the previously prepared query now.
-//! Executes within the context of a transaction object and commits the transaction directly after execution.
-//! \param stmt The prepared statement that will be executed in batch.
-//! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-//! \throws database_error
-//! \return A result set object.
-//! \see open(), prepare(), execute(), result, transaction
-result transact(statement& stmt, long batch_operations);
-
-//! \brief Execute the previously prepared query now and without creating result object.
-//! Executes within the context of a transaction object and commits the transaction directly after execution.
-//! \param stmt The prepared statement that will be executed in batch.
-//! \param batch_operations Numbers of rows to fetch per rowset, or the number of batch parameters to process.
-//! \throws database_error
-//! \return A result set object.
-//! \see open(), prepare(), execute(), result, transaction
-void just_transact(statement& stmt, long batch_operations);
-
-//! \brief Prepares the given statement to execute on it associated connection.
-//! If the statement is not open throws programming_error.
-//! \param stmt The prepared statement that will be executed in batch.
-//! \param query The SQL query that will be executed.
-//! \param timeout The number in seconds before query timeout. Default is 0 indicating no timeout.
-//! \see open()
-//! \throws database_error, programming_error
-void prepare(statement& stmt, const string_type& query, long timeout = 0);
-
-//! @}
-
-} // namespace nanodbc
-
-#endif // NANODBC_H