Browse Source

Merge commit '3d5994381df631739187c89bd0c2752900ae6c2a' as 'Source/ThirdParty/nanodbc'

Yao Wei Tjong 姚伟忠 10 years ago
parent
commit
41b87b3690

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

@@ -0,0 +1,155 @@
+# v2.11.3
+
+- Fixes segmentation fault issue with unixODBC on Linux systems.
+- Adds support for `while(!results.end())` style iteration.
+
+# v2.11.2
+
+- Adds this CHANGELOG.md file. Future releases should update it accordingly!
+- Adds CHANGELOG.md helper script.
+
+# v2.11.1
+
+## New Features
+
+- Major thanks again to Mateusz Łoskot for all the new features!
+- Adds convenient access to catalog objects (tables, columns, primary keys).
+- Adds `database_name` and `catalog_name `methods to connection class.
+- Adds CMake option `NANODBC_ENABLE_LIBCXX` to enable/disable libc++ builds.
+- Adds CMake option `NANODBC_EXAMPLES` to enable/disable the example target.
+- Adds a `latest` release branch to track most recent release.
+
+## Testing
+
+- Massive updates to Travis CI continuous integration.
+- Adds general `odbc_test` to target variety of ODBC drivers.
+- Adds specific MySQL tests.
+- Updates test target organization.
+    - The way the targets were designed is such that:
+        - test: runs all tests, but will not build them
+        - tests: builds all tests, but does not run them
+        - check: builds all tests and then runs all tests
+    - For individual tests then, it makes sense to use:
+        - ${name}_test: runs ${name}_test, but will not build it
+        - ${name}_tests: builds ${name}_test, but does not run it
+        - ${name}_check: builds ${name}_test and then runs it
+
+## Bug Fixes
+
+- Fix test check of `result::affected_rows` for `SELECT` statement.
+- Fix `result::position` to make it consistent with `SQL_ATTR_ROW_NUMBER`.
+- Fix string object construction syntax.
+- Adds missing `#include <cstring>`.
+
+## Other Changes
+
+- More robust and friendly publish and release scripts.
+- Updates to README and documentation.
+- Adds `-DUNICODE` and `-D_UNICODE` for Visual Studio projects.
+- Adds examples based on the documentation.
+- Adds `rowset_iteration` example.
+
+# v2.10.0
+
+## New Features
+
+- Major thanks to Mateusz Łoskot for all the effort!
+- Adds Dockerfile to support testing and development.
+- Adds build.bat convenience script for Visual Studio users.
+- Adds CMake options `NANODBC_INSTALL` and `NANODBC_TEST` to control generation of those targets.
+
+## Bug Fixes
+
+- Fixes cmake build on OS X El Capitan.
+- Refine assert in `result_impl::position` with `SQL_ROW_NUMBER_UNKNOWN`.
+- MSBuild Platform property for 32-bit is Win32.
+- Reset null indicator before move for all columns, not just bound columns.
+- Fixes doxygen generation of macro docs.
+
+# v2.9.1
+
+## New Features
+
+- Adds Vagrantfile to support testing and development.
+- Adds customizable `NANODBC_ASSERT` macro.
+- Adds CMake option `NANODBC_STATIC` (default OFF).
+- Clean up Visual C++ 64-bit warnings.
+
+## Bug Fixes
+
+- CMake: Fixes ODBC linking on Unix.
+- Adds documentation on is_null() limitation.
+- Write null indicator to `cbdata_` if indicated by `SQLGetData`.
+
+## Testing
+
+- Initial configuration of Travis CI build matrix.
+
+## Other Changes
+
+- Added a Contributing section to readme.
+- Updates to SQLite tests.
+- Disable MSVC warning C4244 in tests.
+
+# v2.8.1
+
+- Update CMakeLists.txt to enable builds with Visual Studio. Thanks Mateusz Łoskot!
+- Add async connection support, plus extended database_error info. Thanks Yao Wei Tjong!
+- Add linking against ODBC libraries on Windows.
+- Change `param_type_from_direction` to throw `programming_error`.
+- Define `NANODBC_SNPRINTF` in terms of `_snprintf_s` for MSVC.
+- Setting CMake `-DNANODBC_ODBC_VERSION` option now works.
+
+# v2.7.0
+
+- Adds move constructors.
+- Fixes Xcode MARK comments.
+- Adds section comment banners to header file.
+- Removes `throw()` from header files, uses `noexcept` instead.
+- Adds basic and sqlite `std::move` test case.
+
+# v2.6.0
+
+- Resolves issue with decimal digits/scale and rounding. Thanks dedomilo!
+- Resolve issue with `DECIMAL` to string conversion. Thanks dedomilo!
+
+# v2.5.1
+
+- Disable default unicode on windows.
+- Override ODBC version with `NANODBC_ODBC_VERSION`.
+
+# v2.4.0
+
+- Add `statement::async_execute_direct` and `statement::async_complete`. Thanks Jon Valvatne!
+- Add NOEXCEPT define to allow compilation under Visual Studio 2013.
+
+# v2.3.0
+
+- Provides optional Boost workaround for missing codecvt support in libstdc++.
+
+# v2.2.3
+
+- Adds minimap banners for code navigation.
+- Adds `column_c_datatype()`.
+- Converts line endings to unix.
+- Adds `just_execute` class of functions that don't create result objects.
+
+# v2.1.0
+
+- Adds publish script.
+- Fixes broken links in readme.
+- Use C++11's `=delete` where appropriate.
+
+# v2.0.1
+
+- Fixes many documentation issues.
+- Adds more todo info about updating docs.
+- Adds notes about different versions.
+- 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.
+
+# v1.0.0
+
+Version 1.0.0 and all commits prior are now completely unsupported.

