Forráskód Böngészése

Squashed 'Source/ThirdParty/nanodbc/' changes from 2a32409..8f7d426

8f7d426 Travis CI: bump copyright to 2016. [ccache clear]
f362ba6 Pass compiler defines from the ODBC config tool to our build system. Make FindODBC module to prefer unixODBC over libiodbc when both ODBC driver managers are available.
3068a57 Initial ODBC API support. Enhance db demo to work with ODBC database connectioon string.
6475862 Preparing 2.12.4 release.
bd3e5d6 Adds changelog for v2.12.4.
550c89f Merge pull request #120 from lexicalunit/describeparam_nullable
0000244 Resolves SQLDescribeParam() crash in some drivers.
8f6ce57 Preparing 2.12.3 release.
fbf822f Fix for issue with unicode blob types.
fa82c28 Preparing 2.12.2 release.
2653f85 Updating changelog for v2.12.2.
e9265e0 Merge pull request #118 from lexicalunit/binary_blob_fix
8dbfc45 Fixes implementation of binary blob.
ebffdb2 Preparing 2.12.1 release.
77c779f Update changelog.
706e891 Workaround for a Travis-CI + Catch build issue.
cdb7aee Slight fix to wording in readme.
82cd331 Preparing 2.12.0 release.
dc00f48 Merge pull request #115 from lexicalunit/proposed_v2.12.0
fa99027 Adds CHANGELOG for v2.12.0.
60f89f2 Adds missing ODBC_INCLUDE_DIR to examples build.
90f7d47 Merge pull request #113 from lexicalunit/iodbc_unicode_support
7a012d0 Enable iODBC + Unicode support with `u32string` types.
27fb0fb Merge pull request #112 from mloskot/ml/example-usage-cleanup
a76a27d Fix compiler warnings while building with VS2015.
c8ecba9 Merge pull request #110 from mloskot/ml/example-table-schema
ec7f16c Print SQL data type identifiers in textual form.
beb12bb Specify any_to_string utilities with inline
8aff3d1 Add missing optional schema_name parameter to usage info.
d99315f Add example program table_schema.cpp.
562975d Updates README.md
5f1887c Fixes jekyll issue on ubunut:precise in Dockerfile.
261116a Update README.md
9a164e4 Update README.md
aaa2d1a Merge pull request #109 from lexicalunit/adds_development_roadmap
c3b12cf Merge pull request #108 from lexicalunit/docker_vagrant_sync
c9112b0 Update some docs, add development roadmap.
fd347a2 Syncs Dockerfile and Vagrantfile; adds quick usage docs for vagrant.
21e7a2d Removes debug printf.
8abce95 Merge pull request #96 from lexicalunit/unicode_overhaul
1f08e8d Minor style changes and adds comment explaining workaround code.
8926169 Work around VS2015 bug in std::codecvt for char16_t.
02223b9 Enables unicode tests on travis-ci.
831e1a9 Prefers singular term boolean expression for unit tests.
e6059ff Converts usages of `wstring` and `wchar_t` to `u16string` and `char16_t`.
36f3712 Prefers `reinterpret_cast` over C-style casts.
2646818 Renames `NANODBC_UNICODE()` to `NANODBC_FUNC()`.
47ea52a Sorts headers and adds using directives to example; easier to read.
87e6c09 Removes sqlite fixture's override for `drop_table()`. Not needed.
f0be9a9 Removes now unused libboost-test from Vagrantfile.
ff12ac4 Switch Dockerfile over to ubuntu:precise (default).
4985142 Merge pull request #105 from mloskot/ml/test-mssql-and-pgsql
642c7cc Merge pull request #106 from mloskot/ml/fix-varlen-retrival
2f0cc83 Fix retrival of variable-length data in parts.
5f3d703 Prefer base_test_fixture::contains_string utility.
67b87f2 Improve odbc_test.cpp to cope with DBMS variations.
49e3ac2 Merge pull request #102 from mloskot/ml/test-decimal-and-numeric
62e6ba9 Merge pull request #98 from mloskot/ml/test-catalog-create-view
495d166 Merge pull request #104 from mloskot/ml/test-check-varchar-variants
9a7aa34 Merge pull request #103 from mloskot/ml/test-check-float-radix
057969f Merge pull request #100 from mloskot/ml/fix-simple-test-ordering
d983a43 Merge pull request #101 from mloskot/ml/test-fix-date-size
702342c Merge pull request #99 from mloskot/ml/fix-catalog-is-nullable
a184de7 Merge pull request #97 from mloskot/ml/test-refine-drop-table
dc44884 Check SQL_WVARCHAR along SQL_VARCHAR data type.
dfad9f3 SQL_FLOAT size depends on numeric precision radix.
e38772e Add SQL_NUMERIC check where SQL_DECIMAL type is expected.
794726a Fix check of total of characters required to display SQL_DATE.
ebf4d39 Fix SELECT result sorting with NULL values involved.
225b435 Fix catalog::columns::is_nullable to handle valid NULL.
b5fa350 Create view based on test table for catalog lookup test.
b5ee009 Avoid swallowing database_error if DROP TABLE failed.
79455aa Merge pull request #95 from mloskot/ml/fix-catch-tags
5610861 Merge pull request #94 from mloskot/ml/drop-table-if-exists
e591fdf Merge pull request #93 from mloskot/ml/post-pull-90-fix
12281dd Correct Catch test cases tags mismatch.
f274fcd Propose more portable test for a table presence.
ab19721 Restore ${ODBC_LIBRARIES} in target_link_libraries arguments.
48d7a73 Some tweaks to README.md.
80f56d4 Merge pull request #90 from lexicalunit/catch_tests
09b73f8 Migrates tests from Boost.Test to Catch framework.
da620bc initial attempt to introduce the CATCH unit testing framework...
825f28a Merge pull request #92 from mloskot/ml/dbms-info
e9bdd2c Add dbms_name and dbms_version methods to connection class.
REVERT: 2a32409 Pass compiler defines from the ODBC config tool to our build system. Make FindODBC module to prefer unixODBC over libiodbc when both ODBC driver managers are available.
REVERT: e6174f8 Initial ODBC API support. Enhance db demo to work with ODBC database connectioon string.

git-subtree-dir: Source/ThirdParty/nanodbc
git-subtree-split: 8f7d42600426e98c8f75f89ab058205a1c1b201b
Yao Wei Tjong 姚伟忠 9 éve
szülő
commit
286f5cf9e6
6 módosított fájl, 355 hozzáadás és 179 törlés
  1. 50 0
      CHANGELOG.md
  2. 1 1
      CMakeLists.txt
  3. 48 22
      README.md
  4. 1 1
      VERSION
  5. 229 143
      src/nanodbc.cpp
  6. 26 12
      src/nanodbc.h

+ 50 - 0
CHANGELOG.md

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

+ 1 - 1
CMakeLists.txt

@@ -1,5 +1,5 @@
 #
 #
-# Copyright (c) 2008-2015 the Urho3D project.
+# Copyright (c) 2008-2016 the Urho3D project.
 #
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
 # of this software and associated documentation files (the "Software"), to deal

+ 48 - 22
README.md

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

+ 1 - 1
VERSION

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

+ 229 - 143
src/nanodbc.cpp

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

+ 26 - 12
src/nanodbc.h

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