+ 41 - 0
Source/ThirdParty/nanodbc/CMakeLists.txt

@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2008-2015 the Urho3D project.
+#
+# 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.
+#
+
+# Define target name
+set (TARGET_NAME nanodbc)
+
+# Define source files
+define_source_files (GLOB_CPP_PATTERNS src/*.cpp GLOB_H_PATTERNS src/*.h)
+
+# Define dependency libs
+if (ODBC_INCLUDE_DIRS)
+    set (INCLUDE_DIRS ${ODBC_INCLUDE_DIRS})
+endif ()
+if (ODBC_DEFINES)
+    add_definitions (${ODBC_DEFINES})
+endif ()
+
+# Setup target
+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

+ 19 - 0
Source/ThirdParty/nanodbc/LICENSE

@@ -0,0 +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.

+ 119 - 0
Source/ThirdParty/nanodbc/README.md

@@ -0,0 +1,119 @@
+![nanodbc-banner](https://cloud.githubusercontent.com/assets/1903876/11858632/cc0e21e6-a428-11e5-9a84-39fa27984914.png)
+
+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.
+
+| Version | Description |
+|:--- |:--- |
+| `release` | [![release](https://travis-ci.org/lexicalunit/nanodbc.svg?branch=release)](https://travis-ci.org/lexicalunit/nanodbc) Most recent release that's been deemed "stable". Always prefer to use this version if you can! |
+| `latest` | [![latest](https://travis-ci.org/lexicalunit/nanodbc.svg?branch=latest)](https://travis-ci.org/lexicalunit/nanodbc) Latest release; still needs testing to be deemed "stable". **[See all available releases.](https://github.com/lexicalunit/nanodbc/releases)** |
+| `master`  | [![master](https://travis-ci.org/lexicalunit/nanodbc.svg?branch=master)](https://travis-ci.org/lexicalunit/nanodbc) Contains the latest development code, not yet ready for a release. |
+| `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.
+
+Building unit tests requires [Boost.Test](http://www.boost.org/doc/libs/release/libs/test/). To build the tests you will also need to have either unixODBC or iODBC installed and discoverable by CMake. This is easy on OS X where you can use [Homebrew](http://brew.sh/) to install unixODBC with `brew install unixodbc`, or use the system provided iODBC if you have OS X 10.9 or earlier. Also note that you can install Boost via Homebrew as well, which is super convenient!
+
+The unit tests attempt to connect to a [SQLite](https://www.sqlite.org/) database, so you will have to have that and a SQLite ODBC driver installed. At the time of this writing, there happens to be a nice [SQLite ODBC driver](http://www.ch-werner.de/sqliteodbc/) available from Christian Werner's website, also available via Homebrew as `sqliteobdc`! The tests expect to find a data source named `sqlite` on *nix systems and `SQLite3 ODBC Driver` on Windows systems. For example, your `odbcinst.ini` file on OS X must have a section like the following.
+
+```
+[sqlite]
+Description             = SQLite3 ODBC Driver
+Driver                  = /usr/lib/libsqlite3odbc-0.93.dylib
+Setup                   = /usr/lib/libsqlite3odbc-0.93.dylib
+Threading               = 2
+```
+
+## Example Build Process
+
+It's most convenient to create a build directory for an out of source build, but this isn't required. After you've used cmake to generate your Makefiles, `make nanodbc` will build your shared object. `make check` will build and run the unit tests. If the unit tests fail, please don't hesitate to report it by creating an issue [with your detailed test log](http://stackoverflow.com/questions/5709914/using-cmake-how-do-i-get-verbose-output-from-ctest)! You can also install nanodbc to your system using `make install`.
+
+```shell
+cd path/to/nanodbc/repository
+mkdir build
+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 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.
+
+| CMake&nbsp;Option                     | Possible&nbsp;Values  | Default       | Details |
+| ------------------------------------- | --------------------- | ------------- | ------- |
+| `‑D NANODBC_USE_UNICODE=...`          | `OFF` or `ON`         | `OFF`         | Enables full unicode support. `nanodbc::string` becomes `std::wstring`. |
+| `‑D NANODBC_HANDLE_NODATA_BUG=...`    | `OFF` or `ON`         | `OFF`         | Provided to resolve issue [#33](https://github.com/lexicalunit/nanodbc/issues/33), details [in this commit](https://github.com/lexicalunit/nanodbc/commit/918d73cdf12d5903098381344eecde8e7d5d896e). |
+| `‑D NANODBC_USE_BOOST_CONVERT=...`    | `OFF` or `ON`         | `OFF`         | Provided as workaround to issue [#44](https://github.com/lexicalunit/nanodbc/issues/44). |
+| `‑D NANODBC_STATIC=...`               | `OFF` or `ON`         | `OFF`         | Enables building a static library, otherwise the build process produces a shared library. |
+| `‑D NANODBC_INSTALL=...`              | `OFF` or `ON`         | `ON`          | Enables install target. |
+| `‑D NANODBC_EXAMPLES=...`             | `OFF` or `ON`         | `ON`          | Enables building of examples. |
+| `‑D NANODBC_TEST=...`                 | `OFF` or `ON`         | `ON`          | Enables tests target (alias `check`). |
+| `-D NANODBC_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`. |
+
+---
+
+# Contributing
+
+## Publish and Release Process
+
+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.
+
+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 `scripts/release.sh` script. All this script does is push out the `master` branch to the `release` branch, indicating that a new "stable" version of nanodbc exists. To do so manually, execute `git push -f origin master:release`. **Only do this for releases that have been deemed "stable" based on suitable criteria.**
+
+## 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:
+
+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 of nanodbc for more details.
+
+## Testing Environments
+
+To get up and running with nanodbc as quickly as possible consider using the provided Dockerfile or Vagrantfile. For example, to spin up a [docker](https://www.docker.com/) container suitable for testing and development of nanodbc:
+
+```shell
+$ cd /path/to/nanodbc
+$ docker build -t nanodbc .
+$ docker run -v "$(pwd)":"/opt/$(basename $(pwd))" -it nanodbc /bin/bash
+root@hash:/# mkdir -p /opt/nanodbc/docker_build && cd /opt/nanodbc/docker_build
+root@hash:/opt/nanodbc/docker_build# cmake -DNANODBC_USE_BOOST_CONVERT=YES ..
+root@hash:/opt/nanodbc/docker_build# make nanodbc
+```
+
+## Future work
+
+- 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.
+- Fill out the Contributing section of this readme with more helpful information. Maybe a getting started section?

+ 1 - 0
Source/ThirdParty/nanodbc/VERSION

@@ -0,0 +1 @@
+2.11.3

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

@@ -0,0 +1,3907 @@
+//! \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>
+#include <sstream>
+
+#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 -
+
+#if defined(_MSC_VER)
+    #ifdef NANODBC_USE_UNICODE
+        #define NANODBC_TEXT(s) L ## s
+        #define NANODBC_SNPRINTF std::swprintf
+        #define NANODBC_STRFTIME std::wcsftime
+        #define NANODBC_STRLEN std::wcslen
+        #define NANADBC_STRNCMP std::wcsncmp
+        #define NANODBC_UNICODE(f) f ## W
+        #define NANODBC_SQLCHAR SQLWCHAR
+    #else
+        #define NANODBC_TEXT(s) s
+        #define NANODBC_SNPRINTF(buffer, count, format, ...) _snprintf_s(buffer, count, _TRUNCATE, format, __VA_ARGS__)
+        #define NANODBC_STRFTIME std::strftime
+        #define NANODBC_STRLEN std::strlen
+        #define NANADBC_STRNCMP std::strncmp
+        #define NANODBC_UNICODE(f) f
+        #define NANODBC_SQLCHAR SQLCHAR
+
+        // Disable unicode in sqlucode.h on Windows when NANODBC_USE_UNICODE
+        // is not defined. This is required because unicode is enabled by
+        // default on many Windows systems.
+        #define SQL_NOUNICODEMAP
+    #endif
+#else
+    #ifdef NANODBC_USE_UNICODE
+        #define NANODBC_TEXT(s) L ## s
+        #define NANODBC_SNPRINTF std::swprintf
+        #define NANODBC_STRFTIME std::wcsftime
+        #define NANODBC_STRLEN std::wcslen
+        #define NANADBC_STRNCMP std::wcsncmp
+        #define NANODBC_UNICODE(f) f ## W
+        #define NANODBC_SQLCHAR SQLWCHAR
+    #else
+        #define NANODBC_TEXT(s) s
+        #define NANODBC_SNPRINTF std::snprintf
+        #define NANODBC_STRFTIME std::strftime
+        #define NANODBC_STRLEN std::strlen
+        #define NANADBC_STRNCMP std::strncmp
+        #define NANODBC_UNICODE(f) f
+        #define NANODBC_SQLCHAR SQLCHAR
+    #endif
+#endif
+
+//  .d88888b.  8888888b.  888888b.    .d8888b.       888b     d888
+// 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 std::wstring& in, std::string& out)
+    {
+        #ifdef NANODBC_USE_BOOST_CONVERT
+            using boost::locale::conv::utf_to_utf;
+            out = utf_to_utf<char>(in.c_str(), in.c_str() + in.size());
+        #else
+            out = std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(in);
+        #endif
+    }
+
+    #ifdef NANODBC_USE_UNICODE
+        inline void convert(const std::string& in, std::wstring& out)
+        {
+            #ifdef NANODBC_USE_BOOST_CONVERT
+                using boost::locale::conv::utf_to_utf;
+                out = utf_to_utf<wchar_t>(in.c_str(), in.c_str() + in.size());
+            #else
+                out = std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(in);
+            #endif
+        }
+
+        inline void convert(const std::wstring& in, std::wstring & 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_UNICODE(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_UNICODE(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_UNICODE(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_UNICODE(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 driver_name() const
+    {
+        NANODBC_SQLCHAR name[1024];
+        SQLSMALLINT length;
+        RETCODE rc;
+        NANODBC_CALL_RC(
+            NANODBC_UNICODE(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_UNICODE(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_UNICODE(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_UNICODE(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_UNICODE(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_UNICODE(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;
+        SQLULEN parameter_size;
+        NANODBC_CALL_RC(
+            SQLDescribeParam
+            , rc
+            , stmt_
+            , param + 1
+            , &data_type
+            , &parameter_size
+            , 0
+            , 0);
+        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;
+        NANODBC_CALL_RC(
+            SQLDescribeParam
+            , rc
+            , stmt_
+            , param + 1
+            , &data_type
+            , &parameter_size
+            , &scale
+            , 0);
+        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)
+    {
+        const string_type rhs(null_sentry);
+        for(std::size_t i = 0; i < elements; ++i)
+        {
+            const string_type lhs(values + i * length, values + (i + 1) * length);
+            if(NANADBC_STRNCMP(lhs.c_str(), rhs.c_str(), length))
+                bind_len_or_null_[param][i] = parameter_size;
+        }
+    }
+    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_UNICODE(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:
+                    col.ctype_ = SQL_C_BINARY;
+                    col.clen_ = col.sqlsize_ + sizeof(NANODBC_SQLCHAR);
+                    break;
+                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 = *((date*)(col.pdata_ + rowset_position_ * col.clen_));
+            return;
+        case SQL_C_TIMESTAMP:
+        {
+            timestamp stamp = *( (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 = *((date*)(col.pdata_ + rowset_position_ * col.clen_));
+            timestamp stamp = { d.year, d.month, d.day, 0, 0, 0, 0 };
+            result = stamp;
+            return;
+        }
+        case SQL_C_TIMESTAMP:
+            result = *((timestamp*)(col.pdata_ + rowset_position_ * col.clen_));
+            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:
+        {
+            if(col.blob_)
+            {
+                // Input is always std::string, while output may be std::string or std::wstring
+                std::stringstream ss;
+                char buff[1024] = {0};
+                std::size_t buff_size = sizeof(buff);
+                SQLLEN ValueLenOrInd;
+                SQLRETURN rc;
+                void* handle = native_statement_handle();
+                do
+                {
+                    NANODBC_CALL_RC(
+                        SQLGetData
+                        , rc
+                        , handle            // StatementHandle
+                        , column + 1        // Col_or_Param_Num
+                        , SQL_C_CHAR        // TargetType
+                        , buff              // TargetValuePtr
+                        , buff_size         // BufferLength
+                        , &ValueLenOrInd);  // StrLen_or_IndPtr
+                    if(ValueLenOrInd > 0)
+                        ss << buff;
+                    else if(ValueLenOrInd == SQL_NULL_DATA)
+                        *col.cbdata_ = (SQLINTEGER) SQL_NULL_DATA;
+                } while(rc > 0);
+                convert(ss.str(), 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 std::wstring, output might be std::string or std::wstring.
+                // Use a string builder to build the output string.
+                std::wstringstream ss;
+                wchar_t buffer[512] = {0};
+                std::size_t buffer_size = sizeof(buffer);
+                SQLLEN ValueLenOrInd;
+                SQLRETURN rc;
+                void* handle = native_statement_handle();
+                do
+                {
+                    NANODBC_CALL_RC(
+                        SQLGetData
+                        , rc
+                        , handle            // StatementHandle
+                        , column + 1        // Col_or_Param_Num
+                        , SQL_C_WCHAR       // TargetType
+                        , buffer            // TargetValuePtr
+                        , buffer_size       // BufferLength
+                        , &ValueLenOrInd);  // StrLen_or_IndPtr
+                    if(ValueLenOrInd > 0)
+                        ss << buffer;
+                    else if(ValueLenOrInd == SQL_NULL_DATA)
+                        *col.cbdata_ = (SQLINTEGER) SQL_NULL_DATA;
+                } while(rc > 0);
+                convert(ss.str(), result);
+            }
+            else
+            {
+                // Type is unicode in the database, convert if necessary
+                const SQLWCHAR* s = reinterpret_cast<SQLWCHAR*>(col.pdata_ + rowset_position_ * col.clen_);
+                const string_type::size_type str_size = *col.cbdata_ / sizeof(SQLWCHAR);
+                std::wstring temp(s, s + str_size);
+                convert(temp, result);
+            }
+            return;
+        }
+
+        case SQL_C_GUID:
+        case SQL_C_BINARY:
+        {
+            if(col.blob_)
+                throw std::runtime_error("blob not implemented yet");
+            const char* s = col.pdata_ + rowset_position_ * col.clen_;
+            result.assign(s, s + column_size);
+            return;
+        }
+
+        case SQL_C_LONG:
+        {
+           result.resize(column_size);
+           if(NANODBC_SNPRINTF(
+                    const_cast<string_type::value_type*>(result.data())
+                    , column_size
+                    , NANODBC_TEXT("%d")
+                    , *(int32_t*)(col.pdata_ + rowset_position_ * col.clen_)) == -1)
+               throw type_incompatible_error();
+           result.resize(NANODBC_STRLEN(result.c_str()));
+           return;
+        }
+
+        case SQL_C_SBIGINT:
+        {
+            using namespace std; // in case intmax_t is in namespace std
+            result.resize(column_size);
+            if(NANODBC_SNPRINTF(
+                    const_cast<string_type::value_type*>(result.data())
+                    , column_size
+                    , NANODBC_TEXT("%jd")
+                    , (intmax_t) *(int64_t*)(col.pdata_ + rowset_position_ * col.clen_)) == -1)
+                throw type_incompatible_error();
+            result.resize(NANODBC_STRLEN(result.c_str()));
+            return;
+        }
+
+        case SQL_C_FLOAT:
+        {
+            result.resize(column_size);
+            if(NANODBC_SNPRINTF(
+                    const_cast<string_type::value_type*>(result.data())
+                    , column_size
+                    , NANODBC_TEXT("%f")
+                    , *(float*)(col.pdata_ + rowset_position_ * col.clen_)) == -1)
+                throw type_incompatible_error();
+            result.resize(NANODBC_STRLEN(result.c_str()));
+            return;
+        }
+
+        case SQL_C_DOUBLE:
+        {
+            result.resize(column_size + 2);     // account for decimal mark and sign
+            if(NANODBC_SNPRINTF(
+                const_cast<string_type::value_type*>(result.data())
+                , column_size + 2
+                , NANODBC_TEXT("%.*lf")         // restrict the number of digits
+                , col.scale_                    // number of digits after the decimal point
+                , *(double*)(col.pdata_ + rowset_position_ * col.clen_)) == -1)
+            throw type_incompatible_error();
+            result.resize(NANODBC_STRLEN(result.c_str()));
+            return;
+        }
+
+        case SQL_C_DATE:
+        {
+            date d = *((date*)(col.pdata_ + rowset_position_ * col.clen_));
+            std::tm st = { 0 };
+            st.tm_year = d.year - 1900;
+            st.tm_mon = d.month - 1;
+            st.tm_mday = d.day;
+            char* old_lc_time = std::setlocale(LC_TIME, NULL);
+            std::setlocale(LC_TIME, "");
+            string_type::value_type date_str[512];
+            NANODBC_STRFTIME(
+                date_str
+                , sizeof(date_str) / sizeof(string_type::value_type)
+                , NANODBC_TEXT("%Y-%m-%d")
+                , &st);
+            std::setlocale(LC_TIME, old_lc_time);
+            result.assign(date_str);
+            return;
+        }
+
+        case SQL_C_TIMESTAMP:
+        {
+            timestamp stamp = *((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, "");
+            string_type::value_type date_str[512];
+            NANODBC_STRFTIME(
+                date_str
+                , sizeof(date_str) / sizeof(string_type::value_type)
+                , NANODBC_TEXT("%Y-%m-%d %H:%M:%S %z")
+                , &st);
+            std::setlocale(LC_TIME, old_lc_time);
+           result.assign(date_str);
+           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::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 is never NULL
+    return result_.get<string_type>(17);
+}
+
+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_UNICODE(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_UNICODE(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_UNICODE(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

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

@@ -0,0 +1,1377 @@
+//! \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 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
+        typedef std::wstring string_type;
+    #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
+    //! string_type will be std::wstring if NANODBC_USE_UNICODE is defined, otherwise std::string.
+    typedef unspecified-type string_type;
+    //! null_type will be int64_t for 64-bit compilations, otherwise long.
+    typedef unspecified-type null_type;
+#endif // DOXYGEN
+
+#if defined(_MSC_VER) && _MSC_VER <= 1800
+    // These versions of Visual C++ do not yet support noexcept or std::move.
+    #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 type_incompatible_error, null_access_error, and index_range_error can arise
+//! from improper use of the nanodbc library. The general database_error is for all other situations
+//! in which the ODBC driver or C API reports an error condition. The explanatory string for database_error
+//! will, if possible, contain a diagnostic message obtained from SQLGetDiagRec().
+//! @{
+
+//! \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 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