From 855681a40b4508473f4f37aad05af43c8f764669 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Sat, 2 Dec 2017 13:44:06 -0500 Subject: [PATCH] Squashed 'src/soci/' changes from b2855dce5..79e222e3c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 79e222e3c [Travis] Switch to latest Oracle downloader v2.0.3 3992cd8f6 [Travis] Try travis-oracle downloader hotfix 958f1b010 [Travis] Use only ORACLE_LOGIN_userid and ORACLE_LOGIN_pass f03569ffe [Travis] Oracle has changed login form 682486db9 Merge pull request #607 from rocksolidwebdesign/master e8ed51c37 use fully qualified soci::session to fix build on macosx sierra c98c877bc [doc] Add note on factory objects not exported from DLLs d0552e64a Catch unhandled exception in PostgreSQL ODBC unit test 643853322 Add missing -DWITH_POSTGRESQL=ON to scripts\build.bat 82867ced7 [docs] Clarify name of MySQL package with client library a35851478 Complete scripts\built.bat with basic CMake options. 225d5a3a4 [CMake] Simplify warning messages d90a7acfc [docs] Fix CMake options for SQLite 3 3d32f5253 Merge pull request #596 from mloskot/ml/macro-soci-override 5d980ee98 Merge pull request #600 from mloskot/ml/cmake-update-FindDB2 05ab0bcf6 Add SOCI_OVERRIDE macro as conditional C++11 override specifier aa9744e0a [DB2] Add additional install paths to search for DB2 client 3069f242b Merge pull request #594 from mloskot/ml/cmake-postgresql-nosinglerow a5bb763ab Merge pull request #599 from mloskot/ml/cmake-drop-cdash 466fdf32c [docs] Update name DB2 driver/client library name a7f5240a1 [CMake] Add SOCI_POSTGRESQL_NOSINLGEROWMODE option (default OFF) 601a89227 [CMake] Remove CDash remains fffe6d9c8 [CMake] Tidy up FindOracle.cmake formatting 6157f8d97 [CMake] Add /usr/lib/oracle/*/client${LIB_SUFFIX} to searched paths bbc18d73d [firebird] Add Ubuntu firebird-dev package as dependency example fd18f5c6c [CMake] Determine LIB_SUFFIX from SOCI_TARGET_ARCH_X64 c5f59c9d3 [Travis] Revert remove of any notifications to soci-devel mailing list f7f2a6123 [Travis] Remove any notifications to soci-devel mailing list 6df733826 [docs] Add pages for Ada language bindings 41f288bf3 Merge pull request #595 from mloskot/ml/db2-null-dereference 62f4bf5c4 [db2] Fix null pointer dereference 340a72eda [CMake] FindPostgreSQL.cmake update lost POSTGRESQL_VERSION return 5da552537 [docs] Remove Ada pages from TOC due to building failure 5e0e70c95 [docs] Add copyright footer ea0f6c11e [docs] Add languages/ada pages to TOC 2ab0fb901 Add CHANGELOG.md generated from GitHub 961368043 Add changelog.sh utility to generate CHANGELOG.md from GitHub 0621bd610 [Circle] Fix missing checkout in workflow 2c45f2bc5 [docs] Fix issues reported by markdownlint 46071f5f6 [docs] Replace HTML tables with Markdown tables 80a86c0eb Format compact table in README 14b693e64 [docs] Avoid markdownlint warnings about line length 13031a831 [docs] Fix README.md linting issues 2d575b8d7 [docs] Add link to master branch docs on docs.html 8427054b7 [Circle] Fix markdownlint invocation cc75d6e09 [Circle] Fix global installation of markdownlint-cli 522c7bbb6 [docs] Add .markdownlint.json configuration file 498381595 [AppVeyor] Add build job with Visual Studio 2017 (#588) fd37e3b80 [AppVeyor] Update badge after switch to organizational account 51d058f04 Repair VC8 compile of test applications (#593) c2310cf54 [Circle] Add markdownlint and define lint-build-deploy workflow for docs b9f4007e4 Fix __GNUC__ condition ignoring -Wmaybe-uninitialized 08fe54076 Ignored -Wmaybe-uninitialized in GCC all versions following 4.7 29be6f860 Merge pull request #592 from mloskot/ml/replace-alternative-tokens a161e24c1 Replace C alternative boolean operator representations with primary tokens 07a23d8a2 [CircleCI] Disable gem. Requires separate docker w/ Ruby df87eb1eb [CircleCI] Install markdownlint gem 92de7a387 [Vagrant] Fix bach if. Silence wget. Add logging. 6b9531179 [Vagrant] Update setup with primary VM and optionals. 4cb3b636f [Travis] Disable clang jobs to save on built times 4e98cd2cf [docs] Add MySQL 8.0.1 on Windows to tested versions 275965736 Add CMake options to disable/enable individual backends. cba05c702 Add build.bat for convenience of Visual Studio users 67c479ed9 Merge pull request #590 from mloskot/ml/cmake-update-FindPostgreSQL 35d2df6f4 [cmake] Update FindPostgreSQL.cmake from CMake 3.9 4fdfbf817 [cmake] Improve testing of Abc_FOUND and ABC_FOUND variables 3f006d3ae Fix copy-paste typo error in README bd3ee7a48 [CI] Fix webhook specs and events d31d17be3 [CI] Fix webhook URLs and use default events 34ced08e3 Fix URLs for badges in README 1653bd0b7 Update badges in README 7a4704be3 [Travis] Update webhook for notifications on gitter.im/SOCI/soci 17167fd97 [AppVeyor] Update webhook for notifications on gitter.im/SOCI/soci 95e55689e [Circle] Update webhook for notifications on gitter.im/SOCI/soci 68b914b19 Add footer: BSL © Maciej Sobczak and contributors. 8bf913e9c [Circle] Add webhook to receive notification on Gitter chat 25b2ab9af Actually link the generated docs 4aa8d5ab9 Link docs generated for master as (development) a40420e4c [Circle] Use API token for the badge fc59a621c [Circle] Fix status badge 15eba50e8 Add CircleCI setup to build docs and deploy to SF.net 3fa0a58cb [EditorConfig] Add settings for YAML and CMake scripts b36f437bc Merge branch 'more-xml' b85ec90c6 Check that inserting malformed XML values fails 2b89a134a Enable XML/CLOB tests for PostgreSQL via ODBC 6e4732102 Add very simple XML and CLOB support to ODBC backend fabb8f0f0 Make XML comparison in the test work for SQL Server too 11a8982d5 Add a helper function for copying strings in ODBC backend too 556f5d336 Add exchange_type_traits<> specializations for XML and CLOB f1243acdf Add helper function for copying string to PostgreSQL backend 5e6f37e35 Document types used for XML and CLOB support in Firebird backend bd236c8c8 Merge branch 'xml-firebird' e6e1ad305 Merge pull request #585 from mloskot/ml/enable-oracle-for-pulls 0cadedaf5 Enable Oracle builds for pull requests 93e844d02 [Travis] Update Oracle installation setup 3a82540aa Also throw if unsupported type is used for vector "into" in DB2 0f1756c55 Throw if unsupported type is used for vector "into" parameter 3a7677425 Merge branch 'odbc-vec-into-null' ff2464925 Merge branch 'oracle-fix-null-str' 023f4df03 Skip running Oracle tests for PR builds on Travis CI df4039923 Fix bug with null strings in bull selections in ODBC and DB2 6ca9cd64a Add REQUIRE_NOTHROW() around SQL statements in the string test 63bcc8944 Add workaround for empty strings being null in Oracle 2d74f9aaa Merge pull request #579 from mloskot/ml/travis-oracle-restore c3b943c87 Restore Oracle build job on Travis CI 9943459f4 Add support for XML type to Firebird backend too 059faf5d9 Add CLOB support to Firebird backend 85159db6f Refactor: move XML and CLOB tests to common code 4744c1705 Fix the build after the previous commit 201c89f0e Fixed wrong handling of errors in single-row mode for PostgreSQL. e26d134b4 Merge pull request #577 from mloskot/ml/bin-to-scripts 6363537e6 Add Windows shell scripts with CRLF to .editorconfig d38b21ca6 Move directory /bin to /scripts 316885a37 Merge pull request #576 from alexott/editorconfig 461a44585 Update .editorconfig with comments from review 2c7c312d7 Add support for EditorConfig 09c26314d Allow defining SOCI_POSTGRESQL_NOSINLGEROWMODE for PostgreSQL < 9 4b451971a Avoid using overloaded virtual methods in vector type backends f6980d2ce Merge pull request #572 from alexott/coverity-fixes 23d2f2593 fix some of warnings from fresh Coverity report 25eb5bdb3 Initialize indISCHolder_ member in firebird_into/use_type_backend classes 042dc0fd9 Merge pull request #570 from mcencora/master 9d82ee8e7 Remove unnecessary namespace qualification 3db8f4371 cmake: export SOCI public include directories e4ab099df Fix undefined sanitizer warnings 8fdb2e7ea Merge pull request #566 from mloskot/ml/mkdocs 80941a9d9 Update structure of SOCI 4.0.0 documentation 9c8191903 Fixed Makefile build for PostgreSQL (broken by other modifications). e7db59c74 Merge pull request #557 from msobczak/master f6d08a75f Merge pull request #564 from Arenoros/master 43229ecfe Fix types for SQLite3 backend ed25eea2c Fix session::get_table_names() for sqlite backend 7a6559c18 Merge branch 'odbc-string-len' c6aef12b1 Add test checking string length when using bulk insert 51c4c1e1a Fix the length of strings in DB2 vector operations be4f26fea Fix the length of strings in ODBC vector operations 521a84085 Improve get_affected_rows() documentation e7e7fb78a Return -1 from get_affected_rows() in ODBC backend if unknown 97a4728cb Remove loop that could be never executed from ODBC code 1a0eeb186 Fix harmless variable shadowing warnings e7cdd4754 Fix loss of error message in ODBC backend when using vectors 06915e483 Add check for absence of unneeded truncation too 82038018c Merge branch 'refactor-parse-std-tm' 56bc9f9c5 Return int, not long, from parse10() helper 281f54f51 Check for negative date components in parse10() f2e84c4bf Don't pass exception message to parse10() helper a1e07e389 Avoid duplicate code for parsing dates in different backends e2f1ca530 Fix libraries link order when linking tests statically a3685fd09 Fix unused parameter warnings in Oracle backend code 488825079 Explicitly use Ubuntu Precise for Travis builds 638b89d1f Merge remote-tracking branch 'upstream/master' 138b29e43 Lazy initialization of the temporary LOB objects for Oracle. a9ab0db16 Merge pull request #541 from ckaminski/master f938c0133 Get rid of allowed failures in Travis CI configuration 52539f8ab Merge branch 'odbc-mysql-bug' 2aeb32872 Fix reading from unallocated memory in ODBC with MySQL dc05c8e82 Use switch over exchange type in ODBC vector backend code 7bd491cf2 Fix check for clang in CMake configuration 702c214a9 Remove dummy stream insertion overload for boost::optional 1b416275d Remove Travis CI builds using Postgression d7c986c6b Disable MySQL unit test currently always resulting in a crash 2f65f02e8 Define SOCI_SQLITE3_SOURCE in all SQLite3 backend sources 4f1fbfcd4 Suppress memory leak report in glibc NSS under Ubuntu d91c2228c Add get_dummy_from_{table,clause}() methods 78bedc931 Consistently check for connection in the session object methods cf9cfa90c Fix DB2 backend build after x_xmltype and x_longstring additions 4b642ac5c Fixed handling of BINARY_DOUBLE in dynamic row. 2d621e8f3 Fixed memleak when reusing into and use elements. 9bb0f70b6 Revert "Fix inserting strings longer than 8000 bytes with ODBC/MS SQL" 7c28bd6c3 Fix inserting strings longer than 8000 bytes with ODBC/MS SQL b510e1a63 moved to latest Catch UT Framework (#544) 33ae81a71 Merge pull request #545 from snikulov/odbc_handle_defaults 92afc1f91 Use defaults for unsupported types in ODBC 7fba06984 disable autosetting of SOCI_CXX_C11 to OFF fa0e5b5a2 Merge pull request #536 from ddowling/master 723230fcc Support the OCI_SUCCESS_WITH_INFO return code in get_error_details f21578e2a Fix an uninitialised variable access when using the Oracle backend with a database type not in the standard accepted set. a912148f9 Fix uninitialized procedure::gotData_ 646efa070 Merge pull request #534 from mutcher/master 87509d52d Corrections in README.md 34c534722 Export struct sqlite3_soci_error from DLL on Windows. 840100102 Update .gitignore patterns for Visual Studio (Code) dc87a2c58 Docs markdown cleanup. e719c44bb Merge pull request #524 from ravselj/firebird_blob_bug 2edb58395 Firebird backend blob write bug fix. 0a88814c2 Added Firebird specific test indicating BLOB write bug. 6eb1a3e97 Fixed handling of empty long strings. e7194837e Added empty_blob() and nvl() to portable utilities. adae5ade8 Merge pull request #509 from msobczak/xml_clob 45eaff9e9 Added handling of big string in DDL. 9b2d48f3b Merge pull request #510 from mloskot/ml/add-missing-decl 8ae0f3962 Add missing declspec to properly export exception classes. f7b11ca71 Fixed handling of dt_xml in sqlite3. 1655a195e Added bigstring (XML and CLOB) support. 81a48efaf Fixed missing space in DDL. d68170494 Fixed handling of DDL alter column syntax. 1eb4c2935 Merge pull request #508 from msobczak/blob_offset 4b0dbab3e Added uniform offset for BLOB read/write operations. c5e37d273 Merge pull request #503 from wisk/cmake-package-export e082831ce export cmake package ba9963779 travis-ci: Allows to fail for odbc and mysql 92c430669 Merge pull request #454 from snikulov/enable_asan ec746bd1d Merge pull request #375 from ravselj/sqlite_load_one 88b331507 Merge pull request #487 from msobczak/bulkiterators 780a5c5d2 Corrected handling of vectors with user-defined types. 9e83a50c3 Merge pull request #494 from snikulov/fix_travis da09c7c6b Fix travis-ci build 307d94caf Merge pull request #492 from snikulov/av_odbc_driver_name 12708b190 Merge pull request #491 from snikulov/av_mingw_fix 4a0ac2ba3 Fixed driver name and passwd for PostgreSQL ODBC driver on AppVeyor 783e8b089 Fixed MinGW build with proper URL 0142d057f Merge pull request #489 from snikulov/fix_383 0723512f3 fixed #383 8d2901675 Added unit tests and docs for bulk iterators. 2f8a7a05e Added bulk iterators for Oracle and PostgreSQL. 2fcad8b24 Merge pull request #486 from msobczak/failover 002f6a9a7 Added docs for failover. 3daef94c7 Added failover callback for Oracle and PostgreSQL. a0b29bc94 Merge pull request #484 from msobczak/portable-DDL c959c3812 Added Oracle unit tests for DDL and metadata. 2a8e8965e Added PostgreSQL unit tests for DDL and metadata. cbee6429a Typo fixes and improved docs. 4244fae07 Merge pull request #482 from msobczak/PostgreSQL-singlerows 8d2a75baa Added support for portable DDL statements. a0917b7c8 Fixed multiple definitions bug with column_info. 596c6762b Merge pull request #480 from msobczak/metadata b70a4e8b2 Added singlerows mode for PostgreSQL. 816839514 Merge remote-tracking branch 'upstream/master' into metadata 30aca6230 Added docs for error categories. f2b2f4bc0 Added more error codes for error categories in Oracle. a05b61f1f Added docs and removed impractical function for metadata queries. 3c3fec623 Portable metadata queries. cd26169ef Added (partial) handling of OCI_SUCCESS_WITH_INFO in Oracle. 21a062023 Merge pull request #479 from snikulov/fix_478 bb8aaca05 fixed #478 376362493 Merge pull request #474 from wdavilaneto/master 23967e281 Merge pull request #475 from snikulov/cov_fixes ec65da5a5 Fixed uninited local std::tm structs 8cb99538c adding oracle remote connection exemple without tnsnames.ora 56379eb81 Merge pull request #471 from snikulov/fix_travis 25ac65fc5 Travis-CI: allowed failure for DB2 backend 8d8002b7b Merge pull request #470 from snikulov/fix_appvr 2895c7d06 Appveyour now using MySQL 5.7 d419e945a Update AppVeyor badge URL 5c14d49cc Remove info on binding char[] support. 57db62210 Added basic support for error categories. a1d45503b Documented Oracle wallet authentication option. cae51eec9 Added Oracle wallet authentication. 8c6b47977 Added NLS support for connection parameters in Oracle. 912649c2c Merge pull request #459 from shelomentsevd/master 8b70e0983 Documentation. exchange.md cleaned from typos c80646c0d Merge pull request #455 from snikulov/msvc2012_fix 3470079d1 compilation fix for msvc2012 d27fba19a Merge pull request #453 from snikulov/catch_updates 36e8fc91b updated catch.hpp version to 1.3.3 56b0b02b8 added Address Sanitizer to expose memory errors f26138913 Merge pull request #452 from vadz/cxx11 c07a42da0 Merge pull request #451 from vadz/config-vs-platform ec7d2dfc3 Define cxx_details::auto_ptr<> to get rid of preprocessor checks e06bfe96e Include soci/soci-platform.h instead of soci-config.h 6fbd8481e Avoid recursive inclusion between soci-{config,platform}.h 6caeb0b50 Merge pull request #449 from snikulov/appvr_up 78ea85ca1 fixed #447 ffc564b70 Use noexcept(false) for once_temp_type dtor with MSVS 2015 too 5e3813f50 Use C++11 deleted members for SOCI_NOT_{ASSIGNABLE,COPYABLE} 449e743b9 Merge pull request #446 from ArnaudD-FR/version 46513d877 CMake read version from version.h a8ab4c88b Revert "CMake generates soci/version.h" 6f68a7f00 Clean up already implemented features from TODO. 38d405935 Vagrant: update usage and development workflow. c2a387b2f Vagrant: correct DB2 database lookup for existing soci test db. f856f7730 Vagrant: fix accidental syntax error fb545a9b8 Vagrant: update usage documentation bd4d11d34 Add #define SOCI_HAVE_CXX_C11 mapped from with CMake SOCI_CXX_C11 option. e476b9d93 Vagrant: build with SOCI_CXX_C11=ON c6deb5d7e Vagrant: script formatting [ci skip] f4a47bfcb Vagrant: correct connection strings for tests 9b10e0ba2 Vagrant: set DB2 connection string for the backend tests. a84dade10 CMake: add boost_message_value as counterpart of boost_report_value. 540d33272 Vagrant: add SOCI_HOST variables for VMs networking d9e6d687a Merge pull request #438 from mloskot/db2-sqldriverconnect da61b6379 DB2: document option db2_option_driver_complete 838b99b62 DB2: Add sample SQLDriverConnect connection string 5d1ab2c77 DB2: Switch session from SQLConnect ot SQLDriverConnect ba8b1aa24 DB2: SQLExecute return code was not tested for SQL_NO_DATA. 837e88dec Vagrant: reorder booting to load DBs before dev env 8afea2f90 Vagrant: add SOCI_DB2_USER and SOCI_DB2_PASS variables 4a576a0c2 Vagrant: switch back to building from /vagrant share cd167df2e Vagrant: enable host to db2 networking on port 50000 7903d8e1a Vagrant: document networking support 04a59095d Vagrant: increase vm memory to 1024M 98ae7b206 Vagrant: try to avoid re-creation of soci DB if it exists 48a9c9797 Vagrant: do not run tests during provision 29c429ce9 Merge branch 'master' of https://github.com/SOCI/soci d6182bd9b Vagrant: add Avahi/MDNS to support resolving .local hostnames c22bfbcc1 Merge pull request #436 from ArnaudD-FR/test_version 094e19c68 CMake generates soci/version.h 2104cc9a6 Merge pull request #435 from ArnaudD-FR/test_definition 6908e473f Specify definitions in soci-config.h 634b2bf84 Vagrant: update DB2 CLI download steps 137efc496 Vagrant: add DB2 CLI client to soci.vm 435547064 Vagrant: define SOCI_* env variables via /etc/profile.d 569e8ecea CMake: add DB2 CLI driver locations 521f83cc0 Vagrant: correct comment [ci skip] e50025ea9 Vagrant: install zip [ci skip] a3e3f9671 Merge branch 'master' of https://github.com/SOCI/soci 6043cdf54 Vagrant: Add VM provisioned with DB2 instance and sample database 3a5066d9b Travis: remove sourcing of common.sh as unnecessary baad25922 Spell-check and formatting 111f5644d Preserve LF or CRLF for certain file types. 016687f03 Vagrant: explain debconf issue while installing Firebird 8a04c61c4 Merge pull request #434 from mloskot/master 01b9b48a3 Add Vagrant configuration 0ba242f51 Merge pull request #430 from ArnaudD-FR/sqlite3_reset d219a5790 [SQLITE3] Add reset API to reset sqlite3 statement fa708ad9f Merge pull request #428 from MonsieurNicolas/affectedRowFixSqlite 8cd3cf584 added test that reuses prepared statement 6af865fce reset count of affected rows (sqlite3) 149c53a43 Merge pull request #427 from snikulov/pr_coverity_init 0cc5c50ab - Fixed Coverity issues for SQLite3: 12644, 12645, 12646 - Fixed gcc/clang warns produced with -Wextra switch for SQLite3 9476a7a53 Merge pull request #426 from snikulov/pr_odbc_enable 86890bd2b Enabled mysql/postgresql - fixed #407 0232debc8 Merge pull request #425 from snikulov/cmake_win_cxx11 c2b37f0d2 moved SOCI_CXX_C11 define to common section 91fb559f0 Merge pull request #422 from snikulov/pr_374_fix 2af6c5efc Initialize rowid_backend-derived classes members in ctor 10e3c768a Avoid harmless Coverity dead code warning in MySQL code 4fd5c965a If prepare, execute, fetch... scenario is used and fetch() is called after gotData=false was already returned it begins another loop instead of returning false again indicating that all the data was already read. The following backend fix resolves this issue. e07f634a7 Added additional basic functionality test from prepare, execute, fetch scenario. 5b96f8c69 Clang: fixed #374 4576731e6 Disable email notifications to soci-devel 35bde9728 Merge pull request #420 from jeking3/feature/postgresql-uuid-unit-test ddde1f4c8 Merge pull request #421 from jeking3/bugfix/test-access-dsn-file-ignore 315b2cb5b add ignore of cmake generated dsn file for access 60abeb88c add a unit test for the postgresql uuid data type b1f730cd8 Merge pull request #419 from snikulov/pr_valgrind 8f51034cf Travis-CI: added valgrind check 185593f91 Merge pull request #418 from hw-dwalter/master b63e850cc fixed get_affected_rows() to pass test and fixed formatting 1ebc2661c Update .travis.yml to change coverity invokation branch ae3c4d2d8 Merge pull request #414 from hw-dwalter/master 90097ec73 added test to demonstrate bug #221 and make it valid with all backends b86a7b8ca Update README.md 6fe0a64bd Merge pull request #417 from snikulov/coverity_part_2 dff81b73e coverity: updated .travis.yml 8c0f35e21 Merge pull request #415 from snikulov/pr_413_rework 59d2cf016 Merge pull request #416 from snikulov/pr_travis_upd 1fa1a9413 Travis-CI: try add Coverity tool 63ef39126 AppVeyor: enable MSSQL ODBC connection 7aab87e8d Merge pull request #410 from snikulov/pr_appvr_mysql d36b06e26 Appveyor: added build for MySQL backend 8a67dbc63 Merge pull request #409 from snikulov/pr_appvr_postgres c0de792ae Appveyor: added PosgreSQL 9.4 backend 58975c6aa Merge pull request #408 from snikulov/pr_appvr_sqlite d0f6dec5f Appveyor: Added SQLite to build 4d2e19533 Merge pull request #406 from snikulov/appvr_db_services f7278ce54 Merge pull request #405 from snikulov/pr_odbc_test_reorg 629e64231 appvr: added db services to build workers 9d06e47a7 ODBC: test updates 14dfe5195 Appveyor: run ctest in test_script step 0530f6fec Avoid use of POSIX strptime function 68a335707 Add some variations for tests for use with indicators. 0d84fef30 Add std::tm checks to the use with indicators tests 985571234 Add tests for some variants with char* query 0b2837def Add test for query string as char* 3a49359bd Request CATCH to disable all the C++11 features. 24cbf26cd Merge pull request #403 from DraconPern/minorcmakefix 4fab0bb33 fixed typo 0e8223f89 Notify Gitter on every build 474c44443 Fix Gitter invalid token error 1eac4da5d Merge pull request #401 from snikulov/pr_appvr_boost da59e623d Merge pull request #400 from snikulov/pr_boost_opt c02d65340 Merge pull request #402 from ArnaudD-FR/master 09d6f9c81 Name struct to avoid clang error/warning (issue #374) 87e1c8384 Appveyor: added Boost libraries to build 063e75374 fix for #370 1fd43ebf6 Fix accidentally broken YAML syntax 87492930c Add Gitter integration webhooks for Travis CI and Appveyor 21ba1ddc8 Merge pull request #398 from snikulov/mingw_final 2060fa062 Merge pull request #399 from SOCI/snikulov-patch-1 c683042f6 Update README.md 59c49ed50 mingw: not use ms extension for MinGW G++ compilation 492d04249 Merge pull request #397 from snikulov/make_verbose e3e093b2e cmake: generate verbose build 903590290 Merge pull request #395 from snikulov/av_warn_fix d1fd85b45 connection-pool: moved common part out of #ifdef 6c8b6cd42 Switch Appveyor URL from snikulov/soci to mloskot/soci. b6443a4a2 Restore using -pedantic but use -Wno-pedantic-ms-format too 8fe5d2a1d Initialise in/out pos variable 96783407e Update connection-pool.cpp 21357eda7 Merge pull request #391 from snikulov/pr_appveyor_init 443beaa0b Don't add "-fPIC" explicitly, CMake does it automatically e607b1c73 Don't use "-pedantic" option when building with g++ 56207063b initial integration with AppVeyor-CI 8fda2bb37 boost: disable autolink, because soci controls required libs itself 7747cab51 updated to latest Catch UT framework to work with MinGW with PR #496 bb79b6074 cmake: updated FindODBC.cmake to search right sqlext.h on Windows 64dfb79f7 Merge pull request #381 from ravselj/sqlite_bug 23d398994 sqlite3_close moved after sqlite3_errmsg is called. d0f718350 Merge pull request #380 from dgrafe/subproject_integration_fixed f2261c40a Build system: Replacing CMAKE_SOURCE_DIR with CMAKE_CURRENT_SOURCE_DIR git-subtree-dir: src/soci git-subtree-split: 79e222e3c2278e6108137a2d26d3689418b37544 --- .circleci/config.yml | 59 + .editorconfig | 32 + .gitattributes | 8 + .gitignore | 13 +- .markdownlint.json | 7 + .travis.yml | 59 +- CHANGELOG.md | 456 ++ CHANGES | 1 + CMakeLists.txt | 98 +- CTestConfig.cmake | 13 - README.md | 73 +- TODO | 26 +- Vagrantfile | 56 + appveyor.yml | 104 + bin/ci/before_install_oracle.sh | 123 - bin/ci/oracle.sh | 14 - bin/ci/script_postgression.sh | 47 - cmake/SociBackend.cmake | 49 +- cmake/SociConfig.cmake | 57 +- cmake/SociDependencies.cmake | 18 +- cmake/SociUtilities.cmake | 8 + cmake/SociVersion.cmake | 19 +- .../configs/test-access.cmake | 6 +- .../configs/test-mysql.cmake | 2 +- cmake/dependencies/PostgreSQL.cmake | 2 +- cmake/modules/FindDB2.cmake | 10 +- cmake/modules/FindMySQL.cmake | 2 +- cmake/modules/FindODBC.cmake | 34 +- cmake/modules/FindOracle.cmake | 47 +- cmake/modules/FindPostgreSQL.cmake | 233 +- docs/{backends.md => api/backend.md} | 390 +- docs/{reference.md => api/client.md} | 787 +- docs/backends/db2.md | 107 +- docs/backends/firebird.md | 226 +- docs/backends/index.md | 111 +- docs/backends/mysql.md | 154 +- docs/backends/odbc.md | 261 +- docs/backends/oracle.md | 234 +- docs/backends/postgresql.md | 219 +- docs/backends/sqlite3.md | 191 +- docs/beyond.md | 108 +- docs/binding.md | 184 + docs/boost.md | 97 +- docs/connections.md | 134 +- docs/errors.md | 143 +- docs/exchange.md | 536 -- docs/{rationale.md => faq.md} | 86 +- docs/images/structure.png | Bin 0 -> 26919 bytes docs/index.md | 119 +- docs/indicators.md | 102 + docs/installation.md | 276 +- docs/interfaces.md | 88 +- docs/languages/ada/concepts.md | 7 +- docs/languages/ada/idioms.md | 260 +- docs/languages/ada/index.md | 18 +- docs/languages/ada/reference.md | 685 +- docs/languages/index.md | 3 + docs/license.md | 29 + docs/lobs.md | 71 + docs/logging.md | 26 + docs/multithreading.md | 51 +- docs/procedures.md | 20 + docs/queries.md | 98 +- docs/quickstart.md | 56 + docs/statements.md | 394 +- docs/structure.md | 26 +- docs/transactions.md | 45 + docs/types.md | 292 + docs/utilities.md | 151 + docs/vagrant.md | 125 + include/private/soci-exchange-cast.h | 13 + include/private/soci-mktime.h | 5 + include/soci/bind-values.h | 21 +- include/soci/blob.h | 14 + include/soci/boost-optional.h | 13 +- include/soci/callbacks.h | 47 + include/soci/column-info.h | 123 + include/soci/connection-parameters.h | 1 + include/soci/connection-pool.h | 2 + include/soci/db2/soci-db2.h | 118 +- include/soci/empty/soci-empty.h | 104 +- include/soci/error.h | 20 +- include/soci/exchange-traits.h | 17 + include/soci/firebird/soci-firebird.h | 155 +- include/soci/into-type.h | 77 +- include/soci/into.h | 20 +- include/soci/mysql/soci-mysql.h | 134 +- include/soci/odbc/soci-odbc.h | 159 +- include/soci/once-temp-type.h | 57 +- include/soci/oracle/soci-oracle.h | 312 +- include/soci/postgresql/soci-postgresql.h | 223 +- include/soci/procedure.h | 5 +- include/soci/query_transformation.h | 2 +- include/soci/ref-counted-prepare-info.h | 2 +- include/soci/ref-counted-statement.h | 17 +- include/soci/row-exchange.h | 11 +- include/soci/rowset.h | 10 +- include/soci/session.h | 69 +- include/soci/soci-backend.h | 226 +- .../soci/{soci-config.h => soci-config.h.in} | 5 +- include/soci/soci-platform.h | 58 +- include/soci/soci.h | 2 + include/soci/sqlite3/soci-sqlite3.h | 195 +- include/soci/statement.h | 1 + include/soci/type-conversion.h | 295 +- include/soci/type-holder.h | 4 +- include/soci/type-wrappers.h | 33 + include/soci/use-type.h | 128 +- include/soci/use.h | 40 +- include/soci/values-exchange.h | 18 +- mkdocs.yml | 51 + scripts/build.bat | 79 + scripts/changelog.sh | 24 + {bin/ci => scripts/travis}/before_install.sh | 7 +- .../travis}/before_install_db2.sh | 6 +- .../travis}/before_install_firebird.sh | 2 +- .../travis}/before_install_odbc.sh | 2 +- scripts/travis/before_install_oracle.sh | 27 + {bin/ci => scripts/travis}/before_script.sh | 4 +- .../travis}/before_script_db2.sh | 2 +- .../travis}/before_script_firebird.sh | 2 +- .../travis}/before_script_mysql.sh | 2 +- .../travis}/before_script_odbc.sh | 2 +- .../travis}/before_script_oracle.sh | 28 +- .../travis}/before_script_postgresql.sh | 2 +- scripts/travis/before_script_valgrind.sh | 12 + {bin/ci => scripts/travis}/common.sh | 5 + scripts/travis/oracle.sh | 12 + {bin/ci => scripts/travis}/script.sh | 4 +- {bin/ci => scripts/travis}/script_db2.sh | 4 +- {bin/ci => scripts/travis}/script_empty.sh | 4 +- {bin/ci => scripts/travis}/script_firebird.sh | 4 +- {bin/ci => scripts/travis}/script_mysql.sh | 4 +- {bin/ci => scripts/travis}/script_odbc.sh | 6 +- {bin/ci => scripts/travis}/script_oracle.sh | 6 +- .../travis}/script_postgresql.sh | 4 +- {bin/ci => scripts/travis}/script_sqlite3.sh | 4 +- scripts/travis/script_valgrind.sh | 17 + scripts/vagrant/bootstrap.sh | 18 + scripts/vagrant/build.sh | 42 + scripts/vagrant/common.env | 11 + scripts/vagrant/db2.sh | 25 + scripts/vagrant/db2cli.sh | 55 + scripts/vagrant/devel.sh | 27 + scripts/vagrant/firebird.sh | 49 + scripts/vagrant/mysql.sh | 27 + scripts/vagrant/postgresql.sh | 27 + .../vm/debian-oracle10g-install.sh | 0 scripts/windows/Get-ODBCList.ps1 | 105 + scripts/windows/mssql_db_create.sql | 1 + src/backends/CMakeLists.txt | 2 + src/backends/db2/session.cpp | 59 +- src/backends/db2/standard-use-type.cpp | 2 + src/backends/db2/statement.cpp | 2 +- src/backends/db2/vector-into-type.cpp | 39 +- src/backends/db2/vector-use-type.cpp | 12 +- src/backends/firebird/blob.cpp | 27 +- src/backends/firebird/standard-into-type.cpp | 29 + src/backends/firebird/standard-use-type.cpp | 24 + src/backends/firebird/vector-into-type.cpp | 2 +- src/backends/mysql/common.cpp | 57 - src/backends/mysql/common.h | 3 - src/backends/mysql/session.cpp | 43 +- src/backends/mysql/standard-into-type.cpp | 1 + src/backends/mysql/statement.cpp | 6 +- src/backends/mysql/vector-into-type.cpp | 3 +- src/backends/odbc/session.cpp | 50 +- src/backends/odbc/standard-into-type.cpp | 12 +- src/backends/odbc/standard-use-type.cpp | 76 +- src/backends/odbc/statement.cpp | 38 +- src/backends/odbc/vector-into-type.cpp | 52 +- src/backends/odbc/vector-use-type.cpp | 155 +- src/backends/oracle/error.cpp | 24 +- src/backends/oracle/factory.cpp | 55 +- src/backends/oracle/session.cpp | 264 +- src/backends/oracle/standard-into-type.cpp | 101 + src/backends/oracle/standard-use-type.cpp | 124 + src/backends/oracle/statement.cpp | 10 + src/backends/oracle/vector-into-type.cpp | 287 +- src/backends/oracle/vector-use-type.cpp | 177 +- src/backends/postgresql/CMakeLists.txt | 13 + src/backends/postgresql/Makefile.basic | 4 +- src/backends/postgresql/common.h | 3 - src/backends/postgresql/error.cpp | 95 +- src/backends/postgresql/factory.cpp | 95 +- src/backends/postgresql/row-id.cpp | 2 +- src/backends/postgresql/session.cpp | 27 +- .../postgresql/standard-into-type.cpp | 8 + src/backends/postgresql/standard-use-type.cpp | 28 +- src/backends/postgresql/statement.cpp | 395 +- src/backends/postgresql/vector-into-type.cpp | 134 +- src/backends/postgresql/vector-use-type.cpp | 82 +- src/backends/sqlite3/Makefile.basic | 46 +- src/backends/sqlite3/blob.cpp | 1 + src/backends/sqlite3/common.cpp | 59 - src/backends/sqlite3/common.h | 3 - src/backends/sqlite3/row-id.cpp | 3 +- src/backends/sqlite3/session.cpp | 4 +- src/backends/sqlite3/standard-into-type.cpp | 2 + src/backends/sqlite3/standard-use-type.cpp | 1 + src/backends/sqlite3/statement.cpp | 35 +- src/backends/sqlite3/vector-into-type.cpp | 19 +- src/backends/sqlite3/vector-use-type.cpp | 1 + src/core/CMakeLists.txt | 23 +- src/core/Makefile.basic | 7 +- src/core/blob.cpp | 12 + src/{backends/postgresql => core}/common.cpp | 47 +- src/core/connection-pool.cpp | 63 +- src/core/into-type.cpp | 35 +- src/core/once-temp-type.cpp | 145 +- src/core/ref-counted-statement.cpp | 5 +- src/core/session.cpp | 122 +- src/core/soci-simple.cpp | 16 +- src/core/statement.cpp | 23 + src/core/use-type.cpp | 40 +- tests/CMakeLists.txt | 5 + tests/catch.hpp | 6340 ++++++++++++----- tests/common-tests.h | 431 +- tests/db2/test-db2.cpp | 15 +- tests/empty/test-empty.cpp | 19 +- tests/firebird/test-firebird.cpp | 72 +- tests/mysql/test-mysql.cpp | 10 +- tests/mysql/test-mysql.h | 23 +- tests/odbc/CMakeLists.txt | 7 +- tests/odbc/soci_test.mdb | Bin 0 -> 77824 bytes tests/odbc/test-mssql.dsn | 14 +- tests/odbc/test-odbc-access.cpp | 22 +- tests/odbc/test-odbc-db2.cpp | 5 + tests/odbc/test-odbc-mssql.cpp | 50 +- tests/odbc/test-odbc-postgresql.cpp | 108 +- tests/odbc/test-postgresql-win64.dsn | 9 + tests/odbc/test-postgresql.dsn | 5 +- tests/oracle/test-oracle.cpp | 486 +- tests/postgresql/test-postgresql.cpp | 486 +- tests/sqlite3/Makefile.basic | 12 + tests/sqlite3/test-sqlite3.cpp | 32 +- valgrind.suppress | 9 + www/doc.html | 3 +- www/doc/index.html | 3 +- 239 files changed, 16094 insertions(+), 7461 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 .editorconfig create mode 100644 .markdownlint.json create mode 100644 CHANGELOG.md delete mode 100644 CTestConfig.cmake create mode 100644 Vagrantfile create mode 100644 appveyor.yml delete mode 100755 bin/ci/before_install_oracle.sh delete mode 100644 bin/ci/oracle.sh delete mode 100755 bin/ci/script_postgression.sh rename tests/odbc/test-access.dsn => cmake/configs/test-access.cmake (50%) rename tests/odbc/test-mysql.dsn => cmake/configs/test-mysql.cmake (56%) rename docs/{backends.md => api/backend.md} (50%) rename docs/{reference.md => api/client.md} (53%) create mode 100644 docs/binding.md delete mode 100644 docs/exchange.md rename docs/{rationale.md => faq.md} (89%) create mode 100644 docs/images/structure.png create mode 100644 docs/indicators.md create mode 100644 docs/languages/index.md create mode 100644 docs/license.md create mode 100644 docs/lobs.md create mode 100644 docs/logging.md create mode 100644 docs/procedures.md create mode 100644 docs/quickstart.md create mode 100644 docs/transactions.md create mode 100644 docs/types.md create mode 100644 docs/utilities.md create mode 100644 docs/vagrant.md create mode 100644 include/soci/callbacks.h create mode 100644 include/soci/column-info.h rename include/soci/{soci-config.h => soci-config.h.in} (81%) create mode 100644 include/soci/type-wrappers.h create mode 100644 mkdocs.yml create mode 100644 scripts/build.bat create mode 100644 scripts/changelog.sh rename {bin/ci => scripts/travis}/before_install.sh (76%) rename {bin/ci => scripts/travis}/before_install_db2.sh (92%) rename {bin/ci => scripts/travis}/before_install_firebird.sh (91%) rename {bin/ci => scripts/travis}/before_install_odbc.sh (84%) create mode 100755 scripts/travis/before_install_oracle.sh rename {bin/ci => scripts/travis}/before_script.sh (61%) rename {bin/ci => scripts/travis}/before_script_db2.sh (85%) rename {bin/ci => scripts/travis}/before_script_firebird.sh (87%) rename {bin/ci => scripts/travis}/before_script_mysql.sh (79%) rename {bin/ci => scripts/travis}/before_script_odbc.sh (83%) rename {bin/ci => scripts/travis}/before_script_oracle.sh (61%) rename {bin/ci => scripts/travis}/before_script_postgresql.sh (80%) create mode 100755 scripts/travis/before_script_valgrind.sh rename {bin/ci => scripts/travis}/common.sh (78%) mode change 100644 => 100755 create mode 100755 scripts/travis/oracle.sh rename {bin/ci => scripts/travis}/script.sh (69%) rename {bin/ci => scripts/travis}/script_db2.sh (84%) rename {bin/ci => scripts/travis}/script_empty.sh (80%) rename {bin/ci => scripts/travis}/script_firebird.sh (84%) rename {bin/ci => scripts/travis}/script_mysql.sh (82%) rename {bin/ci => scripts/travis}/script_odbc.sh (76%) rename {bin/ci => scripts/travis}/script_oracle.sh (77%) rename {bin/ci => scripts/travis}/script_postgresql.sh (83%) rename {bin/ci => scripts/travis}/script_sqlite3.sh (82%) create mode 100755 scripts/travis/script_valgrind.sh create mode 100755 scripts/vagrant/bootstrap.sh create mode 100755 scripts/vagrant/build.sh create mode 100644 scripts/vagrant/common.env create mode 100755 scripts/vagrant/db2.sh create mode 100755 scripts/vagrant/db2cli.sh create mode 100755 scripts/vagrant/devel.sh create mode 100755 scripts/vagrant/firebird.sh create mode 100755 scripts/vagrant/mysql.sh create mode 100755 scripts/vagrant/postgresql.sh rename {bin => scripts}/vm/debian-oracle10g-install.sh (100%) mode change 100644 => 100755 create mode 100644 scripts/windows/Get-ODBCList.ps1 create mode 100644 scripts/windows/mssql_db_create.sql delete mode 100644 src/backends/sqlite3/common.cpp rename src/{backends/postgresql => core}/common.cpp (59%) create mode 100644 tests/odbc/soci_test.mdb create mode 100644 tests/odbc/test-postgresql-win64.dsn create mode 100644 tests/sqlite3/Makefile.basic create mode 100644 valgrind.suppress diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..6293bf98d5 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,59 @@ +version: 2 +jobs: + lint: + docker: + - image: circleci/ruby:2.4.1-node + steps: + - checkout + - run: + name: install markdownlint + command: | + sudo npm install -g markdownlint-cli + markdownlint --version + - run: + name: lint README.md + command: markdownlint --config .markdownlint.json README.md + - run: + name: lint docs/ + command: markdownlint --config .markdownlint.json docs/ + build-deploy: + docker: + - image: circleci/python:2.7 + steps: + - checkout + - run: + name: install mkdocs + command: sudo pip install mkdocs + - run: + name: generate docs + command: mkdocs build --clean + - run: + name: install lftp + command: | + sudo apt-get update -q + sudo apt-get install -y lftp + - run: + name: deploy docs + command: lftp sftp://${DEPLOY_DOCS_USER}:${DEPLOY_DOCS_PASS}@${DEPLOY_DOCS_HOST} -e "set ftp:ssl-force true; set ftp:ssl-protect-data true; set ssl:verify-certificate no; set sftp:auto-confirm yes; mirror -v -R ./site ${DEPLOY_DOCS_BASE}/doc/${CIRCLE_BRANCH}; quit" + +workflows: + version: 2 + docs-workflow: + jobs: + - lint: + filters: + branches: + only: + - master + - /release*/ + - build-deploy: + requires: + - lint + filters: + branches: + only: + - master + - /release*/ +notify: + webhooks: + - url: https://webhooks.gitter.im/e/9ff4be3a6f08f55106e2 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..3db57fb750 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,32 @@ +# This is the EditorConfig (http://editorconfig.org/) coding style file for SOCI. +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# CMake configuration files +[{CMakeLists.txt,*.cmake}] +indent_size = 2 +indent_style = space +trim_trailing_whitespace = true +insert_final_newline = true + +# CI configuration files +[{.travis.yml,appveyor.yml,Vagrantfile}] +indent_size = 2 +indent_style = space +trim_trailing_whitespace = true +insert_final_newline = true + +# Windows shell scripts +[*.{cmd,bat,ps1}] +end_of_line = crlf +indent_size = 4 +indent_style = space +insert_final_newline = false +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes index 412eeda78d..638b14ee1a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,14 @@ # Auto detect text files and perform LF normalization * text=auto +*.bash eol=lf +*.env eol=lf +*.sh eol=lf +*.cmd eol=crlf +*.bat eol=crlf +*.BAT eol=crlf +*.CMD eol=crlf + # Custom for Visual Studio *.cs diff=csharp *.sln merge=union diff --git a/.gitignore b/.gitignore index 609b4e844a..0452661566 100644 --- a/.gitignore +++ b/.gitignore @@ -5,13 +5,14 @@ tags tmp # Build directories -_build* -src/_build* -src/build +/*build* +/*site* # Files generated by CMake Makefile src/core/soci_backends_config.h +tests/odbc/test-access.dsn +tests/odbc/test-mysql.dsn # ... and the rest of CMake spam CMakeFiles/ @@ -19,10 +20,11 @@ CMakeCache.txt CTestTestfile.cmake cmake_install.cmake -# Visual Studio +# Visual Studio / Visual Studio Code *.opensdf *.sdf *.suo +/*.vs* # KDevelop *.kate-swp @@ -37,3 +39,6 @@ CMakeLists.txt.user # Eclipse /.project + +# Vagrant +/.vagrant/ diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000000..7d75b410c9 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,7 @@ +{ + "default": true, + "MD013": false, + "MD026": false, + "MD024": false, + "MD029": {"style": "ordered"} +} diff --git a/.travis.yml b/.travis.yml index bfe6ff13cf..cff9f9a18d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,12 @@ # # Copyright (c) 2013 Mateusz Loskot # +# TODO: Switch to Trusty https://github.com/SOCI/soci/issues/574 +dist: precise language: cpp +sudo: required + compiler: - g++ #- clang @@ -14,32 +18,43 @@ services: - postgresql env: - - SOCI_TRAVIS_BACKEND=db2 - - SOCI_TRAVIS_BACKEND=empty - - SOCI_TRAVIS_BACKEND=firebird - - SOCI_TRAVIS_BACKEND=mysql - - SOCI_TRAVIS_BACKEND=odbc - - SOCI_TRAVIS_BACKEND=oracle CFLAGS=-m32 CXXFLAGS=-m32 WITH_BOOST=OFF - - SOCI_TRAVIS_BACKEND=postgresql - - SOCI_TRAVIS_BACKEND=postgression - - SOCI_TRAVIS_BACKEND=sqlite3 + global: + - secure: "I7/28jg7R24y64426d5XsfILrd/VW0BdwFbNpEgBfW1qNk9GpkNGTvp/ET6hKwBVrW5jmN9QdEviGcPpQRIAlMj6g9GvZeAUxM+VZTcXD2u30REUPPxNTJMRVHPfL9DA7EMFCST8SjBCgMdTHFwqLV4vSQEF4NTXbntley/IPfM=" + matrix: + - SOCI_TRAVIS_BACKEND=db2 + - SOCI_TRAVIS_BACKEND=empty + - SOCI_TRAVIS_BACKEND=firebird + - SOCI_TRAVIS_BACKEND=mysql + - SOCI_TRAVIS_BACKEND=odbc + - SOCI_TRAVIS_BACKEND=postgresql + - SOCI_TRAVIS_BACKEND=sqlite3 + - SOCI_TRAVIS_BACKEND=valgrind + - SOCI_TRAVIS_BACKEND=oracle WITH_BOOST=OFF -matrix: - fast_finish: true - allow_failures: - - env: SOCI_TRAVIS_BACKEND=postgression +addons: + apt: + sources: + - ubuntu-toolchain-r-test + - kalakris-cmake + packages: + - cmake + coverity_scan: + project: + name: "SOCI/soci" + notification_email: soci-devel@lists.sourceforge.net + build_command_prepend: "mkdir build.cov; cd build.cov; cmake .." + build_command: "make -j 4" + branch_pattern: coverity_scan -before_install: ./bin/ci/before_install.sh -before_script: ./bin/ci/before_script.sh -script: ./bin/ci/script.sh +before_install: ./scripts/travis/before_install.sh +before_script: ./scripts/travis/before_script.sh +script: ./scripts/travis/script.sh notifications: - email: - recipients: - - soci-devel@lists.sourceforge.net - on_success: change # [always|never|change] # default: change - on_failure: always # [always|never|change] # default: always - + webhooks: + urls: + - https://webhooks.gitter.im/e/000781732a3b1637ef82 + on_start: always irc: channels: - "irc.freenode.org#soci" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..054f0ce987 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,456 @@ +# Change Log + +## [Unreleased](https://github.com/SOCI/soci/tree/HEAD) + +[Full Changelog](https://github.com/SOCI/soci/compare/3.2.3...HEAD) + +**Closed issues:** + +- Exception while selecting null values with indicators [\#581](https://github.com/SOCI/soci/issues/581) +- Null value fetched and no indicator defined [\#580](https://github.com/SOCI/soci/issues/580) +- sequence support MySQL and PostgreSQL [\#569](https://github.com/SOCI/soci/issues/569) +- Implement session::get\_table\_names\(\) for sqlite3 [\#563](https://github.com/SOCI/soci/issues/563) +- Make Oracle work again on Travis CI [\#554](https://github.com/SOCI/soci/issues/554) +- Split AppVeyor jobs per backend [\#550](https://github.com/SOCI/soci/issues/550) +- how to build soci with postgresql? [\#543](https://github.com/SOCI/soci/issues/543) +- How can I install SOCI on HP UX? [\#538](https://github.com/SOCI/soci/issues/538) +- Error C2065 'x\_type': undeclared identifier [\#531](https://github.com/SOCI/soci/issues/531) +- Unable to catch sqlite3\_soci\_error, program aborts [\#529](https://github.com/SOCI/soci/issues/529) +- unresolved external symbol sqlite3\_soci\_error::result when linking soci\_sqlite3\_test.exe on VS2015 U3 [\#528](https://github.com/SOCI/soci/issues/528) +- How to use rowset with Core interface [\#527](https://github.com/SOCI/soci/issues/527) +- can std::string of soci hold the right utf8 data? [\#525](https://github.com/SOCI/soci/issues/525) +- \[Q\] Store query in unsorted\_map [\#522](https://github.com/SOCI/soci/issues/522) +- use vector for `in` [\#520](https://github.com/SOCI/soci/issues/520) +- Deadlock found when trying to get lock; try restarting transaction [\#518](https://github.com/SOCI/soci/issues/518) +- Link issue with master [\#513](https://github.com/SOCI/soci/issues/513) +- Uninitialised type in oracle\_statement\_backend::describe\_column [\#511](https://github.com/SOCI/soci/issues/511) +- unfortunate placement of soci headers with common names [\#505](https://github.com/SOCI/soci/issues/505) +- SOCI\_STATIC and plugins [\#497](https://github.com/SOCI/soci/issues/497) +- Illegal memory access on Windows [\#488](https://github.com/SOCI/soci/issues/488) +- into/use\_container\ is causing troubles [\#485](https://github.com/SOCI/soci/issues/485) +- Provide CMake package configuration file [\#481](https://github.com/SOCI/soci/issues/481) +- delete already deleted pimpl if connection\_pool is assigned [\#478](https://github.com/SOCI/soci/issues/478) +- Is there any roadmap to support vertica ? [\#477](https://github.com/SOCI/soci/issues/477) +- Handle OCI\_SUCCESS\_WITH\_INFO [\#473](https://github.com/SOCI/soci/issues/473) +- Coverity not fully configured [\#472](https://github.com/SOCI/soci/issues/472) +- soci 3.2.3 build on windows is not complete [\#468](https://github.com/SOCI/soci/issues/468) +- Char \* not supported in use/into. Documentation states otherwise. [\#466](https://github.com/SOCI/soci/issues/466) +- VS2015 Exceptions =\> abort\(\) [\#465](https://github.com/SOCI/soci/issues/465) +- Error classification [\#464](https://github.com/SOCI/soci/issues/464) +- Oracle wallet authentication [\#463](https://github.com/SOCI/soci/issues/463) +- get the result in a c++ code of stored procedure after a call [\#460](https://github.com/SOCI/soci/issues/460) +- NLS supports for connection params in Oracle [\#458](https://github.com/SOCI/soci/issues/458) +- AppVeyor can not find sh.exe? [\#447](https://github.com/SOCI/soci/issues/447) +- async query call [\#445](https://github.com/SOCI/soci/issues/445) +- Does this library to work with firebird events? [\#442](https://github.com/SOCI/soci/issues/442) +- SOCI doesn't define SOCI\_HAVE\_CXX\_C11 for VS2015 [\#441](https://github.com/SOCI/soci/issues/441) +- ...is not a member of 'sqlite\_api' [\#440](https://github.com/SOCI/soci/issues/440) +- Compile errors [\#439](https://github.com/SOCI/soci/issues/439) +- C++11 \#define should be in soci-config.hpp defined [\#437](https://github.com/SOCI/soci/issues/437) +- Fix discrepency between SOCI\_USE\_BOOST and HAVE\_BOOST [\#433](https://github.com/SOCI/soci/issues/433) +- Add Vagrant configuration for easy development [\#432](https://github.com/SOCI/soci/issues/432) +- DB2 statement execute error handling is ambiguous [\#431](https://github.com/SOCI/soci/issues/431) +- Issue using boost::tuple integration after upgrading from soci 3.2.2 to 3.2.3 [\#429](https://github.com/SOCI/soci/issues/429) +- conversion\_use\_type misses members initialization [\#412](https://github.com/SOCI/soci/issues/412) +- AppVeyor: ODBC tests are failed [\#407](https://github.com/SOCI/soci/issues/407) +- Can't run SOCI tests under Windows using ODBC data source [\#396](https://github.com/SOCI/soci/issues/396) +- Problems building SOCI under windows [\#384](https://github.com/SOCI/soci/issues/384) +- std::strings in nvarchar\(max\) is still a problem [\#383](https://github.com/SOCI/soci/issues/383) +- Build VS2015 Win10 Errors [\#379](https://github.com/SOCI/soci/issues/379) +- soci-3.2.3/backends/mysql/session.cpp:202: bad if test ? [\#377](https://github.com/SOCI/soci/issues/377) +- Clang warnings preventing compilation [\#374](https://github.com/SOCI/soci/issues/374) +- Boost 1.58.0 fails static assertion when streaming optional w/o optional\_io header [\#370](https://github.com/SOCI/soci/issues/370) +- Building of very first file fails with mingw [\#369](https://github.com/SOCI/soci/issues/369) +- How to pass dynamic multiple variables to query [\#354](https://github.com/SOCI/soci/issues/354) +- Rename SOCI headers [\#342](https://github.com/SOCI/soci/issues/342) +- OSX: struct session conflicting with unqualified soci::session [\#340](https://github.com/SOCI/soci/issues/340) +- Remove old build .tcl scripts [\#339](https://github.com/SOCI/soci/issues/339) +- Building errors with mingw, linking and undef. reference [\#338](https://github.com/SOCI/soci/issues/338) +- Missing static ODBC object file in installation script [\#337](https://github.com/SOCI/soci/issues/337) +- Mac OS: master branch fails to build on Yosemite [\#334](https://github.com/SOCI/soci/issues/334) +- Port: Allow SOCI\_LIBDIR to be set on the command line for easier packaging. \#195 [\#311](https://github.com/SOCI/soci/issues/311) +- Port: Add WITH\_CXX11 configuration flag \(currently for gcc and clang\) \#203 [\#310](https://github.com/SOCI/soci/issues/310) +- Incorrect time zone in struct tm returned from SOCI [\#308](https://github.com/SOCI/soci/issues/308) +- Drop GitFlow branching model [\#305](https://github.com/SOCI/soci/issues/305) +- Merge master 3.2.3 into develop [\#303](https://github.com/SOCI/soci/issues/303) +- Get rid of asserts in the code [\#300](https://github.com/SOCI/soci/issues/300) +- Set up a test database for Oracle Travis CI build. [\#290](https://github.com/SOCI/soci/issues/290) +- Big std::strings in nvarchar\(max\)s via ODBC fails with HY104 [\#287](https://github.com/SOCI/soci/issues/287) +- BUG problem on the second iteration in a rowset [\#280](https://github.com/SOCI/soci/issues/280) +- ODBC stored procedure [\#275](https://github.com/SOCI/soci/issues/275) +- Build and compile soci odbc failed [\#274](https://github.com/SOCI/soci/issues/274) +- Add MinGW build to Travis CI [\#247](https://github.com/SOCI/soci/issues/247) +- Fix warnings in MySQL backend compilation [\#244](https://github.com/SOCI/soci/issues/244) +- \[fix\] Remove -ansi when building on FreeBSD 10 [\#236](https://github.com/SOCI/soci/issues/236) +- Clean up terse or misspelled exception messages in ODBC backend [\#231](https://github.com/SOCI/soci/issues/231) +- odbc\_soci\_error should append sqlstate\_, sqlcode\_ and message\_ to the string used to initialize soci\_error [\#228](https://github.com/SOCI/soci/issues/228) +- Install Oracle on Travis CI server [\#224](https://github.com/SOCI/soci/issues/224) +- ODBC string bulk insertions are adding an escape char at the end of each string [\#202](https://github.com/SOCI/soci/issues/202) +- soci test projects for MSVC using cmake end up with wrong include path and linker error [\#177](https://github.com/SOCI/soci/issues/177) +- \#include "soci-backend.h" in sqlite3/soci-sqlite3.h [\#169](https://github.com/SOCI/soci/issues/169) +- double free [\#142](https://github.com/SOCI/soci/issues/142) +- Allow unknown toolsets in SociConfig.cmake [\#104](https://github.com/SOCI/soci/issues/104) +- Includes are wrong with make-install on Linux Mint [\#101](https://github.com/SOCI/soci/issues/101) +- Throw if truncation happens but no indicators was provided [\#51](https://github.com/SOCI/soci/issues/51) +- MySQL IEEE floating point representation [\#22](https://github.com/SOCI/soci/issues/22) + +**Merged pull requests:** + +- Repair VC8 compile of test applications [\#593](https://github.com/SOCI/soci/pull/593) ([hrabe](https://github.com/hrabe)) +- Replace C alternative boolean operator representations with primary [\#592](https://github.com/SOCI/soci/pull/592) ([mloskot](https://github.com/mloskot)) +- \[cmake\] Update FindPostgreSQL.cmake from CMake 3.9 [\#590](https://github.com/SOCI/soci/pull/590) ([mloskot](https://github.com/mloskot)) +- \[AppVeyor\] Add build job with Visual Studio 2017 [\#588](https://github.com/SOCI/soci/pull/588) ([mloskot](https://github.com/mloskot)) +- More XML/CLOB-related changes, including ODBC "support" [\#586](https://github.com/SOCI/soci/pull/586) ([vadz](https://github.com/vadz)) +- Enable Oracle builds for pull requests [\#585](https://github.com/SOCI/soci/pull/585) ([mloskot](https://github.com/mloskot)) +- Fix bug with null strings in bulk selections in ODBC and DB2 [\#583](https://github.com/SOCI/soci/pull/583) ([vadz](https://github.com/vadz)) +- Oracle fix empty string as null handling in the test [\#582](https://github.com/SOCI/soci/pull/582) ([vadz](https://github.com/vadz)) +- Restore Oracle build job on Travis CI [\#579](https://github.com/SOCI/soci/pull/579) ([mloskot](https://github.com/mloskot)) +- Add CLOB and XML support to Firebird backend [\#578](https://github.com/SOCI/soci/pull/578) ([vadz](https://github.com/vadz)) +- Move directory /bin to /scripts [\#577](https://github.com/SOCI/soci/pull/577) ([mloskot](https://github.com/mloskot)) +- Add support for EditorConfig [\#576](https://github.com/SOCI/soci/pull/576) ([alexott](https://github.com/alexott)) +- fix some of warnings from fresh Coverity report [\#572](https://github.com/SOCI/soci/pull/572) ([alexott](https://github.com/alexott)) +- Sanitizer fix & cmake support for build in superprojects [\#570](https://github.com/SOCI/soci/pull/570) ([mcencora](https://github.com/mcencora)) +- Update structure of SOCI 4.0.0 documentation [\#566](https://github.com/SOCI/soci/pull/566) ([mloskot](https://github.com/mloskot)) +- Some fix for SQLite3 backend [\#564](https://github.com/SOCI/soci/pull/564) ([Arenoros](https://github.com/Arenoros)) +- Fix handling of strings in ODBC backend bulk operations [\#561](https://github.com/SOCI/soci/pull/561) ([vadz](https://github.com/vadz)) +- Refactor code for parsing date/time to avoid duplication [\#559](https://github.com/SOCI/soci/pull/559) ([vadz](https://github.com/vadz)) +- Lazy initialization of the temporary LOB objects for Oracle. [\#557](https://github.com/SOCI/soci/pull/557) ([msobczak](https://github.com/msobczak)) +- Fix check for clang in CMake configuration [\#556](https://github.com/SOCI/soci/pull/556) ([vadz](https://github.com/vadz)) +- Work around MySQL ODBC driver bug [\#555](https://github.com/SOCI/soci/pull/555) ([vadz](https://github.com/vadz)) +- Consistently check for connection in the session object methods [\#548](https://github.com/SOCI/soci/pull/548) ([vadz](https://github.com/vadz)) +- Use defaults for unsupported types in ODBC backend [\#545](https://github.com/SOCI/soci/pull/545) ([snikulov](https://github.com/snikulov)) +- moved to latest Catch UT Framework [\#544](https://github.com/SOCI/soci/pull/544) ([snikulov](https://github.com/snikulov)) +- disable autosetting of SOCI\_CXX\_C11 to OFF [\#541](https://github.com/SOCI/soci/pull/541) ([ckaminski](https://github.com/ckaminski)) +- Pull request for issues 511 and 473 [\#536](https://github.com/SOCI/soci/pull/536) ([ddowling](https://github.com/ddowling)) +- Fixing uninitialized gotData\_ [\#535](https://github.com/SOCI/soci/pull/535) ([csteifel](https://github.com/csteifel)) +- Corrections in README.md [\#534](https://github.com/SOCI/soci/pull/534) ([mutcher](https://github.com/mutcher)) +- Firebird blob bug [\#524](https://github.com/SOCI/soci/pull/524) ([ravselj](https://github.com/ravselj)) +- Add JSON support to the MySQL backend [\#517](https://github.com/SOCI/soci/pull/517) ([ayllon](https://github.com/ayllon)) +- Add missing declspec to properly export exception classes [\#510](https://github.com/SOCI/soci/pull/510) ([mloskot](https://github.com/mloskot)) +- Added bigstring \(XML and CLOB\) support. [\#509](https://github.com/SOCI/soci/pull/509) ([msobczak](https://github.com/msobczak)) +- Added uniform offset for BLOB read/write operations. [\#508](https://github.com/SOCI/soci/pull/508) ([msobczak](https://github.com/msobczak)) +- export cmake package [\#503](https://github.com/SOCI/soci/pull/503) ([wisk](https://github.com/wisk)) +- Fix travis-ci build [\#494](https://github.com/SOCI/soci/pull/494) ([snikulov](https://github.com/snikulov)) +- Fixed \#468 [\#493](https://github.com/SOCI/soci/pull/493) ([snikulov](https://github.com/snikulov)) +- Fixed driver name and passwd for PostgreSQL ODBC driver on AppVeyor [\#492](https://github.com/SOCI/soci/pull/492) ([snikulov](https://github.com/snikulov)) +- Fixed MinGW build with proper URL on AppVeyor [\#491](https://github.com/SOCI/soci/pull/491) ([snikulov](https://github.com/snikulov)) +- MSSQL ODBC // fixed \#383 - long lines insert/select [\#489](https://github.com/SOCI/soci/pull/489) ([snikulov](https://github.com/snikulov)) +- Bulk iterators [\#487](https://github.com/SOCI/soci/pull/487) ([msobczak](https://github.com/msobczak)) +- Added failover callback for Oracle and PostgreSQL. [\#486](https://github.com/SOCI/soci/pull/486) ([msobczak](https://github.com/msobczak)) +- Added support for portable DDL statements. [\#484](https://github.com/SOCI/soci/pull/484) ([msobczak](https://github.com/msobczak)) +- Added singlerows mode for PostgreSQL. [\#482](https://github.com/SOCI/soci/pull/482) ([msobczak](https://github.com/msobczak)) +- Portable metadata queries. [\#480](https://github.com/SOCI/soci/pull/480) ([msobczak](https://github.com/msobczak)) +- fixed \#478 [\#479](https://github.com/SOCI/soci/pull/479) ([snikulov](https://github.com/snikulov)) +- coverity // Fixed uninited local std::tm structs [\#475](https://github.com/SOCI/soci/pull/475) ([snikulov](https://github.com/snikulov)) +- Adding example of a oracle connection to a remote server without need of tnsnames.ora [\#474](https://github.com/SOCI/soci/pull/474) ([wdavilaneto](https://github.com/wdavilaneto)) +- Travis-CI: allowed failure for DB2 backend [\#471](https://github.com/SOCI/soci/pull/471) ([snikulov](https://github.com/snikulov)) +- Appveyour now using MySQL 5.7 [\#470](https://github.com/SOCI/soci/pull/470) ([snikulov](https://github.com/snikulov)) +- Documentation. exchange.md cleaned from typos [\#459](https://github.com/SOCI/soci/pull/459) ([shelomentsevd](https://github.com/shelomentsevd)) +- compilation fix for msvc2012 [\#455](https://github.com/SOCI/soci/pull/455) ([snikulov](https://github.com/snikulov)) +- added Address Sanitizer to expose memory errors [\#454](https://github.com/SOCI/soci/pull/454) ([snikulov](https://github.com/snikulov)) +- updated catch.hpp version to 1.3.3 [\#453](https://github.com/SOCI/soci/pull/453) ([snikulov](https://github.com/snikulov)) +- Define cxx\_details::auto\_ptr\<\> to get rid of preprocessor checks [\#452](https://github.com/SOCI/soci/pull/452) ([vadz](https://github.com/vadz)) +- Include soci/soci-platform.h instead of soci-config.h [\#451](https://github.com/SOCI/soci/pull/451) ([vadz](https://github.com/vadz)) +- Avoid recursive inclusion between soci-{config,platform}.h [\#450](https://github.com/SOCI/soci/pull/450) ([vadz](https://github.com/vadz)) +- fixed \#447 [\#449](https://github.com/SOCI/soci/pull/449) ([snikulov](https://github.com/snikulov)) +- Handle MSVS 2015 \(and 2013, to some extent\) as C++11 compiler [\#448](https://github.com/SOCI/soci/pull/448) ([vadz](https://github.com/vadz)) +- CMake read version from version.h [\#446](https://github.com/SOCI/soci/pull/446) ([ArnaudD-FR](https://github.com/ArnaudD-FR)) +- postgres library built as static would not install unless also buildi… [\#443](https://github.com/SOCI/soci/pull/443) ([julien-lecomte](https://github.com/julien-lecomte)) +- DB2: Switch session from SQLConnect ot SQLDriverConnect [\#438](https://github.com/SOCI/soci/pull/438) ([mloskot](https://github.com/mloskot)) +- CMake generates soci/version.h [\#436](https://github.com/SOCI/soci/pull/436) ([ArnaudD-FR](https://github.com/ArnaudD-FR)) +- Specify definitions in soci-config.h [\#435](https://github.com/SOCI/soci/pull/435) ([ArnaudD-FR](https://github.com/ArnaudD-FR)) +- Add Vagrant configuration [\#434](https://github.com/SOCI/soci/pull/434) ([mloskot](https://github.com/mloskot)) +- \[SQLITE3\] Add reset API to reset sqlite3 statement [\#430](https://github.com/SOCI/soci/pull/430) ([ArnaudD-FR](https://github.com/ArnaudD-FR)) +- reset count of affected rows \(sqlite3\) [\#428](https://github.com/SOCI/soci/pull/428) ([MonsieurNicolas](https://github.com/MonsieurNicolas)) +- Fixed Coverity issues for SQLite3: 12644, 12645, 12646 [\#427](https://github.com/SOCI/soci/pull/427) ([snikulov](https://github.com/snikulov)) +- Enabled mysql/postgresql odbc tests - fixed \#407 [\#426](https://github.com/SOCI/soci/pull/426) ([snikulov](https://github.com/snikulov)) +- moved SOCI\_CXX\_C11 define to common section [\#425](https://github.com/SOCI/soci/pull/425) ([snikulov](https://github.com/snikulov)) +- Clang: fixed \#374 [\#422](https://github.com/SOCI/soci/pull/422) ([snikulov](https://github.com/snikulov)) +- add ignore of cmake generated dsn file for access [\#421](https://github.com/SOCI/soci/pull/421) ([jeking3](https://github.com/jeking3)) +- add a unit test for the postgresql uuid data type [\#420](https://github.com/SOCI/soci/pull/420) ([jeking3](https://github.com/jeking3)) +- Travis-CI: added valgrind check [\#419](https://github.com/SOCI/soci/pull/419) ([snikulov](https://github.com/snikulov)) +- fixed get\_affected\_rows\(\) to pass test thats broken since \#414 [\#418](https://github.com/SOCI/soci/pull/418) ([hw-dwalter](https://github.com/hw-dwalter)) +- coverity: updated .travis.yml [\#417](https://github.com/SOCI/soci/pull/417) ([snikulov](https://github.com/snikulov)) +- Travis-CI: try add Coverity tool [\#416](https://github.com/SOCI/soci/pull/416) ([snikulov](https://github.com/snikulov)) +- AppVeyor: enable MSSQL ODBC connection [\#415](https://github.com/SOCI/soci/pull/415) ([snikulov](https://github.com/snikulov)) +- added test to demonstrate bug \#221 [\#414](https://github.com/SOCI/soci/pull/414) ([hw-dwalter](https://github.com/hw-dwalter)) +- Appveyor: added build for MySQL backend [\#410](https://github.com/SOCI/soci/pull/410) ([snikulov](https://github.com/snikulov)) +- Appveyor: added PosgreSQL 9.4 backend [\#409](https://github.com/SOCI/soci/pull/409) ([snikulov](https://github.com/snikulov)) +- Appveyor: Added SQLite to build [\#408](https://github.com/SOCI/soci/pull/408) ([snikulov](https://github.com/snikulov)) +- appvr: added db services to build workers [\#406](https://github.com/SOCI/soci/pull/406) ([snikulov](https://github.com/snikulov)) +- ODBC: test updates [\#405](https://github.com/SOCI/soci/pull/405) ([snikulov](https://github.com/snikulov)) +- fixed typo [\#403](https://github.com/SOCI/soci/pull/403) ([DraconPern](https://github.com/DraconPern)) +- Name struct to avoid clang error/warning \(issue \#374\) [\#402](https://github.com/SOCI/soci/pull/402) ([ArnaudD-FR](https://github.com/ArnaudD-FR)) +- Appveyor: added Boost libraries to build [\#401](https://github.com/SOCI/soci/pull/401) ([snikulov](https://github.com/snikulov)) +- fix for \#370 [\#400](https://github.com/SOCI/soci/pull/400) ([snikulov](https://github.com/snikulov)) +- Update README.md [\#399](https://github.com/SOCI/soci/pull/399) ([snikulov](https://github.com/snikulov)) +- mingw: not use ms extension for MinGW G++ compilation [\#398](https://github.com/SOCI/soci/pull/398) ([snikulov](https://github.com/snikulov)) +- cmake: generate verbose build [\#397](https://github.com/SOCI/soci/pull/397) ([snikulov](https://github.com/snikulov)) +- connection-pool: moved common part out of \#ifdef [\#395](https://github.com/SOCI/soci/pull/395) ([snikulov](https://github.com/snikulov)) +- Restore using -pedantic but use -Wno-pedantic-ms-format too [\#392](https://github.com/SOCI/soci/pull/392) ([vadz](https://github.com/vadz)) +- initial integration with AppVeyor-CI [\#391](https://github.com/SOCI/soci/pull/391) ([snikulov](https://github.com/snikulov)) +- boost: disable autolink, because soci controls required libs itself [\#389](https://github.com/SOCI/soci/pull/389) ([snikulov](https://github.com/snikulov)) +- cmake: updated FindODBC.cmake to search right sqlext.h on Windows [\#387](https://github.com/SOCI/soci/pull/387) ([snikulov](https://github.com/snikulov)) +- sqlite3\_close moved after sqlite3\_errmsg is called. [\#381](https://github.com/SOCI/soci/pull/381) ([ravselj](https://github.com/ravselj)) +- Build system: Replacing CMAKE\_SOURCE\_DIR with CMAKE\_CURRENT\_SOURCE\_DIR [\#380](https://github.com/SOCI/soci/pull/380) ([dgrafe](https://github.com/dgrafe)) +- Memory leak fix in sqlite3 backend [\#378](https://github.com/SOCI/soci/pull/378) ([ravselj](https://github.com/ravselj)) +- CMake debug postfix [\#376](https://github.com/SOCI/soci/pull/376) ([ravselj](https://github.com/ravselj)) +- Sqlite backend fetch bug [\#375](https://github.com/SOCI/soci/pull/375) ([ravselj](https://github.com/ravselj)) +- Fixed ambiguous 'session' reference [\#373](https://github.com/SOCI/soci/pull/373) ([musopr](https://github.com/musopr)) +- Include SOCI\_CXX\_VERSION\_FLAGS when compiling with Clang [\#372](https://github.com/SOCI/soci/pull/372) ([musopr](https://github.com/musopr)) +- CMake changes [\#368](https://github.com/SOCI/soci/pull/368) ([ravselj](https://github.com/ravselj)) +- MSVC warnings in SQLite backend [\#365](https://github.com/SOCI/soci/pull/365) ([ravselj](https://github.com/ravselj)) +- Fix connection-parameters options bug [\#364](https://github.com/SOCI/soci/pull/364) ([ravselj](https://github.com/ravselj)) +- Allow setting ORACLE\_HOME directly in CMake GUI [\#363](https://github.com/SOCI/soci/pull/363) ([ravselj](https://github.com/ravselj)) +- Sqlite3 optimization \[\#2\] [\#362](https://github.com/SOCI/soci/pull/362) ([ArnaudD-FR](https://github.com/ArnaudD-FR)) +- Add dt\_blob [\#361](https://github.com/SOCI/soci/pull/361) ([ArnaudD-FR](https://github.com/ArnaudD-FR)) +- Split Statement::clean\_up into bind\_clean\_up and clean\_up [\#358](https://github.com/SOCI/soci/pull/358) ([ArnaudD-FR](https://github.com/ArnaudD-FR)) +- Improve ODBC error messages. [\#357](https://github.com/SOCI/soci/pull/357) ([vadz](https://github.com/vadz)) +- Set CMake variable SOCI\_CORE\_TARGET at PARENT\_SCOPE [\#352](https://github.com/SOCI/soci/pull/352) ([ravselj](https://github.com/ravselj)) +- Updated classic Makefile for PostgreSQL backend. [\#348](https://github.com/SOCI/soci/pull/348) ([msobczak](https://github.com/msobczak)) +- Make it easier to override SOCI\_LIBDIR. [\#347](https://github.com/SOCI/soci/pull/347) ([jsonn](https://github.com/jsonn)) +- Updated classic Makefiles for Oracle Express 11.2. [\#346](https://github.com/SOCI/soci/pull/346) ([msobczak](https://github.com/msobczak)) +- Markdown Documentation [\#344](https://github.com/SOCI/soci/pull/344) ([OniDaito](https://github.com/OniDaito)) +- Minor C++11 Pragma bugfix and Inlining C++11 test pragmas [\#343](https://github.com/SOCI/soci/pull/343) ([OniDaito](https://github.com/OniDaito)) +- Fully qualify uses of session class with namespace soci::session [\#341](https://github.com/SOCI/soci/pull/341) ([mloskot](https://github.com/mloskot)) +- Added the C++11 changes back in. Replaces \#327 [\#336](https://github.com/SOCI/soci/pull/336) ([OniDaito](https://github.com/OniDaito)) +- Add -Wl,-flat\_namespace -Wl,-undefined -Wl,suppress to LINK\_FLAGS on Apple/OSX [\#335](https://github.com/SOCI/soci/pull/335) ([mloskot](https://github.com/mloskot)) +- Restore Oracle setup on Travis CI server [\#326](https://github.com/SOCI/soci/pull/326) ([mloskot](https://github.com/mloskot)) +- Use parameter names from query in the error messages. [\#318](https://github.com/SOCI/soci/pull/318) ([vadz](https://github.com/vadz)) +- Small cleanup remaining from the attempt to fix timezone handling [\#316](https://github.com/SOCI/soci/pull/316) ([vadz](https://github.com/vadz)) +- Updated FindMySQL.cmake to work under 64-bit windows. [\#314](https://github.com/SOCI/soci/pull/314) ([piotaixr](https://github.com/piotaixr)) +- Don't add DB2\_INCLUDE\_DIR to global include directories. [\#313](https://github.com/SOCI/soci/pull/313) ([vadz](https://github.com/vadz)) +- Add postgression service to Travis CI setup [\#306](https://github.com/SOCI/soci/pull/306) ([mloskot](https://github.com/mloskot)) +- Merging master \(SOCI 3.2.3\) into develop [\#304](https://github.com/SOCI/soci/pull/304) ([mloskot](https://github.com/mloskot)) + +## [3.2.3](https://github.com/SOCI/soci/tree/3.2.3) (2015-04-08) +[Full Changelog](https://github.com/SOCI/soci/compare/3.2.2...3.2.3) + +**Closed issues:** + +- I want adaptable dumper. [\#299](https://github.com/SOCI/soci/issues/299) +- ODBC Connection parameter SQL\_DRIVER\_NOPROMPT throws std::logic\_error [\#273](https://github.com/SOCI/soci/issues/273) +- Problems with UTF-8 encoding while using std::strings in soci::use [\#269](https://github.com/SOCI/soci/issues/269) +- Build failure on Visual C++ 2013 due to strtoll\(\) and strtoull\(\) functions [\#262](https://github.com/SOCI/soci/issues/262) +- soci odbi-backend spelling error Commiting -\> Committing [\#260](https://github.com/SOCI/soci/issues/260) +- Assignment operator := needs to be treated specially when parsing the MySQL query [\#259](https://github.com/SOCI/soci/issues/259) +- ORA-00932: inconsistent datatypes: expected NUMBER got TIMESTAMP [\#252](https://github.com/SOCI/soci/issues/252) +- Oracle Connection to Cluster via TNS description [\#250](https://github.com/SOCI/soci/issues/250) +- can't generate vc7.1 project [\#227](https://github.com/SOCI/soci/issues/227) +- Memory leak after using open/creating soci::session with std::string [\#222](https://github.com/SOCI/soci/issues/222) +- Uncaughtable exception [\#219](https://github.com/SOCI/soci/issues/219) +- SOCI\_LIBDIR ends up being hard-coded to 'lib64' [\#212](https://github.com/SOCI/soci/issues/212) +- Add SOCI to Upstream Tracker [\#210](https://github.com/SOCI/soci/issues/210) +- Set CMake 2.8.8 as minimum required [\#200](https://github.com/SOCI/soci/issues/200) +- CMake 2.8.7 cannot set include\_directories per target [\#199](https://github.com/SOCI/soci/issues/199) +- Set up postgression service for Travis CI [\#167](https://github.com/SOCI/soci/issues/167) +- Implement new source tree layout [\#125](https://github.com/SOCI/soci/issues/125) +- Buried headers and includes cleanup [\#25](https://github.com/SOCI/soci/issues/25) +- Deadlock in non-windows implementation of soci::connection\_pool::try\_lease [\#284](https://github.com/SOCI/soci/issues/284) +- Patch for Visual Studio 2013 \(VC12\) [\#272](https://github.com/SOCI/soci/issues/272) +- Patch for using Oracle Instantclient v. 12 [\#271](https://github.com/SOCI/soci/issues/271) +- Avoid exception from destructor while stack unwinding [\#256](https://github.com/SOCI/soci/issues/256) +- git flow hotfix finish 3.2.3 [\#235](https://github.com/SOCI/soci/issues/235) +- git flow hotfix start 3.2.3 [\#234](https://github.com/SOCI/soci/issues/234) +- Compiling SOCI on Visual Studio 2013 Express [\#233](https://github.com/SOCI/soci/issues/233) +- Add git-flow support branch for 3.2 releases [\#206](https://github.com/SOCI/soci/issues/206) +- Clarify bulk examples [\#204](https://github.com/SOCI/soci/issues/204) + +**Merged pull requests:** + +- Provide error context in the exceptions. [\#302](https://github.com/SOCI/soci/pull/302) ([vadz](https://github.com/vadz)) +- Add helper exchange\_type\_cast\<\>\(\) template function. [\#301](https://github.com/SOCI/soci/pull/301) ([vadz](https://github.com/vadz)) +- Use a base-class member instead of shadowing it. [\#297](https://github.com/SOCI/soci/pull/297) ([nbougalis](https://github.com/nbougalis)) +- Fix for the issue \#169: cleaner way to include headers. [\#294](https://github.com/SOCI/soci/pull/294) ([denisarnaud](https://github.com/denisarnaud)) +- Don't start implicit transaction too eagerly in Firebird backend. [\#292](https://github.com/SOCI/soci/pull/292) ([vadz](https://github.com/vadz)) +- Fix PostgreSQL unit test to pass with PostgreSQL \< 9.0. [\#242](https://github.com/SOCI/soci/pull/242) ([vadz](https://github.com/vadz)) +- Include all public headers using "soci/" prefix inside SOCI itself. [\#239](https://github.com/SOCI/soci/pull/239) ([vadz](https://github.com/vadz)) +- Add helper cstring\_to\_double\(\) and use it in PostgreSQL backend. [\#238](https://github.com/SOCI/soci/pull/238) ([vadz](https://github.com/vadz)) +- Fix compilation of ODBC-specific SOCI header with new include paths. [\#237](https://github.com/SOCI/soci/pull/237) ([vadz](https://github.com/vadz)) +- Add get\_last\_insert\_id for sqlite3 and mysql backends [\#216](https://github.com/SOCI/soci/pull/216) ([dgrambow](https://github.com/dgrambow)) +- Fix in soci::oracle to allow spaces in the params [\#213](https://github.com/SOCI/soci/pull/213) ([ayllon](https://github.com/ayllon)) +- Adding cmake SOCI\_SHARED option and lowercasing windows.h [\#286](https://github.com/SOCI/soci/pull/286) ([bingmann](https://github.com/bingmann)) +- fixed deadlock in soci::connection\_pool::try\_lease [\#285](https://github.com/SOCI/soci/pull/285) ([javerskulpa](https://github.com/javerskulpa)) +- Add missing include for std::max [\#278](https://github.com/SOCI/soci/pull/278) ([mika-fischer](https://github.com/mika-fischer)) +- Two enhancements for SOCI 3.2.3 hotfix, PostgreSQL UUID column support and SQLITE3 error code in exception [\#263](https://github.com/SOCI/soci/pull/263) ([Alex-Vol](https://github.com/Alex-Vol)) +- Spelling fix Comiting -\> Comitting \(Fixes \#260\) [\#261](https://github.com/SOCI/soci/pull/261) ([coldtobi](https://github.com/coldtobi)) +- vs2013 got strtoll/strtoull [\#217](https://github.com/SOCI/soci/pull/217) ([fly2xj](https://github.com/fly2xj)) + +## [3.2.2](https://github.com/SOCI/soci/tree/3.2.2) (2013-09-10) +[Full Changelog](https://github.com/SOCI/soci/compare/3.2.1...3.2.2) + +**Closed issues:** + +- sqlite3 backend abstract on build [\#139](https://github.com/SOCI/soci/issues/139) +- sqlite3: sqlite3\_prepare will return SQLITE\_SCHEMA, please use sqlite3\_prepare\_v2 [\#188](https://github.com/SOCI/soci/issues/188) +- CMake option SQLITE3\_LIBRARIES not handled correctly [\#182](https://github.com/SOCI/soci/issues/182) +- noexcept on once\_temp\_type [\#181](https://github.com/SOCI/soci/issues/181) +- git flow hotfix finish 3.2.2 [\#180](https://github.com/SOCI/soci/issues/180) +- Wrong GCC\_VERSION for commandline-overriden GCC [\#178](https://github.com/SOCI/soci/issues/178) +- Separate Travis CI builds per backend [\#171](https://github.com/SOCI/soci/issues/171) +- MSVC build of ODBC with UNICODE enabled fails on error handling [\#163](https://github.com/SOCI/soci/issues/163) +- git flow hotfix start 3.2.2 [\#159](https://github.com/SOCI/soci/issues/159) +- Calling undefine\_and\_bind and then define\_and\_bind causes a leak. [\#154](https://github.com/SOCI/soci/issues/154) +- Uninitialized soci::indicator values in type conversion specialisations [\#152](https://github.com/SOCI/soci/issues/152) +- Incorrect sscanf format strings on windows [\#149](https://github.com/SOCI/soci/issues/149) +- db2\_standard\_use\_type\_backend don't detect correctly size changes in string parameters [\#141](https://github.com/SOCI/soci/issues/141) +- db2\_standard\_use\_type\_backend doesn't detect correctly size changes in parameters [\#140](https://github.com/SOCI/soci/issues/140) +- Problem doing a cast because of the colon parsing [\#121](https://github.com/SOCI/soci/issues/121) +- Documentation for specializing type\_conversion\<\> doesn't fully explain indicators [\#102](https://github.com/SOCI/soci/issues/102) +- get\_affected\_rows\(\) returns -1 for bulk operations [\#83](https://github.com/SOCI/soci/issues/83) +- Undefined behaviour when use unitialized indicator [\#28](https://github.com/SOCI/soci/issues/28) + +**Merged pull requests:** + +- Handle SQLITE3\_LIBRARIES by find\_package\_handle\_standard\_args [\#193](https://github.com/SOCI/soci/pull/193) ([mloskot](https://github.com/mloskot)) +- Specify once\_temp\_type dtor with noexcept\(false\) [\#192](https://github.com/SOCI/soci/pull/192) ([mloskot](https://github.com/mloskot)) +- Replace sqlite3\_prepare with sqlite3\_prepare\_v2 [\#191](https://github.com/SOCI/soci/pull/191) ([mloskot](https://github.com/mloskot)) +- fixed wrong size parameter in final memcpy\(\) [\#185](https://github.com/SOCI/soci/pull/185) ([ghost](https://github.com/ghost)) +- In Unix System cmake could not find ORACLE\_NNZ\_LIBRARY for Oracle 12g.li... [\#183](https://github.com/SOCI/soci/pull/183) ([cngzhnp](https://github.com/cngzhnp)) +- Travis matrix builds [\#174](https://github.com/SOCI/soci/pull/174) ([mloskot](https://github.com/mloskot)) +- Explicitly include stdarg.h pulled in by namespace-protected sqlite.h. [\#172](https://github.com/SOCI/soci/pull/172) ([jsonn](https://github.com/jsonn)) +- A more compatible test for issue \#154, make firebird run it. [\#165](https://github.com/SOCI/soci/pull/165) ([ricardofandrade](https://github.com/ricardofandrade)) +- ODBC: Reviewed the local use of the rows\_processed [\#161](https://github.com/SOCI/soci/pull/161) ([ricardofandrade](https://github.com/ricardofandrade)) +- Fixes the compilation of the firebird tests with the backend as a DLL. [\#147](https://github.com/SOCI/soci/pull/147) ([ricardofandrade](https://github.com/ricardofandrade)) +- Handle PostgreSQL's UNKNOWNOID type as a string into row [\#135](https://github.com/SOCI/soci/pull/135) ([ricardofandrade](https://github.com/ricardofandrade)) +- Don't allow exceptions escape from postgresql\_statement\_backend dtor. [\#134](https://github.com/SOCI/soci/pull/134) ([vadz](https://github.com/vadz)) +- Implement session::get\_next\_sequence\_value\(\) for PostgreSQL backend. [\#133](https://github.com/SOCI/soci/pull/133) ([vadz](https://github.com/vadz)) +- Fix harmless warning about signed/unsigned comparison. [\#132](https://github.com/SOCI/soci/pull/132) ([vadz](https://github.com/vadz)) +- Some trivial optimizations in ODBC statement preparation code. [\#111](https://github.com/SOCI/soci/pull/111) ([vadz](https://github.com/vadz)) + +## [3.2.1](https://github.com/SOCI/soci/tree/3.2.1) (2013-04-12) +[Full Changelog](https://github.com/SOCI/soci/compare/3.2.0...3.2.1) + +**Closed issues:** + +- MySQL Backend issues on Mac OS X [\#130](https://github.com/SOCI/soci/issues/130) +- git flow hotfix finish 3.2.1 [\#131](https://github.com/SOCI/soci/issues/131) +- Incorrect usage of numeric\_limits trait in Oracle backed [\#126](https://github.com/SOCI/soci/issues/126) +- SOCI 3.2.0 does not build on Windows [\#123](https://github.com/SOCI/soci/issues/123) +- Clean up names and use of SOCI\_POSTGRESQL\_NOPARAMS [\#120](https://github.com/SOCI/soci/issues/120) +- Prepared insert statement with data exchange and custom type does not work [\#117](https://github.com/SOCI/soci/issues/117) +- deallocate\_prepared\_statement called for non-existing statement [\#116](https://github.com/SOCI/soci/issues/116) +- Query transformation not working with sessions from pool [\#113](https://github.com/SOCI/soci/issues/113) +- Add common tests to DB2 [\#94](https://github.com/SOCI/soci/issues/94) + +**Merged pull requests:** + +- DB2 backend patches [\#97](https://github.com/SOCI/soci/pull/97) ([toonen](https://github.com/toonen)) + +## [3.2.0](https://github.com/SOCI/soci/tree/3.2.0) (2013-03-26) +[Full Changelog](https://github.com/SOCI/soci/compare/3.1.0...3.2.0) + +**Closed issues:** + +- Run time error: what\(\): Malformed connection string. [\#20](https://github.com/SOCI/soci/issues/20) +- Unexpected behavior when making multiple calls to rowset's begin\(\) and end\(\) [\#17](https://github.com/SOCI/soci/issues/17) +- git flow finish release 3.2.0 [\#109](https://github.com/SOCI/soci/issues/109) +- Run common tests against DB2 [\#106](https://github.com/SOCI/soci/issues/106) +- Re-enable g++ testing on travis-ci [\#96](https://github.com/SOCI/soci/issues/96) +- Update CHANGES for 3.2.0 [\#93](https://github.com/SOCI/soci/issues/93) +- Add testing against clang on travis-ci [\#92](https://github.com/SOCI/soci/issues/92) +- Mark Makefile.basic as deprecated [\#91](https://github.com/SOCI/soci/issues/91) +- Build failure with Oracle Instant Client 11.2.0.3.0 [\#89](https://github.com/SOCI/soci/issues/89) +- SOCI\_COMPILER\_NAME not determined for VS2012 [\#87](https://github.com/SOCI/soci/issues/87) +- postgresql backend leaks a PGResult\* when an exception is thrown [\#86](https://github.com/SOCI/soci/issues/86) +- Update documentation for 3.2.0 release [\#85](https://github.com/SOCI/soci/issues/85) +- Binding procedure IN/OUT parameter has no out effect [\#81](https://github.com/SOCI/soci/issues/81) +- Add CMake option SOCI\_STATIC [\#80](https://github.com/SOCI/soci/issues/80) +- Patch with DB2 backend [\#70](https://github.com/SOCI/soci/issues/70) +- Allocated statement backend memory leaks on exception [\#67](https://github.com/SOCI/soci/issues/67) +- Add query transformation callback feature [\#66](https://github.com/SOCI/soci/issues/66) +- Add Firebird support to Travis-CI configuration [\#64](https://github.com/SOCI/soci/issues/64) +- Add missing ODBC tests to CMake configuration [\#62](https://github.com/SOCI/soci/issues/62) +- Add CMake configuration for Firebird [\#57](https://github.com/SOCI/soci/issues/57) +- Add ODBC support to Travis-CI configuration [\#49](https://github.com/SOCI/soci/issues/49) +- Incorrect assertion in postgresql::get\_error\_details [\#48](https://github.com/SOCI/soci/issues/48) +- Update code examples [\#47](https://github.com/SOCI/soci/issues/47) +- Placeholder at the last position of statement causing crash [\#44](https://github.com/SOCI/soci/issues/44) +- warning: comparing floating point with == or != is unsafe [\#42](https://github.com/SOCI/soci/issues/42) +- Bump SOCI version in master to 3.2.0 [\#39](https://github.com/SOCI/soci/issues/39) +- FindODBC.cmake deviates from soci\_backend\(\) conventions [\#32](https://github.com/SOCI/soci/issues/32) +- Building with Visual Studio 2010: identifier strtoull not found [\#27](https://github.com/SOCI/soci/issues/27) +- CMake ignores -DWITH\_\ option [\#23](https://github.com/SOCI/soci/issues/23) +- make install - Fedora 17 [\#21](https://github.com/SOCI/soci/issues/21) +- Server-side memory leak in PostgreSQL [\#19](https://github.com/SOCI/soci/issues/19) +- Apply Git workflow: GitFlow [\#18](https://github.com/SOCI/soci/issues/18) +- FindMySQL.cmake update for custom mysqlclient location [\#16](https://github.com/SOCI/soci/issues/16) +- Premature deallocation of prepared statement [\#13](https://github.com/SOCI/soci/issues/13) + +**Merged pull requests:** + +- Fix int and long mismatch for vector in ODBC [\#103](https://github.com/SOCI/soci/pull/103) ([mloskot](https://github.com/mloskot)) +- Fix OUT direction of IN/OUT procedure parameter [\#88](https://github.com/SOCI/soci/pull/88) ([mloskot](https://github.com/mloskot)) +- Implementation of query transformation callback feature [\#82](https://github.com/SOCI/soci/pull/82) ([mloskot](https://github.com/mloskot)) +- Fix Firebird tests: test\_get\_affected\_rows\(\) [\#74](https://github.com/SOCI/soci/pull/74) ([vnaydionov](https://github.com/vnaydionov)) +- Fix connection parsing in SOCI Firebird backend to handle spaces in values. [\#73](https://github.com/SOCI/soci/pull/73) ([vadz](https://github.com/vadz)) +- Firebird backend specific: add option to fetch decimals as strings [\#71](https://github.com/SOCI/soci/pull/71) ([vnaydionov](https://github.com/vnaydionov)) +- Improve fixes in pull \#5 [\#69](https://github.com/SOCI/soci/pull/69) ([mloskot](https://github.com/mloskot)) +- Fix memory leak of statement backend \(issue \#67\) [\#68](https://github.com/SOCI/soci/pull/68) ([mloskot](https://github.com/mloskot)) +- Improve input parameter binding in Firebird backend. [\#65](https://github.com/SOCI/soci/pull/65) ([vnaydionov](https://github.com/vnaydionov)) +- Add missing ODBC tests to CMake configuration \(issue \#62\) [\#63](https://github.com/SOCI/soci/pull/63) ([mloskot](https://github.com/mloskot)) +- Add PostgreSQL test for bytea to pull request \#46 [\#61](https://github.com/SOCI/soci/pull/61) ([mloskot](https://github.com/mloskot)) +- Added CMake scripts for Firebird backend and fixed it to pass all the tests [\#60](https://github.com/SOCI/soci/pull/60) ([vnaydionov](https://github.com/vnaydionov)) +- Fix to build the Firebird backend [\#56](https://github.com/SOCI/soci/pull/56) ([vnaydionov](https://github.com/vnaydionov)) +- Properly compute LIBDIR on \(modern\) Debian [\#55](https://github.com/SOCI/soci/pull/55) ([hasselmm](https://github.com/hasselmm)) +- Support reading bytea data in PostgreSQL backend as another string type [\#46](https://github.com/SOCI/soci/pull/46) ([hobu](https://github.com/hobu)) +- Patch received from Daniel Beaudoin \(Optel Vision\) on 23/07/2012\) [\#45](https://github.com/SOCI/soci/pull/45) ([mloskot](https://github.com/mloskot)) +- For Visual C++, defined std::strtoll in terms of \_strtoi64\(\) [\#43](https://github.com/SOCI/soci/pull/43) ([mloskot](https://github.com/mloskot)) +- Bumped version number to 3.2.0 [\#40](https://github.com/SOCI/soci/pull/40) ([mloskot](https://github.com/mloskot)) +- handle WITH\_{dependency} switch to turn on/off deps [\#38](https://github.com/SOCI/soci/pull/38) ([snikulov](https://github.com/snikulov)) +- qualify strtoull/strtoll with std namespace [\#37](https://github.com/SOCI/soci/pull/37) ([avg-I](https://github.com/avg-I)) +- FindSoci.cmake: fix couple of minor issues [\#36](https://github.com/SOCI/soci/pull/36) ([avg-I](https://github.com/avg-I)) +- FindODBC.cmake: rename ODBC\_INCLUDE\_DIRECTORIES to ODBC\_INCLUDE\_DIR [\#35](https://github.com/SOCI/soci/pull/35) ([avg-I](https://github.com/avg-I)) +- README update and minimal CMake version update [\#34](https://github.com/SOCI/soci/pull/34) ([mloskot](https://github.com/mloskot)) +- backends/oracle: support working in multithreaded programs [\#31](https://github.com/SOCI/soci/pull/31) ([avg-I](https://github.com/avg-I)) +- Set INSTALL\_NAME\_DIR as used on OS X [\#30](https://github.com/SOCI/soci/pull/30) ([arsenm](https://github.com/arsenm)) +- cmake: FindSQLite3 - add handling of SQLITE\_ROOT\_DIR [\#29](https://github.com/SOCI/soci/pull/29) ([snikulov](https://github.com/snikulov)) +- Fix proposal for issue SOCI/soci\#13 [\#14](https://github.com/SOCI/soci/pull/14) ([mloskot](https://github.com/mloskot)) +- This makes possible to disable empty backend build [\#11](https://github.com/SOCI/soci/pull/11) ([krieger-od](https://github.com/krieger-od)) +- OSX clang std::getline bug [\#10](https://github.com/SOCI/soci/pull/10) ([hobu](https://github.com/hobu)) +- cmake: updated backends build [\#8](https://github.com/SOCI/soci/pull/8) ([snikulov](https://github.com/snikulov)) +- SQLite database path may contain spaces if enclosed in quotes. [\#6](https://github.com/SOCI/soci/pull/6) ([belobrov-andrey](https://github.com/belobrov-andrey)) +- Backend is not set properly with connection pool [\#5](https://github.com/SOCI/soci/pull/5) ([ghost](https://github.com/ghost)) +- Fixed a compile error in FreeBSD 8.2, using the lates GCC version 4.2.1 [\#2](https://github.com/SOCI/soci/pull/2) ([jamercee](https://github.com/jamercee)) +- Implement get\_affected\_rows for SQLite3 backend [\#1](https://github.com/SOCI/soci/pull/1) ([mika-fischer](https://github.com/mika-fischer)) + +## [3.1.0](https://github.com/SOCI/soci/tree/3.1.0) (2011-10-20) +[Full Changelog](https://github.com/SOCI/soci/compare/v3.1.0...3.1.0) + +## [v3.1.0](https://github.com/SOCI/soci/tree/v3.1.0) (2011-10-20) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-3.1.0.zip...v3.1.0) + +## [soci-3.1.0.zip](https://github.com/SOCI/soci/tree/soci-3.1.0.zip) (2011-10-08) +[Full Changelog](https://github.com/SOCI/soci/compare/v3.0.0...soci-3.1.0.zip) + +## [v3.0.0](https://github.com/SOCI/soci/tree/v3.0.0) (2008-07-09) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-3.0.0.zip...v3.0.0) + +## [soci-3.0.0.zip](https://github.com/SOCI/soci/tree/soci-3.0.0.zip) (2008-07-08) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-2.2.0.zip...soci-3.0.0.zip) + +## [soci-2.2.0.zip](https://github.com/SOCI/soci/tree/soci-2.2.0.zip) (2006-12-02) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-2.1.0.zip...soci-2.2.0.zip) + +## [soci-2.1.0.zip](https://github.com/SOCI/soci/tree/soci-2.1.0.zip) (2006-05-12) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-2.0.1.zip...soci-2.1.0.zip) + +## [soci-2.0.1.zip](https://github.com/SOCI/soci/tree/soci-2.0.1.zip) (2006-02-15) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-2.0.0.zip...soci-2.0.1.zip) + +## [soci-2.0.0.zip](https://github.com/SOCI/soci/tree/soci-2.0.0.zip) (2006-01-15) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-1.2.1.zip...soci-2.0.0.zip) + +## [soci-1.2.1.zip](https://github.com/SOCI/soci/tree/soci-1.2.1.zip) (2005-10-13) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-1.2.0.zip...soci-1.2.1.zip) + +## [soci-1.2.0.zip](https://github.com/SOCI/soci/tree/soci-1.2.0.zip) (2005-10-02) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-1.1.0.zip...soci-1.2.0.zip) + +## [soci-1.1.0.zip](https://github.com/SOCI/soci/tree/soci-1.1.0.zip) (2005-02-27) +[Full Changelog](https://github.com/SOCI/soci/compare/soci-1.0.1.zip...soci-1.1.0.zip) + +## [soci-1.0.1.zip](https://github.com/SOCI/soci/tree/soci-1.0.1.zip) (2004-10-16) + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/CHANGES b/CHANGES index b66729305d..6e664b99b3 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,7 @@ Version 4.0.0 differs from 3.2.x in the following ways: - Provide context of the failure in soci_error::what() which now returns a longer and more useful message. Use the new get_error_message() method to get just the brief error message which used to be returned by what(). +- Add helpers for generating portable DDL and DML statements. - Firebird -- Add SOCI_FIREBIRD_EMBEDDED option to allow building with embedded library. diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d686b2f74..7798b45fe9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,20 @@ ############################################################################### # General settings ############################################################################### -cmake_minimum_required(VERSION 2.8.0 FATAL_ERROR) +cmake_minimum_required(VERSION 2.8.10 FATAL_ERROR) project(SOCI) +############################################################################### +# Build features and variants +############################################################################## + +option(SOCI_SHARED "Enable build of shared libraries" ON) +option(SOCI_STATIC "Enable build of static libraries" ON) +option(SOCI_TESTS "Enable build of collection of SOCI tests" ON) +option(SOCI_ASAN "Enable address sanitizer on GCC v4.8+/Clang v 3.1+" OFF) + + ############################################################################### # SOCI CMake modules ############################################################################### @@ -32,24 +42,20 @@ colormsg(_HIBLUE_ "Configuring SOCI:") ############################################################################### include(SociVersion) -# The version here should be in sync with the one in include/soci/version.h. -soci_version(MAJOR 4 MINOR 0 PATCH 0) +soci_version() ############################################################################### # Build features and variants ############################################################################## -option(SOCI_SHARED "Enable build of shared libraries" ON) boost_report_value(SOCI_SHARED) - -option(SOCI_STATIC "Enable build of static libraries" ON) boost_report_value(SOCI_STATIC) - -option(SOCI_TESTS "Enable build of collection of SOCI tests" ON) boost_report_value(SOCI_TESTS) +boost_report_value(SOCI_ASAN) # from SociConfig.cmake boost_report_value(SOCI_CXX_C11) +boost_report_value(LIB_SUFFIX) # Put the libaries and binaries that get built into directories at the # top of the build tree rather than in hard-to-find leaf @@ -69,7 +75,7 @@ set(SOCI_CORE_DEPS_LIBS) include(SociDependencies) -get_property(SOCI_INCLUDE_DIRS DIRECTORY ${CMAKE_SOURCE_DIR} +get_property(SOCI_INCLUDE_DIRS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) if(Threads_FOUND) @@ -86,21 +92,20 @@ if(NOT MSVC) set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES ${DL_INCLUDE_DIR}) add_definitions(-DHAVE_DL=1) endif() -else() #MSVC - # This flag enables multi process compiling for Visual Studio 2005 and above - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") endif() if(Boost_FOUND) get_property(SOCI_COMPILE_DEFINITIONS - DIRECTORY ${CMAKE_SOURCE_DIR} + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY COMPILE_DEFINITIONS) - list(APPEND SOCI_COMPILE_DEFINITIONS "HAVE_BOOST=1") + set(SOCI_HAVE_BOOST ON) + + list(APPEND SOCI_COMPILE_DEFINITIONS "BOOST_ALL_NO_LIB") if(Boost_DATE_TIME_FOUND) list(APPEND SOCI_CORE_DEPS_LIBS ${Boost_DATE_TIME_LIBRARY}) - list(APPEND SOCI_COMPILE_DEFINITIONS "HAVE_BOOST_DATE_TIME=1") + set(SOCI_HAVE_BOOST_DATE_TIME ON) endif() list(APPEND SOCI_INCLUDE_DIRS ${Boost_INCLUDE_DIRS}) @@ -110,11 +115,17 @@ if(Boost_FOUND) set_property(DIRECTORY ${SOCI_SOURCE_DIR} PROPERTY COMPILE_DEFINITIONS "${SOCI_COMPILE_DEFINITIONS}") +else() + set(SOCI_HAVE_BOOST OFF) + set(SOCI_HAVE_BOOST_DATE_TIME OFF) endif() +set(SOCI_HAVE_BOOST ${SOCI_HAVE_BOOST} CACHE INTERNAL "Boost library") +set(SOCI_HAVE_BOOST_DATE_TIME ${SOCI_HAVE_BOOST_DATE_TIME} CACHE INTERNAL "Boost date_time library") + list(APPEND SOCI_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}) -set_property(DIRECTORY ${CMAKE_SOURCE_DIR} +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES ${SOCI_INCLUDE_DIRS}) @@ -136,24 +147,61 @@ set(DATADIR "share" CACHE PATH "The directory to install data files into.") set(INCLUDEDIR "include" CACHE PATH "The directory to install includes into.") ############################################################################### -# Enable tests +# Configuration files ############################################################################### -enable_testing() -# Configure for testing with dashboard submissions to CDash -#include(CTest) # disabled as unused +set(CONFIG_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/include) +install(DIRECTORY ${CONFIG_INCLUDE_DIR}/soci DESTINATION ${INCLUDEDIR}) +set(CONFIG_FILE_IN "include/soci/soci-config.h.in") +set(CONFIG_FILE_OUT "${CONFIG_INCLUDE_DIR}/soci/soci-config.h") + -# Define "make check" as alias for "make test" -add_custom_target(check COMMAND ctest) ############################################################################### # Build configured components ############################################################################### include(SociBackend) -include_directories(${SOCI_SOURCE_DIR}/include) - +include_directories(${SOCI_SOURCE_DIR}/include ${CONFIG_INCLUDE_DIR}) add_subdirectory(src) -add_subdirectory(tests) + +if(SOCI_TESTS) + ############################################################################### + # Enable tests + ############################################################################### + enable_testing() + + file(TO_NATIVE_PATH ${PROJECT_SOURCE_DIR} TEST_ACCESS_PATH) + configure_file(${PROJECT_SOURCE_DIR}/cmake/configs/test-access.cmake ${PROJECT_SOURCE_DIR}/tests/odbc/test-access.dsn @ONLY) + + set(MYSQL_DRIVER_NAME "MySQL") + if(WIN32) + set(MYSQL_DRIVER_NAME "MySQL ODBC 5.3 ANSI Driver") + endif() + configure_file(${PROJECT_SOURCE_DIR}/cmake/configs/test-mysql.cmake ${PROJECT_SOURCE_DIR}/tests/odbc/test-mysql.dsn @ONLY) + + # Define "make check" as alias for "make test" + add_custom_target(check COMMAND ctest) + add_subdirectory(tests) +endif() + +############################################################################### +# build config file +############################################################################### + +get_cmake_property(ALL_VARIABLES CACHE_VARIABLES) +set(CONFIGURED_VARIABLES) +foreach(v ${ALL_VARIABLES}) + if (v MATCHES "^SOCI_HAVE.*") + get_property(CACHE_HELPSTRING CACHE ${v} PROPERTY HELPSTRING) + set(CONFIGURED_VARIABLES "${CONFIGURED_VARIABLES}\n// ${CACHE_HELPSTRING}\n") + if (${${v}}) + set(CONFIGURED_VARIABLES "${CONFIGURED_VARIABLES}#define ${v}\n") + else() + set(CONFIGURED_VARIABLES "${CONFIGURED_VARIABLES}/* #undef ${v} */\n") + endif() + endif() +endforeach() +configure_file("${CONFIG_FILE_IN}" "${CONFIG_FILE_OUT}") message(STATUS "") diff --git a/CTestConfig.cmake b/CTestConfig.cmake deleted file mode 100644 index 7ecf20dca7..0000000000 --- a/CTestConfig.cmake +++ /dev/null @@ -1,13 +0,0 @@ -## This file should be placed in the root directory of your project. -## Then modify the CMakeLists.txt file in the root directory of your -## project to incorporate the testing dashboard. -## # The following are required to uses Dart and the Cdash dashboard -## ENABLE_TESTING() -## INCLUDE(CTest) -set(CTEST_PROJECT_NAME "SOCI") -set(CTEST_NIGHTLY_START_TIME "00:00:00 EST") - -set(CTEST_DROP_METHOD "http") -set(CTEST_DROP_SITE "my.cdash.org") -set(CTEST_DROP_LOCATION "/submit.php?project=SOCI") -set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/README.md b/README.md index 5f93f8a9a6..bea1d6bad1 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,24 @@ -SOCI - The C++ Database Access Library -====================================== +# SOCI - The C++ Database Access Library -Website: http://soci.sourceforge.net +[![GitHub release](https://img.shields.io/github/tag/SOCI/soci.svg)](https://github.com/SOCI/soci/releases/tag/3.2.3) +[![GitHub commits](https://img.shields.io/github/commits-since/SOCI/soci/3.2.3.svg)](https://github.com/SOCI/soci/tree/master) -GitHub hosts SOCI source code repository, issues tracker and wiki: -https://github.com/SOCI +[![Website](https://img.shields.io/website-up-down-green-red/http/shields.io.svg?label=soci.sourceforge.net)](http://soci.sourceforge.net) +[![SourceForge](https://img.shields.io/sourceforge/dm/soci.svg)](https://sourceforge.net/projects/soci/files/) -Downloads and mailing lists at -http://sourceforge.net/projects/soci/ +[![Gitter](https://img.shields.io/gitter/room/SOCI/soci.svg)](https://gitter.im/SOCI/soci) +[![Mailing Lists](https://img.shields.io/badge/mailing--lists-ok-yellowgreen.svg)](https://sourceforge.net/p/soci/mailman/) +[![StackExchange](https://img.shields.io/stackexchange/stackoverflow/t/soci.svg)](https://stackoverflow.com/questions/tagged/soci) -Travis CI service at https://travis-ci.org/SOCI/soci +## Build Status -[![Build Status](https://api.travis-ci.org/SOCI/soci.png)](https://travis-ci.org/SOCI/soci) +| Branches | Travis-CI | AppVeyor-CI | Coverity Scan | Documentation | +|-------------|-----------|-------------|----------------|---------------| +| master | [![Build Status](https://travis-ci.org/SOCI/soci.svg?branch=master)](https://travis-ci.org/SOCI/soci) | [![Build status](https://ci.appveyor.com/api/projects/status/dtp5mvbeyu9aqupr/branch/master?svg=true)](https://ci.appveyor.com/project/SOCI/soci/branch/master) | [![Coverage](https://scan.coverity.com/projects/6581/badge.svg)](https://scan.coverity.com/projects/soci-soci) | [![Docs Status](https://circleci.com/gh/SOCI/soci.svg?style=shield&circle-token=5d31c692ed5fcffa5c5fc6b7fe2257b34d78f3c9)](https://circleci.com/gh/SOCI/soci) | +| release/3.2 | [![Build Status](https://travis-ci.org/SOCI/soci.svg?branch=release%2F3.2)](https://travis-ci.org/SOCI/soci) | | | | -License -------- +## History -The SOCI library is distributed under the terms of the [Boost Software License](http://www.boost.org/LICENSE_1_0.txt). - -Requirements ------------- - -Core: -* C++ compiler -* Boost C++ Libraries (optional, headers and Boost.DateTime) - -Backend specific client libraries for: -* DB2 -* Firebird -* MySQL -* ODBC with specific database driver -* Oracle -* PostgreSQL -* SQLite 3 - -See documentation at http://soci.sourceforge.net for details - -Brief History -------------- Originally, SOCI was developed by [Maciej Sobczak](http://www.msobczak.com/) at [CERN](http://www.cern.ch/) as abstraction layer for Oracle, a **Simple Oracle Call Interface**. @@ -50,3 +31,29 @@ or something similar. > layer in some of the control system components." -- Maciej Sobczak at [Inspirel](http://www.inspirel.com/users.html) + +## License + +SOCI library is distributed under the terms of the [Boost Software License](http://www.boost.org/LICENSE_1_0.txt). + +## Requirements + +Core: + +* C++ compiler +* Boost C++ Libraries (optional, headers and Boost.DateTime) + +Backend specific client libraries for: + +* DB2 +* Firebird +* MySQL +* ODBC with specific database driver +* Oracle +* PostgreSQL +* SQLite 3 + +See documentation at [soci.sourceforge.net](http://soci.sourceforge.net) for details + +[BSL](http://www.boost.org/LICENSE_1_0.txt) © +[Maciej Sobczak](http://github.com/msobczak) and [contributors](https://github.com/SOCI/soci/graphs/contributors). diff --git a/TODO b/TODO index 40a828904c..f19f03f16f 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,4 @@ -$Id: ideas.txt,v 1.8 2006/12/12 07:39:22 msobczak Exp $ +TODO This file contains a raw bunch of ideas for future releases. Not all of these ideas will necessarily make sense - they are here to get them together. @@ -10,17 +10,6 @@ http://lists.boost.org/Archives/boost/2006/12/113961.php --- RAII for transactions. ---- -bjam -Add JamFile files - ---- -Refactoring of core, more fine-grained file structure. - ---- -Session constructor overload that accepts map for param=value pairs. -This should rather be provided as a separate function? - --- Query construction utilities (kind of Ultimate++) - can be easily incorporated into SOCI by just making them streamable. @@ -50,13 +39,6 @@ Note: default is a reserved word. --- Provide statement-wide flag for eNoData case (because actually it *is* statement-wide, not field-wide). With this, boost.optional would handle the eNull case and the indicators could be dropped. ---- -Session sql("mysql://user:password@host/database"); - -So that Session tries to find backend_mysql.(dll|so) if not yet registered. ---- - -number of rows affected (insert/update + select?) --- query backend for supported featureset at runtime @@ -65,9 +47,6 @@ query backend for supported featureset at runtime Rowset --- -boost::optional? - ---- Rowset, including Rowset - way to indicate nulls? Additional pair based val/indicator interface? @@ -129,6 +108,3 @@ rowset rs = (s.prepare << "firstname='John' AND age > 28") // multi-fields - very simple home-made SQL parser or SQL-like queries support (see OGR utils from http://www.gdal.org) --- -Session::reconnect (should connect again with the same params). -Why not also Session::open/close? - diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000000..a10d7309e8 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,56 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrant virtual development environments for SOCI + +Vagrant.configure(2) do |config| + config.vm.box = "bento/ubuntu-14.04" + config.vm.box_check_update = true + + # Main SOCI development box with build essentials, FOSS DBs + config.vm.define "soci", primary: true do |soci| + soci.vm.hostname = "vmsoci" + soci.vm.network "private_network", type: "dhcp" + soci.vm.provider :virtualbox do |vb| + vb.customize ["modifyvm", :id, "--memory", "1024"] + end + scripts = [ + "bootstrap.sh", + "devel.sh", + "db2cli.sh", + "firebird.sh", + "mysql.sh", + "postgresql.sh", + "build.sh" + ] + scripts.each { |script| + soci.vm.provision :shell, privileged: false, :path => "scripts/vagrant/" << script + } + end + + # Database box with IBM DB2 Express-C + config.vm.define "db2", autostart: false do |db2| + db2.vm.hostname = "vmdb2" + db2.vm.network "private_network", type: "dhcp" + # Access to DB2 instance from host + db2.vm.network :forwarded_port, host: 50000, guest: 50000 + db2.vm.provider :virtualbox do |vb| + vb.customize ["modifyvm", :id, "--memory", "1024"] + end + scripts = [ + "bootstrap.sh", + "db2.sh" + ] + scripts.each { |script| + db2.vm.provision :shell, privileged: false, :path => "scripts/vagrant/" << script + } + end + + # Database box with Oracle XE + # config.vm.define "oracle", autostart: false do |oracle| + # oracle.vm.provision "database", type: "shell" do |s| + # s.inline = "echo Installing Oracle'" + # end + # end + +end diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..3d7917f875 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,104 @@ +version: 4.0.0.{build} + +configuration: Release + +environment: + MINGW_ARCHIVE: C:\projects\mingw\x86_64-4.8.3-release-posix-seh-rt_v3-rev2.7z + MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.8.3/threads-posix/seh/x86_64-4.8.3-release-posix-seh-rt_v3-rev2.7z/download + matrix: + - G: "Visual Studio 15 2017 Win64" + BOOST_ROOT: C:\Libraries\boost_1_59_0 + POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\9.6 + MYSQL_DIR: C:\Program Files\MySql\MySQL Server 5.7 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - G: "Visual Studio 14 2015 Win64" + BOOST_ROOT: C:\Libraries\boost_1_59_0 + POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\9.4 + MYSQL_DIR: C:\Program Files\MySql\MySQL Server 5.7 + - G: "Visual Studio 12 2013 Win64" + BOOST_ROOT: C:\Libraries\boost_1_58_0 + POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\9.4 + MYSQL_DIR: C:\Program Files\MySql\MySQL Server 5.7 + - G: "Visual Studio 11 2012 Win64" + BOOST_ROOT: C:\Libraries\boost_1_58_0 + POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\9.4 + MYSQL_DIR: C:\Program Files\MySql\MySQL Server 5.7 + - G: "MinGW Makefiles" + MINGW_ROOT: C:\projects\mingw\4.8.3\mingw64\bin + BOOST_ROOT: C:\Libraries\boost_1_59_0 + POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\9.4 + MYSQL_DIR: C:\Program Files\MySql\MySQL Server 5.7 + +services: + - mssql2014 + - mysql + - postgresql + +cache: + - C:\projects\mingw + +install: + - ps: | + if ($env:G -eq "MinGW Makefiles") + { + if (!(Test-Path C:\projects\mingw)) + { + mkdir C:\projects\mingw + } + if (!(Test-Path $env:MINGW_ARCHIVE)) + { + (new-object net.webclient).DownloadFile("$env:MINGW_URL", "$env:MINGW_ARCHIVE") + 7z x -y -oC:\projects\mingw\4.8.3\ $env:MINGW_ARCHIVE > $null + } + } + Import-Module C:\projects\soci\scripts\windows\Get-ODBCList.ps1 + Get-ODBCList + - git clone https://github.com/snikulov/sqlite.cmake.build.git C:\projects\sqlite\src + +before_build: + # dirty little hack - remove sh from Git to make generator happy + - ps: | + if ($env:G -eq "MinGW Makefiles") + { + $shellPath = (Get-Command sh.exe).definition + if ($shellPath) + { + if (Test-Path $shellPath) + { + Remove-Item $shellPath + } + } + } + - cd C:\projects\sqlite\src + - mkdir build + - cd build + - set SQLITE_ROOT=C:\projects\sqlite\sqlite + - set PATH=%MINGW_ROOT%;%PATH%;%SQLITE_ROOT%\bin;%POSTGRESQL_ROOT%\bin;%MYSQL_DIR%\bin;%MYSQL_DIR%\lib + - echo %PATH% + - cmake --version + - set PGUSER=postgres + - set PGPASSWORD=Password12! + - createdb soci_test + - set MYSQL_PWD=Password12! + - set USER=root + - mysql -e "create database soci_test;" --user=root + - sqlcmd -U sa -P Password12! -S (local)\SQL2014 -i C:\projects\soci\scripts\windows\mssql_db_create.sql + - cmake .. -G"%G%" -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_INSTALL_PREFIX=C:\projects\sqlite\sqlite + - cmake --build . --config %CONFIGURATION% --target install + +build_script: + - cd C:\projects\soci + - mkdir build + - cd build + - cmake .. -G"%G%" -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_VERBOSE_MAKEFILE=ON + - cmake --build . --config %CONFIGURATION% --clean-first + +test_script: + - ctest -V --output-on-failure -R "soci_empty|soci_postgresql|soci_sqlite3|soci_odbc_test_mssql|soci_mysql|soci_odbc_test_mysql|soci_odbc_test_postgresql" + +notifications: + - provider: Webhook + url: https://webhooks.gitter.im/e/2038138a652d952f9372 + on_build_success: true + on_build_failure: true + on_build_status_changed: true diff --git a/bin/ci/before_install_oracle.sh b/bin/ci/before_install_oracle.sh deleted file mode 100755 index 5502e63d48..0000000000 --- a/bin/ci/before_install_oracle.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash -# Script performs non-interactive installation of Oracle XE 10g on Debian -# -# Based on oracle10g-update.sh from HTSQL project: -# https://bitbucket.org/prometheus/htsql -# -# Modified by Mateusz Loskot -# Changes: -# - Add fake swap support (backup /usr/bin/free manually anyway!) -# - Increase Oracle XE's PROCESSES parameter to value from range 100-200. -# Required to prevent random ORA-12520 errors while running tests. -# -# Modified by Peter Butkovic to enable i386 install on amd64 architecture (precise 64) -# based on: http://www.ubuntugeek.com/how-to-install-oracle-10g-xe-in-64-bit-ubuntu.html -# -# set -ex -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh - -# -# Utilities -# -function free_backup() -{ - # Multiple copies to be on safe side - sudo cp /usr/bin/free /root - sudo mv /usr/bin/free /usr/bin/free.original -} - -function free_restore() -{ - sudo cp /usr/bin/free.original /usr/bin/free -} - -# Install fake free -# http://www.axelog.de/2010/02/7-oracle-ee-refused-to-install-into-openvz/ -free_backup - -sudo tee /usr/bin/free < /dev/null -#!/bin/sh -cat <<__eof - total used free shared buffers cached -Mem: 1048576 327264 721312 0 0 0 --/+ buffers/cache: 327264 721312 -Swap: 2000000 0 2000000 -__eof -exit -EOF - -sudo chmod 755 /usr/bin/free - -# -# ok, bc, is the dependency that is required by DB2 as well => let's remove it from oracle xe dependencies and provide 64bit one only -# - -# Install the Oracle 10g dependant packages -sudo apt-fast install -qq -y --force-yes libc6:i386 -# travis needs the "apt-transport-https" to enable https transport -sudo apt-fast install -qq -y bc apt-transport-https - -# add Oracle repo + key (please note https is a must here, otherwise "apt-get update" fails for this repo with the "Undetermined error") -sudo bash -c 'echo "deb https://oss.oracle.com/debian/ unstable main non-free" >/etc/apt/sources.list.d/oracle.list' -wget -q https://oss.oracle.com/el4/RPM-GPG-KEY-oracle -O- | sudo apt-key add - -sudo apt-fast update -qq -y - -# only download the package, to manually install afterwards -sudo apt-fast install -qq -y --force-yes -d oracle-xe-universal:i386 -sudo apt-fast install -qq -y --force-yes libaio:i386 - -# remove key + repo (to prevent failures on next updates) -sudo apt-key del B38A8516 -sudo bash -c 'rm -rf /etc/apt/sources.list.d/oracle.list' -sudo apt-fast update -qq -y -sudo apt-get autoremove -qq - -# remove bc from the dependencies of the oracle-xe-universal package (to keep 64bit one installed) -mkdir /tmp/oracle_unpack -dpkg-deb -x /var/cache/apt/archives/oracle-xe-universal_10.2.0.1-1.1_i386.deb /tmp/oracle_unpack -cd /tmp/oracle_unpack -dpkg-deb --control /var/cache/apt/archives/oracle-xe-universal_10.2.0.1-1.1_i386.deb -sed -i "s/,\ bc//g" /tmp/oracle_unpack/DEBIAN/control -mkdir /tmp/oracle_repack -dpkg -b /tmp/oracle_unpack /tmp/oracle_repack/oracle-xe-universal_fixed_10.2.0.1-1.1_i386.deb - -# install Oracle 10g with the fixed dependencies, to prevent i386/amd64 conflicts on bc package -sudo dpkg -i --force-architecture /tmp/oracle_repack/oracle-xe-universal_fixed_10.2.0.1-1.1_i386.deb - -# Fix the problem when the configuration script eats the last -# character of the password if it is 'n': replace IFS="\n" with IFS=$'\n'. -sudo sed -i -e s/IFS=\"\\\\n\"/IFS=\$\'\\\\n\'/ /etc/init.d/oracle-xe - -# Configure the server; provide the answers for the following questions: -# The HTTP port for Oracle Application Express: 8080 -# A port for the database listener: 1521 -# The password for the SYS and SYSTEM database accounts: admin -# Start the server on boot: yes -sudo /etc/init.d/oracle-xe configure <>/root/.bashrc -#. /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/bin/oracle_env.sh -#END - -free_restore - -# Install development toolset for 32-bit for Travis CI 64-bit -sudo apt-fast install -qq -y g++-multilib diff --git a/bin/ci/oracle.sh b/bin/ci/oracle.sh deleted file mode 100644 index 9cbfadd4d7..0000000000 --- a/bin/ci/oracle.sh +++ /dev/null @@ -1,14 +0,0 @@ -# Definitions used by SOCI when building Oracle backend at travis-ci.org -# -# Copyright (c) 2015 Vadim Zeitlin -# -# Notice that this file is not executable, it is supposed to be sourced from -# the other files. - -# Load Oracle environment variables -. /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/bin/oracle_env.sh -echo "ORACLE_HOME=${ORACLE_HOME}" -echo "ORACLE_SID=${ORACLE_SID}" - -LD_LIBRARY_PATH=${ORACLE_HOME}:${LD_LIBRARY_PATH} -export LD_LIBRARY_PATH diff --git a/bin/ci/script_postgression.sh b/bin/ci/script_postgression.sh deleted file mode 100755 index 846bfcd2d6..0000000000 --- a/bin/ci/script_postgression.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -e -# Builds and tests SOCI backend PostgreSQL at travis-ci.org -# -# Copyright (C) 2014 Vadim Zeitlin -# Copyright (C) 2015 Mateusz Loskot -# -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh - -# Get Postgression's PostgreSQL connection parameters as URI -SOCI_POSTGRESQL_CONNSTR=$(curl http://api.postgression.com) -# or old-style conninfo string, both should work. -#SOCI_POSTGRESQL_CONNSTR=$(curl http://api.postgression.com | \ -#sed 's|postgres://\([^:]\+\):\([^@]\+\)@\([^:]\+\):\([0-9]\+\)/\(.*\)|user=\1 password=\2 host=\3 port=\4 dbname=\5|') - -# Before proceeding with build, check Postgression availability -echo $SOCI_POSTGRESQL_CONNSTR | grep NO_DATABASES_AVAILABLE -if [ $? -eq 0 ];then - echo ${SOCI_POSTGRESQL_CONNSTR} - exit 1 -fi - -echo "Postgression connection parameters: $SOCI_POSTGRESQL_CONNSTR" - -echo "PostgreSQL client version:" -psql --version -# WARNING: Somehow, connecting to Postgression service with psql -# seems to terminate Travis CI session preventing the job to -# continue with build and tests. -#psql -c 'select version();' "$SOCI_POSTGRESQL_CONNSTR" - -cmake \ - -DCMAKE_VERBOSE_MAKEFILE=ON \ - -DSOCI_TESTS=ON \ - -DSOCI_STATIC=OFF \ - -DSOCI_DB2=OFF \ - -DSOCI_EMPTY=OFF \ - -DSOCI_FIREBIRD=OFF \ - -DSOCI_MYSQL=OFF \ - -DSOCI_ODBC=OFF \ - -DSOCI_ORACLE=OFF \ - -DSOCI_POSTGRESQL=ON \ - -DSOCI_SQLITE3=OFF \ - -DSOCI_POSTGRESQL_TEST_CONNSTR:STRING="$SOCI_POSTGRESQL_CONNSTR" \ - .. - -run_make -run_test diff --git a/cmake/SociBackend.cmake b/cmake/SociBackend.cmake index 4c113de368..442d81d2fc 100644 --- a/cmake/SociBackend.cmake +++ b/cmake/SociBackend.cmake @@ -22,7 +22,6 @@ macro(soci_backend_deps_found NAME DEPS SUCCESS) # Determine required dependencies set(DEPS_INCLUDE_DIRS) set(DEPS_LIBRARIES) - set(DEPS_DEFS) set(DEPS_NOT_FOUND) # CMake 2.8+ syntax only: @@ -33,10 +32,13 @@ macro(soci_backend_deps_found NAME DEPS SUCCESS) list(APPEND DEPS_NOT_FOUND ${dep}) else() string(TOUPPER "${dep}" DEPU) - list(APPEND DEPS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIR}) - list(APPEND DEPS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIRS}) + if( ${DEPU}_INCLUDE_DIR ) + list(APPEND DEPS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIR}) + endif() + if( ${DEPU}_INCLUDE_DIRS ) + list(APPEND DEPS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIRS}) + endif() list(APPEND DEPS_LIBRARIES ${${DEPU}_LIBRARIES}) - list(APPEND DEPS_DEFS HAVE_${DEPU}=1) endif() endforeach() @@ -47,7 +49,6 @@ macro(soci_backend_deps_found NAME DEPS SUCCESS) else() set(${NAME}_DEPS_INCLUDE_DIRS ${DEPS_INCLUDE_DIRS}) set(${NAME}_DEPS_LIBRARIES ${DEPS_LIBRARIES}) - set(${NAME}_DEPS_DEFS ${DEPS_DEFS}) set(${SUCCESS} True) endif() @@ -83,8 +84,7 @@ macro(soci_backend NAME) soci_backend_deps_found(${NAMEU} "${THIS_BACKEND_DEPENDS}" ${NAMEU}_DEPS_FOUND) if(NOT ${NAMEU}_DEPS_FOUND) - colormsg(_RED_ "WARNING:") - colormsg(RED "Some required dependencies of ${NAME} backend not found:") + colormsg(_RED_ "WARNING: Some required dependencies of ${NAME} backend not found:") if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 2.8) foreach(dep ${DEPENDS_NOT_FOUND}) @@ -214,18 +214,23 @@ macro(soci_backend NAME) if (SOCI_SHARED) install(TARGETS ${THIS_BACKEND_TARGET} - RUNTIME DESTINATION ${BINDIR} - LIBRARY DESTINATION ${LIBDIR} - ARCHIVE DESTINATION ${LIBDIR}) + EXPORT SOCI + RUNTIME DESTINATION ${BINDIR} + LIBRARY DESTINATION ${LIBDIR} + ARCHIVE DESTINATION ${LIBDIR}) endif() - if (SOCI_SHARED) + if (SOCI_STATIC) install(TARGETS ${THIS_BACKEND_TARGET_STATIC} - RUNTIME DESTINATION ${BINDIR} - LIBRARY DESTINATION ${LIBDIR} - ARCHIVE DESTINATION ${LIBDIR}) + EXPORT SOCI + RUNTIME DESTINATION ${BINDIR} + LIBRARY DESTINATION ${LIBDIR} + ARCHIVE DESTINATION ${LIBDIR} + ) endif() + install(EXPORT SOCI NAMESPACE SOCI:: DESTINATION cmake) + else() colormsg(HIRED "${NAME}" RED "backend disabled, since") endif() @@ -320,8 +325,7 @@ macro(soci_backend_test) INCLUDE_DIRECTORIES "${THIS_INCLUDE_DIRS}" COMPILE_DEFINITIONS "${THIS_COMPILE_DEFS}") else() - colormsg(_RED_ "WARNING:") - colormsg(RED "Some dependencies of ${THIS_TEST_BACKEND} test not found") + colormsg(_RED_ "WARNING: Some dependencies of ${THIS_TEST_BACKEND} test not found") endif() set(TEST_CONNSTR_VAR ${TEST_FULL_NAME}_CONNSTR) @@ -330,8 +334,13 @@ macro(soci_backend_test) if(NOT ${TEST_CONNSTR_VAR} AND THIS_TEST_CONNSTR) set(${TEST_CONNSTR_VAR} ${THIS_TEST_CONNSTR}) + if(${TEST_CONNSTR_VAR} MATCHES ".dsn") + set(_dsnpath "${CMAKE_CURRENT_SOURCE_DIR}/${${TEST_CONNSTR_VAR}}") + set(${TEST_CONNSTR_VAR} "FILEDSN=${_dsnpath}") + endif() endif() - boost_report_value(${TEST_CONNSTR_VAR}) + + boost_message_value(${TEST_CONNSTR_VAR}) if( SOCI_SHARED ) # Shared libraries test @@ -362,8 +371,8 @@ macro(soci_backend_test) target_link_libraries(${TEST_TARGET_STATIC} ${SOCI_CORE_DEPS_LIBS} ${THIS_TEST_DEPENDS_LIBRARIES} - soci_core_static - soci_${BACKENDL}_static) + soci_${BACKENDL}_static + soci_core_static) add_test(${TEST_TARGET_STATIC} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TARGET_STATIC} @@ -375,7 +384,7 @@ macro(soci_backend_test) add_dependencies(check ${TEST_TARGET_STATIC}) endif(SOCI_STATIC) - + # Group source files for IDE source explorers (e.g. Visual Studio) source_group("Source Files" FILES ${THIS_TEST_SOURCE}) diff --git a/cmake/SociConfig.cmake b/cmake/SociConfig.cmake index 73610e0f2f..97d907e4e9 100644 --- a/cmake/SociConfig.cmake +++ b/cmake/SociConfig.cmake @@ -23,47 +23,71 @@ else(WIN32) check_cxx_symbol_exists("__arm__" "" SOCI_TARGET_ARCH_ARM) endif(WIN32) +if(NOT DEFINED LIB_SUFFIX) + if(SOCI_TARGET_ARCH_X64) + set(_lib_suffix "64") + else() + set(_lib_suffix "") + endif() + set(LIB_SUFFIX ${_lib_suffix} CACHE STRING "Specifies suffix for the lib directory") +endif() + # # C++11 Option # -set (SOCI_CXX_C11 OFF CACHE BOOL "Build to the C++11 standard") - +if(NOT SOCI_CXX_C11) + set (SOCI_CXX_C11 OFF CACHE BOOL "Build to the C++11 standard") +endif() # # Force compilation flags and set desired warnings level # if (MSVC) - if (MSVC80 OR MSVC90 OR MSVC10) - add_definitions(-D_CRT_SECURE_NO_DEPRECATE) - add_definitions(-D_CRT_SECURE_NO_WARNINGS) - add_definitions(-D_CRT_NONSTDC_NO_WARNING) - add_definitions(-D_SCL_SECURE_NO_WARNINGS) - endif() + add_definitions(-D_CRT_SECURE_NO_DEPRECATE) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_definitions(-D_CRT_NONSTDC_NO_WARNING) + add_definitions(-D_SCL_SECURE_NO_WARNINGS) if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /we4266") endif() else() set(SOCI_GCC_CLANG_COMMON_FLAGS - "-pedantic -Werror -Wall -Wpointer-arith -Wcast-align -Wcast-qual -Wfloat-equal -Wredundant-decls -Wno-long-long") + "-pedantic -Werror -Wno-error=parentheses -Wall -Wextra -Wpointer-arith -Wcast-align -Wcast-qual -Wfloat-equal -Woverloaded-virtual -Wredundant-decls -Wno-long-long") if (SOCI_CXX_C11) set(SOCI_CXX_VERSION_FLAGS "-std=c++11") - add_definitions(-DSOCI_CXX_C11) else() set(SOCI_CXX_VERSION_FLAGS "-std=gnu++98") endif() - if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER}" MATCHES "clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC ${SOCI_GCC_CLANG_COMMON_FLAGS} ${SOCI_CXX_VERSION_FLAGS} ") + if(NOT CMAKE_CXX_COMPILER_VERSION LESS 3.1 AND SOCI_ASAN) + set(SOCI_GCC_CLANG_COMMON_FLAGS "${SOCI_GCC_CLANG_COMMON_FLAGS} -fsanitize=address") + endif() + + # enforce C++11 for Clang + set(SOCI_CXX_C11 ON) + set(SOCI_CXX_VERSION_FLAGS "-std=c++11") + add_definitions(-DCATCH_CONFIG_CPP11_NO_IS_ENUM) + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SOCI_GCC_CLANG_COMMON_FLAGS} ${SOCI_CXX_VERSION_FLAGS}") + + elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + + if(NOT CMAKE_CXX_COMPILER_VERSION LESS 4.8 AND SOCI_ASAN) + set(SOCI_GCC_CLANG_COMMON_FLAGS "${SOCI_GCC_CLANG_COMMON_FLAGS} -fsanitize=address") + endif() + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SOCI_GCC_CLANG_COMMON_FLAGS} ${SOCI_CXX_VERSION_FLAGS} ") if (CMAKE_COMPILER_IS_GNUCXX) if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") @@ -72,12 +96,11 @@ else() endif() endif() - elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER}" MATCHES "clang") - - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SOCI_GCC_CLANG_COMMON_FLAGS} ${SOCI_CXX_VERSION_FLAGS}") - else() message(WARNING "Unknown toolset - using default flags to build SOCI") endif() endif() + +# Set SOCI_HAVE_* variables for soci-config.h generator +set(SOCI_HAVE_CXX_C11 ${SOCI_CXX_C11} CACHE INTERNAL "Enables C++11 support") diff --git a/cmake/SociDependencies.cmake b/cmake/SociDependencies.cmake index a1014ed3bd..c097add67f 100644 --- a/cmake/SociDependencies.cmake +++ b/cmake/SociDependencies.cmake @@ -44,24 +44,22 @@ colormsg(_HIBLUE_ "Looking for SOCI dependencies:") macro(boost_external_report NAME) set(VARNAME ${NAME}) - set(SUCCESS ${${VARNAME}_FOUND}) + string(TOUPPER ${NAME} VARNAMEU) set(VARNAMES ${ARGV}) list(REMOVE_AT VARNAMES 0) # Test both, given original name and uppercase version too - if(NOT SUCCESS) - string(TOUPPER ${NAME} VARNAME) - set(SUCCESS ${${VARNAME}_FOUND}) - if(NOT SUCCESS) - colormsg(_RED_ "WARNING:") - colormsg(RED "${NAME} not found, some libraries or features will be disabled.") - colormsg(RED "See the documentation for ${NAME} or manually set these variables:") - endif() + if(NOT ${VARNAME}_FOUND AND NOT ${VARNAMEU}_FOUND) + colormsg(_RED_ "WARNING: ${NAME} libraries not found, some features will be disabled.") endif() foreach(variable ${VARNAMES}) - boost_report_value(${VARNAME}_${variable}) + if(${VARNAMEU}_FOUND) + boost_report_value(${VARNAMEU}_${variable}) + elseif(${VARNAME}_FOUND) + boost_report_value(${VARNAME}_${variable}) + endif() endforeach() endmacro() diff --git a/cmake/SociUtilities.cmake b/cmake/SociUtilities.cmake index 404ef1abf8..0e6711c92e 100644 --- a/cmake/SociUtilities.cmake +++ b/cmake/SociUtilities.cmake @@ -301,6 +301,14 @@ function(boost_report_value NAME) colormsg("${NAME}${varpadding} = ${${NAME}}") endfunction() +function(boost_message_value NAME) + string(LENGTH "${NAME}" varlen) + math(EXPR padding_len 40-${varlen}) + string(SUBSTRING " " + 0 ${padding_len} varpadding) + message(STATUS "${NAME}${varpadding} = ${${NAME}}") +endfunction() + function(trace NAME) if(BOOST_CMAKE_TRACE) string(LENGTH "${NAME}" varlen) diff --git a/cmake/SociVersion.cmake b/cmake/SociVersion.cmake index d9e277351c..5c8b5d75a7 100644 --- a/cmake/SociVersion.cmake +++ b/cmake/SociVersion.cmake @@ -20,14 +20,19 @@ # MAJOR.MINOR version is used to set SOVERSION # macro(soci_version) - parse_arguments(THIS_VERSION "MAJOR;MINOR;PATCH;" - "" - ${ARGN}) + # get version from soci/version.h + file( + STRINGS + "${PROJECT_SOURCE_DIR}/include/soci/version.h" + _VERSION + REGEX + "#define SOCI_VERSION ([0-9]+)" + ) + string(REGEX MATCH "([0-9]+)" _VERSION "${_VERSION}") - # Set version components - set(${PROJECT_NAME}_VERSION_MAJOR ${THIS_VERSION_MAJOR}) - set(${PROJECT_NAME}_VERSION_MINOR ${THIS_VERSION_MINOR}) - set(${PROJECT_NAME}_VERSION_PATCH ${THIS_VERSION_PATCH}) + math(EXPR ${PROJECT_NAME}_VERSION_MAJOR "${_VERSION} / 100000") + math(EXPR ${PROJECT_NAME}_VERSION_MINOR "${_VERSION} / 100 % 1000") + math(EXPR ${PROJECT_NAME}_VERSION_PATCH "${_VERSION} % 100") # Set VERSION string set(${PROJECT_NAME}_VERSION diff --git a/tests/odbc/test-access.dsn b/cmake/configs/test-access.cmake similarity index 50% rename from tests/odbc/test-access.dsn rename to cmake/configs/test-access.cmake index 597ff131d2..abc44deb03 100644 --- a/tests/odbc/test-access.dsn +++ b/cmake/configs/test-access.cmake @@ -1,5 +1,5 @@ [ODBC] -DRIVER=Microsoft Access Driver (*.mdb) +DRIVER=Microsoft Access Driver (*.mdb, *.accdb) UID=admin UserCommitSync=Yes Threads=3 @@ -9,5 +9,5 @@ MaxScanRows=8 MaxBufferSize=2048 FIL=MS Access DriverId=25 -DefaultDir=.\ -DBQ=.\soci_test.mdb +DefaultDir=@TEST_ACCESS_PATH@\tests\odbc +DBQ=@TEST_ACCESS_PATH@\tests\odbc\soci_test.mdb diff --git a/tests/odbc/test-mysql.dsn b/cmake/configs/test-mysql.cmake similarity index 56% rename from tests/odbc/test-mysql.dsn rename to cmake/configs/test-mysql.cmake index 59dff62191..48e54033d5 100644 --- a/tests/odbc/test-mysql.dsn +++ b/cmake/configs/test-mysql.cmake @@ -1,4 +1,4 @@ [ODBC] -DRIVER=MySQL +DRIVER=@MYSQL_DRIVER_NAME@ DATABASE=soci_test OPTION=0 diff --git a/cmake/dependencies/PostgreSQL.cmake b/cmake/dependencies/PostgreSQL.cmake index 4a4feeec84..c6f2154258 100644 --- a/cmake/dependencies/PostgreSQL.cmake +++ b/cmake/dependencies/PostgreSQL.cmake @@ -2,4 +2,4 @@ set(PostgreSQL_FIND_QUIETLY TRUE) find_package(PostgreSQL) -boost_external_report(PostgreSQL INCLUDE_DIR LIBRARIES VERSION) \ No newline at end of file +boost_external_report(PostgreSQL INCLUDE_DIRS LIBRARIES VERSION) diff --git a/cmake/modules/FindDB2.cmake b/cmake/modules/FindDB2.cmake index 2fd243efa7..b4c36a06fb 100644 --- a/cmake/modules/FindDB2.cmake +++ b/cmake/modules/FindDB2.cmake @@ -19,12 +19,14 @@ if(UNIX) /opt/ibm/db2/V10.1 /opt/ibm/db2/V9.7 /opt/ibm/db2/V9.5 - /opt/ibm/db2/V9.1) + /opt/ibm/db2/V9.1 + /opt/ibm/clidriver + /opt/clidriver) if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(DB2_LIBDIRS "lib32" "lib") else() - set(DB2_LIBDIRS "lib64") + set(DB2_LIBDIRS "lib64" "lib") endif() set(DB2_FIND_INCLUDE_PATHS) @@ -70,11 +72,15 @@ endif() find_path(DB2_INCLUDE_DIR sqlcli1.h $ENV{DB2_INCLUDE_DIR} $ENV{DB2_DIR}/include + $ENV{DB2_HOME} + $ENV{IBM_DB_INCLUDE} ${DB2_FIND_INCLUDE_PATHS}) find_library(DB2_LIBRARY NAMES db2 db2api PATHS + $ENV{DB2LIB} + $ENV{IBM_DB_LIB} ${DB2_FIND_LIB_PATHS} ${DB2_FIND_LIB_NO_LIB}) diff --git a/cmake/modules/FindMySQL.cmake b/cmake/modules/FindMySQL.cmake index 76112f140a..1233568932 100644 --- a/cmake/modules/FindMySQL.cmake +++ b/cmake/modules/FindMySQL.cmake @@ -67,7 +67,7 @@ if(WIN32) $ENV{SystemDrive}/MySQL/*/lib/opt $ENV{ProgramW6432}/MySQL/*/lib ) - find_library(MYSQL_LIBRARIES NAMES mysqlclient libmysql + find_library(MYSQL_LIBRARIES NAMES libmysql PATHS ${MYSQL_LIB_PATHS} ) diff --git a/cmake/modules/FindODBC.cmake b/cmake/modules/FindODBC.cmake index fd3d9e36df..6cee4088bd 100644 --- a/cmake/modules/FindODBC.cmake +++ b/cmake/modules/FindODBC.cmake @@ -17,21 +17,25 @@ # ODBC_LIBRARY, where to find the ODBC driver manager library. set(ODBC_FOUND FALSE) +include(CheckIncludeFiles) +check_include_files("windows.h;sqlext.h" HAVE_SQLEXT_H) -find_path(ODBC_INCLUDE_DIR sql.h - /usr/include - /usr/include/odbc - /usr/local/include - /usr/local/include/odbc - /usr/local/odbc/include - "C:/Program Files (x86)/Windows Kits/8.0/include/um" - "C:/Program Files (x86)/Microsoft SDKs/Windows/v7.0A/Include" - "C:/Program Files/ODBC/include" - "C:/Program Files/Microsoft SDKs/Windows/v7.0/include" - "C:/Program Files/Microsoft SDKs/Windows/v6.0a/include" - "C:/ODBC/include" - DOC "Specify the directory containing sql.h." -) +if(NOT HAVE_SQLEXT_H) + find_path(ODBC_INCLUDE_DIR sqlext.h + /usr/include + /usr/include/odbc + /usr/local/include + /usr/local/include/odbc + /usr/local/odbc/include + "C:/Program Files (x86)/Windows Kits/8.0/include/um" + "C:/Program Files (x86)/Microsoft SDKs/Windows/v7.0A/Include" + "C:/Program Files/ODBC/include" + "C:/Program Files/Microsoft SDKs/Windows/v7.0/include" + "C:/Program Files/Microsoft SDKs/Windows/v6.0a/include" + "C:/ODBC/include" + DOC "Specify the directory containing sql.h." + ) +endif() if(MSVC) # msvc knows where to find sdk libs @@ -54,7 +58,7 @@ else() endif() if(ODBC_LIBRARY) - if(ODBC_INCLUDE_DIR) + if(ODBC_INCLUDE_DIR OR HAVE_SQLEXT_H) set( ODBC_FOUND 1 ) endif() endif() diff --git a/cmake/modules/FindOracle.cmake b/cmake/modules/FindOracle.cmake index 1daf24c005..97997c61d0 100644 --- a/cmake/modules/FindOracle.cmake +++ b/cmake/modules/FindOracle.cmake @@ -23,47 +23,52 @@ ############################################################################### # First check for CMAKE variable -if( NOT ORACLE_HOME ) +if(NOT ORACLE_HOME) # If ORACLE_HOME is not defined check for env var and if exists set from env var if(EXISTS $ENV{ORACLE_HOME}) set(ORACLE_HOME $ENV{ORACLE_HOME}) - endif(EXISTS $ENV{ORACLE_HOME}) -endif(NOT ORACLE_HOME) + endif() +endif() message(STATUS "ORACLE_HOME=${ORACLE_HOME}") + find_path(ORACLE_INCLUDE_DIR -NAMES oci.h -PATHS -${ORACLE_HOME}/rdbms/public -${ORACLE_HOME}/include -${ORACLE_HOME}/sdk/include # Oracle SDK -${ORACLE_HOME}/OCI/include) # Oracle XE on Windows + NAMES oci.h + PATHS + ${ORACLE_HOME}/rdbms/public + ${ORACLE_HOME}/include + ${ORACLE_HOME}/sdk/include # Oracle SDK + ${ORACLE_HOME}/OCI/include # Oracle XE on Windows + # instant client from rpm + /usr/include/oracle/*/client${LIB_SUFFIX}) set(ORACLE_OCI_NAMES clntsh libclntsh oci) # Dirty trick might help on OSX, see issues/89 set(ORACLE_OCCI_NAMES libocci occi oraocci10 oraocci11 oraocci12) set(ORACLE_NNZ_NAMES nnz10 libnnz10 nnz11 libnnz11 nnz12 libnnz12 ociw32) set(ORACLE_LIB_DIR -${ORACLE_HOME} -${ORACLE_HOME}/lib -${ORACLE_HOME}/sdk/lib # Oracle SDK -${ORACLE_HOME}/sdk/lib/msvc -${ORACLE_HOME}/OCI/lib/msvc) # Oracle XE on Windows + ${ORACLE_HOME} + ${ORACLE_HOME}/lib + ${ORACLE_HOME}/sdk/lib # Oracle SDK + ${ORACLE_HOME}/sdk/lib/msvc + ${ORACLE_HOME}/OCI/lib/msvc # Oracle XE on Windows + # Instant client from rpm + /usr/lib/oracle/*/client${LIB_SUFFIX}/lib) find_library(ORACLE_OCI_LIBRARY -NAMES ${ORACLE_OCI_NAMES} PATHS ${ORACLE_LIB_DIR}) + NAMES ${ORACLE_OCI_NAMES} PATHS ${ORACLE_LIB_DIR}) find_library(ORACLE_OCCI_LIBRARY -NAMES ${ORACLE_OCCI_NAMES} PATHS ${ORACLE_LIB_DIR}) + NAMES ${ORACLE_OCCI_NAMES} PATHS ${ORACLE_LIB_DIR}) find_library(ORACLE_NNZ_LIBRARY -NAMES ${ORACLE_NNZ_NAMES} PATHS ${ORACLE_LIB_DIR}) + NAMES ${ORACLE_NNZ_NAMES} PATHS ${ORACLE_LIB_DIR}) set(ORACLE_LIBRARY -${ORACLE_OCI_LIBRARY} -${ORACLE_OCCI_LIBRARY} -${ORACLE_NNZ_LIBRARY}) + ${ORACLE_OCI_LIBRARY} + ${ORACLE_OCCI_LIBRARY} + ${ORACLE_NNZ_LIBRARY}) if(NOT WIN32) -set(ORACLE_LIBRARY ${ORACLE_LIBRARY} ${ORACLE_CLNTSH_LIBRARY}) + set(ORACLE_LIBRARY ${ORACLE_LIBRARY} ${ORACLE_CLNTSH_LIBRARY}) endif(NOT WIN32) set(ORACLE_LIBRARIES ${ORACLE_LIBRARY}) diff --git a/cmake/modules/FindPostgreSQL.cmake b/cmake/modules/FindPostgreSQL.cmake index 91e9cc20cd..48937e2efa 100644 --- a/cmake/modules/FindPostgreSQL.cmake +++ b/cmake/modules/FindPostgreSQL.cmake @@ -1,83 +1,176 @@ -# - Find PostgreSQL -# Find the PostgreSQL includes and client library +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindPostgreSQL +# -------------- +# +# Find the PostgreSQL installation. +# # This module defines -# POSTGRESQL_INCLUDE_DIR, where to find libpq-fe.h -# POSTGRESQL_LIBRARIES, libraries needed to use PostgreSQL -# POSTGRESQL_VERSION, if found, version of PostgreSQL -# POSTGRESQL_FOUND, if false, do not try to use PostgreSQL # -# Copyright (c) 2010, Mateusz Loskot, -# Copyright (c) 2006, Jaroslaw Staniek, +# :: # -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# POSTGRESQL_LIBRARIES - the PostgreSQL libraries needed for linking +# POSTGRESQL_INCLUDE_DIRS - the directories of the PostgreSQL headers +# POSTGRESQL_LIBRARY_DIRS - the link directories for PostgreSQL libraries +# POSTGRESQL_VERSION_STRING - the version of PostgreSQL found (since CMake 2.8.8) -find_program(PG_CONFIG NAMES pg_config - PATHS - /usr/bin - /usr/local/bin - $ENV{ProgramFiles}/PostgreSQL/*/bin - $ENV{SystemDrive}/PostgreSQL/*/bin - DOC "Path to pg_config utility") +# ---------------------------------------------------------------------------- +# History: +# This module is derived from the module originally found in the VTK source tree. +# +# ---------------------------------------------------------------------------- +# Note: +# POSTGRESQL_ADDITIONAL_VERSIONS is a variable that can be used to set the +# version mumber of the implementation of PostgreSQL. +# In Windows the default installation of PostgreSQL uses that as part of the path. +# E.g C:\Program Files\PostgreSQL\8.4. +# Currently, the following version numbers are known to this module: +# "9.6" "9.5" "9.4" "9.3" "9.2" "9.1" "9.0" "8.4" "8.3" "8.2" "8.1" "8.0" +# +# To use this variable just do something like this: +# set(POSTGRESQL_ADDITIONAL_VERSIONS "9.2" "8.4.4") +# before calling find_package(PostgreSQL) in your CMakeLists.txt file. +# This will mean that the versions you set here will be found first in the order +# specified before the default ones are searched. +# +# ---------------------------------------------------------------------------- +# You may need to manually set: +# POSTGRESQL_INCLUDE_DIR - the path to where the PostgreSQL include files are. +# POSTGRESQL_LIBRARY_DIR - The path to where the PostgreSQL library files are. +# If FindPostgreSQL.cmake cannot find the include files or the library files. +# +# ---------------------------------------------------------------------------- +# The following variables are set if PostgreSQL is found: +# POSTGRESQL_FOUND - Set to true when PostgreSQL is found. +# POSTGRESQL_INCLUDE_DIRS - Include directories for PostgreSQL +# POSTGRESQL_LIBRARY_DIRS - Link directories for PostgreSQL libraries +# POSTGRESQL_LIBRARIES - The PostgreSQL libraries. +# +# ---------------------------------------------------------------------------- +# If you have installed PostgreSQL in a non-standard location. +# (Please note that in the following comments, it is assumed that +# points to the root directory of the include directory of PostgreSQL.) +# Then you have three options. +# 1) After CMake runs, set POSTGRESQL_INCLUDE_DIR to /include and +# POSTGRESQL_LIBRARY_DIR to wherever the library pq (or libpq in windows) is +# 2) Use CMAKE_INCLUDE_PATH to set a path to /PostgreSQL<-version>. This will allow find_path() +# to locate POSTGRESQL_INCLUDE_DIR by utilizing the PATH_SUFFIXES option. e.g. In your CMakeLists.txt file +# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "/include") +# 3) Set an environment variable called ${POSTGRESQL_ROOT} that points to the root of where you have +# installed PostgreSQL, e.g. . +# +# ---------------------------------------------------------------------------- -if(PG_CONFIG) - exec_program(${PG_CONFIG} - ARGS "--version" - OUTPUT_VARIABLE PG_CONFIG_VERSION) +set(POSTGRESQL_INCLUDE_PATH_DESCRIPTION "top-level directory containing the PostgreSQL include directories. E.g /usr/local/include/PostgreSQL/8.4 or C:/Program Files/PostgreSQL/8.4/include") +set(POSTGRESQL_INCLUDE_DIR_MESSAGE "Set the POSTGRESQL_INCLUDE_DIR cmake cache entry to the ${POSTGRESQL_INCLUDE_PATH_DESCRIPTION}") +set(POSTGRESQL_LIBRARY_PATH_DESCRIPTION "top-level directory containing the PostgreSQL libraries.") +set(POSTGRESQL_LIBRARY_DIR_MESSAGE "Set the POSTGRESQL_LIBRARY_DIR cmake cache entry to the ${POSTGRESQL_LIBRARY_PATH_DESCRIPTION}") +set(POSTGRESQL_ROOT_DIR_MESSAGE "Set the POSTGRESQL_ROOT system variable to where PostgreSQL is found on the machine E.g C:/Program Files/PostgreSQL/8.4") - if(${PG_CONFIG_VERSION} MATCHES "^[A-Za-z]+[ ](.*)$") - string(REGEX REPLACE "^[A-Za-z]+[ ](.*)$" "\\1" POSTGRESQL_VERSION "${PG_CONFIG_VERSION}") + +set(POSTGRESQL_KNOWN_VERSIONS ${POSTGRESQL_ADDITIONAL_VERSIONS} + "9.6" "9.5" "9.4" "9.3" "9.2" "9.1" "9.0" "8.4" "8.3" "8.2" "8.1" "8.0") + +# Define additional search paths for root directories. +set( POSTGRESQL_ROOT_DIRECTORIES + ENV POSTGRESQL_ROOT + ${POSTGRESQL_ROOT} +) +foreach(suffix ${POSTGRESQL_KNOWN_VERSIONS}) + if(WIN32) + list(APPEND POSTGRESQL_LIBRARY_ADDITIONAL_SEARCH_SUFFIXES + "PostgreSQL/${suffix}/lib") + list(APPEND POSTGRESQL_INCLUDE_ADDITIONAL_SEARCH_SUFFIXES + "PostgreSQL/${suffix}/include") + list(APPEND POSTGRESQL_TYPE_ADDITIONAL_SEARCH_SUFFIXES + "PostgreSQL/${suffix}/include/server") endif() + if(UNIX) + list(APPEND POSTGRESQL_LIBRARY_ADDITIONAL_SEARCH_SUFFIXES + "pgsql-${suffix}/lib") + list(APPEND POSTGRESQL_INCLUDE_ADDITIONAL_SEARCH_SUFFIXES + "pgsql-${suffix}/include") + list(APPEND POSTGRESQL_TYPE_ADDITIONAL_SEARCH_SUFFIXES + "postgresql/${suffix}/server" + "pgsql-${suffix}/include/server") + endif() +endforeach() - exec_program(${PG_CONFIG} - ARGS "--includedir" - OUTPUT_VARIABLE PG_CONFIG_INCLUDEDIR) - - exec_program(${PG_CONFIG} - ARGS "--libdir" - OUTPUT_VARIABLE PG_CONFIG_LIBDIR) -else() - set(POSTGRESQL_VERSION "unknown") -endif() - -find_path(POSTGRESQL_INCLUDE_DIR libpq-fe.h - ${PG_CONFIG_INCLUDEDIR} - /usr/include/server - /usr/include/pgsql/server - /usr/local/include/pgsql/server - /usr/include/postgresql - /usr/include/postgresql/server - /usr/include/postgresql/*/server - $ENV{ProgramFiles}/PostgreSQL/*/include - $ENV{SystemDrive}/PostgreSQL/*/include) - -find_library(POSTGRESQL_LIBRARIES NAMES pq libpq +# +# Look for an installation. +# +find_path(POSTGRESQL_INCLUDE_DIR + NAMES libpq-fe.h PATHS - ${PG_CONFIG_LIBDIR} - /usr/lib - /usr/local/lib - /usr/lib/postgresql - /usr/lib64 - /usr/local/lib64 - /usr/lib64/postgresql - $ENV{ProgramFiles}/PostgreSQL/*/lib - $ENV{SystemDrive}/PostgreSQL/*/lib - $ENV{ProgramFiles}/PostgreSQL/*/lib/ms - $ENV{SystemDrive}/PostgreSQL/*/lib/ms) + # Look in other places. + ${POSTGRESQL_ROOT_DIRECTORIES} + PATH_SUFFIXES + pgsql + postgresql + include + ${POSTGRESQL_INCLUDE_ADDITIONAL_SEARCH_SUFFIXES} + # Help the user find it if we cannot. + DOC "The ${POSTGRESQL_INCLUDE_DIR_MESSAGE}" +) -if(POSTGRESQL_INCLUDE_DIR AND POSTGRESQL_LIBRARIES) - set(POSTGRESQL_FOUND TRUE) -else() - set(POSTGRESQL_FOUND FALSE) +# The PostgreSQL library. +set (POSTGRESQL_LIBRARY_TO_FIND pq) +# Setting some more prefixes for the library +set (POSTGRESQL_LIB_PREFIX "") +if ( WIN32 ) + set (POSTGRESQL_LIB_PREFIX ${POSTGRESQL_LIB_PREFIX} "lib") + set (POSTGRESQL_LIBRARY_TO_FIND ${POSTGRESQL_LIB_PREFIX}${POSTGRESQL_LIBRARY_TO_FIND}) endif() -# Handle the QUIETLY and REQUIRED arguments and set POSTGRESQL_FOUND to TRUE -# if all listed variables are TRUE -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(PostgreSQL - DEFAULT_MSG - POSTGRESQL_INCLUDE_DIR - POSTGRESQL_LIBRARIES - POSTGRESQL_VERSION) +find_library(POSTGRESQL_LIBRARY + NAMES ${POSTGRESQL_LIBRARY_TO_FIND} + PATHS + ${POSTGRESQL_ROOT_DIRECTORIES} + PATH_SUFFIXES + lib + ${POSTGRESQL_LIBRARY_ADDITIONAL_SEARCH_SUFFIXES} + # Help the user find it if we cannot. + DOC "The ${POSTGRESQL_LIBRARY_DIR_MESSAGE}" +) +get_filename_component(POSTGRESQL_LIBRARY_DIR ${POSTGRESQL_LIBRARY} PATH) -mark_as_advanced(POSTGRESQL_INCLUDE_DIR POSTGRESQL_LIBRARIES) +if (POSTGRESQL_INCLUDE_DIR) + # Some platforms include multiple pg_config.hs for multi-lib configurations + # This is a temporary workaround. A better solution would be to compile + # a dummy c file and extract the value of the symbol. + file(GLOB _PG_CONFIG_HEADERS "${POSTGRESQL_INCLUDE_DIR}/pg_config*.h") + foreach(_PG_CONFIG_HEADER ${_PG_CONFIG_HEADERS}) + if(EXISTS "${_PG_CONFIG_HEADER}") + file(STRINGS "${_PG_CONFIG_HEADER}" pgsql_version_str + REGEX "^#define[\t ]+PG_VERSION[\t ]+\".*\"") + if(pgsql_version_str) + string(REGEX REPLACE "^#define[\t ]+PG_VERSION[\t ]+\"([^\"]*)\".*" + "\\1" POSTGRESQL_VERSION_STRING "${pgsql_version_str}") + break() + endif() + endif() + endforeach() + unset(pgsql_version_str) +endif() + +# Did we find anything? +# +#include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) +include(FindPackageHandleStandardArgs) +# +find_package_handle_standard_args(PostgreSQL + REQUIRED_VARS POSTGRESQL_LIBRARY POSTGRESQL_INCLUDE_DIR + VERSION_VAR POSTGRESQL_VERSION_STRING) +set(POSTGRESQL_FOUND ${POSTGRESQL_FOUND}) + +# Now try to get the include and library path. +if(POSTGRESQL_FOUND) + set(POSTGRESQL_INCLUDE_DIRS ${POSTGRESQL_INCLUDE_DIR}) + set(POSTGRESQL_LIBRARY_DIRS ${POSTGRESQL_LIBRARY_DIR}) + set(POSTGRESQL_LIBRARIES ${POSTGRESQL_LIBRARY}) + set(POSTGRESQL_VERSION ${POSTGRESQL_VERSION_STRING}) +endif() + +mark_as_advanced(POSTGRESQL_INCLUDE_DIR POSTGRESQL_LIBRARY) diff --git a/docs/backends.md b/docs/api/backend.md similarity index 50% rename from docs/backends.md rename to docs/api/backend.md index 84eb7b4f98..22fae9e70f 100644 --- a/docs/backends.md +++ b/docs/api/backend.md @@ -1,177 +1,207 @@ -## Backends reference +# Backends reference -This part of the documentation is provided for those who want towrite (and contribute!) their own backends. It is anyway recommendedthat authors of new backend see the code of some existing backend forhints on how things are really done. +This part of the documentation is provided for those who want towrite (and contribute!) their +own backends. It is anyway recommendedthat authors of new backend see the code of some existing +backend forhints on how things are really done. -The backend interface is a set of base classes that the actual backendsare supposed to specialize. The main SOCI interface uses only theinterface and respecting the protocol (for example, the order offunction calls) described here. Note that both the interface and theprotocol were initially designed with the Oracle database in mind, -which means that whereas it is quite natural with respect to the way Oracle API -(OCI) works, it might impose some implementation burden on otherbackends, where things are done differently and therefore have to beadjusted, cached, converted, etc. +The backend interface is a set of base classes that the actual backendsare supposed to specialize. +The main SOCI interface uses only theinterface and respecting the protocol (for example, +the order of function calls) described here. +Note that both the interface and theprotocol were initially designed with the Oracle database in +mind, which means that whereas it is quite natural with respect to the way Oracle API (OCI) works, +it might impose some implementation burden on otherbackends, where things are done differently +and therefore have to beadjusted, cached, converted, etc. -The interface to the common SOCI interface is defined in the `core/soci-backend.h` header file. This file is dissected below. +The interface to the common SOCI interface is defined in the `core/soci-backend.h` header file. +This file is dissected below. All names are defined in either `soci` or `soci::details` namespace. +```cpp +// data types, as seen by the user +enum data_type +{ + dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long +}; - // data types, as seen by the user - enum data_type - { - dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long - }; +// the enum type for indicator variables +enum indicator { i_ok, i_null, i_truncated }; - // the enum type for indicator variables - enum indicator { i_ok, i_null, i_truncated }; +// data types, as used to describe exchange format +enum exchange_type +{ + x_char, + x_stdstring, + x_short, + x_integer, + x_long_long, + x_unsigned_long_long, + x_double, + x_stdtm, + x_statement, + x_rowid, + x_blob +}; - // data types, as used to describe exchange format - enum exchange_type - { - x_char, - x_stdstring, - x_short, - x_integer, - x_long_long, - x_unsigned_long_long, - x_double, - x_stdtm, - x_statement, - x_rowid, - x_blob - }; +struct cstring_descriptor +{ + cstring_descriptor(char * str, std::size_t bufSize) + : str_(str), bufSize_(bufSize) {} - struct cstring_descriptor - { - cstring_descriptor(char * str, std::size_t bufSize) - : str_(str), bufSize_(bufSize) {} + char * str_; + std::size_t bufSize_; +}; - char * str_; - std::size_t bufSize_; - }; +// actually in error.h: +class soci_error : public std::runtime_error +{ +public: + soci_error(std::string const & msg); +}; +``` - // actually in error.h: - class soci_error : public std::runtime_error - { - public: - soci_error(std::string const & msg); - }; +The `data_type` enumeration type defines all types that form the core type support for SOCI. +The enum itself can be used by clients when dealing with dynamic rowset description. -The `data_type` enumeration type defines all types that form the core type support for SOCI. The enum itself can be used by clients when dealing with dynamic rowset description. +The `indicator` enumeration type defines all recognized *states* of data. +The `i_truncated` state is provided for the case where the string is retrieved from the database +into the char buffer that is not long enough to hold the whole value. -The `indicator` enumeration type defines all recognized *states* of data. The `i_truncated` state is provided for the case where the string is retrieved from the database into the char buffer that is not long enough to hold the whole value. +The `exchange_type` enumeration type defines all possible types that can be used +with the `into` and `use` elements. -The `exchange_type` enumeration type defines all possible types that can be used with the `into` and `use` elements. +The `cstring_descriptor` is a helper class that allows to store the address of `char` buffer +together with its size. +The objects of this class are passed to the backend when the `x_cstring` type is involved. -The `cstring_descriptor` is a helper class that allows to store the address of `char` buffer together with its size. The objects of this class are passed to the backend when the `x_cstring` type is involved. +The `soci_error` class is an exception type used for database-related (and also usage-related) errors. +The backends should throw exceptions of this or derived type only. -The `soci_error` class is an exception type used for database-related (and also usage-related) errors. The backends should throw exceptions of this or derived type only. +```cpp +class standard_into_type_backend +{ +public: + standard_into_type_backend() {} + virtual ~standard_into_type_backend() {} - class standard_into_type_backend - { - public: - standard_into_type_backend() {} - virtual ~standard_into_type_backend() {} + virtual void define_by_pos(int& position, void* data, exchange_type type) = 0; - virtual void define_by_pos(int& position, void* data, exchange_type type) = 0; + virtual void pre_fetch() = 0; + virtual void post_fetch(bool gotData, bool calledFromFetch, indicator* ind) = 0; - virtual void pre_fetch() = 0; - virtual void post_fetch(bool gotData, bool calledFromFetch, indicator* ind) = 0; + virtual void clean_up() = 0; +}; +``` - virtual void clean_up() = 0; - }; - -The `standard_into_type_back_end` class implements the dynamic interactions with the simple (non-bulk) `into` elements. The objects of this class (or, rather, of the derived class implemented by the actual backend) are created by the `statement` object when the `into` element is bound - in terms of lifetime management, `statement` is the master of this class. +The `standard_into_type_back_end` class implements the dynamic interactions with the simple +(non-bulk) `into` elements. +The objects of this class (or, rather, of the derived class implemented by the actual backend) +are created by the `statement` object when the `into` element is bound - in terms of lifetime +management, `statement` is the master of this class. * `define_by_pos` - Called when the `into` element is bound, once and before the statement is executed. The `data` pointer points to the variable used for `into` element (or to the `cstring_descriptor` object, which is artificially created when the plain `char` buffer is used for data exchange). The `position` parameter is a "column number", assigned by the library. The backend should increase this parameter, according to the number of fields actually taken (usually 1). * `pre_fetch` - Called before each row is fetched. * `post_fetch` - Called after each row is fetched. The `gotData` parameter is `true` if the fetch operation really retrievedsome data and `false` otherwise; `calledFromFetch` is `true` when the call is from the fetch operation and `false` if it is from the execute operation (this is also the case for simple, one-time queries). In particular, `(calledFromFetch && !gotData)` indicates that there is an end-of-rowset condition. `ind` points to the indicator provided by the user, or is `NULL`, if there is no indicator. * `clean_up` - Called once when the statement is destroyed. -The intended use of `pre_fetch` and `post_fetch` functions is to manage any internal buffer and/or data conversion foreach value retrieved from the database. If the given server supportsbinary data transmission and the data format for the given type agreeswith what is used on the client machine, then these two functions neednot do anything; otherwise buffer management and data conversionsshould go there. +The intended use of `pre_fetch` and `post_fetch` functions is to manage any internal buffer and/or data conversion foreach value retrieved from the database. +If the given server supportsbinary data transmission and the data format for the given type agreeswith what is used on the client machine, then these two functions neednot do anything; otherwise buffer management and data conversionsshould go there. - class vector_into_type_backend - { - public: - vector_into_type_backend() {} - virtual ~vector_into_type_backend() {} +```cpp +class vector_into_type_backend +{ +public: + vector_into_type_backend() {} + virtual ~vector_into_type_backend() {} - virtual void define_by_pos(int& position, void* data, exchange_type type) = 0; + virtual void define_by_pos(int& position, void* data, exchange_type type) = 0; - virtual void pre_fetch() = 0; - virtual void post_fetch(bool gotData, indicator* ind) = 0; + virtual void pre_fetch() = 0; + virtual void post_fetch(bool gotData, indicator* ind) = 0; - virtual void resize(std::size_t sz) = 0; - virtual std::size_t size() = 0; + virtual void resize(std::size_t sz) = 0; + virtual std::size_t size() = 0; - virtual void clean_up() = 0; - }; + virtual void clean_up() = 0; +}; +``` The `vector_into_type_back_end` has similar structure and purpose as the previous one, but is used for vectors (bulk data retrieval). -The `data` pointer points to the variable of type `std::vector;` (and *not* to its internal buffer), `resize` is supposed to really resize the user-provided vector and `size` -is supposed to return the current size of this vector. The important difference with regard to the previous class is that `ind` points (if not `NULL`) to the beginning of the *array* of indicators. The backend should fill this array according to the actual state of the retrieved data. - +The `data` pointer points to the variable of type `std::vector;` (and *not* to its internal buffer), `resize` is supposed to really resize the user-provided vector and `size` is supposed to return the current size of this vector. +The important difference with regard to the previous class is that `ind` points (if not `NULL`) to the beginning of the *array* of indicators. +The backend should fill this array according to the actual state of the retrieved data. * `bind_by_pos` - Called for each `use` element, once and before the statement is executed - for those `use` elements that do not provide explicit names for parameter binding. The meaning of parameters is same as in previous classes. -* `bind_by_name` - Called for those `use` elements that provide the explicit name. +* `bind_by_name` - Called for those `use` elements that provide the explicit name. * `pre_use` - Called before the data is transmitted to the server (this means before the statement is executed, which can happen many times for the prepared statement). `ind` points to the indicator provided by the user (or is `NULL`). * `post_use` - Called after statement execution. `gotData` and `ind` have the same meaning as in `standard_into_type_back_end::post_fetch`, and this can be used by those backends whose respective servers support two-way data exchange (like in/out parameters in stored procedures). -The intended use for `pre_use` and `post_use` methods is to manage any internal buffers and/or data conversion. They can be called many times with the same statement. +The intended use for `pre_use` and `post_use` methods is to manage any internal buffers and/or data conversion. +They can be called many times with the same statement. - class vector_use_type_backend +```cpp +class vector_use_type_backend +{ +public: + virtual ~vector_use_type_backend() {} + + virtual void bind_by_pos(int& position, + void* data, exchange_type type) = 0; + virtual void bind_by_name(std::string const& name, + void* data, exchange_type type) = 0; + + virtual void pre_use(indicator const* ind) = 0; + + virtual std::size_t size() = 0; + + virtual void clean_up() = 0; +}; +``` + +Objects of this type (or rather of type derived from this one) are used to implement interactions with user-provided vector (bulk) `use` elements and are managed by the `statement` object. +The `data` pointer points to the whole vector object provided by the user (and *not* to its internal buffer); `ind` points to the beginning of the array of indicators (or is `NULL`). +The meaning of this interface is analogous to those presented above. + +```cpp +class statement_backend +{ +public: + statement_backend() {} + virtual ~statement_backend() {} + + virtual void alloc() = 0; + virtual void clean_up() = 0; + + virtual void prepare(std::string const& query, statement_type eType) = 0; + + enum exec_fetch_result { - public: - virtual ~vector_use_type_backend() {} - - virtual void bind_by_pos(int& position, - void* data, exchange_type type) = 0; - virtual void bind_by_name(std::string const& name, - void* data, exchange_type type) = 0; - - virtual void pre_use(indicator const* ind) = 0; - - virtual std::size_t size() = 0; - - virtual void clean_up() = 0; + ef_success, + ef_no_data }; + virtual exec_fetch_result execute(int number) = 0; + virtual exec_fetch_result fetch(int number) = 0; -Objects of this type (or rather of type derived from this one) are used to implement interactions with user-provided vector (bulk) `use` elements and are managed by the `statement` object. The `data` pointer points to the whole vector object provided by the user (and *not* to its internal buffer); `ind` points to the beginning of the array of indicators (or is `NULL`). The meaning of this -interface is analogous to those presented above. + virtual long long get_affected_rows() = 0; + virtual int get_number_of_rows() = 0; - class statement_backend - { - public: - statement_backend() {} - virtual ~statement_backend() {} + virtual std::string rewrite_for_procedure_call(std::string const& query) = 0; - virtual void alloc() = 0; - virtual void clean_up() = 0; + virtual int prepare_for_describe() = 0; + virtual void describe_column(int colNum, data_type& dtype, + std::string& column_name) = 0; - virtual void prepare(std::string const& query, statement_type eType) = 0; + virtual standard_into_type_backend* make_into_type_backend() = 0; + virtual standard_use_type_backend* make_use_type_backend() = 0; + virtual vector_into_type_backend* make_vector_into_type_backend() = 0; + virtual vector_use_type_backend* make_vector_use_type_backend() = 0; +}; +``` - enum exec_fetch_result - { - ef_success, - ef_no_data - }; - - virtual exec_fetch_result execute(int number) = 0; - virtual exec_fetch_result fetch(int number) = 0; - - virtual long long get_affected_rows() = 0; - virtual int get_number_of_rows() = 0; - - virtual std::string rewrite_for_procedure_call(std::string const& query) = 0; - - virtual int prepare_for_describe() = 0; - virtual void describe_column(int colNum, data_type& dtype, - std::string& column_name) = 0; - - virtual standard_into_type_backend* make_into_type_backend() = 0; - virtual standard_use_type_backend* make_use_type_backend() = 0; - virtual vector_into_type_backend* make_vector_into_type_backend() = 0; - virtual vector_use_type_backend* make_vector_use_type_backend() = 0; - }; - -The `statement_backend` type implements the internals of the `statement` objects. The objects of this class are created by the `session` object. +The `statement_backend` type implements the internals of the `statement` objects. +The objects of this class are created by the `session` object. * `alloc` - Called once to allocate everything that is needed for the statement to work correctly. * `clean_up` - Supposed to clean up the resources, called once. @@ -185,53 +215,59 @@ The `statement_backend` type implements the internals of the `statement` objects * `describe_column` - Called once for each column (column numbers - `colNum` - start from 1), should fill its parameters according to the column properties. * `make_into_type_backend`, `make_use_type_backend`, `make_vector_into_type_backend`, `make_vector_use_type_backend` - Called once for each `into` or `use` element, to create the objects of appropriate classes (described above). -Notes +**Notes:** 1. Whether the query is executed using the simple one-time syntax or is prepared, the `alloc`, `prepare` and `execute` functions are always called, in this order. 2. All `into` and `use` elements are bound (their `define_by_pos` or `bind_by_pos`/`bind_by_name` functions are called) *between* statement preparation and execution. - class rowid_backend - { - public: - virtual ~rowid_backend() {} - }; +```cpp +class rowid_backend +{ +public: + virtual ~rowid_backend() {} +}; +``` The `rowid_backend` class is a hook for the backends to provide their own state for the row identifier. It has no functions, since the only portable interaction with the row identifier object is to use it with `into` and `use` elements. - class blob_backend - { - public: - virtual ~blob_backend() {} +```cpp +class blob_backend +{ +public: + virtual ~blob_backend() {} - virtual std::size_t get_len() = 0; - virtual std::size_t read(std::size_t offset, char * buf, - std::size_t toRead) = 0; - virtual std::size_t write(std::size_t offset, char const * buf, - std::size_t toWrite) = 0; - virtual std::size_t append(char const * buf, std::size_t toWrite) = 0; - virtual void trim(std::size_t newLen) = 0; - }; + virtual std::size_t get_len() = 0; + virtual std::size_t read(std::size_t offset, char * buf, + std::size_t toRead) = 0; + virtual std::size_t write(std::size_t offset, char const * buf, + std::size_t toWrite) = 0; + virtual std::size_t append(char const * buf, std::size_t toWrite) = 0; + virtual void trim(std::size_t newLen) = 0; +}; +``` The `blob_backend` interface provides the entry points for the `blob` methods. - class session_backend - { - public: - virtual ~session_backend() {} +```cpp +class session_backend +{ +public: + virtual ~session_backend() {} - virtual void begin() = 0; - virtual void commit() = 0; - virtual void rollback() = 0; + virtual void begin() = 0; + virtual void commit() = 0; + virtual void rollback() = 0; - virtual bool get_next_sequence_value(session&, std::string const&, long&); - virtual bool get_last_insert_id(session&, std::string const&, long&); + virtual bool get_next_sequence_value(session&, std::string const&, long&); + virtual bool get_last_insert_id(session&, std::string const&, long&); - virtual std::string get_backend_name() const = 0; + virtual std::string get_backend_name() const = 0; - virtual statement_backend * make_statement_backend() = 0; - virtual rowid_backend * make_rowid_backend() = 0; - virtual blob_backend * make_blob_backend() = 0; - }; + virtual statement_backend * make_statement_backend() = 0; + virtual rowid_backend * make_rowid_backend() = 0; + virtual blob_backend * make_blob_backend() = 0; +}; +``` The object of the class derived from `session_backend` implements the internals of the `session` object. @@ -239,13 +275,15 @@ The object of the class derived from `session_backend` implements the internals * `get_next_sequence_value`, `get_last_insert_id` - Called to retrieve sequences or auto-generated values and every backend should define at least one of them to allow the code using auto-generated values to work. * `make_statement_backend`, `make_rowid_backend`, `make_blob_backend` - Called to create respective implementations for the `statement`, `rowid` and `blob` classes. - struct backend_factory - { - virtual ~backend_factory() {} +```cpp +struct backend_factory +{ + virtual ~backend_factory() {} - virtual details::session_backend * make_session( - std::string const& connectString) const = 0; - }; + virtual details::session_backend * make_session( + std::string const& connectString) const = 0; +}; +``` The `backend_factory` is a base class for backend-provided factory class that is able to create valid sessions. The `connectString` parameter passed to `make_session` is provided here by the `session` constructor and contains only the backend-related parameters, without the backend name (if the dynamic backend loading is used). @@ -253,21 +291,23 @@ The actual backend factory object is supposed to be provided by the backend impl The following example is taken from `soci-postgresql.h`, which declares entities of the PostgreSQL backend: - struct postgresql_backend_factory : backend_factory - { - virtual postgresql_session_backend* make_session( - std::string const& connectString) const; - }; - extern postgresql_backend_factory const postgresql; +```cpp +struct postgresql_backend_factory : backend_factory +{ + virtual postgresql_session_backend* make_session( + std::string const& connectString) const; +}; +extern postgresql_backend_factory const postgresql; - extern "C" - { +extern "C" +{ - // for dynamic backend loading - backend_factory const * factory_postgresql(); +// for dynamic backend loading +backend_factory const * factory_postgresql(); - } // extern "C" +} // extern "C" +``` With the above declarations, it is enough to pass the `postgresql` factory name to the constructor of the `session` object, which will use this factory to create concrete implementations for any other objects that are needed, with the help of appropriate `make_XYZ` functions. Alternatively, the `factory_postgresql` function will be called automatically by the backend loader if the backend name is provided at run-time instead. -Note that the backend source code is placed in the `backends/*name*` directory (for example, `backends/oracle`) and the test driver is in `backends/*name*/test`. There is also `backends/empty` directory provided as a skeleton for development of new backends and their tests. It is recommended that all backends respect naming conventions by just appending their name to the base-class names. The backend name used for the global factory object should clearly identify the given database engine, like `oracle`, `postgresql`, `mysql`, and so on. \ No newline at end of file +Note that the backend source code is placed in the `backends/*name*` directory (for example, `backends/oracle`) and the test driver is in `backends/*name*/test`. There is also `backends/empty` directory provided as a skeleton for development of new backends and their tests. It is recommended that all backends respect naming conventions by just appending their name to the base-class names. The backend name used for the global factory object should clearly identify the given database engine, like `oracle`, `postgresql`, `mysql`, and so on. diff --git a/docs/reference.md b/docs/api/client.md similarity index 53% rename from docs/reference.md rename to docs/api/client.md index cc8d8e706e..c21c96ec9c 100644 --- a/docs/reference.md +++ b/docs/api/client.md @@ -1,39 +1,26 @@ -## Client interface reference +# API Reference -* [commonly used types](#commontypes) -* [class session](#session) -* [class connection_parameters](#parameters) -* [class connection_pool](#pool) -* [class transaction](#transaction) -* [function into](#into) -* [function use](#use) -* [class statement](#statement) -* [class procedure](#procedure) -* [class type_conversion](#typeconversion) -* [class row](#row) -* [class column_properties](#columnproperties) -* [class values](#values) -* [class blob](#blob) -* [class rowid](#rowid) -* [class backend_factory](#backendfactory) -* [Simple client interface](#simpleclient) +The core client interface is a set of classes and free functions declared in the `soci.h` header file. +All names are dbeclared in the `soci` namespace. -The core client interface is a set of classes and free functions declared in the `soci.h` header file. All names are dbeclared in the `soci` namespace. +There are also additional names declared in the `soci::details` namespace, but they are not supposed to be directly used by the users of the library and are therefore not documented here. +When such types are used in the declarations that are part of the "public" interface, they are replaced by "IT", which means "internal type". +Types related to the backend interface are named here. -There are also additional names declared in the `soci::details` namespace, but they are not supposed to be directly used by the users of the library and are therefore not documented here. When such types are used in the declarations that are part of the "public" interface, they are replaced by "IT", which means "internal type". Types related to the backend interface are named here. - -### commonly used types +## Commonly used types The following types are commonly used in the rest of the interface: - // data types, as seen by the user - enum data_type { dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long }; +```cpp +// data types, as seen by the user +enum data_type { dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long }; - // the enum type for indicator variables - enum indicator { i_ok, i_null, i_truncated }; +// the enum type for indicator variables +enum indicator { i_ok, i_null, i_truncated }; - // the type used for reporting exceptions - class soci_error : public std::runtime_error { /* ... */ }; +// the type used for reporting exceptions +class soci_error : public std::runtime_error { /* ... */ }; +``` The `data_type` type defines the basic SOCI data types. User provided data types need to be associated with one of these basic types. @@ -41,81 +28,93 @@ The `indicator` type defines the possible states of data. The `soci_error` type is used for error reporting. - -### class session +## class session The `session` class encapsulates the connection to the database. - class session - { - public: - session(); - explicit session(connection_parameters const & parameters); - session(backend_factory const & factory, std::string const & connectString); - session(std::string const & backendName, std::string const & connectString); - explicit session(std::string const & connectString); - explicit session(connection_pool & pool); +```cpp +class session +{ +public: + session(); + explicit session(connection_parameters const & parameters); + session(backend_factory const & factory, std::string const & connectString); + session(std::string const & backendName, std::string const & connectString); + explicit session(std::string const & connectString); + explicit session(connection_pool & pool); - ~session(); + ~session(); - void open(backend_factory const & factory, std::string const & connectString); - void open(std::string const & backendName, std::string const & connectString); - void open(std::string const & connectString); - void close(); - void reconnect(); + void open(backend_factory const & factory, std::string const & connectString); + void open(std::string const & backendName, std::string const & connectString); + void open(std::string const & connectString); + void close(); + void reconnect(); - void begin(); - void commit(); - void rollback(); + void begin(); + void commit(); + void rollback(); - *IT* once; - *IT* prepare; + *IT* once; + *IT* prepare; - template *IT* operator<<(T const & t); + template *IT* operator<<(T const & t); - bool got_data() const; + bool got_data() const; - bool get_next_sequence_value(std::string const & sequence, long & value); - bool get_last_insert_id(std::string const & table, long & value); + bool get_next_sequence_value(std::string const & sequence, long & value); + bool get_last_insert_id(std::string const & table, long & value); - std::ostringstream & get_query_stream(); + std::ostringstream & get_query_stream(); - void set_log_stream(std::ostream * s); - std::ostream * get_log_stream() const; + void set_log_stream(std::ostream * s); + std::ostream * get_log_stream() const; - std::string get_last_query() const; + std::string get_last_query() const; - void uppercase_column_names(bool forceToUpper); + void uppercase_column_names(bool forceToUpper); - details::session_backend * get_backend(); + std::string get_dummy_from_table() const; + std::string get_dummy_from_clause() const; - std::string get_backend_name() const; - }; + details::session_backend * get_backend(); + + std::string get_backend_name() const; +}; +``` This class contains the following members: * Various constructors. The default one creates the session in the disconnected state. The others expect the backend factory object, or the backend name, or the URL-like composed connection string or the special parameters object containing both the backend and the connection string as well as possibly other connection options. The last constructor creates a session proxy associated with the session that is available in the given pool and releases it back to the pool when its lifetime ends. Example: - session sql(postgresql, "dbname=mydb"); - session sql("postgresql", "dbname=mydb"); - session sql("postgresql://dbname=mydb"); +```cpp +session sql(postgresql, "dbname=mydb"); +session sql("postgresql", "dbname=mydb"); +session sql("postgresql://dbname=mydb"); +``` * The constructors that take backend name as string load the shared library (if not yet loaded) with name computed as `libsoci_ABC.so` (or `libsoci_ABC.dll` on Windows) where `ABC` is the given backend name. * `open`, `close` and `reconnect` functions for reusing the same session object many times; the `reconnect` function attempts to establish the connection with the same parameters as most recently used with constructor or `open`. The arguments for `open` are treated in the same way as for constructors. * `begin`, `commit` and `rollback` functions for transaction control. * `once` member, which is used for performing *instant* queries that do not need to be separately prepared. Example: - sql.once << "drop table persons"; +```cpp +sql.once << "drop table persons"; +``` * `prepare` member, which is used for statement preparation - the result of the statement preparation must be provided to the constructor of the `statement` class. Example: - int i; - statement st = (sql.prepare << - "insert into numbers(value) values(:val)", use(i)); +```cpp +int i; +statement st = (sql.prepare << + "insert into numbers(value) values(:val)", use(i)); +``` `operator<<` that is a shortcut forwarder to the equivalent operator of the `once` member. Example: - sql << "drop table persons"; +```cpp +sql << "drop table persons"; +``` * `got_data` returns true if the last executed query had non-empty result. * `get_next_sequence_value` returns true if the next value of the sequence with the specified name was generated and returned in its second argument. Unless you can be sure that your program will use only databases that support sequences, consider using this method in conjunction with `get_last_insert_id()` as explained in ["Working with sequences"](beyond.html#sequences) section. @@ -124,28 +123,29 @@ This class contains the following members: * `set_log_stream` and `get_log_stream` functions for setting and getting the current stream object used for basic query logging. By default, it is `NULL`, which means no logging The string value that is actually logged into the stream is one-line verbatim copy of the query string provided by the user, without including any data from the `use` elements. The query is logged exactly once, before the preparation step. * `get_last_query` retrieves the text of the last used query. * `uppercase_column_names` allows to force all column names to uppercase in dynamic row description; this function is particularly useful for portability, since various database servers report column names differently (some preserve case, some change it). +* `get_dummy_from_table` and `get_dummy_from_clause()`: helpers for writing portable DML statements, see [DML helpers](statement.html#dml) for more details. * `get_backend` returns the internal pointer to the concrete backend implementation of the session. This is provided for advanced users that need access to the functionality that is not otherwise available. -*`get_backend_name` is a convenience forwarder to the same function of the backend object. +* `get_backend_name` is a convenience forwarder to the same function of the backend object. See [Connections and simple queries](basics.html) for more examples. -### class connection_parameters +## class connection_parameters The `connection_parameters` class is a simple container for the backend pointer, connection string and any other connection options. It is used together with `session` constructor and `open()` method. +```cpp +class connection_parameters +{ +public: + connection_parameters(); + connection_parameters(backend_factory const & factory, std::string const & connectString); + connection_parameters(std::string const & backendName, std::string const & connectString); + explicit connection_parameters(std::string const & fullConnectString); - class connection_parameters - { - public: - connection_parameters(); - connection_parameters(backend_factory const & factory, std::string const & connectString); - connection_parameters(std::string const & backendName, std::string const & connectString); - explicit connection_parameters(std::string const & fullConnectString); - - void set_option(const char * name, std::string const & value); - bool get_option(const char * name, std::string & value) const - }; - + void set_option(const char * name, std::string const & value); + bool get_option(const char * name, std::string & value) const +}; +``` The methods of this class are: @@ -153,150 +153,164 @@ The methods of this class are: * The other constructors correspond to the similar constructors of the `session` class and specify both the backend, either as a pointer to it or by name, and the connection string. * `set_option` can be used to set the value of an option with the given name. Currently all option values are strings, so if you need to set a numeric option you need to convert it to a string first. If an option with the given name had been already set before, its old value is overwritten. -### class connection_pool +## class connection_pool The `connection_pool` class encapsulates the thread-safe pool of connections and ensures that only one thread at a time has access to any connection that it manages. - class connection_pool - { - public: - explicit connection_pool(std::size_t size); - ~connection_pool(); +```cpp +class connection_pool +{ +public: + explicit connection_pool(std::size_t size); + ~connection_pool(); - session & at(std::size_t pos); + session & at(std::size_t pos); - std::size_t lease(); - bool try_lease(std::size_t & pos, int timeout); - void give_back(std::size_t pos); - }; + std::size_t lease(); + bool try_lease(std::size_t & pos, int timeout); + void give_back(std::size_t pos); +}; +``` The operations of the pool are: -* Constructor that takes the intended size of the pool. After construction, the pool contains regular `session` objects in disconnected state. +* Constructor that takes the intended size of the pool. After construction, the pool contains regular `session` objects in disconnected state. * `at` function that provides direct access to any given entryin the pool. This function is *non-synchronized*. * `lease` function waits until some entry is available (which means that it is not used) and returns the position of that entry in the pool, marking it as *locked*. * `try_lease` acts like `lease`, but allows to set up a time-out (relative, in milliseconds) on waiting. Negative time-out value means no time-out. Returns `true` if the entry was obtained, in which case its position is written to the `pos` parametr, and `false` if no entry was available before the time-out. * `give_back` should be called when the entry on the given position is no longer in use and can be passed to other requesting thread. - -### class transaction +## class transaction The class `transaction` can be used for associating the transaction with some code scope. It is a RAII wrapper for regular transaction operations that automatically rolls back in its destructor *if* the transaction was not explicitly committed before. - class transaction - { - public: - explicit transaction(session & sql); +```cpp +class transaction +{ +public: + explicit transaction(session & sql); - ~transaction(); + ~transaction(); - void commit(); - void rollback(); + void commit(); + void rollback(); - private: - // ... - }; +private: + // ... +}; +``` Note that objects of this class are not notified of other transaction related operations that might be executed by user code explicitly or hidden inside SQL queries. It is not recommended to mix different ways of managing transactions. -### function into +## function into The function `into` is used for binding local output data (in other words, it defines where the results of the query are stored). - template - IT into(T & t); +```cpp +template +IT into(T & t); - template - IT into(T & t, T1 p1); +template +IT into(T & t, T1 p1); - template - IT into(T & t, indicator & ind); +template +IT into(T & t, indicator & ind); - template - IT into(T & t, indicator & ind, T1 p1); +template +IT into(T & t, indicator & ind, T1 p1); - template - IT into(T & t, std::vector & ind); +template +IT into(T & t, std::vector & ind); +``` Example: - int count; - sql << "select count(*) from person", into(count); +```cpp +int count; +sql << "select count(*) from person", into(count); +``` See [Binding local dat](exchange.html#bind_local) for more examples -### function use +## function use The function `use` is used for binding local input data (in other words, it defines where the parameters of the query come from). - template - *IT* use(T & t); +```cpp +template +*IT* use(T & t); - template - *IT* use(T & t, T1 p1); +template +*IT* use(T & t, T1 p1); - template - *IT* use(T & t, indicator & ind); +template +*IT* use(T & t, indicator & ind); - template - *IT* use(T & t, indicator & ind, T1 p1); +template +*IT* use(T & t, indicator & ind, T1 p1); - template - *IT* use(T & t, std::vector const & ind); - - template - *IT* use(T & t, std::vector const & ind, T1 p1); +template +*IT* use(T & t, std::vector const & ind); +template +*IT* use(T & t, std::vector const & ind, T1 p1); +``` Example: - int val = 7; - sql << "insert into numbers(val) values(:val)", use(val); +```cpp +int val = 7; +sql << "insert into numbers(val) values(:val)", use(val); +``` See [Binding local data](exchange.html#bind_local) for more examples. -### class statement +## class statement The `statement` class encapsulates the prepared statement. - class statement - { - public: - statement(session & s); - statement(*IT* const & prep); - ~statement(); +```cpp +class statement +{ +public: + statement(session & s); + statement(*IT* const & prep); + ~statement(); - statement(statement const & other); - void operator=(statement const & other); + statement(statement const & other); + void operator=(statement const & other); - void alloc(); - void bind(values & v); - void exchange(*IT* const & i); - void exchange(*IT* const & u); - void clean_up(); - void bind_clean_up(); + void alloc(); + void bind(values & v); + void exchange(*IT* const & i); + void exchange(*IT* const & u); + void clean_up(); + void bind_clean_up(); - void prepare(std::string const & query); - void define_and_bind(); + void prepare(std::string const & query); + void define_and_bind(); - bool execute(bool withDataExchange = false); - long long get_affected_rows(); - bool fetch(); + bool execute(bool withDataExchange = false); + long long get_affected_rows(); + bool fetch(); - bool got_data() const; + bool got_data() const; - void describe(); - void set_row(row * r); - void exchange_for_rowset(*IT* const & i); + void describe(); + void set_row(row * r); + void exchange_for_rowset(*IT* const & i); - details::statement_backend * get_backend(); - }; + details::statement_backend * get_backend(); +}; +``` This class contains the following members: * Constructor accepting the `session` object. This can be used for later query preparation. Example: - statement stmt(sql); +```cpp +statement stmt(sql); +``` * Constructor accepting the result of using `prepare` on the `session` object, see example provided above for the `session` class. * Copy operations. @@ -311,7 +325,7 @@ This class contains the following members: * `get_affected_rows` function returns the number of rows affected by the last statement. Returns `-1` if it's not implemented by the backend being used. * `fetch` function for retrieving the next portion of the result. Returns `true` if there was new data. * `got_data` return `true` if the most recent execution returned any rows. -* `describe` function for extracting the type information for the result (note: no data is exchanged). This is normally called automatically and only when dynamic resultset binding is used. +* `describe` function for extracting the type information for the result (**Note:** no data is exchanged). This is normally called automatically and only when dynamic resultset binding is used. * `set_row` function for associating the `statement` and `row` objects, normally called automatically. * `exchange_for_rowset` as a special case for binding `rowset` objects. * `get_backend` function that returns the internal pointer to the concrete backend implementation of the statement object. This is provided for advanced users that need access to the functionality that is not otherwise available. @@ -320,81 +334,87 @@ See [Statement preparation and repeated execution](statements.html#preparation) Most of the functions from the `statement` class interface are called automatically, but can be also used explicitly. See [Interfaces](interfaces) for the description of various way to use this interface. -### class procedure +## class procedure The `procedure` class encapsulates the call to the stored procedure and is aimed for higher portability of the client code. - class procedure - { - public: - procedure(*IT* const & prep); +```cpp +class procedure +{ +public: + procedure(*IT* const & prep); - bool execute(bool withDataExchange = false); - bool fetch(); - bool got_data() const; - }; + bool execute(bool withDataExchange = false); + bool fetch(); + bool got_data() const; +}; +``` The constructor expects the result of using `prepare` on the `session` object. See [Stored procedures](statements.html#procedures) for examples. -### class type_conversion +## class type_conversion The `type_conversion` class is a traits class that is supposed to be provided (specialized) by the user for defining conversions to and from one of the basic SOCI types. - template - struct type_conversion - { - typedef T base_type; +```cpp +template +struct type_conversion +{ + typedef T base_type; - static void from_base(base_type const & in, indicator ind, T & out); + static void from_base(base_type const & in, indicator ind, T & out); - static void to_base(T const & in, base_type & out, indicator & ind); - }; + static void to_base(T const & in, base_type & out, indicator & ind); +}; +``` Users are supposed to properly implement the `from_base` and `to_base` functions in their specializations of this template class. See [Extending SOCI to support custom (user-defined) C++ types](exchange.html#custom_types). -### class row +## class row The `row` class encapsulates the data and type information retrieved for the single row when the dynamic rowset binding is used. - class row - { - public: - row(); - ~row(); +```cpp +class row +{ +public: + row(); + ~row(); - void uppercase_column_names(bool forceToUpper); + void uppercase_column_names(bool forceToUpper); - std::size_t size() const; + std::size_t size() const; - indicator get_indicator(std::size_t pos) const; - indicator get_indicator(std::string const & name) const; + indicator get_indicator(std::size_t pos) const; + indicator get_indicator(std::string const & name) const; - column_properties const & get_properties (std::size_t pos) const; - column_properties const & get_properties (std::string const & name) const; + column_properties const & get_properties (std::size_t pos) const; + column_properties const & get_properties (std::string const & name) const; - template - T get(std::size_t pos) const; + template + T get(std::size_t pos) const; - template - T get(std::size_t pos, T const & nullValue) const; + template + T get(std::size_t pos, T const & nullValue) const; - template - T get(std::string const & name) const; + template + T get(std::string const & name) const; - template - T get(std::string const & name, T const & nullValue) const; + template + T get(std::string const & name, T const & nullValue) const; - template - row const & operator>>(T & value) const; + template + row const & operator>>(T & value) const; - void skip(std::size_t num = 1) const; + void skip(std::size_t num = 1) const; - void reset_get_counter() const - }; + void reset_get_counter() const +}; +``` This class contains the following members: @@ -409,16 +429,18 @@ This class contains the following members: See [Dynamic resultset binding](exchange.html#dynamic) for examples. -### class column_properties +## class column_properties The `column_properties` class provides the type and name information about the particular column in a rowset. - class column_properties - { - public: - std::string get_name() const; - data_type get_data_type() const; - }; +```cpp +class column_properties +{ +public: + std::string get_name() const; + data_type get_data_type() const; +}; +``` This class contains the following members: @@ -427,47 +449,49 @@ This class contains the following members: See [Dynamic resultset binding](exchange.html#dynamic) for examples. -### class values +## class values The `values` class encapsulates the data and type information and is used for object-relational mapping. - class values - { - public: - values(); +```cpp +class values +{ +public: + values(); - void uppercase_column_names(bool forceToUpper); + void uppercase_column_names(bool forceToUpper); - indicator get_indicator(std::size_t pos) const; - indicator get_indicator(std::string const & name) const; + indicator get_indicator(std::size_t pos) const; + indicator get_indicator(std::string const & name) const; - template - T get(std::size_t pos) const; + template + T get(std::size_t pos) const; - template - T get(std::size_t pos, T const & nullValue) const; + template + T get(std::size_t pos, T const & nullValue) const; - template - T get(std::string const & name) const; + template + T get(std::string const & name) const; - template - T get(std::string const & name, T const & nullValue) const; + template + T get(std::string const & name, T const & nullValue) const; - template - values const & operator>>(T & value) const; + template + values const & operator>>(T & value) const; - void skip(std::size_t num = 1) const; - void reset_get_counter() const; + void skip(std::size_t num = 1) const; + void reset_get_counter() const; - template - void set(std::string const & name, T const & value, indicator indic = i_ok); + template + void set(std::string const & name, T const & value, indicator indic = i_ok); - template - void set(const T & value, indicator indic = i_ok); + template + void set(const T & value, indicator indic = i_ok); - template - values & operator<<(T const & value); - }; + template + values & operator<<(T const & value); +}; +``` This class contains the same members as the `row` class (with the same meaning) plus: @@ -476,24 +500,26 @@ This class contains the same members as the `row` class (with the same meaning) See [Object-relational mapping](exchange.html#object_relational) for examples. -### class blob +## class blob The `blob` class encapsulates the "large object" functionality. - class blob - { - public: - explicit blob(session & s); - ~blob(); +```cpp +class blob +{ +public: + explicit blob(session & s); + ~blob(); - std::size_t getLen(); - std::size_t read(std::size_t offset, char * buf, std::size_t toRead); - std::size_t write(std::size_t offset, char const * buf, std::size_t toWrite); - std::size_t append(char const * buf, std::size_t toWrite); - void trim(std::size_t newLen); + std::size_t getLen(); + std::size_t read(std::size_t offset, char * buf, std::size_t toRead); + std::size_t write(std::size_t offset, char const * buf, std::size_t toWrite); + std::size_t append(char const * buf, std::size_t toWrite); + void trim(std::size_t newLen); - details::blob_backend * get_backend(); - }; + details::blob_backend * get_backend(); +}; +``` This class contains the following members: @@ -507,45 +533,50 @@ This class contains the following members: See [Large objects (BLOBs)](exchange.html#blob) for more discussion. -### class rowid +## class rowid The `rowid` class encapsulates the "row identifier" object. - class rowid - { - public: - explicit rowid(Session & s); - ~rowid(); +```cpp +class rowid +{ +public: + explicit rowid(Session & s); + ~rowid(); - details::rowid_backend * get_backend(); - }; + details::rowid_backend * get_backend(); +}; +``` This class contains the following members: * Constructor associating the `rowid` object with the `session` object. * `get_backend` function that returns the internal pointer to the concrete backend implementation of the `rowid` object. - -### class backend_factory +## class backend_factory The `backend_factory` class provides the abstract interface for concrete backend factories. - struct backend_factory - { - virtual details::session_backend * make_session( - std::string const & connectString) const = 0; - }; +```cpp +struct backend_factory +{ + virtual details::session_backend * make_session( + std::string const & connectString) const = 0; +}; +``` The only member of this class is the `make_session` function that is supposed to create concrete backend implementation of the session object. Objects of this type are declared by each backend and should be provided to the constructor of the `session` class. In simple programs users do not need to use this class directly, but the example use is: - backend_factory & factory = postgresql; - std::string connectionParameters = "dbname=mydb"; +```cpp +backend_factory & factory = postgresql; +std::string connectionParameters = "dbname=mydb"; - session sql(factory, parameters); +session sql(factory, parameters); +``` -### Simple client interface +## Simple Client Interface The simple client interface is provided with other languages in mind, to allow easy integration of the SOCI library with script interpreters and those languages that have the ability to link directly with object files using the "C" calling convention. @@ -553,34 +584,37 @@ The functionality of this interface is limited and in particular the dynamic row Users of this interface need to explicitly `#include `. - typedef void * session_handle; - session_handle soci_create_session(char const * connectionString); - void soci_destroy_session(session_handle s); +```c +typedef void * session_handle; +session_handle soci_create_session(char const * connectionString); +void soci_destroy_session(session_handle s); - void soci_begin(session_handle s); - void soci_commit(session_handle s); - void soci_rollback(session_handle s); +void soci_begin(session_handle s); +void soci_commit(session_handle s); +void soci_rollback(session_handle s); - int soci_session_state(session_handle s); - char const * soci_session_error_message(session_handle s); +int soci_session_state(session_handle s); +char const * soci_session_error_message(session_handle s); +``` The functions above provide the *session* abstraction with the help of opaque handle. The `soci_session_state` function returns `1` if there was no error during the most recently executed function and `0` otherwise, in which case the `soci_session_error_message` can be used to obtain a human-readable error description. Note that the only function that cannot report all errors this way is `soci_create_session`, which returns `NULL` if it was not possible to create an internal object representing the session. However, if the proxy object was created, but the connection could not be established for whatever reason, the error message can be obtained in the regular way. +```c +typedef void *blob_handle; +blob_handle soci_create_blob(session_handle s); +void soci_destroy_blob(blob_handle b); - typedef void *blob_handle; - blob_handle soci_create_blob(session_handle s); - void soci_destroy_blob(blob_handle b); +int soci_blob_get_len(blob_handle b); +int soci_blob_read(blob_handle b, int offset, char *buf, int toRead); +int soci_blob_write(blob_handle b, int offset, char const *buf, int toWrite); +int soci_blob_append(blob_handle b, char const *buf, int toWrite); +int soci_blob_trim(blob_handle b, int newLen); - int soci_blob_get_len(blob_handle b); - int soci_blob_read(blob_handle b, int offset, char *buf, int toRead); - int soci_blob_write(blob_handle b, int offset, char const *buf, int toWrite); - int soci_blob_append(blob_handle b, char const *buf, int toWrite); - int soci_blob_trim(blob_handle b, int newLen); - - int soci_blob_state(blob_handle b); - char const * soci_blob_error_message(blob_handle b); +int soci_blob_state(blob_handle b); +char const * soci_blob_error_message(blob_handle b); +``` The functions above provide the *blob* abstraction with the help of opaque handle. The `soci_blob_state` function returns `1` if there was no error during the most recently executed function and `0` otherwise, in which case the `soci_session_error_message` can be used to obtain a human-readable error description. @@ -588,117 +622,132 @@ For easy error testing, functions `soci_blob_read`, `soci_blob_write`, `soci_blo Note that the only function that cannot report all errors this way is `soci_create_blob`, which returns `NULL` if it was not possible to create an internal object representing the blob. +```c +typedef void * statement_handle; +statement_handle soci_create_statement(session_handle s); +void soci_destroy_statement(statement_handle st); - typedef void * statement_handle; - statement_handle soci_create_statement(session_handle s); - void soci_destroy_statement(statement_handle st); - - int soci_statement_state(statement_handle s); - char const * soci_statement_error_message(statement_handle s); +int soci_statement_state(statement_handle s); +char const * soci_statement_error_message(statement_handle s); +``` The functions above create and destroy the statement object. If the statement cannot be created by the `soci_create_statement` function, the error condition is set up in the related session object; for all other functions the error condition is set in the statement object itself. +```c +int soci_into_string (statement_handle st); +int soci_into_int (statement_handle st); +int soci_into_long_long(statement_handle st); +int soci_into_double (statement_handle st); +int soci_into_date (statement_handle st); +int soci_into_blob (statement_handle st); - int soci_into_string (statement_handle st); - int soci_into_int (statement_handle st); - int soci_into_long_long(statement_handle st); - int soci_into_double (statement_handle st); - int soci_into_date (statement_handle st); - int soci_into_blob (statement_handle st); +int soci_into_string_v (statement_handle st); +int soci_into_int_v (statement_handle st); +int soci_into_long_long_v(statement_handle st); +int soci_into_double_v (statement_handle st); +int soci_into_date_v (statement_handle st); +``` - int soci_into_string_v (statement_handle st); - int soci_into_int_v (statement_handle st); - int soci_into_long_long_v(statement_handle st); - int soci_into_double_v (statement_handle st); - int soci_into_date_v (statement_handle st); - -These functions create new data items for storing query results (*into elements*). These elements can be later identified by their position, which is counted from 0. For convenience, these function return the position of the currently added element. In case of error, `-1` is returned and the error condition is set in the statement object. +These functions create new data items for storing query results (*into elements*). These elements can be later identified by their position, which is counted from 0. For convenience, these function return the position of the currently added element. In case of error, `-1` is returned and the error condition is set in the statement object. The `_v` versions create a `vector` into elements, which can be used to retrieve whole arrays of results. - int soci_get_into_state(statement_handle st, int position); - int soci_get_into_state_v(statement_handle st, int position, int index); +```c +int soci_get_into_state(statement_handle st, int position); +int soci_get_into_state_v(statement_handle st, int position, int index); +``` This function returns `1` if the into element at the given position has non-null value and `0` otherwise. The `_v` version works with `vector` elements and expects an array index. - char const * soci_get_into_string (statement_handle st, int position); - int soci_get_into_int (statement_handle st, int position); - long long soci_get_into_long_long(statement_handle st, int position); - double soci_get_into_double (statement_handle st, int position); - char const * soci_get_into_date (statement_handle st, int position); - blob_handle soci_get_into_blob (statement_handle st, int position); +```c +char const * soci_get_into_string (statement_handle st, int position); +int soci_get_into_int (statement_handle st, int position); +long long soci_get_into_long_long(statement_handle st, int position); +double soci_get_into_double (statement_handle st, int position); +char const * soci_get_into_date (statement_handle st, int position); +blob_handle soci_get_into_blob (statement_handle st, int position); - char const * soci_get_into_string_v (statement_handle st, int position, int index); - int soci_get_into_int_v (statement_handle st, int position, int index); - long long soci_get_into_long_long_v(statement_handle st, int position, int index); - double soci_get_into_double_v (statement_handle st, int position, int index); - char const * soci_get_into_date_v (statement_handle st, int position, int index); +char const * soci_get_into_string_v (statement_handle st, int position, int index); +int soci_get_into_int_v (statement_handle st, int position, int index); +long long soci_get_into_long_long_v(statement_handle st, int position, int index); +double soci_get_into_double_v (statement_handle st, int position, int index); +char const * soci_get_into_date_v (statement_handle st, int position, int index); +``` The functions above allow to retrieve the current value of the given into element. ---- -Note: the `date` function returns the date value in the "`YYYY MM DD HH mm ss`" string format. - void soci_use_string (statement_handle st, char const * name); - void soci_use_int (statement_handle st, char const * name); - void soci_use_long_long(statement_handle st, char const * name); - void soci_use_double (statement_handle st, char const * name); - void soci_use_date (statement_handle st, char const * name); - void soci_use_blob (statement_handle st, char const * name); +**Note:** The `date` function returns the date value in the "`YYYY MM DD HH mm ss`" string format. - void soci_use_string_v (statement_handle st, char const * name); - void soci_use_int_v (statement_handle st, char const * name); - void soci_use_long_long_v(statement_handle st, char const * name); - void soci_use_double_v (statement_handle st, char const * name); - void soci_use_date_v (statement_handle st, char const * name); ---- +```c +void soci_use_string (statement_handle st, char const * name); +void soci_use_int (statement_handle st, char const * name); +void soci_use_long_long(statement_handle st, char const * name); +void soci_use_double (statement_handle st, char const * name); +void soci_use_date (statement_handle st, char const * name); +void soci_use_blob (statement_handle st, char const * name); + +void soci_use_string_v (statement_handle st, char const * name); +void soci_use_int_v (statement_handle st, char const * name); +void soci_use_long_long_v(statement_handle st, char const * name); +void soci_use_double_v (statement_handle st, char const * name); +void soci_use_date_v (statement_handle st, char const * name); +``` The functions above allow to create new data elements that will be used to provide data to the query (*use elements*). The new elements can be later identified by given name, which must be unique for the given statement. - void soci_set_use_state(statement_handle st, char const * name, int state); +```c +void soci_set_use_state(statement_handle st, char const * name, int state); +``` The `soci_set_use_state` function allows to set the state of the given use element. If the `state` parameter is set to non-zero the use element is considered non-null (which is also the default state after creating the use element). - int soci_use_get_size_v(statement_handle st); - void soci_use_resize_v (statement_handle st, int new_size); +```c +int soci_use_get_size_v(statement_handle st); +void soci_use_resize_v (statement_handle st, int new_size); +``` These functions get and set the size of vector use elements (see comments for vector into elements above). - void soci_set_use_string (statement_handle st, char const * name, char const * val); - void soci_set_use_int (statement_handle st, char const * name, int val); - void soci_set_use_long_long(statement_handle st, char const * name, long long val); - void soci_set_use_double (statement_handle st, char const * name, double val); - void soci_set_use_date (statement_handle st, char const * name, char const * val); - void soci_set_use_blob (statement_handle st, char const * name, blob_handle blob); +```c +void soci_set_use_string (statement_handle st, char const * name, char const * val); +void soci_set_use_int (statement_handle st, char const * name, int val); +void soci_set_use_long_long(statement_handle st, char const * name, long long val); +void soci_set_use_double (statement_handle st, char const * name, double val); +void soci_set_use_date (statement_handle st, char const * name, char const * val); +void soci_set_use_blob (statement_handle st, char const * name, blob_handle blob); - void soci_set_use_state_v (statement_handle st, char const * name, int index, int state); - void soci_set_use_string_v (statement_handle st, char const * name, int index, char const * val); - void soci_set_use_int_v (statement_handle st, char const * name, int index, int val); - void soci_set_use_long_long_v(statement_handle st, char const * name, int index, long long val); - void soci_set_use_double_v (statement_handle st, char const * name, int index, double val); - void soci_set_use_date_v (statement_handle st, char const * name, int index, char const * val); +void soci_set_use_state_v (statement_handle st, char const * name, int index, int state); +void soci_set_use_string_v (statement_handle st, char const * name, int index, char const * val); +void soci_set_use_int_v (statement_handle st, char const * name, int index, int val); +void soci_set_use_long_long_v(statement_handle st, char const * name, int index, long long val); +void soci_set_use_double_v (statement_handle st, char const * name, int index, double val); +void soci_set_use_date_v (statement_handle st, char const * name, int index, char const * val); +``` The functions above set the value of the given use element, for both single and vector elements. ---- -Note: the expected format for the data values is "`YYYY MM DD HH mm ss`". +**Note:** The expected format for the data values is "`YYYY MM DD HH mm ss`". - int soci_get_use_state (statement_handle st, char const * name); - char const * soci_get_use_string (statement_handle st, char const * name); - int soci_get_use_int (statement_handle st, char const * name); - long long soci_get_use_long_long(statement_handle st, char const * name); - double soci_get_use_double (statement_handle st, char const * name); - char const * soci_get_use_date (statement_handle st, char const * name); - blob_handle soci_get_use_blob (statement_handle st, char const * name); +```c +int soci_get_use_state (statement_handle st, char const * name); +char const * soci_get_use_string (statement_handle st, char const * name); +int soci_get_use_int (statement_handle st, char const * name); +long long soci_get_use_long_long(statement_handle st, char const * name); +double soci_get_use_double (statement_handle st, char const * name); +char const * soci_get_use_date (statement_handle st, char const * name); +blob_handle soci_get_use_blob (statement_handle st, char const * name); +``` These functions allow to inspect the state and value of named use elements. ---- -Note: these functions are provide only for single use elements, not for vectors; the rationale for this is that modifiable use elements are not supported for bulk operations. - void soci_prepare(statement_handle st, char const * query); - int soci_execute(statement_handle st, int withDataExchange); - int soci_fetch(statement_handle st); - int soci_got_data(statement_handle st); +***Note:*** these functions are provide only for single use elements, not for vectors; the rationale for this is that modifiable use elements are not supported for bulk operations. + +```c +void soci_prepare(statement_handle st, char const * query); +int soci_execute(statement_handle st, int withDataExchange); +int soci_fetch(statement_handle st); +int soci_got_data(statement_handle st); +``` The functions above provide the core execution functionality for the statement object and their meaning is equivalent to the respective functions in the core C++ interface described above. - diff --git a/docs/backends/db2.md b/docs/backends/db2.md index e24fabb8ba..e1aef851b6 100644 --- a/docs/backends/db2.md +++ b/docs/backends/db2.md @@ -1,90 +1,93 @@ -## DB2 Backend Reference +# DB2 Backend Reference -* [Prerequisites](#prerequisites) - * [Supported Versions](#versions) - * [Tested Platforms](#platforms) - * [Required Client Libraries](#required) -* [Connecting to the Database](#connecting) -* [SOCI Feature Support](#features) - * [Dynamic Binding](#dynamic) - * [Binding by Name](#name) - * [Bulk Operations](#bulk) - * [Transactions](#transactions) - * [BLOB Data Type](#blob) - * [RowID Data Type](#rowid) - * [Nested Statements](#nested) - * [Stored Procedures](#stored) -* [Accessing the Native Database API](#native) -* [Backend-specific Extensions](#extensions) -* [Configuration options](#config) +SOCI backend for accessing IBM DB2 database. -### Prerequisites -#### Supported Versions +## Prerequisites -The SOCI DB2 backend. +### Supported Versions -#### Tested Platforms +See [Tested Platforms](#tested-platforms). - - - - - - - - - - -
DB2 versionOperating SystemCompiler
-Linux PPC64GCC
9.1LinuxGCC
9.5LinuxGCC
9.7LinuxGCC
10.1LinuxGCC
10.1Windows 8Visual Studio 2012
+### Tested Platforms -#### Required Client Libraries +|DB2 |OS|Compiler| +|--- |--- |--- | +|-|Linux PPC64|GCC| +|9.1|Linux|GCC| +|9.5|Linux|GCC| +|9.7|Linux|GCC| +|10.1|Linux|GCC| +|10.1|Windows 8|Visual Studio 2012| -The SOCI DB2 backend requires IBM DB2 Call Level Interface (CLI) library. +### Required Client Libraries -#### Connecting to the Database +The SOCI DB2 backend requires client library from the [IBM Data Server Driver Package (DS Driver)](https://www-01.ibm.com/support/docview.wss?uid=swg21385217). + +## Connecting to the Database On Unix, before using the DB2 backend please make sure, that you have sourced DB2 profile into your environment: - . ~/db2inst1/sqllib/db2profile +```console +. ~/db2inst1/sqllib/db2profile +``` -To establish a connection to the DB2 database, create a session object using the DB2 backend factory together with the database file name: +To establish a connection to the DB2 database, create a session object using the DB2 +backend factory together with the database file name: - soci::session sql(soci::db2, "your DB2 connection string here"); +```cpp +soci::session sql(soci::db2, "your DB2 connection string here"); +``` -### SOCI Feature Support +## SOCI Feature Support -#### Dynamic Binding +### Dynamic Binding TODO -#### Bulk Operations +### Bulk Operations Supported, but with caution as it hasn't been extensively tested. -#### Transactions +### Transactions Currently, not supported. -#### BLOB Data Type +### BLOB Data Type Currently, not supported. -#### Nested Statements +### Nested Statements -Nesting statements are not processed by SOCI in any special way and they work as implemented by the DB2 database. +Nesting statements are not processed by SOCI in any special way and they work as implemented +by the DB2 database. -#### Stored Procedures +### Stored Procedures -Stored procedures are supported, with CALL statement. +Stored procedures are supported, with `CALL` statement. -### Acessing the native database API +## Native API Access TODO -### Backend-specific extensions +## Backend-specific extensions None. -### Configuration options +## Configuration options -None. \ No newline at end of file +This backend supports `db2_option_driver_complete` option which can be passed to it via +`connection_parameters` class. The value of this option is passed to `SQLDriverConnect()` +function as "driver completion" parameter and so must be one of `SQL_DRIVER_XXX` values, +in the string form. The default value of this option is `SQL_DRIVER_PROMPT` meaning +that the driver will query the user for the user name and/or the password if they are +not stored together with the connection. If this is undesirable for some reason, +you can use `SQL_DRIVER_NOPROMPT` value for this option to suppress showing the message box: + +```cpp +connection_parameters parameters("db2", "DSN=sample"); +parameters.set_option(db2_option_driver_complete, "0" /* SQL_DRIVER_NOPROMPT */); +session sql(parameters); +``` + +Note, `db2_option_driver_complete` controls driver completion specific to the IBM DB2 driver +for ODBC and CLI. diff --git a/docs/backends/firebird.md b/docs/backends/firebird.md index f1a92bd15a..30275a7b20 100644 --- a/docs/backends/firebird.md +++ b/docs/backends/firebird.md @@ -1,58 +1,45 @@ -## Firebird Backend Reference +# Firebird Backend Reference -* [Prerequisites](#prerequisites) - * [Supported Versions](#versions) - * [Tested Platforms](#platforms) - * [Required Client Libraries](#required) -* [Connecting to the Database](#connecting) -* [SOCI Feature Support](#features) - * [Dynamic Binding](#dynamic) - * [Binding by Name](#name) - * [Bulk Operations](#bulk) - * [Transactions](#transactions) - * [BLOB Data Type](#blob) - * [RowID Data Type](#rowid) - * [Nested Statements](#nested) - * [Stored Procedures](#stored) -* [Accessing the Native Database API](#native) -* [Backend-specific Extensions](#extensions) -* [Configuration options](#configuration) - * [FirebirdSOCIError](#firebirdsocierror) +SOCI backend for accessing Firebird database. -### Prerequisites +## Prerequisites -#### Supported Versions +### Supported Versions -The SOCI Firebird backend supports versions of Firebird from 1.5 to 2.5 and can be used with either the client-server or embedded Firebird libraries. The former is the default, to select the latter set SOCI_FIREBIRD_EMBEDDED CMake option to ON value when building. +The SOCI Firebird backend supports versions of Firebird from 1.5 to 2.5 and can be used with +either the client-server or embedded Firebird libraries. +The former is the default, to select the latter set `SOCI_FIREBIRD_EMBEDDED` CMake option to `ON` +value when building. -#### Tested Platforms +### Tested Platforms - - - - - - - - -
Firebird versionOperating SystemCompiler
1.5.2.4731SunOS 5.10g++ 3.4.3
1.5.2.4731Windows XPVisual C++ 8.0
1.5.3.4870Windows XPVisual C++ 8.0 Professional
2.5.2.26540Debian GNU/Linux 7g++ 4.7.2
+|Firebird |OS|Compiler| +|--- |--- |--- | +|1.5.2.4731|SunOS 5.10|g++ 3.4.3| +|1.5.2.4731|Windows XP|Visual C++ 8.0| +|1.5.3.4870|Windows XP|Visual C++ 8.0 Professional| +|2.5.2.26540|Debian GNU/Linux 7|g++ 4.7.2| -#### Required Client Libraries +### Required Client Libraries The Firebird backend requires Firebird's `libfbclient` client library. +For example, on Ubuntu Linux, for example, `firebird-dev` package and its dependencies are required. -### Connecting to the Database +## Connecting to the Database -To establish a connection to a Firebird database, create a Session object using the firebird backend factory together with a connection string: +To establish a connection to a Firebird database, create a Session object using the firebird +backend factory together with a connection string: - BackEndFactory const &backEnd = firebird; - Session sql(backEnd, - "service=/usr/local/firbird/db/test.fdb user=SYSDBA password=masterkey"); +```cpp +BackEndFactory const &backEnd = firebird; +session sql(backEnd, "service=/usr/local/firbird/db/test.fdb user=SYSDBA password=masterkey"); +``` or simply: - Session sql(firebird, - "service=/usr/local/firbird/db/test.fdb user=SYSDBA password=masterkey"); +```cpp +session sql(firebird, "service=/usr/local/firbird/db/test.fdb user=SYSDBA password=masterkey"); +``` The set of parameters used in the connection string for Firebird is: @@ -62,139 +49,106 @@ The set of parameters used in the connection string for Firebird is: * role * charset -The following parameters have to be provided as part of the connection string : *service*, *user*, *password*. Role and charset parameters are optional. +The following parameters have to be provided as part of the connection string : *service*, *user*, +*password*. Role and charset parameters are optional. -Once you have created a `Session` object as shown above, you can use it to access the database, for example: +Once you have created a `session` object as shown above, you can use it to access the database, for example: - int count; - sql << "select count(*) from user_tables", into(count); +```cpp +int count; +sql << "select count(*) from user_tables", into(count); +``` -(See the [SOCI basics](../basics.html) and [exchanging data](../exchange.html) documentation for general information on using the `Session` class.) +(See the [SOCI basics](../basics.html) and [exchanging data](../exchange.html) documentation +for general information on using the `session` class.) +## SOCI Feature Support -### SOCI Feature Support +### Dynamic Binding -#### Dynamic Binding +The Firebird backend supports the use of the SOCI `row` class, which facilitates retrieval of data whose +type is not known at compile time. -The Firebird backend supports the use of the SOCI `Row` class, which facilitates retrieval of data whose type is not known at compile time. +When calling `row::get()`, the type you should pass as T depends upon the underlying database type. +For the Firebird backend, this type mapping is: -When calling `Row::get()`, the type you should pass as T depends upon the underlying database type. For the Firebird backend, this type mapping is: +|Firebird Data Type|SOCI Data Type|`row::get` specializations| +|--- |--- |--- | +|numeric, decimal (where scale > 0)|eDouble|double| +|numeric, decimal [^1] (where scale = 0)|eInteger, eDouble|int, double| +|double precision, float|eDouble|double| +|smallint, integer|eInteger|int| +|char, varchar|eString|std::string| +|date, time, timestamp|eDate|std::tm| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Firebird Data TypeSOCI Data TypeRow::get<T> specializations
numeric, decimal
*(where scale > 0)*
eDoubledouble
numeric, decimal [1]
*(where scale = 0)*
eInteger, eDoubleint, double
double precision, floateDoubledouble
smallint, integereIntegerint
char, varchareStringstd::string
date, time, timestampeDatestd::tm
- - [1]  There is also 64bit integer type for larger values which is +[^1] There is also 64bit integer type for larger values which is currently not supported. -(See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `Row` class.) +(See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information +on using the `Row` class.) +### Binding by Name -#### Binding by Name - -In addition to [binding by position](../exchange.html#bind_position), the Firebird backend supports [binding by name](../exchange.html#bind_name), via an overload of the `use()` function: +In addition to [binding by position](../exchange.html#bind_position), the Firebird backend supports +[binding by name](../exchange.html#bind_name), via an overload of the `use()` function: int id = 7; sql << "select name from person where id = :id", use(id, "id") -It should be noted that parameter binding by name is supported only by means of emulation, since the underlying API used by the backend doesn't provide this feature. +It should be noted that parameter binding by name is supported only by means of emulation, +since the underlying API used by the backend doesn't provide this feature. -#### Bulk Operations +### Bulk Operations -The Firebird backend has full support for SOCI's [bulk operations](../statements.html#bulk) interface. This feature is also supported by emulation. +The Firebird backend has full support for SOCI [bulk operations](../statements.html#bulk) interface. +This feature is also supported by emulation. -#### Transactions +### Transactions -[Transactions](../statements.html#transactions) are also fully supported by the Firebird backend. In fact, an implicit transaction is always started when using this backend if one hadn't been started by explicitly calling begin() before. The current transaction is automatically committed in `Session's` destructor. +[Transactions](../statements.html#transactions) are also fully supported by the Firebird backend. +In fact, an implicit transaction is always started when using this backend if one hadn't been +started by explicitly calling `begin()` before. The current transaction is automatically +committed in `session` destructor. -#### BLOB Data Type +### BLOB Data Type -The Firebird backend supports working with data stored in columns of type Blob, via SOCI's `[BLOB](../exchange.html#blob)` class. +The Firebird backend supports working with data stored in columns of type Blob, +via SOCI `[BLOB](../exchange.html#blob)` class. -It should by noted, that entire Blob data is fetched from database to allow random read and write access. This is because Firebird itself allows only writing to a new Blob or reading from existing one - modifications of existing Blob means creating a new one. Firebird backend hides those details from user. +It should by noted, that entire Blob data is fetched from database to allow random read and write access. +This is because Firebird itself allows only writing to a new Blob or reading from existing one - +modifications of existing Blob means creating a new one. +Firebird backend hides those details from user. -#### RowID Data Type +### RowID Data Type This feature is not supported by Firebird backend. -#### Nested Statements +### Nested Statements This feature is not supported by Firebird backend. -#### Stored Procedures +### Stored Procedures -Firebird stored procedures can be executed by using SOCI's [Procedure](../statements.html#procedures) class. +Firebird stored procedures can be executed by using SOCI [Procedure](../statements.html#procedures) class. +## Native API Access -### Acessing the native database API - -SOCI provides access to underlying datbabase APIs via several getBackEnd() functions, as described in the [beyond SOCI](../beyond.html) documentation. +SOCI provides access to underlying datbabase APIs via several getBackEnd() functions, +as described in the [beyond SOCI](../beyond.html) documentation. The Firebird backend provides the following concrete classes for navite API access: - - - - - - - - - - - - - - - - - - - - - code>FirebirdRowIDBackEnd - - -
Accessor FunctionConcrete Class
SessionBackEnd* Session::getBackEnd()FirebirdSessionBackEnd
StatementBackEnd* Statement::getBackEnd()FirebirdStatementBackEnd
BLOBBackEnd* BLOB::getBackEnd()FirebirdBLOBBackEnd
RowIDBackEnd* RowID::getBackEnd()
+|Accessor Function|Concrete Class| +|--- |--- | +|SessionBackEnd* Session::getBackEnd()|FirebirdSessionBackEnd| +|StatementBackEnd* Statement::getBackEnd()|FirebirdStatementBackEnd| +|BLOBBackEnd* BLOB::getBackEnd()|FirebirdBLOBBackEnd| +|RowIDBackEnd* RowID::getBackEnd()| -### Backend-specific extensions +## Backend-specific extensions -#### FirebirdSOCIError +### FirebirdSOCIError -The Firebird backend can throw instances of class `FirebirdSOCIError`, which is publicly derived from `SOCIError` and has an additional public `status_` member containing the Firebird status vector. \ No newline at end of file +The Firebird backend can throw instances of class `FirebirdSOCIError`, which is publicly derived +from `SOCIError` and has an additional public `status_` member containing the Firebird status vector. diff --git a/docs/backends/index.md b/docs/backends/index.md index cbb8cec5ee..84158b6a15 100644 --- a/docs/backends/index.md +++ b/docs/backends/index.md @@ -1,101 +1,14 @@ -## Existing backends and supported platforms +# Supported Backends and Features -### Supported Features +Follow the links to learn more about each backend and detailed supported features. -(Follow the links to learn more about each backend.) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OraclePostgreSQLMySQLSQLite3FirebirdODBCDB2
Binding by NameYESYES (>=8.0)YESYESYESYESYES
Dynamic BindingYESYESYESYESYESYES
Bulk OperationsYESYESYESYESYESYESYES
TransactionsYESYESYES - (with servers that support them, usually >= 4.0)YESYESYESYES
BLOB Data TypeYESYESMySQL's BLOB type is mapped to std::stringYESYESNONO
RowID Data TypeYESYESNONONONONO
Nested StatementsYESNONONONONOYES
Stored ProceduresYESYESNO (but stored functions, YES)NOYESNOYES
+|Oracle|PostgreSQL|MySQL|SQLite3|Firebird|ODBC|DB2| +|--- |--- |--- |--- |--- |--- |--- | +|Binding by Name|YES|YES (>=8.0)|YES|YES|YES|YES|YES| +|Dynamic Binding|YES|YES|YES|YES|YES|YES| +|Bulk Operations|YES|YES|YES|YES|YES|YES|YES| +|Transactions|YES|YES|YES (>=4.0)|YES|YES|YES|YES| +|BLOB Data Type|YES|YES|YES (mapped to `std::string`)|YES|YES|NO|NO| +|RowID Data Type|YES|YES|NO|NO|NO|NO|NO| +|Nested Statements|YES|NO|NO|NO|NO|NO|YES| +|Stored Procedures|YES|YES|NO (but stored functions, YES)|NO|YES|NO|YES| diff --git a/docs/backends/mysql.md b/docs/backends/mysql.md index 0b289de17a..33df4e82f8 100644 --- a/docs/backends/mysql.md +++ b/docs/backends/mysql.md @@ -1,53 +1,31 @@ -## MySQL Backend Reference +# MySQL Backend Reference -* [Prerequisites](#prerequisites) - * [Supported Versions](#versions) - * [Tested Platforms](#tested) - * [Required Client Libraries](#required) -* [Connecting to the Database](#connecting) -* [SOCI Feature Support](#support) - * [Dynamic Binding](#dynamic) - * [Binding by Name](#name) - * [Bulk Operations](#bulk) - * [Transactions](#transactions) - * [BLOB Data Type](#blob) - * [RowID Data Type](#rowid) - * [Nested Statements](#nested) - * [Stored Procedures](#stored) -* [Accessing the Native Database API](#native) -* [Backend-specific Extensions](#extensions) -* [Configuration options](#configuration) +SOCI backend for accessing MySQL database. +## Prerequisites +### Supported Versions -### Prerequisites +The SOCI MySQL backend should in principle work with every version of MySQL 5.x. +Some of the features (transactions, stored functions) are not available when MySQL server doesn't support them. -#### Supported Versions +### Tested Platforms -The SOCI MySQL backend should in principle work with every version of MySQL 5.x. Some of the features (transactions, stored functions) are not available when MySQL server doesn't support them. +|MySQL|OS|Compiler| +|--- |--- |--- | +|8.0.1|Windows 10|Visual Studio 2017 (15.3.3)| +|5.5.28|OS X 10.8.2|Apple LLVM version 4.2 (clang-425.0.24)| +|5.0.96|Ubuntu 8.04.4 LTS (Hardy Heron)|g++ (GCC) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)| -#### Tested Platforms +### Required Client Libraries - - - - - - - - -
MySQL versionOperating SystemCompiler
5.5.28OS X 10.8.2Apple LLVM version 4.2 -(clang-425.0.24)
5.0.96Ubuntu 8.04.4 LTS (Hardy Heron)g++ (GCC) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)
- -#### Required Client Libraries - -The SOCI MySQL backend requires MySQL's `libmysqlclient` client library. +The SOCI MySQL backend requires MySQL's `libmysqlclient` client library from the [MySQL Connector/C](https://dev.mysql.com/downloads/connector/c/). Note that the SOCI library itself depends also on `libdl`, so the minimum set of libraries needed to compile a basic client program is: -lsoci_core -lsoci_mysql -ldl -lmysqlclient -### Connecting to the Database +## Connecting to the Database To establish a connection to a MySQL server, create a `session` object using the `mysql` backend factory together with a connection string: @@ -77,69 +55,30 @@ Once you have created a `session` object as shown above, you can use it to acces int count; sql << "select count(*) from invoices", into(count); - (See the [SOCI basics]("../basics.html) and [exchanging data](../exchange.html) documentation for general information on using the `session` class.) +## SOCI Feature Support -### SOCI Feature Support - -#### Dynamic Binding +### Dynamic Binding The MySQL backend supports the use of the SOCI `row` class, which facilitates retrieval of data which type is not known at compile time. When calling `row::get<T>()`, the type you should pass as `T` depends upon the underlying database type. For the MySQL backend, this type mapping is: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MySQL Data TypeSOCI Data Typerow::get<T> specializations
FLOAT, DOUBLE, DECIMAL and synonymsdt_doubledouble
TINYINT, TINYINT UNSIGNED, SMALLINT, SMALLINT UNSIGNED, INTdt_integerint
INT UNSIGNEDdt_long_longlong long or unsigned
BIGINTdt_long_longlong long
BIGINT UNSIGNEDdt_unsigned_long_longunsigned long long
CHAR, VARCHAR, BINARY, VARBINARY, TINYBLOB, MEDIUMBLOB, BLOB, - LONGBLOB, TINYTEXT, MEDIUMTEXT, TEXT, LONGTEXT, ENUMdt_stringstd::string
TIMESTAMP (works only with MySQL >= 5.0), DATE, - TIME, DATETIMEdt_datestd::tm
+|MySQL Data Type|SOCI Data Type|`row::get` specializations| +|--- |--- |--- | +|FLOAT, DOUBLE, DECIMAL and synonyms|dt_double|double| +|TINYINT, TINYINT UNSIGNED, SMALLINT, SMALLINT UNSIGNED, INT|dt_integer|int| +|INT UNSIGNED|dt_long_long|long long or unsigned| +|BIGINT|dt_long_long|long long| +|BIGINT UNSIGNED|dt_unsigned_long_long|unsigned long long| +|CHAR, VARCHAR, BINARY, VARBINARY, TINYBLOB, MEDIUMBLOB, BLOB,LONGBLOB, TINYTEXT, MEDIUMTEXT, TEXT, LONGTEXT, ENUM|dt_string|std::string| +|TIMESTAMP (works only with MySQL >= 5.0), DATE, TIME, DATETIME|dt_date|std::tm| (See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `Row` class.) -#### Binding by Name +### Binding by Name In addition to [binding by position](../exchange.html#bind_position), the MySQL backend supports [binding by name](../exchange.html#bind_name), via an overload of the `use()` function: @@ -149,56 +88,43 @@ In addition to [binding by position](../exchange.html#bind_position), the MySQL It should be noted that parameter binding of any kind is supported only by means of emulation, since the underlying API used by the backend doesn't provide this feature. -#### Bulk Operations +### Bulk Operations [Transactions](../statements.html#transactions) are also supported by the MySQL backend. Please note, however, that transactions can only be used when the MySQL server supports them (it depends on options used during the compilation of the server; typically, but not always, servers >=4.0 support transactions and earlier versions do not) and only with appropriate table types. -#### BLOB Data Type +### BLOB Data Type SOCI `blob` interface is not supported by the MySQL backend. Note that this does not mean you cannot use MySQL's `BLOB` types. They can be selected using the usual SQL syntax and read into `std::string` on the C++ side, so no special interface is required. -#### RowID Data Type +### RowID Data Type The `rowid` functionality is not supported by the MySQL backend. -#### Nested Statements +### Nested Statements Nested statements are not supported by the MySQL backend. -#### Stored Procedures +### Stored Procedures MySQL version 5.0 and later supports two kinds of stored routines: stored procedures and stored functions (for details, please consult the [procedure MySQL documentation](http://dev.mysql.com/doc/refman/5.0/en/stored-procedures.html)). Stored functions can be executed by using SOCI's [procedure class](../statements.html#procedures). There is currently no support for stored procedures. - -### Accessing the native database API +## Native API Access SOCI provides access to underlying datbabase APIs via several `get_backend()` functions, as described in the [Beyond SOCI](../beyond.html) documentation. The MySQL backend provides the following concrete classes for native API access: - - - - - - - - - - - - - - - -
Accessor FunctionConcrete Class
session_backend * session::get_backend()mysql_session_backend
statement_backend * statement::get_backend()mysql_statement_backend
+|Accessor Function|Concrete Class| +|--- |--- | +|session_backend * session::get_backend()|mysql_session_backend| +|statement_backend * statement::get_backend()|mysql_statement_backend| -### Backend-specific extensions +## Backend-specific extensions None. -### Configuration options +## Configuration options -None. \ No newline at end of file +None. diff --git a/docs/backends/odbc.md b/docs/backends/odbc.md index 4d56979ac9..013940512a 100644 --- a/docs/backends/odbc.md +++ b/docs/backends/odbc.md @@ -1,252 +1,173 @@ -## ODBC Backend Reference +# ODBC Backend Reference -* [Prerequisites](#prerequisites) - * [Supported Versions](#versions) - * [Tested Platforms](#tested) - * [Required Client Libraries](#required) -* [Connecting to the Database](#connecting) -* [SOCI Feature Support](#support) - * [Dynamic Binding](#dynamic) - * [Binding by Name](#name) - * [Bulk Operations](#bulk) - * [Transactions](#transactions) - * [BLOB Data Type](#blob) - * [RowID Data Type](#rowid) - * [Nested Statements](#nested) - * [Stored Procedures](#stored) -* [Accessing the Native Database API](#native) -* [Backend-specific Extensions](#extensions) - * [odbc_soci_error](#odbcsocierror) -* [Configuration options](#configuration) +SOCI backend for accessing variety of databases via ODBC API. +## Prerequisites -### Prerequisites - -#### Supported Versions +### Supported Versions The SOCI ODBC backend is supported for use with ODBC 3. -#### Tested Platforms +### Tested Platforms - - - - - - - - - - - -
ODBC versionOperating SystemCompiler
3Linux (Ubuntu 12.04)g++ 4.6.3
3Linux (Ubuntu 12.04)clang 3.2
3.8Windows 8Visual Studio 2012
3Windows 7Visual Studio 2010
3Windows XPVisual Studio 2005 (express)
3Windows XPVisual C++ 8.0 Professional
3Windows XPg++ 3.3.4 (Cygwin)
+|ODBC|OS|Compiler| +|--- |--- |--- | +|3|Linux (Ubuntu 12.04)|g++ 4.6.3| +|3|Linux (Ubuntu 12.04)|clang 3.2| +|3.8|Windows 8|Visual Studio 2012| +|3|Windows 7|Visual Studio 2010| +|3|Windows XP|Visual Studio 2005 (express)| +|3|Windows XP|Visual C++ 8.0 Professional| +|3|Windows XP|g++ 3.3.4 (Cygwin)| -#### Required Client Libraries +### Required Client Libraries The SOCI ODBC backend requires the ODBC client library. -### Connecting to the Database +## Connecting to the Database To establish a connection to the ODBC database, create a Session object using the `ODBC` backend factory together with a connection string: - backend_factory const& backEnd = odbc; - session sql(backEnd, "filedsn=c:\\my.dsn"); +```cpp +backend_factory const& backEnd = odbc; +session sql(backEnd, "filedsn=c:\\my.dsn"); +``` or simply: - session sql(odbc, "filedsn=c:\\my.dsn"); +```cpp +session sql(odbc, "filedsn=c:\\my.dsn"); +``` -The set of parameters used in the connection string for ODBC is the same as accepted by the `[SQLDriverConnect](http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbcsql/od_odbc_d_4x4k.asp)` function from the ODBC library. +The set of parameters used in the connection string for ODBC is the same as accepted by the [SQLDriverConnect](http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbcsql/od_odbc_d_4x4k.asp) function from the ODBC library. Once you have created a `session` object as shown above, you can use it to access the database, for example: - int count; - sql << "select count(*) from invoices", into(count); +```cpp +int count; +sql << "select count(*) from invoices", into(count); +``` (See the [SOCI basics](../basics.html) and [exchanging data](../exchange.html) documentation for general information on using the `session` class.) -### SOCI Feature Support +## SOCI Feature Support -#### Dynamic Binding +### Dynamic Binding The ODBC backend supports the use of the SOCI `row` class, which facilitates retrieval of data whose type is not known at compile time. When calling `row::get()`, the type you should pass as T depends upon the underlying database type. For the ODBC backend, this type mapping is: - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ODBC Data TypeSOCI Data Typerow::get<T> specializations
SQL_DOUBLE - , SQL_DECIMAL - , SQL_REAL - , SQL_FLOAT - , SQL_NUMERIC - dt_doubledouble
SQL_TINYINT - , SQL_SMALLINT - , SQL_INTEGER - , SQL_BIGINTdt_integerint
SQL_CHAR, SQL_VARCHARdt_stringstd::string
SQL_TYPE_DATE - , SQL_TYPE_TIME - , SQL_TYPE_TIMESTAMPdt_datestd::tm
+|ODBC Data Type|SOCI Data Type|`row::get` specializations| +|--- |--- |--- | +|SQL_DOUBLE, SQL_DECIMAL, SQL_REAL, SQL_FLOAT, SQL_NUMERIC|dt_double|double| +|SQL_TINYINT, SQL_SMALLINT, SQL_INTEGER, SQL_BIGINT|dt_integer|int| +|SQL_CHAR, SQL_VARCHAR|dt_string|std::string| +|SQL_TYPE_DATE, SQL_TYPE_TIME, SQL_TYPE_TIMESTAMP|dt_date|std::tm| Not all ODBC drivers support all datatypes. (See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `row` class.) - -#### Binding by Name +### Binding by Name In addition to [binding by position](../exchange.html#bind_position), the ODBC backend supports [binding by name](../exchange.html#bind_name), via an overload of the `use()` function: - int id = 7; - sql << "select name from person where id = :id", use(id, "id") +```cpp +int id = 7; +sql << "select name from person where id = :id", use(id, "id") +``` Apart from the portable "colon-name" syntax above, which is achieved by rewriting the query string, the backend also supports the ODBC ? syntax: - int i = 7; - int j = 8; - sql << "insert into t(x, y) values(?, ?)", use(i), use(j); +```cpp +int i = 7; +int j = 8; +sql << "insert into t(x, y) values(?, ?)", use(i), use(j); +``` -#### Bulk Operations +### Bulk Operations The ODBC backend has support for SOCI's [bulk operations](../statements.html#bulk) interface. Not all ODBC drivers support bulk operations, the following is a list of some tested backends: - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ODBC DriverBulk ReadBulk Insert
MS SQL Server 2005YESYES
MS Access 2003YESNO
PostgresQL 8.1YESYES
MySQL 4.1NONO
+|ODBC Driver|Bulk Read|Bulk Insert| +|--- |--- |--- | +|MS SQL Server 2005|YES|YES| +|MS Access 2003|YES|NO| +|PostgresQL 8.1|YES|YES| +|MySQL 4.1|NO|NO| -#### Transactions +### Transactions [Transactions](../statements.html#transactions) are also fully supported by the ODBC backend, provided that they are supported by the underlying database. -#### BLOB Data Type +### BLOB Data Type Not currently supported. -#### RowID Data Type +### RowID Data Type Not currently supported. -#### Nested Statements +### Nested Statements Not currently supported. -#### Stored Procedures +### Stored Procedures Not currently supported. -### Acessing the native database API +## Native API Access SOCI provides access to underlying datbabase APIs via several getBackEnd() functions, as described in the [beyond SOCI](../beyond.html) documentation. The ODBC backend provides the following concrete classes for navite API access: - - - - - - - - - - - - - - - - - - - -
Accessor FunctionConcrete Class
session_backend* session::get_backend()odbc_statement_backend
statement_backend* statement::get_backend()odbc_statement_backend
rowid_backend* rowid::get_backend()odbc_rowid_backend
+|Accessor Function|Concrete Class| +|--- |--- | +|session_backend* session::get_backend()|odbc_statement_backend| +|statement_backend* statement::get_backend()|odbc_statement_backend| +|rowid_backend* rowid::get_backend()|odbc_rowid_backend| -### Backend-specific extensions +## Backend-specific extensions -#### odbc_soci_error +### odbc_soci_error The ODBC backend can throw instances of class `odbc_soci_error`, which is publicly derived from `soci_error` and has additional public members containing the ODBC error code, the Native database error code, and the message returned from ODBC: - int main() +```cpp +int main() +{ + try { - try - { - // regular code - } - catch (soci::odbc_soci_error const& e) - { - cerr << "ODBC Error Code: " << e.odbc_error_code() << endl - << "Native Error Code: " << e.native_error_code() << endl - << "SOCI Message: " << e.what() << std::endl - << "ODBC Message: " << e.odbc_error_message() << endl; - } - catch (exception const &e) - { - cerr << "Some other error: " << e.what() << endl; - } + // regular code } + catch (soci::odbc_soci_error const& e) + { + cerr << "ODBC Error Code: " << e.odbc_error_code() << endl + << "Native Error Code: " << e.native_error_code() << endl + << "SOCI Message: " << e.what() << std::endl + << "ODBC Message: " << e.odbc_error_message() << endl; + } + catch (exception const &e) + { + cerr << "Some other error: " << e.what() << endl; + } +} +``` -#### get_connection_string() +### get_connection_string() The `odbc_session_backend` class provides `std::string get_connection_string() const` method that returns fully expanded connection string as returned by the `SQLDriverConnect` function. -### Configuration options +## Configuration options This backend supports `odbc_option_driver_complete` option which can be passed to it via `connection_parameters` class. The value of this option is passed to `SQLDriverConnect()` function as "driver completion" parameter and so must be one of `SQL_DRIVER_XXX` values, in the string form. The default value of this option is `SQL_DRIVER_PROMPT` meaning that the driver will query the user for the user name and/or the password if they are not stored together with the connection. If this is undesirable for some reason, you can use `SQL_DRIVER_NOPROMPT` value for this option to suppress showing the message box: - connection_parameters parameters("odbc", "DSN=mydb"); - parameters.set_option(odbc_option_driver_complete, "0" /* SQL_DRIVER_NOPROMPT */); - session sql(parameters); \ No newline at end of file +```cpp +connection_parameters parameters("odbc", "DSN=mydb"); +parameters.set_option(odbc_option_driver_complete, "0" /* SQL_DRIVER_NOPROMPT */); +session sql(parameters); +``` diff --git a/docs/backends/oracle.md b/docs/backends/oracle.md index dbe33ce169..3db6be01d9 100644 --- a/docs/backends/oracle.md +++ b/docs/backends/oracle.md @@ -1,219 +1,171 @@ -## Oracle Backend Reference +# Oracle Backend Reference -* [Prerequisites](#prerequisites) - * [Supported Versions](#versions) - * [Tested Platforms](#tested) - * [Required Client Libraries](#required) -* [Connecting to the Database](#connecting) -* [SOCI Feature Support](#support) - * [Dynamic Binding](#dynamic) - * [Binding by Name](#name) - * [Bulk Operations](#bulk) - * [Transactions](#transactions) - * [BLOB Data Type](#blob) - * [RowID Data Type](#rowid) - * [Nested Statements](#nested) - * [Stored Procedures](#stored) -* [Accessing the Native Database API](#native) -* [Backend-specific Extensions](#extensions) - * [oracle_soci_error](#oraclesocierror) +SOCI backend for accessing Oracle database. +## Prerequisites -### Prerequisites - -#### Supported Versions +### Supported Versions The SOCI Oracle backend is currently supported for use with Oracle 10 or later. Older versions of Oracle may work as well, but they have not been tested by the SOCI team. -#### Tested Platforms +### Tested Platforms - - - - - -
Oracle versionOperating SystemCompiler
10.2.0 (XE)RedHat 5g++ 4.3
+|Oracle|OS|Compiler| +|--- |--- |--- | +|10.2.0 (XE)|RedHat 5|g++ 4.3| +|11.2.0 (XE)|Ubuntu 12.04|g++ 4.6.3| - -#### Required Client Libraries +### Required Client Libraries The SOCI Oracle backend requires Oracle's `libclntsh` client library. Depending on the particular system, the `libnnz10` library might be needed as well. Note that the SOCI library itself depends also on `libdl`, so the minimum set of libraries needed to compile a basic client program is: - -lsoci_core -lsoci_oracle -ldl -lclntsh -lnnz10 +```console +-lsoci_core -lsoci_oracle -ldl -lclntsh -lnnz10 +``` -#### Connecting to the Database +### Connecting to the Database To establish a connection to an Oracle database, create a `session` object using the oracle backend factory together with a connection string: - session sql(oracle, "service=orcl user=scott password=tiger"); +```cpp +session sql(oracle, "service=orcl user=scott password=tiger"); - // or: - session sql("oracle", "service=orcl user=scott password=tiger"); +// or: +session sql("oracle", "service=orcl user=scott password=tiger"); - // or: - session sql("oracle://service=orcl user=scott password=tiger"); +// or: +session sql("oracle://service=orcl user=scott password=tiger"); + +// or: +session sql(oracle, "service=//your_host:1521/your_sid user=scott password=tiger"); +``` The set of parameters used in the connection string for Oracle is: * `service` * `user` * `password` -* `mode` (optional) +* `mode` (optional; valid values are `sysdba`, `sysoper` and `default`) +* `charset` and `ncharset` (optional; valid values are `utf8`, `utf16`, `we8mswin1252` and `win1252`) + +If both `user` and `password` are provided, the session will authenticate using the database credentials, whereas if none of them is set, then external Oracle credentials will be used - this allows integration with so called Oracle wallet authentication. Once you have created a `session` object as shown above, you can use it to access the database, for example: - int count; - sql << "select count(*) from user_tables", into(count); +```cpp +int count; +sql << "select count(*) from user_tables", into(count); +``` (See the [SOCI basics](../basics.html) and [exchanging data](../exchange.html) documentation for general information on using the `session` class.)# -### SOCI Feature Support +## SOCI Feature Support -#### Dynamic Binding +### Dynamic Binding The Oracle backend supports the use of the SOCI `row` class, which facilitates retrieval of data which type is not known at compile time. -When calling `row::get()`, the type you should pass as `T` depends upon the nderlying database type.
For the Oracle backend, this type mapping is: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Oracle Data TypeSOCI Data Typerow::get<T> specializations
number (where scale > 0)dt_doubledouble
number
(where scale = 0 and precision ≤ std::numeric_limits<int>::digits10)
dt_integerint
numberdt_long_longlong long
char, varchar, varchar2dt_stringstd::string
datedt_datestd::tm
+When calling `row::get()`, the type you should pass as `T` depends upon the nderlying database type. For the Oracle backend, this type mapping is: +|Oracle Data Type|SOCI Data Type|`row::get` specializations| +|--- |--- |--- | +|number (where scale > 0)|dt_double|double| +|number(where scale = 0 and precision ≤ `std::numeric_limits::digits10`)|dt_integer|int| +|number|dt_long_long|long long| +|char, varchar, varchar2|dt_string|std::string| +|date|dt_date|std::tm| (See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `row` class.) -#### Binding by Name +### Binding by Name In addition to [binding by position](../exchange.html#bind_position), the Oracle backend supports [binding by name](../exchange.html#bind_name), via an overload of the `use()` function: - int id = 7; - sql << "select name from person where id = :id", use(id, "id") +```cpp +int id = 7; +sql << "select name from person where id = :id", use(id, "id") +``` SOCI's use of ':' to indicate a value to be bound within a SQL string is consistant with the underlying Oracle client library syntax. -#### Bulk Operations +### Bulk Operations The Oracle backend has full support for SOCI's [bulk operations](../statements.html#bulk) interface. -#### Transactions +### Transactions [Transactions](../statements.html#transactions) are also fully supported by the Oracle backend, although transactions with non-default isolation levels have to be managed by explicit SQL statements. -#### blob Data Type +### blob Data Type The Oracle backend supports working with data stored in columns of type Blob, via SOCI's [blob](../exchange.html#blob) class. -#### rowid Data Type +### rowid Data Type Oracle rowid's are accessible via SOCI's [rowid](../reference.html#rowid) class. -#### Nested Statements +### Nested Statements The Oracle backend supports selecting into objects of type `statement`, so that you may work with nested sql statements and PL/SQL cursors: - statement stInner(sql); - statement stOuter = (sql.prepare << - "select cursor(select name from person order by id)" - " from person where id = 1", - into(stInner)); - stInner.exchange(into(name)); - stOuter.execute(); - stOuter.fetch(); +```cpp +statement stInner(sql); +statement stOuter = (sql.prepare << + "select cursor(select name from person order by id)" + " from person where id = 1", + into(stInner)); +stInner.exchange(into(name)); +stOuter.execute(); +stOuter.fetch(); - while (stInner.fetch()) - { - std::cout << name << '\n'; - } +while (stInner.fetch()) +{ + std::cout << name << '\n'; +} +``` -#### Stored Procedures +### Stored Procedures Oracle stored procedures can be executed by using SOCI's [procedure](../statements.html#procedures) class. -### Acessing the native database API +## Native API Access SOCI provides access to underlying datbabase APIs via several `get_backend()` functions, as described in the [Beyond SOCI](../beyond.html) documentation. The Oracle backend provides the following concrete classes for navite API access: - - - - - - - - - - - - - - - - - - - - - - - -
Accessor FunctionConcrete Class
session_backend * session::get_backend()oracle_session_backend
statement_backend * statement::get_backend()oracle_statement_backend
blob_backend * blob::get_backend()oracle_blob_backend
rowid_backend * rowid::get_backend()oracle_rowid_backend
+|Accessor Function|Concrete Class| +|--- |--- | +|session_backend * session::get_backend()|oracle_session_backend| +|statement_backend * statement::get_backend()|oracle_statement_backend| +|blob_backend * blob::get_backend()|oracle_blob_backend| +|rowid_backend * rowid::get_backend()|oracle_rowid_backend| -### Backend-specific extensions +## Backend-specific extensions -#### oracle_soci_error +### oracle_soci_error The Oracle backend can throw instances of class `oracle_soci_error`, which is publicly derived from `soci_error` and has an additional public `err_num_` member containing the Oracle error code: - int main() +```cpp +int main() +{ + try { - try - { - // regular code - } - catch (oracle_soci_error const & e) - { - cerr << "Oracle error: " << e.err_num_ - << " " << e.what() << endl; - } - catch (exception const &e) - { - cerr << "Some other error: "<< e.what() << endl; - } - } \ No newline at end of file + // regular code + } + catch (oracle_soci_error const & e) + { + cerr << "Oracle error: " << e.err_num_ + << " " << e.what() << endl; + } + catch (exception const &e) + { + cerr << "Some other error: "<< e.what() << endl; + } +} +``` diff --git a/docs/backends/postgresql.md b/docs/backends/postgresql.md index e6f9985b2d..998f30b920 100644 --- a/docs/backends/postgresql.md +++ b/docs/backends/postgresql.md @@ -1,204 +1,171 @@ -## PostgreSQL Backend Reference +# PostgreSQL Backend Reference -* [Prerequisites](#prerequisites) - * [Supported Versions](#versions) - * [Tested Platforms](#tested) - * [Required Client Libraries](#required) -* [Connecting to the Database](#connecting) -* [SOCI Feature Support](#support) - * [Dynamic Binding](#dynamic) - * [Binding by Name](#name) - * [Bulk Operations](#bulk) - * [Transactions](#transactions) - * [BLOB Data Type](#blob) - * [RowID Data Type](#rowid) - * [Nested Statements](#nested) - * [Stored Procedures](#stored) -* [Accessing the Native Database API](#native) -* [Backend-specific Extensions](#extensions) - * [UUID Data Type](#uuid) -* [Configuration options](#configuration) +SOCI backend for accessing PostgreSQL database. -### Prerequisites +## Prerequisites -#### Supported Versions +### Supported Versions The SOCI PostgreSQL backend is supported for use with PostgreSQL >= 7.3, although versions older than 8.0 will suffer from limited feature support. See below for details. -#### Tested Platforms +### Tested Platforms - - - - - - - - -
PostgreSQL versionOperating SystemCompiler
9.0Mac OS X 10.6.6g++ 4.2
8.4FreeBSD 8.2g++ 4.1
8.4Debian 6g++ 4.3
8.4RedHat 5g++ 4.3
+|PostgreSQL|OS|Compiler| +|--- |--- |--- | +|9.6|Windows Server 2016|MSVC++ 14.1| +|9.4|Windows Server 2012 R2|MSVC++ 14.0| +|9.4|Windows Server 2012 R2|MSVC++ 12.0| +|9.4|Windows Server 2012 R2|MSVC++ 11.0| +|9.4|Windows Server 2012 R2|Mingw-w64/GCC 4.8| +|9.3|Ubuntu 12.04|g++ 4.6.3| +|9.0|Mac OS X 10.6.6|g++ 4.2| +|8.4|FreeBSD 8.2|g++ 4.1| +|8.4|Debian 6|g++ 4.3| +|8.4|RedHat 5|g++ 4.3| -#### Required Client Libraries +### Required Client Libraries The SOCI PostgreSQL backend requires PostgreSQL's `libpq` client library. Note that the SOCI library itself depends also on `libdl`, so the minimum set of libraries needed to compile a basic client program is: - -lsoci_core -lsoci_postgresql -ldl -lpq +```console +-lsoci_core -lsoci_postgresql -ldl -lpq +``` -#### Connecting to the Database +### Connecting to the Database To establish a connection to the PostgreSQL database, create a `session` object using the `postgresql` backend factory together with a connection string: +```cpp +session sql(postgresql, "dbname=mydatabase"); - session sql(postgresql, "dbname=mydatabase"); +// or: +session sql("postgresql", "dbname=mydatabase"); - // or: - session sql("postgresql", "dbname=mydatabase"); - - // or: - session sql("postgresql://dbname=mydatabase"); +// or: +session sql("postgresql://dbname=mydatabase"); +``` The set of parameters used in the connection string for PostgreSQL is the same as accepted by the `[PQconnectdb](http://www.postgresql.org/docs/8.3/interactive/libpq.html#LIBPQ-CONNECT)` function from the `libpq` library. +In addition to standard PostgreSQL connection parameters, the following can be set: + +* `singlerow` or `singlerows` + +For example: + +```cpp +session sql(postgresql, "dbname=mydatabase singlerows=true"); +``` + +If the `singlerows` parameter is set to `true` or `yes`, then queries will be executed in the single-row mode, which prevents the client library from loading full query result set into memory and instead fetches rows one by one, as they are requested by the statement's fetch() function. This mode can be of interest to those users who want to make their client applications more responsive (with more fine-grained operation) by avoiding potentially long blocking times when complete query results are loaded to client's memory. +Note that in the single-row operation: + +* bulk queries are not supported, and +* in order to fulfill the expectations of the underlying client library, the complete rowset has to be exhausted before executing further queries on the same session. + +Also please note that single rows mode requires PostgreSQL 9 or later, both at +compile- and run-time. If you need to support earlier versions of PostgreSQL, +you can define `SOCI_POSTGRESQL_NOSINLGEROWMODE` when building the library to +disable it. + Once you have created a `session` object as shown above, you can use it to access the database, for example: - int count; - sql << "select count(*) from invoices", into(count); +```cpp +int count; +sql << "select count(*) from invoices", into(count); +``` (See the [exchanging data](../basics.html">SOCI basics and SOCI Feature Support +## SOCI Feature Support -#### Dynamic Binding +### Dynamic Binding The PostgreSQL backend supports the use of the SOCI `row` class, which facilitates retrieval of data whose type is not known at compile time. -When calling `row::get()`, the type you should pass as `T` depends upon the underlying database type.
For the PostgreSQL backend, this type mapping is: +When calling `row::get()`, the type you should pass as `T` depends upon the underlying database type. For the PostgreSQL backend, this type mapping is: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PostgreSQL Data TypeSOCI Data Typerow::get<T> specializations
numeric, real, doubledt_doubledouble
boolean, smallint, integerdt_integerint
int8dt_long_longlong long
oiddt_integerunsigned long
char, varchar, text, cstring, bpchardt_stringstd::string
abstime, reltime, date, time, timestamp, timestamptz, timetzdt_datestd::tm
+|PostgreSQL Data Type|SOCI Data Type|`row::get` specializations| +|--- |--- |--- | +|numeric, real, double|dt_double|double| +|boolean, smallint, integer|dt_integer|int| +|int8|dt_long_long|long long| +|oid|dt_integer|unsigned long| +|char, varchar, text, cstring, bpchar|dt_string|std::string| +|abstime, reltime, date, time, timestamp, timestamptz, timetz|dt_date|std::tm| (See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `row` class.) -#### Binding by Name +### Binding by Name In addition to [binding by position](../exchange.html#bind_position), the PostgreSQL backend supports [binding by name](../exchange.html#bind_name), via an overload of the `use()` function: - int id = 7; - sql << "select name from person where id = :id", use(id, "id") +```cpp +int id = 7; +sql << "select name from person where id = :id", use(id, "id") +``` Apart from the portable "colon-name" syntax above, which is achieved by rewriting the query string, the backend also supports the PostgreSQL native numbered syntax: - int i = 7; - int j = 8; - sql << "insert into t(x, y) values($1, $2)", use(i), use(j); +```cpp +int i = 7; +int j = 8; +sql << "insert into t(x, y) values($1, $2)", use(i), use(j); +``` The use of native syntax is not recommended, but can be nevertheless imposed by switching off the query rewriting. This can be achieved by defining the macro `SOCI_POSTGRESQL_NOBINDBYNAME` and it is actually necessary for PostgreSQL 7.3, in which case binding of use elements is not supported at all. See the [Configuration options](#options) section for details. -#### Bulk Operations +### Bulk Operations The PostgreSQL backend has full support for SOCI's [bulk operations](../statements.html#bulk) interface. -#### Transactions +### Transactions [Transactions](../statements.html#transactions) are also fully supported by the PostgreSQL backend. -#### blob Data Type +### blob Data Type The PostgreSQL backend supports working with data stored in columns of type Blob, via SOCI's [blob](../exchange.html#blob) class with the exception that trimming is not supported. -#### rowid Data Type +### rowid Data Type The concept of row identifier (OID in PostgreSQL) is supported via SOCI's [rowid](../reference.html#rowid) class. -#### Nested Statements +### Nested Statements Nested statements are not supported by PostgreSQL backend. -#### Stored Procedures +### Stored Procedures PostgreSQL stored procedures can be executed by using SOCI's [procedure](../statements.html#procedures) class. -### Acessing the native database API +## Native API Access SOCI provides access to underlying datbabase APIs via several `get_backend()` functions, as described in the [beyond SOCI](../beyond.html) documentation. The PostgreSQL backend provides the following concrete classes for navite API access: - - - - - - - - - - - - - - - - - - - - - - - -
Accessor FunctionConcrete Class
session_backend * session::get_backend()postgresql_session_backend
statement_backend * statement::get_backend()postgresql_statement_backend
blob_backend * blob::get_backend()postgresql_blob_backend
rowid_backend * rowid::get_backend()postgresql_rowid_backend
+|Accessor Function|Concrete Class| +|--- |--- | +|session_backend * session::get_backend()|postgresql_session_backend| +|statement_backend * statement::get_backend()|postgresql_statement_backend| +|blob_backend * blob::get_backend()|postgresql_blob_backend| +|rowid_backend * rowid::get_backend()|postgresql_rowid_backend| +## Backend-specific extensions -### Backend-specific extensions - -#### uuid Data Type +### uuid Data Type The PostgreSQL backend supports working with data stored in columns of type UUID via simple string operations. All string representations of UUID supported by PostgreSQL are accepted on input, the backend will return the standard format of UUID on output. See the test `test_uuid_column_type_support` for usage examples. -### Configuration options +## Configuration options To support older PostgreSQL versions, the following configuration macros are recognized: * `SOCI_POSTGRESQL_NOBINDBYNAME` - switches off the query rewriting. * `SOCI_POSTGRESQL_NOPARAMS` - disables support for parameterized queries (binding of use elements),automatically imposes also the `SOCI_POSTGRESQL_NOBINDBYNAME` macro. It is necessary for PostgreSQL 7.3. -* `SOCI_POSTGRESQL_NOPREPARE` - disables support for separate query preparation, which in this backend is significant only in terms of optimization. It is necessary for PostgreSQL 7.3 and 7.4. \ No newline at end of file +* `SOCI_POSTGRESQL_NOPREPARE` - disables support for separate query preparation, which in this backend is significant only in terms of optimization. It is necessary for PostgreSQL 7.3 and 7.4. +* `SOCI_POSTGRESQL_NOSINLGEROWMODE` - disable single mode retrieving query results row-by-row. It is necessary for PostgreSQL prior to version 9. diff --git a/docs/backends/sqlite3.md b/docs/backends/sqlite3.md index 409905fc74..ff64f8f6df 100644 --- a/docs/backends/sqlite3.md +++ b/docs/backends/sqlite3.md @@ -1,61 +1,48 @@ -## SQLite3 Backend Reference +# SQLite3 Backend Reference -* [Prerequisites](#prerequisites) - * [Supported Versions](#versions) - * [Tested Platforms](#tested) - * [Required Client Libraries](#required) -* [Connecting to the Database](#connecting) -* [SOCI Feature Support](#support) - * [Dynamic Binding](#dynamic) - * [Binding by Name](#name) - * [Bulk Operations](#bulk) - * [Transactions](#transactions) - * [BLOB Data Type](#blob) - * [RowID Data Type](#rowid) - * [Nested Statements](#nested) - * [Stored Procedures](#stored) -* [Accessing the Native Database API](#native) -* [Backend-specific Extensions](#extensions) - * [SQLite3 result code support](#sqlite3result) -* [Configuration options](#options) +SOCI backend for accessign SQLite 3 database. +## Prerequisites -### Prerequisites - -#### Supported Versions +### Supported Versions The SOCI SQLite3 backend is supported for use with SQLite3 >= 3.1 -#### Tested Platforms +### Tested Platforms - - - - - - - - - - - - - -
SQLite3 versionOperating SystemCompiler
3.5.2Mac OS X 10.5g++ 4.0.1
3.1.3Mac OS X 10.4g++ 4.0.1
3.2.1Linux i686 2.6.10-gentoo-r6g++ 3.4.5
3.3.4Ubuntu 5.1g++ 4.0.2
3.3.4Windows XP(cygwin) g++ 3.3.4
3.3.4Windows XPVisual C++ 2005 Express Edition
3.3.8Windows XPVisual C++ 2005 Professional
3.4.0Windows XP(cygwin) g++ 3.4.4
3.4.0Windows XPVisual C++ 2005 Express Edition
+|SQLite3|OS|Compiler| +|--- |--- |--- | +|3.12.1|Windows Server 2016|MSVC++ 14.1| +|3.12.1|Windows Server 2012 R2|MSVC++ 14.0| +|3.12.1|Windows Server 2012 R2|MSVC++ 12.0| +|3.12.1|Windows Server 2012 R2|MSVC++ 11.0| +|3.12.1|Windows Server 2012 R2|Mingw-w64/GCC 4.8| +|3.7.9|Ubuntu 12.04|g++ 4.6.3| +|3.4.0|Windows XP|(cygwin) g++ 3.4.4| +|3.4.0|Windows XP|Visual C++ 2005 Express Edition| +|3.3.8|Windows XP|Visual C++ 2005 Professional| +|3.5.2|Mac OS X 10.5|g++ 4.0.1| +|3.3.4|Ubuntu 5.1|g++ 4.0.2| +|3.3.4|Windows XP|(cygwin) g++ 3.3.4| +|3.3.4|Windows XP|Visual C++ 2005 Express Edition| +|3.2.1|Linux i686 2.6.10-gentoo-r6|g++ 3.4.5| +|3.1.3|Mac OS X 10.4|g++ 4.0.1| -#### Required Client Libraries +### Required Client Libraries The SOCI SQLite3 backend requires SQLite3's `libsqlite3` client library. -#### Connecting to the Database +### Connecting to the Database To establish a connection to the SQLite3 database, create a Session object using the `SQLite3` backend factory together with the database file name: - session sql(sqlite3, "database_filename"); +```cpp +session sql(sqlite3, "database_filename"); - // or: +// or: - session sql("sqlite3", "db=db.sqlite timeout=2 shared_cache=true"); +session sql("sqlite3", "db=db.sqlite timeout=2 shared_cache=true"); +``` The set of parameters used in the connection string for SQLite is: @@ -66,14 +53,16 @@ The set of parameters used in the connection string for SQLite is: Once you have created a `session` object as shown above, you can use it to access the database, for example: - int count; - sql << "select count(*) from invoices", into(count); +```cpp +int count; +sql << "select count(*) from invoices", into(count); +``` (See the [SOCI basics](../basics.html) and [exchanging data](../exchange.html) documentation for general information on using the `session` class.) -### SOCI Feature Support +## SOCI Feature Support -#### Dynamic Binding +### Dynamic Binding The SQLite3 backend supports the use of the SOCI `row` class, which facilitates retrieval of data whose type is not known at compile time. @@ -81,120 +70,78 @@ When calling `row::get()`, the type you should pass as T depends upon the und For the SQLite3 backend, this type mapping is complicated by the fact the SQLite3 does not enforce [types][INTEGER_PRIMARY_KEY] and makes no attempt to validate the type names used in table creation or alteration statements. SQLite3 will return the type as a string, SOCI will recognize the following strings and match them the corresponding SOCI types: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SQLite3 Data TypeSOCI Data Typerow::get<T> specializations
*float*, *double*dt_doubledouble
*int8*, *bigint*dt_long_longlong long
*unsigned big int*dt_unsigned_long_longunsigned long long
*int*, *boolean*dt_integerint
*text, *char*dt_stringstd::string
*date*, *time*dt_datestd::tm
+|SQLite3 Data Type|SOCI Data Type|`row::get` specializations| +|--- |--- |--- | +|*float*, *double*|dt_double|double| +|*int8*, *bigint*|dt_long_long|long long| +|*unsigned big int*|dt_unsigned_long_long|unsigned long long| +|*int*, *boolean*|dt_integer|int| +|*text, *char*|dt_string|std::string| +|*date*, *time*|dt_date|std::tm| [INTEGER_PRIMARY_KEY] : There is one case where SQLite3 enforces type. If a column is declared as "integer primary key", then SQLite3 uses that as an alias to the internal ROWID column that exists for every table. Only integers are allowed in this column. (See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `row` class.) -#### Binding by Name +### Binding by Name In addition to [binding by position](../exchange.html#bind_position), the SQLite3 backend supports [binding by name](../exchange.html#bind_name), via an overload of the `use()` function: - int id = 7; - sql << "select name from person where id = :id", use(id, "id") +```cpp +int id = 7; +sql << "select name from person where id = :id", use(id, "id") +``` The backend also supports the SQLite3 native numbered syntax, "one or more literals can be replace by a parameter "?" or ":AAA" or "@AAA" or "$VVV" where AAA is an alphanumeric identifier and VVV is a variable name according to the syntax rules of the TCL programming language." [[1]](http://www.sqlite.org/capi3ref.html#sqlite3_bind_int): - int i = 7; - int j = 8; - sql << "insert into t(x, y) values(?, ?)", use(i), use(j); +```cpp +int i = 7; +int j = 8; +sql << "insert into t(x, y) values(?, ?)", use(i), use(j); +``` -#### Bulk Operations +### Bulk Operations The SQLite3 backend has full support for SOCI's [bulk operations](../statements.html#bulk) interface. However, this support is emulated and is not native. -#### Transactions +### Transactions [Transactions](../statements.html#transactions) are also fully supported by the SQLite3 backend. -#### BLOB Data Type +### BLOB Data Type The SQLite3 backend supports working with data stored in columns of type Blob, via SOCI's blob class. Because of SQLite3 general typelessness the column does not have to be declared any particular type. -#### RowID Data Type +### RowID Data Type In SQLite3 RowID is an integer. "Each entry in an SQLite table has a unique integer key called the "rowid". The rowid is always available as an undeclared column named ROWID, OID, or _ROWID_. If the table has a column of type INTEGER PRIMARY KEY then that column is another an alias for the rowid."[[2]](http://www.sqlite.org/capi3ref.html#sqlite3_last_insert_rowid) -#### Nested Statements +### Nested Statements Nested statements are not supported by SQLite3 backend. -#### Stored Procedures +### Stored Procedures Stored procedures are not supported by SQLite3 backend -### Acessing the native database API +## Native API Access SOCI provides access to underlying datbabase APIs via several `get_backend()` functions, as described in the [beyond SOCI](../beyond.html) documentation. The SQLite3 backend provides the following concrete classes for navite API access: - - - - - - - - - - - - - - - - - - - -
Accessor FunctionConcrete Class
session_backend* session::get_backend()sqlie3_session_backend
statement_backend* statement::get_backend()sqlite3_statement_backend
rowid_backend* rowid::get_backend()sqlite3_rowid_backend
+|Accessor Function|Concrete Class| +|--- |--- | +|session_backend* session::get_backend()|sqlie3_session_backend| +|statement_backend* statement::get_backend()|sqlite3_statement_backend| +|rowid_backend* rowid::get_backend()|sqlite3_rowid_backend| -### Backend-specific extensions +## Backend-specific extensions -#### SQLite3 result code support +### SQLite3 result code support SQLite3 result code is provided via the backend specific `sqlite3_soci_error` class. Catching the backend specific error yields the value of SQLite3 result code via the `result()` method. -### Configuration options +## Configuration options -None \ No newline at end of file +None diff --git a/docs/beyond.md b/docs/beyond.md index 55767b60d1..6a74defa66 100644 --- a/docs/beyond.md +++ b/docs/beyond.md @@ -1,75 +1,89 @@ -## Beyond standard SQL +# Beyond standard SQL -Sometimes the standard SQL is not enough and database-specific syntax needs to be used. When possible and practical, SOCI provides wrappers hiding the differences between the backends and this section describes these wrappers. And if this is still not enough, you can use the backend-specific methods directly as described below. +Sometimes the standard SQL is not enough and database-specific syntax needs to be used. +When possible and practical, SOCI provides wrappers hiding the differences between the backends and this section describes these wrappers. +And, if this is still not enough, you can use the backend-specific methods directly as described below. -### Getting the number of rows affected by an operation +## Affected rows -It can be useful to know how many rows were affected by the last SQL statement, most often when using INSERT, UPDATE or DELETE. SOCI provides `statement::get_affected_rows()` method allowing to do this: +It can be useful to know how many rows were affected by the last SQL statement, most often when using `INSERT`, `UPDATE` or `DELETE`. +SOCI provides `statement::get_affected_rows()` method allowing to do this: - statement st = (sql.prepare << "update some_table ..."); - st.execute(true); +```cpp +statement st = (sql.prepare << "update some_table ..."); +st.execute(true); - if ( !st.get_affected_rows() ) - { - ... investigate why no rows were modified ... - } +if ( !st.get_affected_rows() ) +{ + ... investigate why no rows were modified ... +} +``` ---- -#####Portability note: +### Portability note -This method is currently not supported by the Oracle backend. It is however -supported when using Oracle database via ODBC backend. ---- +This method behaviour in case of partially executed update, i.e. when some records were updated or inserted while some other have failed to be updated or inserted, depends on the exact backend and, in the case of ODBC backend, on the exact ODBC driver used. +It can return `-1`, meaning that the number of rows is unknown, the number of rows actually updated or the total number of affected rows. -### Working with sequences +## Sequences -It is common to have auto-incrementing database fields or fields whose value come from a sequence. In the latter case you need to retrieve the value of the field for a new row before inserting it into the database. In the former case, this is unnecessary but you may still want to know the value generated by the database, e.g. to use it as a foreign key in another table. So it would be useful to have a way to obtain the value of such a field. But, of course, to make life of database programmers more interesting, different products usually support either autoincrement fields or sequences but not both -- and they use different syntaxes for them, too. SOCI tries to help to deal with this unfortunate situation by providing two functions: `session::get_next_sequence_value()` and `session::get_last_insert_id`. +It is common to have auto-incrementing database fields or fields whose value come from a sequence. +In the latter case you need to retrieve the value of the field for a new row before inserting it into the database. +In the former case, this is unnecessary but you may still want to know the value generated by the database, e.g. to use it as a foreign key in another table. +So it would be useful to have a way to obtain the value of such a field. +But, of course, to make life of database programmers more interesting, different products usually support either autoincrement fields or sequences but not both -- and they use different syntaxes for them, too. +SOCI tries to help to deal with this unfortunate situation by providing two functions: `session::get_next_sequence_value()` and `session::get_last_insert_id`. If you know which kind of database you use, you may use only one of them: when working with sequences, the first one allows to generate the next value in a sequence and when working with autoincrement fields, the second one retrieves the last value generated for such a field for the given table. However if you use multiple SOCI backends or even just a single ODBC backend but support connecting to databases of different types, you actually must use both of them in the following way to insert a row: - long id; - statement st; - if ( sql.get_next_sequence_value("table_sequence", id) ) - { - st << "insert into table(id, f1, f2) values(:id, :f1, :f2)", - use(id), use(f1), use(f2); - } - else - { - // We're not using sequences, so don't specify the value, - // it will be automatically generated by the database on insert. - st << "insert into table(f1, f2) value(:f1, :f2)", - use(f1), use(f2); +```cpp +long id; +statement st; +if ( sql.get_next_sequence_value("table_sequence", id) ) +{ + st << "insert into table(id, f1, f2) values(:id, :f1, :f2)", + use(id), use(f1), use(f2); +} +else +{ + // We're not using sequences, so don't specify the value, + // it will be automatically generated by the database on insert. + st << "insert into table(f1, f2) value(:f1, :f2)", + use(f1), use(f2); - // If the ID used for the above row is needed later, get it: - if ( !sql.get_last_insert_id("table", id) ) - ... unexpected error, handle appropriately ... - } + // If the ID used for the above row is needed later, get it: + if ( !sql.get_last_insert_id("table", id) ) + ... unexpected error, handle appropriately ... +} +``` + +### Portability note ---- -##### Portability note: These methods are currently only implemented in Firebird, MySQL, ODBC, PostgreSQL and SQLite3 backends. ---- +## Beyond SOCI API -### Beyond SOCI API - -As the original name of the library (Simple Oracle Call Interface) clearly stated, SOCI is intended to be a *simple* library, targeting the majority of needs in regular C++ application. We do not claim that *everything* can be done with SOCI and it was never the intent of the library. What is important, though, is that the simplicity of the +As the original name of the library (Simple Oracle Call Interface) clearly stated, SOCI is intended to be a *simple* library, targeting the majority of needs in regular C++ application. +We do not claim that *everything* can be done with SOCI and it was never the intent of the library. +What is important, though, is that the simplicity of the library does *not* prevent the client applications from reaching into the low-level specifics of each database backend in order to achieve special configuration or performance goals. -Most of the SOCI classes have the `getBackEnd` method, which returns the pointer to the actual backend object that implements the given functionality. The knowledge of the actual backend allows the client application to get access to all low-level details that are involved. +Most of the SOCI classes have the `getBackEnd` method, which returns the pointer to the actual backend object that implements the given functionality. +The knowledge of the actual backend allows the client application to get access to all low-level details that are involved. - blob b(sql); +```cpp +blob b(sql); - oracle_session_back_end * sessionBackEnd = static_cast(sql.get_back_end()); - oracle_blob_back_end * blobBackEnd = static_cast(b.get_back_end()); +oracle_session_back_end * sessionBackEnd = static_cast(sql.get_back_end()); +oracle_blob_back_end * blobBackEnd = static_cast(b.get_back_end()); - OCILobDisableBuffering(sessionBackEnd->svchp_, sessionBackEnd->errhp_, blobBackEnd->lobp_); +OCILobDisableBuffering(sessionBackEnd->svchp_, sessionBackEnd->errhp_, blobBackEnd->lobp_); +``` -The above example retrieves the `rowid` ("something" that identifies the row in the table) from the table and uses the `get_back_end` function to extract the actual object that implements this functionality. Assuming that it is the `"postgresql"` backend which is in use, the downcast is performed to use the `postgresql_rowid_back_end` interface to get the actual OID value that is a physical, low-level implementation of row identifier on PostgreSQL databases. +The above example retrieves the `rowid` ("something" that identifies the row in the table) from the table and uses the `get_back_end` function to extract the actual object that implements this functionality. +Assuming that it is the `"postgresql"` backend which is in use, the downcast is performed to use the `postgresql_rowid_back_end` interface to get the actual OID value that is a physical, low-level implementation of row identifier on PostgreSQL databases. In order for any of the above to compile, you have to explicitly `#include` the appropriate backend's header file. -Please see the header file related to the given backend to learn what low-level handles and descriptors are available. \ No newline at end of file +Please see the header file related to the given backend to learn what low-level handles and descriptors are available. diff --git a/docs/binding.md b/docs/binding.md new file mode 100644 index 0000000000..c3f0db1990 --- /dev/null +++ b/docs/binding.md @@ -0,0 +1,184 @@ +# Data Binding + +SOCI provides mechanisms to bind local buffers for input and output data. + +**Note:** The Oracle documentation uses two terms: *defining* (for instructing the library where the *output* data should go) and *binding* (for the *input* data and *input/output* PL/SQL parameters). For the sake of simplicity, SOCI uses the term *binding* for both of these. + +## Binding output data (into) + +The `into` expression is used to add binding information to +the statement: + +```cpp +int count; +sql << "select count(*) from person", into(count); + +string name; +sql << "select name from person where id = 7", into(name); +``` + +In the above examples, some data is retrieved from the database and transmitted *into* the given local variable. + +There should be as many `into` elements as there are expected columns in the result (see [dynamic resultset binding](#dynamic") for the exception to this rule). + +## Binding input data (use) + +The `use` expression associates the SQL placeholder (written with colon) with the local data: + +```cpp +int val = 7; +sql << "insert into numbers(val) values(:val)", use(val); +``` + +In the above statement, the first "val" is a column name (assuming that there is appropriate table `numbers` with this column), the second "val" (with colon) is a placeholder and its name is ignored here, and the third "val" is a name of local variable. + +To better understand the meaning of each "val" above, consider also: + +```cpp +int number = 7; +sql << "insert into numbers(val) values(:blabla)", use(number); +``` + +Both examples above will insert the value of some local variable into the table `numbers` - we say that the local variable is *used* in the SQL statement. + +There should be as many `use` elements as there are parameters used in the SQL query. + +### Object lifetime and immutability + +SOCI assumes that local variables provided as `use` elements live at least as long at it takes to execute the whole statement. +In short statement forms like above, the statement is executed *sometime* at the end of the full expression and the whole process is driven by the invisible temporary object handled by the library. +If the data provided by user comes from another temporary variable, it might be possible for the compiler to arrange them in a way that the user data will be destroyed *before* the statement will have its chance to execute, referencing objects that no longer exist: + +```cpp +// Dangerous code! + +string getNameFromSomewhere(); + +sql << "insert into person(name) values(:n)", use(getNameFromSomewhere()); +``` + +In the above example, the data passed to the database comes from the temporary variable that is a result of call to `getNameFromSomewhere` - this should be avoided and named variables should be used to ensure safe lifetime relations: + +```cpp +// Safe code + +string getNameFromSomewhere(); + +string name = getNameFromSomewhere(); +sql << "insert into person(name) values(:n)", use(name); +``` + +It is still possible to provide `const` data for use elements. + +Note that some database servers, like Oracle, allow PL/SQL procedures to modify their in/out parameters - this is detected by the SOCI library and an error is reported if the database attempts to modify the `use` element that holds `const` data. + +The above example can be ultimately written in the following way: + +```cpp +// Safe and efficient code + +string getNameFromSomewhere(); + +string const& name = getNameFromSomewhere(); +sql << "insert into person(name) values(:n)", use(name); +``` + +### Portability note + +Older versions of the PostgreSQL client API do not allow to use input parameters at all. +In order to compile SOCI with those old client libraries, define the `SOCI_POSTGRESQL_NOPARAMS` preprocessor name passing `-DSOCI_POSTGRESQL_NOPARAMS=ON` variable to CMake. + +## Binding by position + +If there is more output or input "holes" in the single statement, it is possible to use many `into` and `use` expressions, separated by commas, where each expression will be responsible for the consecutive "hole" in the statement: + +```cpp +string firstName = "John", lastName = "Smith"; +int personId = 7; + +sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)", + use(personId), use(firstName), use(lastName); + +sql << "select firstname, lastname from person where id = :id", + into(firstName), into(lastName), use(personId); +``` + +In the code above, the order of "holes" in the SQL statement and the order of `into` and `use` expression should match. + +## Binding by name + +The SQL placeholders that have their names (with colon) can be bound by name to clearly associate the local variable with the given placeholder. + +This explicit naming allows to use different order of elements: + +```cpp +string firstName = "John", lastName = "Smith"; +int personId = 7; +sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)", + use(firstName, "fn"), use(lastName, "ln"), use(personId, "id"); +``` + +or bind the same local data to many "holes" at the same time: + +```cpp +string addr = "..."; +sql << "update person" + " set mainaddress = :addr, contactaddress = :addr" + " where id = 7", + use(addr, "addr"); +``` + +### Portability notes + +The PostgreSQL backend allows to use the "native" PostgreSQL way of naming parameters in the query, which is by numbers like `$1`, `$2`, `$3`, etc. +In fact, the backend *rewrites* the given query to the native form - and this is also one of the very few places where SOCI intrudes into the SQL query. +For portability reasons, it is recommended to use named parameters, as shown in the examples above. + +The query rewriting can be switched off by compiling the backend with the `SOCI_POSTGRESQL_NOBINDBYNAME` name defined (pass `-DSOCI_POSTGRESQL_NOBINDBYNAME=ON` variable to CMake). +Note that in this case it is also necessary to define `SOCI_POSTGRESQL_NOPREPARE` (controlled by CMake variable `-DSOCI_POSTGRESQL_NOPREPARE=ON`), because statement preparation relies on successful query rewriting. + +In practice, both macros will be needed for PostgreSQL server older than 8.0. + +## Bulk operations + +Bulk operations allow the user to bind, as into or use element, whole vectors of objects. +This allows the database backend to optimize access and data transfer and benefit from the fact that `std::vector` stores data in contiguous memory blocks (the actual optimization depends on the backend and the capability of the underlying data base server). + +It is possible to `use` the vector as a data source: + +```cpp +std::vector v; +// ... +sql << "insert into t ...", use(v); +``` + +as well as a destination: + +```cpp +std::vector v; +v.resize(100); +sql << "select ...", into(v); +``` + +In the latter case the initial size of the vector defines the maximum number of data elements that the user is willing to accept and after executing the query the vector will be automatically resized to reflect that actual number of rows that were read and transmitted. +That is, the vector will be automatically shrunk if the amount of data that was available was smaller than requested. + +It is also possible to operate on the chosen sub-range of the vector: + +```cpp +std::vector v; +// ... +std::size_t begin = ...; +std::size_t end = ...; +sql << "insert into t ...", use(v, begin, end); + +// or: + +sql << "select ...", into(v, begin, end); +``` + +Above, only the sub-range of the vector is used for data transfer and in the case of `into` operation, the `end` variable will be automatically adjusted to reflect the amount of data that was actually transmitted, but the vector object as a whole will retain its initial size. + +Bulk operations can also involve indicators, see below. + +Bulk operations support user-defined data types, if they have appropriate conversion routines defined. diff --git a/docs/boost.md b/docs/boost.md index d5f4acf478..d0511e45f0 100644 --- a/docs/boost.md +++ b/docs/boost.md @@ -1,8 +1,21 @@ -## Integration with Boost +# Boost Integration -The SOCI user code can be easily integrated with the [Boost library](http://www.boost.org/) thanks to the very flexible type conversion facility. There are three important Boost types that are supported out of the box. +The SOCI user code can be easily integrated with the [Boost library](http://www.boost.org/) thanks to the very flexible type conversion facility. -####boost::optional +The integration with Boost types is optional and is *not* enabled by default, which means that SOCI can also be compiled and used without any dependency on Boost. + +In order to enable the support for any of the above types, the user needs to either include one of these headers: + +```cpp +#include +#include +#include +#include +``` + +or to define the `SOCI_USE_BOOST` macro before including the `soci.h` main header file. + +## Boost.Optional `boost::optional` provides an alternative way to support the null data condition and as such relieves the user from necessity to handle separate indicator values. @@ -10,69 +23,61 @@ The `boost::optional` objects can be used everywhere where the regular user p Example: - boost::optional name; - sql << "select name from person where id = 7", into(name); - - if (name.is_initialized()) - { - // OK, the name was retrieved and is not-null - cout << "The name is " << name.get(); - } - else - { - // the name is null - } +```cpp +boost::optional name; +sql << "select name from person where id = 7", into(name); +if (name.is_initialized()) +{ + // OK, the name was retrieved and is not-null + cout << "The name is " << name.get(); +} +else +{ + // the name is null +} +``` The `boost::optional` objects are fully supported for both `into` and `use` elements, in both single and vector forms. They can be also used for user-defined data types. - -####boost::tuple +## Boost.Tuple `boost::tuple` allows to work with whole rows of information and in some cases can be more convenient to use than the more dynamically-oriented `row` type. - boost::tuple person; +```cpp +boost::tuple person; - sql << "select name, phone, salary from persons where ...", +sql << "select name, phone, salary from persons where ...", into(person); +``` -Tuples are supported for both `into` and `use` elements. They can be used with `rowset` as well. +Tuples are supported for both `into` and `use` elements. +They can be used with `rowset` as well. Tuples can be also composed with `boost::optional` - boost::tuple, int> person; +```cpp +boost::tuple, int> person; - sql << "select name, phone, salary from persons where ...", +sql << "select name, phone, salary from persons where ...", into(person); - if (person.get<1>().is_initialized()) - { - // the given person has a phone number - } - else - { - // this person does not have a phone number - } +if (person.get<1>().is_initialized()) +{ + // the given person has a phone number +} +else +{ + // this person does not have a phone number +} +``` -####boost::fusion::vector +## Boost.Fusion The `boost::fusion::vector` types are supported in the same way as tuples. +**Note:** Support for `boost::fusion::vector` is enabled only if the detected Boost version is at least 1.35. -####boost::gregorian::date +## Boost.DateTime The `boost::gregorian::date` is provided as a conversion for base type `std::tm` and can be used as a replacement for it. - ---- -#####Optional integration: -The integration with Boost types is optional and *not* enabled by default, which means that SOCI can be compiled and used without any dependency on Boost. ---- - -In order to enable the support for any of the above types, the user needs to either include one of these headers: - - #include - #include - #include - #include - -or to define the `SOCI_USE_BOOST` macro before including the `soci.h` main header file. Note that in this case the support for `boost::fusion::vector` is enabled only if the detected Boost version is at least 1.35. \ No newline at end of file diff --git a/docs/connections.md b/docs/connections.md index 2f61585d79..a1587bab6e 100644 --- a/docs/connections.md +++ b/docs/connections.md @@ -1,75 +1,111 @@ -## Connections and simple queries - -### Connecting to the database +# Connections The `session` class encapsulates the database connection and other backend-related details, which are common to all the statements that will be later executed. It has a couple of overloaded constructors. +## Using backend factory + The most basic one expects two parameters: the requested backend factory object and the generic connection string, which meaning is backend-dependent. Example: - session sql(oracle, "service=orcl user=scott password=tiger"); +```cpp +session sql(oracle, "service=orcl user=scott password=tiger"); +``` Another example might be: - session sql(postgresql, "dbname=mydb"); +```cpp +session sql(postgresql, "dbname=mydb"); +``` Above, the `sql` object is a local (automatic) object that encapsulates the connection. This `session` constructor either connects successfully, or throws an exception. -Another constructor allows to name backends at run-time and supports the dynamically loadable backends, which have to be compiled as shared libraries. The usage is similar to the above, but instead of providing the factory object, the backend name is expected: +### Portability note - session sql("postgresql", "dbname=mydb"); +In case of SOCI linked against DLLs on Windows, the factory objects are not exported from the DLLs. +In order to avoid linker errors, access factory objects via dedicated backend functions +provided (eg. `factory_postgresql()`). + +## Using loadable backends + +Dynamically loadable backends are compiled as shared libraries and allow to select backends at run-time by name. + +The usage is similar to the above, but instead of providing the factory object, the backend name is expected: + +```cpp +session sql("postgresql", "dbname=mydb"); +``` For convenience, the URL-like form that combines both the backend name with connection parameters is supported as well: - session sql("postgresql://dbname=mydb"); +```cpp +session sql("postgresql://dbname=mydb"); +``` -The last two constructors described above try to locate the shared library with the name `libsoci_ABC.so` (or `libsoci_ABC.dll` on Windows), where ABC is the backend name. In the above examples, the expected library name will be `libsoci_postgresql.so` for Unix-like systems. +The last two constructors described above try to locate the shared library with the name `libsoci_ABC.so` (or `libsoci_ABC.dll` on Windows), where ABC is the backend name. +In the above examples, the expected library name will be `libsoci_postgresql.so` for Unix-like systems. -The most general form of the constructor takes a single object of `connection_parameters` type which contains a pointer to the backend to use, the connection string and also any connection options. Using this constructor is the only way to pass any non-default options to the backend. For example, to suppress any interactive prompts when using ODBC backend you could do: +The most general form of the constructor takes a single object of `connection_parameters` type which contains a pointer to the backend to use, the connection string and also any connection options. +Using this constructor is the only way to pass any non-default options to the backend. - connection_parameters parameters("odbc", "DSN=mydb"); - parameters.set_option(odbc_option_driver_complete, "0" /* SQL_DRIVER_NOPROMPT */); - session sql(parameters); +For example, to suppress any interactive prompts when using ODBC backend you could do: -Notice that you need to `#include <soci-odbc.h>` to obtain the option name declaration. The existing options are described in the backend-specific part of the documentation. +```cpp +connection_parameters parameters("odbc", "DSN=mydb"); +parameters.set_option(odbc_option_driver_complete, "0" /* SQL_DRIVER_NOPROMPT */); +session sql(parameters); +``` +Notice that you need to `#include` to obtain the option name declaration. +The existing options are described in the backend-specific part of the documentation. + +IBM DB2 driver for ODBC and CLI also support the driver completion requests. +So, the DB2 backend provides similar option `db2_option_driver_complete` with `#include ` required to obtain the option name. ### Environment configuration -The `SOCI_BACKENDS_PATH` environment variable defines the set of paths where the shared libraries will be searched for. There can be many paths, separated by colons, and they are used from left to right until the library with the appropriate name is found. If this variable is not set or is empty, the current directory is used as a default path for dynamically loaded backends. +The `SOCI_BACKENDS_PATH` environment variable defines the set of paths where the shared libraries will be searched for. +There can be many paths, separated by colons, and they are used from left to right until the library with the appropriate name is found. If this variable is not set or is empty, the current directory is used as a default path for dynamically loaded backends. +## Using registered backends +The run-time selection of backends is also supported with libraries linked statically. -The run-time selection of backends is also supported with libraries linked statically. Each backend provides a separate function of the form `register_factory_*name*`, where `*name*` is a backend name. Thus: +Each backend provides a separate function of the form `register_factory_*name*`, where `*name*` is a backend name. Thus: - extern "C" void register_factory_postgresql(); - // ... - register_factory_postgresql(); - session sql("postgresql://dbname=mydb"); +```cpp +extern "C" void register_factory_postgresql(); +// ... +register_factory_postgresql(); +session sql("postgresql://dbname=mydb"); +``` -The above example registers the backend for PostgreSQL and later creates the session object for that backend. This form is provided for those projects that prefer static linking but still wish to benefit from run-time backend selection. +The above example registers the backend for PostgreSQL and later creates the session object for that backend. +This form is provided for those projects that prefer static linking but still wish to benefit from run-time backend selection. An alternative way to set up the session is to create it in the disconnected state and connect later: - session sql; +```cpp +session sql; - // some time later: - sql.open(postgresql, "dbname=mydb"); +// some time later: +sql.open(postgresql, "dbname=mydb"); - // or: - sql.open("postgresql://dbname=mydb"); +// or: +sql.open("postgresql://dbname=mydb"); - // or also: - connection_parameters parameters("postgresql", "dbname=mydb"); - sql.open(parameters); +// or also: +connection_parameters parameters("postgresql", "dbname=mydb"); +sql.open(parameters); +``` The rules for backend naming are the same as with the constructors described above. -The session can be also explicitly `close`d and `reconnect`ed, which can help with basic session error recovery. The `reconnect` function has no parameters and attempts to use the same values as those provided with earlier constructor or `open` calls. +The session can be also explicitly `close`d and `reconnect`ed, which can help with basic session error recovery. +The `reconnect` function has no parameters and attempts to use the same values as those provided with earlier constructor or `open` calls. See also the page devoted to [multithreading](multithreading.html) for a detailed description of connection pools. @@ -87,4 +123,40 @@ The following backends are also available, with various levels of completeness: * [sqlite3](backends/sqlite3.html) (requires `#include "soci-sqlite3.h"`) * [odbc](backends/odbc.html) (requires `#include "soci-odbc.h"`) -* [firebird](backends/firebird.html) (requires `#include "soci-firebird.h"`) \ No newline at end of file +* [firebird](backends/firebird.html) (requires `#include "soci-firebird.h"`) + +## Connection failover + +The `failover_callback` interface can be used as a callback channel for notifications of events that are automatically processed when the session is forcibly closed due to connectivity problems. The user can override the following methods: + +```cpp +// Called when the failover operation has started, +// after discovering connectivity problems. +virtual void started(); + +// Called after successful failover and creating a new connection; +// the sql parameter denotes the new connection and allows the user +// to replay any initial sequence of commands (like session configuration). +virtual void finished(session & sql); + +// Called when the attempt to reconnect failed, +// if the user code sets the retry parameter to true, +// then new connection will be attempted; +// the newTarget connection string is a hint that can be ignored +// by external means. +virtual void failed(bool & retry, std::string & newTarget); + +// Called when there was a failure that prevents further failover attempts. +virtual void aborted(); +``` + +The user-provided callback implementation can be installed (or reset) with: + +```cpp +sql.set_failover_callback(myCallback); +``` + +### Portability note + +The `failover_callback` functionality is currently supported only by PostgreSQL and Oracle backends (in the latter case the failover mechanism is governed by the Oracle-specific cluster configuration settings). +Other backends allow the callback object to be installed, but will ignore it and will not generate notification calls. diff --git a/docs/errors.md b/docs/errors.md index 4e3ce67e83..81618e39ca 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -1,82 +1,99 @@ -## Errors +# Errors All DB-related errors manifest themselves as exceptions of type `soci_error`, which is derived from `std::runtime_error`. This allows to handle database errors within the standard exception framework: - int main() +```cpp +int main() +{ + try { - try - { - // regular code - } - catch (std::exception const & e) - { - cerr << "Bang! " << e.what() << endl; - } + // regular code } - -The only public method of `soci_error` is `std::string get_error_message() const`, which returns just the brief error message, without any additional information that can be present in the full error message returned by `what()`. - - -#### Portability note: - -The Oracle backend can also throw the instances of the `oracle_soci_error`, which is publicly derived from `soci_error` and has an additional public `err_num_` member containing the Oracle error code: - - int main() + catch (std::exception const & e) { - try - { - // regular code - } - catch (soci::oracle_soci_error const & e) - { - cerr << "Oracle error: " << e.err_num_ - << " " << e.what() << endl; - } - catch (soci::exception const & e) - { - cerr << "Some other error: " << e.what() << endl; - } + cerr << "Bang! " << e.what() << endl; } +} +``` -#### Portability note: +The `soci_error` class exposes two public functions: + +The `get_error_message() const` function returns `std::string` with a brief error message, without any additional information that can be present in the full error message returned by `what()`. + +The `get_error_category() const` function returns one of the `error_category` enumeration values, which allows the user to portably react to some subset of common errors. +For example, `connection_error` or `constraint_violation` have meanings that are common across different database backends, even though the actual mechanics might differ. + +## Portability + +Error categories are not universally supported and there is no claim that all possible errors that are reported by the database server are covered or interpreted. +If the error category is not recognized by the backend, it defaults to `unknown`. + +## MySQL The MySQL backend can throw instances of the `mysql_soci_error`, which is publicly derived from `soci_error` and has an additional public `err_num_` member containing the MySQL error code (as returned by `mysql_errno()`): - int main() +```cpp +int main() +{ + try { - try - { - // regular code - } - catch (soci::mysql_soci_error const & e) - { - cerr << "MySQL error: " << e.err_num_ - << " " << e.what() << endl; - } - catch (soci::exception const & e) - { - cerr << "Some other error: " << e.what() << endl; - } + // regular code } + catch (soci::mysql_soci_error const & e) + { + cerr << "MySQL error: " << e.err_num_ + << " " << e.what() << endl; + } + catch (soci::exception const & e) + { + cerr << "Some other error: " << e.what() << endl; + } +} +``` -#### Portability note: +## Oracle + +The Oracle backend can also throw the instances of the `oracle_soci_error`, which is publicly derived from `soci_error` and has an additional public `err_num_` member containing the Oracle error code: + +```cpp +int main() +{ + try + { + // regular code + } + catch (soci::oracle_soci_error const & e) + { + cerr << "Oracle error: " << e.err_num_ + << " " << e.what() << endl; + } + catch (soci::exception const & e) + { + cerr << "Some other error: " << e.what() << endl; + } +} +``` + +## PostgreSQL The PostgreSQL backend can also throw the instances of the `postgresql_soci_error`, which is publicly derived from `soci_error` and has an additional public `sqlstate()` member function returning the five-character "SQLSTATE" error code: - int main() +```cpp +int main() +{ + try { - try - { - // regular code - } - catch (soci::postgresql_soci_error const & e) - { - cerr << "PostgreSQL error: " << e.sqlstate() - << " " << e.what() << endl; - } - catch (soci::exception const & e) - { - cerr << "Some other error: " << e.what() << endl; - } - } \ No newline at end of file + // regular code + } + catch (soci::postgresql_soci_error const & e) + { + cerr << "PostgreSQL error: " << e.sqlstate() + << " " << e.what() << endl; + } + catch (soci::exception const & e) + { + cerr << "Some other error: " << e.what() << endl; + } +} +``` diff --git a/docs/exchange.md b/docs/exchange.md deleted file mode 100644 index 0e90aa6e4c..0000000000 --- a/docs/exchange.md +++ /dev/null @@ -1,536 +0,0 @@ -## Exchanging data - -* [Binding local data](#bind_local) -* [Binding output data](#bind_output) - * [Binding input data](#bind_input) - * [Binding by position](#bind_position) - * [Binding by name](#bind_name) -* [Handling of nulls and other conditions](exchange.html#data_states) - * [Indicators](#indicators) -* [Types](#types) - * [Static binding](#static) - * [Static binding for bulk operations](#static_bulk) - * [Dynamic resultset binding](#dynamic) - * [Extending with user-provided datatypes](#custom_types) - * [Object-relational mapping](#object_relational) -* [Large objects (BLOBs)](#blob) - - -### Binding local data - -##### Note: - -The Oracle documentation uses two terms: *defining* (for instructing the library where the *output* data should go) and *binding* (for the *input* data and *input/output* PL/SQL parameters). For the sake of simplicity, SOCI uses the term *binding* for both of these. - - int count; - sql << "select count(*) from person", into(count); - - string name; - sql << "select name from person where id = 7", into(name); - -In the above examples, some data is retrieved from the database and transmitted *into* the given local variable. - -There should be as many `into` elements as there are expected columns in the result (see [dynamic resultset binding](#dynamic) for the exception to this rule). - - -#### Binding output - -The `into` expression is used to add binding information to -the statement: - - int count; - sql << "select count(*) from person", into(count); - - string name; - sql << "select name from person where id = 7", into(name); - -In the above examples, some data is retrieved from the database and transmitted *into* the given local variable. - - -There should be as many `into` elements as there are expected columns in the result (see [dynamic resultset binding](#dynamic") for the exception to this rule). - -#### Binding input data - -The `use` expression associates the SQL placeholder (written with colon) with the local data: - - int val = 7; - sql << "insert into numbers(val) values(:val)", use(val); - -In the above statement, the first "val" is a column name (assuming that there is appropriate table `numbers` with this column), the second "val" (with colon) is a placeholder and its name is ignored here, and the third "val" is a name of local variable. - -To better understand the meaning of each "val" above, consider also: - - int number = 7; - sql << "insert into numbers(val) values(:blabla)", use(number); - -Both examples above will insert the value of some local variable into the table `numbers` - we say that the local variable is *used* in the SQL statement. - -There should be as many `use` elements as there are parameters used in the SQL query. - -##### Portability note: - -Older versions of the PostgreSQL client API do not allow to use input parameters at all. In order to compile SOCI with those old client libraries, define the `SOCI_POSTGRESQL_NOPARAMS` preprocessor name passing `-DSOCI_POSTGRESQL_NOPARAMS=ON` variable to CMake. - -#### Binding by position - -If there is more output or input "holes" in the single statement, it is possible to use many `into` and `use` expressions, separated by commas, where each expression will be responsible for the consecutive "hole" in the statement: - - string firstName = "John", lastName = "Smith"; - int personId = 7; - - sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)", - use(personId), use(firstName), use(lastName); - - sql << "select firstname, lastname from person where id = :id", - into(firstName), into(lastName), use(personId); - -In the code above, the order of "holes" in the SQL statement and the order of `into` and `use` expression should match. - -#### Binding by name - -The SQL placeholders that have their names (with colon) can be bound by name to clearly associate the local variable with the given placeholder. - -This explicit naming allows to use different order of elements: - - string firstName = "John", lastName = "Smith"; - int personId = 7; - sql << "insert into person(id, firstname, lastname) values(:id, :fn, :ln)", - use(firstName, "fn"), use(lastName, "ln"), use(personId, "id"); - -or bind the same local data to many "holes" at the same time: - - string addr = "..."; - sql << "update person" - " set mainaddress = :addr, contactaddress = :addr" - " where id = 7", - use(addr, "addr"); - -##### Object lifetime and immutability: - -SOCI assumes that local variables provided as `use` elements live at least as long at it takes to execute the whole statement. In short statement forms like above, the statement is executed *sometime* at the end of the full expression and the whole process is driven by the invisible temporary object handled by the library. If the data provided by user comes from another temporary variable, it might be possible for the compiler to arrange them in a way that the user data will be destroyed *before* the statement will have its chance to execute, referencing objects that no longer exist: - - // dangerous code: - - string getNameFromSomewhere(); - - sql << "insert into person(name) values(:n)", - use(getNameFromSomewhere()); - -In the above example, the data passed to the database comes from the temporary variable that is a result of call to `getNameFromSomewhere` - this should be avoided and named variables should be used to ensure safe lifetime relations: - - // safe code: - - string getNameFromSomewhere(); - - string name = getNameFromSomewhere(); - sql << "insert into person(name) values(:n)", - use(name); - -It is still possible to provide `const` data for use elements. Note that some database servers, like Oracle, allow PL/SQL procedures to modify their in/out parameters - this is detected by the SOCI library and an error is reported if the database attempts to modify the `use` element that holds `const` -data. - -The above example can be ultimately written in the following way: - - // safe and efficient code: - - string getNameFromSomewhere(); - - const string & name = getNameFromSomewhere(); - sql << "insert into person(name) values(:n)", - use(name); - -##### Portability notes: -The PostgreSQL backend allows to use the "native" PostgreSQL way of naming parameters in the query, which is by numbers like `$1`, `$2`, `$3`, etc. In fact, the backend *rewrites* the given query to the native form - and this is also one of the very few places where SOCI intrudes into the SQL query. For portability reasons, it is recommended to use named parameters, as shown in the examples above. - -The query rewriting can be switched off by compiling the backend with the `SOCI_POSTGRESQL_NOBINDBYNAME` name defined (pass `-DSOCI_POSTGRESQL_NOBINDBYNAME=ON` variable to CMake). Note that in this case it is also necessary to define `SOCI_POSTGRESQL_NOPREPARE` (controlled by CMake variable `-DSOCI_POSTGRESQL_NOPREPARE=ON`), because statement preparation relies on successful query rewriting. -In practice, both macros will be needed for PostgreSQL server older than 8.0. - -### Handling nulls and other conditions -#### Indicators -In order to support null values and other conditions which are not real errors, the concept of *indicator* is provided. - -For example, when the following SQL query is executed: - - select name from person where id = 7 - -there are three possible outcomes: - -1. there is a person with id = 7 and his name is returned -2. there is a person with id = 7, but he has no name (his name is null in the database table) -3. there is no such person - -Whereas the first alternative is easy to handle, the other two are more complex. Moreover, they are not necessarily errors from the application's point of view and what's more interesting, they are *different* and the application may wish to detect which is the case. -The following example does this: - - string name; - indicator ind; - - sql << "select name from person where id = 7", into(name, ind); - - if (sql.got_data()) - { - switch (ind) - { - case i_ok: - // the data was returned without problems - break; - case i_null: - // there is a person, but he has no name (his name is null) - break; - case i_truncated: - // the name was returned only in part, - // because the provided buffer was too short - // (not possible with std::string, but possible with char* and char[]) - break; - } - } - else - { - // no such person in the database - } - -The use of indicator variable is optional, but if it is not used and the result would be `i_null`, -then the exception is thrown. This means that you should use indicator variables everywhere where the application logic (and database schema) allow the "attribute not set" condition. - -Indicator variables can be also used when binding input data, to control whether the data is to be used as provided, or explicitly overrided to be null: - - int id = 7; - string name; - indicator ind = i_null; - sql << "insert into person(id, name) values(:id, :name)", - use(id), use(name, ind); - -In the above example, the row is inserted with `name` attribute set to null. - -Indicator variables can also be used in conjunction with vector based insert, update, and select statements: - - vector Types -#### Static type binding - -The static binding for types is most useful when the types used in the database are known at compile time - this was already presented above with the help of `into` and `use` functions. - -The following types are currently supported for use with `into` and `use` expressions: - -* `char` (for character values) -* `short`, `int`, `unsigned long`, `long long`, `double` (for numeric values) -* `char*`, `char[]`, `std::string` (for string values) -* `std::tm``` (for datetime values) -* `soci::statement` (for nested statements and PL/SQL cursors) -* `soci::blob` (for Binary Large OBjects) -* `soci::row_id` (for row identifiers) - -See the test code that accompanies the library to see how each of these types is used. - -#### Static type binding for bulk operations - -Bulk inserts, updates, and selects are supported through the following `std::vector` based into and use types: - -*`std::vector Dynamic resultset binding - -For certain applications it is desirable to be able to select data from arbitrarily structured tables (e.g. via "`select * from ...`") and format the resulting data based upon its type. SOCI supports this through the `soci::row` and `soci::column_properties` classes. - -Data is selected into a `row` object, which holds `column_properties` objects describing -the attributes of data contained in each column. Once the data type for each column is known, the data can be formatted appropriately. - -For example, the code below creates an XML document from a selected row of data from an arbitrary table: - - row r; - sql << "select * from some_table", into(r); - - std::ostringstream doc; - doc << "> name >> address >> age; - -Note, however, that this interface is *not* compatible with the standard `std::istream` class and that it is only possible to extract a single row at a time - for "safety" reasons the row boundary is preserved and it is necessary to perform the `fetch` operation explicitly for each consecutive row. - - -#### Extending SOCI to support custom (user-defined) C++ types - -SOCI can be easily extended with support for user-defined datatypes. - -The extension mechanism relies on appropriate specialization of the `type_conversion` -struct that converts to and from one of the following SOCI base types: - -*`double` -*`int` -*`long long` -*`unsigned long long` -*`std::string` -*`char` -*`std::tm` - -There are three required class members for a valid `type_conversion` specialization: - -* the `base_type` type definition, aliasing either one of the base types *or another ser-defined type* -* the `from_base()` static member function, converting from the base type -* the `to_base()` static member function, converting to the base type - -Note that no database-specific code is required to define user conversion. - -The following example shows how the user can extend SOCI to support his own type `MyInt`, which here is some wrapper for the fundamental `int` type: - - class MyInt - { - public: - MyInt() {} - MyInt(int i) : i_(i) {} - - void set(int i) { i_ = i; } - int get() const { return i_; } - - private: - int i_; - }; - - namespace soci - { - template << - struct type_conversion Object-relational mapping - -SOCI provides a class called `values` specifically to enable object-relational mapping via `type_conversion` specializations. - -For example, the following code maps a `Person` object to and from a database table containing columns `"ID"`, `"FIRST_NAME"`, `"LAST_NAME"`, and `"GENDER"`. - -Note that the mapping is non-invasive - the `Person` object itself does not contain any SOCI-specific code: - - struct Person - { - int id; - std::string firstName; - std::string lastName; - std::string gender; - }; - - namespace soci - { - template<< - struct type_conversion Large objects (BLOBs) - -The SOCI library provides also an interface for basic operations on large objects (BLOBs - Binary Large OBjects). - - blob b(sql); // sql is a session object - sql << "select mp3 from mymusic where id = 123", into(b); - -The following functions are provided in the `blob` interface, mimicking the file-like operations: - - *`std::size_t get_len();` - *`std::size_t read(std::size_t offset, char *buf, std::size_t - toRead);` - *`std::size_t write(std::size_t offset, char const *buf, - std::size_t toWrite);` - *`std::size_t append(char const *buf, std::size_t toWrite);` - *`void trim(std::size_t newLen);` - -The `offset` parameter is always counted from the beginning of the BLOB's data. - -##### Portability notes: - -* The way to define BLOB table columns and create or destroy BLOB objects in the database varies between different database engines. Please see the SQL documentation relevant for the given server to learn how this is actually done. The test programs provided with the SOCI library can be also a simple source of full working examples. -* The `trim` function is not currently available for the PostgreSQL backend. \ No newline at end of file diff --git a/docs/rationale.md b/docs/faq.md similarity index 89% rename from docs/rationale.md rename to docs/faq.md index b09a49077b..7ce91ab5de 100644 --- a/docs/rationale.md +++ b/docs/faq.md @@ -1,42 +1,44 @@ -## Rationale FAQ +# FAQ This part of the documentation is supposed to gather in a single place the usual questions (and answers) about SOCI with regard to the design decisions that have shaped it. -### Q: Why "SOCI"? +## Q: Why "SOCI"? -SOCI was initially developed in the environment where Oracle was the main database technology in use. As a wrapper for the native OCI API (Oracle Call Interface), the name "Simple Oracle Call Interface" was quite obvious - until the 2.0 release, when the internal architecture was largely redesigned to allow the use of *backends* that support other database servers. We have kept the same name to indicate that Oracle is the main supported technology in the sense that the library includes only those features that were naturally implemented in Oracle. With the 2.1 release of the library, two new backends were added (MySQL and SQLite3) and we decided to drop the original full name so that new users looking for a library supporting any of these simpler libraries are not discouraged by seeing "Oracle" somewhere in the name. +SOCI was initially developed in the environment where Oracle was the main database technology in use. As a wrapper for the native OCI API (Oracle Call Interface), the name "Simple Oracle Call Interface" was quite obvious - until the 2.0 release, when the internal architecture was largely redesigned to allow the use of *backends* that support other database servers. We have kept the same name to indicate that Oracle is the main supported technology in the sense that the library includes only those features that were naturally implemented in Oracle. With the 2.1 release of the library, two new backends were added (MySQL and SQLite3) and we decided to drop the original full name so that new users looking for a library supporting any of these simpler libraries are not discouraged by seeing "Oracle" somewhere in the name. The other possible interpretation was "Syntax Oriented Call Interface", which stresses the fact that SOCI was built to support the most natural and easy interface for the user that is similar to the Embedded SQL concept (see below). But on the other hand, SOCI also provides other features (like object-relational mapping) and as a whole it is not just "emulator" of the Embedded SQL. With all these considerations in mind, SOCI is just "SOCI - The C++ Database Access Library". Still, Oracle is considered to be the main driving server technology in terms of the set of features that are supported by the library. This also means that backends for other servers might need to work around some of the imposed idioms and protocols, but already available and well-working PostgreSQL, MySQL and SQLite3 backends show that it's actually not that bad and the abstractions provided by the library are actually very universal. Of course, some of the features that were provided for Oracle might not be supported for all other servers, but we think that it's better to have one leading technology (where at least one group is fully happy) instead of some "common denominator" for all databases (where *nobody* is happy). -### Q: Where the basic SOCI syntax comes from? +## Q: Where the basic SOCI syntax comes from? The basic SOCI syntax was inspired by the Embedded SQL, which is part of the SQL standard, supported by the major DB technologies and even available as built-in part of the languages used in some DB-oriented integrated development environments. The term "Embedded SQL" is enough for Google to spit millions of references - one of the typical examples is: - { - int a; - /* ... */ - EXEC SQL SELECT salary INTO :a - FROM Employee - WHERE SSN=876543210; - /* ... */ - printf("The salary is %d\n", a); - /* ... */ - } +```cpp +{ + int a; + /* ... */ + EXEC SQL SELECT salary INTO :a + FROM Employee + WHERE SSN=876543210; + /* ... */ + printf("The salary is %d\n", a); + /* ... */ +} +``` The above is not a regular C (nor C++) code, of course. It's the mix of C and SQL and there is a separate, pecialized preprocessor needed to convert it to something that the actual C (or C++) compiler will be able to understand. This means that the compilation of the program using embedded SQL is two-phase: preprocess the embedded SQL part and compile the result. This two-phase development is quite troublesome, especially when it comes to debugging. Yet, the advantage of it is that the code expresses the programmer's intents in a very straightforward way: read something from the database and put it into the local variable. Just like that. - The SOCI library was born as an anwer to the following question: is it possible to have the same expressive power without the disadvantages of two-phase builds? The following was chosen to be the basic SOCI syntax that can mirror the above Embedded SQL example: - int a; - sql << "SELECT salary FROM Employee WHERE SSN=876543210", into(a); - +```cpp +int a; +sql << "SELECT salary FROM Employee WHERE SSN=876543210", into(a); +``` (as you see, SOCI changes the order of elements a little bit, so that the SQL query is separate and not mixed with other elements) @@ -51,37 +53,41 @@ Everything else is just a couple of operators that allow to treat the whole as a The fact that the basic SOCI syntax is minimal (but without being obscure at the same time, see below) means that the programmer does not need to bother with unnecessary noise that some other database libraries impose. We hope that after having written one line of code like above by themselves, most programmers will react with something like "how obvious!" instead of "how advanced!". -### Q: Why should I use SQL queries as strings in my program? I prefer the query to be generated or composed piece-by-piece by separate functions. +## Q: Why should I use SQL queries as strings in my program? I prefer the query to be generated or composed piece-by-piece by separate functions. First, you don't need to use SQL queries as string literals. In bigger projects it is a common practice to store SQL queries externally (in a file, or in a... database) and load them before use. This means that they are not necessarily expected to appear in the program code, as they do in our simple code examples and the advantage of separating them from the source code of the main program is, among others, the possibility to optimize and tune the SQL queries without recompilation and relinking of the whole program. What is the most important, though, is that SOCI does not try to mess with the text of the query (apart from very few cases), which means that the database server will get exactly the same text of the query as is used in the program. The advantage of this is that there is no new SQL-like (or even SQL-*un*like) syntax that you would need to learn, and also that it's much easier to convince a typical DBA to help with SQL tuning or other specialized activities, if he is given the material in the form that is not polluted with any foreign abstractions. -### Q: Why not some stream-like interface, which is well-known to all C++ programmers? +## Q: Why not some stream-like interface, which is well-known to all C++ programmers? An example of the stream-like interface might be something like this (this is imaginary syntax, not supported by SOCI): - sql.exec("select a, b, c from some_table"); +```cpp +sql.exec("select a, b, c from some_table"); - while (!sql.eof()) - { - int a, b, c; - sql >> a >> b >> c; - // ... - } +while (!sql.eof()) +{ + int a, b, c; + sql >> a >> b >> c; + // ... +} +``` We think that the data stored in the relational database should be treated as a set of relations - which is exactly what it is. This means that what is read from the database as a result of some SQL query is a *set of rows*. This set might be ordered, but it is still a set of rows, not a uniformly flat list of values. This distinction might seem to be unnecessarily low-level and that the uniform stream-like presentation of data is more preferable, but it's actually the other way round - the set of rows is something more structured - and that structure was *designed* into the database - than the flat stream and is therefore less prone to programming errors like miscounting the number of values that is expected in each row. Consider the following programming error: - sql.exec("select a, b from some_table"); // only TWO columns +```cpp +sql.exec("select a, b from some_table"); // only TWO columns - while (!sql.eof()) - { - int a, b, c; - sql >> a >> b >> c; // this causes "row-tearing" - // ... - } +while (!sql.eof()) +{ + int a, b, c; + sql >> a >> b >> c; // this causes "row-tearing" + // ... +} +``` *"How to detect the end of each line in a file"* is a common beginner's question that relates to the use of IOStreams - and this common question clearly shows that for the record-oriented data the stream is not an optimal abstraction. Of course, we don't claim that IOStreams is bad - but we do insist that the record-oriented data is better manipulated in a way that is also record-aware. @@ -89,18 +95,17 @@ better manipulated in a way that is also record-aware. Having said that, we *have* provided some form of the stream-like interface, but with the important limitation that the stream is always bound to the single row, so that the row-tearing effect is not possible. In other words, data returned from the database is still structured into rows, but each row can be separately traversed like a stream. We hope that it provides a good balance between convenience and code safety. -### Q: Why use indicators instead of some special value to discover that something is null? +## Q: Why use indicators instead of some special value to discover that something is null? Some programmers are used to indicating the null value by using some special (which means: "unlikely" to be ever used) value - for example, to use the smallest integer value to indicate null integer. Or to use empty string to indicate null string. And so on. - We think that it's *completely wrong*. Null (in the database sense) is an information *about* the data. It describes the *state* of the data and if it's null, then there's *no data at all*. Nothing. Null. It does not make any sense to talk about some special value if in fact there is *no* value at all - especially if we take into account that, for example, the smallest integer value (or whatever else you choose as the "special" value) might not be *that* special in the given application or domain. Thus, SOCI uses a separate indicators to describe the state of exchanged data. It also has an additional benefit of allowing the library to convey more than two states (null and not null). Indeed, the SOCI library uses indicators also to report that the data was read, but truncated (this applies to strings when reading to fixed-length character arrays). Truncation is also an information about the data and as such it's better to have it in addition to the data, not as part of it. Having said that, it is important to point at the [Integration with Boost](boost.html) that allows to use `boost::optional` to conveniently pack together the data and the information about its state. -### Q: Overloaded comma operator is just obfuscation, I don't like it. +## Q: Overloaded comma operator is just obfuscation, I don't like it. Well, consider the following: @@ -108,18 +113,17 @@ Well, consider the following: Above, the "and" plays a role of the comma. Even if overloading the comma operator is not a very popular practice in C++, some libraries do this, achieving terse and easy to learn syntax. We are pretty sure that in SOCI the comma operator was overloaded with a good effect. -### Q: The `operator<<` provides a bad abstraction for the "input" statements. +## Q: The `operator<<` provides a bad abstraction for the "input" statements. Indeed, the `operator<<` in the basic SOCI syntax shows that something (the query) is *sent* somewhere (to the server). Some people don't like this, especially when the "select" statements are involved. If the high-level idea is to *read* data from somewhere, then `operator<<` seems unintuitive to the die-hard IOStreams users. The fact is, however, that the code containing SQL statement already indicates that there is a client-server relationship with some other software component (very likely remote). In such code it does not make any sense to pretend that the communication is one-way only, because it's clear that even the "select" statements need to be *sent* somewhere. This approach is also more uniform and allows to cover other statements like "drop table" or alike, where no data is expected to be exchanged at all (and therefore the IOStreams analogies for data exchange have no sense at all). No matter what is the kind of the SQL statement, it is *sent* to the server and this "sending" justifies the choice of `operator<<`. - Using different operators (`operator>>` and `operator<<`) as a way of distinguishing between different high-level ideas (*reading* and *writing* from the data store, respectively) does make sense on much higher level of abstraction, where the SQL statement itself is already hidden - and we do encourage programmers to use SOCI for implementing such high-level abstractions. For this, the object-relational mapping facilities available in SOCI might prove to be a valuable tool as well, as an effective bridge between the low-level world of SQL statements and the high-level world of user-defined abstract data types. -### Q: Why the Boost license? +## Q: Why the Boost license? We decided to use the [Boost license](http://www.boost.org/LICENSE_1_0.txt), because it's well recognized in the C++ community, allows us to keep our minimum copyrights, and at the same time allows SOCI to be safely used in commercial projects, without imposing concerns (or just plainuncertainty) typical to other open source licenses, like GPL. We also hope that by choosing the Boost license we have made the life easier for both us and our users. It saves us from answering law-related questions that were already answered on the [Boost license info page](http://www.boost.org/more/license_info.html) and it should also give more confidence to our users - especially to those of them, who already accepted the conditions of the Boost license - the just have one license less to analyze. -Still, if for any reason the conditions of this license are not acceptable, we encourage the users to contact us directly (see [links](http://soci.sourceforge.net/people.html) on the relevant SOCI page) to discuss any remaining concerns. \ No newline at end of file +Still, if for any reason the conditions of this license are not acceptable, we encourage the users to contact us directly (see [links](http://soci.sourceforge.net/people.html) on the relevant SOCI page) to discuss any remaining concerns. diff --git a/docs/images/structure.png b/docs/images/structure.png new file mode 100644 index 0000000000000000000000000000000000000000..1694cb68715464bd7e21004cb38f1e3dca9f47af GIT binary patch literal 26919 zcmZsBV|-=LwrzJh9jB9YY}-!9ww>(Qwr$(CjUB6F+qP{dFaLASyZ7DqyC2s2uu-+D zYOFcN9CL=sN{hfkVL*NR_6=4{R8aogx9>Y&R|*8k*WHL&xc}=5NE=Z#`)}WVjelKZ z-`(ElSHFETSr!xIQ*_BV>wwijTI~4zT+R;n8;;`@L=^K2Qy-$12sFPU!RKRYv3W%_ zVQ%m{2rg=6@`g?;kd0hIp+-pu-7adWFzv?tyD$LEVeMaqsF+SS1Bj}w9Nm+r1ToEn zDm20h4w--08yPtp^34=$aiYmK zI22&R$V6CBD5Hp+QS-99W>~)l+JJL5Bow9cg0fgmv6 zb|d>$9P@6*RgN8SjWPz`q_$dfWP+Ub|t>QgQ^goAYm01}-|8&>qy*6nc%;>pkjqpN-m7O-)Vn6IVHCLEG)ntdTl( z!n`gbLRYgH^hu6Z?2RRbJx3%1YHJ#Jvf zkt_Be?l8mL6;L|M!=;;eIL>UeM>5ztceCM=QLoTza>rkk&c#^|fH_@g zs4n~OB8ne4_xEULeSw?xUarOsdxSkaDG`pMA>GQz3k`bj40HGRrXJN8yR^<#8==OM zAkCl$qT+|Rbnp^rX#oL5-SzPIb$^q<6$9y1 zzabj<-9mXt$Z|PXWh#z)zz<#Qr*T(E%UHQZn2&;+``?vo0Fc}}_(CW;iWvuUsB%;+f2Dr!I>{eB*sT|4ca^iI}m|&0o61(bKY{@~2mP@0*x&w-!w@o3w zvR&2(j}~-}MKg$rDStvrvie!~^-^OOUE|~G!#grg+OYC_f#pYerUuc`s4sybUQG}e z^tEO=sjn=c2pFd9c|8xWR5x|a-=2*GDv$t7TsiKknMw6Oe5mD3aIUarLc5JRYA&HV ziYD`!6%MFhsLVI!DqNwl{iUQiADDCNAV}|3+tCbV^kq0Gr1O_-(@xpgczjeoPUjfS zamp&!T=E~E2wmIOx%xxQjRzIC$yGOkZp4U5>gZrRLWLgZj`qQN?FQ-cqi$O>W&^Sb z38iQ-xcGR!;IRQ}MG0KRT|vxHW{N5v20dtNjX4m7d?|H__=T*Vh6l=$E}9|U*EM7U zSn|lNq+mfwEAd01vz2wOt5v8~VE#zPXhK=Bb%OsSAf(x?$L&B4awB!yrI6-igL4ii z70#K!t4rnH%jI0-rWd3BM1z8>)U{Vp(;Qovtr4KUyJJ~$mc_dttAZt8v; zOZ|H#rxO-zZQaM4LByup&Q3f-U%h2&GGskq@2`qobdqMWuT)l;@ieTCCsJr;XU-vA z=knpxP`S$=oH=LU0)Haw&V!$3YJ-OA7520Bss<~{{5R&y%l#<=0>(%C(p1l6s5El@ zxLbSwt-u^%Gqu+Pb8WzEKv};EopdA3bd@3L#*}m&j`sB%d^I;F_AOMEalUmuTvpIP zip2EbDvDP8EN?shn{o>|JZ{r{U)NORXMV@zB}kn4<#w>8CI19BQvyFB#6`c+*ksT( zPt8O64Bb^B;g~V?l_aAZO5PqM+Um6Yk6mn&F(chKN4Zwq#s)B{!n5int<3qj-Z~s& zmeG|2Ti&2&MpZD`F*U*$T(6~tMj|Oa9weUL#dq^+%cT?Wu>MCot|~D2K@{rn#)0Yn z2S+mxoHST)hW~Z*wM%W-LcVcQ1Nf0_Q^GAZp z%8DI$h!i1u5B$rt9%0ybyWN;my7!R!a|Qrd;^|Rx%LLgkF zS_reUOgbImwt{D+I@GS(!4bFL#jFLS>|wEJYSgW_fD`2HrgHEYt^R|d@oSxXC=f{f z_l?vXx9e)~LgX^M#eJY6i7@zsoY;IN;Y00&vdjFBd_Irka$rzU^y&%&cWO-k2Yv8W zyf-FNQ$a$=ja|#0YSZ^D=jta`M)QU%R+tpW%QW{(&0RBjL<{o?^GROk?K9nr=I**f zH2t=_X+@cqwB`>1~^aB%z-? zU?dtnf7d%{;I9`OleAC~b2Gy=S8ep!CpvvneWa~#Nrq9dQI=pvYWkls@bgeyU7rpq zRh9qpe_&hW7+2PO^Uv%Uf6f2%v9d68s+T+_VeKx?roJGq4rbVwn0*tX=aarqn_XA0 zg*E;)fieN#EzPbuNrPAK=F8t3RM^k6TuPDPr-iXvO0we0_iVXw@)&t1!aJH>DbCG> z!(Rk@Mj2{D15HaDqU@5EXAOSN;Sx(cXu=L5cWynm!mypM1bNoIjDRI1hJbH_vE=R1 zqW;D@2=ihtUX)i#SgR2`+U$^o!7?qiKU{~YeAs>scHLx{qZXCC`*hqS806){4aee~ z8@hK^?%FNWqs%-dv4s#Ti)py>04#6adel9r*>uhK*yux5k3IWlLiGA}#O-_kKog2! z=*M5e=JRPJ(FsWn4N+DzeiRD~C~Xf%p1YwlcO<%i-+n-K(ul-td9(L1zdh{RAc0g@ z_ACEt4id&|bi`|9=!2C3xi*54<)m5kKl~Liuz#z<1ZcYpMlScpi}0Uw4#nO3{mOAu z7ca3=aA_Y(tt3IP@a8f+7E|~)PKD;e)q#7=ul@AzFkq)kHHE-($M9sWji#j_6t<&& z{%W4I8o5aLPtY`x+TmSFQ<(%-bMxtFdKf_4ecx@e?VbzTd-F+fW9O$wC%IgfC2VvP z^X62{`qTZwcchK@$Y@|{F`Mnd_U{FP)5c3R6aY=zw)m~WIC&i^dM849K!}F`z)EV6 z?j|low@*VIu*_5*(j3759EJ(ETl|t%jys4m;hXp4hqZs%x4@23mvN*6QPg?pmREny z=M5+07z?B=390k(kynk9_q*5<&XU}YfVrtvC5qZd^~@5Avz@+tmyubHxE8-`)Jf!m zpp75Yo1WM}F6WD_)@BVB(dlPrB6CGtH1iA5p>tGe?6n3OYvW!_&ozJ3c@33qN4$bI z;rxzn+rYeocaB%ra|=z8x0eqL%QmZw?5j4oJ)%9<)Ykj70F6WntX--sT)kT0s^fbr zIRQVRc$mvxxh_T!8Lg+M=gx=zPPx66siu{w=Of;eG7gJo^pqeSWd^_g102ltpzN&- zjW@Zl>5m@7$#f8zZU$IVOeJ1Ev!)Oaf1!A<{xx)rAL{GOp ziSgY-_)?Tj4m-=LVtDIw)>Xt0CrFFnFNRxOd6cK94>MC~6y#26V*9V!V__ zd5KfKzMl-t!bm2Cj!edZKN-+%JF9}YJ{G6e;H5u72fPfd^?6=bYA*1AJh3P%&Rvn8 zrXq)qXy65t+j%$d-dK#wV;xXR=g}o1;Zk+Z$e@_y@b22rZ~M55U-5nszL=6d%Ko(PWo!Vuo{I{${Y)ce zWaad}+rQcLa5)4pC$^c3yxJU927MfJ+Y7*n!H&ar7YGkI<)Ff=FE9C9Ap*B6r0$Q= z7zheNGAT-9es2R@Z#^q)+wPxCvPA%?#mBOi6DJDpx1Hr(?MHJD$u8LfV7-?c#~qJZ zCR)faYi!YvljtzirlvexK%Toz+UVG{CXAIkNsV3cPg0+R<7-+h7-}c$5 zD&ge8eP<#t8r(mxU6Z$4oj5+%A zE8Xv|j))I&jpnG;dAd8jeyY2N?JBe_W;B3{(oU~Di6M8Cuk(0iy9vUE8#JX)ey#Hw z3)9;(@OV~Jtgi_y919r0hL%DJQ#r5?l@#u=+{dYV3o0S5`^c8!?KX0jaw5(?7ya-X z(b7aa8YU98*wFJRH~8%xc@28=u)Vk0fMW}`!u+uhEK?sk^^2Cg2FEVqjZ z_?Zr#WdQkJn2Q|U#dJqxElHchG{2o)38P*53r>3B@iG=;092gKwjSDx+~Fhq)8P^c z?&}rH33~YLwLFy>d_7EHkx8o-u-CE_@w_}XeGRqo2_pVjSKGG(W1%^y+$=gkHv5G- z5S_01gaVg#7c|QB!dNltg8B{3=bdEOOb?V*E#vXnlH{z`bvHEg6oJufH>80=3#!@_ z0W~hWUpjy5e28I#SOkx358@)X(#(XGei{L#`s|_*jl}qO z3b%6vcwy z!pxPdzf0btHfF0DtVC|XmjU+lPEO9py;I*MEQW8k7i6=<21<(TUGS#jLkW-V+n1D)z2*G}hBBZUSwK zom`dmu8(jV__v2l!UT?M&vYX@RvS*|K@nL>Q{JH zCi|iVer%g^IS{1Fd zU2V-V<>}eMVz;GdKb;!*db`8*4z_n4#O=|Z&&2hEA2A(lS}k+Jwz^DFdwsnho5P-5 zKz09b_70{{VoBGEencAcoMz^R(r27_13g1$NAd0fL2b^q^EVGNNoYv_DZjP9i#!mO z8RJ#6P7&|}J@*oq2_()`R(h#cp0$x(OIV9ZleXCor}r&tZI`TWt=OC!Q=0qeC`}|t zVm&rNCRyCVjWYqv8Ls`}Z^>P;`DxaVjg?Wue(UMZCQzFrU#iRA0$8}_ipGdgOI|LX zPL*nR(47DXoTrzghqNlY2zknnQXV{Dv2D>0Mub3SoxF8_tIb7*{Mu|;@U`G|-T-TZ zhD|FJX7mIVLR83=uu-|98`-_c6DU4_?Vs+|TiKHa6@$V7p{o(5T*Zu`yY7>79#gl& zeigVup=OHf)h6XcPmWSj21l=w_3tX(7)%}yO7f{8L-dW_=~i#;nx^@dJPI= z;##R0W@w-!PHxHbh=1Iw9b{&~s}wqlxJQH}^iNGiwm7fhI1Xm>d~lRE6h`UcO*Y}s zvpg_l#%OO6u~D(p;uDU#DIffb9IrHnA}9PggbY>|H&)x1ud^+xkP)Dlet+#o|M#k4 zf>23}K3DXcUYS;CWC-1$V_ZW2w9)IVhe5t#peBw7Yn<_PA6Pd-fRI#e5__1-KdZi4 z=E!5G7;fkP)!OqH4V(xBzw}}km9K$)^X={{=IfrwrzMo1_2gQ_@Ao86ab~}R< z8FHK{ziEtQo1`=B0m{}v><DnerKz&QuSDkz1QtO(e9ReB_hBV;dVY!)4epfpL!`rLp-oyL zdl`>L^`s>lMu`>c@rnVJh|j{m+`i`8C+u#Nu{p_fk39}5uki> z(i7?~f(aaIKloMRP4&lJ-ydiAYvhs_hA`<~3mrQ@$%BFX^aW%q4X&|gdU~Voiy~*{xI9NAG-)+HGgOfTL^fe& z+@RY&5h$VEsdPdo~+zodFjn zL^1e@*H_Q`D%EQ(b{mQumxnVmSvL!mrZpo#GxFDm{>K_brMSHP*Jan&RR2qMRPUgl z9!;;-XkmRexcqsk-Xgehr-j6%H-90!goeGCobXsp3)YY%)mM^|R_=HCu8d7hqigJMr{qT;>z%L49zF4|u9LlM+8!J?u5Y4(^OJZGNeW|(AkUIm2Xqom zyQ$#wB^pgm%$3lT3Fl@=#CpuiA@1(+`Bjg{q3`Zb$Htx2D|~Njuhj%i&gvn4!325I zJrXR`@T4KzTU9WjNs`11=lizM%8`>D??-zcepGvtTAU!Eyxn5{NWowUyceMIUFZK= zsKQ+C#3~^{PbdI~P4(vrxk7PW)F1={JMq{IWh%pbUgA30)K#4%rITr>_Vx;^o$W#B zO^{|gS5ycuKwXc%d;8R{8T7FEX)W+7t15GGWvb4y!ukcrmzjPXn#95*B$#5~A*|4O zCFW(8Ft0tddZX_c1osk%|9@iw9|IQwkkg3)RMVSXwv%WHA*cTVSvq~x-ty?wk*44Z z!kI7(<{qpi8un*m5|0SVZ?O8aA?S4kZmBa$8zwWpgyWWLKcZFnNw*F+s}Mr zbW4+WoaBWkAGu*VR{gAu(wx_iA!#lh1D8>{v%k=4G(8aqjhkg&H$oQhHD*^0iCz-~ z1JN7_>aN5;QMmYsvzuzczuit>Dat*LT?iL3h06UFj>gTKCmp{u;$=3z;&8X#inTbvn zLiAM?Y4naeJsL(yzCeXjXy zl1@M3(JVk_wb%Tr90OmfqX!Lo z(7E!p==Ko=9_#rI-yX#bMuvE0yL{EegMCb4J|8MdHn6s7#y%Pwk!&UWVU_%fc>?y| zm0V-r7B?78Qm6|N86pkwWx|Y`@IuVX0|>B__-TGBcXea!On%Q#*p(%^@J6Tq@pQ+7 zKtN+ne>!1XaO-X;LqzVr}W`5NAqD zxTw1G8&0YE3i3@( zBPowDd1?(EO@y6Qf}e^kh#YzJfKNGX^f;hygG0P@!vl`=fiTJ9ARlV`q$^-TVebb z2!_8+q6sSO@f)^vMH4#w$%f5-oAA?zzzLkV!~Yw-;xWg9i4+}qMdbx}yOA3^bIbVw zilsm-;{;cjDvmXCH-}E6b9Du40ec5*mF!ER6`jYbDh>&3*!-cX!xHK7N?w2%zoE75 zi^T_Ib7k2xpj;92+}3B z)6U=(+EatVJ2%`Bd`8J6o|`tq;#8yE#Y)`8KtRf=xr!VN*P*@vr^)fu-i`4@zUP$y zdNP4|Z{B{5JtsHUSEj6jxS^i?y|Jee4W=9vKyB>)+S5B%&)jV2hURT%r3(O^C{*!Y z(Mq6lSf6>V!f`XbAM0U}USzUSdc4mRR@4od%*)S~n8|6EZBi=i;T0)q0k$dXdPE)n zJsYu^v)`r;_Z4XNvO7&(>W`e(F2~hom&V+i;alhPFtk^Hdld<%Vm_y3X$U^8D{4CL zXg^A_jBRBx-)2>uZy(MM)1_6F_xxe+8XoAAe4J8ov(3_BruIGOJeh_O&^BE|^~_gO zNCDYOV$0?hk_mh0U8Pj&qPcF)(AnZ1^2j>eC29Z+^?fclS$JpSX0I!2D5iU#Ah1Nk zB?;zfD6|;v;@+S!u&pH~wL2Iq1Ym?OLt*w#yj##O5r*sU%%rzQE0))VFV!LB{3a8R zqT12arg9CWR^(%=Dbs3wR3x4b5uLw3cB2TI+m$R9E~lWg>zOX7WVc3I0Ppi^a4}wb z%iPKq)s&YCZjK2Dzlbm}Ct=VAJRX;4v|k0{vtA%ATq;)1r=3bLoSW;-I${;qzwHGy zV7f)k0zBF3{X->^pARW?w=LuAbrG_9UaETBFl8i$?C`^vBKRlTbp#R9@jpEhVhp){ z23NaC)ycSH4c>%|&JRZNU~A=2i{u-2OPEv;rxX^2jH1er?nx-cp>hxBc7BiiZIFhy z7bA%%E+F-TCZH$gjwzDlw{H|#uZ4vM_vAp6p&jaO#~=U39r1qlU@>~?ofk$CU1U;0 zB(~t;{>b}86yWk^^>0wt(r|32tt6KA28m;8J; zuFh5ys{vMuaC=Qjy(j<1tf9LVOQropA?Y6)t=w3c+cV?4;_^Jyuh*i+o>A6-X!u(5 z3QAo@^FLLVPb0B?nxt*w3g2fpCUkFCpjuW(wgwMhduK%w>bEZzH?(DsR+Mas$-9tE zvYT-5^=4IzCJv%L#1Amz=aok0CK9_oo@tY9kCS|tj^N8Vk}9Lc6-$5JYeZx-ZW_G7 z)yRlS1iMM#xNi4edSAa;U#_U%Y>g@JE*q{qZOl4)yS1T0qI;xt+rQ)?LbI%02O~OL zQHe!yuFtj^NIP`Zl6d@Ac8u|pp8Ud0?B99z{rhcKR`b!HB(t6&f7d`=P!LBCOcoUvqrueOZOzNrT}v-)Zd4k)zoLubTFi z?B@5M37+!=jv@aDn9#1};dbMZov)4kO#RLHYN7c+`WG|EPRpB^RN2}xg(8Ua(QUVM z#0~c5+1P%Afjo@36uQ{kw!JPi`O`4;#S(Ulm|RT%Mc}aJFik8;2wmH}bBW}DdcM}a znxX}C&1nS8SDZE32ILxB*xHjcMnIOl4lym(9(f3`Zc zm1&Bl=BQ3(x$Qr=(Jj@NrzchSmvgPVhd`~pEV)kWb$D-W>>Tyr&tzKtEqq~wU#;Cm z!|fEmB%{@zW4fASs=A(7CQJ{2)+9sgaxJBki-?n58X&DAx89|l{%Ai#YlzWo>dX~q z1t&A66osY|o9Cf7;TI0WNFvjdzwaMgMw&EE$TgPJ3oH~#(`y;WZ|d1RNumlfY(QCb z4|fCtLl4!XZ4gLlanm4Fq_~CIf)sFYF1H$Z$EM$YE|Wb z?bnj*QxRTn6)Z4tuDAtIKAi=stB`TY%*%<UeV`j--ZK$7kf25^?@pa1#Fm;)6` zmG-s@yS{7-TPl7@K(jk|oN2T^>jA$@nohFmLbp;xiCncXUWs8^M>V+^L`gjjd^DYqSNIX{cq=ILS1p1Js(s^+()sZOKBe2~X88d|h-J?C$Qx8haiymRud4_=!F&wDjpE-!-BwmJANRCktSP zkT6y_CW}8d;$rubPZ$c)=A;)6XKN?E+4=#i1?{PuN%L z8ZDG34kg(PHhiag1+u;he*dj?bD-GPMI&0|^QW%4Eej+Go$rB=O(s-}-$UePaJfpY z8wo=|_7B`zLfR=0G%c<0pE9$~HU)6oo~q99{Um2r0-CH7G(N{rL2PJn`M2zGpTRrLh8Gbn*|XM-NAY(r;0CKsq|HL=t?doN4$1PDTBY-Z zRJYRA?W2h#rrZ7kfxg^kpj&r0vjn&8^8%p(mnQu;6_ZHHpzX9JIi}YWgL1H9jez`f`^8A<3`|o2`HwAt6`v%$|NojYEf-uHFZH1dpSF5aO zNFx5_t5WHmCr0o#(zXl9jxx0qfCE2)$&gN|4WG5US2|oAL{XKbJeNkpB(P z|JXPGOtnqRP|0~p`sKFeY|%2D->q8m0(kJo%n8K>)c@k}FI$AA zvYb!udzD>CnjpLX`cW$)IRav+`AJ`3i)fncnvR}``ihl5KDFgnd&_C)9Wwsx;jf%R zRP=heC$?W1f?gD~=|@=naY30kUlYIobxKN%CJmV+w|Rz=fqX@`I<}X8kS6B>}oCWJWl~*(-wltAirmIEe4m3 z(z(ihMEe4PW3F%(CF}1?a4ZEKUamN_X{>IWZ<7m~EDJcEAH&Ep+};V7ODP-f_Wk;+ zT@8=>iH{r9!i?Y_*W_D)yO-aSXJ=+`7S275#yf5tyd1U?rxGeA<-J3ypv9OH4#s=mkLMDJM9D3W=hG?D6C_3kD;>TsR!T^$cYuyi0*6eQDX2>ZHG z^a<}**IhWK)7kq-vffwo>pf;eknxr4N{|Y`Dd26ki)%j3X|mKb8ra#smHD0XdWljb zJ?-Q4^$+U>v*b?$-*lXBEVNy(S5^zf-sIHhH&Sq%?T04YcOO7pENivf;~8tk4OVk` zV#NT-Vgo&GO!3)N1V|;=W_g>28CRv7t zXeMzzpZ5YzSS+lGZwbUnA?$+6B)4EQ&GCP%5V-;)OENFzu;~H@jk!_Y0}*MxopmUZ z=%UGYw0aq>dLln^Ir56up2C+DYtsPMQlAI63S$VxsXsmiZdCg|vl~LJZ@l})W=wLH zUy(QR^@Z6;LJ17m36P!7{mjr~sPExjw`*6QMcyhj=)y(xr} z#Akk9u!XuaG@tV9<1^&06+J8{yy=mxZo`9hz7Dsx2PGy59)Y8VO(KptxuLDZ)RER^ z3rbrYaP2bT-E{k0%H8BWn$$R$j~m_Dnm{v=eSQ@4?GbSaonCt>Os@f+X{+Cedoq?= z4a_#p^K^yIis65%av@R=hG`8yHM;AvnKmc6*9Wm?NTx8vhR!a0tK>0dUS+xn`$ou^ ztno5ItCp~uh94Al7?@XXu%}-5t}ey`od(@iFurWJYbsyH?6UE_&x+-7+&YR><4R5T zcmZ+-uJ5fNbsQ2AVzMK5;G_ttX8O(NFLVT0X**+l7O#Y_M|=@~4{_0XU>1dC8eZ0( z3YryD?0eADVM zgyCfdEb<0|323RLv�YEY+W2cDxptY^SYrEfdD+)!quwQ0oBZ$+{R7$rMDIXKhQq z=iugi$Z|M+>5#qUc1Zgs0S;2OLhr4c^2?3do6s@fp}Dq~U9Y}J^fS`c#y%@0;Iq-| z;?+Ct*<@ne+Q7=C`iP_cqo;pEw(IlKdNcLUu6LW!+vXx`<l((g5ATH)Z>*D_ zoOCF8gHMYU7qFFM}WWW!Ixud{_kDO?eK@%=PmkX zck3d!07s;!g^y2n^vzax6!%4s%P0G!zvjSHD{=*!5g@gI02_@5^~I?`!IdWZ`$9cO zaef#0w*8XI)ro$}^`_{uK5T_{XcSJyiZ35%i{U;yH z#dx^eEL<5}lg8)#15dj%_S2&b7|!sp#&vqK^AJc|a8AN;07$*W+wWw*9zhlZm-p7W zz^#>l{G$*0c$MjQ(901W%Yk8#^eV#A4gBaFJPWM_{)gDAIOpmL=Bq(*f8XTob?tWg ztLFrDYTD69(d^=99%xE%$)cLbM7ZxV)T}9zB-f`K7n~A0kMIc$=b|M!`h*$MJn{hK z8ug+uYF}n1(_t`@bcLdzKZw(Gkpnvms%wOxS#f1@SsweBJASvqA+iZ zse+L617HK4D+Tzlxjz~VRj`CD3Jk7nDs-_=M_qUp)DtA#pd?c$gqmdzilNH-L9wclL8i*? zVJLtmj~Yjcilc6L@LgS}5j|CUs3GNCD+oEWu{F|E#R`mhGX2NZC#DH3-Wr=^Oz?Tc zh32-^l)~A1Xr0y_B9V6mwwd57?h1~O7maMDdH`OkQ{-eHc4_a_`e+ngI_KUi7_N7a z3A0{yIM(+|m zxVeX`MlU!Xi&`@$_it7YFT*)Ql9!Eg?ff48*a32XzRCCLgP~>6`Xz;c^D|V;rsWy} znX;7FOtF512nM@xmhX3ZO>F%vR(|lT>uOO@sWAfoOG@zn;C4tDAr}wyJ0Ye}fc(|4 z7DP=5Gd&6vBi`Om57YdRhhq#x?em&ym?P|I+}feq*>|MSY5dd&!i+Lwk#w=XqZr9o z1n#Dbe3<_sOCzXf^V1J=HnK2ti=3eKM}d>$_MmWtsZ)hTIH*+*Tp8*Y*OIXi&$RZ`8MK8JtW!TrZT6q)kbM$L)@*QT|tN((w*mO@|LaE|eNo`JACr5hEgkrlw;;LFBV`eUi_et zoA!+>zV1b}Is3shA;QJl=^(tbdS6l@F!3FU#%Tru<&>^V!;sp$cGwKtXnC^$^J!~V z9vYO&mwfg!E)*AAz!l8CDO}6XgqE_GH~vq|vLqy^ITeZov4|6%#3Uy*-16-XZug1! z2v{G9{vrj^S;9W5-Z+0&0)=FZ9i6=(7eLnecc87w>l7Bbq@Ajr$1zi5wSX&^_!Fpn zY$}AsE-vz}^dNbXSKzF0jcRad9H!-;3!S&03n)u7OitSUw2+diQR4k}(->(BRB3sT ziADjXE<8c~l8~LzJx1k)aqu5<=9K7m0BZl_94Ou4q>279Ix$B}^4>0P8be*kF;d4` zA-hNWvzy7%pc6hL9U(!=p}a;YVf(0D@}>HKVWzz0$pKFwqTL3Ac7YqY%|0ij*6H1Ky@Q$8)tN>(;z1} ze*UM#k}Fcfhd8xJm=pw~cN%?j5*1PtSVZ32c9I@mM7KR% z9la5dy|>WRl^0xpOv3)uVSDZ+;kFT~uVpij5=X8Yr#UrSnSrt3an0jtuZZa zJSo)FL&;X|V?}GgTKqY?Z%K{nh+d0M-vk5Fju$30;)z$(I!i=bnWF!&QkWcs5H10r zXbkw9Q2%&dv@@r999lU3Iv6_v0c%&Go~#D^yqnJr#Qj{6pUvpIGtz%uNsDsRoKaTN zBVKHvkt0UY;_SD5KX?H<(>gH%(JI0}4~d>?uEzNo=K$n=+#FprdWW~`VeGs_2A^_N zqY{6@(V~k8rjzg*ymfY#t7xlp+0*Ts{bY>3v68bhh>Zam+%M1LP}9jQJH1RF!2qx@ z){~@J<66t3DkwXH{I;hnSY`^ycL z8v8v_yDBpm-sW%O5QM5<_SBNM$7%Mi(BS&VcK zm&LNSMyC64aF$@3>KJ6SLo2|a`m3CWjLO*Pc!Jgtn6;qm~P%i4zYzrX0@5$ zdXK{@ThGd08hKPM8<4o`eKX@0S&8Y!n^hrPQV-fvJiL>;o(vtW6G%+JCh7A{uPL2O z@(-!jft8*wapBZWSL+Lwk=W>C8;~@OWBcAT>oXyqud9Nyhyu#cT*m3e*T##=z&Vjg zSx6(PW_iv=vGnFg$!uF3j-LSmHB@m>t1bZ~#pOv20&R_D;@6$T*2*Uc#FXt0;>w%6q+5n* zA|D{A7|D}WIV74NokOXXX*CMyfisgM8uM{$O^*72C!@=e2=UUsSm$)h!-Ob+H8I^Ig$&eyn+6=3HM*_?w3Zd zTqpR??L3f|?EC01ikjKk8^nNYo#oCI#Kpj>W^ew*z!?46d%W5!fxQE;H5cEbuvbQ$ z)+0fmo;jvae%>wNd`*Y`A3p~pZA0+_z%=}RweEZe*Q~<}K5&B%&?a;$@zjLsNsdV) zf$+OB7nMQ;<$Gc-D1!9Ed&7{Q1?7YH8b>p8yISk6dE(@4t2_xs(;z2X`9Wpbv66(d ze_;34Q7<0)(BmL1%e$+&P0(49II!ZzN}9nEfi0fq4aJdfUdWYxNU8Ggdj` zc-g(@!&(fk6*5=S9?$4^H+FPbQfTMYpqoODTK)FZ{jPu^c4RsFGC$`w$L`-1M^2Dl5(KIhAGpV2&2i78) z9Tu5CoSScPH8DFS?32K9+!{#jRwzaVI*$>96!{;>YkGQR*Eg?(9R%%AHCC6_iyMNj zO-vHb>**;e`)UkpiwaR_#j{OmIVNP#9jt&FoVQ}Q-E(*1{N$d35DoiMmMxzH zF_flycCHqa&_5Sf2$~^fz_PA#`0S5oF*pT#<@@nwr zua#q>OGynD7|`@*6+bpKBo?K=SDi~`sOeF)jU);ahprx_Ka}YUrzv-BxZ38r z3V-`FgqWSCe*Y1gBTWmiIlllnJOzV`SM${aWkHR4(UoKQYK_2ZVi$MuadL^)1P%-D zkdnPL_x<)iMPF^h7Lb!iCQ%+q)20lZ%)>FO#(C2r%mI8S6L-Gp9Lz{0fhT{Cy;ba6 zD-5PK(kdTgZ(a{r+t$1D^QTBfMcjn&l(dD$<^yRkuuClkB-}wr7Vn} z3|KX>4#9OB<>zh!rSy1KrpyX+?DzK2WK9B@o#m@!W2`4jafWw4%dOge6YmcpBa@XD z{bIl)5JR2jfkuX_^G)j?wweg2?{5!DWaZt|KDow#wgM0S7aaUhZWZ(qxjSs08!LW! za?Mdt(vpwMp~wV&`w;r5u-F6^D*`G$^q}v-&~GgBpV$zTcH&6?5Vsawt_KqppfN9? z+C=`^(i#zt%8JcG^Rx>gI3U7%Vxy2#>1 za6JVIv8!vHi39O>l4)}x`HEH2`U91aR|@>rUF8tAp&p3=f*&P#On|F!sS#EQIf`?^ z;EarA^>CJQ{##sy%o(nb_+1crb38nU6_m)5s87So9?+XyLMZf-nGyZzg*tIx_NU(o zWGtgVI@0}=mg|V0(M?B42UKH`KbGLSku7@@8ippmjT+bQ=Kfu#E{=JD7Qh;(K5(x_ zf6|pjF)LdO&o@0tD>EA&WITHPJtA)g{E?VqU5C>V?v4fECNv6c&|vr)2CIu+%Ll7f zh6}*s%l~>Q%x_MK0%(BlAYHgWt%9hm9L71y@T=rr%Jw}GHT;=H#h^^(qk~$0~CMP60YgZ5~0S;kr@1*AF z%eLIJpPAG-1BEr&2A*(9S)?{Z4M@dji%{h;ffCl4>7>7SkKWC>p_Qq9#i(bRKn9b9tn~&BIsD7t9~r**#~%HmcTIk8{i|{_1a?3{}}Hu`I6L^I*5Tz z#f`g$NH0H19Y&|SG)>Tw??||$j2t2BMKUI{$#3J;r5&-R(97i!Er~r)M9HJv3a30E z4Os6Y)cP5NWk?nY8jU#)%`A*#W8|UAP8qYJN@Amth(?2YuZJ{kKKtlW=tty$@Of?t zs4RhcC=^Pd)`ud=_R@bkKN4Bunca%HsOrb8cM=%?yffTxJ>r6yA9=`{Kzw2vi+var zX<>Czt*oROcy#cem63Q9PRh$CDkgU>-6H5D#0hd8`HD&+dWH&;S-f>?evB?N<-dda z3QR5O*mjW2Z0+f#NF}s_+u| zCB+}Y*xRbH2RbA7(EDmtClENJDogV?cKl4)357FJIgFtBX%nBncr_)(XM*L5GVqwG z_i&!p)Eci{?8vPmW`JwZ(m#ix9bNdfU4R8{yBT=~m~E0+AD-s2!u9lz+msLEG)8`d z6;_DHmXa{7S{g~Q=*-f})t3K)>xcNvJE+pywGfkD zsJlTB?)p$qTzjC%kW7+yYn0{e(_}sdJ*2S-@Za9#t&Yw;Ipu7NuGM;Hy8m9&&|~+LZLNB9r{!?H)KqUiH*+&q<3r zahNvl%*$NyGN~|`O!6WU6TYc;H<{9LahUfdgL81n%mqe$&R@!cs}A=i1)1l0;G3>k zEryZQ7qPxqL~6%JLui-eHoJ5<|5bF3T@o0JtbwiPYxpq*;1%1_M%DSf7abaReYDE? zU~o+9N+`zPY5cnIkbg%Y=?(54SFHO0RG#XuSW#l6lI|#iAxQmylR&~O?q)=r!;v$wjU-GdfxF^ z!S*h0)(3!ITV@~J)ss3eMl|oevtAON1mgYkQ@Q97<4s@tzjycJHrr+WvOL9RLFhIFqy9Vhl z{SFfAS|@od{)~Sz9~Q$V+)b_M_t^c2+oiRdh_i9Vt?S~Yog(Zte9IX^?FAEWGtfQuQYtEO zaKw+4Uh?v9a9P}7aL~q{7PPsQ9NYK&UocC#SSnNSgO#9Eyi(>55iYY-pxqNz`PNec zIl3rP*IW*!wTt`f`{W#F>~Sk2dViSYeEz7mEW~(1bK|rnlE6=z2V5RbIFW&H0?d&| zg_Q$M9%{~LM1qvAygqE(Q7)D)E1EBDHp13g zZ34WBmMEr2Q?NIS zsU3?CWBLI`zEL;dLMbr7Z<7b1qA!w?)+-L5r}NKo%_~&3!{yQdMlLT-TIO@4Va%L}3UmlDGHQPdezL{a!tPdlA%f*$1wzW&XV08=@+a=S zp&c=WWu_o9Mu=Tf_(|y3yr4p}k=5{}JGiVm500*kpgV%PE}~jt3-t9k%6z{>@*O;` zvCMh_yZ{ISIkUJC#8QiHZE~Ml=;!HM3-B!;=}_rycjkoNqZ>l|@*+OiC>G@-NZ)l) zZERP@-@Nm@xwmle-ETf%%Oz{`Cz}#{tY}?6MBiAR(Eq1M?WLD6DRn^Z<`sekW56rl z>wH`6?|Z6?W}g^hVxB)R7G{^CMI|p05bryT{#JX~t`KN7^lGRoHT5`Urp6bpU%Z!{ z^8dksY<_=-Y;EUua;m3k<$E8(;Y+!OXMT)B>SdDpL_5&Xsa)|CLW4r33p+mSAJjE+ zRT-O;(#PPn$O(agG4?D>|$s5x{@Y{3|mJZIr$ zSlj2l;xWvqgLB)bL%gH8DQn)uhl zado`AmVRWSoY4utV)E_>36>9pU>>M(cP^{@#PwU&55$ZMk^iZ?94w4m-yOq^`r@CO zTA24!@_v5+NrxHZht&*F(pu0GIV&*4tkoC()Tp=Oc)2alOS|a@fAD0-2E)YBSL}$P z#?OLr3xE2MbMaZiH|7`fUBod zhe^2Q(Q!Gpa9^`Y&5rwa>XgB$ZrX3Rbu}H1lYSi~Z+nmkinq&lNvfz^-`C@+BFhak z?+fMOvc z4WfG4bn<&B_a|_ap!*C9V-w^t@Z>{_+XH+WZwytKz6&`4~jdxcKN2538=cXx`r<& zxNJX+$C~vKr+R6KQ9PW|pR_h;_cxK0oCch)wOBnnB*w7{>eO#Pu3`|OP1Q|^c{lE# zjpwb!@^!Uud(ksKYZ+(HyJ9xXSE2CKF;fwdCz9sb35cHf4ECT5WfoNS)>^5V#Hc@TD#+GM!;sslM7MC^aZ zPQ{pgUrVl699Vw2ELJVI+i$-tSW_lA-c>>=-X|RSVth za^Njt(W-YOAh*a{ zu;cLGQK$;G)2-&PU>3OwJc7o>Z+60vK~)dbq#WA52I~*zKk07DS0+lO??sw- zqHVHpzhRaB_8IDsJ545%|3ZZJMHkKYpWdh60wIGor z3a+#ZC{k;#;F}_y4GNX`)%Q`o+4;brYvsjA5VqCh_FMyI$T+g(xV_pGa&YtJazsrb~r#U%+{a71h5)tB!AE^##FzM`kP^$dX)Jg2D%ABVtaZAztu`xE6Yf`Zbw8 z92$2xZI#s;8oP>Po>MLFu`|qbpCujo4|MwMp$A=xMkY&zdmw~XTFFZ!#nK^Dscspg zn-o^1RF#zDK$8JKs5BO#<|-;g2~tIbet@QC$G-oc(GpJYFcSS$;m^8K%=|8p{qQBy z@*^zyX4uylJN%qw4Q>Gb5~~DRF!Iv*CKpouA^nkYka1dj=|qSJ^x5GlD7u6F>>ku8 z!@~2)-&lGI`0^*nti99UHseHm>P}X9U;mXsaIUhSnD67?%FOHvq4i>Rgta;n51r@U z)~+GzpHQ+b?@!ev^?pw~c|_FL!-knh??vtdgYY~(8+-(u#(%f!=>EZoaqzeKHyx8D z=Xuc>zZt@^VT2b#7;khzd0uM7b-(}%`^j|v@RJ|=9km2df7+bJVK{$y=22r0+J>z) z@k+acsyY{mSO3sY`7I54{O*zCq)sNwFIfY$p}B(@5~Y#e3I>9H53~r{8odwBEq)x* zT60WvMcIl0P7*(s`YH;y5!*CY6GF z12R61_nuB=D}mwtjV7K-Nwk5Rl%O%k+jpfov|0IFa#i23a30=0vvY%0RasO}7>84K zhlG}&xLm6w|1d&TZ|~QIW5l^@(}amhCN?9v8$$oVSf52;T(a<8H(iK_b|Q}CXjEf* zyVbi0Q}n?sIv1g$cm~V6K}OK~!QO=H$suuCo0?vg%fTg9$!NrWCWGq2xo;XbWMlH4 zaO!g*`tqaj*Q%YEry7n;XkO2bPVYtqdc47g(GDd)UZ9zXA*LyqVLRX**QC&jPn*=c z!_i;R;T%V5FFe`M8Hu)jSZpu~8?i{S!K}^*VIguf(MTpa zfvIGvfZL#trj=LCq|u?C?QYNK;t>NemkXIgc;!M4KI8uI-oJbExwAIl6{r0QD|!`6 zh;dLuOtvfDA-3$D3X>xWN2S_IggYE31wkTOi9L9-f4DT>I3f{ki~2FxA=%pg54@D% zMg`7E1H1jP3iw5|%Ig!vY5&RP)h?MWH5Cb%d0y@c*MaD1;K*O!Nfc&oWku9s_8R(y zw`X;071yXRfUR2XpatAVL^6Ue>dm9?#-{~j8Tvds#GO7b z-Q@h(rE3sb%jTjarC@z|Y7=3JfP!L~$L6K_xIB)x$+`L0r(GDv7{n zBpmBCK>u*Wuz4HKzzO|j(@A9u-QMPGfCdeFNkm^JUTZ_+!E`8^WhDF%H&#Nye_vO- z1&7EfkNuw|b@R?}#x-;4_q}S$3XW3%?_3A!oI{~(P_jwxMH4EzXRLK(2uS#t!^4p) z$4aKXKOS)|gUVJ`=~;H86Y_w%DQ6wB@lkJvzld@6K$s-|J#(69HYf>}s$id1SUZ?- z3|UxJBaq^woOBU);Fo*cM7_aiIx2O}E{FfL$xu4<0{4GiX6IgstP_~u8|gbgE-o+) zhOs{ClVe|`R6o0pl;7~Ae+vdMZu{UP002?c7I~+P-m=I10OFI)SCD}0AjTac3a(5q zp!$&V+76sMTjG>1sh}0^+6A!T*~uKE)5uS{#1y>pTN5WxEv+2$)=2sNxjnjz?zM$k zEZo3!ACW5)#Sv2FxggKwkCS>saaqXYPr+iL9N`Ka;;SnqKaL{FYqE?!E_Q_@(eBrd zyyBf#5MdjT)6Q-k*obZK!nZe)RU|N~?3&}PQS@vY>*EIpns#A#oM*UVYxIsUIHaq?x^N5;>%SCe1in|&+tNQKPB_=I-m*pXkHhD3 z?_$P@>f$xxHCgAqn$kKho4l#zOe?IXm@XXpxH&trU;&wi{k4BUZ;5s8TDf&4?xwTO zlsb(tR{&h0hBWru4z;8a=wPnHMIY0;Zrd&2I<|Z~zTI?S##)fM9g6%%Oh+ zHoN)9K(xzJ0=+T{MoPacx_kiLM%O;(RJ$XY;1CA@ZWi-Hbhfx`MECLrpX#5WTpI?3 zUbdL&07QL~^EXl(mwcfhXo^5mR5*$!%kd_kHKRq4+oLYg){Q*-0=dO@))}awU1Q{s8SH0XwdzAno$|UX(Vy zYPO!rUCS@mZZJc(3~`cj<(?FGCa~<>?P+U22R83fM2=D~R(0)^UeKRT^11rkkaQPX zcfy`q0~Do5{K;a-=%;gIeHpJqgc=xq22L7P1%0P!R34%E1g zS~Sx61$fW5pqAYM32yhEdALH}x&%XLlc}_4BW_Hb$y)5P{4w$A@Cd;U>8DachTd&A zhN)g5y5y#<^#GLQw^m1#zWJC=uZIWRYbLcsO5rYPym43gEUQH+9I3-q*<$kNC14t&*o355 zotrsR-@@39{1+gbFd*$cl@3L| zWHp_y4PWG9SU1^N7y&bX2iajossIqsHfQjc<|!=xt?Z6Q+~dC0Md0FD}*ofK$ZNN8-t^YbKL|q=7C? zUpN|B(!+a<==tHYb;}Xp!-b}9wb8lbpgd9m76~j6=C_sWxQ(t7vJE zb1ZR^OVpm+93>_>Nrnzt)A7Rajw|%Qm1$5$o|BCtz;El4!9H<&_(l<(@rn@Pl~55E zZ?VvuL~wqWt)TKB3~oLWib{ln%*#~-Xm}ATlmc+I$T6bVfOytYzNJQ-=7U_1Eg*Xo6u@x^48?9Oe_k+u@p zKQ*pNIjj{i)e};C54*xoN3$+3JQG%;l+mncOrhI2^=udu(!LBvCz-MtP?u&ev2tn( z-I;oysE#}yS;6kN|KI8KztFtqYi+~fy)70NVtyM_{e6`i_hMtbfWwF1?CJlRjuiKd;2oUcm5+!hql z(hBUSGx?udLB~qcMex08TE2Xm5L;p@DuiplwZh9RHcIS&6FC32f)rEh+kyJsQg0+M zXjN6U1DHlhPwURH??qP?6CUP~Rk>8rZ_oAA==a85U)Z_B#$2+y1cZ{hj!1mXDC0&V zPInLWFZ<}&O7+=Y0=@!azA7?1`gU)z#$k~L(_IgbGy*G}QVb3b-;)2J_jTQ0)Lin` zxi+_sFIRtgVnP`k^+{HS>eJnZo*I%(5JZ4)<}C(8Po00miX59`RsAZQ@n__2Q~J}5 z-1*S$zZzOFiSTVKYBA-YPc7T%I<>XTO8EF3teZEV9+)>?!Jk>HEO95+3 zKY38iYmu^;$4I~|gi~;W1YXR3q*&+b05;dCGJNCUIBQiUOv_s3vOlg!c_m+^X#Vwe z^BBxoCIWme)R-(IOh>D7jR8kRj7mhKLI0N2P3Kdp7T`RAbj*Lf8CG}>212bO#4p&rZ1zJ9X9mFpK4A; z{U!}@878=9vQi8nF;6Xg_xNl4Hlu7D6#O2Yylhu}`Pcq)0+sABu9Pas@%rf7J>(d| zu~#!#B=PP6P4U9?08Ry0^--Ko2iqFk@{2a}j`iee@i?2sB@6t$Ppna z7?B)u2|mhHu5`$a7-yKFIP!mKYr0`6-e=+UJ2mm?9W-GAkjq=IK)`FCDBag<)RPJ5 zv)qbHM6w#YbN)80uF}Lzt8v_g7&R!}C+f|Cn+#)SUM@@9SE02}c9tZe2hDfcE%=A} zk!P}YRlBp>kfF7}K*P2+F{~C+fqJgO0gfXu_E?@^06S%O2kFLL=Kl8OZ-w{Y2^%>-vroJxb-yF41mDY~hDUrAyAYeR*6;c;s&}=xj z2OiN+NNt!qZC~~<_T2irWL)wa18rYj7Z##_Z9L^uKm$RUTE)M378T>MFaW-z_uspJ z|CaxviIInOS0Sd|F0;(k`3M^Z$h2qy?1iY6D0rF8Xxvrl40oQ%cd@1uH9X?Hw&4k33PzXeM>K&{K7Q|w<_T`MYK!RmZ&HC7?&JeadDdahr| z(d70fC}=Z`;^XsF0{OMrJK87RN~N3ksQ8Doy?=O0@0$>NQ&M1LcL#Cf9YvST8%$!< zq#du1imbX1HDvoMP*Ek%BP>84wGrJzSo<#s<( z5($>IGhu4?+^*z+m|My3QXdYT{i#oSvu6!hTzfO7HrCw3g`;I7HM*bL7kGP#W&BxG zd=u%SXficFF&tRvt>JOHAv`r)x_F>duvfcL>0_|@Xyr)95TclA?)S_5Zf>R=su`{? zC?-Vje19LlI2?_&GpTg@PhN}otxx{$wV4tozTcjyn4~S~4k%&LnD8+0?RNM!sX_FQ zet2zrIo0>DdJ0I_TE}=4n+lk<{l}GTg8iUW?y(jni8uQSB zsK07vD5)u-yc!onbkQBJzr=>8JGaYYx%G=i_+#sX#OC8BEoA%7&gpT9yn%uR-L7!! z&Ax3Hl|+aMpbnfss1EKEo}*mPkk6=C0TA)Y&<+nu1bnOeqyc{K27kKS(2^O}-Q51m z(r)A|Tnb->BzJB*;C{ya54vlJYs003{&VwF*?enZ8NXk#5($Yp`!`=Qicm1E3;z`) zEFd&E^&QarvDm@pPGL?b95}Wszq~PRZ)u596SF|vX;gU8(lT~Zea&A&dVaKLr>yOe zC8{Dz5(WWpsP(Ws^#8aBn-0A`r}hnAgrNrSA7+T)NrC?ll}!2w2ZD_b+i|AOy zzty9uf}zpI9Y^lWORdB)_5n3>ZLmFC5+6Fm=X!)GqrA*xEC&4>59jjX{jbCF?g zyMc_qQ6MOp&cP<^CkM@;*_7l!a}mDQIBm_u&~{#j_IZT{VPVeV_i!!ZAsC%NH|pTKCzu z{NVjHR8>-vWRq*@!e{oo`gC^5f;HpQ+GF%)udsWEMwHbkN6GPq;ynJM_2F;S6vZ6% zF8*GZXEq3wx?ClGCw7cQJl(|t j0$Cn=(dH|@Qm|-- - #include - #include - #include - #include +## Basic Syntax - using namespace soci; - using namespace std; +The simplest motivating code example for the SQL query that is supposed to retrieve a single row is: - bool get_name(string &name) { - cout << "Enter name: "; - return cin >> name; - } +```cpp +int id = ...; +string name; +int salary; - int main() - { - try - { - session sql(oracle, "service=mydb user=john password=secret"); +sql << "select name, salary from persons where id = " << id, + into(name), into(salary); +``` - int count; - sql << "select count(*) from phonebook", into(count); +## Basic ORM - cout << "We have " << count << " entries in the phonebook.\n"; +The following benefits from extensive support for object-relational mapping: - string name; - while (get_name(name)) - { - string phone; - indicator ind; - sql << "select phone from phonebook where name = :name", - into(phone, ind), use(name); +```cpp +int id = ...; +Person p; - if (ind == i_ok) - { - cout << "The phone number is " << phone << '\n'; - } - else - { - cout << "There is no phone for " << name << '\n'; - } - } - } - catch (exception const &e) - { - cerr << "Error: " << e.what() << '\n'; - } - } \ No newline at end of file +sql << "select first_name, last_name, date_of_birth " + "from persons where id = " << id, into(p); +``` + +## Integrations + +Integration with STL is also supported: + +```cpp +Rowset rs = (sql.prepare << "select name from persons"); +std::copy(rs.begin(), rs.end(), std::ostream_iterator(std::cout, "\n")); +``` + +SOCI offers also extensive [integration with Boost](boost.md) datatypes (optional, tuple and fusion) and flexible support for user-defined datatypes. + +## Database Backends + +Starting from its 2.0.0 release, SOCI uses the plug-in architecture for +backends - this allows to target various database servers. + +Currently (SOCI 4.0.0), backends for following database systems are supported: + +* [DB2](backends/db2.md) +* [Firebird](backends/firebird.md) +* [MySQL](backends/mysql.md) +* [ODBC](backends/odbc.md) (generic backend) +* [Oracle](backends/oracle.md) +* [PostgreSQL](backends/postgresql.md) +* [SQLite3](backends/sqlite3.md) + +The intent of the library is to cover as many database technologies as possible. +For this, the project has to rely on volunteer contributions from other programmers, +who have expertise with the existing database interfaces and would like to help +writing dedicated backends. + +## Langauge Bindings + +Even though SOCI is mainly a C++ library, it also allows to use it from other programming languages. +Currently the package contains the Ada binding, with more bindings likely to come in the future. diff --git a/docs/indicators.md b/docs/indicators.md new file mode 100644 index 0000000000..e020cafaf2 --- /dev/null +++ b/docs/indicators.md @@ -0,0 +1,102 @@ +# Data Indicators + +In order to support SQL NULL values and other conditions which are not real errors, the concept of *indicator* is provided. + +## Select with NULL values + +For example, when the following SQL query is executed: + +```sql +select name from person where id = 7 +``` + +there are three possible outcomes: + +1. there is a person with id = 7 and her name is returned +2. there is a person with id = 7, but she has no name (her name is null in the database table) +3. there is no such person + +Whereas the first alternative is easy to handle, the other two are more complex. +Moreover, they are not necessarily errors from the application's point of view and what's more interesting, they are *different* and the application may wish to detect which is the case. +The following example does this: + +```cpp +string name; +indicator ind; + +sql << "select name from person where id = 7", into(name, ind); + +if (sql.got_data()) +{ + switch (ind) + { + case i_ok: + // the data was returned without problems + break; + case i_null: + // there is a person, but he has no name (his name is null) + break; + case i_truncated: + // the name was returned only in part, + // because the provided buffer was too short + // (not possible with std::string, but possible with char* and char[]) + break; + } +} +else +{ + // no such person in the database +} +``` + +The use of indicator variable is optional, but if it is not used and the result would be `i_null`, +then the exception is thrown. +This means that you should use indicator variables everywhere where the application logic (and database schema) allow the "attribute not set" condition. + +## Insert with NULL values + +Indicator variables can be also used when binding input data, to control whether the data is to be used as provided, or explicitly overrided to be null: + +```cpp +int id = 7; +string name; +indicator ind = i_null; +sql << "insert into person(id, name) values(:id, :name)", + use(id), use(name, ind); +``` + +In the above example, the row is inserted with `name` attribute set to null. + +## Bulk operations with NULL values + +Indicator variables can also be used in conjunction with vector based insert, update, and select statements: + +```cpp +vector names(100); +vector inds; +sql << "select name from person where id = 7", into(names, inds); +``` + +The above example retrieves first 100 rows of data (or less). +The initial size of `names` vector provides the (maximum) number of rows that should be read. +Both vectors will be automatically resized according to the number of rows that were actually read. + +The following example inserts null for each value of name: + +```cpp +vector ids; +vector names; +vector nameIndicators; + +for (int i = 0; i != 10; ++i) +{ + ids.push_back(i); + names.push_back(""); + nameIndicators.push_back(i_null); +} + +sql << "insert into person(id, name) values(:id, :name)", + use(ids), use(name, nameIndicators); +``` + +See also [Integration with Boost](boost.html) to learn how the Boost.Optional library can be used to handle null data conditions in a more natural way. diff --git a/docs/installation.md b/docs/installation.md index 6721625148..a1f3e80090 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,15 +1,6 @@ -## Installation +# Installation -* [Requirements](#requirements) -* [Downloading](#downloading) -* [Building using CMake](#building) - * [On Unix](#unix) - * [On Windows](#windows) -* [Building using classic Makefiles on Unix](#makefiles) -* [Running Tests](#regression) -* [Usage](#library) - -### Requirements +## Requirements Below is an overall list of SOCI core: @@ -27,187 +18,204 @@ and backend-specific dependencies: * [libpq](http://www.postgresql.org/docs/8.4/static/libpq.html) - C API to PostgreSQL * [SQLite 3](http://www.sqlite.org/) library -### Downloading +## Downloads -Download package with latest release of the SOCI source code: [soci-X.Y.Z](https://sourceforge.net/projects/soci/), where X.Y.Z is the version number. Unpack the archive. +Download package with latest release of the SOCI source code: [soci-X.Y.Z](https://sourceforge.net/projects/soci/), where X.Y.Z is the version number. +Unpack the archive. You can always clone SOCI from the Git repository: - git clone git://github.com/SOCI/soci.git +```console +git clone git://github.com/SOCI/soci.git +``` -### Building using CMake +## Building with CMake SOCI is configured to build using [CMake](http://cmake.org/) system in version 2.8+. -The build configuration allows to control various aspects of compilation and installation by setting common CMake variables that change behaviour, describe system or control build (see [CMake help](http://cmake.org/cmake/help/documentation.html)) as well as SOCI-specific variables described below. All these variables are available regardless of platform or compilation toolset used. +The build configuration allows to control various aspects of compilation and installation by setting common CMake variables that change behaviour, describe system or control build (see [CMake help](http://cmake.org/cmake/help/documentation.html)) as well as SOCI-specific variables described below. +All these variables are available regardless of platform or compilation toolset used. -Running CMake from the command line allows to set variables in the CMake cache with the following syntax: `-DVARIABLE:TYPE=VALUE`. If you are new to CMake, you may find the tutorial [Running CMake](http://cmake.org/cmake/help/runningcmake.html") helpful. +Running CMake from the command line allows to set variables in the CMake cache with the following syntax: `-DVARIABLE:TYPE=VALUE`. +If you are new to CMake, you may find the tutorial [Running CMake](http://cmake.org/cmake/help/runningcmake.html") helpful. -The following tables provide summary of variables accepted by CMake scripts configuring SOCI build. The lists consist of common variables for SOCI core and all backends as well as variables specific to SOCI backends and their direct dependencies. +### Running CMake on Unix -#### List of a few essential CMake variables +Steps outline using GNU Make makefiles: -* CMAKE_BUILD_TYPE - string - Specifies the build type for make based generators (see CMake [help](http://cmake.org/cmake/help/cmake-2-8-docs.html#variable:CMAKE_BUILD_TYPE)). -* CMAKE_INSTALL_PREFIX - path - Install directory used by install command (see CMake [help](http://cmake.org/cmake/help/cmake-2-8-docs.html#variable:CMAKE_INSTALL_PREFIX)). -* CMAKE_VERBOSE_MAKEFILE - boolean - If ON, create verbose makefile (see CMake [help](http://cmake.org/cmake/help/cmake-2-8-docs.html#variable:CMAKE_VERBOSE_MAKEFILE)). +```console +mkdir build +cd build +cmake -G "Unix Makefiles" -DWITH_BOOST=OFF -DWITH_ORACLE=OFF (...) /path/to/soci-X.Y.Z +make +make install +``` -#### List of variables to control common SOCI features and dependencies +### Running CMake on Windows -* SOCI_STATIC - boolean - Request to build static libraries, along with shared, of SOCI core and all successfully configured backends. -* SOCI_TESTS - boolean - Request to build regression tests for SOCI core and all successfully configured backends. -* WITH_BOOST - boolean - Should CMake try to detect [Boost C++ Libraries](http://www.boost.org/). If ON, CMake will try to find Boost headers and binaries of [Boost.Date_Time](http://www.boost.org/doc/libs/release/doc/html/date_time.html) library. +Steps outline using Visual Studio 2010 and MSBuild: -#### IBM DB2 +```console +mkdir build +cd build +cmake -G "Visual Studio 10" -DWITH_BOOST=OFF -DWITH_ORACLE=OFF (...) C:\path\to\soci-X.Y.Z +msbuild.exe SOCI.sln +``` -#### SOCI DB2 backend configuration +### CMake configuration -* WITH_DB2 - boolean - Should CMake try to detect IBM DB2 Call Level Interface (CLI) library. -* DB2_INCLUDE_DIR - string - Path to DB2 CLI include directories where CMake should look for `sqlcli1.h` header. -* DB2_LIBRARIES - string - Full paths to `db2` or `db2api` libraries to link SOCI against to enable the backend support. -* SOCI_DB2 - boolean - Requests to build [DB2](backends/db2.html) backend. Automatically switched on, if `WITH_DB2` is set to ON. -* SOCI_DB2_TEST_CONNSTR - string - See [DB2 backend reference](backends/db2.html) for details. Example: `-DSOCI_DB2_TEST_CONNSTR:STRING="DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off"` +By default, CMake will try to determine availability of all depdendencies automatically. +If you are lucky, you will not need to specify any of the CMake variables explained below. +However, if CMake reports some of the core or backend-specific dependencies as missing, you will need specify relevant variables to tell CMake where to look for the required components. +CMake configures SOCI build performing sequence of steps. +Each subsequent step is dependant on result of previous steps corresponding with particular feature. +First, CMake checks system platform and compilation toolset. +Next, CMake tries to find all external dependencies. +Then, depending on the results of the dependency check, CMake determines SOCI backends which are possible to build. +The SOCI-specific variables described below provide users with basic control of this behaviour. +The following sections provide summary of variables accepted by CMake scripts configuring SOCI build. +The lists consist of common variables for SOCI core and all backends as well as variables specific to SOCI backends and their direct dependencies. -#### Firebird +List of a few essential CMake variables: -##### SOCI Firebird backend configuration +* `CMAKE_BUILD_TYPE` - string - Specifies the build type for make based generators (see CMake [help](http://cmake.org/cmake/help/cmake-2-8-docs.html#variable:CMAKE_BUILD_TYPE)). +* `CMAKE_INSTALL_PREFIX` - path - Install directory used by install command (see CMake [help](http://cmake.org/cmake/help/cmake-2-8-docs.html#variable:CMAKE_INSTALL_PREFIX)). +* `CMAKE_VERBOSE_MAKEFILE` - boolean - If ON, create verbose makefile (see CMake [help](http://cmake.org/cmake/help/cmake-2-8-docs.html#variable:CMAKE_VERBOSE_MAKEFILE)). -* WITH_FIREBIRD - boolean - Should CMake try to detect Firebird client library. -* FIREBIRD_INCLUDE_DIR - string - Path to Firebird include directories where CMake should look for `ibase.h` header. -* FIREBIRD_LIBRARIES - string - Full paths to Firebird `fbclient` or `fbclient_ms` libraries to link SOCI against to enable the backend support. -* SOCI_FIREBIRD - boolean - Requests to build [Firebird](backends/firebird.html) backend. Automatically switched on, if `WITH_FIREBIRD` is set to ON. -* SOCI_FIREBIRD_TEST_CONNSTR - string - See [Firebird backend refernece](backends/firebird.html) for details. Example: `-DSOCI_FIREBIRD_TEST_CONNSTR:STRING="service=LOCALHOST:/tmp/soci_test.fdb user=SYSDBA password=masterkey"` +List of variables to control common SOCI features and dependencies: -#### MySQL - -##### SOCI MySQL backend configuration - -* WITH_MYSQL - boolean - Should CMake try to detect [mysqlclient](http://dev.mysql.com/doc/refman/5.0/en/c.html) libraries providing MySQL C API. Note, currently the [mysql_config](http://dev.mysql.com/doc/refman/5.0/en/building-clients.html) program is not being used. -* MYSQL_DIR - string - Path to MySQL installation root directory. CMake will scan subdirectories `MYSQL_DIR/include` and `MYSQL_DIR/lib` respectively for MySQL headers and libraries. -* MYSQL_INCLUDE_DIR - string - Path to MySQL include directory where CMake should look for `mysql.h` header. -* MYSQL_LIBRARIES - string - Full paths to libraries to link SOCI against to enable the backend support. -* SOCI_MYSQL - boolean - Requests to build [MySQL](backends/mysql.html) backend. Automatically switched on, if `WITH_MYSQL` is set to ON. -* SOCI_MYSQL_TEST_CONNSTR - string - Connection string to MySQL test database. Format of the string is explained [MySQL backend refernece](backends/mysql.html). Example: `-DSOCI_MYSQL_TEST_CONNSTR:STRING="db=mydb user=mloskot password=secret"` - - -#### ODBC - -##### SOCI ODBC backend configuration - -* WITH_ODBC - boolean - Should CMake try to detect ODBC libraries. On Unix systems, CMake tries to find [unixODBC](http://www.unixodbc.org/) or [iODBC](http://www.iodbc.org/) implementations. -* ODBC_INCLUDE_DIR - string - Path to ODBC implementation include directories where CMake should look for `sql.h` header. -* ODBC_LIBRARIES - string - Full paths to libraries to link SOCI against to enable the backend support. -* SOCI_ODBC - boolean - Requests to build [ODBC](backends/odbc.html) backend. Automatically switched on, if `WITH_ODBC` is set to ON. -* SOCI_ODBC_TEST_{database}_CONNSTR - string - ODBC Data Source Name (DSN) or ODBC File Data Source Name (FILEDSN) to test database: Microsoft Access (.mdb), Microsoft SQL Server, MySQL, PostgreSQL or any other ODBC SQL data source. {database} is placeholder for name of database driver ACCESS, MYSQL, POSTGRESQL, etc. See [ODBC](backends/odbc.html) backend refernece for details. Example: `-DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="FILEDSN=/home/mloskot/dev/soci/_git/build/test-postgresql.dsn"` - -#### Oracle - -##### SOCI Oracle backend configuration - -* WITH_ORACLE - boolean - Should CMake try to detect [Oracle Call Interface (OCI)](http://en.wikipedia.org/wiki/Oracle_Call_Interface) libraries. -* ORACLE_INCLUDE_DIR - string - Path to Oracle include directory where CMake should look for `oci.h` header. -* ORACLE_LIBRARIES - string - Full paths to libraries to link SOCI against to enable the backend support. -* SOCI_ORACLE - boolean - Requests to build [Oracle](backends/oracle.html) backend. Automatically switched on, if `WITH_ORACLE` is set to ON. -* SOCI_ORACLE_TEST_CONNSTR - string - Connection string to Oracle test database. Format of the string is explained [Oracle backend reference](backends/oracle.html). Example: `-DSOCI_ORACLE_TEST_CONNSTR:STRING="service=orcl user=scott password=tiger"` - -#### PostgreSQL - -##### SOCI PostgreSQL backend configuration - -* WITH_POSTGRESQL - boolean - Should CMake try to detect PostgreSQL client interface libraries. SOCI relies on [libpq](http://www.postgresql.org/docs/9.0/interactive/libpq.html") C library. -* POSTGRESQL_INCLUDE_DIR - string - Path to PostgreSQL include directory where CMake should look for `libpq-fe.h` header. -* POSTGRESQL_LIBRARIES - string - Full paths to libraries to link SOCI against to enable the backend support. -* SOCI_POSTGRESQL - boolean - Requests to build [PostgreSQL](backends/postgresql.html") backend. Automatically switched on, if `WITH_POSTGRESQL` is set to ON. -* SOCI_POSTGRESQL_TEST_CONNSTR - boolean - Should CMak try to detect [SQLite C/C++ library](http://www.sqlite.org/cintro.html). As bonus, the configuration tries [OSGeo4W distribution](http://trac.osgeo.org/osgeo4w/) if `OSGEO4W_ROOT` environment variable is set. -* SQLITE_INCLUDE_DIR - string - Path to SQLite 3 include directory where CMake should look for `sqlite3.h` header. -* SQLITE_LIBRARIES - string - Full paths to libraries to link SOCI against to enable the backend support. -* SOCI_SQLITE3 - boolean - Requests to build [SQLite3](backends/sqlite3.html) backend. Automatically switched on, if `WITH_SQLITE3` is set to ON. -* SOCI_SQLITE3_TEST_CONNSTR - string - Connection string is simply a file path where SQLite3 test database will be created (e.g. /home/john/soci_test.db). Check [SQLite3 backend reference](backends/sqlite3.html) for details. Example: `-DSOCI_SQLITE3_TEST_CONNSTR="my.db"` +* `SOCI_STATIC` - boolean - Request to build static libraries, along with shared, of SOCI core and all successfully configured backends. +* `SOCI_TESTS` - boolean - Request to build regression tests for SOCI core and all successfully configured backends. +* `WITH_BOOST` - boolean - Should CMake try to detect [Boost C++ Libraries](http://www.boost.org/). If ON, CMake will try to find Boost headers and binaries of [Boost.Date_Time](http://www.boost.org/doc/libs/release/doc/html/date_time.html) library. #### Empty (sample backend) -##### SOCI empty sample backend configuration +* `SOCI_EMPTY` - boolean - Builds the [sample backend](backends.html) called Empty. Always ON by default. +* `SOCI_EMPTY_TEST_CONNSTR` - string - Connection string used to run regression tests of the Empty backend. It is a dummy value. Example: `-DSOCI_EMPTY_TEST_CONNSTR="dummy connection"` -* SOCI_EMPTY - boolean - Builds the [sample backend](backends.html) called Empty. Always ON by default. -* SOCI_EMPTY_TEST_CONNSTR - string - Connection string used to run regression tests of the Empty backend. It is a dummy value. Example: `-DSOCI_EMPTY_TEST_CONNSTR="dummy connection"` +#### IBM DB2 -By default, CMake will try to determine availability of all depdendencies automatically. If you are lucky, you will not need to specify any of the CMake variables explained above. However, if CMake reports some of the core or backend-specific dependencies as missing, you will need specify relevant variables to tell CMake where to look for the required components. +* `WITH_DB2` - boolean - Should CMake try to detect IBM DB2 Call Level Interface (CLI) library. +* `DB2_INCLUDE_DIR` - string - Path to DB2 CLI include directories where CMake should look for `sqlcli1.h` header. +* `DB2_LIBRARIES` - string - Full paths to `db2` or `db2api` libraries to link SOCI against to enable the backend support. +* `SOCI_DB2` - boolean - Requests to build [DB2](backends/db2.html) backend. Automatically switched on, if `WITH_DB2` is set to ON. +* `SOCI_DB2_TEST_CONNSTR` - string - See [DB2 backend reference](backends/db2.html) for details. Example: `-DSOCI_DB2_TEST_CONNSTR:STRING="DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off"` -CMake configures SOCI build performing sequence of steps. Each subsequent step is dependant on result of previous steps corresponding with particular feature. First, CMake checks system platform and compilation toolset. Next, CMake tries to find all external dependencies. Then, depending on the results of the dependency check, CMake determines SOCI backends which are possible to build. The SOCI-specific variables described above provide users with basic control of this behaviour. +#### Firebird -#### Building using CMake on Unix +* `WITH_FIREBIRD` - boolean - Should CMake try to detect Firebird client library. +* `FIREBIRD_INCLUDE_DIR` - string - Path to Firebird include directories where CMake should look for `ibase.h` header. +* `FIREBIRD_LIBRARIES` - string - Full paths to Firebird `fbclient` or `fbclient_ms` libraries to link SOCI against to enable the backend support. +* `SOCI_FIREBIRD` - boolean - Requests to build [Firebird](backends/firebird.html) backend. Automatically switched on, if `WITH_FIREBIRD` is set to ON. +* `SOCI_FIREBIRD_TEST_CONNSTR` - string - See [Firebird backend refernece](backends/firebird.html) for details. Example: `-DSOCI_FIREBIRD_TEST_CONNSTR:STRING="service=LOCALHOST:/tmp/soci_test.fdb user=SYSDBA password=masterkey"` -Short version using GNU Make makefiles: +#### MySQL - $ mkdir build - $ cd build - $ cmake -G "Unix Makefiles" -DWITH_BOOST=OFF -DWITH_ORACLE=OFF (...) ../soci-X.Y.Z - $ make - $ make install +* `WITH_MYSQL` - boolean - Should CMake try to detect [mysqlclient](http://dev.mysql.com/doc/refman/5.0/en/c.html) libraries providing MySQL C API. Note, currently the [mysql_config](http://dev.mysql.com/doc/refman/5.0/en/building-clients.html) program is not being used. +* `MYSQL_DIR` - string - Path to MySQL installation root directory. CMake will scan subdirectories `MYSQL_DIR/include` and `MYSQL_DIR/lib` respectively for MySQL headers and libraries. +* `MYSQL_INCLUDE_DIR` - string - Path to MySQL include directory where CMake should look for `mysql.h` header. +* `MYSQL_LIBRARIES` - string - Full paths to libraries to link SOCI against to enable the backend support. +* `SOCI_MYSQL` - boolean - Requests to build [MySQL](backends/mysql.html) backend. Automatically switched on, if `WITH_MYSQL` is set to ON. +* `SOCI_MYSQL_TEST_CONNSTR` - string - Connection string to MySQL test database. Format of the string is explained [MySQL backend refernece](backends/mysql.html). Example: `-DSOCI_MYSQL_TEST_CONNSTR:STRING="db=mydb user=mloskot password=secret"` +#### ODBC -#### Building using CMake on Windows +* `WITH_ODBC` - boolean - Should CMake try to detect ODBC libraries. On Unix systems, CMake tries to find [unixODBC](http://www.unixodbc.org/) or [iODBC](http://www.iodbc.org/) implementations. +* `ODBC_INCLUDE_DIR` - string - Path to ODBC implementation include directories where CMake should look for `sql.h` header. +* `ODBC_LIBRARIES` - string - Full paths to libraries to link SOCI against to enable the backend support. +* `SOCI_ODBC` - boolean - Requests to build [ODBC](backends/odbc.html) backend. Automatically switched on, if `WITH_ODBC` is set to ON. +* `SOCI_ODBC_TEST_{database}_CONNSTR` - string - ODBC Data Source Name (DSN) or ODBC File Data Source Name (FILEDSN) to test database: Microsoft Access (.mdb), Microsoft SQL Server, MySQL, PostgreSQL or any other ODBC SQL data source. {database} is placeholder for name of database driver ACCESS, MYSQL, POSTGRESQL, etc. See [ODBC](backends/odbc.html) backend refernece for details. Example: `-DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="FILEDSN=/home/mloskot/soci/build/test-postgresql.dsn"` +#### Oracle -Short version using Visual Studio 2010 and MSBuild: +* `WITH_ORACLE` - boolean - Should CMake try to detect [Oracle Call Interface (OCI)](http://en.wikipedia.org/wiki/Oracle_Call_Interface) libraries. +* `ORACLE_INCLUDE_DIR` - string - Path to Oracle include directory where CMake should look for `oci.h` header. +* `ORACLE_LIBRARIES` - string - Full paths to libraries to link SOCI against to enable the backend support. +* `SOCI_ORACLE` - boolean - Requests to build [Oracle](backends/oracle.html) backend. Automatically switched on, if `WITH_ORACLE` is set to ON. +* `SOCI_ORACLE_TEST_CONNSTR` - string - Connection string to Oracle test database. Format of the string is explained [Oracle backend reference](backends/oracle.html). Example: `-DSOCI_ORACLE_TEST_CONNSTR:STRING="service=orcl user=scott password=tiger"` - C:\>MKDIR build - C:\>cd build - C:\build>cmake -G "Visual Studio 10" -DWITH_BOOST=OFF -DWITH_ORACLE=OFF (...) ..\soci-X.Y.Z - C:\build>msbuild.exe SOCI.sln +#### PostgreSQL +* `WITH_POSTGRESQL` - boolean - Should CMake try to detect PostgreSQL client interface libraries. SOCI relies on [libpq](http://www.postgresql.org/docs/9.0/interactive/libpq.html") C library. +* `POSTGRESQL_INCLUDE_DIR` - string - Path to PostgreSQL include directory where CMake should look for `libpq-fe.h` header. +* `POSTGRESQL_LIBRARIES` - string - Full paths to libraries to link SOCI against to enable the backend support. +* `SOCI_POSTGRESQL` - boolean - Requests to build [PostgreSQL](backends/postgresql.html") backend. Automatically switched on, if `WITH_POSTGRESQL` is set to ON. +* `SOCI_POSTGRESQL_TEST_CONNSTR` - string - Connection string to PostgreSQL test database. Format of the string is explained PostgreSQL backend refernece. Example: `-DSOCI_POSTGRESQL_TEST_CONNSTR:STRING="dbname=mydb user=scott"` -### Building using classic Makefiles on Unix (deprecated) +#### SQLite 3 -*NOTE: These Makefiles have not been maintained for long time. The officially maintained build configuration is CMake. If you still want to use these Makefiles, you've been warned that you may need to patch them.* +* `WITH_SQLITE3` - boolean - Should CMak try to detect SQLite C/C++ library. As bonus, the configuration tries OSGeo4W distribution if OSGEO4W_ROOT environment variable is set. +* `SQLITE_INCLUDE_DIR` - string - Path to SQLite 3 include directory where CMake should look for `sqlite3.h` header. +* `SQLITE_LIBRARIES` - string - Full paths to libraries to link SOCI against to enable the backend support. +* `SOCI_SQLITE3` - boolean - Requests to build [SQLite3](backends/sqlite3.html) backend. Automatically switched on, if `WITH_SQLITE3` is set to ON. +* `SOCI_SQLITE3_TEST_CONNSTR` - string - Connection string is simply a file path where SQLite3 test database will be created (e.g. /home/john/soci_test.db). Check [SQLite3 backend reference](backends/sqlite3.html) for details. Example: `-DSOCI_SQLITE3_TEST_CONNSTR="my.db"` or `-DSOCI_SQLITE3_TEST_CONNSTR=":memory:"`. -The classic set of Makefiles for Unix/Linux systems is provided for those users who need complete control over the whole processand who can benefit from the basic scaffolding that they can extend on their own. In this sense, the basic Makefiles are supposed to provide a minimal starting point for custom experimentation and are not intended to be a complete build/installation solution. +## Building with Makefiles on Unix + +*NOTE: These (classic) Makefiles have not been maintained for long time. +The officially maintained build configuration is CMake. +If you still want to use these Makefiles, you've been warned that you may need to patch them.* + +The classic set of Makefiles for Unix/Linux systems is provided for those users who need complete control over the whole processand who can benefit from the basic scaffolding that they can extend on their own. +In this sense, the basic Makefiles are supposed to provide a minimal starting point for custom experimentation and are not intended to be a complete build/installation solution. At the same time, they are complete in the sense that they can compile the library with all test programs and for some users this level of support will be just fine. -The `core` directory of the library distribution contains the `Makefile.basic` that can be used to compile the core part of the library. Run `make -f Makefile.basic` or `make -f Makefile.basic shared` to get the static and shared versions, respectively. Similarly, the `backends/name` directory contains the backend part for each supported backend with the appropriate `Makefile.basic` and the `backends/name/test` directory contains the test program for the given backend. +The `core` directory of the library distribution contains the `Makefile.basic` that can be used to compile the core part of the library. +Run `make -f Makefile.basic` or `make -f Makefile.basic shared` to get the static and shared versions, respectively. +Similarly, the `backends/name` directory contains the backend part for each supported backend with the appropriate `Makefile.basic` and the `backends/name/test` directory contains the test program for the given backend. -For example, the simplest way to compile the static version of the -library and the test program for PostgreSQL is: +For example, the simplest way to compile the static version of the library and the test program for PostgreSQL is: - $ cd src/core - $ make -f Makefile.basic - $ cd ../backends/postgresql - $ make -f Makefile.basic - $ cd test - $ make -f Makefile.basic +```console +cd src/core +make -f Makefile.basic +cd ../backends/postgresql +make -f Makefile.basic +cd test +make -f Makefile.basic +``` - -For each backend and its test program, the `Makefile.basic`s contain the variables that can have values specific to the given environment - they usually name the include and library paths. These variables are placed at the beginning of the `Makefile.basic`s. Please review their values in case of any compilation problems. +For each backend and its test program, the `Makefile.basic`s contain the variables that can have values specific to the given environment - they usually name the include and library paths. +These variables are placed at the beginning of the `Makefile.basic`s. +Please review their values in case of any compilation problems. The Makefiles for test programs can be a good starting point to find out correct compiler and linker options. -### Running regression tests +## Running tests -The process of running regression tests highly depends on user's environment and build configuration, so it may be quite involving process. The CMake configuration provides variables to allow users willing to run the tests to configure build and specify database connection parameters (see the tables above for variable names). +The process of running regression tests highly depends on user's environment and build configuration, so it may be quite involving process. +The CMake configuration provides variables to allow users willing to run the tests to configure build and specify database connection parameters (see the lists above for variable names). In order to run regression tests, configure and build desired SOCI backends and prepare working database instances for them. -While configuring build with CMake, specify `SOCI_TESTS=ON` to enable building regression tests. Also, specify `SOCI_{backend name}_TEST_CONNSTR` variables to tell the tests runner how to connect with your test databases. +While configuring build with CMake, specify `SOCI_TESTS=ON` to enable building regression tests. +Also, specify `SOCI_{backend name}_TEST_CONNSTR` variables to tell the tests runner how to connect with your test databases. Dedicated `make test` target can be used to execute regression tests on build completion: - $ mkdir build - $ cd build - $ cmake -G "Unix Makefiles" -DWITH_BOOST=OFF \ - -DSOCI_TESTS=ON \ - -DSOCI_EMPTY_TEST_CONNSTR="dummy connection" \ - -DSOCI_SQLITE3_TEST_CONNSTR="test.db" \ - (...) - ../soci-X.Y.Z - $ make - $ make test - $ make install +```console +mkdir build +cd build +cmake -G "Unix Makefiles" \ + -DWITH_BOOST=OFF \ + -DSOCI_TESTS=ON \ + -DSOCI_EMPTY_TEST_CONNSTR="dummy connection" \ + -DSOCI_SQLITE3_TEST_CONNSTR="test.db" \ + (...) + ../soci-X.Y.Z +make +make test +make install +``` In the example above, regression tests for the sample Empty backend and SQLite 3 backend are configured for execution by `make test` target. -### Libraries usage +## Using library -CMake build produces set of shared and static libraries for SOCI core and backends separately. On Unix, for example, `build/lib` directory will consist of the static libraries named like `libsoci_core.a`, `libsoci_sqlite3.a` and shared libraries with names like `libsoci_core.so.4.0.0`, `libsoci_sqlite3.so.4.0.0`, and so on. +CMake build produces set of shared and static libraries for SOCI core and backends separately. +On Unix, for example, `build/lib` directory will consist of the static libraries named like `libsoci_core.a`, `libsoci_sqlite3.a` and shared libraries with names like `libsoci_core.so.4.0.0`, `libsoci_sqlite3.so.4.0.0`, and so on. -In order to use SOCI in your program, you need to specify your project build configuration with paths to SOCI headers and libraries, and specify linker to link against the libraries you want to use in your program. \ No newline at end of file +In order to use SOCI in your program, you need to specify your project build configuration with paths to SOCI headers and libraries. +Then, tell the linker to link against the libraries you want to use in your program. diff --git a/docs/interfaces.md b/docs/interfaces.md index ebc56a9f4e..144222257c 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -1,74 +1,84 @@ -## Interfaces +# Interfaces One of the major features of SOCI, although not immediately visible, is the variety of interfaces (APIs) that are available for the user. These can be divided into *sugar*, *core* and *simple*. -#### Sugar +## Sugar -The most exposed and promoted interface supports the syntax sugar that makes SOCI similar in look and feel to embedded SQL. The example of application code using this interface is: +The most exposed and promoted interface supports the syntax sugar that makes SOCI similar in look and feel to embedded SQL. +The example of application code using this interface is: - session sql("postgresql://dbname=mydb"); +```cpp +session sql("postgresql://dbname=mydb"); - int id = 123; - string name; +int id = 123; +string name; - sql << "select name from persons where id = :id", into(name), use(id); +sql << "select name from persons where id = :id", into(name), use(id); +``` -#### Core +## Core The above example is equivalent to the following, more explicit sequence of calls: - session sql("postgresql://dbname=mydb"); +```cpp +session sql("postgresql://dbname=mydb"); - int id = 123; - string name; +int id = 123; +string name; - statement st(sql); - st.exchange(into(name)); - st.exchange(use(id)); - st.alloc(); - st.prepare("select name from persons where id = :id"); - st.define_and_bind(); - st.execute(true); +statement st(sql); +st.exchange(into(name)); +st.exchange(use(id)); +st.alloc(); +st.prepare("select name from persons where id = :id"); +st.define_and_bind(); +st.execute(true); +``` - -The value of the *core* interface is that it is the basis for all other interfaces, and can be also used by developers to easily prepare their own convenience interfaces. Users who cannot or for some reason do not want to use the natural *sugar* interface should try the *core* one as the foundation and access point to all SOCI functionality. +The value of the *core* interface is that it is the basis for all other interfaces, and can be also used by developers to easily prepare their own convenience interfaces. +Users who cannot or for some reason do not want to use the natural *sugar* interface should try the *core* one as the foundation and access point to all SOCI functionality. Note that the *sugar* interface wraps only those parts of the *core* that are related to data binding and query streaming. -#### Simple +## Simple -The *simple* interface is provided specifically to allow easy integration of the SOCI library with other languages that have the ability to link with binaries using the "C" calling convention. To facilitate this integration, the *simple* interface does not use any pointers to data except C-style strings and opaque handles, but the consequence of this is that user data is managed by SOCI and not by user code. To avoid exceptions passing the module boundaries, all errors are reported as state variables of relevant objects. +The *simple* interface is provided specifically to allow easy integration of the SOCI library with other languages that have the ability to link with binaries using the "C" calling convention. +To facilitate this integration, the *simple* interface does not use any pointers to data except C-style strings and opaque handles, but the consequence of this is that user data is managed by SOCI and not by user code. +To avoid exceptions passing the module boundaries, all errors are reported as state variables of relevant objects. The above examples can be rewritten as (without error-handling): - #include <soci-simple.h> +```cpp +#include - // ... - session_handle sql = soci_create_session("postgresql://dbname=mydb"); +// ... +session_handle sql = soci_create_session("postgresql://dbname=mydb"); - statement_handle st = soci_create_statement(sql); +statement_handle st = soci_create_statement(sql); - soci_use_int(st, "id"); - soci_set_use_int(st, "id", 123); +soci_use_int(st, "id"); +soci_set_use_int(st, "id", 123); - int namePosition = soci_into_string(st); +int namePosition = soci_into_string(st); - soci_prepare(st, "select name from persons where id = :id"); +soci_prepare(st, "select name from persons where id = :id"); - soci_execute(st, true); +soci_execute(st, true); - char const * name = soci_get_into_string(st, namePosition); +char const * name = soci_get_into_string(st, namePosition); - printf("name is %s\n", name); +printf("name is %s\n", name); - soci_destroy_statement(st); - soci_destroy_session(sql); +soci_destroy_statement(st); +soci_destroy_session(sql); +``` - -The *simple* interface supports single and bulk data exchange for static binding. Dynamic row description is not supported in this release. +The *simple* interface supports single and bulk data exchange for static binding. +Dynamic row description is not supported in this release. See [Simple client interface](/reference.html#simpleclient) reference documentation for more details. -#### Low-level backend interface +## Low-level backend interface -The low-level backend interface allows to interact with backends directly and in principle allows to access the database without involving any other component. There is no particular reason to use this interface in the user code. \ No newline at end of file +The low-level backend interface allows to interact with backends directly and in principle allows to access the database without involving any other component. +There is no particular reason to use this interface in the user code. diff --git a/docs/languages/ada/concepts.md b/docs/languages/ada/concepts.md index 7a7c66abbb..28443ca1ba 100644 --- a/docs/languages/ada/concepts.md +++ b/docs/languages/ada/concepts.md @@ -1,6 +1,4 @@ -# SOCI-Ada - manual - -## Concepts +# Ada Concepts The SOCI-Ada library borrows its concepts and naming from the main SOCI project. They are shortly explained here in the bottom-up fashion. @@ -12,7 +10,6 @@ There are two kinds of objects that can be managed by the SOCI-Ada library: * *Use elements*, which are data objects that are transferred from the user program to the database as parameters of the query (and, if supported by the target database, that can be modified by the database server and transferred back to the user program). There are single use elements for binding parameters of single-row queries and vector use elements for binding whole bunches of data for transfer. The use elements are identified by their *name*. - The user program can read the current value of into and use elements and assign new values to use elements. Elements are strongly typed and the following types are currently supported: * `String` @@ -29,4 +26,4 @@ All statements are handled within the context of some *session*, which also supp Sessions can be managed in isolation or as a group called *connection pool*, which helps to decouple tasking design choices from the concurrency policies at the database connection level. Sessions are *leased* from the pool for some time during which no other task can access them and returned back when no longer needed, where they can be acquired again by other tasks. -All potential problems are signalled via exceptions that have some descriptive message attached to them. \ No newline at end of file +All potential problems are signalled via exceptions that have some descriptive message attached to them. diff --git a/docs/languages/ada/idioms.md b/docs/languages/ada/idioms.md index 034e6ab2eb..e210aa1dc7 100644 --- a/docs/languages/ada/idioms.md +++ b/docs/languages/ada/idioms.md @@ -1,6 +1,4 @@ -# SOCI-Ada - manual - -## Common Idioms +# Ada Idioms As any other library, SOCI-Ada has its set of idioms that ensure optimal work in terms of performance and resource usage. Still, the optimal use will depend on the concrete usage scenario - the places where programmer choices are needed will be described explicitly. @@ -10,251 +8,228 @@ The idioms below are provided as *complete programs* with the intent to make the This type of query is useful for DDL commands and can be executed directly on the given session, without explicit statement management. +```ada +with SOCI; - with SOCI; +procedure My_Program is - procedure My_Program is + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); - SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); +begin - begin + SQL.Execute ("drop table some_table"); - SQL.Execute ("drop table some_table"); +end My_Program; - end My_Program; - -
-Note: - -The session object is initialized by a constructor function call. An alternative would be to declare it without initialization and later use the `Open` operation to establish a physical connection with the database. - -
+``` +Note: The session object is initialized by a constructor function call. An alternative would be to declare it without initialization and later use the `Open` operation to establish a physical connection with the database. ## Simple query without parameters resulting in one row of data This type of query requires only single into elements, which together with the statement have to be manipulated explicitly. +```ada +with SOCI; +with Ada.Text_IO; - with SOCI; - with Ada.Text_IO; +procedure My_Program is - procedure My_Program is + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; - SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); - St : SOCI.Statement := SOCI.Make_Statement (SQL); - Pos : SOCI.Into_Position; + Num_Of_Persons : SOCI.DB_Integer; - Num_Of_Persons : SOCI.DB_Integer; +begin - begin + Pos := St.Into_Integer; + St.Prepare ("select count(*) from persons"); + St.Execute (True); - Pos := St.Into_Integer; - St.Prepare ("select count(*) from persons"); - St.Execute (True); + Num_Of_Persons := St.Get_Into_Integer (Pos); - Num_Of_Persons := St.Get_Into_Integer (Pos); + Ada.Text_IO.Put_Line ("Number of persons: " & SOCI.DB_Integer'Image (Num_Of_Persons)); - Ada.Text_IO.Put_Line ("Number of persons: " & SOCI.DB_Integer'Image (Num_Of_Persons)); - - end My_Program; - -
-Note: - -The into element is inspected by providing the position value that was obtained at the time if was created. No operations are defined for the position type. There can be many into elements with a single query. - -
+end My_Program; +``` +Note: The into element is inspected by providing the position value that was obtained at the time if was created. No operations are defined for the position type. There can be many into elements with a single query. ## Simple query with parameters and without results This type of query requires only use elements. - with SOCI; +```ada +with SOCI; - procedure My_Program is +procedure My_Program is - SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); - St : SOCI.Statement := SOCI.Make_Statement (SQL); + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); + St : SOCI.Statement := SOCI.Make_Statement (SQL); - begin +begin - St.Use_Integer ("increase"); - St.Set_Use_Integer ("increase", 1000); + St.Use_Integer ("increase"); + St.Set_Use_Integer ("increase", 1000); - St.Prepare ("update persons set salary = salary + :increase"); - St.Execute (True); + St.Prepare ("update persons set salary = salary + :increase"); + St.Execute (True); - end My_Program; - -
-Note: - -The "`:increase`" in the query is a placeholder variable. There can be many such variables and each of them needs to be filled in by respective use element. - -
+end My_Program; +``` +Note: The "`:increase`" in the query is a placeholder variable. There can be many such variables and each of them needs to be filled in by respective use element. ## Repeated query with parameters and without results This type of query requires only use elements, but they can be set differently for each statement execution. +```ada +with SOCI; - with SOCI; +procedure My_Program is - procedure My_Program is + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); + St : SOCI.Statement := SOCI.Make_Statement (SQL); - SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); - St : SOCI.Statement := SOCI.Make_Statement (SQL); +begin - begin + St.Use_String ("name"); - St.Use_String ("name"); + St.Prepare ("insert into countries(country_name) values(:name)"); - St.Prepare ("insert into countries(country_name) values(:name)"); + St.Set_Use_String ("name", "Poland"); + St.Execute (True); - St.Set_Use_String ("name", "Poland"); - St.Execute (True); + St.Set_Use_String ("name", "Switzerland"); + St.Execute (True); - St.Set_Use_String ("name", "Switzerland"); - St.Execute (True); + St.Set_Use_String ("name", "France"); + St.Execute (True); - St.Set_Use_String ("name", "France"); - St.Execute (True); +end My_Program; +``` - end My_Program; - -
-Note: - -Each time the query is executed, the *current* values of use elements are transferred to the database. - -
+Note: Each time the query is executed, the *current* values of use elements are transferred to the database. ## Batch query with parameters and without results This type of query requires vector use elements. Compare with the previous example. - with SOCI; +```ada +with SOCI; - procedure My_Program is +procedure My_Program is - SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); - St : SOCI.Statement := SOCI.Make_Statement (SQL); - First : SOCI.Vector_Index; + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); + St : SOCI.Statement := SOCI.Make_Statement (SQL); + First : SOCI.Vector_Index; - use type SOCI.Vector_Index; + use type SOCI.Vector_Index; - begin +begin - St.Use_Vector_String ("name"); + St.Use_Vector_String ("name"); - St.Use_Vectors_Resize (3); + St.Use_Vectors_Resize (3); - First := St.Use_Vectors_First_Index; + First := St.Use_Vectors_First_Index; - St.Set_Use_Vector_String ("name", First + 0, "Poland"); - St.Set_Use_Vector_String ("name", First + 1, "Switzerland"); - St.Set_Use_Vector_String ("name", First + 2, "France"); + St.Set_Use_Vector_String ("name", First + 0, "Poland"); + St.Set_Use_Vector_String ("name", First + 1, "Switzerland"); + St.Set_Use_Vector_String ("name", First + 2, "France"); - St.Prepare ("insert into countries(country_name) values(:name)"); - St.Execute (True); + St.Prepare ("insert into countries(country_name) values(:name)"); + St.Execute (True); - end My_Program; +end My_Program; +``` -
-Note: +Note: The whole bunch of data is transferred to the database if the target database server supports it and the statement is automatically repeated otherwise. This is the preferred way to transfer many rows of data to the server when the data for all rows are known before executing the query. -
- - -
-Note: +Note: The query can be executed many times and each time a new batch of data can be transferred to the server. The size of the batch (set by calling `Use_Vectors_Resize`) can be different each time the query is executed, but cannot be larger than the size that was used the first time. The size of the batch defines a tradeoff between the amount of data being transmitted in a single step (this influences the memory used by the user program and the time of a single call) and the number of executions required to handle big data sets. The optimal size of the batch will therefore differ depending on the application, but in general tens of thousands is a reasonable limit for a batch size - the performance of the whole operation is usually not affected above this value so there is no need to imply higher memory usage at the client side. -
- ## Simple query with many rows of results This type of query requires simple into elements. - with SOCI; - with Ada.Text_IO; +```ada +with SOCI; +with Ada.Text_IO; - procedure My_Program is +procedure My_Program is - SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); - St : SOCI.Statement := SOCI.Make_Statement (SQL); - Pos : SOCI.Into_Position; + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; - begin +begin - Pos := St.Into_String; + Pos := St.Into_String; - St.Prepare ("select country_name from countries"); - St.Execute; + St.Prepare ("select country_name from countries"); + St.Execute; - while St.Fetch loop + while St.Fetch loop - Ada.Text_IO.Put_Line (St.Get_Into_String (Pos)); + Ada.Text_IO.Put_Line (St.Get_Into_String (Pos)); - end loop; + end loop; - end My_Program; +end My_Program; +``` -
-Note: +Note: The loop above executes as many times as there are rows in the result. After each row is read, the into elements contain the respective values from that row. The `Execute` operation is called without parameter, which is `False` by default, meaning that no data transfer is intended. The data is being transferred only during the `Fetch` operation, which returns `False` when no data has been retrieved and the result is exhausted. This type of query can have simple parameters which are fixed at the execution time. -
- - ## Batch query with many rows of results This type of query requires vector into elements. Compare with previous example. - with SOCI; - with Ada.Text_IO; +```ada +with SOCI; +with Ada.Text_IO; - procedure My_Program is +procedure My_Program is - SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); - St : SOCI.Statement := SOCI.Make_Statement (SQL); - Pos : SOCI.Into_Position; + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); + St : SOCI.Statement := SOCI.Make_Statement (SQL); + Pos : SOCI.Into_Position; - Batch_Size : constant := 10; + Batch_Size : constant := 10; - begin +begin - Pos := St.Into_Vector_String; - St.Into_Vectors_Resize (Batch_Size); + Pos := St.Into_Vector_String; + St.Into_Vectors_Resize (Batch_Size); - St.Prepare ("select country_name from countries"); - St.Execute; + St.Prepare ("select country_name from countries"); + St.Execute; - while St.Fetch loop + while St.Fetch loop - for I in St.Into_Vectors_First_Index .. St.Into_Vectors_Last_Index loop + for I in St.Into_Vectors_First_Index .. St.Into_Vectors_Last_Index loop - Ada.Text_IO.Put_Line (St.Get_Into_Vector_String (Pos, I)); + Ada.Text_IO.Put_Line (St.Get_Into_Vector_String (Pos, I)); - end loop; + end loop; - St.Into_Vectors_Resize (Batch_Size); + St.Into_Vectors_Resize (Batch_Size); - end loop; + end loop; - end My_Program; +end My_Program; +``` - -##### Note: +Note: The loop above is nested. The outer `while` loop fetches consecutive batches of rows from the database with requested batch size; the returned batch can be smaller than requested (the into vector elements are downsized automatically if needed) and the intended batch size is requested again before repeating the `Fetch` operation. For each returned batch, the into vector elements are inspected in the inner `for` loop. This scheme ensures correct operation independently on the size of returned batch and is therefore a recommended idiom for efficiently returning many rows of data. @@ -262,7 +237,6 @@ There is a tradeoff between efficiency and memory usage and this tradeoff is con This type of query can have simple (not vectors) parameters that are fixed at execution time. - -##### Final note: +## Final note Follow good database usage principles and avoid constructing queries by concatenating strings computed at run-time. Thanks to a good type system Ada is much better in preventing various SQL-injection attacks than weaker languages like PHP, but there is still a potential for vulnerability or at least performance loss. As a rule of thumb, rely on *use elements* to parameterize your queries and to provide clean separation between data and code. This will prevent many security vulnerabilities and will allow some servers to optimize their work by reusing already cached execution plans. diff --git a/docs/languages/ada/index.md b/docs/languages/ada/index.md index 4fd7921b5b..afebb2ce46 100644 --- a/docs/languages/ada/index.md +++ b/docs/languages/ada/index.md @@ -1,10 +1,8 @@ -# SOCI-Ada Language Binding - documentation +# Ada Bindings -* [Introduction](#introduction) -* [Compilation](#compilation) -* [Concepts](concepts.html) -* [Common Idioms](idioms.html) -* [API Reference](reference.html) +* [Concepts](concepts.md) +* [Idioms](idioms.md) +* [API Reference](reference.md) ## Introduction @@ -22,9 +20,9 @@ The SOCI-Ada library offers the following features to the Ada community: Currently the following database servers are directly supported via their native interfaces: - * Oracle - * PostgreSQL - * MySQL +* Oracle +* PostgreSQL +* MySQL Other backends exist in the SOCI Git repository and can be provided with future version of the library. @@ -36,4 +34,4 @@ In order to use SOCI-Ada, compile the C++ parts first (core and required backend The SOCI-Ada library itself is a single package named `SOCI`. This package can be just imported in the target project as is or pre-built to the binary form if required. -In order to link the user programs the `-lsoci_core -lstdc++` linker options need to be provided on the Unix/Linux platforms. \ No newline at end of file +In order to link the user programs the `-lsoci_core -lstdc++` linker options need to be provided on the Unix/Linux platforms. diff --git a/docs/languages/ada/reference.md b/docs/languages/ada/reference.md index 9650eb9921..e27196854a 100644 --- a/docs/languages/ada/reference.md +++ b/docs/languages/ada/reference.md @@ -1,147 +1,150 @@ -# SOCI-Ada - manual - -## API Reference +# Ada API Reference The SOCI-Ada library is entirely implemented as a single package named `SOCI`. Additional child packages contain single procedures for static registration of backends - these child packages are not necessary for typical use, but can be useful to force static linking of backend code. The following describes all publicly visible elements of this package: +```ada +-- +-- General exception related to database and library usage. +-- - -- - -- General exception related to database and library usage. - -- - - Database_Error : exception; +Database_Error : exception; +``` Each problem related to the interaction with the database or to the incorrect usage of the library itself is signalled by raising this exception. Each occurrence of this exception has some human-readable error message that can be obtained by a call to `Ada.Exceptions.Exception_Message`. - -- - -- Session. - -- +```ada +-- +-- Session. +-- - type Session is tagged limited private; +type Session is tagged limited private; - not overriding - function Make_Session (Connection_String : in String) return Session; +not overriding +function Make_Session (Connection_String : in String) return Session; - not overriding - procedure Open (This : in out Session; Connection_String : in String); +not overriding +procedure Open (This : in out Session; Connection_String : in String); - not overriding - procedure Close (This : in out Session); +not overriding +procedure Close (This : in out Session); - not overriding - function Is_Open (This : in Session) return Boolean; +not overriding +function Is_Open (This : in Session) return Boolean; +``` The `Session` object can exist in two states: "connected" (or "open") and "disconnected". It can be created as connected at initialization time with a call to the constructor function `Make_Session` or left default-initialized in the disconnected state and later changed to connected with `Open` (the latter option is the only that is available in the Ada 95 version of the library). `Session` objects can be also associated with the connection pool, see below. The `Connection_String` should have the form `"backendname://parameters"`, where `backendname` is used to construct the name of the dynamically loadable library that will be used to provide specific database services. Backends included in the current distribution of the main SOCI library are: - * `oracle` (implemented as `libsoci_oracle.so` or `libsoci_oracle.dll`) * `postgresql` (implemented as `libsoci_postgresql.so` or `libsoci_postgresql.dll`) * `mysql` (implemented as `libsoci_mysql.so` or `libsoci_mysql.dll`) - Other backends can be added to the library in the future or by the user himself, please see the documentation of the main SOCI library for details. The `parameters` component of the `Connection_String` depends on the given backend, please see the documentation of the main SOCI project for the meaning and recognized options. The web pages related to the backends above are: -* Oracle: * [http://soci.sourceforge.net/doc/backends/oracle.html](http://soci.sourceforge.net/doc/backends/oracle.html" target="_blank) -* PostgreSQL: * [http://soci.sourceforge.net/doc/backends/postgresql.html](http://soci.sourceforge.net/doc/backends/postgresql.html" target="_blank) -* MySQL: * [http://soci.sourceforge.net/doc/backends/mysql.html](http://soci.sourceforge.net/doc/backends/mysql.html" target="_blank) +* [Oracle](http://soci.sourceforge.net/doc/backends/oracle.html) +* [PostgreSQL](http://soci.sourceforge.net/doc/backends/postgresql.html) +* [MySQL](http://soci.sourceforge.net/doc/backends/mysql.html) The `Open` operation can be called only in the disconnected state (which changes the state of `Session` object to connected). The `Close` operation can be called in any state (provided that the session is not associated with the connection pool, see below) and after that the `Session` is in the disconnected state. `Session` objects are closed automatically as part of their finalization. If the `Session` object is associated with the connection pool, the finalizer detaches from the pool without closing the connection. +```ada +-- Transaction management. - -- Transaction management. +not overriding +procedure Start (This : in Session); - not overriding - procedure Start (This : in Session); +not overriding +procedure Commit (This : in Session); - not overriding - procedure Commit (This : in Session); - - not overriding - procedure Rollback (This : in Session); +not overriding +procedure Rollback (This : in Session); +``` These operations handle transactions. The exact meaning of transactions and whether transactions are automatic for some kinds of statements (and which ones) depend on the target database. - - -- Immediate query execution. - not overriding - procedure Execute (This : in Session; Query : in String); +```ada +-- Immediate query execution. +not overriding +procedure Execute (This : in Session; Query : in String); +``` This operation allows to create implicit statement, prepare it for the given `Query` and execute it. - -- - -- Connection pool management. - -- +```ada +-- +-- Connection pool management. +-- - type Connection_Pool (Size : Positive) is tagged limited private; +type Connection_Pool (Size : Positive) is tagged limited private; - not overriding - procedure Open - (This : in out Connection_Pool; - Position : in Positive; - Connection_String : in String); +not overriding +procedure Open + (This : in out Connection_Pool; + Position : in Positive; + Connection_String : in String); - not overriding - procedure Close (This : in out Connection_Pool; Position : in Positive); +not overriding +procedure Close (This : in out Connection_Pool; Position : in Positive); - not overriding - procedure Lease (This : in out Connection_Pool; S : in out Session'Class); +not overriding +procedure Lease (This : in out Connection_Pool; S : in out Session'Class); +``` The `Connection_Pool` encapsulates a fixed-size collection of sessions. Individual sessions are indexed from `1` to `Size` (provided as discriminant) and can be `Open`ed and `Close`d explicitly. Each connection in the pool can be created with different `Connection_String`, if needed. - The `Lease` operation allows to associate a given `Session` object (that has to be in the disconnected state itself) with one connection from the pool. The pool guarantees that at most one task can lease a given connection from the pool. If there are no free connections in the pool, the `Lease` operation will block waiting until some connection is freed. The `Session` object that is associated with a connection from the pool automatically gives it back to pool as part of the `Session`'s finalizer. There is no other way to "detach" from the pool. ---- -#####Note: +Note: It is assumed that the lifetime of `Connection_Pool` encloses the lifetimes of all `Session` objects that are leased from it. There is no particular protection against it and it is possible to construct a code example with allocators that create partially overlapping `Connection_Pool` and `Session`, but this is considered obscure and not representative to the actual use scenarios. To avoid any potential problems, create `Connection_Pool` in the scope that encloses the scopes of leased `Session`s. ---- - -- - -- Statement. - -- +```ada +-- +-- Statement. +-- - type Statement (<>) is tagged limited private; +type Statement (<>) is tagged limited private; - type Data_State is (Data_Null, Data_Not_Null); +type Data_State is (Data_Null, Data_Not_Null); - type Into_Position is private; +type Into_Position is private; - type Vector_Index is new Natural; +type Vector_Index is new Natural; +``` The `Statement` type and supporting types. `Data_State` is used to indicate null values in the database sense - each value of into or use elements has a state from this type. +```ada +-- Statement preparation and execution. - -- Statement preparation and execution. +not overriding +procedure Prepare (This : in Statement; Query : in String); - not overriding - procedure Prepare (This : in Statement; Query : in String); +not overriding +procedure Execute + (This : in Statement; + With_Data_Exchange : in Boolean := False); - not overriding - procedure Execute - (This : in Statement; - With_Data_Exchange : in Boolean := False); +not overriding +function Execute + (This : in Statement; + With_Data_Exchange : in Boolean := False) return Boolean; - not overriding - function Execute - (This : in Statement; - With_Data_Exchange : in Boolean := False) return Boolean; +not overriding +function Fetch (This : in Statement) return Boolean; - not overriding - function Fetch (This : in Statement) return Boolean; - - not overriding - function Got_Data (This : in Statement) return Boolean; +not overriding +function Got_Data (This : in Statement) return Boolean; +``` The `Prepare` operation needs to be called before any other operation in the above group and it prepares the execution for the given `Query`. No into and use elements can be created after this operation is called. @@ -151,19 +154,20 @@ The `Fetch` function is used to transfer next portion of data (a single row or a The `Got_Data` function returns `True` if the last execution or fetch resulted in some data being transmitted from the database server. +```ada +-- +-- Data items handling. +-- - -- - -- Data items handling. - -- +-- Database-specific types. +-- These types are most likely identical to standard Integer, +-- Long_Long_Integer and Long_Float, but are defined distinctly +-- to avoid interfacing problems with other compilers. - -- Database-specific types. - -- These types are most likely identical to standard Integer, - -- Long_Long_Integer and Long_Float, but are defined distinctly - -- to avoid interfacing problems with other compilers. - - type DB_Integer is new Interfaces.C.int; - type DB_Long_Long_Integer is new Interfaces.Integer_64; - type DB_Long_Float is new Interfaces.C.double; +type DB_Integer is new Interfaces.C.int; +type DB_Long_Long_Integer is new Interfaces.Integer_64; +type DB_Long_Float is new Interfaces.C.double; +``` The data types used for interaction with the database are: @@ -173,92 +177,88 @@ The data types used for interaction with the database are: * `DB_Long_Float`, defined above * `Ada.Calendar.Time` - -- Creation of single into elements. +```ada +-- Creation of single into elements. - not overriding - function Into_String (This : in Statement) return Into_Position; +not overriding +function Into_String (This : in Statement) return Into_Position; - not overriding - function Into_Integer (This : in Statement) return Into_Position; +not overriding +function Into_Integer (This : in Statement) return Into_Position; - not overriding - function Into_Long_Long_Integer (This : in Statement) return Into_Position; +not overriding +function Into_Long_Long_Integer (This : in Statement) return Into_Position; - not overriding - function Into_Long_Float (This : in Statement) return Into_Position; - - not overriding - function Into_Time (This : in Statement) return Into_Position; +not overriding +function Into_Long_Float (This : in Statement) return Into_Position; +not overriding +function Into_Time (This : in Statement) return Into_Position; +``` These functions instruct the library to create internal simple into elements of the relevant type. They return the position of the into element, which can be later used to identify it. ---- -#####Note: +Note: Simple into elements cannot be created together with vector into elements for the same statement. -Simple into elements cannot be created together with vector into elements for the same statement. +Note: Simple into elements cannot be created together with vector into elements for the same statement. ---- +```ada +-- Inspection of single into elements. ---- -#####Note: -Simple into elements cannot be created together with vector into elements for the same statement. +not overriding +function Get_Into_State + (This : in Statement; + Position : in Into_Position) + return Data_State; ---- +not overriding +function Get_Into_String + (This : in Statement; + Position : in Into_Position) + return String; - -- Inspection of single into elements. +not overriding +function Get_Into_Integer + (This : in Statement; + Position : in Into_Position) + return DB_Integer; - not overriding - function Get_Into_State - (This : in Statement; - Position : in Into_Position) - return Data_State; +not overriding +function Get_Into_Long_Long_Integer + (This : in Statement; + Position : in Into_Position) + return DB_Long_Long_Integer; - not overriding - function Get_Into_String - (This : in Statement; - Position : in Into_Position) - return String; +not overriding +function Get_Into_Long_Float + (This : in Statement; + Position : in Into_Position) + return DB_Long_Float; - not overriding - function Get_Into_Integer - (This : in Statement; - Position : in Into_Position) - return DB_Integer; - - not overriding - function Get_Into_Long_Long_Integer - (This : in Statement; - Position : in Into_Position) - return DB_Long_Long_Integer; - - not overriding - function Get_Into_Long_Float - (This : in Statement; - Position : in Into_Position) - return DB_Long_Float; - - not overriding - function Get_Into_Time - (This : in Statement; - Position : in Into_Position) - return Ada.Calendar.Time; +not overriding +function Get_Into_Time + (This : in Statement; + Position : in Into_Position) + return Ada.Calendar.Time; +``` These functions allow to inspect the state and value of the simple into element identified by its position. If the state of the given element is `Data_Null`, the data-reading functions raise exceptions for that element. - -- Inspection of vector into elements. +```ada +-- Inspection of vector into elements. - not overriding - function Get_Into_Vectors_Size (This : in Statement) return Natural; +not overriding +function Get_Into_Vectors_Size (This : in Statement) return Natural; - not overriding - function Into_Vectors_First_Index (This : in Statement) return Vector_Index; +not overriding +function Into_Vectors_First_Index (This : in Statement) return Vector_Index; - not overriding - function Into_Vectors_Last_Index (This : in Statement) return Vector_Index; +not overriding +function Into_Vectors_Last_Index (This : in Statement) return Vector_Index; - not overriding - procedure Into_Vectors_Resize (This : in Statement; New_Size : in Natural); +not overriding +procedure Into_Vectors_Resize (This : in Statement; New_Size : in Natural); +``` The `Get_Into_Vectors_Size` returns the number of entries in any of the vector into elements for the given statement. @@ -266,163 +266,161 @@ The `Into_Vectors_First_Index` returns the lowest index value for vector into el The `Into_Vectors_Resize` procedure allows to change the size of all use vectors for the given statement. - not overriding - function Get_Into_Vector_State - (This : in Statement; - Position : in Into_Position; - Index : in Vector_Index) - return Data_State; +```ada +not overriding +function Get_Into_Vector_State + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return Data_State; - not overriding - function Get_Into_Vector_String - (This : in Statement; - Position : in Into_Position; - Index : in Vector_Index) - return String; +not overriding +function Get_Into_Vector_String + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return String; - not overriding - function Get_Into_Vector_Integer - (This : in Statement; - Position : in Into_Position; - Index : in Vector_Index) - return DB_Integer; +not overriding +function Get_Into_Vector_Integer + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return DB_Integer; - not overriding - function Get_Into_Vector_Long_Long_Integer - (This : in Statement; - Position : in Into_Position; - Index : in Vector_Index) - return DB_Long_Long_Integer; +not overriding +function Get_Into_Vector_Long_Long_Integer + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return DB_Long_Long_Integer; - not overriding - function Get_Into_Vector_Long_Float - (This : in Statement; - Position : in Into_Position; - Index : in Vector_Index) - return DB_Long_Float; +not overriding +function Get_Into_Vector_Long_Float + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return DB_Long_Float; - not overriding - function Get_Into_Vector_Time - (This : in Statement; - Position : in Into_Position; - Index : in Vector_Index) - return Ada.Calendar.Time; +not overriding +function Get_Into_Vector_Time + (This : in Statement; + Position : in Into_Position; + Index : in Vector_Index) + return Ada.Calendar.Time; +``` These functions allow to inspect the state and value of the vector use element identified by its position and index. If the state of the given element is `Data_Null`, the data-reading functions raise exceptions for that element. +```ada +-- Creation of single use elements. - -- Creation of single use elements. +not overriding +procedure Use_String (This : in Statement; Name : in String); - not overriding - procedure Use_String (This : in Statement; Name : in String); +not overriding +procedure Use_Integer (This : in Statement; Name : in String); - not overriding - procedure Use_Integer (This : in Statement; Name : in String); +not overriding +procedure Use_Long_Long_Integer (This : in Statement; Name : in String); - not overriding - procedure Use_Long_Long_Integer (This : in Statement; Name : in String); +not overriding +procedure Use_Long_Float (This : in Statement; Name : in String); - not overriding - procedure Use_Long_Float (This : in Statement; Name : in String); - - not overriding - procedure Use_Time (This : in Statement; Name : in String); +not overriding +procedure Use_Time (This : in Statement; Name : in String); +``` These functions instruct the library to create internal simple use elements of the relevant type, identified by the given `Name`. ---- -##### Note: -Simple use elements cannot be created together with vector use elements for the same statement. +Note: -Vector use elements cannot be created together with any into elements for the same statement. +* Simple use elements cannot be created together with vector use elements for the same statement. +* Vector use elements cannot be created together with any into elements for the same statement. ---- +```ada +-- Creation of vector use elements. +not overriding +procedure Use_Vector_String (This : in Statement; Name : in String); - -- Creation of vector use elements. +not overriding +procedure Use_Vector_Integer (This : in Statement; Name : in String); - not overriding - procedure Use_Vector_String (This : in Statement; Name : in String); +not overriding +procedure Use_Vector_Long_Long_Integer (This : in Statement; Name : in String); - not overriding - procedure Use_Vector_Integer (This : in Statement; Name : in String); - - not overriding - procedure Use_Vector_Long_Long_Integer (This : in Statement; Name : in String); - - not overriding - procedure Use_Vector_Long_Float (This : in Statement; Name : in String); - - not overriding - procedure Use_Vector_Time (This : in Statement; Name : in String); +not overriding +procedure Use_Vector_Long_Float (This : in Statement; Name : in String); +not overriding +procedure Use_Vector_Time (This : in Statement; Name : in String); +``` These functions instruct the library to create internal vector use elements of the relevant type, identified by the given `Name`. +Note: ---- -#####Note: +* Simple use elements cannot be created together with vector use elements for the same statement. +* Vector use elements cannot be created together with any into elements for the same statement. -Simple use elements cannot be created together with vector use elements for the same statement. +```ada +-- Modifiers for single use elements. -Vector use elements cannot be created together with any into elements for the same statement. +not overriding +procedure Set_Use_State + (This : in Statement; + Name : in String; + State : in Data_State); ---- +not overriding +procedure Set_Use_String + (This : in Statement; + Name : in String; + Value : in String); - -- Modifiers for single use elements. +not overriding +procedure Set_Use_Integer + (This : in Statement; + Name : in String; + Value : in DB_Integer); - not overriding - procedure Set_Use_State - (This : in Statement; - Name : in String; - State : in Data_State); +not overriding +procedure Set_Use_Long_Long_Integer + (This : in Statement; + Name : in String; + Value : in DB_Long_Long_Integer); - not overriding - procedure Set_Use_String - (This : in Statement; - Name : in String; - Value : in String); +not overriding +procedure Set_Use_Long_Float + (This : in Statement; + Name : in String; + Value : in DB_Long_Float); - not overriding - procedure Set_Use_Integer - (This : in Statement; - Name : in String; - Value : in DB_Integer); - - not overriding - procedure Set_Use_Long_Long_Integer - (This : in Statement; - Name : in String; - Value : in DB_Long_Long_Integer); - - not overriding - procedure Set_Use_Long_Float - (This : in Statement; - Name : in String; - Value : in DB_Long_Float); - - not overriding - procedure Set_Use_Time - (This : in Statement; - Name : in String; - Value : in Ada.Calendar.Time); +not overriding +procedure Set_Use_Time + (This : in Statement; + Name : in String; + Value : in Ada.Calendar.Time); +``` These operations allow to modify the state and value of simple use elements. Setting the value of use element automatically sets its state to `Data_Not_Null`. +```ada +-- Modifiers for vector use elements. - -- Modifiers for vector use elements. +not overriding +function Get_Use_Vectors_Size (This : in Statement) return Natural; - not overriding - function Get_Use_Vectors_Size (This : in Statement) return Natural; +not overriding +function Use_Vectors_First_Index (This : in Statement) return Vector_Index; - not overriding - function Use_Vectors_First_Index (This : in Statement) return Vector_Index; +not overriding +function Use_Vectors_Last_Index (This : in Statement) return Vector_Index; - not overriding - function Use_Vectors_Last_Index (This : in Statement) return Vector_Index; - - not overriding - procedure Use_Vectors_Resize (This : in Statement; New_Size : in Natural); +not overriding +procedure Use_Vectors_Resize (This : in Statement; New_Size : in Natural); +``` The `Get_Use_Vectors_Size` returns the number of entries in any of the vector use elements for the given statement. @@ -430,94 +428,95 @@ The `Use_Vectors_First_Index` returns the lowest index value for vector use elem The `Use_Vectors_Resize` procedure allows to change the size of all use vectors for the given statement. +```ada +not overriding +procedure Set_Use_Vector_State + (This : in Statement; + Name : in String; + Index : in Vector_Index; + State : in Data_State); - not overriding - procedure Set_Use_Vector_State - (This : in Statement; - Name : in String; - Index : in Vector_Index; - State : in Data_State); +not overriding +procedure Set_Use_Vector_String + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in String); - not overriding - procedure Set_Use_Vector_String - (This : in Statement; - Name : in String; - Index : in Vector_Index; - Value : in String); +not overriding +procedure Set_Use_Vector_Integer + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in DB_Integer); - not overriding - procedure Set_Use_Vector_Integer - (This : in Statement; - Name : in String; - Index : in Vector_Index; - Value : in DB_Integer); +not overriding +procedure Set_Use_Vector_Long_Long_Integer + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in DB_Long_Long_Integer); - not overriding - procedure Set_Use_Vector_Long_Long_Integer - (This : in Statement; - Name : in String; - Index : in Vector_Index; - Value : in DB_Long_Long_Integer); - - not overriding - procedure Set_Use_Vector_Long_Float - (This : in Statement; - Name : in String; - Index : in Vector_Index; - Value : in DB_Long_Float); - - not overriding - procedure Set_Use_Vector_Time - (This : in Statement; - Name : in String; - Index : in Vector_Index; - Value : in Ada.Calendar.Time); +not overriding +procedure Set_Use_Vector_Long_Float + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in DB_Long_Float); +not overriding +procedure Set_Use_Vector_Time + (This : in Statement; + Name : in String; + Index : in Vector_Index; + Value : in Ada.Calendar.Time); +``` These operations allow to modify the state and value of vector use elements. Setting the value of use element automatically sets its state to `Data_Not_Null`. +```ada +-- Inspection of single use elements. +-- +-- Note: Use elements can be modified by the database if they +-- are bound to out and inout parameters of stored procedures +-- (although this is not supported by all database backends). +-- This feature is available only for single use elements. - -- Inspection of single use elements. - -- - -- Note: Use elements can be modified by the database if they - -- are bound to out and inout parameters of stored procedures - -- (although this is not supported by all database backends). - -- This feature is available only for single use elements. +not overriding +function Get_Use_State + (This : in Statement; + Name : in String) + return Data_State; - not overriding - function Get_Use_State - (This : in Statement; - Name : in String) - return Data_State; +not overriding +function Get_Use_String + (This : in Statement; + Name : in String) + return String; - not overriding - function Get_Use_String - (This : in Statement; - Name : in String) - return String; +not overriding +function Get_Use_Integer + (This : in Statement; + Name : in String) + return DB_Integer; - not overriding - function Get_Use_Integer - (This : in Statement; - Name : in String) - return DB_Integer; +not overriding +function Get_Use_Long_Long_Integer + (This : in Statement; + Name : in String) + return DB_Long_Long_Integer; - not overriding - function Get_Use_Long_Long_Integer - (This : in Statement; - Name : in String) - return DB_Long_Long_Integer; +not overriding +function Get_Use_Long_Float + (This : in Statement; + Name : in String) + return DB_Long_Float; - not overriding - function Get_Use_Long_Float - (This : in Statement; - Name : in String) - return DB_Long_Float; +not overriding +function Get_Use_Time + (This : in Statement; + Name : in String) + return Ada.Calendar.Time; +``` - not overriding - function Get_Use_Time - (This : in Statement; - Name : in String) - return Ada.Calendar.Time; - -These functions allow to inspect the state and value of the simple use element identified by its name. If the state of the given element is `Data_Null`, the data-reading functions raise exceptions for that element. \ No newline at end of file +These functions allow to inspect the state and value of the simple use element identified by its name. If the state of the given element is `Data_Null`, the data-reading functions raise exceptions for that element. diff --git a/docs/languages/index.md b/docs/languages/index.md new file mode 100644 index 0000000000..cf09638157 --- /dev/null +++ b/docs/languages/index.md @@ -0,0 +1,3 @@ +# Language bindings + +* [Ada](ada/index.md) diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 0000000000..18aef88a57 --- /dev/null +++ b/docs/license.md @@ -0,0 +1,29 @@ +# License + +The SOCI library is distributed under the terms of the [Boost Software License](http://www.boost.org/LICENSE_1_0.txt). + +## Boost Software License + +Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/docs/lobs.md b/docs/lobs.md new file mode 100644 index 0000000000..a6273262c1 --- /dev/null +++ b/docs/lobs.md @@ -0,0 +1,71 @@ +# Large Objects (LOBs) + +## Binary (BLOBs) + +The SOCI library provides also an interface for basic operations on large objects (BLOBs - Binary Large OBjects). + + blob b(sql); // sql is a session object + sql << "select mp3 from mymusic where id = 123", into(b); + +The following functions are provided in the `blob` interface, mimicking the file-like operations: + +* `std::size_t get_len();` +* `std::size_t read_from_start(char * buf, std::size_t toRead, std::size_t offset = 0);` +* `std::size_t write_from_start(const char * buf, std::size_t toWrite, std::size_t offset = 0);` +* `std::size_t append(char const *buf, std::size_t toWrite);` +* `void trim(std::size_t newLen);` + +The `offset` parameter is always counted from the beginning of the BLOB's data. + +### Portability notes + +* The way to define BLOB table columns and create or destroy BLOB objects in the database varies between different database engines. + Please see the SQL documentation relevant for the given server to learn how this is actually done. The test programs provided with the SOCI library can be also a simple source of full working examples. +* The `trim` function is not currently available for the PostgreSQL backend. + +## Long strings and XML + +The SOCI library recognizes the fact that long string values are not handled portably and in some databases long string values need to be stored as a different data type. +Similar concerns relate to the use of XML values, which are essentially strings at the application level, but can be stored in special database-level field types. + +In order to facilitate handling of long strings and XML values the following wrapper types are defined: + + struct xml_type + { + std::string value; + }; + + struct long_string + { + std::string value; + }; + +Values of these wrapper types can be used with `into` and `use` elements with the database target type that is specifically intended to handle XML and long strings data types. + +For Oracle, these database-side types are, respectively: + +* `XMLType`, +* `CLOB` + +For PostgreSQL, these types are: + +* `XML` +* `text` + +For Firebird, there is no special XML support, but `BLOB SUB_TYPE TEXT` can be +used for storing it, as well as long strings. + +For ODBC backend, these types depend on the type of the database connected to. +In particularly important special case of Microsoft SQL Server, these types +are: + +* `xml` +* `text` + +When using ODBC backend to connect to a PostgreSQL database, please be aware +that by default PostgreSQL ODBC driver truncates all "unknown" types, such as +XML, to maximal varchar type size which is just 256 bytes and so is often +insufficient for XML values in practice. It is advised to set the +`UnknownsAsLongVarchar` connection option to 1 to avoid truncating XML strings +or use PostgreSQL ODBC driver 9.6.300 or later, which allows the backend to set +this option to 1 automatically on connection. diff --git a/docs/logging.md b/docs/logging.md new file mode 100644 index 0000000000..31799efd0c --- /dev/null +++ b/docs/logging.md @@ -0,0 +1,26 @@ +# Logging + +SOCI provides a very basic logging facility. + +The following members of the `session` class support the basic logging functionality: + +* `void set_log_stream(std::ostream * s);` +* `std::ostream * get_log_stream() const;` +* `std::string get_last_query() const;` + +The first two functions allow to set the user-provided output stream object for logging. +The `NULL` value, which is the default, means that there is no logging. + +An example use might be: + + session sql(oracle, "..."); + + ofstream file("my_log.txt"); + sql.set_log_stream(&file); + + // ... + +Each statement logs its query string before the preparation step (whether explicit or implicit) and therefore logging is effective whether the query succeeds or not. +Note that each prepared query is logged only once, independent on how many times it is executed. + +The `get_last_query` function allows to retrieve the last used query. diff --git a/docs/multithreading.md b/docs/multithreading.md index 41e147b066..7fcd51aed9 100644 --- a/docs/multithreading.md +++ b/docs/multithreading.md @@ -1,38 +1,47 @@ -## Multithreading +# Multithreading The general rule for multithreading is that SOCI classes are *not* thread-safe, meaning that their instances should not be used concurrently by multiple threads. -The simplest solution for multithreaded code is to set up a separate `session` object for each thread that needs to inteact with the database. Depending on the design of the client application this might be also the most straightforward approach. +The simplest solution for multithreaded code is to set up a separate `session` object for each thread that needs to inteact with the database. +Depending on the design of the client application this might be also the most straightforward approach. -For some applications, however, it might be preferable to decouple the set of threads from the set of sessions, so that they can be optimized separately with different resources in mind. The `connection_pool` class is provided for this purpose: +For some applications, however, it might be preferable to decouple the set of threads from the set of sessions, so that they can be optimized separately with different resources in mind. +The `connection_pool` class is provided for this purpose: +```cpp +// phase 1: preparation - // phase 1: preparation +const size_t poolSize = 10; +connection_pool pool(poolSize); - const size_t poolSize = 10; - connection_pool pool(poolSize); +for (size_t i = 0; i != poolSize; ++i) +{ + session & sql = pool.at(i); - for (size_t i = 0; i != poolSize; ++i) - { - session & sql = pool.at(i); + sql.open("postgresql://dbname=mydb"); +} - sql.open("postgresql://dbname=mydb"); - } +// phase 2: usage from working threads - // phase 2: usage from working threads +{ + session sql(pool); - { - session sql(pool); + sql << "select something from somewhere..."; - sql << "select something from somewhere..."; +} // session is returned to the pool automatically +``` - } // session is returned to the pool automatically - - -The `connection_pool`'s constructor expects the size of the pool and internally creates an array of `session`s in the disconnected state. Later, the `at` function provides *non-synchronized* access to each element of the array. Note that this function is *not* thread-safe and exists only to make it easier to set up the pool in the initialization phase. +The `connection_pool`'s constructor expects the size of the pool and internally creates an array of `session`s in the disconnected state. +Later, the `at` function provides *non-synchronized* access to each element of the array. +Note that this function is *not* thread-safe and exists only to make it easier to set up the pool in the initialization phase. Note that it is not obligatory to use the same connection parameters for all sessions in the pool, although this will be most likely the usual case. -The working threads that need to *lease* a single session from the pool use the dedicated constructor of the `session` class - this constructor blocks until some session object becomes available in the pool and attaches to it, so that all further uses will be forwarded to the `session` object managed by the pool. As long as the local `session` object exists, the associated session in the pool is *locked* and no other thread will gain access to it. When the local `session` variable goes out of scope, the related entry in the pool's internal array is released, so that it can be used by other threads. This way, the connection pool guarantees that its session objects are never used by more than one thread at a time. +The working threads that need to *lease* a single session from the pool use the dedicated constructor of the `session` class - this constructor blocks until some session object becomes available in the pool and attaches to it, so that all further uses will be forwarded to the `session` object managed by the pool. +As long as the local `session` object exists, the associated session in the pool is *locked* and no other thread will gain access to it. +When the local `session` variable goes out of scope, the related entry in the pool's internal array is released, so that it can be used by other threads. +This way, the connection pool guarantees that its session objects are never used by more than one thread at a time. -Note that the above scheme is the simplest way to use the connection pool, but it is also constraining in the fact that the `session`'s constructor can *block* waiting for the availability of some entry in the pool. For more demanding users there are also low-level functions that allow to lease sessions from the pool with timeout on wait. Please consult the [reference](reference.html) for details. \ No newline at end of file +Note that the above scheme is the simplest way to use the connection pool, but it is also constraining in the fact that the `session`'s constructor can *block* waiting for the availability of some entry in the pool. +For more demanding users there are also low-level functions that allow to lease sessions from the pool with timeout on wait. +Please consult the [reference](reference.html) for details. diff --git a/docs/procedures.md b/docs/procedures.md new file mode 100644 index 0000000000..b7473b936d --- /dev/null +++ b/docs/procedures.md @@ -0,0 +1,20 @@ +# Stored Procedures + +The `procedure` class provides a convenient mechanism for calling stored procedures: + +```cpp +sql << "create or replace procedure echo(output out varchar2," + "input in varchar2) as " + "begin output := input; end;"; + +std::string in("my message"); +std::string out; +procedure proc = (sql.prepare << "echo(:output, :input)", use(out, "output"), use(in, "input")); +proc.execute(true); +assert(out == "my message"); +``` + +## Portability note + +The above way of calling stored procedures is provided for portability of the code that might need it. +It is of course still possible to call procedures or functions using the syntax supported by the given database server. diff --git a/docs/queries.md b/docs/queries.md index d8d3efe71e..65f12d931a 100644 --- a/docs/queries.md +++ b/docs/queries.md @@ -1,25 +1,30 @@ -## Queries +# Queries -### Simple SQL statements +## Simple SQL statements In many cases, the SQL query is intended to be executed only once, which means that statement parsing and execution can go together. The `session` class provides a special `once` member, which triggers parsing and execution of such one-time statements: - sql.once << "drop table persons"; +```cpp +sql.once << "drop table persons"; +``` For shorter syntax, the following form is also allowed: - sql << "drop table persons"; +```cpp +sql << "drop table persons"; +``` The IOStream-like interface is exactly what it looks like, so that the statement text can be composed of many parts, involving anything that is *streamable* (including custom classes, if they have appropriate `operator<<`): +```cpp +string tableName = "persons"; +sql << "drop table " << tableName; - string tableName = "persons"; - sql << "drop table " << tableName; +int id = 123; +sql << "delete from companies where id = " << id; +``` - int id = 123; - sql << "delete from companies where id = " << id; - -### Query transformation +## Query transformation In SOCI 3.2.0, query transformation mechanism was introduced. @@ -31,46 +36,51 @@ For one-time statements, query transformation is performed before each execution A few short examples how to use query transformation: -*defined as free function:* +* defined as free function: - std::string less_than_ten(std::string query) +```cpp +std::string less_than_ten(std::string query) +{ + return query + " WHERE price < 10"; +} + +session sql(postgresql, "dbname=mydb"); +sql.set_query_transformation(less_than_ten); +sql << "DELETE FROM item"; +``` + +* defined as function object: + +```cpp +struct order : std::unary_function +#include +#include +#include +#include + +using namespace soci; +using namespace std; + +bool get_name(string &name) { + cout << "Enter name: "; + return cin >> name; +} + +int main() +{ + try + { + session sql(oracle, "service=mydb user=john password=secret"); + + int count; + sql << "select count(*) from phonebook", into(count); + + cout << "We have " << count << " entries in the phonebook.\n"; + + string name; + while (get_name(name)) + { + string phone; + indicator ind; + sql << "select phone from phonebook where name = :name", + into(phone, ind), use(name); + + if (ind == i_ok) + { + cout << "The phone number is " << phone << '\n'; + } + else + { + cout << "There is no phone for " << name << '\n'; + } + } + } + catch (exception const &e) + { + cerr << "Error: " << e.what() << '\n'; + } +} +``` diff --git a/docs/statements.md b/docs/statements.md index cb4a9fb1dd..c2b9f4181b 100644 --- a/docs/statements.md +++ b/docs/statements.md @@ -1,54 +1,56 @@ -## Statements, procedures and transactions +# Statements -* [Statement preparation and repeated execution](#preparation) -* [Rowset and iterator-based access](#rowset) -* [Bulk operations](#bulk) -* [Stored procedures](#procedures) -* [Transactions](#transactions) -* [Basic logging support](#logging) - -### Statement preparation and repeated execution +## Prepared statement Consider the following examples: - // Example 1. - for (int i = 0; i != 100; ++i) - { - sql << "insert into numbers(value) values(" << i << ")"; - } - - // Example 2. - for (int i = 0; i != 100; ++i) - { - sql << "insert into numbers(value) values(:val)", use(i); - } +```cpp +// Example 1. +for (int i = 0; i != 100; ++i) +{ + sql << "insert into numbers(value) values(" << i << ")"; +} +// Example 2. +for (int i = 0; i != 100; ++i) +{ + sql << "insert into numbers(value) values(:val)", use(i); +} +``` Both examples will populate the table `numbers` with the values from `0` to `99`. -The problem is that in both examples, not only the statement execution is repeated 100 times, but also the statement parsing and preparation. This means unnecessary overhead, even if some of the database servers are likely to optimize the second case. In fact, more complicated queries are likely to suffer in terms of lower performance, because finding the optimal execution plan is quite expensive and here it would be needlessly repeated. +The problem is that in both examples, not only the statement execution is repeated 100 times, but also the statement parsing and preparation. +This means unnecessary overhead, even if some of the database servers are likely to optimize the second case. +In fact, more complicated queries are likely to suffer in terms of lower performance, because finding the optimal execution plan is quite expensive and here it would be needlessly repeated. + +### Statement preparation The following example uses the class `statement` explicitly, by preparing the statement only once and repeating its execution with changing data (note the use of `prepare` member of `session` class): - int i; - statement st = (sql.prepare << - "insert into numbers(value) values(:val)", - use(i)); - for (i = 0; i != 100; ++i) - { - st.execute(true); - } +```cpp +int i; +statement st = (sql.prepare << + "insert into numbers(value) values(:val)", + use(i)); +for (i = 0; i != 100; ++i) +{ + st.execute(true); +} +``` -The `true` parameter given to the `execute` method indicates that the actual data exchange is wanted, so that the meaning of the whole example is "prepare the statement and exchange the data for each value of variable `i`". +The `true` parameter given to the `execute` method indicates that the actual data exchange is wanted, so that the meaning of the whole example is -#####Portability note: +> "prepare the statement and exchange the data for each value of variable `i`". + +### Portability note: The above syntax is supported for all backends, even if some database server does not actually provide this functionality - in which case the library will internally execute the query in a single phase, without really separating the statement preparation from execution. -For PostgreSQL servers older than 8.0 it is necessary to define the `SOCI_POSTGRESQL_NOPREPARE` macro while compiling the library to fall back to this one-phase behaviour. Simply, pass `-DSOCI_POSTGRESQL_NOPREPARE=ON` variable to CMake. +For PostgreSQL servers older than 8.0 it is necessary to define the `SOCI_POSTGRESQL_NOPREPARE` macro while compiling the library to fall back to this one-phase behaviour. +Simply, pass `-DSOCI_POSTGRESQL_NOPREPARE=ON` variable to CMake. - -### Rowset and iterator-based access +## Rowset and iterator The `rowset` class provides an alternative means of executing queries and accessing results using STL-like iterator interface. @@ -56,68 +58,75 @@ The `rowset_iterator` type is compatible with requirements defined for input ite The `rowset` itself can be used only with select queries. -The following example creates an instance of the `rowset` class and binds query results into elements of `int` type - in this query only one result column is expected. After executing the query the code iterates through the query result using `rowset_iterator`: +The following example creates an instance of the `rowset` class and binds query results into elements of `int` type - in this query only one result column is expected. +After executing the query the code iterates through the query result using `rowset_iterator`: +```cpp +rowset rs = (sql.prepare << "select values from numbers"); - rowset rs = (sql.prepare << "select values from numbers"); - - for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) - { - cout << *it << '\n'; - } +for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) +{ + cout << *it << '\n'; +} +``` Another example shows how to retrieve more complex results, where `rowset` elements are of type `row` and therefore use [dynamic bindings](exchange.html#dynamic): +```cpp +// person table has 4 columns - // person table has 4 columns +rowset rs = (sql.prepare << "select id, firstname, lastname, gender from person"); - rowset rs = (sql.prepare << "select id, firstname, lastname, gender from person"); +// iteration through the resultset: +for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) +{ + row const& row = *it; - // iteration through the resultset: - for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) - { - row const& row = *it; + // dynamic data extraction from each row: + cout << "Id: " << row.get(0) << '\n' + << "Name: " << row.get(1) << " " << row.get(2) << '\n' + << "Gender: " << row.get(3) << endl; +} +``` - // dynamic data extraction from each row: - cout << "Id: " << row.get(0) << '\n' - << "Name: " << row.get(1) << " " << row.get(2) << '\n' - << "Gender: " << row.get(3) << endl; - } +The `rowset_iterator` can be used with standard algorithms as well: -`rowset_iterator` can be used with standard algorithms as well: +```cpp +rowset rs = (sql.prepare << "select firstname from person"); +std::copy(rs.begin(), rs.end(), std::ostream_iterator(std::cout, "\n")); +``` - rowset rs = (sql.prepare << "select firstname from person"); +Above, the query result contains a single column which is bound to `rowset` element of type of `std::string`. +All records are sent to standard output using the `std::copy` algorithm. - std::copy(rs.begin(), rs.end(), std::ostream_iterator(std::cout, "\n")); +## Bulk operations -Above, the query result contains a single column which is bound to `rowset` element of type of `std::string`. All records are sent to standard output using the `std::copy` algorithm. +When using some databases, further performance improvements may be possible by having the underlying database API group operations together to reduce network roundtrips. +SOCI makes such bulk operations possible by supporting `std::vector` based types. -### Bulk operations +The following example presents how to insert 100 records in 4 batches. +It is also important to note, that size of vector remains equal in every batch interaction. +This ensures vector is not reallocated and, what's crucial for the bulk trick, new data should be pushed to the vector before every call to `statement::execute`: -When using some databases, further performance improvements may be possible by having the underlying database API group operations together to reduce network roundtrips. SOCI makes such bulk operations possible by supporting `std::vector` based types. +```cpp +// Example 3. +void fill_ids(std::vector& ids) +{ + for (std::size_t i = 0; i < ids.size(); ++i) + ids[i] = i; // mimics source of a new ID +} -The following example presents how to insert 100 records in 4 batches. It is also important to note, that size of vector remains equal in every batch interaction. This ensures vector is not reallocated and, what's crucial for the bulk trick, new data should be pushed to the vector before every call to `statement::execute`: +const int BATCH_SIZE = 25; +std::vector ids(BATCH_SIZE); - - // Example 3. - void fill_ids(std::vector& ids) - { - for (std::size_t i = 0; i < ids.size(); ++i) - ids[i] = i; // mimics source of a new ID - } - - const int BATCH_SIZE = 25; - std::vector ids(BATCH_SIZE); - - statement st = (sql.prepare << - "insert into numbers(value) values(:val)", - use(ids)); - for (int i = 0; i != 4; ++i) - { - fill_ids(ids); - st.execute(true); - } +statement st = (sql.prepare << "insert into numbers(value) values(:val)", use(ids)); +for (int i = 0; i != 4; ++i) +{ + fill_ids(ids); + st.execute(true); +} +``` Given batch size is 25, this example should insert 4 x 25 = 100 records. @@ -125,52 +134,60 @@ Given batch size is 25, this example should insert 4 x 25 = 100 records. It is also possible to read all the numbers written in the above examples: - int i; - statement st = (sql.prepare << - "select value from numbers order by value", - into(i)); - st.execute(); - while (st.fetch()) - { - cout << i << '\n'; - } +```cpp +int i; +statement st = (sql.prepare << "select value from numbers order by value", into(i)); +st.execute(); +while (st.fetch()) +{ + cout << i << '\n'; +} +``` -In the above example, the `execute` method is called with the default parameter `false`. This means that the statement should be executed, but the actual data exchange will be performed later. +In the above example, the `execute` method is called with the default parameter `false`. +This means that the statement should be executed, but the actual data exchange will be performed later. -Further `fetch` calls perform the actual data retrieval and cursor traversal. The *end-of-cursor* condition is indicated by the `fetch` function returning `false`. +Further `fetch` calls perform the actual data retrieval and cursor traversal. +The *end-of-cursor* condition is indicated by the `fetch` function returning `false`. The above code example should be treated as an idiomatic way of reading many rows of data, *one at a time*. It is further possible to select records in batches into `std::vector` based types, with the size of the vector specifying the number of records to retrieve in each round trip: - std::vector valsOut(100); - sql << "select val from numbers", into(valsOut); +```cpp +std::vector valsOut(100); +sql << "select val from numbers", into(valsOut); +``` -Above, the value `100` indicates that no more values should be retrieved, even if it would be otherwise possible. If there are less rows than asked for, the vector will be appropriately down-sized. +Above, the value `100` indicates that no more values should be retrieved, even if it would be otherwise possible. +If there are less rows than asked for, the vector will be appropriately down-sized. The `statement::execute()` and `statement::fetch()` functions can also be used to repeatedly select all rows returned by a query into a vector based type: - - const int BATCH_SIZE = 30; - std::vector valsOut(BATCH_SIZE); - statement st = (sql.prepare << - "select value from numbers", - into(valsOut)); - st.execute(); - while (st.fetch()) +```cpp +const int BATCH_SIZE = 30; +std::vector valsOut(BATCH_SIZE); +statement st = (sql.prepare << + "select value from numbers", + into(valsOut)); +st.execute(); +while (st.fetch()) +{ + std::vector::iterator pos; + for(pos = valsOut.begin(); pos != valsOut.end(); ++pos) { - std::vector::iterator pos; - for(pos = valsOut.begin(); pos != valsOut.end(); ++pos) - { - cout << *pos << '\n'; - } - - valsOut.resize(BATCH_SIZE); + cout << *pos << '\n'; } -Assuming there are 100 rows returned by the query, the above code will retrieve and print all of them. Since the output vector was created with size 30, it will take (at least) 4 calls to `fetch()` to retrieve all 100 values. Each call to `fetch()` can potentially resize the vector to a size less than its initial size -- how often this happens depends on the underlying database implementation. This explains why the `resize(BATCH_SIZE)` operation is needed - it is there to ensure that each time the `fetch()` is called, the vector is ready to accept the next bunch of values. Without this operation, the vector *might* be getting smaller with subsequent iterations of the loop, forcing more iterations to be performed (because *all* rows will be read anyway), than really needed. + valsOut.resize(BATCH_SIZE); +} +``` +Assuming there are 100 rows returned by the query, the above code will retrieve and print all of them. +Since the output vector was created with size 30, it will take (at least) 4 calls to `fetch()` to retrieve all 100 values. +Each call to `fetch()` can potentially resize the vector to a size less than its initial size - how often this happens depends on the underlying database implementation. +This explains why the `resize(BATCH_SIZE)` operation is needed - it is there to ensure that each time the `fetch()` is called, the vector is ready to accept the next bunch of values. +Without this operation, the vector *might* be getting smaller with subsequent iterations of the loop, forcing more iterations to be performed (because *all* rows will be read anyway), than really needed. Note the following details about the above examples: @@ -179,133 +196,54 @@ Note the following details about the above examples: Taking these points under consideration, the above code example should be treated as an idiomatic way of reading many rows by bunches of requested size. -### Statement caching +### Portability note -Some backends have some facilities to improve statement parsing and compilation to limit overhead when creating commonly used query. But for backends that does not support this kind optimization you can keep prepared statement and use it later with new references. To do such, prepare a statement as usual, you have to use `exchange` to bind new variables to statement object, then `execute` statement and finish by cleaning bound references with `bind_clean_up`. +Actually, all supported backends guarantee that the requested number of rows will be read with each fetch and that the vector will never be down-sized, unless for the last fetch, when the end of rowset condition is met. +This means that the manual vector resizing is in practice not needed - the vector will keep its size until the end of rowset. +The above idiom, however, is provided with future backends in mind, where the constant size of the vector might be too expensive to guarantee and where allowing `fetch` to down-size the vector even before reaching the end of rowset might buy some performance gains. - sql << "CREATE TABLE test(a INTEGER)"; +## Statement caching + +Some backends have some facilities to improve statement parsing and compilation to limit overhead when creating commonly used query. +But for backends that does not support this kind optimization you can keep prepared statement and use it later with new references. +To do such, prepare a statement as usual, you have to use `exchange` to bind new variables to statement object, then `execute` statement and finish by cleaning bound references with `bind_clean_up`. + +```cpp +sql << "CREATE TABLE test(a INTEGER)"; + +{ + // prepare statement + soci::statement stmt = (db.prepare << "INSERT INTO numbers(value) VALUES(:val)"); { - // prepare statement - soci::statement stmt = (db.prepare << "INSERT INTO numbers(value) VALUES(:val)"); + // first insert + int a0 = 0; - { - // first insert - int a0 = 0; + // update reference + stmt.exchange(soci::use(a0)); - // update reference - stmt.exchange(soci::use(a0)); - - stmt.define_and_bind(); - stmt.execute(true); - stmt.bind_clean_up(); - } - - { - // come later, second insert - int a1 = 1; - - // update reference - stmt.exchange(soci::use(a1)); - - stmt.define_and_bind(); - stmt.execute(true); - stmt.bind_clean_up(); - } + stmt.define_and_bind(); + stmt.execute(true); + stmt.bind_clean_up(); } { - std::vector v(10); - db << "SELECT value FROM numbers", soci::into(v); - for (int i = 0; i < v.size(); ++i) - std::cout << "value " << i << ": " << v[i] << std::endl; + // come later, second insert + int a1 = 1; + + // update reference + stmt.exchange(soci::use(a1)); + + stmt.define_and_bind(); + stmt.execute(true); + stmt.bind_clean_up(); } +} - -#####Portability note: -Actually, all supported backends guarantee that the requested number of rows will be read with each fetch and that the vector will never be down-sized, unless for the last fetch, when the end of rowset condition is met. This means that the manual vector resizing is in practice not needed - the vector will keep its size until the end of rowset. The above idiom, however, is provided with future backends in mind, where the constant size of the vector might be too expensive to guarantee and where allowing `fetch` to down-size the vector even before reaching the end of rowset might buy some performance gains. - - -### Stored procedures - -The `procedure` class provides a convenient mechanism for calling stored procedures: - - sql << "create or replace procedure echo(output out varchar2," - "input in varchar2) as " - "begin output := input; end;"; - - std::string in("my message"); - std::string out; - procedure proc = (sql.prepare << "echo(:output, :input)", - use(out, "output"), - use(in, "input")); - proc.execute(true); - assert(out == "my message"); - - -#####Portability note: - -The above way of calling stored procedures is provided for portability of the code that might need it. It is of course still possible to call procedures or functions using the syntax supported by the given database server. - - -### Transactions - -The SOCI library provides the following members of the `session` class for transaction management: - -* `void begin();` -* `void commit();` -* `void rollback();` - -In addition to the above there is a RAII wrapper that allows to associate the transaction with the given scope of code: - - class transaction - { - public: - explicit transaction(session & sql); - - ~transaction(); - - void commit(); - void rollback(); - - private: - // ... - }; - -The object of class `transaction` will roll back automatically when the object is destroyed -(usually as a result of leaving the scope) *and* when the transaction was not explicitly committed before that. - -A typical usage pattern for this class might be: - - { - transaction tr(sql); - - sql << "insert into ..."; - sql << "more sql queries ..."; - // ... - - tr.commit(); - } - -With the above pattern the transaction is committed only when the code successfully reaches the end of block. If some exception is thrown before that, the scope will be left without reaching the final statement and the transaction object will automatically roll back in its destructor. - -### Basic logging support - -The following members of the `session` class support the basic logging functionality: - -* `void set_log_stream(std::ostream * s);` -* `std::ostream * get_log_stream() const;` -* `std::string get_last_query() const;` - -The first two functions allow to set the user-provided output stream object for logging. The `NULL` value, which is the default, means that there is no logging. An example use might be: - - session sql(oracle, "..."); - - ofstream file("my_log.txt"); - sql.set_log_stream(&file); - - // ... - -Each statement logs its query string before the preparation step (whether explicit or implicit) and therefore logging is effective whether the query succeeds or not. Note that each prepared query is logged only once, independent on how many times it is executed. - -The `get_last_query` function allows to retrieve the last used query. \ No newline at end of file +{ + std::vector v(10); + db << "SELECT value FROM numbers", soci::into(v); + for (int i = 0; i < v.size(); ++i) + std::cout << "value " << i << ": " << v[i] << std::endl; +} +``` diff --git a/docs/structure.md b/docs/structure.md index ee6e63f4ed..b1f5bf4537 100644 --- a/docs/structure.md +++ b/docs/structure.md @@ -1,27 +1,27 @@ -## Structure +# Structure -The picture above presents the structure of the library, together with its clients and servers. The boxes filled with cyan represent components that are part of the library distribution. +The picture below presents the structure of the library, together with its clients and servers. +The boxes filled with cyan represent components that are part of the library distribution. + +![Structure Chart](images/structure.png) The SOCI library is extensible in the following ways: * More backends can be added to target various database servers. * More interfaces can be defined on top of common backend interface. -* Other languages can use the simple interface, which was designed specifically for the "C" calling convention to ensure easy binding. +* Other languages can use the [simple interface](interfaces.md), which was designed specifically for the "C" calling convention to ensure easy binding. The core part of the library and the backend interface definition are placed in the `core` directory of the library distribution. The `soci-backend.h` file is an internal abstract interface to the actual backends, which are needed to perform operations on the given database server. Normally, the C++ client program needs to interface with the `soci.h` header and the header(s) relevant to the given backend(s) (for example, `soci-oracle.h`), although with dynamic backend loading this can be avoided. It is possible for the same program to use many backends at the same time. Everything in SOCI is declared in the namespace `soci`. All code examples presented in this documentation assume that your code begins with something like: +```cpp +#include "soci.h" +// other includes if necessary - #include "soci.h" - // other includes if necessary +using namespace soci; - using namespace soci; +// ... +``` - // ... - ---- -#####Note: - -In simple programs, `#include` for the relevant backend is needed only in the file where the `session` object is created with explicit name of the backend factory. The example program on the [previous page](index.html) shows the appropriate `#include` directive for the Oracle backend. It is also possible to name backends at run-time as part of the connection string, in which case no backend-specific `#include` directive is necessary. ---- \ No newline at end of file +Note: In simple programs, `#include` for the relevant backend is needed only in the file where the `session` object is created with explicit name of the backend factory. The example program on the [previous page](index.html) shows the appropriate `#include` directive for the Oracle backend. It is also possible to name backends at run-time as part of the connection string, in which case no backend-specific `#include` directive is necessary. diff --git a/docs/transactions.md b/docs/transactions.md new file mode 100644 index 0000000000..fc250859ec --- /dev/null +++ b/docs/transactions.md @@ -0,0 +1,45 @@ +# Transactions + +The SOCI library provides the following members of the `session` class for transaction management: + +* `void begin();` +* `void commit();` +* `void rollback();` + +In addition to the above there is a RAII wrapper that allows to associate the transaction with the given scope of code: + +```cpp +class transaction +{ +public: + explicit transaction(session & sql); + + ~transaction(); + + void commit(); + void rollback(); + +private: + // ... +}; +``` + +The object of class `transaction` will roll back automatically when the object is destroyed +(usually as a result of leaving the scope) *and* when the transaction was not explicitly committed before that. + +A typical usage pattern for this class might be: + +```cpp +{ + transaction tr(sql); + + sql << "insert into ..."; + sql << "more sql queries ..."; + // ... + + tr.commit(); +} +``` + +With the above pattern the transaction is committed only when the code successfully reaches the end of block. +If some exception is thrown before that, the scope will be left without reaching the final statement and the transaction object will automatically roll back in its destructor. diff --git a/docs/types.md b/docs/types.md new file mode 100644 index 0000000000..3308b94f4b --- /dev/null +++ b/docs/types.md @@ -0,0 +1,292 @@ +# Data Types + +## Static binding + +The static binding for types is most useful when the types used in the database are known at compile time - this was already presented above with the help of `into` and `use` functions. + +The following types are currently supported for use with `into` and `use` expressions: + +* `char` (for character values) +* `short`, `int`, `unsigned long`, `long long`, `double` (for numeric values) +* `std::string` (for string values) +* `std::tm``` (for datetime values) +* `soci::statement` (for nested statements and PL/SQL cursors) +* `soci::blob` (for Binary Large OBjects) +* `soci::row_id` (for row identifiers) + +See the test code that accompanies the library to see how each of these types is used. + +### Static binding for bulk operations + +Bulk inserts, updates, and selects are supported through the following `std::vector` based into and use types: + +* `std::vector` +* `std::vector` +* `std::vector` +* `std::vector` +* `std::vector` +* `std::vector` +* `std::vector` +* `std::vector` + +Use of the vector based types mirrors that of the standard types, with the size of the vector used to specify the number of records to process at a time. +See below for examples. + +Bulk operations are supported also for `std::vector`s of the user-provided types that have appropriate conversion routines defines. + +## Dynamic binding + +For certain applications it is desirable to be able to select data from arbitrarily structured tables (e.g. via "`select * from ...`") and format the resulting data based upon its type. + +SOCI supports binding dynamic resultset through the `soci::row` and `soci::column_properties` classes. + +Data is selected into a `row` object, which holds `column_properties` objects describing the attributes of data contained in each column. +Once the data type for each column is known, the data can be formatted appropriately. + +For example, the code below creates an XML document from a selected row of data from an arbitrary table: + +```cpp +row r; +sql << "select * from some_table", into(r); + +std::ostringstream doc; +doc << "" << std::endl; +for(std::size_t i = 0; i != r.size(); ++i) +{ + const column_properties & props = r.get_properties(i); + + doc << '<' << props.get_name() << '>'; + + switch(props.get_data_type()) + { + case dt_string: + doc << r.get(i); + break; + case dt_double: + doc << r.get(i); + break; + case dt_integer: + doc << r.get(i); + break; + case dt_long_long: + doc << r.get(i); + break; + case dt_unsigned_long_long: + doc << r.get(i); + break; + case dt_date: + std::tm when = r.get(i); + doc << asctime(&when); + break; + } + + doc << "' << std::endl; +} +doc << ""; +``` + +The type `T` parameter that should be passed to `row::get()` depends on the SOCI data type that is returned from `column_properties::get_data_type()`. + +`row::get()` throws an exception of type `std::bad_cast` if an incorrect type `T` is requested. + +| SOCI Data Type | `row::get` specialization | +|----------------|------------------------------| +| `dt_double` | `double` | +| `dt_integer` | `int` | +| `dt_long_long` | `long long` | +| `dt_unsigned_long_long` | `unsigned long long`| +| `dt_string` | `std::string` | +| `dt_date` | `std::tm` | + +The mapping of underlying database column types to SOCI datatypes is database specific. +See the [backend documentation](backends/index.html) for details. + +The `row` also provides access to indicators for each column: + +```cpp +row r; +sql << "select name from some_table where id = 1", into(r); +if (r.get_indicator(0) != soci::i_null) +{ + std::cout << r.get(0); +} +``` + +It is also possible to extract data from the `row` object using its stream-like interface, where each extracted variable should have matching type respective to its position in the chain: + +```cpp +row r; +sql << "select name, address, age from persons where id = 123", into(r); + +string name, address; +int age; + +r >> name >> address >> age; +``` + +Note, however, that this interface is *not* compatible with the standard `std::istream` class and that it is only possible to extract a single row at a time - for "safety" reasons the row boundary is preserved and it is necessary to perform the `fetch` operation explicitly for each consecutive row. + +## User-defined C++ types + +SOCI can be easily extended with support for user-defined datatypes. + +The extension mechanism relies on appropriate specialization of the `type_conversion` structure that converts to and from one of the following SOCI base types: + +* `double` +* `int` +* `long long` +* `unsigned long long` +* `std::string` +* `char` +* `std::tm` + +There are three required class members for a valid `type_conversion` specialization: + +* the `base_type` type definition, aliasing either one of the base types *or another ser-defined type* +* the `from_base()` static member function, converting from the base type +* the `to_base()` static member function, converting to the base type + +Note that no database-specific code is required to define user conversion. + +The following example shows how the user can extend SOCI to support his own type `MyInt`, which here is some wrapper for the fundamental `int` type: + +```cpp +class MyInt +{ +public: + MyInt() {} + MyInt(int i) : i_(i) {} + + void set(int i) { i_ = i; } + int get() const { return i_; } + +private: + int i_; +}; + +namespace soci +{ + template << + struct type_conversion + { + typedef int base_type; + + static void from_base(int i, indicator ind, MyInt & mi) + { + if (ind == i_null) + { + throw soci_error("Null value not allowed for this type"); + } + + mi.set(i); + } + + static void to_base(const MyInt & mi, int & i, indicator & ind) + { + i = mi.get(); + ind = i_ok; + } + }; +} +``` + +The above specialization for `soci::type_conversion` is enough to enable the following: + +```cpp +MyInt i; + +sql << "select count(*) from person", into(i); + +cout << "We have " << i.get() << " persons in the database.\n"; +``` + +Note that there is a number of types from the Boost library integrated with SOCI out of the box, see [Integration with Boost](boost.html) for complete description. Use these as examples of conversions for more complext data types. + +Another possibility to extend SOCI with custom data types is to use the `into_type + struct type_conversion + { + typedef values base_type; + + static void from_base(values const & v, indicator /* ind */, Person & p) + { + p.id = v.get("ID"); + p.firstName = v.get("FIRST_NAME"); + p.lastName = v.get("LAST_NAME"); + + // p.gender will be set to the default value "unknown" + // when the column is null: + p.gender = v.get("GENDER", "unknown"); + + // alternatively, the indicator can be tested directly: + // if (v.indicator("GENDER") == i_null) + // { + // p.gender = "unknown"; + // } + // else + // { + // p.gender = v.get("GENDER"); + // } + } + + static void to_base(const Person & p, values & v, indicator & ind) + { + v.set("ID", p.id); + v.set("FIRST_NAME", p.firstName); + v.set("LAST_NAME", p.lastName); + v.set("GENDER", p.gender, p.gender.empty() ? i_null : i_ok); + ind = i_ok; + } + }; +} +``` + +With the above `type_conversion` specialization in place, it is possible to use `Person` directly with SOCI: + +```cpp +session sql(oracle, "service=db1 user=scott password=tiger"); + +Person p; +p.id = 1; +p.lastName = "Smith"; +p.firstName = "Pat"; +sql << "insert into person(id, first_name, last_name) " + "values(:ID, :FIRST_NAME, :LAST_NAME)", use(p); + +Person p1; +sql << "select * from person", into(p1); +assert(p1.id == 1); +assert(p1.firstName + p.lastName == "PatSmith"); +assert(p1.gender == "unknown"); + +p.firstName = "Patricia"; +sql << "update person set first_name = :FIRST_NAME " + "where id = :ID", use(p); +``` + +Note: The `values` class is currently not suited for use outside of `type_conversion`specializations. +It is specially designed to facilitate object-relational mapping when used as shown above. diff --git a/docs/utilities.md b/docs/utilities.md new file mode 100644 index 0000000000..110f0e19b1 --- /dev/null +++ b/docs/utilities.md @@ -0,0 +1,151 @@ +# Utilities + +SOCI provides a portable abstraction for selection of database queries. + +## DDL + +SOCI supports some basic methods to construct portable DDL queries. That is, instead of writing explicit SQL statement for creating or modifying tables, it is possible to use dedicated SOCI functions, which prepare appropriate DDL statements behind the scenes, thus enabling the user application to create basic database structures in a way that is portable across different database servers. Note that the actual support for these functions depends on the actual backend implementation. + +It is possible to create a new table in a single statement: + +```cpp +sql.create_table("t1").column("i", soci::dt_integer).column("j", soci::dt_integer); +``` + +Above, table "t1" will be created with two columns ("i", "j") of type integer. + +It is also possible to build similar statements piece by piece, which is useful if the table structure is computed dynamically: + +```cpp +{ + soci::ddl_type ddl = sql.create_table("t2"); + ddl.column("i", soci::dt_integer); + ddl.column("j", soci::dt_integer); + ddl.column("k", soci::dt_integer)("not null"); + ddl.primary_key("t2_pk", "j"); +} +``` + +The actual statement is executed at the end of above block, when the ddl object goes out of scope. The "not null" constraint was added to the definition of column "k" explicitly and in fact any piece of SQL can be inserted this way - with the obvious caveat of having limited portability (the "not null" piece seems to be universaly portable). + +Columns can be added to and dropped from already existing tables as well: + +```cpp +sql.add_column("t1", "k", soci::dt_integer); +// or with constraint: +//sql.add_column("t1", "k", soci::dt_integer)("not null"); + +sql.drop_column("t1", "i"); +``` + +If needed, precision and scale can be defined with additional integer arguments to functions that create columns: + +```cpp +sql.add_column("t1", "s", soci::dt_string, precision); +sql.add_column("t1", "d", soci::dt_double, precision, scale); +``` + +Tables with foreign keys to each other can be also created: + +```cpp +{ + soci::ddl_type ddl = sql.create_table("t3"); + ddl.column("x", soci::dt_integer); + ddl.column("y", soci::dt_integer); + ddl.foreign_key("t3_fk", "x", "t2", "j"); +} +``` + +Tables can be dropped, too: + +```cpp +sql.drop_table("t1"); +sql.drop_table("t3"); +sql.drop_table("t2"); +``` + +Note that due to the differences in the set of types that are actually supported on the target database server, the type mappings, as well as precision and scales, might be different, even in the way that makes them impossible to portably recover with metadata queries. + +In the category of portability utilities, the following functions are also available: + +```cpp +sql.empty_blob() +``` + +the above call returns the string containing expression that represents an empty BLOB value in the given target backend. This expression can be used as part of a bigger SQL statement, for example: + +```cpp +sql << "insert into my_table (x) values (" + sql.empty_blob() + ")"; +``` + +and: + +```cpp +sql.nvl() +``` + +the above call returns the string containing the name of the SQL function that implements the NVL or COALESCE operation in the given target backend, for example: + +```cpp +sql << "select name, " + sql.nvl() + "(phone, \'UNKNOWN\') from phone_book"; +``` + +Note: `empty_blob` and `nvl` are implemented in Oracle, PostgreSQL and SQLite3 backends; for other backends their behaviour is as for PostgreSQL. + +## DML + +Only two related functions are currently available in this category: +`get_dummy_from_clause()` can be used to construct select statements that don't +operate on any table in a portable way, as while some databases allow simply +omitting the from clause in this case, others -- e.g. Oracle -- still require +providing some syntactically valid from clause even if it is not used. To use +this function, simply append the result of this function to the statement: + +```cpp +double databasePi; +session << ("select 4*atan(1)" + session.get_dummy_from_clause()), + into(databasePi); +``` + +If just the name of the dummy table is needed, and not the full clause, you can +use `get_dummy_from_table()` to obtain it. + +Notice that both functions require the session to be connected as their result +depends on the database it is connected to. + +## Database Metadata + +It is possible to portably query the database server to obtain basic metadata information. + +In order to get the list of table names in the current schema: + +```cpp +std::vector names(100); +sql.get_table_names(), into(names); +``` + +alternatively: + +```cpp +std::string name; +soci::statement st = (sql.prepare_table_names(), into(name)); + +st.execute(); +while (st.fetch()) +{ + // ... +} +``` + +Similarly, to get the description of all columns in the given table: + +```cpp +soci::column_info ci; +soci::statement st = (sql.prepare_column_descriptions(table_name), into(ci)); + +st.execute(); +while (st.fetch()) +{ + // ci fields describe each column in turn +} +``` diff --git a/docs/vagrant.md b/docs/vagrant.md new file mode 100644 index 0000000000..b933b11021 --- /dev/null +++ b/docs/vagrant.md @@ -0,0 +1,125 @@ +# Vagrant SOCI + +[Vagrant](https://www.vagrantup.com/) used to build and provision +virtual environments for **hassle-free** SOCI development. + +## Features + +* Ubuntu 14.04 (Trusty) virtual machine +* Multi-machine set-up with three VMs: `soci`, `oracle`, `db2`. +* Support networking between the configured machines. +* `soci.vm`: + * hostname: `vmsoci.local` + * build essentials + * core dependencies + * backend dependencies + * FOSS databases installed with sample `soci` user and instance pre-configured + * during provision, automatically clones and builds SOCI from `master` branch. +* `db2.vm`: + * hostname: `vmdb2.local` + * IBM DB2 Express-C 9.7 installed from [archive.canonical.com](http://archive.canonical.com) packages. +* `oracle.vm`: + * *TODO*: provision with Oracle XE +* SOCI local git repository (aka `$SOCI_HOME`) is automatically shared on host + and all guest machines. + +## Prerequisites + +* Speedy broadband, time and coffee. +* Recommended 4GB or much more RAM (tested with 16GB only). + +### SOCI DB2 backend + +The `soci.vm` will be configured properly to build the DB2 backend only if +it is provisioned with complete DB2 CLI client (libraries and headers). +You need to download "IBM Data Server Driver Package (DS Driver)" manually +and make it visible to Vagrant: + +1. Go to [IBM Data Server Client Packages](http://www-01.ibm.com/support/docview.wss?uid=swg21385217). +2. Download "IBM Data Server Driver Package (DS Driver)". +3. Copy the package to `${SOCI_HOME}/tmp` directory, on host machine. + +## Usage + +Below, simple and easy development workflow with Vagrant is outlined: + +* [Boot](https://docs.vagrantup.com/v2/getting-started/up.html) + +```console +vagrant up +``` + +or boot VMs selectively: + +```console +vagrant up {soci|db2} +``` + +First time you run it, be patient as Vagrant downloads VM box and +provisions it installing all the necessary packages. + +* You can SSH into the machine + +```console +vagrant ssh {soci|db2} +``` + +* Run git commands can either from host or VM `soci` (once connected via SSH) + +```console +cd /vagrant # aka $SOCI_HOME +git pull origin master +``` + +* You can edit source code on both, on host or VM `soci`. +* For example, edit in your favourite editor on host machine, then build, run, test and debug on guest machine from command line. +* Alternatively, edit and build on host machine using your favourite IDE, then test and debug connecting to DBs on guest machines via network. + +* Build on VM `soci` + +```console +vagrant ssh soci +cd /vagrant # aka $SOCI_HOME +cd soci-build # aka $SOCI_BUILD +make +``` + +You can also execute the `build.h` script provided to run CMake and make + +```console +vagrant ssh soci +cd $SOCI_BUILD +/vagrant/scripts/vagrant/build.sh +``` + +* Debug, only on VM `soci`, for example, with gdb or remotely Visual Studio 2017. + +* [Teardown](https://docs.vagrantup.com/v2/getting-started/teardown.html) + +```console +vagrant {suspend|halt|destroy} {soci|db2} +``` + +Check Vagrant [command-line interface](https://docs.vagrantup.com/v2/cli/index.html) for complete list of commands. + +### Environment variables + +All variables available to the `vagrant` user on the VMs are defined in and sourced from `/vagrant/scripts/vagrant/common.env`: + +* `SOCI_HOME` where SOCI master is cloned (`/vagrant` on VM `soci`) +* `SOCI_BUILD` where CMake generates build configuration (`/home/vagrant/soci-build` on VM `soci`) +* `SOCI_HOST` network accessible VM `soci` hostname (`ping vmsoci.local`) +* `SOCI_USER` default database user and database name +* `SOCI_PASS` default database password for both, `SOCI_USER` and root/sysdba + of particular database. +* `SOCI_DB2_HOST` network accessible VM `db2` hostname (`ping vmdb2.local`) +* `SOCI_DB2_USER` admin username to DB2 instance. +* `SOCI_DB2_USER` admin password to DB2 instance. + +Note, those variables are also used by provision scripts to set up databases. + +## Troubleshooting + +* Analyze `vagrant up` output. +* On Windows, prefer `vagrant ssh` from inside MinGW Shell where `ssh.exe` is available or learn how to use Vagrant with PuTTY. +* If you modify any of `scripts/vagrant/*.sh` scripts, **ensure** they have unified end-of-line characters to `LF` only. Otherwise, provisioning steps may fail. diff --git a/include/private/soci-exchange-cast.h b/include/private/soci-exchange-cast.h index 6b28c5bf4e..9f5b6cfdda 100644 --- a/include/private/soci-exchange-cast.h +++ b/include/private/soci-exchange-cast.h @@ -9,6 +9,7 @@ #define SOCI_EXCHANGE_CAST_H_INCLUDED #include "soci/soci-backend.h" +#include "soci/type-wrappers.h" #include @@ -69,6 +70,18 @@ struct exchange_type_traits typedef std::tm value_type; }; +template <> +struct exchange_type_traits +{ + typedef long_string value_type; +}; + +template <> +struct exchange_type_traits +{ + typedef xml_type value_type; +}; + // exchange_type_traits not defined for x_statement, x_rowid and x_blob here. template diff --git a/include/private/soci-mktime.h b/include/private/soci-mktime.h index 1b383d8389..a354ee3cb3 100644 --- a/include/private/soci-mktime.h +++ b/include/private/soci-mktime.h @@ -39,6 +39,11 @@ mktime_from_ymdhms(tm& t, mktime(&t); } +// Helper function for parsing datetime values. +// +// Throws if the string in buf couldn't be parsed as a date or a time string. +SOCI_DECL void parse_std_tm(char const *buf, std::tm &t); + } // namespace details } // namespace soci diff --git a/include/soci/bind-values.h b/include/soci/bind-values.h index 3945636b3c..eee416e839 100644 --- a/include/soci/bind-values.h +++ b/include/soci/bind-values.h @@ -1,6 +1,7 @@ #ifndef SOCI_BIND_VALUES_H_INCLUDED #define SOCI_BIND_VALUES_H_INCLUDED +#include "soci/soci-platform.h" #include "exchange-traits.h" #include "into-type.h" #include "into.h" @@ -9,10 +10,10 @@ #include "use.h" -#ifdef HAVE_BOOST +#ifdef SOCI_HAVE_BOOST # include # include -#endif // HAVE_BOOST +#endif // SOCI_HAVE_BOOST #include namespace soci @@ -35,15 +36,15 @@ public: template void exchange(use_container const &uc) { -#ifdef HAVE_BOOST +#ifdef SOCI_HAVE_BOOST exchange_(uc, (typename boost::fusion::traits::is_sequence::type *)NULL); #else exchange_(uc, NULL); -#endif // HAVE_BOOST +#endif // SOCI_HAVE_BOOST } private: -#ifdef HAVE_BOOST +#ifdef SOCI_HAVE_BOOST template struct use_sequence { @@ -91,7 +92,7 @@ private: boost::fusion::for_each(uc.t, use_sequence(*this)); } -#endif // HAVE_BOOST +#endif // SOCI_HAVE_BOOST template void exchange_(use_container const &uc, ...) @@ -125,15 +126,15 @@ public: template void exchange(into_container const &ic) { -#ifdef HAVE_BOOST +#ifdef SOCI_HAVE_BOOST exchange_(ic, (typename boost::fusion::traits::is_sequence::type *)NULL); #else exchange_(ic, NULL); -#endif // HAVE_BOOST +#endif // SOCI_HAVE_BOOST } private: -#ifdef HAVE_BOOST +#ifdef SOCI_HAVE_BOOST template struct into_sequence { @@ -180,7 +181,7 @@ private: { boost::fusion::for_each(ic.t, into_sequence(*this)); } -#endif // HAVE_BOOST +#endif // SOCI_HAVE_BOOST template void exchange_(into_container const &ic, ...) diff --git a/include/soci/blob.h b/include/soci/blob.h index 50ac0692a1..054777124c 100644 --- a/include/soci/blob.h +++ b/include/soci/blob.h @@ -30,10 +30,24 @@ public: ~blob(); std::size_t get_len(); + + // offset is backend-specific std::size_t read(std::size_t offset, char * buf, std::size_t toRead); + + // offset starts from 0 + std::size_t read_from_start(char * buf, std::size_t toRead, + std::size_t offset = 0); + + // offset is backend-specific std::size_t write(std::size_t offset, char const * buf, std::size_t toWrite); + + // offset starts from 0 + std::size_t write_from_start(const char * buf, std::size_t toWrite, + std::size_t offset = 0); + std::size_t append(char const * buf, std::size_t toWrite); + void trim(std::size_t newLen); details::blob_backend * get_backend() { return backEnd_; } diff --git a/include/soci/boost-optional.h b/include/soci/boost-optional.h index 9e42611f4c..a9322b12cd 100644 --- a/include/soci/boost-optional.h +++ b/include/soci/boost-optional.h @@ -11,16 +11,11 @@ #include "soci/type-conversion-traits.h" // boost #include +#include namespace soci { -// tmp is uninitialized -#if defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ > 6) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#endif - // simple fall-back for boost::optional template struct type_conversion > @@ -36,7 +31,7 @@ struct type_conversion > } else { - T tmp; + T tmp = T(); type_conversion::from_base(in, ind, tmp); out = tmp; } @@ -58,8 +53,4 @@ struct type_conversion > } // namespace soci -#if defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ > 6) -#pragma GCC diagnostic pop -#endif - #endif // SOCI_BOOST_OPTIONAL_H_INCLUDED diff --git a/include/soci/callbacks.h b/include/soci/callbacks.h new file mode 100644 index 0000000000..85825ad1b6 --- /dev/null +++ b/include/soci/callbacks.h @@ -0,0 +1,47 @@ +// +// Copyright (C) 2015 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_CALLBACKS_H_INCLUDED +#define SOCI_CALLBACKS_H_INCLUDED + +namespace soci +{ + +class session; + +// Simple callback interface for reporting failover events. +// The meaning of each operation is intended to be portable, +// but the behaviour details and parameters can be backend-specific. +class SOCI_DECL failover_callback +{ +public: + + // Called when the failover operation has started, + // after discovering connectivity problems. + virtual void started() {} + + // Called after successful failover and creating a new connection; + // the sql parameter denotes the new connection and allows the user + // to replay any initial sequence of commands (like session configuration). + virtual void finished(session & /* sql */) {} + + // Called when the attempt to reconnect failed, + // if the user code sets the retry parameter to true, + // then new connection will be attempted; + // the newTarget connection string is a hint that can be ignored + // by external means. + virtual void failed(bool & /* out */ /* retry */, + std::string & /* out */ /* newTarget */) {} + + // Called when there was a failure that prevents further failover attempts. + virtual void aborted() {} +}; + +} // namespace soci + +#endif // SOCI_CALLBACKS_H_INCLUDED + diff --git a/include/soci/column-info.h b/include/soci/column-info.h new file mode 100644 index 0000000000..15e1529d29 --- /dev/null +++ b/include/soci/column-info.h @@ -0,0 +1,123 @@ +// +// Copyright (C) 2016 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_COLUMN_INFO_H_INCLUDED +#define SOCI_COLUMN_INFO_H_INCLUDED + +#include "soci/soci-backend.h" +#include "soci/type-conversion.h" +#include "soci/values.h" + +namespace soci +{ + +struct SOCI_DECL column_info +{ + std::string name; + data_type type; + std::size_t length; // meaningful for text columns only + std::size_t precision; + std::size_t scale; + bool nullable; +}; + +template <> +struct type_conversion +{ + typedef values base_type; + + static std::size_t get_numeric_value(const values & v, + const std::string & field_name) + { + data_type dt = v.get_properties(field_name).get_data_type(); + switch (dt) + { + case dt_double: + return static_cast( + v.get(field_name, 0.0)); + case dt_integer: + return static_cast( + v.get(field_name, 0)); + case dt_long_long: + return static_cast( + v.get(field_name, 0ll)); + case dt_unsigned_long_long: + return static_cast( + v.get(field_name, 0ull)); + break; + default: + return 0u; + } + } + + static void from_base(values const & v, indicator /* ind */, column_info & ci) + { + ci.name = v.get("COLUMN_NAME"); + + ci.length = get_numeric_value(v, "CHARACTER_MAXIMUM_LENGTH"); + ci.precision = get_numeric_value(v, "NUMERIC_PRECISION"); + ci.scale = get_numeric_value(v, "NUMERIC_SCALE"); + + const std::string & type_name = v.get("DATA_TYPE"); + if (type_name == "text" || type_name == "TEXT" || + type_name == "clob" || type_name == "CLOB" || + type_name.find("char") != std::string::npos || + type_name.find("CHAR") != std::string::npos) + { + ci.type = dt_string; + } + else if (type_name == "integer" || type_name == "INTEGER") + { + ci.type = dt_integer; + } + else if (type_name.find("number") != std::string::npos || + type_name.find("NUMBER") != std::string::npos || + type_name.find("numeric") != std::string::npos || + type_name.find("NUMERIC") != std::string::npos) + { + if (ci.scale != 0) + { + ci.type = dt_double; + } + else + { + ci.type = dt_integer; + } + } + else if (type_name.find("time") != std::string::npos || + type_name.find("TIME") != std::string::npos || + type_name.find("date") != std::string::npos || + type_name.find("DATE") != std::string::npos) + { + ci.type = dt_date; + } + else if (type_name.find("blob") != std::string::npos || + type_name.find("BLOB") != std::string::npos || + type_name.find("oid") != std::string::npos || + type_name.find("OID") != std::string::npos) + { + ci.type = dt_blob; + } + else if (type_name.find("xml") != std::string::npos || + type_name.find("XML") != std::string::npos) + { + ci.type = dt_xml; + } + else + { + // this seems to be a safe default + ci.type = dt_string; + } + + const std::string & nullable_s = v.get("IS_NULLABLE"); + ci.nullable = (nullable_s == "YES"); + } +}; + +} // namespace soci + +#endif // SOCI_COLUMN_INFO_H_INCLUDED diff --git a/include/soci/connection-parameters.h b/include/soci/connection-parameters.h index ba0eef936d..6955a67c8d 100644 --- a/include/soci/connection-parameters.h +++ b/include/soci/connection-parameters.h @@ -32,6 +32,7 @@ public: // Retrieve the backend and the connection strings specified in the ctor. backend_factory const * get_factory() const { return factory_; } + void set_connect_string(const std::string & connectString) { connectString_ = connectString; } std::string const & get_connect_string() const { return connectString_; } // Set the value of the given option, overwriting any previous value. diff --git a/include/soci/connection-pool.h b/include/soci/connection-pool.h index ae38b29235..00215e2470 100644 --- a/include/soci/connection-pool.h +++ b/include/soci/connection-pool.h @@ -32,6 +32,8 @@ public: private: struct connection_pool_impl; connection_pool_impl * pimpl_; + + SOCI_NOT_COPYABLE(connection_pool) }; } diff --git a/include/soci/db2/soci-db2.h b/include/soci/db2/soci-db2.h index 3043096bce..9e61eaa27e 100644 --- a/include/soci/db2/soci-db2.h +++ b/include/soci/db2/soci-db2.h @@ -61,10 +61,10 @@ namespace soci static const std::size_t maxBuffer = 1024 * 1024 * 1024; //CLI limit is about 3 GB, but 1GB should be enough -class db2_soci_error : public soci_error { +class SOCI_DB2_DECL db2_soci_error : public soci_error { public: db2_soci_error(std::string const & msg, SQLRETURN rc) : soci_error(msg),errorCode(rc) {}; - ~db2_soci_error() throw() { }; + ~db2_soci_error() throw() SOCI_OVERRIDE { }; //We have to extract error information before exception throwing, cause CLI handles could be broken at the construction time static const std::string sqlState(std::string const & msg,const SQLSMALLINT htype,const SQLHANDLE hndl); @@ -72,6 +72,12 @@ public: SQLRETURN errorCode; }; +// Option allowing to specify the "driver completion" parameter of +// SQLDriverConnect(). Its possible values are the same as the allowed values +// for this parameter in the official DB2 CLI, i.e. one of SQL_DRIVER_XXX +// (in string form as all options are strings currently). +extern SOCI_DB2_DECL char const * db2_option_driver_complete; + struct db2_statement_backend; struct SOCI_DB2_DECL db2_standard_into_type_backend : details::standard_into_type_backend @@ -80,12 +86,12 @@ struct SOCI_DB2_DECL db2_standard_into_type_backend : details::standard_into_typ : statement_(st),buf(NULL) {} - void define_by_pos(int& position, void* data, details::exchange_type type); + void define_by_pos(int& position, void* data, details::exchange_type type) SOCI_OVERRIDE; - void pre_fetch(); - void post_fetch(bool gotData, bool calledFromFetch, indicator* ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, bool calledFromFetch, indicator* ind) SOCI_OVERRIDE; - void clean_up(); + void clean_up() SOCI_OVERRIDE; db2_statement_backend& statement_; @@ -103,15 +109,15 @@ struct SOCI_DB2_DECL db2_vector_into_type_backend : details::vector_into_type_ba : statement_(st),buf(NULL) {} - void define_by_pos(int& position, void* data, details::exchange_type type); + void define_by_pos(int& position, void* data, details::exchange_type type) SOCI_OVERRIDE; - void pre_fetch(); - void post_fetch(bool gotData, indicator* ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, indicator* ind) SOCI_OVERRIDE; - void resize(std::size_t sz); - std::size_t size(); + void resize(std::size_t sz) SOCI_OVERRIDE; + std::size_t size() SOCI_OVERRIDE; - void clean_up(); + void clean_up() SOCI_OVERRIDE; db2_statement_backend& statement_; @@ -133,13 +139,13 @@ struct SOCI_DB2_DECL db2_standard_use_type_backend : details::standard_use_type_ : statement_(st),buf(NULL),ind(0) {} - void bind_by_pos(int& position, void* data, details::exchange_type type, bool readOnly); - void bind_by_name(std::string const& name, void* data, details::exchange_type type, bool readOnly); + void bind_by_pos(int& position, void* data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; + void bind_by_name(std::string const& name, void* data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; - void pre_use(indicator const* ind); - void post_use(bool gotData, indicator* ind); + void pre_use(indicator const* ind) SOCI_OVERRIDE; + void post_use(bool gotData, indicator* ind) SOCI_OVERRIDE; - void clean_up(); + void clean_up() SOCI_OVERRIDE; db2_statement_backend& statement_; @@ -158,14 +164,14 @@ struct SOCI_DB2_DECL db2_vector_use_type_backend : details::vector_use_type_back db2_vector_use_type_backend(db2_statement_backend &st) : statement_(st),buf(NULL) {} - void bind_by_pos(int& position, void* data, details::exchange_type type); - void bind_by_name(std::string const& name, void* data, details::exchange_type type); + void bind_by_pos(int& position, void* data, details::exchange_type type) SOCI_OVERRIDE; + void bind_by_name(std::string const& name, void* data, details::exchange_type type) SOCI_OVERRIDE; - void pre_use(indicator const* ind); + void pre_use(indicator const* ind) SOCI_OVERRIDE; - std::size_t size(); + std::size_t size() SOCI_OVERRIDE; - void clean_up(); + void clean_up() SOCI_OVERRIDE; db2_statement_backend& statement_; @@ -186,27 +192,27 @@ struct SOCI_DB2_DECL db2_statement_backend : details::statement_backend { db2_statement_backend(db2_session_backend &session); - void alloc(); - void clean_up(); - void prepare(std::string const& query, details::statement_type eType); + void alloc() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + void prepare(std::string const& query, details::statement_type eType) SOCI_OVERRIDE; - exec_fetch_result execute(int number); - exec_fetch_result fetch(int number); + exec_fetch_result execute(int number) SOCI_OVERRIDE; + exec_fetch_result fetch(int number) SOCI_OVERRIDE; - long long get_affected_rows(); - int get_number_of_rows(); - std::string get_parameter_name(int index) const; + long long get_affected_rows() SOCI_OVERRIDE; + int get_number_of_rows() SOCI_OVERRIDE; + std::string get_parameter_name(int index) const SOCI_OVERRIDE; - std::string rewrite_for_procedure_call(std::string const& query); + std::string rewrite_for_procedure_call(std::string const& query) SOCI_OVERRIDE; - int prepare_for_describe(); - void describe_column(int colNum, data_type& dtype, std::string& columnName); + int prepare_for_describe() SOCI_OVERRIDE; + void describe_column(int colNum, data_type& dtype, std::string& columnName) SOCI_OVERRIDE; size_t column_size(int col); - db2_standard_into_type_backend* make_into_type_backend(); - db2_standard_use_type_backend* make_use_type_backend(); - db2_vector_into_type_backend* make_vector_into_type_backend(); - db2_vector_use_type_backend* make_vector_use_type_backend(); + db2_standard_into_type_backend* make_into_type_backend() SOCI_OVERRIDE; + db2_standard_use_type_backend* make_use_type_backend() SOCI_OVERRIDE; + db2_vector_into_type_backend* make_vector_into_type_backend() SOCI_OVERRIDE; + db2_vector_use_type_backend* make_vector_use_type_backend() SOCI_OVERRIDE; db2_session_backend& session_; @@ -222,20 +228,20 @@ struct db2_rowid_backend : details::rowid_backend { db2_rowid_backend(db2_session_backend &session); - ~db2_rowid_backend(); + ~db2_rowid_backend() SOCI_OVERRIDE; }; struct db2_blob_backend : details::blob_backend { db2_blob_backend(db2_session_backend& session); - ~db2_blob_backend(); + ~db2_blob_backend() SOCI_OVERRIDE; - std::size_t get_len(); - std::size_t read(std::size_t offset, char* buf, std::size_t toRead); - std::size_t write(std::size_t offset, char const* buf, std::size_t toWrite); - std::size_t append(char const* buf, std::size_t toWrite); - void trim(std::size_t newLen); + std::size_t get_len() SOCI_OVERRIDE; + std::size_t read(std::size_t offset, char* buf, std::size_t toRead) SOCI_OVERRIDE; + std::size_t write(std::size_t offset, char const* buf, std::size_t toWrite) SOCI_OVERRIDE; + std::size_t append(char const* buf, std::size_t toWrite) SOCI_OVERRIDE; + void trim(std::size_t newLen) SOCI_OVERRIDE; db2_session_backend& session_; }; @@ -244,26 +250,26 @@ struct db2_session_backend : details::session_backend { db2_session_backend(connection_parameters const& parameters); - ~db2_session_backend(); + ~db2_session_backend() SOCI_OVERRIDE; - void begin(); - void commit(); - void rollback(); + void begin() SOCI_OVERRIDE; + void commit() SOCI_OVERRIDE; + void rollback() SOCI_OVERRIDE; - std::string get_backend_name() const { return "DB2"; } + std::string get_dummy_from_table() const SOCI_OVERRIDE { return "sysibm.sysdummy1"; } + + std::string get_backend_name() const SOCI_OVERRIDE { return "DB2"; } void clean_up(); - db2_statement_backend* make_statement_backend(); - db2_rowid_backend* make_rowid_backend(); - db2_blob_backend* make_blob_backend(); + db2_statement_backend* make_statement_backend() SOCI_OVERRIDE; + db2_rowid_backend* make_rowid_backend() SOCI_OVERRIDE; + db2_blob_backend* make_blob_backend() SOCI_OVERRIDE; void parseConnectString(std::string const &); void parseKeyVal(std::string const &); - std::string dsn; - std::string username; - std::string password; + std::string connection_string_; bool autocommit; bool in_transaction; @@ -275,7 +281,7 @@ struct SOCI_DB2_DECL db2_backend_factory : backend_factory { db2_backend_factory() {} db2_session_backend* make_session( - connection_parameters const & parameters) const; + connection_parameters const & parameters) const SOCI_OVERRIDE; }; extern SOCI_DB2_DECL db2_backend_factory const db2; diff --git a/include/soci/empty/soci-empty.h b/include/soci/empty/soci-empty.h index 2b3f2215eb..a428872bc7 100644 --- a/include/soci/empty/soci-empty.h +++ b/include/soci/empty/soci-empty.h @@ -39,12 +39,12 @@ struct SOCI_EMPTY_DECL empty_standard_into_type_backend : details::standard_into : statement_(st) {} - void define_by_pos(int& position, void* data, details::exchange_type type); + void define_by_pos(int& position, void* data, details::exchange_type type) SOCI_OVERRIDE; - void pre_fetch(); - void post_fetch(bool gotData, bool calledFromFetch, indicator* ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, bool calledFromFetch, indicator* ind) SOCI_OVERRIDE; - void clean_up(); + void clean_up() SOCI_OVERRIDE; empty_statement_backend& statement_; }; @@ -55,15 +55,15 @@ struct SOCI_EMPTY_DECL empty_vector_into_type_backend : details::vector_into_typ : statement_(st) {} - void define_by_pos(int& position, void* data, details::exchange_type type); + void define_by_pos(int& position, void* data, details::exchange_type type) SOCI_OVERRIDE; - void pre_fetch(); - void post_fetch(bool gotData, indicator* ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, indicator* ind) SOCI_OVERRIDE; - void resize(std::size_t sz); - std::size_t size(); + void resize(std::size_t sz) SOCI_OVERRIDE; + std::size_t size() SOCI_OVERRIDE; - void clean_up(); + void clean_up() SOCI_OVERRIDE; empty_statement_backend& statement_; }; @@ -74,13 +74,13 @@ struct SOCI_EMPTY_DECL empty_standard_use_type_backend : details::standard_use_t : statement_(st) {} - void bind_by_pos(int& position, void* data, details::exchange_type type, bool readOnly); - void bind_by_name(std::string const& name, void* data, details::exchange_type type, bool readOnly); + void bind_by_pos(int& position, void* data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; + void bind_by_name(std::string const& name, void* data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; - void pre_use(indicator const* ind); - void post_use(bool gotData, indicator* ind); + void pre_use(indicator const* ind) SOCI_OVERRIDE; + void post_use(bool gotData, indicator* ind) SOCI_OVERRIDE; - void clean_up(); + void clean_up() SOCI_OVERRIDE; empty_statement_backend& statement_; }; @@ -90,14 +90,14 @@ struct SOCI_EMPTY_DECL empty_vector_use_type_backend : details::vector_use_type_ empty_vector_use_type_backend(empty_statement_backend &st) : statement_(st) {} - void bind_by_pos(int& position, void* data, details::exchange_type type); - void bind_by_name(std::string const& name, void* data, details::exchange_type type); + void bind_by_pos(int& position, void* data, details::exchange_type type) SOCI_OVERRIDE; + void bind_by_name(std::string const& name, void* data, details::exchange_type type) SOCI_OVERRIDE; - void pre_use(indicator const* ind); + void pre_use(indicator const* ind) SOCI_OVERRIDE; - std::size_t size(); + std::size_t size() SOCI_OVERRIDE; - void clean_up(); + void clean_up() SOCI_OVERRIDE; empty_statement_backend& statement_; }; @@ -107,26 +107,26 @@ struct SOCI_EMPTY_DECL empty_statement_backend : details::statement_backend { empty_statement_backend(empty_session_backend &session); - void alloc(); - void clean_up(); - void prepare(std::string const& query, details::statement_type eType); + void alloc() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + void prepare(std::string const& query, details::statement_type eType) SOCI_OVERRIDE; - exec_fetch_result execute(int number); - exec_fetch_result fetch(int number); + exec_fetch_result execute(int number) SOCI_OVERRIDE; + exec_fetch_result fetch(int number) SOCI_OVERRIDE; - long long get_affected_rows(); - int get_number_of_rows(); - std::string get_parameter_name(int index) const; + long long get_affected_rows() SOCI_OVERRIDE; + int get_number_of_rows() SOCI_OVERRIDE; + std::string get_parameter_name(int index) const SOCI_OVERRIDE; - std::string rewrite_for_procedure_call(std::string const& query); + std::string rewrite_for_procedure_call(std::string const& query) SOCI_OVERRIDE; - int prepare_for_describe(); - void describe_column(int colNum, data_type& dtype, std::string& columnName); + int prepare_for_describe() SOCI_OVERRIDE; + void describe_column(int colNum, data_type& dtype, std::string& columnName) SOCI_OVERRIDE; - empty_standard_into_type_backend* make_into_type_backend(); - empty_standard_use_type_backend* make_use_type_backend(); - empty_vector_into_type_backend* make_vector_into_type_backend(); - empty_vector_use_type_backend* make_vector_use_type_backend(); + empty_standard_into_type_backend* make_into_type_backend() SOCI_OVERRIDE; + empty_standard_use_type_backend* make_use_type_backend() SOCI_OVERRIDE; + empty_vector_into_type_backend* make_vector_into_type_backend() SOCI_OVERRIDE; + empty_vector_use_type_backend* make_vector_use_type_backend() SOCI_OVERRIDE; empty_session_backend& session_; }; @@ -135,20 +135,20 @@ struct empty_rowid_backend : details::rowid_backend { empty_rowid_backend(empty_session_backend &session); - ~empty_rowid_backend(); + ~empty_rowid_backend() SOCI_OVERRIDE; }; struct empty_blob_backend : details::blob_backend { empty_blob_backend(empty_session_backend& session); - ~empty_blob_backend(); + ~empty_blob_backend() SOCI_OVERRIDE; - std::size_t get_len(); - std::size_t read(std::size_t offset, char* buf, std::size_t toRead); - std::size_t write(std::size_t offset, char const* buf, std::size_t toWrite); - std::size_t append(char const* buf, std::size_t toWrite); - void trim(std::size_t newLen); + std::size_t get_len() SOCI_OVERRIDE; + std::size_t read(std::size_t offset, char* buf, std::size_t toRead) SOCI_OVERRIDE; + std::size_t write(std::size_t offset, char const* buf, std::size_t toWrite) SOCI_OVERRIDE; + std::size_t append(char const* buf, std::size_t toWrite) SOCI_OVERRIDE; + void trim(std::size_t newLen) SOCI_OVERRIDE; empty_session_backend& session_; }; @@ -157,25 +157,27 @@ struct empty_session_backend : details::session_backend { empty_session_backend(connection_parameters const& parameters); - ~empty_session_backend(); + ~empty_session_backend() SOCI_OVERRIDE; - void begin(); - void commit(); - void rollback(); + void begin() SOCI_OVERRIDE; + void commit() SOCI_OVERRIDE; + void rollback() SOCI_OVERRIDE; - std::string get_backend_name() const { return "empty"; } + std::string get_dummy_from_table() const SOCI_OVERRIDE { return std::string(); } + + std::string get_backend_name() const SOCI_OVERRIDE { return "empty"; } void clean_up(); - empty_statement_backend* make_statement_backend(); - empty_rowid_backend* make_rowid_backend(); - empty_blob_backend* make_blob_backend(); + empty_statement_backend* make_statement_backend() SOCI_OVERRIDE; + empty_rowid_backend* make_rowid_backend() SOCI_OVERRIDE; + empty_blob_backend* make_blob_backend() SOCI_OVERRIDE; }; struct SOCI_EMPTY_DECL empty_backend_factory : backend_factory { empty_backend_factory() {} - empty_session_backend* make_session(connection_parameters const& parameters) const; + empty_session_backend* make_session(connection_parameters const& parameters) const SOCI_OVERRIDE; }; extern SOCI_EMPTY_DECL empty_backend_factory const empty; diff --git a/include/soci/error.h b/include/soci/error.h index 4036095545..1de84dbf44 100644 --- a/include/soci/error.h +++ b/include/soci/error.h @@ -25,14 +25,14 @@ public: soci_error(soci_error const& e); soci_error& operator=(soci_error const& e); - virtual ~soci_error() throw(); + ~soci_error() throw() SOCI_OVERRIDE; // Returns just the error message itself, without the context. std::string get_error_message() const; // Returns the full error message combining the message given to the ctor // with all the available context records. - virtual char const* what() const throw(); + char const* what() const throw() SOCI_OVERRIDE; // This is used only by SOCI itself to provide more information about the // exception as it bubbles up. It can be called multiple times, with the @@ -40,6 +40,22 @@ public: // highest level context. void add_context(std::string const& context); + // Basic error classes. + enum error_category + { + connection_error, + invalid_statement, + no_privilege, + no_data, + constraint_violation, + unknown_transaction_state, + system_error, + unknown + }; + + // Basic error classification support + virtual error_category get_error_category() const { return unknown; } + private: // Optional extra information (currently just the context data). class soci_error_extra_info* info_; diff --git a/include/soci/exchange-traits.h b/include/soci/exchange-traits.h index 8bb9bf1356..77f5586980 100644 --- a/include/soci/exchange-traits.h +++ b/include/soci/exchange-traits.h @@ -10,6 +10,7 @@ #include "soci/type-conversion-traits.h" #include "soci/soci-backend.h" +#include "soci/type-wrappers.h" // std #include #include @@ -131,6 +132,22 @@ struct exchange_traits > enum { x_type = exchange_traits::x_type }; }; +// handling of wrapper types + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_xmltype }; +}; + +template <> +struct exchange_traits +{ + typedef basic_type_tag type_family; + enum { x_type = x_longstring }; +}; + } // namespace details } // namespace soci diff --git a/include/soci/firebird/soci-firebird.h b/include/soci/firebird/soci-firebird.h index bcb02db0ec..9b0f0912f1 100644 --- a/include/soci/firebird/soci-firebird.h +++ b/include/soci/firebird/soci-firebird.h @@ -49,7 +49,7 @@ public: firebird_soci_error(std::string const & msg, ISC_STATUS const * status = 0); - ~firebird_soci_error() throw() {}; + ~firebird_soci_error() throw() SOCI_OVERRIDE {}; std::vector status_; }; @@ -59,21 +59,22 @@ enum BuffersType eStandard, eVector }; +struct firebird_blob_backend; struct firebird_statement_backend; struct firebird_standard_into_type_backend : details::standard_into_type_backend { firebird_standard_into_type_backend(firebird_statement_backend &st) - : statement_(st), buf_(NULL) + : statement_(st), data_(NULL), type_(), position_(0), buf_(NULL), indISCHolder_(0) {} - virtual void define_by_pos(int &position, - void *data, details::exchange_type type); + void define_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, bool calledFromFetch, - indicator *ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; firebird_statement_backend &statement_; virtual void exchangeData(); @@ -84,24 +85,29 @@ struct firebird_standard_into_type_backend : details::standard_into_type_backend char *buf_; short indISCHolder_; + +private: + // Copy contents of a BLOB (represented by its id) in buf_ into the given + // string. + void copy_from_blob(std::string& out); }; struct firebird_vector_into_type_backend : details::vector_into_type_backend { firebird_vector_into_type_backend(firebird_statement_backend &st) - : statement_(st), buf_(NULL) + : statement_(st), data_(NULL), type_(), position_(0), buf_(NULL), indISCHolder_(0) {} - virtual void define_by_pos(int &position, - void *data, details::exchange_type type); + void define_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, indicator *ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, indicator *ind) SOCI_OVERRIDE; - virtual void resize(std::size_t sz); - virtual std::size_t size(); + void resize(std::size_t sz) SOCI_OVERRIDE; + std::size_t size() SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; firebird_statement_backend &statement_; virtual void exchangeData(std::size_t row); @@ -117,18 +123,19 @@ struct firebird_vector_into_type_backend : details::vector_into_type_backend struct firebird_standard_use_type_backend : details::standard_use_type_backend { firebird_standard_use_type_backend(firebird_statement_backend &st) - : statement_(st), buf_(NULL), indISCHolder_(0) + : statement_(st), data_(NULL), type_(), position_(0), buf_(NULL), indISCHolder_(0), + blob_(NULL) {} - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type, bool readOnly); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type, bool readOnly); + void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; + void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; - virtual void pre_use(indicator const *ind); - virtual void post_use(bool gotData, indicator *ind); + void pre_use(indicator const *ind) SOCI_OVERRIDE; + void post_use(bool gotData, indicator *ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; firebird_statement_backend &statement_; virtual void exchangeData(); @@ -139,24 +146,32 @@ struct firebird_standard_use_type_backend : details::standard_use_type_backend char *buf_; short indISCHolder_; + +private: + // Allocate a temporary blob, fill it with the data from the provided + // string and copy its ID into buf_. + void copy_to_blob(const std::string& in); + + // This is used for types mapping to CLOB. + firebird_blob_backend* blob_; }; struct firebird_vector_use_type_backend : details::vector_use_type_backend { firebird_vector_use_type_backend(firebird_statement_backend &st) - : statement_(st), inds_(NULL), buf_(NULL), indISCHolder_(0) + : statement_(st), data_(NULL), type_(), position_(0), buf_(NULL), indISCHolder_(0) {} - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type); + void bind_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; + void bind_by_name(std::string const &name, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_use(indicator const *ind); + void pre_use(indicator const *ind) SOCI_OVERRIDE; - virtual std::size_t size(); + std::size_t size() SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; firebird_statement_backend &statement_; virtual void exchangeData(std::size_t row); @@ -175,28 +190,28 @@ struct firebird_statement_backend : details::statement_backend { firebird_statement_backend(firebird_session_backend &session); - virtual void alloc(); - virtual void clean_up(); - virtual void prepare(std::string const &query, - details::statement_type eType); + void alloc() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + void prepare(std::string const &query, + details::statement_type eType) SOCI_OVERRIDE; - virtual exec_fetch_result execute(int number); - virtual exec_fetch_result fetch(int number); + exec_fetch_result execute(int number) SOCI_OVERRIDE; + exec_fetch_result fetch(int number) SOCI_OVERRIDE; - virtual long long get_affected_rows(); - virtual int get_number_of_rows(); - virtual std::string get_parameter_name(int index) const; + long long get_affected_rows() SOCI_OVERRIDE; + int get_number_of_rows() SOCI_OVERRIDE; + std::string get_parameter_name(int index) const SOCI_OVERRIDE; - virtual std::string rewrite_for_procedure_call(std::string const &query); + std::string rewrite_for_procedure_call(std::string const &query) SOCI_OVERRIDE; - virtual int prepare_for_describe(); - virtual void describe_column(int colNum, data_type &dtype, - std::string &columnName); + int prepare_for_describe() SOCI_OVERRIDE; + void describe_column(int colNum, data_type &dtype, + std::string &columnName) SOCI_OVERRIDE; - virtual firebird_standard_into_type_backend * make_into_type_backend(); - virtual firebird_standard_use_type_backend * make_use_type_backend(); - virtual firebird_vector_into_type_backend * make_vector_into_type_backend(); - virtual firebird_vector_use_type_backend * make_vector_use_type_backend(); + firebird_standard_into_type_backend * make_into_type_backend() SOCI_OVERRIDE; + firebird_standard_use_type_backend * make_use_type_backend() SOCI_OVERRIDE; + firebird_vector_into_type_backend * make_vector_into_type_backend() SOCI_OVERRIDE; + firebird_vector_use_type_backend * make_vector_use_type_backend() SOCI_OVERRIDE; firebird_session_backend &session_; @@ -242,15 +257,15 @@ struct firebird_blob_backend : details::blob_backend { firebird_blob_backend(firebird_session_backend &session); - ~firebird_blob_backend(); + ~firebird_blob_backend() SOCI_OVERRIDE; - virtual std::size_t get_len(); - virtual std::size_t read(std::size_t offset, char *buf, - std::size_t toRead); - virtual std::size_t write(std::size_t offset, char const *buf, - std::size_t toWrite); - virtual std::size_t append(char const *buf, std::size_t toWrite); - virtual void trim(std::size_t newLen); + std::size_t get_len() SOCI_OVERRIDE; + std::size_t read(std::size_t offset, char *buf, + std::size_t toRead) SOCI_OVERRIDE; + std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite) SOCI_OVERRIDE; + std::size_t append(char const *buf, std::size_t toWrite) SOCI_OVERRIDE; + void trim(std::size_t newLen) SOCI_OVERRIDE; firebird_session_backend &session_; @@ -293,22 +308,24 @@ struct firebird_session_backend : details::session_backend { firebird_session_backend(connection_parameters const & parameters); - ~firebird_session_backend(); + ~firebird_session_backend() SOCI_OVERRIDE; - virtual void begin(); - virtual void commit(); - virtual void rollback(); + void begin() SOCI_OVERRIDE; + void commit() SOCI_OVERRIDE; + void rollback() SOCI_OVERRIDE; - virtual bool get_next_sequence_value(session & s, - std::string const & sequence, long & value); + bool get_next_sequence_value(session & s, + std::string const & sequence, long & value) SOCI_OVERRIDE; - virtual std::string get_backend_name() const { return "firebird"; } + std::string get_dummy_from_table() const SOCI_OVERRIDE { return "rdb$database"; } + + std::string get_backend_name() const SOCI_OVERRIDE { return "firebird"; } void cleanUp(); - virtual firebird_statement_backend * make_statement_backend(); - virtual details::rowid_backend* make_rowid_backend(); - virtual firebird_blob_backend * make_blob_backend(); + firebird_statement_backend * make_statement_backend() SOCI_OVERRIDE; + details::rowid_backend* make_rowid_backend() SOCI_OVERRIDE; + firebird_blob_backend * make_blob_backend() SOCI_OVERRIDE; bool get_option_decimals_as_strings() { return decimals_as_strings_; } @@ -328,8 +345,8 @@ private: struct firebird_backend_factory : backend_factory { firebird_backend_factory() {} - virtual firebird_session_backend * make_session( - connection_parameters const & parameters) const; + firebird_session_backend * make_session( + connection_parameters const & parameters) const SOCI_OVERRIDE; }; extern SOCI_FIREBIRD_DECL firebird_backend_factory const firebird; diff --git a/include/soci/into-type.h b/include/soci/into-type.h index 43fbee9a51..1573eabc53 100644 --- a/include/soci/into-type.h +++ b/include/soci/into-type.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -36,6 +36,7 @@ public: virtual ~into_type_base() {} virtual void define(statement_impl & st, int & position) = 0; + virtual void pre_exec(int num) = 0; virtual void pre_fetch() = 0; virtual void post_fetch(bool gotData, bool calledFromFetch) = 0; virtual void clean_up() = 0; @@ -56,17 +57,18 @@ public: standard_into_type(void * data, exchange_type type, indicator & ind) : data_(data), type_(type), ind_(&ind), backEnd_(NULL) {} - virtual ~standard_into_type(); + ~standard_into_type() SOCI_OVERRIDE; protected: - virtual void post_fetch(bool gotData, bool calledFromFetch); + void post_fetch(bool gotData, bool calledFromFetch) SOCI_OVERRIDE; private: - virtual void define(statement_impl & st, int & position); - virtual void pre_fetch(); - virtual void clean_up(); + void define(statement_impl & st, int & position) SOCI_OVERRIDE; + void pre_exec(int num) SOCI_OVERRIDE; + void pre_fetch() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; - virtual std::size_t size() const { return 1; } + std::size_t size() const SOCI_OVERRIDE { return 1; } // conversion hook (from base type to arbitrary user type) virtual void convert_from_base() {} @@ -83,27 +85,42 @@ class SOCI_DECL vector_into_type : public into_type_base { public: vector_into_type(void * data, exchange_type type) - : data_(data), type_(type), indVec_(NULL), backEnd_(NULL) {} + : data_(data), type_(type), indVec_(NULL), + begin_(0), end_(NULL), backEnd_(NULL) {} + + vector_into_type(void * data, exchange_type type, + std::size_t begin, std::size_t * end) + : data_(data), type_(type), indVec_(NULL), + begin_(begin), end_(end), backEnd_(NULL) {} vector_into_type(void * data, exchange_type type, std::vector & ind) - : data_(data), type_(type), indVec_(&ind), backEnd_(NULL) {} + : data_(data), type_(type), indVec_(&ind), + begin_(0), end_(NULL), backEnd_(NULL) {} - ~vector_into_type(); + vector_into_type(void * data, exchange_type type, + std::vector & ind, + std::size_t begin, std::size_t * end) + : data_(data), type_(type), indVec_(&ind), + begin_(begin), end_(end), backEnd_(NULL) {} + + ~vector_into_type() SOCI_OVERRIDE; protected: - virtual void post_fetch(bool gotData, bool calledFromFetch); + void post_fetch(bool gotData, bool calledFromFetch) SOCI_OVERRIDE; -private: - virtual void define(statement_impl & st, int & position); - virtual void pre_fetch(); - virtual void clean_up(); - virtual void resize(std::size_t sz); - virtual std::size_t size() const; + void define(statement_impl & st, int & position) SOCI_OVERRIDE; + void pre_exec(int num) SOCI_OVERRIDE; + void pre_fetch() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + void resize(std::size_t sz) SOCI_OVERRIDE; + std::size_t size() const SOCI_OVERRIDE; void * data_; exchange_type type_; std::vector * indVec_; + std::size_t begin_; + std::size_t * end_; vector_into_type_backend * backEnd_; @@ -132,9 +149,21 @@ public: into_type(std::vector & v) : vector_into_type(&v, static_cast(exchange_traits::x_type)) {} + + into_type(std::vector & v, std::size_t begin, std::size_t * end) + : vector_into_type(&v, + static_cast(exchange_traits::x_type), + begin, end) {} + into_type(std::vector & v, std::vector & ind) : vector_into_type(&v, static_cast(exchange_traits::x_type), ind) {} + + into_type(std::vector & v, std::vector & ind, + std::size_t begin, std::size_t * end) + : vector_into_type(&v, + static_cast(exchange_traits::x_type), ind, + begin, end) {} }; // helper dispatchers for basic types @@ -157,6 +186,20 @@ into_type_ptr do_into(T & t, std::vector & ind, basic_type_tag) return into_type_ptr(new into_type(t, ind)); } +template +into_type_ptr do_into(std::vector & t, + std::size_t begin, std::size_t * end, basic_type_tag) +{ + return into_type_ptr(new into_type >(t, begin, end)); +} + +template +into_type_ptr do_into(std::vector & t, std::vector & ind, + std::size_t begin, std::size_t * end, basic_type_tag) +{ + return into_type_ptr(new into_type >(t, ind, begin, end)); +} + } // namespace details } // namespace soci diff --git a/include/soci/into.h b/include/soci/into.h index be85f58125..a341ede616 100644 --- a/include/soci/into.h +++ b/include/soci/into.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -67,6 +67,24 @@ details::into_type_ptr into(T & t, std::size_t bufSize) return details::into_type_ptr(new details::into_type(t, bufSize)); } +// vectors with index ranges + +template +details::into_type_ptr into(std::vector & t, + std::size_t begin, std::size_t & end) +{ + return details::do_into(t, begin, &end, + typename details::exchange_traits >::type_family()); +} + +template +details::into_type_ptr into(std::vector & t, std::vector & ind, + std::size_t begin, std::size_t & end) +{ + return details::do_into(t, ind, begin, &end, + typename details::exchange_traits >::type_family()); +} + } // namespace soci #endif // SOCI_INTO_H_INCLUDED diff --git a/include/soci/mysql/soci-mysql.h b/include/soci/mysql/soci-mysql.h index e51fb0f3ad..01d7375263 100644 --- a/include/soci/mysql/soci-mysql.h +++ b/include/soci/mysql/soci-mysql.h @@ -35,7 +35,7 @@ namespace soci { -class mysql_soci_error : public soci_error +class SOCI_MYSQL_DECL mysql_soci_error : public soci_error { public: mysql_soci_error(std::string const & msg, int errNum) @@ -50,14 +50,14 @@ struct mysql_standard_into_type_backend : details::standard_into_type_backend mysql_standard_into_type_backend(mysql_statement_backend &st) : statement_(st) {} - virtual void define_by_pos(int &position, - void *data, details::exchange_type type); + void define_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, bool calledFromFetch, - indicator *ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; mysql_statement_backend &statement_; @@ -71,16 +71,16 @@ struct mysql_vector_into_type_backend : details::vector_into_type_backend mysql_vector_into_type_backend(mysql_statement_backend &st) : statement_(st) {} - virtual void define_by_pos(int &position, - void *data, details::exchange_type type); + void define_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, indicator *ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, indicator *ind) SOCI_OVERRIDE; - virtual void resize(std::size_t sz); - virtual std::size_t size(); + void resize(std::size_t sz) SOCI_OVERRIDE; + std::size_t size() SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; mysql_statement_backend &statement_; @@ -94,15 +94,15 @@ struct mysql_standard_use_type_backend : details::standard_use_type_backend mysql_standard_use_type_backend(mysql_statement_backend &st) : statement_(st), position_(0), buf_(NULL) {} - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type, bool readOnly); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type, bool readOnly); + void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; + void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; - virtual void pre_use(indicator const *ind); - virtual void post_use(bool gotData, indicator *ind); + void pre_use(indicator const *ind) SOCI_OVERRIDE; + void post_use(bool gotData, indicator *ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; mysql_statement_backend &statement_; @@ -118,16 +118,16 @@ struct mysql_vector_use_type_backend : details::vector_use_type_backend mysql_vector_use_type_backend(mysql_statement_backend &st) : statement_(st), position_(0) {} - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type); + void bind_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; + void bind_by_name(std::string const &name, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_use(indicator const *ind); + void pre_use(indicator const *ind) SOCI_OVERRIDE; - virtual std::size_t size(); + std::size_t size() SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; mysql_statement_backend &statement_; @@ -143,28 +143,28 @@ struct mysql_statement_backend : details::statement_backend { mysql_statement_backend(mysql_session_backend &session); - virtual void alloc(); - virtual void clean_up(); - virtual void prepare(std::string const &query, - details::statement_type eType); + void alloc() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + void prepare(std::string const &query, + details::statement_type eType) SOCI_OVERRIDE; - virtual exec_fetch_result execute(int number); - virtual exec_fetch_result fetch(int number); + exec_fetch_result execute(int number) SOCI_OVERRIDE; + exec_fetch_result fetch(int number) SOCI_OVERRIDE; - virtual long long get_affected_rows(); - virtual int get_number_of_rows(); - virtual std::string get_parameter_name(int index) const; + long long get_affected_rows() SOCI_OVERRIDE; + int get_number_of_rows() SOCI_OVERRIDE; + std::string get_parameter_name(int index) const SOCI_OVERRIDE; - virtual std::string rewrite_for_procedure_call(std::string const &query); + std::string rewrite_for_procedure_call(std::string const &query) SOCI_OVERRIDE; - virtual int prepare_for_describe(); - virtual void describe_column(int colNum, data_type &dtype, - std::string &columnName); + int prepare_for_describe() SOCI_OVERRIDE; + void describe_column(int colNum, data_type &dtype, + std::string &columnName) SOCI_OVERRIDE; - virtual mysql_standard_into_type_backend * make_into_type_backend(); - virtual mysql_standard_use_type_backend * make_use_type_backend(); - virtual mysql_vector_into_type_backend * make_vector_into_type_backend(); - virtual mysql_vector_use_type_backend * make_vector_use_type_backend(); + mysql_standard_into_type_backend * make_into_type_backend() SOCI_OVERRIDE; + mysql_standard_use_type_backend * make_use_type_backend() SOCI_OVERRIDE; + mysql_vector_into_type_backend * make_vector_into_type_backend() SOCI_OVERRIDE; + mysql_vector_use_type_backend * make_vector_use_type_backend() SOCI_OVERRIDE; mysql_session_backend &session_; @@ -209,22 +209,22 @@ struct mysql_rowid_backend : details::rowid_backend { mysql_rowid_backend(mysql_session_backend &session); - ~mysql_rowid_backend(); + ~mysql_rowid_backend() SOCI_OVERRIDE; }; struct mysql_blob_backend : details::blob_backend { mysql_blob_backend(mysql_session_backend &session); - ~mysql_blob_backend(); + ~mysql_blob_backend() SOCI_OVERRIDE; - virtual std::size_t get_len(); - virtual std::size_t read(std::size_t offset, char *buf, - std::size_t toRead); - virtual std::size_t write(std::size_t offset, char const *buf, - std::size_t toWrite); - virtual std::size_t append(char const *buf, std::size_t toWrite); - virtual void trim(std::size_t newLen); + std::size_t get_len() SOCI_OVERRIDE; + std::size_t read(std::size_t offset, char *buf, + std::size_t toRead) SOCI_OVERRIDE; + std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite) SOCI_OVERRIDE; + std::size_t append(char const *buf, std::size_t toWrite) SOCI_OVERRIDE; + void trim(std::size_t newLen) SOCI_OVERRIDE; mysql_session_backend &session_; }; @@ -233,21 +233,25 @@ struct mysql_session_backend : details::session_backend { mysql_session_backend(connection_parameters const & parameters); - ~mysql_session_backend(); + ~mysql_session_backend() SOCI_OVERRIDE; - virtual void begin(); - virtual void commit(); - virtual void rollback(); + void begin() SOCI_OVERRIDE; + void commit() SOCI_OVERRIDE; + void rollback() SOCI_OVERRIDE; - virtual bool get_last_insert_id(session&, std::string const&, long&); + bool get_last_insert_id(session&, std::string const&, long&) SOCI_OVERRIDE; - virtual std::string get_backend_name() const { return "mysql"; } + // Note that MySQL supports both "SELECT 2+2" and "SELECT 2+2 FROM DUAL" + // syntaxes, but there doesn't seem to be any reason to use the longer one. + std::string get_dummy_from_table() const SOCI_OVERRIDE { return std::string(); } + + std::string get_backend_name() const SOCI_OVERRIDE { return "mysql"; } void clean_up(); - virtual mysql_statement_backend * make_statement_backend(); - virtual mysql_rowid_backend * make_rowid_backend(); - virtual mysql_blob_backend * make_blob_backend(); + mysql_statement_backend * make_statement_backend() SOCI_OVERRIDE; + mysql_rowid_backend * make_rowid_backend() SOCI_OVERRIDE; + mysql_blob_backend * make_blob_backend() SOCI_OVERRIDE; MYSQL *conn_; }; @@ -256,8 +260,8 @@ struct mysql_session_backend : details::session_backend struct mysql_backend_factory : backend_factory { mysql_backend_factory() {} - virtual mysql_session_backend * make_session( - connection_parameters const & parameters) const; + mysql_session_backend * make_session( + connection_parameters const & parameters) const SOCI_OVERRIDE; }; extern SOCI_MYSQL_DECL mysql_backend_factory const mysql; diff --git a/include/soci/odbc/soci-odbc.h b/include/soci/odbc/soci-odbc.h index df4d739c7c..5159af9fc0 100644 --- a/include/soci/odbc/soci-odbc.h +++ b/include/soci/odbc/soci-odbc.h @@ -33,6 +33,10 @@ #include // ODBC #include // strcpy() +#ifndef SQL_SS_LENGTH_UNLIMITED +#define SQL_SS_LENGTH_UNLIMITED 0 +#endif + namespace soci { @@ -41,6 +45,10 @@ namespace details // TODO: Do we want to make it a part of public interface? --mloskot std::size_t const odbc_max_buffer_length = 100 * 1024 * 1024; + // select max size from following MSDN article + // https://msdn.microsoft.com/en-us/library/ms130896.aspx + SQLLEN const ODBC_MAX_COL_SIZE = 8000; + // This cast is only used to avoid compiler warnings when passing strings // to ODBC functions, the returned string may *not* be really modified. inline SQLCHAR* sqlchar_cast(std::string const& s) @@ -88,14 +96,14 @@ struct odbc_standard_into_type_backend : details::standard_into_type_backend, : odbc_standard_type_backend_base(st), buf_(0) {} - virtual void define_by_pos(int &position, - void *data, details::exchange_type type); + void define_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, bool calledFromFetch, - indicator *ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; char *buf_; // generic buffer void *data_; @@ -114,16 +122,16 @@ struct odbc_vector_into_type_backend : details::vector_into_type_backend, : odbc_standard_type_backend_base(st), indHolders_(NULL), data_(NULL), buf_(NULL) {} - virtual void define_by_pos(int &position, - void *data, details::exchange_type type); + void define_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, indicator *ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, indicator *ind) SOCI_OVERRIDE; - virtual void resize(std::size_t sz); - virtual std::size_t size(); + void resize(std::size_t sz) SOCI_OVERRIDE; + std::size_t size() SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; // helper function for preparing indicators // (as part of the define_by_pos) @@ -146,15 +154,15 @@ struct odbc_standard_use_type_backend : details::standard_use_type_backend, : odbc_standard_type_backend_base(st), position_(-1), data_(0), buf_(0), indHolder_(0) {} - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type, bool readOnly); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type, bool readOnly); + void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; + void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; - virtual void pre_use(indicator const *ind); - virtual void post_use(bool gotData, indicator *ind); + void pre_use(indicator const *ind) SOCI_OVERRIDE; + void post_use(bool gotData, indicator *ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; // Return the pointer to the buffer containing data to be used by ODBC. // This can be either data_ itself or buf_, that is allocated by this @@ -169,6 +177,15 @@ struct odbc_standard_use_type_backend : details::standard_use_type_backend, details::exchange_type type_; char *buf_; SQLLEN indHolder_; + +private: + // Copy string data to buf_ and set size, sqlType and cType to the values + // appropriate for strings. + void copy_from_string(std::string const& s, + SQLLEN& size, + SQLSMALLINT& sqlType, + SQLSMALLINT& cType); + }; struct odbc_vector_use_type_backend : details::vector_use_type_backend, @@ -187,16 +204,16 @@ struct odbc_vector_use_type_backend : details::vector_use_type_backend, void bind_helper(int &position, void *data, details::exchange_type type); - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type); + void bind_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; + void bind_by_name(std::string const &name, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_use(indicator const *ind); + void pre_use(indicator const *ind) SOCI_OVERRIDE; - virtual std::size_t size(); + std::size_t size() SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; SQLLEN *indHolders_; @@ -214,31 +231,31 @@ struct odbc_statement_backend : details::statement_backend { odbc_statement_backend(odbc_session_backend &session); - virtual void alloc(); - virtual void clean_up(); - virtual void prepare(std::string const &query, - details::statement_type eType); + void alloc() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + void prepare(std::string const &query, + details::statement_type eType) SOCI_OVERRIDE; - virtual exec_fetch_result execute(int number); - virtual exec_fetch_result fetch(int number); + exec_fetch_result execute(int number) SOCI_OVERRIDE; + exec_fetch_result fetch(int number) SOCI_OVERRIDE; - virtual long long get_affected_rows(); - virtual int get_number_of_rows(); - virtual std::string get_parameter_name(int index) const; + long long get_affected_rows() SOCI_OVERRIDE; + int get_number_of_rows() SOCI_OVERRIDE; + std::string get_parameter_name(int index) const SOCI_OVERRIDE; - virtual std::string rewrite_for_procedure_call(std::string const &query); + std::string rewrite_for_procedure_call(std::string const &query) SOCI_OVERRIDE; - virtual int prepare_for_describe(); - virtual void describe_column(int colNum, data_type &dtype, - std::string &columnName); + int prepare_for_describe() SOCI_OVERRIDE; + void describe_column(int colNum, data_type &dtype, + std::string &columnName) SOCI_OVERRIDE; // helper for defining into vector std::size_t column_size(int position); - virtual odbc_standard_into_type_backend * make_into_type_backend(); - virtual odbc_standard_use_type_backend * make_use_type_backend(); - virtual odbc_vector_into_type_backend * make_vector_into_type_backend(); - virtual odbc_vector_use_type_backend * make_vector_use_type_backend(); + odbc_standard_into_type_backend * make_into_type_backend() SOCI_OVERRIDE; + odbc_standard_use_type_backend * make_use_type_backend() SOCI_OVERRIDE; + odbc_vector_into_type_backend * make_vector_into_type_backend() SOCI_OVERRIDE; + odbc_vector_use_type_backend * make_vector_use_type_backend() SOCI_OVERRIDE; odbc_session_backend &session_; SQLHSTMT hstmt_; @@ -258,22 +275,22 @@ struct odbc_rowid_backend : details::rowid_backend { odbc_rowid_backend(odbc_session_backend &session); - ~odbc_rowid_backend(); + ~odbc_rowid_backend() SOCI_OVERRIDE; }; struct odbc_blob_backend : details::blob_backend { odbc_blob_backend(odbc_session_backend &session); - ~odbc_blob_backend(); + ~odbc_blob_backend() SOCI_OVERRIDE; - virtual std::size_t get_len(); - virtual std::size_t read(std::size_t offset, char *buf, - std::size_t toRead); - virtual std::size_t write(std::size_t offset, char const *buf, - std::size_t toWrite); - virtual std::size_t append(char const *buf, std::size_t toWrite); - virtual void trim(std::size_t newLen); + std::size_t get_len() SOCI_OVERRIDE; + std::size_t read(std::size_t offset, char *buf, + std::size_t toRead) SOCI_OVERRIDE; + std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite) SOCI_OVERRIDE; + std::size_t append(char const *buf, std::size_t toWrite) SOCI_OVERRIDE; + void trim(std::size_t newLen) SOCI_OVERRIDE; odbc_session_backend &session_; }; @@ -282,27 +299,29 @@ struct odbc_session_backend : details::session_backend { odbc_session_backend(connection_parameters const & parameters); - ~odbc_session_backend(); + ~odbc_session_backend() SOCI_OVERRIDE; - virtual void begin(); - virtual void commit(); - virtual void rollback(); + void begin() SOCI_OVERRIDE; + void commit() SOCI_OVERRIDE; + void rollback() SOCI_OVERRIDE; - virtual bool get_next_sequence_value(session & s, - std::string const & sequence, long & value); - virtual bool get_last_insert_id(session & s, - std::string const & table, long & value); + bool get_next_sequence_value(session & s, + std::string const & sequence, long & value) SOCI_OVERRIDE; + bool get_last_insert_id(session & s, + std::string const & table, long & value) SOCI_OVERRIDE; - virtual std::string get_backend_name() const { return "odbc"; } + std::string get_dummy_from_table() const SOCI_OVERRIDE; + + std::string get_backend_name() const SOCI_OVERRIDE { return "odbc"; } void configure_connection(); void reset_transaction(); void clean_up(); - virtual odbc_statement_backend * make_statement_backend(); - virtual odbc_rowid_backend * make_rowid_backend(); - virtual odbc_blob_backend * make_blob_backend(); + odbc_statement_backend * make_statement_backend() SOCI_OVERRIDE; + odbc_rowid_backend * make_rowid_backend() SOCI_OVERRIDE; + odbc_blob_backend * make_blob_backend() SOCI_OVERRIDE; enum database_product { @@ -317,7 +336,7 @@ struct odbc_session_backend : details::session_backend }; // Determine the type of the database we're connected to. - database_product get_database_product(); + database_product get_database_product() const; // Return full ODBC connection string. std::string get_connection_string() const { return connection_string_; } @@ -326,7 +345,9 @@ struct odbc_session_backend : details::session_backend SQLHDBC hdbc_; std::string connection_string_; - database_product product_; + +private: + mutable database_product product_; }; class SOCI_ODBC_DECL odbc_soci_error : public soci_error @@ -435,8 +456,8 @@ inline bool odbc_standard_type_backend_base::use_string_for_bigint() const struct odbc_backend_factory : backend_factory { odbc_backend_factory() {} - virtual odbc_session_backend * make_session( - connection_parameters const & parameters) const; + odbc_session_backend * make_session( + connection_parameters const & parameters) const SOCI_OVERRIDE; }; extern SOCI_ODBC_DECL odbc_backend_factory const odbc; diff --git a/include/soci/once-temp-type.h b/include/soci/once-temp-type.h index bbe94e3c89..f1940f8314 100644 --- a/include/soci/once-temp-type.h +++ b/include/soci/once-temp-type.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -8,15 +8,10 @@ #ifndef SOCI_ONCE_TEMP_TYPE_H_INCLUDED #define SOCI_ONCE_TEMP_TYPE_H_INCLUDED +#include "soci/soci-platform.h" #include "soci/ref-counted-statement.h" #include "soci/prepare-temp-type.h" -#if __cplusplus >= 201103L -#define SOCI_ONCE_TEMP_TYPE_NOEXCEPT noexcept(false) -#else -#define SOCI_ONCE_TEMP_TYPE_NOEXCEPT -#endif - namespace soci { @@ -36,7 +31,7 @@ public: once_temp_type(once_temp_type const & o); once_temp_type & operator=(once_temp_type const & o); - ~once_temp_type() SOCI_ONCE_TEMP_TYPE_NOEXCEPT; + ~once_temp_type() SOCI_NOEXCEPT_FALSE; template once_temp_type & operator<<(T const & t) @@ -46,6 +41,8 @@ public: } once_temp_type & operator,(into_type_ptr const &); + once_temp_type & operator,(use_type_ptr const &); + template once_temp_type &operator,(into_container const &ic) { @@ -114,6 +111,50 @@ private: } // namespace details +// Note: ddl_type is intended to be used just as once_temp_type, +// but since it can be also used directly (explicitly) by the user code, +// it is declared outside of the namespace details. +class SOCI_DECL ddl_type +{ +public: + + ddl_type(session & s); + ddl_type(const ddl_type & d); + ddl_type & operator=(const ddl_type & d); + + ~ddl_type() SOCI_NOEXCEPT_FALSE; + + void create_table(const std::string & tableName); + void add_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale); + void alter_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale); + void drop_column(const std::string & tableName, + const std::string & columnName); + ddl_type & column(const std::string & columnName, data_type dt, + int precision = 0, int scale = 0); + ddl_type & unique(const std::string & name, + const std::string & columnNames); + ddl_type & primary_key(const std::string & name, + const std::string & columnNames); + ddl_type & foreign_key(const std::string & name, + const std::string & columnNames, + const std::string & refTableName, + const std::string & refColumnNames); + + ddl_type & operator()(const std::string & arbitrarySql); + + // helper function for handling delimiters + // between various parts of DDL statements + void set_tail(const std::string & tail); + +private: + session * s_; + details::ref_counted_statement * rcst_; +}; + } // namespace soci #endif diff --git a/include/soci/oracle/soci-oracle.h b/include/soci/oracle/soci-oracle.h index 2396ef8a3e..5af13f81d8 100644 --- a/include/soci/oracle/soci-oracle.h +++ b/include/soci/oracle/soci-oracle.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2007 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -25,6 +25,7 @@ #include #include // OCI +#include #include #ifdef _MSC_VER @@ -40,7 +41,10 @@ class SOCI_ORACLE_DECL oracle_soci_error : public soci_error public: oracle_soci_error(std::string const & msg, int errNum = 0); + error_category get_error_category() const SOCI_OVERRIDE { return cat_; } + int err_num_; + error_category cat_; }; @@ -51,20 +55,24 @@ struct oracle_standard_into_type_backend : details::standard_into_type_backend : statement_(st), defnp_(NULL), indOCIHolder_(0), data_(NULL), buf_(NULL) {} - virtual void define_by_pos(int &position, - void *data, details::exchange_type type); + void define_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, bool calledFromFetch, - indicator *ind); + void read_from_lob(OCILobLocator * lobp, std::string & value); - virtual void clean_up(); + void pre_exec(int num) SOCI_OVERRIDE; + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind) SOCI_OVERRIDE; + + void clean_up() SOCI_OVERRIDE; oracle_statement_backend &statement_; OCIDefine *defnp_; sb2 indOCIHolder_; void *data_; + void *ociData_; char *buf_; // generic buffer details::exchange_type type_; @@ -75,18 +83,27 @@ struct oracle_vector_into_type_backend : details::vector_into_type_backend { oracle_vector_into_type_backend(oracle_statement_backend &st) : statement_(st), defnp_(NULL), indOCIHolders_(NULL), - data_(NULL), buf_(NULL) {} + data_(NULL), buf_(NULL), user_ranges_(true) {} - virtual void define_by_pos(int &position, - void *data, details::exchange_type type); + void define_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE + { + user_ranges_ = false; + define_by_pos_bulk(position, data, type, 0, &end_var_); + } - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, indicator *ind); + void define_by_pos_bulk( + int & position, void * data, details::exchange_type type, + std::size_t begin, std::size_t * end) SOCI_OVERRIDE; - virtual void resize(std::size_t sz); - virtual std::size_t size(); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, indicator *ind) SOCI_OVERRIDE; - virtual void clean_up(); + void resize(std::size_t sz) SOCI_OVERRIDE; + std::size_t size() SOCI_OVERRIDE; + std::size_t full_size(); + + void clean_up() SOCI_OVERRIDE; // helper function for preparing indicators and sizes_ vectors // (as part of the define_by_pos) @@ -100,6 +117,10 @@ struct oracle_vector_into_type_backend : details::vector_into_type_backend void *data_; char *buf_; // generic buffer details::exchange_type type_; + std::size_t begin_; + std::size_t * end_; + std::size_t end_var_; + bool user_ranges_; std::size_t colSize_; // size of the string column (used for strings) std::vector sizes_; // sizes of data fetched (used for strings) @@ -112,24 +133,32 @@ struct oracle_standard_use_type_backend : details::standard_use_type_backend : statement_(st), bindp_(NULL), indOCIHolder_(0), data_(NULL), buf_(NULL) {} - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type, bool readOnly); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type, bool readOnly); + void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; + void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; // common part for bind_by_pos and bind_by_name void prepare_for_bind(void *&data, sb4 &size, ub2 &oracleType, bool readOnly); - virtual void pre_use(indicator const *ind); - virtual void post_use(bool gotData, indicator *ind); + // common helper for pre_use for LOB-directed wrapped types + void write_to_lob(OCILobLocator * lobp, const std::string & value); - virtual void clean_up(); + // common lazy initialization of the temporary LOB object + void lazy_temp_lob_init(); + + void pre_exec(int num) SOCI_OVERRIDE; + void pre_use(indicator const *ind) SOCI_OVERRIDE; + void post_use(bool gotData, indicator *ind) SOCI_OVERRIDE; + + void clean_up() SOCI_OVERRIDE; oracle_statement_backend &statement_; OCIBind *bindp_; sb2 indOCIHolder_; void *data_; + void *ociData_; bool readOnly_; char *buf_; // generic buffer details::exchange_type type_; @@ -141,10 +170,25 @@ struct oracle_vector_use_type_backend : details::vector_use_type_backend : statement_(st), bindp_(NULL), indOCIHolders_(NULL), data_(NULL), buf_(NULL) {} - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type); + void bind_by_pos(int & position, + void * data, details::exchange_type type) SOCI_OVERRIDE + { + bind_by_pos_bulk(position, data, type, 0, &end_var_); + } + + void bind_by_pos_bulk(int & position, + void * data, details::exchange_type type, + std::size_t begin, std::size_t * end) SOCI_OVERRIDE; + + void bind_by_name(const std::string & name, + void * data, details::exchange_type type) SOCI_OVERRIDE + { + bind_by_name_bulk(name, data, type, 0, &end_var_); + } + + void bind_by_name_bulk(std::string const &name, + void *data, details::exchange_type type, + std::size_t begin, std::size_t * end) SOCI_OVERRIDE; // common part for bind_by_pos and bind_by_name void prepare_for_bind(void *&data, sb4 &size, ub2 &oracleType); @@ -153,11 +197,12 @@ struct oracle_vector_use_type_backend : details::vector_use_type_backend // (as part of the bind_by_pos and bind_by_name) void prepare_indicators(std::size_t size); - virtual void pre_use(indicator const *ind); + void pre_use(indicator const *ind) SOCI_OVERRIDE; - virtual std::size_t size(); + std::size_t size() SOCI_OVERRIDE; // active size (might be lower than full vector size) + std::size_t full_size(); // actual size of the user-provided vector - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; oracle_statement_backend &statement_; @@ -167,6 +212,9 @@ struct oracle_vector_use_type_backend : details::vector_use_type_backend void *data_; char *buf_; // generic buffer details::exchange_type type_; + std::size_t begin_; + std::size_t * end_; + std::size_t end_var_; // used for strings only std::vector sizes_; @@ -178,31 +226,31 @@ struct oracle_statement_backend : details::statement_backend { oracle_statement_backend(oracle_session_backend &session); - virtual void alloc(); - virtual void clean_up(); - virtual void prepare(std::string const &query, - details::statement_type eType); + void alloc() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + void prepare(std::string const &query, + details::statement_type eType) SOCI_OVERRIDE; - virtual exec_fetch_result execute(int number); - virtual exec_fetch_result fetch(int number); + exec_fetch_result execute(int number) SOCI_OVERRIDE; + exec_fetch_result fetch(int number) SOCI_OVERRIDE; - virtual long long get_affected_rows(); - virtual int get_number_of_rows(); - virtual std::string get_parameter_name(int index) const; + long long get_affected_rows() SOCI_OVERRIDE; + int get_number_of_rows() SOCI_OVERRIDE; + std::string get_parameter_name(int index) const SOCI_OVERRIDE; - virtual std::string rewrite_for_procedure_call(std::string const &query); + std::string rewrite_for_procedure_call(std::string const &query) SOCI_OVERRIDE; - virtual int prepare_for_describe(); - virtual void describe_column(int colNum, data_type &dtype, - std::string &columnName); + int prepare_for_describe() SOCI_OVERRIDE; + void describe_column(int colNum, data_type &dtype, + std::string &columnName) SOCI_OVERRIDE; // helper for defining into vector std::size_t column_size(int position); - virtual oracle_standard_into_type_backend * make_into_type_backend(); - virtual oracle_standard_use_type_backend * make_use_type_backend(); - virtual oracle_vector_into_type_backend * make_vector_into_type_backend(); - virtual oracle_vector_use_type_backend * make_vector_use_type_backend(); + oracle_standard_into_type_backend * make_into_type_backend() SOCI_OVERRIDE; + oracle_standard_use_type_backend * make_use_type_backend() SOCI_OVERRIDE; + oracle_vector_into_type_backend * make_vector_into_type_backend() SOCI_OVERRIDE; + oracle_vector_use_type_backend * make_vector_use_type_backend() SOCI_OVERRIDE; oracle_session_backend &session_; @@ -217,7 +265,7 @@ struct oracle_rowid_backend : details::rowid_backend { oracle_rowid_backend(oracle_session_backend &session); - ~oracle_rowid_backend(); + ~oracle_rowid_backend() SOCI_OVERRIDE; OCIRowid *rowidp_; }; @@ -226,15 +274,31 @@ struct oracle_blob_backend : details::blob_backend { oracle_blob_backend(oracle_session_backend &session); - ~oracle_blob_backend(); + ~oracle_blob_backend() SOCI_OVERRIDE; - virtual std::size_t get_len(); - virtual std::size_t read(std::size_t offset, char *buf, - std::size_t toRead); - virtual std::size_t write(std::size_t offset, char const *buf, - std::size_t toWrite); - virtual std::size_t append(char const *buf, std::size_t toWrite); - virtual void trim(std::size_t newLen); + std::size_t get_len() SOCI_OVERRIDE; + + std::size_t read(std::size_t offset, char *buf, + std::size_t toRead) SOCI_OVERRIDE; + + std::size_t read_from_start(char * buf, std::size_t toRead, + std::size_t offset) SOCI_OVERRIDE + { + return read(offset + 1, buf, toRead); + } + + std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite) SOCI_OVERRIDE; + + std::size_t write_from_start(const char * buf, std::size_t toWrite, + std::size_t offset) SOCI_OVERRIDE + { + return write(offset + 1, buf, toWrite); + } + + std::size_t append(char const *buf, std::size_t toWrite) SOCI_OVERRIDE; + + void trim(std::size_t newLen) SOCI_OVERRIDE; oracle_session_backend &session_; @@ -247,21 +311,137 @@ struct oracle_session_backend : details::session_backend std::string const & userName, std::string const & password, int mode, - bool decimals_as_strings = false); + bool decimals_as_strings = false, + int charset = 0, + int ncharset = 0); - ~oracle_session_backend(); + ~oracle_session_backend() SOCI_OVERRIDE; - virtual void begin(); - virtual void commit(); - virtual void rollback(); + void begin() SOCI_OVERRIDE; + void commit() SOCI_OVERRIDE; + void rollback() SOCI_OVERRIDE; - virtual std::string get_backend_name() const { return "oracle"; } + std::string get_table_names_query() const SOCI_OVERRIDE + { + return "select table_name" + " from user_tables"; + } + + std::string get_column_descriptions_query() const SOCI_OVERRIDE + { + return "select column_name," + " data_type," + " char_length as character_maximum_length," + " data_precision as numeric_precision," + " data_scale as numeric_scale," + " decode(nullable, 'Y', 'YES', 'N', 'NO') as is_nullable" + " from user_tab_columns" + " where table_name = :t"; + } + + std::string create_column_type(data_type dt, + int precision, int scale) SOCI_OVERRIDE + { + // Oracle-specific SQL syntax: + + std::string res; + switch (dt) + { + case dt_string: + { + std::ostringstream oss; + + if (precision == 0) + { + oss << "clob"; + } + else + { + oss << "varchar(" << precision << ")"; + } + + res += oss.str(); + } + break; + + case dt_date: + res += "timestamp"; + break; + + case dt_double: + { + std::ostringstream oss; + if (precision == 0) + { + oss << "number"; + } + else + { + oss << "number(" << precision << ", " << scale << ")"; + } + + res += oss.str(); + } + break; + + case dt_integer: + res += "integer"; + break; + + case dt_long_long: + res += "number"; + break; + + case dt_unsigned_long_long: + res += "number"; + break; + + case dt_blob: + res += "blob"; + break; + + case dt_xml: + res += "xmltype"; + break; + + default: + throw soci_error("this data_type is not supported in create_column"); + } + + return res; + } + std::string add_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale) SOCI_OVERRIDE + { + return "alter table " + tableName + " add " + + columnName + " " + create_column_type(dt, precision, scale); + } + std::string alter_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale) SOCI_OVERRIDE + { + return "alter table " + tableName + " modify " + + columnName + " " + create_column_type(dt, precision, scale); + } + std::string empty_blob() SOCI_OVERRIDE + { + return "empty_blob()"; + } + std::string nvl() SOCI_OVERRIDE + { + return "nvl"; + } + + std::string get_dummy_from_table() const SOCI_OVERRIDE { return "dual"; } + + std::string get_backend_name() const SOCI_OVERRIDE { return "oracle"; } void clean_up(); - virtual oracle_statement_backend * make_statement_backend(); - virtual oracle_rowid_backend * make_rowid_backend(); - virtual oracle_blob_backend * make_blob_backend(); + oracle_statement_backend * make_statement_backend() SOCI_OVERRIDE; + oracle_rowid_backend * make_rowid_backend() SOCI_OVERRIDE; + oracle_blob_backend * make_blob_backend() SOCI_OVERRIDE; bool get_option_decimals_as_strings() { return decimals_as_strings_; } @@ -281,8 +461,8 @@ struct oracle_session_backend : details::session_backend struct oracle_backend_factory : backend_factory { oracle_backend_factory() {} - virtual oracle_session_backend * make_session( - connection_parameters const & parameters) const; + oracle_session_backend * make_session( + connection_parameters const & parameters) const SOCI_OVERRIDE; }; extern SOCI_ORACLE_DECL oracle_backend_factory const oracle; diff --git a/include/soci/postgresql/soci-postgresql.h b/include/soci/postgresql/soci-postgresql.h index 49eb427e14..0710b05905 100644 --- a/include/soci/postgresql/soci-postgresql.h +++ b/include/soci/postgresql/soci-postgresql.h @@ -31,17 +31,22 @@ namespace soci { -class postgresql_soci_error : public soci_error +class SOCI_POSTGRESQL_DECL postgresql_soci_error : public soci_error { public: postgresql_soci_error(std::string const & msg, char const * sqlst); std::string sqlstate() const; + error_category get_error_category() const SOCI_OVERRIDE { return cat_; } + private: char sqlstate_[ 5 ]; // not std::string to keep copy-constructor no-throw + error_category cat_; }; +struct postgresql_session_backend; + namespace details { @@ -51,18 +56,21 @@ class postgresql_result { public: // Creates a wrapper for the given, possibly NULL, result. The wrapper - // object takes ownership of the object and will call PQclear() on it. - explicit postgresql_result(PGresult* result = NULL) + // object takes ownership of the result object and will call PQclear() on it. + explicit postgresql_result( + postgresql_session_backend & sessionBackend, + PGresult * result) + : sessionBackend_(sessionBackend) { - init(result); + init(result); } // Frees any currently stored result pointer and takes ownership of the // given one. void reset(PGresult* result = NULL) { - free(); - init(result); + free(); + init(result); } // Check whether the status is PGRES_COMMAND_OK and throw an exception if @@ -98,7 +106,7 @@ public: private: void init(PGresult* result) { - result_ = result; + result_ = result; } void free() @@ -108,6 +116,7 @@ private: PQclear(result_); } + postgresql_session_backend & sessionBackend_; PGresult* result_; SOCI_NOT_COPYABLE(postgresql_result) @@ -121,14 +130,14 @@ struct postgresql_standard_into_type_backend : details::standard_into_type_backe postgresql_standard_into_type_backend(postgresql_statement_backend & st) : statement_(st) {} - virtual void define_by_pos(int & position, - void * data, details::exchange_type type); + void define_by_pos(int & position, + void * data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, bool calledFromFetch, - indicator * ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, bool calledFromFetch, + indicator * ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; postgresql_statement_backend & statement_; @@ -140,23 +149,36 @@ struct postgresql_standard_into_type_backend : details::standard_into_type_backe struct postgresql_vector_into_type_backend : details::vector_into_type_backend { postgresql_vector_into_type_backend(postgresql_statement_backend & st) - : statement_(st) {} + : statement_(st), user_ranges_(true) {} - virtual void define_by_pos(int & position, - void * data, details::exchange_type type); + void define_by_pos(int & position, + void * data, details::exchange_type type) SOCI_OVERRIDE + { + user_ranges_ = false; + define_by_pos_bulk(position, data, type, 0, &end_var_); + } - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, indicator * ind); + void define_by_pos_bulk(int & position, + void * data, details::exchange_type type, + std::size_t begin, std::size_t * end) SOCI_OVERRIDE; - virtual void resize(std::size_t sz); - virtual std::size_t size(); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, indicator * ind) SOCI_OVERRIDE; - virtual void clean_up(); + void resize(std::size_t sz) SOCI_OVERRIDE; + std::size_t size() SOCI_OVERRIDE; // active size (might be lower than full vector size) + std::size_t full_size(); // actual size of the user-provided vector + + void clean_up() SOCI_OVERRIDE; postgresql_statement_backend & statement_; void * data_; details::exchange_type type_; + std::size_t begin_; + std::size_t * end_; + std::size_t end_var_; + bool user_ranges_; int position_; }; @@ -165,15 +187,15 @@ struct postgresql_standard_use_type_backend : details::standard_use_type_backend postgresql_standard_use_type_backend(postgresql_statement_backend & st) : statement_(st), position_(0), buf_(NULL) {} - virtual void bind_by_pos(int & position, - void * data, details::exchange_type type, bool readOnly); - virtual void bind_by_name(std::string const & name, - void * data, details::exchange_type type, bool readOnly); + void bind_by_pos(int & position, + void * data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; + void bind_by_name(std::string const & name, + void * data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; - virtual void pre_use(indicator const * ind); - virtual void post_use(bool gotData, indicator * ind); + void pre_use(indicator const * ind) SOCI_OVERRIDE; + void post_use(bool gotData, indicator * ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; postgresql_statement_backend & statement_; @@ -182,6 +204,10 @@ struct postgresql_standard_use_type_backend : details::standard_use_type_backend int position_; std::string name_; char * buf_; + +private: + // Allocate buf_ of appropriate size and copy string data into it. + void copy_from_string(std::string const& s); }; struct postgresql_vector_use_type_backend : details::vector_use_type_backend @@ -189,57 +215,78 @@ struct postgresql_vector_use_type_backend : details::vector_use_type_backend postgresql_vector_use_type_backend(postgresql_statement_backend & st) : statement_(st), position_(0) {} - virtual void bind_by_pos(int & position, - void * data, details::exchange_type type); - virtual void bind_by_name(std::string const & name, - void * data, details::exchange_type type); + void bind_by_pos(int & position, + void * data, details::exchange_type type) SOCI_OVERRIDE + { + bind_by_pos_bulk(position, data, type, 0, &end_var_); + } - virtual void pre_use(indicator const * ind); + void bind_by_pos_bulk(int & position, + void * data, details::exchange_type type, + std::size_t begin, std::size_t * end) SOCI_OVERRIDE; - virtual std::size_t size(); + void bind_by_name(std::string const & name, + void * data, details::exchange_type type) SOCI_OVERRIDE + { + bind_by_name_bulk(name, data, type, 0, &end_var_); + } - virtual void clean_up(); + void bind_by_name_bulk(const std::string & name, + void * data, details::exchange_type type, + std::size_t begin, std::size_t * end) SOCI_OVERRIDE; + + void pre_use(indicator const * ind) SOCI_OVERRIDE; + + std::size_t size() SOCI_OVERRIDE; // active size (might be lower than full vector size) + std::size_t full_size(); // actual size of the user-provided vector + + void clean_up() SOCI_OVERRIDE; postgresql_statement_backend & statement_; void * data_; details::exchange_type type_; + std::size_t begin_; + std::size_t * end_; + std::size_t end_var_; int position_; std::string name_; std::vector buffers_; }; -struct postgresql_session_backend; struct postgresql_statement_backend : details::statement_backend { - postgresql_statement_backend(postgresql_session_backend & session); - ~postgresql_statement_backend(); + postgresql_statement_backend(postgresql_session_backend & session, + bool single_row_mode); + ~postgresql_statement_backend() SOCI_OVERRIDE; - virtual void alloc(); - virtual void clean_up(); - virtual void prepare(std::string const & query, - details::statement_type stType); + void alloc() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + void prepare(std::string const & query, + details::statement_type stType) SOCI_OVERRIDE; - virtual exec_fetch_result execute(int number); - virtual exec_fetch_result fetch(int number); + exec_fetch_result execute(int number) SOCI_OVERRIDE; + exec_fetch_result fetch(int number) SOCI_OVERRIDE; - virtual long long get_affected_rows(); - virtual int get_number_of_rows(); - virtual std::string get_parameter_name(int index) const; + long long get_affected_rows() SOCI_OVERRIDE; + int get_number_of_rows() SOCI_OVERRIDE; + std::string get_parameter_name(int index) const SOCI_OVERRIDE; - virtual std::string rewrite_for_procedure_call(std::string const & query); + std::string rewrite_for_procedure_call(std::string const & query) SOCI_OVERRIDE; - virtual int prepare_for_describe(); - virtual void describe_column(int colNum, data_type & dtype, - std::string & columnName); + int prepare_for_describe() SOCI_OVERRIDE; + void describe_column(int colNum, data_type & dtype, + std::string & columnName) SOCI_OVERRIDE; - virtual postgresql_standard_into_type_backend * make_into_type_backend(); - virtual postgresql_standard_use_type_backend * make_use_type_backend(); - virtual postgresql_vector_into_type_backend * make_vector_into_type_backend(); - virtual postgresql_vector_use_type_backend * make_vector_use_type_backend(); + postgresql_standard_into_type_backend * make_into_type_backend() SOCI_OVERRIDE; + postgresql_standard_use_type_backend * make_use_type_backend() SOCI_OVERRIDE; + postgresql_vector_into_type_backend * make_vector_into_type_backend() SOCI_OVERRIDE; + postgresql_vector_use_type_backend * make_vector_use_type_backend() SOCI_OVERRIDE; postgresql_session_backend & session_; + bool single_row_mode_; + details::postgresql_result result_; std::string query_; details::statement_type stType_; @@ -274,7 +321,7 @@ struct postgresql_rowid_backend : details::rowid_backend { postgresql_rowid_backend(postgresql_session_backend & session); - ~postgresql_rowid_backend(); + ~postgresql_rowid_backend() SOCI_OVERRIDE; unsigned long value_; }; @@ -283,15 +330,31 @@ struct postgresql_blob_backend : details::blob_backend { postgresql_blob_backend(postgresql_session_backend & session); - ~postgresql_blob_backend(); + ~postgresql_blob_backend() SOCI_OVERRIDE; - virtual std::size_t get_len(); - virtual std::size_t read(std::size_t offset, char * buf, - std::size_t toRead); - virtual std::size_t write(std::size_t offset, char const * buf, - std::size_t toWrite); - virtual std::size_t append(char const * buf, std::size_t toWrite); - virtual void trim(std::size_t newLen); + std::size_t get_len() SOCI_OVERRIDE; + + std::size_t read(std::size_t offset, char * buf, + std::size_t toRead) SOCI_OVERRIDE; + + std::size_t read_from_start(char * buf, std::size_t toRead, + std::size_t offset) SOCI_OVERRIDE + { + return read(offset, buf, toRead); + } + + std::size_t write(std::size_t offset, char const * buf, + std::size_t toWrite) SOCI_OVERRIDE; + + std::size_t write_from_start(const char * buf, std::size_t toWrite, + std::size_t offset) SOCI_OVERRIDE + { + return write(offset, buf, toWrite); + } + + std::size_t append(char const * buf, std::size_t toWrite) SOCI_OVERRIDE; + + void trim(std::size_t newLen) SOCI_OVERRIDE; postgresql_session_backend & session_; @@ -301,30 +364,36 @@ struct postgresql_blob_backend : details::blob_backend struct postgresql_session_backend : details::session_backend { - postgresql_session_backend(connection_parameters const & parameters); + postgresql_session_backend(connection_parameters const & parameters, + bool single_row_mode); - ~postgresql_session_backend(); + ~postgresql_session_backend() SOCI_OVERRIDE; - virtual void begin(); - virtual void commit(); - virtual void rollback(); + void connect(connection_parameters const & parameters); + + void begin() SOCI_OVERRIDE; + void commit() SOCI_OVERRIDE; + void rollback() SOCI_OVERRIDE; void deallocate_prepared_statement(const std::string & statementName); - virtual bool get_next_sequence_value(session & s, - std::string const & sequence, long & value); + bool get_next_sequence_value(session & s, + std::string const & sequence, long & value) SOCI_OVERRIDE; - virtual std::string get_backend_name() const { return "postgresql"; } + std::string get_dummy_from_table() const SOCI_OVERRIDE { return std::string(); } + + std::string get_backend_name() const SOCI_OVERRIDE { return "postgresql"; } void clean_up(); - virtual postgresql_statement_backend * make_statement_backend(); - virtual postgresql_rowid_backend * make_rowid_backend(); - virtual postgresql_blob_backend * make_blob_backend(); + postgresql_statement_backend * make_statement_backend() SOCI_OVERRIDE; + postgresql_rowid_backend * make_rowid_backend() SOCI_OVERRIDE; + postgresql_blob_backend * make_blob_backend() SOCI_OVERRIDE; std::string get_next_statement_name(); int statementCount_; + bool single_row_mode_; PGconn * conn_; }; @@ -332,8 +401,8 @@ struct postgresql_session_backend : details::session_backend struct postgresql_backend_factory : backend_factory { postgresql_backend_factory() {} - virtual postgresql_session_backend * make_session( - connection_parameters const & parameters) const; + postgresql_session_backend * make_session( + connection_parameters const & parameters) const SOCI_OVERRIDE; }; extern SOCI_POSTGRESQL_DECL postgresql_backend_factory const postgresql; diff --git a/include/soci/procedure.h b/include/soci/procedure.h index 4edbf58de7..ec50ddad1c 100644 --- a/include/soci/procedure.h +++ b/include/soci/procedure.h @@ -42,13 +42,13 @@ class SOCI_DECL procedure public: // this is a conversion constructor procedure(details::prepare_temp_type const & prep) - : impl_(new details::procedure_impl(prep)) {} + : impl_(new details::procedure_impl(prep)), gotData_(false) {} ~procedure() { impl_->dec_ref(); } // copy is supported here procedure(procedure const & other) - : impl_(other.impl_) + : impl_(other.impl_), gotData_(other.gotData_) { impl_->inc_ref(); } @@ -57,6 +57,7 @@ public: other.impl_->inc_ref(); impl_->dec_ref(); impl_ = other.impl_; + gotData_ = other.gotData_; } // forwarders to procedure_impl diff --git a/include/soci/query_transformation.h b/include/soci/query_transformation.h index ff3d4fe9ca..72582a8ec6 100644 --- a/include/soci/query_transformation.h +++ b/include/soci/query_transformation.h @@ -43,7 +43,7 @@ public: : callback_(callback) {} - result_type operator()(argument_type query) const + result_type operator()(argument_type query) const SOCI_OVERRIDE { return callback_(query); } diff --git a/include/soci/ref-counted-prepare-info.h b/include/soci/ref-counted-prepare-info.h index 59af69dd96..10bafaaaeb 100644 --- a/include/soci/ref-counted-prepare-info.h +++ b/include/soci/ref-counted-prepare-info.h @@ -47,7 +47,7 @@ public: void exchange(into_container const &ic) { intos_.exchange(ic); } - void final_action(); + void final_action() SOCI_OVERRIDE; private: friend class statement_impl; diff --git a/include/soci/ref-counted-statement.h b/include/soci/ref-counted-statement.h index cf3407d480..365b3ba463 100644 --- a/include/soci/ref-counted-statement.h +++ b/include/soci/ref-counted-statement.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -37,6 +37,11 @@ public: { try { + if (tail_.empty() == false) + { + accumulate(tail_); + } + final_action(); } catch (...) @@ -52,6 +57,10 @@ public: template void accumulate(T const & t) { get_query_stream() << t; } + void set_tail(const std::string & tail) { tail_ = tail; } + void set_need_comma(bool need_comma) { need_comma_ = need_comma; } + bool get_need_comma() const { return need_comma_; } + protected: // this function allows to break the circular dependenc // between session and this class @@ -61,6 +70,10 @@ protected: session & session_; + // used mainly for portable ddl + std::string tail_; + bool need_comma_; + private: SOCI_NOT_COPYABLE(ref_counted_statement_base) }; @@ -73,7 +86,7 @@ public: ref_counted_statement(session & s) : ref_counted_statement_base(s), st_(s) {} - virtual void final_action(); + void final_action() SOCI_OVERRIDE; template void exchange(T &t) { st_.exchange(t); } diff --git a/include/soci/row-exchange.h b/include/soci/row-exchange.h index deb32da6e6..b94337fc4b 100644 --- a/include/soci/row-exchange.h +++ b/include/soci/row-exchange.h @@ -33,7 +33,7 @@ public: private: // special handling for Row - virtual void define(statement_impl & st, int & /* position */) + void define(statement_impl & st, int & /* position */) SOCI_OVERRIDE { st.set_row(&r_); @@ -41,8 +41,9 @@ private: // as part of the statement execute } - virtual void pre_fetch() {} - virtual void post_fetch(bool gotData, bool /* calledFromFetch */) + void pre_exec(int /* num */) SOCI_OVERRIDE {} + void pre_fetch() SOCI_OVERRIDE {} + void post_fetch(bool gotData, bool /* calledFromFetch */) SOCI_OVERRIDE { r_.reset_get_counter(); @@ -55,9 +56,9 @@ private: } } - virtual void clean_up() {} + void clean_up() SOCI_OVERRIDE {} - virtual std::size_t size() const { return 1; } + std::size_t size() const SOCI_OVERRIDE { return 1; } virtual void convert_from_base() {} diff --git a/include/soci/rowset.h b/include/soci/rowset.h index 3c835731cf..c15b3abbeb 100644 --- a/include/soci/rowset.h +++ b/include/soci/rowset.h @@ -8,6 +8,7 @@ #ifndef SOCI_ROWSET_H_INCLUDED #define SOCI_ROWSET_H_INCLUDED +#include "soci/soci-platform.h" #include "soci/statement.h" // std #include @@ -147,13 +148,8 @@ private: unsigned int refs_; -#ifdef SOCI_CXX_C11 - const std::unique_ptr st_; - const std::unique_ptr define_; -#else - const std::auto_ptr st_; - const std::auto_ptr define_; -#endif + const cxx_details::auto_ptr st_; + const cxx_details::auto_ptr define_; SOCI_NOT_COPYABLE(rowset_impl) }; // class rowset_impl diff --git a/include/soci/session.h b/include/soci/session.h index ce46e7e06a..04af650f5e 100644 --- a/include/soci/session.h +++ b/include/soci/session.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -8,6 +8,7 @@ #ifndef SOCI_SESSION_H_INCLUDED #define SOCI_SESSION_H_INCLUDED +#include "soci/soci-platform.h" #include "soci/once-temp-type.h" #include "soci/query_transformation.h" #include "soci/connection-parameters.h" @@ -35,16 +36,13 @@ class blob_backend; } // namespace details class connection_pool; +class failover_callback; class SOCI_DECL session { private: -#ifdef SOCI_CXX_C11 - void set_query_transformation_(std::unique_ptr & qtf); -#else - void set_query_transformation_(std::auto_ptr qtf); -#endif + void set_query_transformation_(cxx_details::auto_ptr& qtf); @@ -84,13 +82,9 @@ public: void set_query_transformation(T callback) { -#ifdef SOCI_CXX_C11 - std::unique_ptr qtf(new details::query_transformation(callback)); -#else - std::auto_ptr qtf(new details::query_transformation(callback)); -#endif + cxx_details::auto_ptr qtf(new details::query_transformation(callback)); set_query_transformation_(qtf); - } + } // support for basic logging void set_log_stream(std::ostream * s); @@ -120,7 +114,58 @@ public: // return the last value auto-generated in this session). bool get_last_insert_id(std::string const & table, long & value); + // Returns once_temp_type for the internally composed query + // for the list of tables in the current schema. + // Since this query usually returns multiple results (for multiple tables), + // it makes sense to bind std::vector for the single output field. + details::once_temp_type get_table_names(); + // Returns prepare_temp_type for the internally composed query + // for the list of tables in the current schema. + // Since this is intended for use with statement objects, where results are obtained one row after another, + // it makes sense to bind std::string for the output field. + details::prepare_temp_type prepare_table_names(); + + // Returns prepare_temp_type for the internally composed query + // for the list of column descriptions. + // Since this is intended for use with statement objects, where results are obtained one row after another, + // it makes sense to bind either std::string for each output field or soci::column_info for the whole row. + // Note: table_name is a non-const reference to prevent temporary objects, + // this argument is bound as a regular "use" element. + details::prepare_temp_type prepare_column_descriptions(std::string & table_name); + + // Functions for basic portable DDL statements. + + ddl_type create_table(const std::string & tableName); + void drop_table(const std::string & tableName); + void truncate_table(const std::string & tableName); + ddl_type add_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision = 0, int scale = 0); + ddl_type alter_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision = 0, int scale = 0); + ddl_type drop_column(const std::string & tableName, + const std::string & columnName); + std::string empty_blob(); + std::string nvl(); + + // And some functions to help with writing portable DML statements. + + // Get the name of the dummy table that needs to be used in the FROM clause + // of a SELECT statement not operating on any tables, e.g. "dual" for + // Oracle. The returned string is empty if no such table is needed. + std::string get_dummy_from_table() const; + + // Returns a possibly empty string that needs to be used as a FROM clause + // of a SELECT statement not operating on any tables, e.g. " FROM DUAL" + // (notice the leading space). + std::string get_dummy_from_clause() const; + + + // Sets the failover callback object. + void set_failover_callback(failover_callback & callback); + // for diagnostics and advanced users // (downcast it to expected back-end session class) details::session_backend * get_backend() { return backEnd_; } diff --git a/include/soci/soci-backend.h b/include/soci/soci-backend.h index 0f7513f6b1..583041bf52 100644 --- a/include/soci/soci-backend.h +++ b/include/soci/soci-backend.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -14,6 +14,7 @@ #include #include #include +#include namespace soci { @@ -21,13 +22,15 @@ namespace soci // data types, as seen by the user enum data_type { - dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long, dt_blob + dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long, + dt_blob, dt_xml }; // the enum type for indicator variables enum indicator { i_ok, i_null, i_truncated }; class session; +class failover_callback; namespace details { @@ -45,7 +48,10 @@ enum exchange_type x_stdtm, x_statement, x_rowid, - x_blob + x_blob, + + x_xmltype, + x_longstring }; // type of statement (used for optimizing statement preparation) @@ -65,6 +71,7 @@ public: virtual void define_by_pos(int& position, void* data, exchange_type type) = 0; + virtual void pre_exec(int /* num */) {} virtual void pre_fetch() = 0; virtual void post_fetch(bool gotData, bool calledFromFetch, indicator* ind) = 0; @@ -81,8 +88,16 @@ public: vector_into_type_backend() {} virtual ~vector_into_type_backend() {} + virtual void define_by_pos_bulk( + int & /* position */, void * /* data */, exchange_type /* type */, + std::size_t /* begin */, std::size_t * /* end */) + { + throw soci_error("into bulk iterators are not supported with this backend"); + } + virtual void define_by_pos(int& position, void* data, exchange_type type) = 0; + virtual void pre_exec(int /* num */) {} virtual void pre_fetch() = 0; virtual void post_fetch(bool gotData, indicator* ind) = 0; @@ -108,6 +123,7 @@ public: virtual void bind_by_name(std::string const& name, void* data, exchange_type type, bool readOnly) = 0; + virtual void pre_exec(int /* num */) {} virtual void pre_use(indicator const* ind) = 0; virtual void post_use(bool gotData, indicator * ind) = 0; @@ -124,9 +140,23 @@ public: virtual ~vector_use_type_backend() {} virtual void bind_by_pos(int& position, void* data, exchange_type type) = 0; + virtual void bind_by_pos_bulk(int& /* position */, void* /* data */, exchange_type /* type */, + std::size_t /* begin */, std::size_t * /* end */) + { + throw soci_error("use bulk iterators are not supported with this backend"); + } + virtual void bind_by_name(std::string const& name, void* data, exchange_type type) = 0; + virtual void bind_by_name_bulk(std::string const& /* name */, + void* /* data */, exchange_type /* type */, + std::size_t /* begin */, std::size_t * /* end */) + { + throw soci_error("use bulk iterators are not supported with this backend"); + } + + virtual void pre_exec(int /* num */) {} virtual void pre_use(indicator const* ind) = 0; virtual std::size_t size() = 0; @@ -196,11 +226,27 @@ public: virtual ~blob_backend() {} virtual std::size_t get_len() = 0; + virtual std::size_t read(std::size_t offset, char* buf, std::size_t toRead) = 0; + + virtual std::size_t read_from_start(char * /* buf */, std::size_t /* toRead */, + std::size_t /* offset */) + { + throw soci_error("read_from_start is not implemented for this backend"); + } + virtual std::size_t write(std::size_t offset, char const* buf, std::size_t toWrite) = 0; + + virtual std::size_t write_from_start(const char * /* buf */, std::size_t /* toWrite */, + std::size_t /* offset */) + { + throw soci_error("write_from_start is not implemented for this backend"); + } + virtual std::size_t append(char const* buf, std::size_t toWrite) = 0; + virtual void trim(std::size_t newLen) = 0; private: @@ -212,7 +258,7 @@ private: class session_backend { public: - session_backend() {} + session_backend() : failoverCallback_(NULL), session_(NULL) {} virtual ~session_backend() {} virtual void begin() = 0; @@ -234,12 +280,184 @@ public: return false; } + // There is a set of standard SQL metadata structures that can be + // queried in a portable way - backends that are standard compliant + // do not need to override the following methods, which are intended + // to return a proper query for basic metadata statements. + + // Returns a parameterless query for the list of table names in the current schema. + virtual std::string get_table_names_query() const + { + return "select table_name as \"TABLE_NAME\"" + " from information_schema.tables" + " where table_schema = 'public'"; + } + + // Returns a query with a single parameter (table name) for the list + // of columns and their properties. + virtual std::string get_column_descriptions_query() const + { + return "select column_name as \"COLUMN_NAME\"," + " data_type as \"DATA_TYPE\"," + " character_maximum_length as \"CHARACTER_MAXIMUM_LENGTH\"," + " numeric_precision as \"NUMERIC_PRECISION\"," + " numeric_scale as \"NUMERIC_SCALE\"," + " is_nullable as \"IS_NULLABLE\"" + " from information_schema.columns" + " where table_schema = 'public' and table_name = :t"; + } + + virtual std::string create_table(const std::string & tableName) + { + return "create table " + tableName + " ("; + } + virtual std::string drop_table(const std::string & tableName) + { + return "drop table " + tableName; + } + virtual std::string truncate_table(const std::string & tableName) + { + return "truncate table " + tableName; + } + virtual std::string create_column_type(data_type dt, + int precision, int scale) + { + // PostgreSQL was selected as a baseline for the syntax: + + std::string res; + switch (dt) + { + case dt_string: + { + std::ostringstream oss; + + if (precision == 0) + { + oss << "text"; + } + else + { + oss << "varchar(" << precision << ")"; + } + + res += oss.str(); + } + break; + + case dt_date: + res += "timestamp"; + break; + + case dt_double: + { + std::ostringstream oss; + if (precision == 0) + { + oss << "numeric"; + } + else + { + oss << "numeric(" << precision << ", " << scale << ")"; + } + + res += oss.str(); + } + break; + + case dt_integer: + res += "integer"; + break; + + case dt_long_long: + res += "bigint"; + break; + + case dt_unsigned_long_long: + res += "bigint"; + break; + + case dt_blob: + res += "oid"; + break; + + case dt_xml: + res += "xml"; + break; + + default: + throw soci_error("this data_type is not supported in create_column"); + } + + return res; + } + virtual std::string add_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale) + { + return "alter table " + tableName + " add column " + columnName + + " " + create_column_type(dt, precision, scale); + } + virtual std::string alter_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale) + { + return "alter table " + tableName + " alter column " + + columnName + " type " + + create_column_type(dt, precision, scale); + } + virtual std::string drop_column(const std::string & tableName, + const std::string & columnName) + { + return "alter table " + tableName + + " drop column " + columnName; + } + virtual std::string constraint_unique(const std::string & name, + const std::string & columnNames) + { + return "constraint " + name + + " unique (" + columnNames + ")"; + } + virtual std::string constraint_primary_key(const std::string & name, + const std::string & columnNames) + { + return "constraint " + name + + " primary key (" + columnNames + ")"; + } + virtual std::string constraint_foreign_key(const std::string & name, + const std::string & columnNames, + const std::string & refTableName, + const std::string & refColumnNames) + { + return "constraint " + name + + " foreign key (" + columnNames + ")" + + " references " + refTableName + " (" + refColumnNames + ")"; + } + virtual std::string empty_blob() + { + return "lo_creat(-1)"; + } + virtual std::string nvl() + { + return "coalesce"; + } + + virtual std::string get_dummy_from_table() const = 0; + + void set_failover_callback(failover_callback & callback, session & sql) + { + failoverCallback_ = &callback; + session_ = &sql; + } + virtual std::string get_backend_name() const = 0; virtual statement_backend* make_statement_backend() = 0; virtual rowid_backend* make_rowid_backend() = 0; virtual blob_backend* make_blob_backend() = 0; + failover_callback * failoverCallback_; + session * session_; + private: SOCI_NOT_COPYABLE(session_backend) }; diff --git a/include/soci/soci-config.h b/include/soci/soci-config.h.in similarity index 81% rename from include/soci/soci-config.h rename to include/soci/soci-config.h.in index 9b26d48fd9..77fff775db 100644 --- a/include/soci/soci-config.h +++ b/include/soci/soci-config.h.in @@ -8,6 +8,9 @@ #ifndef SOCICONFIG_H_INCLUDED #define SOCICONFIG_H_INCLUDED -#include "soci/soci-platform.h" +// +// SOCI has been build with support for: +// +@CONFIGURED_VARIABLES@ #endif // SOCICONFIG_H_INCLUDED diff --git a/include/soci/soci-platform.h b/include/soci/soci-platform.h index 52369100cc..0b13374af5 100644 --- a/include/soci/soci-platform.h +++ b/include/soci/soci-platform.h @@ -19,8 +19,11 @@ #include #include #include +#include -#if defined(_MSC_VER) || defined(__MINGW32__) +#include "soci/soci-config.h" // for SOCI_HAVE_CXX_C11 + +#if defined(_MSC_VER) #define LL_FMT_FLAGS "I64" #else #define LL_FMT_FLAGS "ll" @@ -71,6 +74,9 @@ namespace std { //define DLL import/export on WIN32 #ifdef _WIN32 +# ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0502 //_WIN32_WINNT_WS03, VS2015 support: https://msdn.microsoft.com/de-de/library/6sehtctf.aspx +# endif // _WIN32_WINNT # ifdef SOCI_DLL # ifdef SOCI_SOURCE # define SOCI_DECL __declspec(dllexport) @@ -85,15 +91,53 @@ namespace std { # define SOCI_DECL #endif -#define SOCI_NOT_ASSIGNABLE(classname) \ - classname& operator=(const classname&); +// C++11 features are always available in MSVS as it has no separate C++98 +// mode, we just need to check for the minimal compiler version supporting them +// (see https://msdn.microsoft.com/en-us/library/hh567368.aspx). -#define SOCI_NOT_COPYABLE(classname) \ - classname(const classname&); \ - SOCI_NOT_ASSIGNABLE(classname) +#if defined(SOCI_HAVE_CXX_C11) || (defined(_MSC_VER) && _MSC_VER >= 1800) +# define SOCI_OVERRIDE override +#else +# define SOCI_OVERRIDE +#endif + +namespace soci +{ + +namespace cxx_details +{ + +#if defined(SOCI_HAVE_CXX_C11) || (defined(_MSC_VER) && _MSC_VER >= 1800) + template + using auto_ptr = std::unique_ptr; +#else // std::unique_ptr<> not available + using std::auto_ptr; +#endif + +} // namespace cxx_details + +} // namespace soci + +#if defined(SOCI_HAVE_CXX_C11) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #define SOCI_NOT_ASSIGNABLE(classname) \ + classname& operator=(const classname&) = delete; + #define SOCI_NOT_COPYABLE(classname) \ + classname(const classname&) = delete; \ + SOCI_NOT_ASSIGNABLE(classname) +#else // no C++11 deleted members support + #define SOCI_NOT_ASSIGNABLE(classname) \ + classname& operator=(const classname&); + #define SOCI_NOT_COPYABLE(classname) \ + classname(const classname&); \ + SOCI_NOT_ASSIGNABLE(classname) +#endif // C++11 deleted members available #define SOCI_UNUSED(x) (void)x; - +#if defined(SOCI_HAVE_CXX_C11) || (defined(_MSC_VER) && _MSC_VER >= 1900) + #define SOCI_NOEXCEPT_FALSE noexcept(false) +#else + #define SOCI_NOEXCEPT_FALSE +#endif #endif // SOCI_PLATFORM_H_INCLUDED diff --git a/include/soci/soci.h b/include/soci/soci.h index d370a0ea06..9057abf08b 100644 --- a/include/soci/soci.h +++ b/include/soci/soci.h @@ -13,6 +13,7 @@ #include "soci/backend-loader.h" #include "soci/blob.h" #include "soci/blob-exchange.h" +#include "soci/column-info.h" #include "soci/connection-pool.h" #include "soci/error.h" #include "soci/exchange-traits.h" @@ -36,6 +37,7 @@ #include "soci/type-conversion-traits.h" #include "soci/type-holder.h" #include "soci/type-ptr.h" +#include "soci/type-wrappers.h" #include "soci/unsigned-types.h" #include "soci/use.h" #include "soci/use-type.h" diff --git a/include/soci/sqlite3/soci-sqlite3.h b/include/soci/sqlite3/soci-sqlite3.h index 9b01079896..a93b321d90 100644 --- a/include/soci/sqlite3/soci-sqlite3.h +++ b/include/soci/sqlite3/soci-sqlite3.h @@ -56,7 +56,7 @@ typedef void (*sqlite3_destructor_type)(void*); namespace soci { -class sqlite3_soci_error : public soci_error +class SOCI_SQLITE3_DECL sqlite3_soci_error : public soci_error { public: sqlite3_soci_error(std::string const & msg, int result); @@ -71,16 +71,18 @@ struct sqlite3_statement_backend; struct sqlite3_standard_into_type_backend : details::standard_into_type_backend { sqlite3_standard_into_type_backend(sqlite3_statement_backend &st) - : statement_(st) {} + : statement_(st), data_(0), type_(), position_(0) + { + } - virtual void define_by_pos(int &position, - void *data, details::exchange_type type); + void define_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_fetch(); - virtual void post_fetch(bool gotData, bool calledFromFetch, - indicator *ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, bool calledFromFetch, + indicator *ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; sqlite3_statement_backend &statement_; @@ -92,17 +94,19 @@ struct sqlite3_standard_into_type_backend : details::standard_into_type_backend struct sqlite3_vector_into_type_backend : details::vector_into_type_backend { sqlite3_vector_into_type_backend(sqlite3_statement_backend &st) - : statement_(st) {} + : statement_(st), data_(0), type_(), position_(0) + { + } - void define_by_pos(int& position, void* data, details::exchange_type type); + void define_by_pos(int& position, void* data, details::exchange_type type) SOCI_OVERRIDE; - void pre_fetch(); - void post_fetch(bool gotData, indicator* ind); + void pre_fetch() SOCI_OVERRIDE; + void post_fetch(bool gotData, indicator* ind) SOCI_OVERRIDE; - void resize(std::size_t sz); - std::size_t size(); + void resize(std::size_t sz) SOCI_OVERRIDE; + std::size_t size() SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; sqlite3_statement_backend& statement_; @@ -115,15 +119,15 @@ struct sqlite3_standard_use_type_backend : details::standard_use_type_backend { sqlite3_standard_use_type_backend(sqlite3_statement_backend &st); - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type, bool readOnly); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type, bool readOnly); + void bind_by_pos(int &position, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; + void bind_by_name(std::string const &name, + void *data, details::exchange_type type, bool readOnly) SOCI_OVERRIDE; - virtual void pre_use(indicator const *ind); - virtual void post_use(bool gotData, indicator *ind); + void pre_use(indicator const *ind) SOCI_OVERRIDE; + void post_use(bool gotData, indicator *ind) SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; sqlite3_statement_backend &statement_; @@ -136,18 +140,20 @@ struct sqlite3_standard_use_type_backend : details::standard_use_type_backend struct sqlite3_vector_use_type_backend : details::vector_use_type_backend { sqlite3_vector_use_type_backend(sqlite3_statement_backend &st) - : statement_(st) {} + : statement_(st), data_(0), type_(), position_(0) + { + } - virtual void bind_by_pos(int &position, - void *data, details::exchange_type type); - virtual void bind_by_name(std::string const &name, - void *data, details::exchange_type type); + void bind_by_pos(int &position, + void *data, details::exchange_type type) SOCI_OVERRIDE; + void bind_by_name(std::string const &name, + void *data, details::exchange_type type) SOCI_OVERRIDE; - virtual void pre_use(indicator const *ind); + void pre_use(indicator const *ind) SOCI_OVERRIDE; - virtual std::size_t size(); + std::size_t size() SOCI_OVERRIDE; - virtual void clean_up(); + void clean_up() SOCI_OVERRIDE; sqlite3_statement_backend &statement_; @@ -157,6 +163,16 @@ struct sqlite3_vector_use_type_backend : details::vector_use_type_backend std::string name_; }; +struct sqlite3_column_buffer +{ + std::size_t size_; + union + { + const char *constData_; + char *data_; + }; +}; + struct sqlite3_column { bool isNull_; @@ -164,16 +180,7 @@ struct sqlite3_column union { - struct - { - std::size_t size_; - union - { - const char *constData_; - char *data_; - }; - } buffer_; - + sqlite3_column_buffer buffer_; int int32_; sqlite_api::sqlite3_int64 int64_; double double_; @@ -196,29 +203,30 @@ struct sqlite3_statement_backend : details::statement_backend { sqlite3_statement_backend(sqlite3_session_backend &session); - virtual void alloc(); - virtual void clean_up(); - virtual void prepare(std::string const &query, - details::statement_type eType); + void alloc() SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + void prepare(std::string const &query, + details::statement_type eType) SOCI_OVERRIDE; void reset_if_needed(); + void reset(); - virtual exec_fetch_result execute(int number); - virtual exec_fetch_result fetch(int number); + exec_fetch_result execute(int number) SOCI_OVERRIDE; + exec_fetch_result fetch(int number) SOCI_OVERRIDE; - virtual long long get_affected_rows(); - virtual int get_number_of_rows(); - virtual std::string get_parameter_name(int index) const; + long long get_affected_rows() SOCI_OVERRIDE; + int get_number_of_rows() SOCI_OVERRIDE; + std::string get_parameter_name(int index) const SOCI_OVERRIDE; - virtual std::string rewrite_for_procedure_call(std::string const &query); + std::string rewrite_for_procedure_call(std::string const &query) SOCI_OVERRIDE; - virtual int prepare_for_describe(); - virtual void describe_column(int colNum, data_type &dtype, - std::string &columnName); + int prepare_for_describe() SOCI_OVERRIDE; + void describe_column(int colNum, data_type &dtype, + std::string &columnName) SOCI_OVERRIDE; - virtual sqlite3_standard_into_type_backend * make_into_type_backend(); - virtual sqlite3_standard_use_type_backend * make_use_type_backend(); - virtual sqlite3_vector_into_type_backend * make_vector_into_type_backend(); - virtual sqlite3_vector_use_type_backend * make_vector_use_type_backend(); + sqlite3_standard_into_type_backend * make_into_type_backend() SOCI_OVERRIDE; + sqlite3_standard_use_type_backend * make_use_type_backend() SOCI_OVERRIDE; + sqlite3_vector_into_type_backend * make_vector_into_type_backend() SOCI_OVERRIDE; + sqlite3_vector_use_type_backend * make_vector_use_type_backend() SOCI_OVERRIDE; sqlite3_session_backend &session_; sqlite_api::sqlite3_stmt *stmt_; @@ -242,7 +250,7 @@ struct sqlite3_rowid_backend : details::rowid_backend { sqlite3_rowid_backend(sqlite3_session_backend &session); - ~sqlite3_rowid_backend(); + ~sqlite3_rowid_backend() SOCI_OVERRIDE; unsigned long value_; }; @@ -251,15 +259,15 @@ struct sqlite3_blob_backend : details::blob_backend { sqlite3_blob_backend(sqlite3_session_backend &session); - ~sqlite3_blob_backend(); + ~sqlite3_blob_backend() SOCI_OVERRIDE; - virtual std::size_t get_len(); - virtual std::size_t read(std::size_t offset, char *buf, - std::size_t toRead); - virtual std::size_t write(std::size_t offset, char const *buf, - std::size_t toWrite); - virtual std::size_t append(char const *buf, std::size_t toWrite); - virtual void trim(std::size_t newLen); + std::size_t get_len() SOCI_OVERRIDE; + std::size_t read(std::size_t offset, char *buf, + std::size_t toRead) SOCI_OVERRIDE; + std::size_t write(std::size_t offset, char const *buf, + std::size_t toWrite) SOCI_OVERRIDE; + std::size_t append(char const *buf, std::size_t toWrite) SOCI_OVERRIDE; + void trim(std::size_t newLen) SOCI_OVERRIDE; sqlite3_session_backend &session_; @@ -275,30 +283,63 @@ struct sqlite3_session_backend : details::session_backend { sqlite3_session_backend(connection_parameters const & parameters); - ~sqlite3_session_backend(); + ~sqlite3_session_backend() SOCI_OVERRIDE; - virtual void begin(); - virtual void commit(); - virtual void rollback(); + void begin() SOCI_OVERRIDE; + void commit() SOCI_OVERRIDE; + void rollback() SOCI_OVERRIDE; - virtual bool get_last_insert_id(session&, std::string const&, long&); + bool get_last_insert_id(session&, std::string const&, long&) SOCI_OVERRIDE; - virtual std::string get_backend_name() const { return "sqlite3"; } + std::string empty_blob() SOCI_OVERRIDE + { + return "x\'\'"; + } + + std::string get_dummy_from_table() const SOCI_OVERRIDE { return std::string(); } + + std::string get_backend_name() const SOCI_OVERRIDE { return "sqlite3"; } void clean_up(); - virtual sqlite3_statement_backend * make_statement_backend(); - virtual sqlite3_rowid_backend * make_rowid_backend(); - virtual sqlite3_blob_backend * make_blob_backend(); + sqlite3_statement_backend * make_statement_backend() SOCI_OVERRIDE; + sqlite3_rowid_backend * make_rowid_backend() SOCI_OVERRIDE; + sqlite3_blob_backend * make_blob_backend() SOCI_OVERRIDE; + std::string get_table_names_query() const SOCI_OVERRIDE + { + return "select name as \"TABLE_NAME\"" + " from sqlite_master where type = 'table'"; + } + std::string create_column_type(data_type dt, + int , int ) SOCI_OVERRIDE + { + switch (dt) + { + case dt_xml: + case dt_string: + return "text"; + case dt_double: + return "real"; + case dt_date: + case dt_integer: + case dt_long_long: + case dt_unsigned_long_long: + return "integer"; + case dt_blob: + return "blob"; + default: + throw soci_error("this data_type is not supported in create_column"); + } + } sqlite_api::sqlite3 *conn_; }; struct sqlite3_backend_factory : backend_factory { sqlite3_backend_factory() {} - virtual sqlite3_session_backend * make_session( - connection_parameters const & parameters) const; + sqlite3_session_backend * make_session( + connection_parameters const & parameters) const SOCI_OVERRIDE; }; extern SOCI_SQLITE3_DECL sqlite3_backend_factory const sqlite3; diff --git a/include/soci/statement.h b/include/soci/statement.h index 0791877f6f..b1cfe7d1f6 100644 --- a/include/soci/statement.h +++ b/include/soci/statement.h @@ -151,6 +151,7 @@ private: std::size_t intos_size(); std::size_t uses_size(); + void pre_exec(int num); void pre_fetch(); void pre_use(); void post_fetch(bool gotData, bool calledFromFetch); diff --git a/include/soci/type-conversion.h b/include/soci/type-conversion.h index c5b208352f..1dfd891ac9 100644 --- a/include/soci/type-conversion.h +++ b/include/soci/type-conversion.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -28,7 +28,12 @@ namespace details template struct base_value_holder { + base_value_holder() + : ownInd_(i_ok) + {} + typename type_conversion::base_type val_; + indicator ownInd_; }; // Automatically create into_type from a type_conversion @@ -42,33 +47,29 @@ public: typedef typename type_conversion::base_type base_type; conversion_into_type(T & value) - : into_type(details::base_value_holder::val_, ownInd_) + : into_type(base_value_holder::val_, base_value_holder::ownInd_) , value_(value) - , ownInd_() - , ind_(ownInd_) + , ind_(base_value_holder::ownInd_) { } conversion_into_type(T & value, indicator & ind) - : into_type(details::base_value_holder::val_, ind) + : into_type(base_value_holder::val_, ind) , value_(value) - , ownInd_(ind) // unused, just keep the pair of indicator(s) consistent , ind_(ind) { } private: - void convert_from_base() + void convert_from_base() SOCI_OVERRIDE { type_conversion::from_base( - details::base_value_holder::val_, ind_, value_); + base_value_holder::val_, ind_, value_); } T & value_; - indicator ownInd_; - // ind_ refers to either ownInd_, or the one provided by the user // in any case, ind_ refers to some valid indicator // and can be used by conversion routines @@ -81,17 +82,16 @@ private: template class conversion_use_type - : private details::base_value_holder, + : private base_value_holder, public use_type::base_type> { public: typedef typename type_conversion::base_type base_type; conversion_use_type(T & value, std::string const & name = std::string()) - : use_type(details::base_value_holder::val_, ownInd_, name) + : use_type(base_value_holder::val_, base_value_holder::ownInd_, name) , value_(value) - , ownInd_() - , ind_(ownInd_) + , ind_(base_value_holder::ownInd_) , readOnly_(false) { // TODO: likely to be removed (SHA: c166625a28f7c907318134f625ff5acea7d9a1f8) @@ -99,10 +99,9 @@ public: } conversion_use_type(T const & value, std::string const & name = std::string()) - : use_type(details::base_value_holder::val_, ownInd_, name) + : use_type(base_value_holder::val_, base_value_holder::ownInd_, name) , value_(const_cast(value)) - , ownInd_() - , ind_(ownInd_) + , ind_(base_value_holder::ownInd_) , readOnly_(true) { // TODO: likely to be removed (SHA: c166625a28f7c907318134f625ff5acea7d9a1f8) @@ -111,7 +110,7 @@ public: conversion_use_type(T & value, indicator & ind, std::string const & name = std::string()) - : use_type(details::base_value_holder::val_, ind, name) + : use_type(base_value_holder::val_, ind, name) , value_(value) , ind_(ind) , readOnly_(false) @@ -122,7 +121,7 @@ public: conversion_use_type(T const & value, indicator & ind, std::string const & name = std::string()) - : use_type(details::base_value_holder::val_, ind, name) + : use_type(base_value_holder::val_, ind, name) , value_(const_cast(value)) , ind_(ind) , readOnly_(true) @@ -131,7 +130,7 @@ public: //convert_to_base(); } - void convert_from_base() + void convert_from_base() SOCI_OVERRIDE { // NOTE: // readOnly_ flag indicates that use_type object has been generated @@ -142,21 +141,19 @@ public: if (readOnly_ == false) { type_conversion::from_base( - details::base_value_holder::val_, ind_, value_); + base_value_holder::val_, ind_, value_); } } - void convert_to_base() + void convert_to_base() SOCI_OVERRIDE { type_conversion::to_base(value_, - details::base_value_holder::val_, ind_); + base_value_holder::val_, ind_); } private: T & value_; - indicator ownInd_; - // ind_ refers to either ownInd_, or the one provided by the user // in any case, ind_ refers to some valid indicator // and can be used by conversion routines @@ -181,7 +178,7 @@ struct base_vector_holder template class conversion_into_type > - : private details::base_vector_holder, + : private base_vector_holder, public into_type::base_type> > { public: @@ -190,48 +187,73 @@ public: typename type_conversion::base_type > base_type; - conversion_into_type(std::vector & value) - : details::base_vector_holder(value.size()) - , into_type(details::base_vector_holder::vec_, ownInd_) - , value_(value) - , ownInd_() - , ind_(ownInd_) + conversion_into_type(std::vector & value, + std::size_t begin = 0, std::size_t * end = NULL) + : base_vector_holder(value.size()), + into_type( + base_vector_holder::vec_, ownInd_, begin, end), + value_(value), + ownInd_(), + ind_(ownInd_), + begin_(begin), + end_(end) { + user_ranges_ = end != NULL; } - conversion_into_type(std::vector & value, std::vector & ind) - : details::base_vector_holder(value.size()) - , into_type(details::base_vector_holder::vec_, ind) - , value_(value) - , ind_(ind) - {} + conversion_into_type(std::vector & value, std::vector & ind, + std::size_t begin = 0, std::size_t * end = NULL) + : base_vector_holder(value.size()), + into_type( + base_vector_holder::vec_, ind, begin, end), + value_(value), + ind_(ind), + begin_(begin), + end_(end) + { + user_ranges_ = end != NULL; + } - virtual std::size_t size() const + std::size_t size() const SOCI_OVERRIDE { // the user might have resized his vector in the meantime // -> synchronize the base-value mirror to have the same size std::size_t const userSize = value_.size(); - details::base_vector_holder::vec_.resize(userSize); - return userSize; + base_vector_holder::vec_.resize(userSize); + + return into_type::size(); } - virtual void resize(std::size_t sz) + void resize(std::size_t sz) SOCI_OVERRIDE { - value_.resize(sz); - ind_.resize(sz); - details::base_vector_holder::vec_.resize(sz); + into_type::resize(sz); + + std::size_t actual_size = base_vector_holder::vec_.size(); + value_.resize(actual_size); + ind_.resize(actual_size); } private: - void convert_from_base() + void convert_from_base() SOCI_OVERRIDE { - std::size_t const sz = details::base_vector_holder::vec_.size(); - - for (std::size_t i = 0; i != sz; ++i) + if (user_ranges_) { - type_conversion::from_base( - details::base_vector_holder::vec_[i], ind_[i], value_[i]); + for (std::size_t i = begin_; i != *end_; ++i) + { + type_conversion::from_base( + base_vector_holder::vec_[i], ind_[i], value_[i]); + } + } + else + { + std::size_t const sz = base_vector_holder::vec_.size(); + + for (std::size_t i = 0; i != sz; ++i) + { + type_conversion::from_base( + base_vector_holder::vec_[i], ind_[i], value_[i]); + } } } @@ -244,6 +266,10 @@ private: // and can be used by conversion routines std::vector & ind_; + std::size_t begin_; + std::size_t * end_; + bool user_ranges_; + SOCI_NOT_COPYABLE(conversion_into_type) }; @@ -252,7 +278,7 @@ private: template class conversion_use_type > - : private details::base_vector_holder, + : private base_vector_holder, public use_type::base_type> > { public: @@ -262,48 +288,109 @@ public: > base_type; conversion_use_type(std::vector & value, - std::string const & name=std::string()) - : details::base_vector_holder(value.size()) - , use_type( - details::base_vector_holder::vec_, ownInd_, name) - , value_(value) - , ownInd_() - , ind_(ownInd_) + std::string const & name=std::string()) + : base_vector_holder(value.size()), + use_type( + base_vector_holder::vec_, ownInd_, 0, NULL, name), + value_(value), + ownInd_(), + ind_(ownInd_), + begin_(0), + end_(NULL), + user_ranges_(false) { } conversion_use_type(std::vector & value, - std::vector & ind, - std::string const & name = std::string()) - : details::base_vector_holder(value.size()) - , use_type( - details::base_vector_holder::vec_, ind, name) - , value_(value) - , ind_(ind) - {} + std::size_t begin, std::size_t * end, + std::string const & name=std::string()) + : base_vector_holder(value.size()), + use_type( + base_vector_holder::vec_, ownInd_, begin, end, name), + value_(value), + ownInd_(), + ind_(ownInd_), + begin_(begin), + end_(end) + { + user_ranges_ = end != NULL; + } + + conversion_use_type(std::vector & value, + std::vector & ind, + std::string const & name = std::string()) + : base_vector_holder(value.size()), + use_type( + base_vector_holder::vec_, ind, 0, NULL, name), + value_(value), + ind_(ind), + begin_(0), + end_(NULL), + user_ranges_(false) + { + } + + conversion_use_type(std::vector & value, + std::vector & ind, + std::size_t begin, std::size_t * end, + std::string const & name = std::string()) + : base_vector_holder(value.size()), + use_type( + base_vector_holder::vec_, ind, begin, end, name), + value_(value), + ind_(ind), + begin_(begin), + end_(end) + { + user_ranges_ = end != NULL; + } private: void convert_from_base() { - std::size_t const sz = details::base_vector_holder::vec_.size(); + std::size_t const sz = base_vector_holder::vec_.size(); value_.resize(sz); ind_.resize(sz); - for (std::size_t i = 0; i != sz; ++i) + + if (user_ranges_) { - type_conversion::from_base( - details::base_vector_holder::vec_[i], value_[i], ind_[i]); + for (std::size_t i = begin_; i != *end_; ++i) + { + type_conversion::from_base( + base_vector_holder::vec_[i], value_[i], ind_[i]); + } + } + else + { + for (std::size_t i = 0; i != sz; ++i) + { + type_conversion::from_base( + base_vector_holder::vec_[i], value_[i], ind_[i]); + } } } - void convert_to_base() + void convert_to_base() SOCI_OVERRIDE { std::size_t const sz = value_.size(); - details::base_vector_holder::vec_.resize(sz); + base_vector_holder::vec_.resize(sz); ind_.resize(sz); - for (std::size_t i = 0; i != sz; ++i) + + if (user_ranges_) { - type_conversion::to_base(value_[i], - details::base_vector_holder::vec_[i], ind_[i]); + for (std::size_t i = begin_; i != *end_; ++i) + { + type_conversion::to_base(value_[i], + base_vector_holder::vec_[i], ind_[i]); + } + } + else + { + for (std::size_t i = 0; i != sz; ++i) + { + type_conversion::to_base(value_[i], + base_vector_holder::vec_[i], ind_[i]); + } } } @@ -316,6 +403,10 @@ private: // and can be used by conversion routines std::vector & ind_; + std::size_t begin_; + std::size_t * end_; + bool user_ranges_; + SOCI_NOT_COPYABLE(conversion_use_type) }; @@ -331,6 +422,22 @@ into_type_ptr do_into(T & t, indicator & ind, user_type_tag) return into_type_ptr(new conversion_into_type(t, ind)); } +template +into_type_ptr do_into(std::vector & t, + std::size_t begin, size_t * end, user_type_tag) +{ + return into_type_ptr( + new conversion_into_type >(t, begin, end)); +} + +template +into_type_ptr do_into(std::vector & t, std::vector & ind, + std::size_t begin, size_t * end, user_type_tag) +{ + return into_type_ptr( + new conversion_into_type >(t, ind, begin, end)); +} + template use_type_ptr do_use(T & t, std::string const & name, user_type_tag) { @@ -357,6 +464,42 @@ use_type_ptr do_use(T const & t, indicator & ind, return use_type_ptr(new conversion_use_type(t, ind, name)); } +template +use_type_ptr do_use(std::vector & t, + std::size_t begin, size_t * end, + std::string const & name, user_type_tag) +{ + return use_type_ptr( + new conversion_use_type >(t, begin, end, name)); +} + +template +use_type_ptr do_use(const std::vector & t, + std::size_t begin, size_t * end, + std::string const & name, user_type_tag) +{ + return use_type_ptr( + new conversion_use_type >(t, begin, end, name)); +} + +template +use_type_ptr do_use(std::vector & t, std::vector & ind, + std::size_t begin, size_t * end, + std::string const & name, user_type_tag) +{ + return use_type_ptr( + new conversion_use_type >(t, ind, begin, end, name)); +} + +template +use_type_ptr do_use(const std::vector & t, std::vector & ind, + std::size_t begin, size_t * end, + std::string const & name, user_type_tag) +{ + return use_type_ptr( + new conversion_use_type >(t, ind, begin, end, name)); +} + } // namespace details } // namespace soci diff --git a/include/soci/type-holder.h b/include/soci/type-holder.h index 2b51d96599..61c9613bc0 100644 --- a/include/soci/type-holder.h +++ b/include/soci/type-holder.h @@ -7,6 +7,8 @@ #ifndef SOCI_TYPE_HOLDER_H_INCLUDED #define SOCI_TYPE_HOLDER_H_INCLUDED + +#include "soci/soci-platform.h" // std #include @@ -52,7 +54,7 @@ class type_holder : public holder { public: type_holder(T * t) : t_(t) {} - ~type_holder() { delete t_; } + ~type_holder() SOCI_OVERRIDE { delete t_; } template TypeValue value() const { return *t_; } diff --git a/include/soci/type-wrappers.h b/include/soci/type-wrappers.h new file mode 100644 index 0000000000..04a5a2f2fb --- /dev/null +++ b/include/soci/type-wrappers.h @@ -0,0 +1,33 @@ +// +// Copyright (C) 2016 Maciej Sobczak +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef SOCI_TYPE_WRAPPERS_H_INCLUDED +#define SOCI_TYPE_WRAPPERS_H_INCLUDED + +namespace soci +{ + +// These wrapper types can be used by the application +// with 'into' and 'use' elements in order to guide the library +// in selecting specialized methods for binding and transferring data; +// if the target database does not provide any such specialized methods, +// it is expected to handle these wrappers as equivalent +// to their contained field types. + +struct xml_type +{ + std::string value; +}; + +struct long_string +{ + std::string value; +}; + +} // namespace soci + +#endif // SOCI_TYPE_WRAPPERS_H_INCLUDED diff --git a/include/soci/use-type.h b/include/soci/use-type.h index 8762cfbbd9..3a431d0975 100644 --- a/include/soci/use-type.h +++ b/include/soci/use-type.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -32,6 +32,7 @@ public: virtual void bind(statement_impl & st, int & position) = 0; virtual std::string get_name() const = 0; virtual void dump_value(std::ostream& os) const = 0; + virtual void pre_exec(int num) = 0; virtual void pre_use() = 0; virtual void post_use(bool gotData) = 0; virtual void clean_up() = 0; @@ -72,10 +73,10 @@ public: //convert_to_base(); } - virtual ~standard_use_type(); - virtual void bind(statement_impl & st, int & position); - virtual std::string get_name() const { return name_; } - virtual void dump_value(std::ostream& os) const; + ~standard_use_type() SOCI_OVERRIDE; + void bind(statement_impl & st, int & position) SOCI_OVERRIDE; + std::string get_name() const SOCI_OVERRIDE { return name_; } + void dump_value(std::ostream& os) const SOCI_OVERRIDE; virtual void * get_data() { return data_; } // conversion hook (from arbitrary user type to base type) @@ -83,12 +84,13 @@ public: virtual void convert_from_base() {} protected: - virtual void pre_use(); + void pre_use() SOCI_OVERRIDE; private: - virtual void post_use(bool gotData); - virtual void clean_up(); - virtual std::size_t size() const { return 1; } + void pre_exec(int num) SOCI_OVERRIDE; + void post_use(bool gotData) SOCI_OVERRIDE; + void clean_up() SOCI_OVERRIDE; + std::size_t size() const SOCI_OVERRIDE { return 1; } void* data_; exchange_type type_; @@ -107,6 +109,20 @@ public: : data_(data) , type_(type) , ind_(NULL) + , begin_(0) + , end_(NULL) + , name_(name) + , backEnd_(NULL) + {} + + vector_use_type(void* data, exchange_type type, + std::size_t begin, std::size_t * end, + std::string const& name = std::string()) + : data_(data) + , type_(type) + , ind_(NULL) + , begin_(begin) + , end_(end) , name_(name) , backEnd_(NULL) {} @@ -117,24 +133,42 @@ public: : data_(data) , type_(type) , ind_(&ind) + , begin_(0) + , end_(NULL) , name_(name) , backEnd_(NULL) {} - ~vector_use_type(); + vector_use_type(void* data, exchange_type type, + std::vector const& ind, + std::size_t begin, std::size_t * end, + std::string const& name = std::string()) + : data_(data) + , type_(type) + , ind_(&ind) + , begin_(begin) + , end_(end) + , name_(name) + , backEnd_(NULL) + {} + + ~vector_use_type() SOCI_OVERRIDE; private: - virtual void bind(statement_impl& st, int & position); - virtual std::string get_name() const { return name_; } - virtual void dump_value(std::ostream& os) const; - virtual void pre_use(); - virtual void post_use(bool) { /* nothing to do */ } - virtual void clean_up(); - virtual std::size_t size() const; + void bind(statement_impl& st, int & position) SOCI_OVERRIDE; + std::string get_name() const SOCI_OVERRIDE { return name_; } + void dump_value(std::ostream& os) const SOCI_OVERRIDE; + void pre_exec(int num) SOCI_OVERRIDE; + void pre_use() SOCI_OVERRIDE; + void post_use(bool) SOCI_OVERRIDE { /* nothing to do */ } + void clean_up() SOCI_OVERRIDE; + std::size_t size() const SOCI_OVERRIDE; void* data_; exchange_type type_; std::vector const* ind_; + std::size_t begin_; + std::size_t * end_; std::string name_; vector_use_type_backend * backEnd_; @@ -179,22 +213,46 @@ public: static_cast(exchange_traits::x_type), name) {} + use_type(std::vector& v, std::size_t begin, std::size_t * end, + std::string const& name = std::string()) + : vector_use_type(&v, + static_cast(exchange_traits::x_type), begin, end, name) + {} + use_type(std::vector const& v, std::string const& name = std::string()) : vector_use_type(const_cast*>(&v), static_cast(exchange_traits::x_type), name) {} + use_type(std::vector const& v, std::size_t begin, std::size_t * end, + std::string const& name = std::string()) + : vector_use_type(const_cast*>(&v), + static_cast(exchange_traits::x_type), begin, end, name) + {} + use_type(std::vector& v, std::vector const& ind, std::string const& name = std::string()) : vector_use_type(&v, static_cast(exchange_traits::x_type), ind, name) {} + use_type(std::vector& v, std::vector const& ind, + std::size_t begin, std::size_t * end, std::string const& name = std::string()) + : vector_use_type(&v, + static_cast(exchange_traits::x_type), ind, begin, end, name) + {} + use_type(std::vector const& v, std::vector const& ind, std::string const& name = std::string()) : vector_use_type(const_cast *>(&v), static_cast(exchange_traits::x_type), ind, name) {} + + use_type(std::vector const& v, std::vector const& ind, + std::size_t begin, std::size_t * end, std::string const& name = std::string()) + : vector_use_type(const_cast *>(&v), + static_cast(exchange_traits::x_type), ind, begin, end, name) + {} }; // helper dispatchers for basic types @@ -239,6 +297,42 @@ use_type_ptr do_use(T const & t, std::vector & ind, return use_type_ptr(new use_type(t, ind, name)); } +template +use_type_ptr do_use(std::vector & t, + std::size_t begin, std::size_t * end, + std::string const & name, basic_type_tag) +{ + return use_type_ptr( + new use_type >(t, begin, end, name)); +} + +template +use_type_ptr do_use(const std::vector & t, + std::size_t begin, std::size_t * end, + std::string const & name, basic_type_tag) +{ + return use_type_ptr( + new use_type >(t, begin, end, name)); +} + +template +use_type_ptr do_use(std::vector & t, std::vector & ind, + std::size_t begin, std::size_t * end, + std::string const & name, basic_type_tag) +{ + return use_type_ptr( + new use_type >(t, ind, begin, end, name)); +} + +template +use_type_ptr do_use(const std::vector & t, std::vector & ind, + std::size_t begin, std::size_t * end, + std::string const & name, basic_type_tag) +{ + return use_type_ptr( + new use_type >(t, ind, begin, end, name)); +} + } // namespace details } // namesapce soci diff --git a/include/soci/use.h b/include/soci/use.h index f97263723a..cf8c32a9e7 100644 --- a/include/soci/use.h +++ b/include/soci/use.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -73,6 +73,44 @@ details::use_container, details::no_indicator > use(std::vector &t, const std::string &name = std::string()) { return details::use_container, details::no_indicator>(t, name); } +// vectors with index ranges + +template +details::use_type_ptr use(std::vector & t, + std::size_t begin, std::size_t & end, + const std::string &name = std::string()) +{ + return details::do_use(t, begin, &end, name, + typename details::exchange_traits >::type_family()); +} + +template +details::use_type_ptr use(const std::vector & t, + std::size_t begin, std::size_t & end, + const std::string &name = std::string()) +{ + return details::do_use(t, begin, &end, name, + typename details::exchange_traits >::type_family()); +} + +template +details::use_type_ptr use(std::vector & t, std::vector & ind, + std::size_t begin, std::size_t & end, + const std::string &name = std::string()) +{ + return details::do_use(t, ind, begin, &end, name, + typename details::exchange_traits >::type_family()); +} + +template +details::use_type_ptr use(const std::vector & t, std::vector & ind, + std::size_t begin, std::size_t & end, + const std::string &name = std::string()) +{ + return details::do_use(t, ind, begin, &end, name, + typename details::exchange_traits >::type_family()); +} + } // namespace soci #endif // SOCI_USE_H_INCLUDED diff --git a/include/soci/values-exchange.h b/include/soci/values-exchange.h index 1d2fe5f6ee..d7caeb5323 100644 --- a/include/soci/values-exchange.h +++ b/include/soci/values-exchange.h @@ -46,7 +46,7 @@ public: : v_(v) {} - virtual void bind(details::statement_impl & st, int & /*position*/) + void bind(details::statement_impl & st, int & /*position*/) SOCI_OVERRIDE { v_.uppercase_column_names(st.session_.get_uppercase_column_names()); @@ -54,7 +54,7 @@ public: st.bind(v_); } - virtual std::string get_name() const + std::string get_name() const SOCI_OVERRIDE { std::ostringstream oss; @@ -74,21 +74,23 @@ public: return oss.str(); } - virtual void dump_value(std::ostream& os) const + void dump_value(std::ostream& os) const SOCI_OVERRIDE { // TODO: Dump all columns. os << ""; } - virtual void post_use(bool /*gotData*/) + void pre_exec(int /* num */) SOCI_OVERRIDE {} + + void post_use(bool /*gotData*/) SOCI_OVERRIDE { v_.reset_get_counter(); convert_from_base(); } - virtual void pre_use() {convert_to_base();} - virtual void clean_up() {v_.clean_up();} - virtual std::size_t size() const { return 1; } + void pre_use() SOCI_OVERRIDE {convert_to_base();} + void clean_up() SOCI_OVERRIDE {v_.clean_up();} + std::size_t size() const SOCI_OVERRIDE { return 1; } // these are used only to re-dispatch to derived class // (the derived class might be generated automatically by @@ -122,7 +124,7 @@ public: : into_type(v.get_row(), ind), v_(v) {} - void clean_up() + void clean_up() SOCI_OVERRIDE { v_.clean_up(); } diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..92ea50528e --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,51 @@ +site_name: SOCI 4.0.0 +site_description: SOCI - The C++ Database Access Library +repo_url: https://github.com/SOCI/soci/ +copyright: Copyright © 2017 Maciej Sobczak and SOCI Team. + +pages: + - Home: index.md + - Overview: + - Getting Started: quickstart.md + - Installation: installation.md + - Library Structure: structure.md + - License: license.md + - FAQ: faq.md + - User Guide: + - Connections: connections.md + - Queries: queries.md + - Data Binding: binding.md + - Data Indicators: indicators.md + - Data Types: types.md + - LOBs: lobs.md + - Statemets: statements.md + - Transactions: transactions.md + - Procedures: procedures.md + - Errors: errors.md + - Logging: logging.md + - Interfaces: interfaces.md + - Backends: + - Features: backends/index.md + - DB2: backends/db2.md + - Firebird: backends/firebird.md + - MySQL: backends/mysql.md + - ODBC: backends/odbc.md + - Oracle: backends/oracle.md + - PostgreSQL: backends/postgresql.md + - SQLite3: backends/sqlite3.md + - Miscellaneous: + - Beyond SQL: beyond.md + - Multi-threading: multithreading.md + - Boost: boost.md + - Utilities: utilities.md + - Vagrant: vagrant.md + - API: + - Client API: api/client.md + - Backend API: api/backend.md + - Ada: + - languages/ada/index.md + - languages/ada/concepts.md + - languages/ada/idioms.md + - languages/ada/reference.md + +#theme: readthedocs diff --git a/scripts/build.bat b/scripts/build.bat new file mode 100644 index 0000000000..2ba3fe43ca --- /dev/null +++ b/scripts/build.bat @@ -0,0 +1,79 @@ +@echo off +rem Runs CMake to configure SOCI for Visual Studio 2017. +rem Runs MSBuild to build the generated solution. +rem +rem Usage: +rem 1. Copy build.bat to build.locale.bat (git ignored file) +rem 2. Make your adjustments in the CONFIGURATION section below +rem 3. Run build.local.bat 32|64 +rem 4. Optionally, run devenv.exe SOCI{32|64}.sln from command line + +rem ### CONFIGURATION ##################################### +rem ### Connection strings for tests (alternatively, use command line-c option) +rem ### For example, SQL Server LocalDB instance, MySQL and PostgreSQL on the Vagrant VM. +set TEST_CONNSTR_MSSQL="" +set TEST_CONNSTR_MYSQL="" +set TEST_CONNSTR_PGSQL="" +setlocal +set BOOST_ROOT=C:/local/boost_1_59_0 +rem ####################################################### + +set U="" +if /I "%2"=="U" set U=U +if [%1]==[32] goto :32 +if [%1]==[64] goto :64 +goto :Usage + +:32 +set P=32 +set MSBUILDP=Win32 +set GENERATOR="Visual Studio 15 2017" +goto :Build + +:64 +set P=64 +set MSBUILDP=x64 +set GENERATOR="Visual Studio 15 2017 Win64" +goto :Build + +:Build +set BUILDDIR=_build%P%%U% +mkdir %BUILDDIR% +pushd %BUILDDIR% +cmake.exe ^ + -G %GENERATOR% ^ + -DWITH_BOOST=ON ^ + -DWITH_DB2=ON ^ + -DWITH_FIREBIRD=ON ^ + -DWITH_MYSQL=ON ^ + -DWITH_ODBC=ON ^ + -DWITH_ORACLE=ON ^ + -DWITH_POSTGRESQL=ON ^ + -DWITH_SQLITE3=ON ^ + -DSOCI_EMPTY=ON ^ + -DSOCI_EMPTY_TEST_CONNSTR="" ^ + -DSOCI_DB2=ON ^ + -DSOCI_DB2_TEST_CONNSTR="" ^ + -DSOCI_FIREBIRD=ON ^ + -DSOCI_FIREBIRD_TEST_CONNSTR="" ^ + -DSOCI_MYSQL=ON ^ + -DSOCI_MYSQL_TEST_CONNSTR="" ^ + -DSOCI_ODBC=ON ^ + -DSOCI_ODBC_TEST_MYSQL_CONNSTR="" ^ + -DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="" ^ + -DSOCI_ORACLE=ON ^ + -DSOCI_ORACLE_TEST_CONNSTR="" ^ + -DSOCI_POSTGRESQL=ON ^ + -DSOCI_POSTGRESQL_TEST_CONNSTR="" ^ + -DSOCI_SQLITE3=ON ^ + -DSOCI_SQLITE3_TEST_CONNSTR="" ^ + .. +move SOCI.sln SOCI%P%%U%.sln +rem msbuild.exe SOCI%P%%U%.sln /p:Configuration=Release /p:Platform=%MSBUILDP% +popd +goto :EOF + +:Usage +@echo build.bat +@echo Usage: build.bat [32 or 64] +exit /B 1 diff --git a/scripts/changelog.sh b/scripts/changelog.sh new file mode 100644 index 0000000000..3eac2a54f3 --- /dev/null +++ b/scripts/changelog.sh @@ -0,0 +1,24 @@ +#!/bin/bash -e +# Generates CHANGELOG.md for SOCI project. +# +# Runs github-changelog-generator to automatically generate changelog +# from your tags, closed issues and merged pull requests. +# +# Requirements: +# - https://github.com/skywinder/github-changelog-generator +# +# Copyright (c) 2017 Mateusz Loskot +# +if [[ -z "$CHANGELOG_GITHUB_TOKEN" ]]; then + echo "Environment variable CHANGELOG_GITHUB_TOKEN not found!" >&2 + echo "GitHub API token is required to avoid hitting limit of requests per hour." >&2 + exit 1 +fi + +if ! type "github_changelog_generator" > /dev/null; then + echo "github_changelog_generator not found." >&2 + echo "Go to https://github.com/skywinder/github-changelog-generator" + exit 1 +fi + +github_changelog_generator --verbose --date-format "%Y-%m-%d" diff --git a/bin/ci/before_install.sh b/scripts/travis/before_install.sh similarity index 76% rename from bin/ci/before_install.sh rename to scripts/travis/before_install.sh index ebd5aeb700..719a70d7ac 100755 --- a/bin/ci/before_install.sh +++ b/scripts/travis/before_install.sh @@ -3,16 +3,17 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 16126D3A3E5C1192 sudo add-apt-repository -y ppa:apt-fast/stable + sudo apt-get update -qq -y sudo apt-get install -qq -y apt-fast sudo apt-fast update -qq -y -sudo apt-fast install -qq -y libboost-dev libboost-date-time-dev +sudo apt-fast install -qq -y libboost-dev libboost-date-time-dev valgrind -before_install="${TRAVIS_BUILD_DIR}/bin/ci/before_install_${SOCI_TRAVIS_BACKEND}.sh" +before_install="${TRAVIS_BUILD_DIR}/scripts/travis/before_install_${SOCI_TRAVIS_BACKEND}.sh" if [ -x ${before_install} ]; then echo "Running ${before_install}" ${before_install} diff --git a/bin/ci/before_install_db2.sh b/scripts/travis/before_install_db2.sh similarity index 92% rename from bin/ci/before_install_db2.sh rename to scripts/travis/before_install_db2.sh index 52953bb8ab..e4a0e7a76e 100755 --- a/bin/ci/before_install_db2.sh +++ b/scripts/travis/before_install_db2.sh @@ -4,11 +4,9 @@ # Copyright (c) 2013 Brian R. Toonen # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh - sudo bash -c 'echo "deb http://archive.canonical.com/ubuntu precise partner" >> /etc/apt/sources.list' -sudo apt-get update -qq -sudo apt-get install -y db2exc +sudo apt-get update -qq -y +sudo apt-get install -qq -y db2exc echo "Running db2profile and db2rmln" sudo /bin/sh -c '. ~db2inst1/sqllib/db2profile ; $DB2DIR/cfg/db2rmln' diff --git a/bin/ci/before_install_firebird.sh b/scripts/travis/before_install_firebird.sh similarity index 91% rename from bin/ci/before_install_firebird.sh rename to scripts/travis/before_install_firebird.sh index e03bfd4575..fc56f82420 100755 --- a/bin/ci/before_install_firebird.sh +++ b/scripts/travis/before_install_firebird.sh @@ -3,7 +3,7 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh sudo apt-get install -qq firebird2.5-super firebird2.5-dev diff --git a/bin/ci/before_install_odbc.sh b/scripts/travis/before_install_odbc.sh similarity index 84% rename from bin/ci/before_install_odbc.sh rename to scripts/travis/before_install_odbc.sh index e9f9346b4d..605e5544fe 100755 --- a/bin/ci/before_install_odbc.sh +++ b/scripts/travis/before_install_odbc.sh @@ -3,7 +3,7 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh sudo apt-get install -qq \ tar bzip2 \ diff --git a/scripts/travis/before_install_oracle.sh b/scripts/travis/before_install_oracle.sh new file mode 100755 index 0000000000..8ad956112f --- /dev/null +++ b/scripts/travis/before_install_oracle.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Script performs non-interactive installation of Oracle XE on Linux +# +# Uses Oracle downloader and installer from https://github.com/cbandy/travis-oracle +# +# set -ex +source ${TRAVIS_BUILD_DIR}/scripts/travis/oracle.sh + +# Install Oracle and travis-oracle requirements +sudo apt-get install -y libaio1 rpm + +curl -s -o $HOME/.nvm/nvm.sh https://raw.githubusercontent.com/creationix/nvm/v0.31.0/nvm.sh +source $HOME/.nvm/nvm.sh +nvm install stable +node --version + +# Install travis-oracle +wget 'https://github.com/cbandy/travis-oracle/archive/v2.0.3.tar.gz' +mkdir -p .travis/oracle +tar x -C .travis/oracle --strip-components=1 -f v2.0.3.tar.gz + +# Download Oracle (do not use Travis CI secure environment!) +export ORACLE_LOGIN_pass="T$(echo $ORACLE_LOGIN_userid | rev)#2017" +bash .travis/oracle/download.sh + +# Install Oracle +bash .travis/oracle/install.sh diff --git a/bin/ci/before_script.sh b/scripts/travis/before_script.sh similarity index 61% rename from bin/ci/before_script.sh rename to scripts/travis/before_script.sh index 681bbf9c85..4c1ae2147b 100755 --- a/bin/ci/before_script.sh +++ b/scripts/travis/before_script.sh @@ -3,9 +3,9 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh -before_script="${TRAVIS_BUILD_DIR}/bin/ci/before_script_${SOCI_TRAVIS_BACKEND}.sh" +before_script="${TRAVIS_BUILD_DIR}/scripts/travis/before_script_${SOCI_TRAVIS_BACKEND}.sh" if [ -x ${before_script} ]; then echo "Running ${before_script}" ${before_script} diff --git a/bin/ci/before_script_db2.sh b/scripts/travis/before_script_db2.sh similarity index 85% rename from bin/ci/before_script_db2.sh rename to scripts/travis/before_script_db2.sh index 9480c4d50a..29b5dd02ce 100755 --- a/bin/ci/before_script_db2.sh +++ b/scripts/travis/before_script_db2.sh @@ -4,7 +4,7 @@ # Copyright (c) 2013 Brian R. Toonen # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh sudo -u db2inst1 -i db2 "CREATE DATABASE SOCITEST" sudo -u db2inst1 -i db2 "ACTIVATE DATABASE SOCITEST" diff --git a/bin/ci/before_script_firebird.sh b/scripts/travis/before_script_firebird.sh similarity index 87% rename from bin/ci/before_script_firebird.sh rename to scripts/travis/before_script_firebird.sh index 35954571a4..f940b234d1 100755 --- a/bin/ci/before_script_firebird.sh +++ b/scripts/travis/before_script_firebird.sh @@ -3,7 +3,7 @@ # # Mateusz Loskot , http://github.com/SOCI # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh isql-fb -z -q -i /dev/null # --version echo 'CREATE DATABASE "LOCALHOST:/tmp/soci_test.fdb" PAGE_SIZE = 16384;' > /tmp/create_soci_test.sql diff --git a/bin/ci/before_script_mysql.sh b/scripts/travis/before_script_mysql.sh similarity index 79% rename from bin/ci/before_script_mysql.sh rename to scripts/travis/before_script_mysql.sh index b32f31492a..52076703c4 100755 --- a/bin/ci/before_script_mysql.sh +++ b/scripts/travis/before_script_mysql.sh @@ -3,7 +3,7 @@ # # Mateusz Loskot , http://github.com/SOCI # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh mysql --version mysql -e 'create database soci_test;' diff --git a/bin/ci/before_script_odbc.sh b/scripts/travis/before_script_odbc.sh similarity index 83% rename from bin/ci/before_script_odbc.sh rename to scripts/travis/before_script_odbc.sh index 1bff332e5f..9494732ed0 100755 --- a/bin/ci/before_script_odbc.sh +++ b/scripts/travis/before_script_odbc.sh @@ -3,7 +3,7 @@ # # Mateusz Loskot , http://github.com/SOCI # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh mysql --version mysql -e 'create database soci_test;' diff --git a/bin/ci/before_script_oracle.sh b/scripts/travis/before_script_oracle.sh similarity index 61% rename from bin/ci/before_script_oracle.sh rename to scripts/travis/before_script_oracle.sh index 91bac34143..a63958ed48 100755 --- a/bin/ci/before_script_oracle.sh +++ b/scripts/travis/before_script_oracle.sh @@ -7,39 +7,35 @@ # Changes: # - Check connection as user for testing # - -# for some reason the file is not found any more here => creating user in install script -# Load Oracle environment variables so that we could run `sqlplus`. -. /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/bin/oracle_env.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/oracle.sh echo "ORACLE_HOME=${ORACLE_HOME}" echo "ORACLE_SID=${ORACLE_SID}" -# create user for testing -echo "CREATE USER travis IDENTIFIED BY travis;" | \ -sqlplus -S -L sys/admin AS SYSDBA +# travis-oracle installer created travis user w/o password +echo "ALTER USER travis IDENTIFIED BY travis;" | \ +$ORACLE_HOME/bin/sqlplus -S -L sys/admin AS SYSDBA echo "grant connect, resource to travis;" | \ -sqlplus -S -L sys/admin AS SYSDBA +$ORACLE_HOME/bin/sqlplus -S -L sys/admin AS SYSDBA echo "grant create session, alter any procedure to travis;" | \ -sqlplus -S -L sys/admin AS SYSDBA +$ORACLE_HOME/bin/sqlplus -S -L sys/admin AS SYSDBA # to enable xa recovery, see: https://community.oracle.com/thread/378954 echo "grant select on sys.dba_pending_transactions to travis;" | \ -sqlplus -S -L sys/admin AS SYSDBA +$ORACLE_HOME/bin/sqlplus -S -L sys/admin AS SYSDBA echo "grant select on sys.pending_trans$ to travis;" | \ -sqlplus -S -L sys/admin AS SYSDBA +$ORACLE_HOME/bin/sqlplus -S -L sys/admin AS SYSDBA echo "grant select on sys.dba_2pc_pending to travis;" | \ -sqlplus -S -L sys/admin AS SYSDBA +$ORACLE_HOME/bin/sqlplus -S -L sys/admin AS SYSDBA echo "grant execute on sys.dbms_system to travis;" | \ -sqlplus -S -L sys/admin AS SYSDBA +$ORACLE_HOME/bin/sqlplus -S -L sys/admin AS SYSDBA # increase default=40 value of processes to prevent ORA-12520 failures while testing echo "alter system set processes=100 scope=spfile;" | \ -sqlplus -S -L sys/admin AS SYSDBA +$ORACLE_HOME/bin/sqlplus -S -L sys/admin AS SYSDBA # check connection as user for testing echo "Connecting using travis/travis@XE" echo "SELECT * FROM product_component_version;" | \ -sqlplus -S -L travis/travis@XE - +$ORACLE_HOME/bin/sqlplus -S -L travis/travis@XE diff --git a/bin/ci/before_script_postgresql.sh b/scripts/travis/before_script_postgresql.sh similarity index 80% rename from bin/ci/before_script_postgresql.sh rename to scripts/travis/before_script_postgresql.sh index 039a4a6e55..54272cb750 100755 --- a/bin/ci/before_script_postgresql.sh +++ b/scripts/travis/before_script_postgresql.sh @@ -3,7 +3,7 @@ # # Mateusz Loskot , http://github.com/SOCI # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh psql --version psql -c 'create database soci_test;' -U postgres diff --git a/scripts/travis/before_script_valgrind.sh b/scripts/travis/before_script_valgrind.sh new file mode 100755 index 0000000000..daa72e6f39 --- /dev/null +++ b/scripts/travis/before_script_valgrind.sh @@ -0,0 +1,12 @@ +#!/bin/bash -e +# Sets up environment for SOCI backend at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# Copyright (c) 2015 Sergei Nikulov +# +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh + +mysql --version +mysql -e 'create database soci_test;' +psql --version +psql -c 'create database soci_test;' -U postgres diff --git a/bin/ci/common.sh b/scripts/travis/common.sh old mode 100644 new mode 100755 similarity index 78% rename from bin/ci/common.sh rename to scripts/travis/common.sh index feb15a771c..3f43bfea8f --- a/bin/ci/common.sh +++ b/scripts/travis/common.sh @@ -32,3 +32,8 @@ run_test() { ctest -V --output-on-failure "$@" . } + +run_test_memcheck() +{ + valgrind --leak-check=full --suppressions=${TRAVIS_BUILD_DIR}/valgrind.suppress --error-exitcode=1 --trace-children=yes ctest -V --output-on-failure "$@" . +} diff --git a/scripts/travis/oracle.sh b/scripts/travis/oracle.sh new file mode 100755 index 0000000000..a9acece9bb --- /dev/null +++ b/scripts/travis/oracle.sh @@ -0,0 +1,12 @@ +# Definitions used by SOCI when building Oracle backend at travis-ci.org +# +# Copyright (c) 2015 Vadim Zeitlin +# +# Notice that this file is not executable, it is supposed to be sourced from +# the other files. + +# Oracle environment required by https://github.com/cbandy/travis-oracle +export ORACLE_COOKIE=sqldev +export ORACLE_FILE=oracle11g/xe/oracle-xe-11.2.0-1.0.x86_64.rpm.zip +export ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe +export ORACLE_SID=XE diff --git a/bin/ci/script.sh b/scripts/travis/script.sh similarity index 69% rename from bin/ci/script.sh rename to scripts/travis/script.sh index 538dd53b40..018c0e2dcc 100755 --- a/bin/ci/script.sh +++ b/scripts/travis/script.sh @@ -3,7 +3,7 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh # prepare build directory builddir="${TRAVIS_BUILD_DIR}/_build" @@ -11,6 +11,6 @@ mkdir -p ${builddir} cd ${builddir} # build and run tests -SCRIPT=${TRAVIS_BUILD_DIR}/bin/ci/script_${SOCI_TRAVIS_BACKEND}.sh +SCRIPT=${TRAVIS_BUILD_DIR}/scripts/travis/script_${SOCI_TRAVIS_BACKEND}.sh echo "Running ${SCRIPT}" ${SCRIPT} diff --git a/bin/ci/script_db2.sh b/scripts/travis/script_db2.sh similarity index 84% rename from bin/ci/script_db2.sh rename to scripts/travis/script_db2.sh index 1120c0d458..41f6ff0847 100755 --- a/bin/ci/script_db2.sh +++ b/scripts/travis/script_db2.sh @@ -4,9 +4,11 @@ # Copyright (c) 2013 Brian R. Toonen # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DSOCI_ASAN=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ diff --git a/bin/ci/script_empty.sh b/scripts/travis/script_empty.sh similarity index 80% rename from bin/ci/script_empty.sh rename to scripts/travis/script_empty.sh index af7982d694..1dcd1aed9b 100755 --- a/bin/ci/script_empty.sh +++ b/scripts/travis/script_empty.sh @@ -3,9 +3,11 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DSOCI_ASAN=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ diff --git a/bin/ci/script_firebird.sh b/scripts/travis/script_firebird.sh similarity index 84% rename from bin/ci/script_firebird.sh rename to scripts/travis/script_firebird.sh index 1ad00f540d..356c3ac657 100755 --- a/bin/ci/script_firebird.sh +++ b/scripts/travis/script_firebird.sh @@ -3,9 +3,11 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DSOCI_ASAN=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ diff --git a/bin/ci/script_mysql.sh b/scripts/travis/script_mysql.sh similarity index 82% rename from bin/ci/script_mysql.sh rename to scripts/travis/script_mysql.sh index 8589a21f4a..0c86831131 100755 --- a/bin/ci/script_mysql.sh +++ b/scripts/travis/script_mysql.sh @@ -3,9 +3,11 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DSOCI_ASAN=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ diff --git a/bin/ci/script_odbc.sh b/scripts/travis/script_odbc.sh similarity index 76% rename from bin/ci/script_odbc.sh rename to scripts/travis/script_odbc.sh index 5193857daf..a9b7a8135d 100755 --- a/bin/ci/script_odbc.sh +++ b/scripts/travis/script_odbc.sh @@ -3,7 +3,7 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh ODBC_TEST=${PWD}/../tests/odbc if test ! -d ${ODBC_TEST}; then @@ -12,6 +12,8 @@ if test ! -d ${ODBC_TEST}; then fi cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DSOCI_ASAN=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ @@ -23,8 +25,6 @@ cmake \ -DSOCI_ORACLE=OFF \ -DSOCI_POSTGRESQL=OFF \ -DSOCI_SQLITE3=OFF \ - -DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="FILEDSN=${ODBC_TEST}/test-postgresql.dsn;" \ - -DSOCI_ODBC_TEST_MYSQL_CONNSTR="FILEDSN=${ODBC_TEST}/test-mysql.dsn;" \ .. run_make diff --git a/bin/ci/script_oracle.sh b/scripts/travis/script_oracle.sh similarity index 77% rename from bin/ci/script_oracle.sh rename to scripts/travis/script_oracle.sh index f9863da425..c1c92eab51 100755 --- a/bin/ci/script_oracle.sh +++ b/scripts/travis/script_oracle.sh @@ -3,10 +3,12 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh -source ${TRAVIS_BUILD_DIR}/bin/ci/oracle.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/oracle.sh cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DSOCI_ASAN=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DWITH_BOOST=OFF \ -DSOCI_TESTS=ON \ diff --git a/bin/ci/script_postgresql.sh b/scripts/travis/script_postgresql.sh similarity index 83% rename from bin/ci/script_postgresql.sh rename to scripts/travis/script_postgresql.sh index 892d74372e..515705a726 100755 --- a/bin/ci/script_postgresql.sh +++ b/scripts/travis/script_postgresql.sh @@ -3,9 +3,11 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DSOCI_ASAN=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ diff --git a/bin/ci/script_sqlite3.sh b/scripts/travis/script_sqlite3.sh similarity index 82% rename from bin/ci/script_sqlite3.sh rename to scripts/travis/script_sqlite3.sh index 5dfc0d0027..105a9d3f90 100755 --- a/bin/ci/script_sqlite3.sh +++ b/scripts/travis/script_sqlite3.sh @@ -3,9 +3,11 @@ # # Copyright (c) 2013 Mateusz Loskot # -source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DSOCI_ASAN=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ diff --git a/scripts/travis/script_valgrind.sh b/scripts/travis/script_valgrind.sh new file mode 100755 index 0000000000..070f21652a --- /dev/null +++ b/scripts/travis/script_valgrind.sh @@ -0,0 +1,17 @@ +#!/bin/bash -e +# Builds and tests SOCI at travis-ci.org +# +# Copyright (c) 2013 Mateusz Loskot +# Copyright (c) 2015 Sergei Nikulov +# +source ${TRAVIS_BUILD_DIR}/scripts/travis/common.sh + +cmake \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DCMAKE_BUILD_TYPE=Debug \ + .. + +run_make +run_test_memcheck diff --git a/scripts/vagrant/bootstrap.sh b/scripts/vagrant/bootstrap.sh new file mode 100755 index 0000000000..61e03c07ac --- /dev/null +++ b/scripts/vagrant/bootstrap.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Part of Vagrant virtual development environments for SOCI + +# Pre-installation +echo "Bootstrap: setting common environment in /etc/profile.d/vagrant-soci.sh" +sudo sh -c "cat /vagrant/scripts/vagrant/common.env > /etc/profile.d/vagrant-soci.sh" +export DEBIAN_FRONTEND="noninteractive" +# Installation +# TODO: Switch to apt-fast when it is avaiable for Trusty +sudo apt-get update -y -q +sudo apt-get -o Dpkg::Options::='--force-confnew' -y -q install \ + build-essential \ + avahi-daemon \ + zip +# Post-installation +## Configure Avahi to enable .local hostnames used to connect between VMs. +echo "Bootstrap: updating /etc/nsswitch.conf to configure Avahi/MDNS for .local lookup" +sudo sed -i 's/hosts:.*/hosts: files mdns4_minimal [NOTFOUND=return] dns myhostname/g' /etc/nsswitch.conf diff --git a/scripts/vagrant/build.sh b/scripts/vagrant/build.sh new file mode 100755 index 0000000000..98794deaa7 --- /dev/null +++ b/scripts/vagrant/build.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Part of Vagrant virtual development environments for SOCI + +# Builds and tests SOCI from git master branch +source /vagrant/scripts/vagrant/common.env + +# Build SOCI in /home/vagrant on Linux filesystem, +# outside /vagrant which is VM shared directory. +# Otherwise, CMake will fail: +# CMake Error: cmake_symlink_library: System Error: Protocol error +# Explanation from https://github.com/mitchellh/vagrant/issues/713 +# The VirtualBox shared folder filesystem doesn't allow symlinks, unfortunately. +# Your only option is to deploy outside of the shared folders. +if [[ ! -d "${SOCI_BUILD}" ]] ; then + mkdir -p ${SOCI_BUILD} +fi + +echo "Build: building SOCI from sources in ${SOCI_HOME} to build in ${SOCI_BUILD}" +cd ${SOCI_BUILD} && \ +cmake \ + -DSOCI_CXX_C11=ON \ + -DSOCI_TESTS=ON \ + -DSOCI_STATIC=OFF \ + -DSOCI_DB2=ON \ + -DSOCI_ODBC=OFF \ + -DSOCI_ORACLE=OFF \ + -DSOCI_EMPTY=ON \ + -DSOCI_FIREBIRD=ON \ + -DSOCI_MYSQL=ON \ + -DSOCI_POSTGRESQL=ON \ + -DSOCI_SQLITE3=ON \ + -DSOCI_DB2_TEST_CONNSTR:STRING="DATABASE=${SOCI_USER}\\;hostname=${SOCI_DB2_HOST}\\;UID=${SOCI_DB2_USER}\\;PWD=${SOCI_DB2_PASS}\\;ServiceName=50000\\;Protocol=TCPIP\\;" \ + -DSOCI_FIREBIRD_TEST_CONNSTR:STRING="service=LOCALHOST:/tmp/soci.fdb user=${SOCI_USER} password=${SOCI_PASS}" \ + -DSOCI_MYSQL_TEST_CONNSTR:STRING="host=localhost db=${SOCI_USER} user=${SOCI_USER} password=${SOCI_PASS}" \ + -DSOCI_POSTGRESQL_TEST_CONNSTR:STRING="host=localhost port=5432 dbname=${SOCI_USER} user=${SOCI_USER} password=${SOCI_PASS}" \ + ${SOCI_HOME} && \ +make +echo "Build: building DONE" + +# Do not run tests during provisioning, thay may fail terribly, so just build +# and let to run them manually after developer vagrant ssh'ed to the VM. +echo "Build: ready to test SOCI by running: cd ${SOCI_BUILD}; ctest -V --output-on-failure ." diff --git a/scripts/vagrant/common.env b/scripts/vagrant/common.env new file mode 100644 index 0000000000..7f9740590b --- /dev/null +++ b/scripts/vagrant/common.env @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Part of Vagrant virtual development environments for SOCI +export SOCI_HOME=/vagrant +export SOCI_BUILD=/home/vagrant/soci-build +export SOCI_HOST=vmsoci.local +# SOCI_USER has two purposes: database user name, database name +export SOCI_USER=soci +export SOCI_PASS=soci +export SOCI_DB2_HOST=vmdb2.local +export SOCI_DB2_USER=db2inst1 +export SOCI_DB2_PASS=db2inst1 diff --git a/scripts/vagrant/db2.sh b/scripts/vagrant/db2.sh new file mode 100755 index 0000000000..eda1eaa89f --- /dev/null +++ b/scripts/vagrant/db2.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Part of Vagrant virtual development environments for SOCI + +# Installs DB2 Express-C 9.7 +# Pre-installation +source /vagrant/scripts/vagrant/common.env +export DEBIAN_FRONTEND="noninteractive" +# Installation +/vagrant/scripts/travis/before_install_db2.sh +# Post-installation +## Let's be gentle to DB2 and try to not to recreate existing databases +echo "db2: checking if ${SOCI_USER} database exists" +# First time guest machine is created, there is no database, hence test for: +# SQL1031N The database directory cannot be found on the indicated file system. +sudo -u db2inst1 -i db2 "LIST DATABASE DIRECTORY" | grep SQL1031N +NODB=$? +sudo -u db2inst1 -i db2 "LIST DATABASE DIRECTORY" | grep -i ${SOCI_USER} +HASSOCI=$? +if [[ $NODB -eq 0 || $HASSOCI -ne 0 ]]; then + echo "db2: creating database ${SOCI_USER}" + sudo -u db2inst1 -i db2 "CREATE DATABASE ${SOCI_USER}" + sudo -u db2inst1 -i db2 "ACTIVATE DATABASE ${SOCI_USER}" +else + echo "db2: database ${SOCI_USER} (may) exists, skipping" +fi diff --git a/scripts/vagrant/db2cli.sh b/scripts/vagrant/db2cli.sh new file mode 100755 index 0000000000..99102cfec6 --- /dev/null +++ b/scripts/vagrant/db2cli.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Part of Vagrant virtual development environments for SOCI + +# Installs DB2 CLI Driver 10.5 (64-bit and 32-bit) from +# IBM Data Server Driver Package (DS Driver) + +# Prerequisities (manual download if wget fails): +# 1. Go to http://www-01.ibm.com/support/docview.wss?uid=swg21385217 +# 2. Go to "IBM Data Server Driver Package (DS Driver)" +# 3. Download "IBM Data Server Driver Package (Linux AMD64 and Intel EM64T)" +# 4. Copy the package to '{SOCI SOURCE TREE ROOT}/tmp' directory. +DSPKG="ibm_data_server_driver_package_linuxx64_v10.5.tar.gz" +DSPREFIX=/opt/ibm +DSDRIVER=${DSPREFIX}/dsdriver +SOCITMP=/vagrant/tmp + +# Try to download from known location +echo "DB2CLI: downloading ${DSPKG} from github.com/rorymckinley/gold_importer" +wget -q https://raw.githubusercontent.com/rorymckinley/gold_importer/master/3rdparty/ibm_data_server_driver_package_linuxx64_v10.5.tar.gz + +# Check if driver package is available +if [[ ! -f ${DSPKG} && ! -f ${SOCITMP}/${DSPKG} ]]; then + echo "DB2CLI: missing ${SOCITMP}/${DSPKG}" + echo "DB2CLI: try manual download, then provision VM to install" + echo "DB2CLI: meanwhile, skipping driver installation" + exit 0 +fi + +if [ -f ${SOCITMP}/${DSPKG} ]; then + DSPKG=${SOCITMP}/${DSPKG} +fi + +# If the drivers is already installed, skip re-installation +# because installDSDriver script does not support it +if [[ -d "${DSDRIVER}" && -f ${DSDRIVER}/db2profile ]]; then + echo "DB2CLI: ${DSDRIVER} already installed, skipping" + echo "DB2CLI: if necessary, upgrade manually running: ${DSDRIVER}/installDSDriver -upgrade ${DSDRIVER}" + exit 0 +fi + +# Korn shell required by installDSDriver script +sudo apt-get -o Dpkg::Options::='--force-confnew' -y -q install \ + ksh + +echo "DB2CLI: unpacking ${DSPKG} to ${DSPREFIX}" +sudo mkdir -p ${DSPREFIX} +sudo tar -zxf ${DSPKG} -C ${DSPREFIX} +echo "DB2CLI: running ${DSDRIVER}/installDSDriver script" +sudo ${DSDRIVER}/installDSDriver +echo "DB2CLI: cat ${DSDRIVER}/installDSDriver.log" +cat ${DSDRIVER}/installDSDriver.log +echo +echo "DB2CLI: installing ${DSDRIVER}/db2profile in /etc/profile.d/db2profile.sh for 64-bit env" +sudo cp ${DSDRIVER}/db2profile /etc/profile.d/db2profile.sh +echo "DB2CLI: For 32-bit, manually source ${DSDRIVER}/db2profile32 to overwrite 64-bit env" diff --git a/scripts/vagrant/devel.sh b/scripts/vagrant/devel.sh new file mode 100755 index 0000000000..67ce137e0f --- /dev/null +++ b/scripts/vagrant/devel.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Part of Vagrant virtual development environments for SOCI + +# Installs essentials and core dependencies +# Pre-installation +source /vagrant/scripts/vagrant/common.env +export DEBIAN_FRONTEND="noninteractive" +# Trusty has CMake 2.8, we need CMake 3 +sudo apt-get install software-properties-common +sudo add-apt-repository ppa:george-edison55/cmake-3.x +sudo apt-get update -y -q +# Installation +sudo apt-get -o Dpkg::Options::='--force-confnew' -y -q install \ + build-essential \ + cmake \ + firebird2.5-dev \ + git \ + libboost-dev \ + libboost-date-time-dev \ + libmyodbc \ + libmysqlclient-dev \ + libpq-dev \ + libsqlite3-dev \ + odbc-postgresql \ + unixodbc-dev \ + valgrind +# Post-installation diff --git a/scripts/vagrant/firebird.sh b/scripts/vagrant/firebird.sh new file mode 100755 index 0000000000..e7e785859c --- /dev/null +++ b/scripts/vagrant/firebird.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Part of Vagrant virtual development environments for SOCI + +# Installs PostgreSQL with 'soci' user and database +# Pre-installation +source /vagrant/scripts/vagrant/common.env +export DEBIAN_FRONTEND="noninteractive" +# FIXME: these debconf lines should automate the Firebird config but do/may not :( +# However, keep them enabled to allow smooth(er) apt-get/dpkg operations. +# Othwerise, installation of firebird packages may fail, randomly. +# We work around this bug with Expect script below used to update SYSDBA password. +sudo debconf-set-selections <<< "firebird2.5-super shared/firebird/enabled boolean true" +sudo debconf-set-selections <<< "firebird2.5-super shared/firebird/sysdba_password/first_install password masterkey" +# Installation +sudo apt-get -o Dpkg::Options::='--force-confnew' -y -q install \ + expect \ + firebird2.5-super +# Post-installation +FB_ORIGINAL_PASS=`sudo cat /etc/firebird/2.5/SYSDBA.password \ + | grep ISC_PASSWORD | sed -e 's/ISC_PASSWORD="\([^"]*\)".*/\1/'` +echo "Firebird: dpkg-reconfigure resetting sysdba password from ${FB_ORIGINAL_PASS} to ${SOCI_PASS}" +export DEBIAN_FRONTEND="readline" +# Expect script feeding dpkg-reconfigure prompts +sudo /usr/bin/expect - << ENDMARK > /dev/null +spawn dpkg-reconfigure firebird2.5-super -freadline +expect "Enable Firebird server?" +send "Y\r" + +expect "Password for SYSDBA:" +send "${SOCI_PASS}\r" + +# done +expect eof +ENDMARK +# End of Expect script +export DEBIAN_FRONTEND="noninteractive" +echo "Firebird: cat /etc/firebird/2.5/SYSDBA.password" +sudo cat /etc/firebird/2.5/SYSDBA.password | grep ISC_ +echo +echo "Firebird: creating user ${SOCI_USER}" +sudo gsec -user sysdba -pass ${SOCI_PASS} -delete ${SOCI_USER} +sudo gsec -user sysdba -pass ${SOCI_PASS} -add ${SOCI_USER} -pw ${SOCI_PASS} -admin yes +echo "Firebird: creating database /tmp/${SOCI_USER}.fdb" +sudo rm -f /tmp/${SOCI_USER}.fdb +echo "CREATE DATABASE \"LOCALHOST:/tmp/${SOCI_USER}.fdb\";" \ + | isql-fb -q -u ${SOCI_USER} -p ${SOCI_PASS} +echo "Firebird: restarting" +sudo service firebird2.5-super restart +echo "Firebird: DONE" diff --git a/scripts/vagrant/mysql.sh b/scripts/vagrant/mysql.sh new file mode 100755 index 0000000000..89eb3bf515 --- /dev/null +++ b/scripts/vagrant/mysql.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Part of Vagrant virtual development environments for SOCI + +# Installs MySQL with 'soci' user and database +# Pre-installation +source /vagrant/scripts/vagrant/common.env +export DEBIAN_FRONTEND="noninteractive" +sudo debconf-set-selections <<< "mysql-server mysql-server/root_password password ${SOCI_PASS}" +sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again password ${SOCI_PASS}" +# Installation +sudo apt-get -o Dpkg::Options::='--force-confnew' -y -q install \ + mysql-server +# Post-installation +echo "MySQL: updating /etc/mysql/my.cnf" +sudo sed -i "s/bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/my.cnf +echo "MySQL: setting root password to ${SOCI_PASS}" +mysql -uroot -p${SOCI_PASS} -e \ + "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '${SOCI_PASS}' WITH GRANT OPTION; FLUSH PRIVILEGES;" +echo "MySQL: creating user ${SOCI_USER}" +mysql -uroot -p${SOCI_PASS} -e \ + "GRANT ALL PRIVILEGES ON ${SOCI_USER}.* TO '${SOCI_USER}'@'%' IDENTIFIED BY '${SOCI_PASS}' WITH GRANT OPTION" +mysql -uroot -p${SOCI_PASS} -e "DROP DATABASE IF EXISTS ${SOCI_USER}" +echo "MySQL: creating database ${SOCI_USER}" +mysql -uroot -p${SOCI_PASS} -e "CREATE DATABASE ${SOCI_USER}" +echo "MySQL: restarting" +sudo service mysql restart +echo "MySQL: DONE" diff --git a/scripts/vagrant/postgresql.sh b/scripts/vagrant/postgresql.sh new file mode 100755 index 0000000000..21f0d135f8 --- /dev/null +++ b/scripts/vagrant/postgresql.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Part of Vagrant virtual development environments for SOCI + +# Installs PostgreSQL with 'soci' user and database +# Pre-installation +source /vagrant/scripts/vagrant/common.env +export DEBIAN_FRONTEND="noninteractive" +# Installation +sudo apt-get -o Dpkg::Options::='--force-confnew' -y -q install \ + postgresql \ + postgresql-contrib +# Post-installation +echo "PostgreSQL: updating /etc/postgresql/9.3/main/postgresql.conf" +sudo sed -i "s/#listen_address.*/listen_addresses '*'/" /etc/postgresql/9.3/main/postgresql.conf +echo "PostgreSQL: updating /etc/postgresql/9.3/main/pg_hba.conf" +sudo cat >> /etc/postgresql/9.3/main/pg_hba.conf < Get-ODBCList -ComputerName "g1","g2","g3" + + ODBC ComputerName + ---- ------------ + SQL Server g1 + Microsoft Access Driver (*.mdb, *.accdb) g1 + Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb) g1 + Microsoft Access dBASE Driver (*.dbf, *.ndx, *.mdx) g1 + Microsoft Access Text Driver (*.txt, *.csv) g1 + SQL Server Native Client 10.0 g1 + SQL Native Client g1 + SQL Server g2 + SQL Server g3 + + .NOTES + Author: Michal Gajda + Blog : http://commandlinegeeks.com/ + #> + [CmdletBinding( + SupportsShouldProcess=$True, + ConfirmImpact="Low" + )] + Param + ( + [parameter(ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + [String[]]$ComputerName = $env:COMPUTERNAME + ) + + Begin + { + $Key = "SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers" + $KeyWow64 = "SOFTWARE\Wow6432Node\ODBC\ODBCINST.INI\ODBC Drivers" + $Type = [Microsoft.Win32.RegistryHive]::LocalMachine + } #End Begin + + Process + { + Foreach($Computer in $ComputerName) + { + If(Test-Connection $Computer -Quiet) + { + Try + { + $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Type, $Computer) + $regSubKey = $regKey.OpenSubKey($Key) + $regSubKeyWow64 = $regKey.OpenSubKey($KeyWow64) + } + Catch + { + Write-Error $_.Exception.Message -ErrorAction Stop + } + + if($regSubKey -and $regSubKeyWow64) + { + $Bits = 64 + } #End if $regSubKey -and $regSubKeyWow64 + else + { + $Bits = 32 + } #End Else $regSubKey -and $regSubKeyWow64 + + Foreach($val in $regSubKey.GetValueNames()) + { + $log = New-Object PSObject -Property @{ + ComputerName = $Computer + ODBC = $val + Bits = $Bits + } #End New-Object PSObject + + $log + } #End ForEach $val in $regSubKey.GetValueNames() + + if($Bits -eq 64) + { + Foreach($val in $regSubKeyWow64.GetValueNames()) + { + $log = New-Object PSObject -Property @{ + ComputerName = $Computer + ODBC = $val + Bits = 32 + } #End New-Object PSObject + + $log + } #End ForEach $val in $regSubKeyWow64.GetValueNames() + } #End If $Bits -eq 64 + } #End If Test-Connection $Computer -Quiet + } #End Foreach $Computer in $ComputerName + } #End Process + + End{} +} #In The End :) \ No newline at end of file diff --git a/scripts/windows/mssql_db_create.sql b/scripts/windows/mssql_db_create.sql new file mode 100644 index 0000000000..c5a0b6795f --- /dev/null +++ b/scripts/windows/mssql_db_create.sql @@ -0,0 +1 @@ +IF db_id('soci_test') IS NULL BEGIN CREATE DATABASE [soci_test] END; diff --git a/src/backends/CMakeLists.txt b/src/backends/CMakeLists.txt index a2bfee21eb..871e151ef4 100644 --- a/src/backends/CMakeLists.txt +++ b/src/backends/CMakeLists.txt @@ -21,6 +21,7 @@ foreach(dep ${SOCI_BACKENDS_DB_DEPENDENCIES}) else() set(${depUP}_FOUND OFF) endif() + set(SOCI_HAVE_${depUP} OFF CACHE INTERNAL "${depUP} backend") endforeach() # get all files in backends @@ -40,6 +41,7 @@ foreach(dir ${backend_dirs}) string(TOUPPER ${dir} dirUP) if(${dirUP}_FOUND AND WITH_${dirUP}) add_subdirectory(${dir}) + set(SOCI_HAVE_${dirUP} ON CACHE INTERNAL "${dirUP} backend") endif() endif() endif() diff --git a/src/backends/db2/session.cpp b/src/backends/db2/session.cpp index 52576a7c85..1d63d9a339 100644 --- a/src/backends/db2/session.cpp +++ b/src/backends/db2/session.cpp @@ -10,6 +10,8 @@ #include "soci/db2/soci-db2.h" #include "soci/connection-parameters.h" +#include + #ifdef _MSC_VER #pragma warning(disable:4355) #endif @@ -17,6 +19,8 @@ using namespace soci; using namespace soci::details; +const char* soci::db2_option_driver_complete = "db2.driver_complete"; + const std::string db2_soci_error::sqlState(std::string const & msg,const SQLSMALLINT htype,const SQLHANDLE hndl) { std::ostringstream ss(msg, std::ostringstream::app); @@ -45,15 +49,6 @@ void db2_session_backend::parseKeyVal(std::string const & keyVal) { std::string key=keyVal.substr(0,delimiter); std::string value=keyVal.substr(delimiter+1,keyVal.length()); - if (!key.compare("DSN")) { - this->dsn=value; - } - if (!key.compare("Uid")) { - this->username=value; - } - if (!key.compare("Pwd")) { - this->password=value; - } this->autocommit=true; //Default value if (!key.compare("autocommit")) { if (!value.compare("off")) { @@ -63,6 +58,7 @@ void db2_session_backend::parseKeyVal(std::string const & keyVal) { } /* DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;AutoCommit=off */ +/* DATABASE=SAMPLE;hostname=server.com;UID=db2inst1;PWD=db2inst1;ServiceName=50000;Protocol=TCPIP; */ void db2_session_backend::parseConnectString(std::string const & connectString) { std::string processingString(connectString); size_t delimiter=processingString.find_first_of(";"); @@ -81,8 +77,10 @@ db2_session_backend::db2_session_backend( connection_parameters const & parameters) : in_transaction(false) { - parseConnectString(parameters.get_connect_string()); - SQLRETURN cliRC = SQL_SUCCESS; + std::string const& connectString = parameters.get_connect_string(); + parseConnectString(connectString); + + SQLRETURN cliRC = SQL_ERROR; /* Prepare handles */ cliRC = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&hEnv); @@ -111,15 +109,48 @@ db2_session_backend::db2_session_backend( } /* Connect to database */ - cliRC = SQLConnect(hDbc, const_cast((const SQLCHAR *) dsn.c_str()), SQL_NTS, - const_cast((const SQLCHAR *) username.c_str()), SQL_NTS, - const_cast((const SQLCHAR *) password.c_str()), SQL_NTS); + // NOTE: SQLDriverConnect preparation steps below copied from ODBC backend. + SQLCHAR outConnString[1024]; + SQLSMALLINT strLength; + + // Prompt the user for any missing information (typically UID/PWD) in the + // connection string by default but allow overriding this using "prompt" + // option. + SQLHWND hwnd_for_prompt = NULL; + unsigned completion = SQL_DRIVER_COMPLETE; + std::string completionString; + if (parameters.get_option(db2_option_driver_complete, completionString)) + { + // The value of the option is supposed to be just the integer value of + // one of SQL_DRIVER_XXX constants but don't check for the exact value in + // case more of them are added in the future, the ODBC driver will return + // an error if we pass it an invalid value anyhow. + if (std::sscanf(completionString.c_str(), "%u", &completion) != 1) + { + throw soci_error("Invalid non-numeric driver completion option value \"" + + completionString + "\"."); + } + } + + #ifdef _WIN32 + if (completion != SQL_DRIVER_NOPROMPT) + hwnd_for_prompt = ::GetDesktopWindow(); + #endif // _WIN32 + + cliRC = SQLDriverConnect(hDbc, hwnd_for_prompt, + reinterpret_cast(const_cast(connectString.c_str())), + (SQLSMALLINT)connectString.size(), + outConnString, 1024, &strLength, + static_cast(completion)); + if (cliRC != SQL_SUCCESS) { std::string msg=db2_soci_error::sqlState("Error connecting to database",SQL_HANDLE_DBC,hDbc); SQLFreeHandle(SQL_HANDLE_DBC,hDbc); SQLFreeHandle(SQL_HANDLE_ENV,hEnv); throw db2_soci_error(msg,cliRC); } + + connection_string_.assign((const char*)outConnString, strLength); } db2_session_backend::~db2_session_backend() diff --git a/src/backends/db2/standard-use-type.cpp b/src/backends/db2/standard-use-type.cpp index ff3dfb968c..6c935fa022 100644 --- a/src/backends/db2/standard-use-type.cpp +++ b/src/backends/db2/standard-use-type.cpp @@ -99,6 +99,8 @@ void *db2_standard_use_type_backend::prepare_for_bind( break; case x_blob: + case x_xmltype: + case x_longstring: break; case x_statement: case x_rowid: diff --git a/src/backends/db2/statement.cpp b/src/backends/db2/statement.cpp index 1e15559b00..73b7432bbd 100644 --- a/src/backends/db2/statement.cpp +++ b/src/backends/db2/statement.cpp @@ -144,7 +144,7 @@ db2_statement_backend::execute(int number ) } cliRC = SQLExecute(hStmt); - if (cliRC != SQL_SUCCESS && cliRC != SQL_SUCCESS_WITH_INFO) + if (cliRC != SQL_SUCCESS && cliRC != SQL_SUCCESS_WITH_INFO && cliRC != SQL_NO_DATA) { throw db2_soci_error(db2_soci_error::sqlState("Statement execution error",SQL_HANDLE_STMT,hStmt),cliRC); } diff --git a/src/backends/db2/vector-into-type.cpp b/src/backends/db2/vector-into-type.cpp index 262e52835b..2e5932d83a 100644 --- a/src/backends/db2/vector-into-type.cpp +++ b/src/backends/db2/vector-into-type.cpp @@ -146,9 +146,12 @@ void db2_vector_into_type_backend::define_by_pos( } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + case x_statement: + case x_rowid: + case x_blob: + case x_xmltype: + case x_longstring: + throw soci_error("Unsupported type for vector into parameter"); } SQLRETURN cliRC = SQLBindCol(statement_.hStmt, static_cast(position++), @@ -192,12 +195,30 @@ void db2_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) std::vector &v(*vp); - char *pos = buf; + const char *pos = buf; std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) + for (std::size_t i = 0; i != vsize; ++i, pos += colSize) { - v[i].assign(pos, strlen(pos)); - pos += colSize; + // See ODBC backend for explanation, this code for determining + // the string length is exactly the same as there. + SQLLEN const len = indVec[i]; + if (len == -1) + { + v[i].clear(); + continue; + } + + const char* end = pos + len; + while (end != pos) + { + if (*--end != ' ') + { + ++end; + break; + } + } + + v[i].assign(pos, end - pos); } } else if (type == x_stdtm) @@ -321,6 +342,8 @@ void db2_vector_into_type_backend::resize(std::size_t sz) case x_statement: break; // not supported case x_rowid: break; // not supported case x_blob: break; // not supported + case x_xmltype: break; // not supported + case x_longstring:break; // not supported } } @@ -387,6 +410,8 @@ std::size_t db2_vector_into_type_backend::size() case x_statement: break; // not supported case x_rowid: break; // not supported case x_blob: break; // not supported + case x_xmltype: break; // not supported + case x_longstring:break; // not supported } return sz; diff --git a/src/backends/db2/vector-use-type.cpp b/src/backends/db2/vector-use-type.cpp index e29dc63970..dc1cca9910 100644 --- a/src/backends/db2/vector-use-type.cpp +++ b/src/backends/db2/vector-use-type.cpp @@ -139,18 +139,20 @@ void db2_vector_use_type_backend::prepare_for_bind(void *&data, SQLUINTEGER &siz prepare_indicators(vecSize); for (std::size_t i = 0; i != vecSize; ++i) { - std::size_t sz = v[i].length() + 1; // add one for null + std::size_t sz = v[i].length(); indVec[i] = static_cast(sz); maxSize = sz > maxSize ? sz : maxSize; } + maxSize++; // For terminating nul. + buf = new char[maxSize * vecSize]; memset(buf, 0, maxSize * vecSize); char *pos = buf; for (std::size_t i = 0; i != vecSize; ++i) { - strncpy(pos, v[i].c_str(), v[i].length()); + memcpy(pos, v[i].c_str(), v[i].length()); pos += maxSize; } @@ -179,6 +181,8 @@ void db2_vector_use_type_backend::prepare_for_bind(void *&data, SQLUINTEGER &siz case x_statement: break; // not supported case x_rowid: break; // not supported case x_blob: break; // not supported + case x_xmltype: break; // not supported + case x_longstring:break; // not supported } colSize = size; @@ -307,7 +311,7 @@ void db2_vector_use_type_backend::pre_use(indicator const *ind) { // no indicators - treat all fields as OK std::size_t const vsize = size(); - for (std::size_t i = 0; i != vsize; ++i, ++ind) + for (std::size_t i = 0; i != vsize; ++i) { // for strings we have already set the values if (type != x_stdstring) @@ -381,6 +385,8 @@ std::size_t db2_vector_use_type_backend::size() case x_statement: break; // not supported case x_rowid: break; // not supported case x_blob: break; // not supported + case x_xmltype: break; // not supported + case x_longstring:break; // not supported } return sz; diff --git a/src/backends/firebird/blob.cpp b/src/backends/firebird/blob.cpp index 2193e0239f..ea433a3add 100644 --- a/src/backends/firebird/blob.cpp +++ b/src/backends/firebird/blob.cpp @@ -13,8 +13,8 @@ using namespace soci; using namespace soci::details::firebird; firebird_blob_backend::firebird_blob_backend(firebird_session_backend &session) - : session_(session), from_db_(false), bhp_(0), loaded_(false), - max_seg_size_(0) + : session_(session), bid_(), from_db_(false), bhp_(0), data_(), + loaded_(false), max_seg_size_(0) {} firebird_blob_backend::~firebird_blob_backend() @@ -247,13 +247,26 @@ void firebird_blob_backend::save() if (data_.size() > 0) { // write data - if (isc_put_segment(stat, &bhp_, - static_cast(data_.size()), &data_[0])) + size_t size = data_.size(); + size_t offset = 0; + // Segment Size : Specifying the BLOB segment is throwback to times past, when applications for working + // with BLOB data were written in C(Embedded SQL) with the help of the gpre pre - compiler. + // Nowadays, it is effectively irrelevant.The segment size for BLOB data is determined by the client side and is usually larger than the data page size, + // in any case. + do { - throw_iscerror(stat); - } + unsigned short segmentSize = 0xFFFF; //last unsigned short number + if (size - offset < segmentSize) //if content size is less than max segment size or last data segment is about to be written + segmentSize = static_cast(size - offset); + //write segment + if (isc_put_segment(stat, &bhp_, segmentSize, &data_[0]+offset)) + { + throw_iscerror(stat); + } + offset += segmentSize; + } + while (offset < size); } - cleanUp(); from_db_ = true; } diff --git a/src/backends/firebird/standard-into-type.cpp b/src/backends/firebird/standard-into-type.cpp index 30e754dedf..0025163485 100644 --- a/src/backends/firebird/standard-into-type.cpp +++ b/src/backends/firebird/standard-into-type.cpp @@ -11,6 +11,8 @@ #include "firebird/common.h" #include "soci/soci.h" +#include + using namespace soci; using namespace soci::details; using namespace soci::details::firebird; @@ -119,11 +121,38 @@ void firebird_standard_into_type_backend::exchangeData() blob->assign(*reinterpret_cast(buf_)); } break; + + case x_longstring: + copy_from_blob(exchange_type_cast(data_).value); + break; + + case x_xmltype: + copy_from_blob(exchange_type_cast(data_).value); + break; + default: throw soci_error("Into element used with non-supported type."); } // switch } +void firebird_standard_into_type_backend::copy_from_blob(std::string& out) +{ + firebird_blob_backend blob(statement_.session_); + blob.assign(*reinterpret_cast(buf_)); + + std::size_t const len_total = blob.get_len(); + out.resize(len_total); + + std::size_t const len_read = blob.read(0, &out[0], len_total); + if (len_read != len_total) + { + std::ostringstream os; + os << "Read " << len_read << " bytes instead of expected " + << len_total << " from Firebird text blob object"; + throw soci_error(os.str()); + } +} + void firebird_standard_into_type_backend::clean_up() { if (buf_ != NULL) diff --git a/src/backends/firebird/standard-use-type.cpp b/src/backends/firebird/standard-use-type.cpp index 5b216fa828..da0d304ef2 100644 --- a/src/backends/firebird/standard-use-type.cpp +++ b/src/backends/firebird/standard-use-type.cpp @@ -147,11 +147,28 @@ void firebird_standard_use_type_backend::exchangeData() memcpy(buf_, &blob->bid_, var->sqllen); } break; + + case x_longstring: + copy_to_blob(exchange_type_cast(data_).value); + break; + + case x_xmltype: + copy_to_blob(exchange_type_cast(data_).value); + break; + default: throw soci_error("Use element used with non-supported type."); } // switch } +void firebird_standard_use_type_backend::copy_to_blob(const std::string& in) +{ + blob_ = new firebird_blob_backend(statement_.session_); + blob_->append(in.c_str(), in.length()); + blob_->save(); + memcpy(buf_, &blob_->bid_, sizeof(blob_->bid_)); +} + void firebird_standard_use_type_backend::post_use( bool /* gotData */, indicator * /* ind */) { @@ -175,6 +192,13 @@ void firebird_standard_use_type_backend::clean_up() delete [] buf_; buf_ = NULL; } + + if (blob_) + { + delete blob_; + blob_ = NULL; + } + std::vector::iterator it = std::find(statement_.uses_.begin(), statement_.uses_.end(), this); if (it != statement_.uses_.end()) diff --git a/src/backends/firebird/vector-into-type.cpp b/src/backends/firebird/vector-into-type.cpp index 2de719ab9d..6a522f30ba 100644 --- a/src/backends/firebird/vector-into-type.cpp +++ b/src/backends/firebird/vector-into-type.cpp @@ -93,7 +93,7 @@ void firebird_vector_into_type_backend::exchangeData(std::size_t row) break; case x_stdtm: { - std::tm data; + std::tm data = std::tm(); tmDecode(var->sqltype, buf_, &data); setIntoVector(data_, row, data); } diff --git a/src/backends/mysql/common.cpp b/src/backends/mysql/common.cpp index 4c1ae7dea7..7a06e00dcb 100644 --- a/src/backends/mysql/common.cpp +++ b/src/backends/mysql/common.cpp @@ -6,68 +6,11 @@ // #include "common.h" -#include "soci/soci-backend.h" -#include "soci-mktime.h" #include #include #include #include -namespace // anonymous -{ - -// helper function for parsing decimal data (for std::tm) -long parse10(char const *&p1, char *&p2, const char *msg) -{ - long v = std::strtol(p1, &p2, 10); - if (p2 != p1) - { - p1 = p2 + 1; - return v; - } - else - { - throw soci::soci_error(msg); - } -} - -} // namespace anonymous - - -void soci::details::mysql::parse_std_tm(char const *buf, std::tm &t) -{ - char const *p1 = buf; - char *p2; - long year, month, day; - long hour = 0, minute = 0, second = 0; - - const char *errMsg = "Cannot convert data to std::tm."; - - if (strchr(buf, '-') != NULL) - { - year = parse10(p1, p2, errMsg); - month = parse10(p1, p2, errMsg); - day = parse10(p1, p2, errMsg); - } - else - { - year = 2000; - month = 1; - day = 1; - } - - - if (strchr(buf, ':') != NULL) - { - // there is also the time of day available - hour = parse10(p1, p2, errMsg); - minute = parse10(p1, p2, errMsg); - second = parse10(p1, p2, errMsg); - } - - details::mktime_from_ymdhms(t, year, month, day, hour, minute, second); -} - char * soci::details::mysql::quote(MYSQL * conn, const char *s, size_t len) { char *retv = new char[2 * len + 3]; diff --git a/src/backends/mysql/common.h b/src/backends/mysql/common.h index eae0d92743..0d822c4b16 100644 --- a/src/backends/mysql/common.h +++ b/src/backends/mysql/common.h @@ -27,9 +27,6 @@ namespace details namespace mysql { -// helper function for parsing datetime values -void parse_std_tm(char const *buf, std::tm &t); - // The idea is that infinity - infinity gives NaN, and NaN != NaN is true. // // This should work on any IEEE-754-compliant implementation, which is diff --git a/src/backends/mysql/session.cpp b/src/backends/mysql/session.cpp index e503c80185..a782418047 100644 --- a/src/backends/mysql/session.cpp +++ b/src/backends/mysql/session.cpp @@ -63,7 +63,7 @@ std::string param_name(std::string::const_iterator *i, std::string val(""); for (;;) { - if (*i == end or (not std::isalpha(**i) and **i != '_')) + if (*i == end || (!std::isalpha(**i) && **i != '_')) { break; } @@ -113,7 +113,7 @@ string param_value(string::const_iterator *i, throw soci_error(err); } } - if (not quot and std::isspace(**i)) + if (!quot && std::isspace(**i)) { break; } @@ -137,7 +137,7 @@ bool valid_int(const string & s) const char *cstr = s.c_str(); errno = 0; long n = std::strtol(cstr, &tail, 10); - if (errno != 0 or n > INT_MAX or n < INT_MIN) + if (errno != 0 || n > INT_MAX || n < INT_MIN) { return false; } @@ -192,9 +192,9 @@ void parse_connect_string(const string & connectString, } skip_white(&i, end, false); string val = param_value(&i, end); - if (par == "port" and not *port_p) + if (par == "port" && !*port_p) { - if (not valid_int(val)) + if (!valid_int(val)) { throw soci_error(err); } @@ -205,60 +205,59 @@ void parse_connect_string(const string & connectString, } *port_p = true; } - else if (par == "host" and not *host_p) + else if (par == "host" && !*host_p) { *host = val; *host_p = true; } - else if (par == "user" and not *user_p) + else if (par == "user" && !*user_p) { *user = val; *user_p = true; } - else if ((par == "pass" or par == "password") and not *password_p) + else if ((par == "pass" || par == "password") && !*password_p) { *password = val; *password_p = true; } - else if ((par == "db" or par == "dbname" or par == "service") and - not *db_p) + else if ((par == "db" || par == "dbname" || par == "service") and !*db_p) { *db = val; *db_p = true; } - else if (par == "unix_socket" and not *unix_socket_p) + else if (par == "unix_socket" && !*unix_socket_p) { *unix_socket = val; *unix_socket_p = true; } - else if (par == "sslca" and not *ssl_ca_p) + else if (par == "sslca" && !*ssl_ca_p) { *ssl_ca = val; *ssl_ca_p = true; } - else if (par == "sslcert" and not *ssl_cert_p) + else if (par == "sslcert" && !*ssl_cert_p) { *ssl_cert = val; *ssl_cert_p = true; } - else if (par == "sslkey" and not *ssl_key_p) + else if (par == "sslkey" && !*ssl_key_p) { *ssl_key = val; *ssl_key_p = true; } - else if (par == "local_infile" and not *local_infile_p) + else if (par == "local_infile" && !*local_infile_p) { - if (not valid_int(val)) + if (!valid_int(val)) { throw soci_error(err); } *local_infile = std::atoi(val.c_str()); - if (*local_infile != 0 and *local_infile != 1) + if (*local_infile != 0 && *local_infile != 1) { throw soci_error(err); } *local_infile_p = true; - } else if (par == "charset" and not *charset_p) + } else if (par == "charset" && !*charset_p) { *charset = val; *charset_p = true; @@ -278,7 +277,7 @@ void parse_connect_string(const string & connectString, #pragma clang diagnostic ignored "-Wuninitialized" #endif -#if defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ > 6) +#if defined(__GNUC__) && ( __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ > 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif @@ -314,9 +313,9 @@ mysql_session_backend::mysql_session_backend( { mysql_ssl_set(conn_, ssl_key_p ? ssl_key.c_str() : NULL, ssl_cert_p ? ssl_cert.c_str() : NULL, - ssl_ca_p ? ssl_ca.c_str() : NULL, 0, 0); + ssl_ca.c_str(), 0, 0); } - if (local_infile_p and local_infile == 1) + if (local_infile_p && local_infile == 1) { if (0 != mysql_options(conn_, MYSQL_OPT_LOCAL_INFILE, NULL)) { @@ -345,7 +344,7 @@ mysql_session_backend::mysql_session_backend( } } -#if defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ > 6) +#if defined(__GNUC__) && ( __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ > 6))) #pragma GCC diagnostic pop #endif diff --git a/src/backends/mysql/standard-into-type.cpp b/src/backends/mysql/standard-into-type.cpp index 9984e0141f..06dfc47cc0 100644 --- a/src/backends/mysql/standard-into-type.cpp +++ b/src/backends/mysql/standard-into-type.cpp @@ -11,6 +11,7 @@ #include "soci/soci-platform.h" #include "common.h" #include "soci-exchange-cast.h" +#include "soci-mktime.h" // std #include #include diff --git a/src/backends/mysql/statement.cpp b/src/backends/mysql/statement.cpp index 0d0eb843ff..cc70e15816 100644 --- a/src/backends/mysql/statement.cpp +++ b/src/backends/mysql/statement.cpp @@ -163,7 +163,7 @@ mysql_statement_backend::execute(int number) "Binding for use elements must be either by position " "or by name."); } - long long rowsAffectedBulkTemp = 0; + long long rowsAffectedBulkTemp = -1; for (int i = 0; i != numberOfExecutions; ++i) { std::vector paramValues; @@ -241,6 +241,10 @@ mysql_statement_backend::execute(int number) } else { + if(rowsAffectedBulkTemp == -1) + { + rowsAffectedBulkTemp = 0; + } rowsAffectedBulkTemp += static_cast(mysql_affected_rows(session_.conn_)); } if (mysql_field_count(session_.conn_) != 0) diff --git a/src/backends/mysql/vector-into-type.cpp b/src/backends/mysql/vector-into-type.cpp index 09c86be489..57b98b4314 100644 --- a/src/backends/mysql/vector-into-type.cpp +++ b/src/backends/mysql/vector-into-type.cpp @@ -8,6 +8,7 @@ #define SOCI_MYSQL_SOURCE #include "soci/mysql/soci-mysql.h" +#include "soci-mktime.h" #include "common.h" #include "soci/soci-platform.h" #include @@ -145,7 +146,7 @@ void mysql_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) case x_stdtm: { // attempt to parse the string and convert to std::tm - std::tm t; + std::tm t = std::tm(); parse_std_tm(buf, t); set_invector_(data_, i, t); diff --git a/src/backends/odbc/session.cpp b/src/backends/odbc/session.cpp index 9a8cefb434..fb4c6c13a3 100644 --- a/src/backends/odbc/session.cpp +++ b/src/backends/odbc/session.cpp @@ -133,6 +133,23 @@ void odbc_session_backend::configure_connection() throw odbc_soci_error(SQL_HANDLE_DBC, henv_, "setting extra_float_digits for PostgreSQL"); } + + // This is extracted from pgapifunc.h header from psqlODBC driver. + enum + { + SQL_ATTR_PGOPT_UNKNOWNSASLONGVARCHAR = 65544 + }; + + // Also configure the driver to handle unknown types, such as "xml", + // that we use for x_xmltype, as long varchar instead of limiting them + // to 256 characters (by default). + rc = SQLSetConnectAttr(hdbc_, SQL_ATTR_PGOPT_UNKNOWNSASLONGVARCHAR, (SQLPOINTER)1, 0); + + // Ignore the error from this one, failure to set it is not fatal and + // the attribute is only supported in very recent version of the driver + // (>= 9.6.300). Using "UnknownsAsLongVarchar=1" in odbc.ini (or + // setting the corresponding option in the driver dialog box) should + // work with all versions however. } } @@ -253,6 +270,37 @@ bool odbc_session_backend::get_last_insert_id( return true; } +std::string odbc_session_backend::get_dummy_from_table() const +{ + std::string table; + + switch ( get_database_product() ) + { + case prod_firebird: + table = "rdb$database"; + break; + + case prod_oracle: + table = "dual"; + break; + + case prod_mssql: + case prod_mysql: + case prod_sqlite: + case prod_postgresql: + // No special dummy table needed. + break; + + // These cases are here just to make the switch exhaustive, we + // can't really do anything about them anyhow. + case prod_unknown: + case prod_uninitialized: + break; + } + + return table; +} + void odbc_session_backend::reset_transaction() { SQLRETURN rc = SQLSetConnectAttr( hdbc_, SQL_ATTR_AUTOCOMMIT, @@ -301,7 +349,7 @@ odbc_blob_backend * odbc_session_backend::make_blob_backend() } odbc_session_backend::database_product -odbc_session_backend::get_database_product() +odbc_session_backend::get_database_product() const { // Cache the product type, it's not going to change during our life time. if (product_ != prod_uninitialized) diff --git a/src/backends/odbc/standard-into-type.cpp b/src/backends/odbc/standard-into-type.cpp index b79c043c37..69b6b3d90e 100644 --- a/src/backends/odbc/standard-into-type.cpp +++ b/src/backends/odbc/standard-into-type.cpp @@ -33,11 +33,13 @@ void odbc_standard_into_type_backend::define_by_pos( data = buf_; break; case x_stdstring: + case x_longstring: + case x_xmltype: odbcType_ = SQL_C_CHAR; // Patch: set to min between column size and 100MB (used ot be 32769) // Column size for text data type can be too large for buffer allocation size = static_cast(statement_.column_size(position_)); - size = size > odbc_max_buffer_length ? odbc_max_buffer_length : size; + size = (size > odbc_max_buffer_length || size == 0) ? odbc_max_buffer_length : size; size++; buf_ = new char[size]; data = buf_; @@ -159,6 +161,14 @@ void odbc_standard_into_type_backend::post_fetch( throw soci_error("Buffer size overflow; maybe got too large string"); } } + else if (type_ == x_longstring) + { + exchange_type_cast(data_).value = buf_; + } + else if (type_ == x_xmltype) + { + exchange_type_cast(data_).value = buf_; + } else if (type_ == x_stdtm) { std::tm& t = exchange_type_cast(data_); diff --git a/src/backends/odbc/standard-use-type.cpp b/src/backends/odbc/standard-use-type.cpp index 4a47b22882..0f90617892 100644 --- a/src/backends/odbc/standard-use-type.cpp +++ b/src/backends/odbc/standard-use-type.cpp @@ -86,22 +86,8 @@ void* odbc_standard_use_type_backend::prepare_for_bind( case x_stdstring: { std::string const& s = exchange_type_cast(data_); - sqlType = SQL_VARCHAR; - cType = SQL_C_CHAR; - size = s.size(); - buf_ = new char[size+1]; - memcpy(buf_, s.c_str(), size); - buf_[size++] = '\0'; - indHolder_ = SQL_NTS; - // Strings of greater length are silently truncated at 8000 limit by MS - // SQL unless SQL_SS_LENGTH_UNLIMITED (which is defined as 0, but not - // available in all headers) is used. - if (size > 8000) - { - sqlType = SQL_LONGVARCHAR; - size = 0 /* SQL_SS_LENGTH_UNLIMITED */; - } + copy_from_string(s, size, sqlType, cType); } break; case x_stdtm: @@ -127,25 +113,18 @@ void* odbc_standard_use_type_backend::prepare_for_bind( } break; - case x_blob: - { -// sqlType = SQL_VARBINARY; -// cType = SQL_C_BINARY; + case x_longstring: + copy_from_string(exchange_type_cast(data_).value, + size, sqlType, cType); + break; + case x_xmltype: + copy_from_string(exchange_type_cast(data_).value, + size, sqlType, cType); + break; -// BLOB *b = static_cast(data); - -// odbc_blob_backend *bbe -// = static_cast(b->getBackEnd()); - -// size = 0; -// indHolder_ = size; - //TODO data = &bbe->lobp_; - } - break; - case x_statement: - case x_rowid: - // Unsupported data types. - return NULL; + // unsupported types + default: + throw soci_error("Use element used with non-supported type."); } // Return either the pointer to C++ data itself or the buffer that we @@ -153,6 +132,22 @@ void* odbc_standard_use_type_backend::prepare_for_bind( return buf_ ? buf_ : data_; } +void odbc_standard_use_type_backend::copy_from_string( + std::string const& s, + SQLLEN& size, + SQLSMALLINT& sqlType, + SQLSMALLINT& cType + ) +{ + sqlType = SQL_VARCHAR; + cType = SQL_C_CHAR; + size = s.size(); + buf_ = new char[size+1]; + memcpy(buf_, s.c_str(), size); + buf_[size++] = '\0'; + indHolder_ = SQL_NTS; +} + void odbc_standard_use_type_backend::bind_by_pos( int &position, void *data, exchange_type type, bool /* readOnly */) { @@ -209,17 +204,22 @@ void odbc_standard_use_type_backend::bind_by_name( void odbc_standard_use_type_backend::pre_use(indicator const *ind) { // first deal with data - SQLSMALLINT sqlType; - SQLSMALLINT cType; - SQLLEN size; + SQLSMALLINT sqlType(0); + SQLSMALLINT cType(0); + SQLLEN size(0); + SQLLEN bufLen(0); void* const sqlData = prepare_for_bind(size, sqlType, cType); - + if (size > ODBC_MAX_COL_SIZE) + { + bufLen = size; + size = SQL_SS_LENGTH_UNLIMITED; + } SQLRETURN rc = SQLBindParameter(statement_.hstmt_, static_cast(position_), SQL_PARAM_INPUT, cType, sqlType, size, 0, - sqlData, 0, &indHolder_); + sqlData, bufLen, &indHolder_); if (is_odbc_error(rc)) { diff --git a/src/backends/odbc/statement.cpp b/src/backends/odbc/statement.cpp index 5dcc034b58..a83124fbb1 100644 --- a/src/backends/odbc/statement.cpp +++ b/src/backends/odbc/statement.cpp @@ -150,20 +150,29 @@ odbc_statement_backend::execute(int number) SQLRETURN rc = SQLExecute(hstmt_); if (is_odbc_error(rc)) { + // Construct the error object immediately, before calling any other + // ODBC functions, in order to not lose the error message. + const odbc_soci_error err(SQL_HANDLE_STMT, hstmt_, "executing statement"); + + // There is no universal way to determine the number of affected rows + // after a failed update. + rowsAffected_ = -1LL; + // If executing bulk operation a partial // number of rows affected may be available. if (hasVectorUseElements_) { - rowsAffected_ = 0; - do { SQLLEN res = 0; // SQLRowCount will return error after a partially executed statement. // SQL_DIAG_ROW_COUNT returns the same info but must be collected immediatelly after the execution. rc = SQLGetDiagField(SQL_HANDLE_STMT, hstmt_, 0, SQL_DIAG_ROW_COUNT, &res, 0, NULL); - if (!is_odbc_error(rc) && res > 0) // 'res' will be -1 for the where the statement failed. + if (!is_odbc_error(rc) && res != -1) { + if (rowsAffected_ == -1LL) + rowsAffected_ = res; + else rowsAffected_ += res; } --rows_processed; // Avoid unnecessary calls to SQLGetDiagField @@ -171,7 +180,7 @@ odbc_statement_backend::execute(int number) // Move forward to the next result while there are rows processed. while (rows_processed > 0 && SQLMoreResults(hstmt_) == SQL_SUCCESS); } - throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, "executing statement"); + throw err; } else if (hasVectorUseElements_) { @@ -180,20 +189,15 @@ odbc_statement_backend::execute(int number) } else // We need to retrieve the number of rows affected explicitly. { - rowsAffected_ = 0; - - do { - SQLLEN res = 0; - SQLRETURN rc = SQLRowCount(hstmt_, &res); - if (is_odbc_error(rc)) - { - throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, - "getting number of affected rows"); - } - rowsAffected_ += res; + SQLLEN res = 0; + rc = SQLRowCount(hstmt_, &res); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, + "getting number of affected rows"); } - // Move forward to the next result if executing a bulk operation. - while (hasVectorUseElements_ && SQLMoreResults(hstmt_) == SQL_SUCCESS); + + rowsAffected_ = res; } SQLSMALLINT colCount; SQLNumResultCols(hstmt_, &colCount); diff --git a/src/backends/odbc/vector-into-type.cpp b/src/backends/odbc/vector-into-type.cpp index 04fb31671c..5275173a35 100644 --- a/src/backends/odbc/vector-into-type.cpp +++ b/src/backends/odbc/vector-into-type.cpp @@ -173,9 +173,8 @@ void odbc_vector_into_type_backend::define_by_pos( } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + default: + throw soci_error("Into element used with non-supported type."); } SQLRETURN rc @@ -222,12 +221,41 @@ void odbc_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) std::vector &v(*vp); - char *pos = buf_; + const char *pos = buf_; std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) + for (std::size_t i = 0; i != vsize; ++i, pos += colSize_) { - v[i].assign(pos, strlen(pos)); - pos += colSize_; + SQLLEN const len = indHolderVec_[i]; + if (len == -1) + { + // Value is null. + v[i].clear(); + continue; + } + + // Find the actual length of the string: for a VARCHAR(N) + // column, it may be right-padded with spaces up to the length + // of the longest string in the result set. This happens with + // at least MS SQL (and the exact behaviour depends on the + // value of the ANSI_PADDING option) and it seems like some + // other ODBC drivers also have options like "PADVARCHAR", so + // it's probably not the only case when it does. + // + // So deal with this generically by just trimming all the + // spaces from the right hand-side. + const char* end = pos + len; + while (end != pos) + { + // Pre-decrement as "end" is one past the end, as usual. + if (*--end != ' ') + { + // We must count the last non-space character. + ++end; + break; + } + } + + v[i].assign(pos, end - pos); } } else if (type_ == x_stdtm) @@ -380,9 +408,8 @@ void odbc_vector_into_type_backend::resize(std::size_t sz) } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + default: + throw soci_error("Into vector element used with non-supported type."); } } @@ -446,9 +473,8 @@ std::size_t odbc_vector_into_type_backend::size() } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + default: + throw soci_error("Into vector element used with non-supported type."); } return sz; diff --git a/src/backends/odbc/vector-use-type.cpp b/src/backends/odbc/vector-use-type.cpp index 75f2da1091..92b200dfb6 100644 --- a/src/backends/odbc/vector-use-type.cpp +++ b/src/backends/odbc/vector-use-type.cpp @@ -165,18 +165,20 @@ void odbc_vector_use_type_backend::prepare_for_bind(void *&data, SQLUINTEGER &si prepare_indicators(vecSize); for (std::size_t i = 0; i != vecSize; ++i) { - std::size_t sz = v[i].length() + 1; // add one for null + std::size_t sz = v[i].length(); indHolderVec_[i] = static_cast(sz); maxSize = sz > maxSize ? sz : maxSize; } + maxSize++; // For terminating nul. + buf_ = new char[maxSize * vecSize]; memset(buf_, 0, maxSize * vecSize); char *pos = buf_; for (std::size_t i = 0; i != vecSize; ++i) { - strncpy(pos, v[i].c_str(), v[i].length()); + memcpy(pos, v[i].c_str(), v[i].length()); pos += maxSize; } @@ -202,9 +204,9 @@ void odbc_vector_use_type_backend::prepare_for_bind(void *&data, SQLUINTEGER &si } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + // not supported + default: + throw soci_error("Use vector element used with non-supported type."); } colSize_ = size; @@ -215,9 +217,9 @@ void odbc_vector_use_type_backend::bind_helper(int &position, void *data, exchan data_ = data; // for future reference type_ = type; // for future reference - SQLSMALLINT sqlType; - SQLSMALLINT cType; - SQLUINTEGER size; + SQLSMALLINT sqlType(0); + SQLSMALLINT cType(0); + SQLUINTEGER size(0); prepare_for_bind(data, size, sqlType, cType); @@ -290,57 +292,94 @@ void odbc_vector_use_type_backend::bind_by_name( void odbc_vector_use_type_backend::pre_use(indicator const *ind) { // first deal with data - if (type_ == x_stdtm) + SQLLEN non_null_indicator = 0; + switch (type_) { - std::vector *vp - = static_cast *>(data_); + case x_short: + case x_integer: + case x_double: + // Length of the parameter value is ignored for these types. + break; - std::vector &v(*vp); + case x_char: + case x_stdstring: + non_null_indicator = SQL_NTS; + break; - char *pos = buf_; - std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) - { - std::tm t = v[i]; - TIMESTAMP_STRUCT * ts = reinterpret_cast(pos); + case x_stdtm: + { + std::vector *vp + = static_cast *>(data_); - ts->year = static_cast(t.tm_year + 1900); - ts->month = static_cast(t.tm_mon + 1); - ts->day = static_cast(t.tm_mday); - ts->hour = static_cast(t.tm_hour); - ts->minute = static_cast(t.tm_min); - ts->second = static_cast(t.tm_sec); - ts->fraction = 0; - pos += sizeof(TIMESTAMP_STRUCT); - } - } - else if (type_ == x_long_long && use_string_for_bigint()) - { - std::vector *vp - = static_cast *>(data_); - std::vector &v(*vp); + std::vector &v(*vp); - char *pos = buf_; - std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) - { - snprintf(pos, max_bigint_length, "%" LL_FMT_FLAGS "d", v[i]); - pos += max_bigint_length; - } - } - else if (type_ == x_unsigned_long_long && use_string_for_bigint()) - { - std::vector *vp - = static_cast *>(data_); - std::vector &v(*vp); + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + std::tm t = v[i]; + TIMESTAMP_STRUCT * ts = reinterpret_cast(pos); - char *pos = buf_; - std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) - { - snprintf(pos, max_bigint_length, "%" LL_FMT_FLAGS "u", v[i]); - pos += max_bigint_length; - } + ts->year = static_cast(t.tm_year + 1900); + ts->month = static_cast(t.tm_mon + 1); + ts->day = static_cast(t.tm_mday); + ts->hour = static_cast(t.tm_hour); + ts->minute = static_cast(t.tm_min); + ts->second = static_cast(t.tm_sec); + ts->fraction = 0; + pos += sizeof(TIMESTAMP_STRUCT); + } + } + break; + + case x_long_long: + if (use_string_for_bigint()) + { + std::vector *vp + = static_cast *>(data_); + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + snprintf(pos, max_bigint_length, "%" LL_FMT_FLAGS "d", v[i]); + pos += max_bigint_length; + } + + non_null_indicator = SQL_NTS; + } + break; + + case x_unsigned_long_long: + if (use_string_for_bigint()) + { + std::vector *vp + = static_cast *>(data_); + std::vector &v(*vp); + + char *pos = buf_; + std::size_t const vsize = v.size(); + for (std::size_t i = 0; i != vsize; ++i) + { + snprintf(pos, max_bigint_length, "%" LL_FMT_FLAGS "u", v[i]); + pos += max_bigint_length; + } + + non_null_indicator = SQL_NTS; + } + break; + + case x_statement: + case x_rowid: + case x_blob: + case x_xmltype: + case x_longstring: + // Those are unreachable, we would have thrown from + // prepare_for_bind() if we we were using one of them, only handle + // them here to avoid compiler warnings about unhandled enum + // elements. + break; } // then handle indicators @@ -358,7 +397,7 @@ void odbc_vector_use_type_backend::pre_use(indicator const *ind) // for strings we have already set the values if (type_ != x_stdstring) { - indHolderVec_[i] = SQL_NTS; // value is OK + indHolderVec_[i] = non_null_indicator; } } } @@ -372,7 +411,7 @@ void odbc_vector_use_type_backend::pre_use(indicator const *ind) // for strings we have already set the values if (type_ != x_stdstring) { - indHolderVec_[i] = SQL_NTS; // value is OK + indHolderVec_[i] = non_null_indicator; } } } @@ -438,9 +477,9 @@ std::size_t odbc_vector_use_type_backend::size() } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + // not supported + default: + throw soci_error("Use vector element used with non-supported type."); } return sz; diff --git a/src/backends/oracle/error.cpp b/src/backends/oracle/error.cpp index 3f2f2dcc0a..b7795272c3 100644 --- a/src/backends/oracle/error.cpp +++ b/src/backends/oracle/error.cpp @@ -20,8 +20,29 @@ using namespace soci::details; using namespace soci::details::oracle; oracle_soci_error::oracle_soci_error(std::string const & msg, int errNum) - : soci_error(msg), err_num_(errNum) + : soci_error(msg), err_num_(errNum), cat_(unknown) { + if (errNum == 12162 || errNum == 25403) + { + cat_ = connection_error; + } + else if (errNum == 1400) + { + cat_ = constraint_violation; + } + else if (errNum == 1466 || + errNum == 2055 || + errNum == 2067 || + errNum == 2091 || + errNum == 2092 || + errNum == 25401 || + errNum == 25402 || + errNum == 25405 || + errNum == 25408 || + errNum == 25409) + { + cat_ = unknown_transaction_state; + } } void soci::details::oracle::get_error_details(sword res, OCIError *errhp, @@ -37,6 +58,7 @@ void soci::details::oracle::get_error_details(sword res, OCIError *errhp, msg = "soci error: No data"; break; case OCI_ERROR: + case OCI_SUCCESS_WITH_INFO: OCIErrorGet(errhp, 1, 0, &errcode, errbuf, sizeof(errbuf), OCI_HTYPE_ERROR); msg = reinterpret_cast(errbuf); diff --git a/src/backends/oracle/factory.cpp b/src/backends/oracle/factory.cpp index 8dfd453866..6b09c044fa 100644 --- a/src/backends/oracle/factory.cpp +++ b/src/backends/oracle/factory.cpp @@ -69,17 +69,56 @@ std::string::const_iterator get_key_value(std::string::const_iterator & i, return i; } +// decode charset and ncharset names +int charset_code(const std::string & name) +{ + // Note: unofficial reference for charset ids is: + // http://www.mydul.net/charsets.html + + int code; + + if (name == "utf8") + { + code = 871; + } + else if (name == "utf16") + { + code = OCI_UTF16ID; + } + else if (name == "we8mswin1252" || name == "win1252") + { + code = 178; + } + else + { + // allow explicit number value + + std::istringstream ss(name); + + ss >> code; + if (!ss) + { + throw soci_error("Invalid character set name."); + } + } + + return code; +} + // retrieves service name, user name and password from the // uniform connect string void chop_connect_string(std::string const & connectString, std::string & serviceName, std::string & userName, - std::string & password, int & mode, bool & decimals_as_strings) + std::string & password, int & mode, bool & decimals_as_strings, + int & charset, int & ncharset) { serviceName.clear(); userName.clear(); password.clear(); mode = OCI_DEFAULT; decimals_as_strings = false; + charset = 0; + ncharset = 0; std::string key, value; std::string::const_iterator i = connectString.begin(); @@ -121,6 +160,14 @@ void chop_connect_string(std::string const & connectString, { decimals_as_strings = value == "1" || value == "Y" || value == "y"; } + else if (key == "charset") + { + charset = charset_code(value); + } + else if (key == "ncharset") + { + ncharset = charset_code(value); + } } } @@ -131,12 +178,14 @@ oracle_session_backend * oracle_backend_factory::make_session( std::string serviceName, userName, password; int mode; bool decimals_as_strings; + int charset; + int ncharset; chop_connect_string(parameters.get_connect_string(), serviceName, userName, password, - mode, decimals_as_strings); + mode, decimals_as_strings, charset, ncharset); return new oracle_session_backend(serviceName, userName, password, - mode, decimals_as_strings); + mode, decimals_as_strings, charset, ncharset); } oracle_backend_factory const soci::oracle; diff --git a/src/backends/oracle/session.cpp b/src/backends/oracle/session.cpp index cfe1ad1269..c502fba316 100644 --- a/src/backends/oracle/session.cpp +++ b/src/backends/oracle/session.cpp @@ -7,6 +7,7 @@ #define SOCI_ORACLE_SOURCE #include "soci/oracle/soci-oracle.h" +#include "soci/callbacks.h" #include "error.h" #include #include @@ -22,17 +23,128 @@ using namespace soci; using namespace soci::details; using namespace soci::details::oracle; +namespace // unnamed +{ + +sb4 fo_callback(void * /* svchp */, void * /* envhp */, void * fo_ctx, + ub4 /* fo_type */, ub4 fo_event) +{ + oracle_session_backend * backend = + static_cast(fo_ctx); + + failover_callback * callback = backend->failoverCallback_; + + if (callback != NULL) + { + session * sql = backend->session_; + + switch (fo_event) + { + case OCI_FO_BEGIN: + // failover operation was initiated + + try + { + callback->started(); + } + catch (...) + { + // ignore exceptions from user callbacks + } + + break; + + case OCI_FO_END: + // failover was successful + + try + { + callback->finished(*sql); + } + catch (...) + { + // ignore exceptions from user callbacks + } + + break; + + case OCI_FO_ABORT: + // failover was aborted with no possibility to recovery + + try + { + callback->aborted(); + } + catch (...) + { + // ignore exceptions from user callbacks + } + + break; + + case OCI_FO_ERROR: + // failover failed, but can be retried + + try + { + bool retry = false; + std::string newTarget; + callback->failed(retry, newTarget); + + // newTarget is ignored, as the new target + // is selected by Oracle client configuration + + if (retry) + { + return OCI_FO_RETRY; + } + } + catch (...) + { + // ignore exceptions from user callbacks + } + + break; + + case OCI_FO_REAUTH: + // nothing interesting + break; + + default: + // ignore unknown callback types (if any) + break; + } + } + + return 0; +} + +} // unnamed namespace + oracle_session_backend::oracle_session_backend(std::string const & serviceName, std::string const & userName, std::string const & password, int mode, - bool decimals_as_strings) - : envhp_(NULL), srvhp_(NULL), errhp_(NULL), svchp_(NULL), usrhp_(NULL) - , decimals_as_strings_(decimals_as_strings) + bool decimals_as_strings, int charset, int ncharset) + : envhp_(NULL), srvhp_(NULL), errhp_(NULL), svchp_(NULL), usrhp_(NULL), + decimals_as_strings_(decimals_as_strings) { + // assume service/user/password are utf8-compatible already + const int defaultSourceCharSetId = 871; + + // arbitrary length for charset conversion buffer + const size_t nlsBufLen = 100; + + char nlsService[nlsBufLen]; + size_t nlsServiceLen; + char nlsUserName[nlsBufLen]; + size_t nlsUserNameLen; + char nlsPassword[nlsBufLen]; + size_t nlsPasswordLen; + sword res; // create the environment - res = OCIEnvCreate(&envhp_, OCI_THREADED | OCI_ENV_NO_MUTEX, - 0, 0, 0, 0, 0, 0); + res = OCIEnvNlsCreate(&envhp_, OCI_THREADED | OCI_ENV_NO_MUTEX, + 0, 0, 0, 0, 0, 0, charset, ncharset); if (res != OCI_SUCCESS) { throw soci_error("Cannot create environment"); @@ -56,11 +168,100 @@ oracle_session_backend::oracle_session_backend(std::string const & serviceName, throw soci_error("Cannot create error handle"); } + if (charset != 0) + { + // convert service/user/password to the expected charset + + res = OCINlsCharSetConvert(envhp_, errhp_, + charset, nlsService, nlsBufLen, + defaultSourceCharSetId, serviceName.c_str(), serviceName.size(), &nlsServiceLen); + if (res != OCI_SUCCESS) + { + std::string msg; + int errNum; + get_error_details(res, errhp_, msg, errNum); + clean_up(); + throw oracle_soci_error(msg, errNum); + } + + res = OCINlsCharSetConvert(envhp_, errhp_, + charset, nlsUserName, nlsBufLen, + defaultSourceCharSetId, userName.c_str(), userName.size(), &nlsUserNameLen); + if (res != OCI_SUCCESS) + { + std::string msg; + int errNum; + get_error_details(res, errhp_, msg, errNum); + clean_up(); + throw oracle_soci_error(msg, errNum); + } + + res = OCINlsCharSetConvert(envhp_, errhp_, + charset, nlsPassword, nlsBufLen, + defaultSourceCharSetId, password.c_str(), password.size(), &nlsPasswordLen); + if (res != OCI_SUCCESS) + { + std::string msg; + int errNum; + get_error_details(res, errhp_, msg, errNum); + clean_up(); + throw oracle_soci_error(msg, errNum); + } + } + else + { + // do not perform any charset conversions + + nlsServiceLen = serviceName.size(); + if (nlsServiceLen < nlsBufLen) + { + std::strcpy(nlsService, serviceName.c_str()); + } + else + { + throw soci_error("Service name is too long."); + } + + nlsUserNameLen = userName.size(); + if (nlsUserNameLen < nlsBufLen) + { + std::strcpy(nlsUserName, userName.c_str()); + } + else + { + throw soci_error("User name is too long."); + } + + nlsPasswordLen = password.size(); + if (nlsPasswordLen < nlsBufLen) + { + std::strcpy(nlsPassword, password.c_str()); + } + else + { + throw soci_error("Password is too long."); + } + } + // create the server context - sb4 serviceNameLen = static_cast(serviceName.size()); res = OCIServerAttach(srvhp_, errhp_, - reinterpret_cast(const_cast(serviceName.c_str())), - serviceNameLen, OCI_DEFAULT); + reinterpret_cast(nlsService), nlsServiceLen, OCI_DEFAULT); + if (res != OCI_SUCCESS) + { + std::string msg; + int errNum; + get_error_details(res, errhp_, msg, errNum); + clean_up(); + throw oracle_soci_error(msg, errNum); + } + + // register failover callback + OCIFocbkStruct fo; + fo.fo_ctx = this; + fo.callback_function = &fo_callback; + + res = OCIAttrSet(srvhp_, static_cast(OCI_HTYPE_SERVER), + &fo, 0, static_cast(OCI_ATTR_FOCBK), errhp_); if (res != OCI_SUCCESS) { std::string msg; @@ -100,32 +301,41 @@ oracle_session_backend::oracle_session_backend(std::string const & serviceName, throw soci_error("Cannot allocate user session handle"); } - // set username attribute in the user session handle - sb4 userNameLen = static_cast(userName.size()); - res = OCIAttrSet(usrhp_, OCI_HTYPE_SESSION, - reinterpret_cast(const_cast(userName.c_str())), - userNameLen, OCI_ATTR_USERNAME, errhp_); - if (res != OCI_SUCCESS) + // select credentials type - use rdbms based credentials by default + // and switch to external credentials if username and + // password are both not specified + ub4 credentialType = OCI_CRED_RDBMS; + if (userName.empty() && password.empty()) { - clean_up(); - throw soci_error("Cannot set username"); + credentialType = OCI_CRED_EXT; } - - // set password attribute - sb4 passwordLen = static_cast(password.size()); - res = OCIAttrSet(usrhp_, OCI_HTYPE_SESSION, - reinterpret_cast(const_cast(password.c_str())), - passwordLen, OCI_ATTR_PASSWORD, errhp_); - if (res != OCI_SUCCESS) + else { - clean_up(); - throw soci_error("Cannot set password"); + // set username attribute in the user session handle + res = OCIAttrSet(usrhp_, OCI_HTYPE_SESSION, + reinterpret_cast(nlsUserName), + nlsUserNameLen, OCI_ATTR_USERNAME, errhp_); + if (res != OCI_SUCCESS) + { + clean_up(); + throw soci_error("Cannot set username"); + } + + // set password attribute + res = OCIAttrSet(usrhp_, OCI_HTYPE_SESSION, + reinterpret_cast(nlsPassword), + nlsPasswordLen, OCI_ATTR_PASSWORD, errhp_); + if (res != OCI_SUCCESS) + { + clean_up(); + throw soci_error("Cannot set password"); + } } // begin the session res = OCISessionBegin(svchp_, errhp_, usrhp_, - OCI_CRED_RDBMS, mode); - if (res != OCI_SUCCESS) + credentialType, mode); + if (res != OCI_SUCCESS && res != OCI_SUCCESS_WITH_INFO) { std::string msg; int errNum; diff --git a/src/backends/oracle/standard-into-type.cpp b/src/backends/oracle/standard-into-type.cpp index 527c7dd95d..1c24f990a1 100644 --- a/src/backends/oracle/standard-into-type.cpp +++ b/src/backends/oracle/standard-into-type.cpp @@ -143,6 +143,23 @@ void oracle_standard_into_type_backend::define_by_pos( data = &bbe->lobp_; } break; + + case x_xmltype: + case x_longstring: + { + oracleType = SQLT_CLOB; + + // lazy initialization of the temporary LOB object, + // actual creation of this object is in pre_exec, which + // is called right before statement's execute + + OCILobLocator * lobp = NULL; + + size = sizeof(lobp); + data = &ociData_; + ociData_ = lobp; + } + break; } sword res = OCIDefineByPos(statement_.stmtp_, &defnp_, @@ -156,6 +173,33 @@ void oracle_standard_into_type_backend::define_by_pos( } } +void oracle_standard_into_type_backend::pre_exec(int /* num */) +{ + if (type_ == x_xmltype || type_ == x_longstring) + { + // lazy initialization of the temporary LOB object + + OCILobLocator * lobp; + sword res = OCIDescriptorAlloc(statement_.session_.envhp_, + reinterpret_cast(&lobp), OCI_DTYPE_LOB, 0, 0); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + + res = OCILobCreateTemporary(statement_.session_.svchp_, + statement_.session_.errhp_, + lobp, 0, SQLCS_IMPLICIT, + OCI_TEMP_CLOB, OCI_ATTR_NOCACHE, OCI_DURATION_SESSION); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + + ociData_ = lobp; + } +} + void oracle_standard_into_type_backend::pre_fetch() { // nothing to do except with Statement into objects @@ -167,6 +211,36 @@ void oracle_standard_into_type_backend::pre_fetch() } } +void oracle_standard_into_type_backend::read_from_lob(OCILobLocator * lobp, std::string & value) +{ + ub4 len; + + sword res = OCILobGetLength(statement_.session_.svchp_, statement_.session_.errhp_, + lobp, &len); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + + std::vector buf(len); + + if (len != 0) + { + ub4 offset = 1; + + res = OCILobRead(statement_.session_.svchp_, statement_.session_.errhp_, + lobp, &len, + offset, reinterpret_cast(&buf[0]), + len, 0, 0, 0, 0); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + } + + value.assign(buf.begin(), buf.end()); +} + void oracle_standard_into_type_backend::post_fetch( bool gotData, bool calledFromFetch, indicator *ind) { @@ -218,6 +292,24 @@ void oracle_standard_into_type_backend::post_fetch( statement *st = static_cast(data_); st->define_and_bind(); } + else if (type_ == x_xmltype) + { + if (indOCIHolder_ != -1) + { + OCILobLocator * lobp = static_cast(ociData_); + + read_from_lob(lobp, exchange_type_cast(data_).value); + } + } + else if (type_ == x_longstring) + { + if (indOCIHolder_ != -1) + { + OCILobLocator * lobp = static_cast(ociData_); + + read_from_lob(lobp, exchange_type_cast(data_).value); + } + } } // then - deal with indicators @@ -257,6 +349,15 @@ void oracle_standard_into_type_backend::post_fetch( void oracle_standard_into_type_backend::clean_up() { + if (type_ == x_xmltype || type_ == x_longstring) + { + OCILobLocator * lobp = static_cast(ociData_); + + // ignore errors from this call + (void) OCILobFreeTemporary(statement_.session_.svchp_, statement_.session_.errhp_, + lobp); + } + if (defnp_ != NULL) { OCIHandleFree(defnp_, OCI_HTYPE_DEFINE); diff --git a/src/backends/oracle/standard-use-type.cpp b/src/backends/oracle/standard-use-type.cpp index aff764c85d..5e94326626 100644 --- a/src/backends/oracle/standard-use-type.cpp +++ b/src/backends/oracle/standard-use-type.cpp @@ -11,6 +11,7 @@ #include "error.h" #include "soci/rowid.h" #include "soci/statement.h" +#include "soci/type-wrappers.h" #include "soci/soci-platform.h" #include "soci-compiler.h" @@ -140,6 +141,23 @@ void oracle_standard_use_type_backend::prepare_for_bind( data = &bbe->lobp_; } break; + + case x_xmltype: + case x_longstring: + { + oracleType = SQLT_CLOB; + + // lazy initialization of the temporary LOB object, + // actual creation of this object is in pre_exec, which + // is called right before statement's execute + + OCILobLocator * lobp = NULL; + + size = sizeof(lobp); + data = &ociData_; + ociData_ = lobp; + } + break; } } @@ -203,6 +221,98 @@ void oracle_standard_use_type_backend::bind_by_name( statement_.boundByName_ = true; } +void oracle_standard_use_type_backend::write_to_lob(OCILobLocator * lobp, const std::string & value) +{ + ub4 toWrite = value.size(); + ub4 offset = 1; + sword res; + + if (toWrite != 0) + { + res = OCILobWrite(statement_.session_.svchp_, statement_.session_.errhp_, + lobp, &toWrite, offset, + reinterpret_cast(const_cast(value.data())), + toWrite, OCI_ONE_PIECE, 0, 0, 0, SQLCS_IMPLICIT); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + } + + ub4 len; + + res = OCILobGetLength(statement_.session_.svchp_, statement_.session_.errhp_, + lobp, &len); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + + if (toWrite < len) + { + res = OCILobTrim(statement_.session_.svchp_, statement_.session_.errhp_, + lobp, toWrite); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + } +} + +void oracle_standard_use_type_backend::lazy_temp_lob_init() +{ + OCILobLocator * lobp; + sword res = OCIDescriptorAlloc(statement_.session_.envhp_, + reinterpret_cast(&lobp), OCI_DTYPE_LOB, 0, 0); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + + res = OCILobCreateTemporary(statement_.session_.svchp_, + statement_.session_.errhp_, + lobp, 0, SQLCS_IMPLICIT, + OCI_TEMP_CLOB, OCI_ATTR_NOCACHE, OCI_DURATION_SESSION); + if (res != OCI_SUCCESS) + { + throw_oracle_soci_error(res, statement_.session_.errhp_); + } + + ociData_ = lobp; +} + +void oracle_standard_use_type_backend::pre_exec(int /* num */) +{ + switch (type_) + { + case x_xmltype: + { + // lazy initialization of the temporary LOB object + + lazy_temp_lob_init(); + + OCILobLocator * lobp = static_cast(ociData_); + + write_to_lob(lobp, exchange_type_cast(data_).value); + } + break; + case x_longstring: + { + // lazy initialization of the temporary LOB object + + lazy_temp_lob_init(); + + OCILobLocator * lobp = static_cast(ociData_); + + write_to_lob(lobp, exchange_type_cast(data_).value); + } + break; + default: + // nothing to do + break; + } +} + void oracle_standard_use_type_backend::pre_use(indicator const *ind) { // first deal with data @@ -278,6 +388,9 @@ void oracle_standard_use_type_backend::pre_use(indicator const *ind) s->undefine_and_bind(); } break; + + case x_xmltype: + case x_longstring: case x_rowid: case x_blob: // nothing to do @@ -444,6 +557,8 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) break; case x_rowid: case x_blob: + case x_xmltype: + case x_longstring: // nothing to do here break; } @@ -471,6 +586,15 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) void oracle_standard_use_type_backend::clean_up() { + if (type_ == x_xmltype || type_ == x_longstring) + { + OCILobLocator * lobp = static_cast(ociData_); + + // ignore errors from this call + (void) OCILobFreeTemporary(statement_.session_.svchp_, statement_.session_.errhp_, + lobp); + } + if (bindp_ != NULL) { OCIHandleFree(bindp_, OCI_HTYPE_DEFINE); diff --git a/src/backends/oracle/statement.cpp b/src/backends/oracle/statement.cpp index b80a270c7f..ec3ce21093 100644 --- a/src/backends/oracle/statement.cpp +++ b/src/backends/oracle/statement.cpp @@ -279,9 +279,13 @@ void oracle_statement_backend::describe_column(int colNum, data_type &type, if (dbscale > 0) { if (session_.get_option_decimals_as_strings()) + { type = dt_string; + } else + { type = dt_double; + } } else if (dbprec <= std::numeric_limits::digits10) { @@ -292,9 +296,15 @@ void oracle_statement_backend::describe_column(int colNum, data_type &type, type = dt_long_long; } break; + case OCI_TYPECODE_BDOUBLE: + type = dt_double; + break; case SQLT_DAT: type = dt_date; break; + default: + // Unknown oracle types will just be represented by a string + type = dt_string; } } diff --git a/src/backends/oracle/vector-into-type.cpp b/src/backends/oracle/vector-into-type.cpp index cb40d1b02d..6bb3ee52e0 100644 --- a/src/backends/oracle/vector-into-type.cpp +++ b/src/backends/oracle/vector-into-type.cpp @@ -40,14 +40,20 @@ void oracle_vector_into_type_backend::prepare_indicators(std::size_t size) rCodes_.resize(size); } -void oracle_vector_into_type_backend::define_by_pos( - int &position, void *data, exchange_type type) +void oracle_vector_into_type_backend::define_by_pos_bulk( + int & position, void * data, exchange_type type, + std::size_t begin, std::size_t * end) { data_ = data; // for future reference type_ = type; // for future reference + begin_ = begin; + end_ = end; - ub2 oracleType = 0; // dummy initialization to please the compiler - sb4 size = 0; // also dummy + end_var_ = full_size(); + + ub2 oracleType = 0; // dummy initialization to please the compiler + sb4 elementSize = 0; // also dummy + void * dataBuf; switch (type) { @@ -55,41 +61,41 @@ void oracle_vector_into_type_backend::define_by_pos( case x_char: { oracleType = SQLT_AFC; - size = sizeof(char); + elementSize = sizeof(char); std::vector *vp = static_cast *>(data); std::vector &v(*vp); - prepare_indicators(v.size()); - data = &v[0]; + prepare_indicators(size()); + dataBuf = &v[begin_]; } break; case x_short: { oracleType = SQLT_INT; - size = sizeof(short); + elementSize = sizeof(short); std::vector *vp = static_cast *>(data); std::vector &v(*vp); - prepare_indicators(v.size()); - data = &v[0]; + prepare_indicators(size()); + dataBuf = &v[begin_]; } break; case x_integer: { oracleType = SQLT_INT; - size = sizeof(int); + elementSize = sizeof(int); std::vector *vp = static_cast *>(data); std::vector &v(*vp); - prepare_indicators(v.size()); - data = &v[0]; + prepare_indicators(size()); + dataBuf = &v[begin_]; } break; case x_double: { oracleType = statement_.session_.get_double_sql_type(); - size = sizeof(double); + elementSize = sizeof(double); std::vector *vp = static_cast *>(data); std::vector &v(*vp); - prepare_indicators(v.size()); - data = &v[0]; + prepare_indicators(size()); + dataBuf = &v[begin_]; } break; @@ -98,72 +104,71 @@ void oracle_vector_into_type_backend::define_by_pos( case x_long_long: { oracleType = SQLT_STR; - std::vector *v - = static_cast *>(data); + const std::size_t vecSize = size(); colSize_ = 100; // arbitrary buffer size for each entry - std::size_t const bufSize = colSize_ * v->size(); + std::size_t const bufSize = colSize_ * vecSize; buf_ = new char[bufSize]; - prepare_indicators(v->size()); + prepare_indicators(vecSize); - size = static_cast(colSize_); - data = buf_; + elementSize = static_cast(colSize_); + dataBuf = buf_; } break; case x_unsigned_long_long: { oracleType = SQLT_STR; - std::vector *v - = static_cast *>(data); + const std::size_t vecSize = size(); colSize_ = 100; // arbitrary buffer size for each entry - std::size_t const bufSize = colSize_ * v->size(); + std::size_t const bufSize = colSize_ * vecSize; buf_ = new char[bufSize]; - prepare_indicators(v->size()); + prepare_indicators(vecSize); - size = static_cast(colSize_); - data = buf_; + elementSize = static_cast(colSize_); + dataBuf = buf_; } break; case x_stdstring: { oracleType = SQLT_CHR; - std::vector *v - = static_cast *>(data); + const std::size_t vecSize = size(); colSize_ = statement_.column_size(position) + 1; - std::size_t bufSize = colSize_ * v->size(); + std::size_t bufSize = colSize_ * vecSize; buf_ = new char[bufSize]; - prepare_indicators(v->size()); + prepare_indicators(vecSize); - size = static_cast(colSize_); - data = buf_; + elementSize = static_cast(colSize_); + dataBuf = buf_; } break; case x_stdtm: { oracleType = SQLT_DAT; - std::vector *v - = static_cast *>(data); + const std::size_t vecSize = size(); - prepare_indicators(v->size()); + prepare_indicators(vecSize); - size = 7; // 7 is the size of SQLT_DAT - std::size_t bufSize = size * v->size(); + elementSize = 7; // 7 is the size of SQLT_DAT + std::size_t bufSize = elementSize * vecSize; buf_ = new char[bufSize]; - data = buf_; + dataBuf = buf_; } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + case x_xmltype: + case x_longstring: + case x_statement: + case x_rowid: + case x_blob: + throw soci_error("Unsupported type for vector into parameter"); } sword res = OCIDefineByPos(statement_.stmtp_, &defnp_, statement_.session_.errhp_, - position++, data, size, oracleType, + position++, dataBuf, elementSize, oracleType, indOCIHolders_, &sizes_[0], &rCodes_[0], OCI_DEFAULT); if (res != OCI_SUCCESS) { @@ -176,7 +181,7 @@ void oracle_vector_into_type_backend::pre_fetch() // nothing to do for the supported types } -void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) +void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) { if (gotData) { @@ -191,12 +196,12 @@ void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) std::vector &v(*vp); char *pos = buf_; - std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) + std::size_t const vecSize = size(); + for (std::size_t i = 0; i != vecSize; ++i) { if (indOCIHolderVec_[i] != -1) { - v[i].assign(pos, sizes_[i]); + v[begin_ + i].assign(pos, sizes_[i]); } pos += colSize_; } @@ -209,12 +214,12 @@ void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) std::vector &v(*vp); char *pos = buf_; - std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) + std::size_t const vecSize = size(); + for (std::size_t i = 0; i != vecSize; ++i) { if (indOCIHolderVec_[i] != -1) { - v[i] = std::strtoll(pos, NULL, 10); + v[begin_ + i] = std::strtoll(pos, NULL, 10); } pos += colSize_; } @@ -227,12 +232,12 @@ void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) std::vector &v(*vp); char *pos = buf_; - std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) + std::size_t const vecSize = size(); + for (std::size_t i = 0; i != vecSize; ++i) { if (indOCIHolderVec_[i] != -1) { - v[i] = std::strtoull(pos, NULL, 10); + v[begin_ + i] = std::strtoull(pos, NULL, 10); } pos += colSize_; } @@ -245,8 +250,8 @@ void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) std::vector &v(*vp); ub1 *pos = reinterpret_cast(buf_); - std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) + std::size_t const vecSize = size(); + for (std::size_t i = 0; i != vecSize; ++i) { if (indOCIHolderVec_[i] == -1) { @@ -262,7 +267,8 @@ void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) int const minute = *pos++ - 1; int const second = *pos++ - 1; - details::mktime_from_ymdhms(v[i], year, month, day, hour, minute, second); + details::mktime_from_ymdhms(v[begin_ + i], + year, month, day, hour, minute, second); } } } @@ -280,15 +286,15 @@ void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) { if (indOCIHolderVec_[i] == 0) { - ind[i] = i_ok; + ind[begin_ + i] = i_ok; } else if (indOCIHolderVec_[i] == -1) { - ind[i] = i_null; + ind[begin_ + i] = i_null; } else { - ind[i] = i_truncated; + ind[begin_ + i] = i_truncated; } } } @@ -314,70 +320,105 @@ void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) void oracle_vector_into_type_backend::resize(std::size_t sz) { - switch (type_) + if (user_ranges_) { - // simple cases - case x_char: - { - std::vector *v = static_cast *>(data_); - v->resize(sz); - } - break; - case x_short: - { - std::vector *v = static_cast *>(data_); - v->resize(sz); - } - break; - case x_integer: - { - std::vector *v = static_cast *>(data_); - v->resize(sz); - } - break; - case x_long_long: - { - std::vector *v - = static_cast *>(data_); - v->resize(sz); - } - break; - case x_unsigned_long_long: - { - std::vector *v - = static_cast *>(data_); - v->resize(sz); - } - break; - case x_double: - { - std::vector *v - = static_cast *>(data_); - v->resize(sz); - } - break; - case x_stdstring: - { - std::vector *v - = static_cast *>(data_); - v->resize(sz); - } - break; - case x_stdtm: - { - std::vector *v - = static_cast *>(data_); - v->resize(sz); - } - break; - - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + // resize only in terms of user-provided ranges (below) } + else + { + switch (type_) + { + // simple cases + case x_char: + { + std::vector *v = static_cast *>(data_); + v->resize(sz); + } + break; + case x_short: + { + std::vector *v = static_cast *>(data_); + v->resize(sz); + } + break; + case x_integer: + { + std::vector *v = static_cast *>(data_); + v->resize(sz); + } + break; + case x_long_long: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_unsigned_long_long: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_double: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_stdstring: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + case x_stdtm: + { + std::vector *v + = static_cast *>(data_); + v->resize(sz); + } + break; + + case x_xmltype: break; // not supported + case x_longstring: break; // not supported + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported + } + + end_var_ = sz; + } + + // resize ranges, either user-provided or internally managed + *end_ = begin_ + sz; } std::size_t oracle_vector_into_type_backend::size() +{ + // as a special error-detection measure, check if the actual vector size + // was changed since the original bind (when it was stored in end_var_): + const std::size_t actual_size = full_size(); + if (actual_size != end_var_) + { + // ... and in that case return the actual size + return actual_size; + } + + if (end_ != NULL && *end_ != 0) + { + return *end_ - begin_; + } + else + { + return end_var_; + } +} + +std::size_t oracle_vector_into_type_backend::full_size() { std::size_t sz = 0; // dummy initialization to please the compiler switch (type_) @@ -437,9 +478,11 @@ std::size_t oracle_vector_into_type_backend::size() } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + case x_xmltype: break; // not supported + case x_longstring: break; // not supported + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported } return sz; diff --git a/src/backends/oracle/vector-use-type.cpp b/src/backends/oracle/vector-use-type.cpp index 4a78455fac..0d2846c567 100644 --- a/src/backends/oracle/vector-use-type.cpp +++ b/src/backends/oracle/vector-use-type.cpp @@ -36,7 +36,7 @@ void oracle_vector_use_type_backend::prepare_indicators(std::size_t size) } void oracle_vector_use_type_backend::prepare_for_bind( - void *&data, sb4 &size, ub2 &oracleType) + void * & data, sb4 & elementSize, ub2 & oracleType) { switch (type_) { @@ -44,41 +44,41 @@ void oracle_vector_use_type_backend::prepare_for_bind( case x_char: { oracleType = SQLT_AFC; - size = sizeof(char); - std::vector *vp = static_cast *>(data); + elementSize = sizeof(char); + std::vector *vp = static_cast *>(data_); std::vector &v(*vp); - prepare_indicators(v.size()); - data = &v[0]; + prepare_indicators(size()); + data = &v[begin_]; } break; case x_short: { oracleType = SQLT_INT; - size = sizeof(short); - std::vector *vp = static_cast *>(data); + elementSize = sizeof(short); + std::vector *vp = static_cast *>(data_); std::vector &v(*vp); - prepare_indicators(v.size()); - data = &v[0]; + prepare_indicators(size()); + data = &v[begin_]; } break; case x_integer: { oracleType = SQLT_INT; - size = sizeof(int); - std::vector *vp = static_cast *>(data); + elementSize = sizeof(int); + std::vector *vp = static_cast *>(data_); std::vector &v(*vp); - prepare_indicators(v.size()); - data = &v[0]; + prepare_indicators(size()); + data = &v[begin_]; } break; case x_double: { oracleType = statement_.session_.get_double_sql_type(); - size = sizeof(double); - std::vector *vp = static_cast *>(data); + elementSize = sizeof(double); + std::vector *vp = static_cast *>(data_); std::vector &v(*vp); - prepare_indicators(v.size()); - data = &v[0]; + prepare_indicators(size()); + data = &v[begin_]; } break; @@ -86,36 +86,28 @@ void oracle_vector_use_type_backend::prepare_for_bind( case x_long_long: { - std::vector *vp - = static_cast *>(data); - std::vector &v(*vp); - - std::size_t const vecSize = v.size(); + std::size_t const vecSize = size(); std::size_t const entrySize = 100; // arbitrary std::size_t const bufSize = entrySize * vecSize; buf_ = new char[bufSize]; oracleType = SQLT_STR; data = buf_; - size = entrySize; + elementSize = entrySize; prepare_indicators(vecSize); } break; case x_unsigned_long_long: { - std::vector *vp - = static_cast *>(data); - std::vector &v(*vp); - - std::size_t const vecSize = v.size(); + std::size_t const vecSize = size(); std::size_t const entrySize = 100; // arbitrary std::size_t const bufSize = entrySize * vecSize; buf_ = new char[bufSize]; oracleType = SQLT_STR; data = buf_; - size = entrySize; + elementSize = entrySize; prepare_indicators(vecSize); } @@ -123,15 +115,15 @@ void oracle_vector_use_type_backend::prepare_for_bind( case x_stdstring: { std::vector *vp - = static_cast *>(data); + = static_cast *>(data_); std::vector &v(*vp); std::size_t maxSize = 0; - std::size_t const vecSize = v.size(); + std::size_t const vecSize = size(); prepare_indicators(vecSize); for (std::size_t i = 0; i != vecSize; ++i) { - std::size_t sz = v[i].length(); + std::size_t sz = v[begin_ + i].length(); sizes_.push_back(static_cast(sz)); maxSize = sz > maxSize ? sz : maxSize; } @@ -140,47 +132,53 @@ void oracle_vector_use_type_backend::prepare_for_bind( char *pos = buf_; for (std::size_t i = 0; i != vecSize; ++i) { - strncpy(pos, v[i].c_str(), v[i].length()); + strncpy(pos, v[begin_ + i].c_str(), v[begin_ + i].length()); pos += maxSize; } oracleType = SQLT_CHR; data = buf_; - size = static_cast(maxSize); + elementSize = static_cast(maxSize); } break; case x_stdtm: { - std::vector *vp - = static_cast *>(data); - - prepare_indicators(vp->size()); + std::size_t const vecSize = size(); + prepare_indicators(vecSize); sb4 const dlen = 7; // size of SQLT_DAT - buf_ = new char[dlen * vp->size()]; + buf_ = new char[dlen * vecSize]; oracleType = SQLT_DAT; data = buf_; - size = dlen; + elementSize = dlen; } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + case x_xmltype: break; // not supported + case x_longstring: break; // not supported + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported } } -void oracle_vector_use_type_backend::bind_by_pos(int &position, - void *data, exchange_type type) +void oracle_vector_use_type_backend::bind_by_pos_bulk(int & position, + void * data, exchange_type type, + std::size_t begin, std::size_t * end) { data_ = data; // for future reference type_ = type; // for future reference + begin_ = begin; + end_ = end; + end_var_ = full_size(); + ub2 oracleType; - sb4 size; + sb4 elementSize; + void * dataBuf; - prepare_for_bind(data, size, oracleType); + prepare_for_bind(dataBuf, elementSize, oracleType); ub2 *sizesP = 0; // used only for std::string if (type == x_stdstring) @@ -190,7 +188,7 @@ void oracle_vector_use_type_backend::bind_by_pos(int &position, sword res = OCIBindByPos(statement_.stmtp_, &bindp_, statement_.session_.errhp_, - position++, data, size, oracleType, + position++, dataBuf, elementSize, oracleType, indOCIHolders_, sizesP, 0, 0, 0, OCI_DEFAULT); if (res != OCI_SUCCESS) { @@ -198,16 +196,22 @@ void oracle_vector_use_type_backend::bind_by_pos(int &position, } } -void oracle_vector_use_type_backend::bind_by_name( - std::string const &name, void *data, exchange_type type) +void oracle_vector_use_type_backend::bind_by_name_bulk( + std::string const &name, void *data, exchange_type type, + std::size_t begin, std::size_t * end) { data_ = data; // for future reference type_ = type; // for future reference + begin_ = begin; + end_ = end; + end_var_ = full_size(); + ub2 oracleType; - sb4 size; + sb4 elementSize; + void * dataBuf; - prepare_for_bind(data, size, oracleType); + prepare_for_bind(dataBuf, elementSize, oracleType); ub2 *sizesP = 0; // used only for std::string if (type == x_stdstring) @@ -219,7 +223,7 @@ void oracle_vector_use_type_backend::bind_by_name( statement_.session_.errhp_, reinterpret_cast(const_cast(name.c_str())), static_cast(name.size()), - data, size, oracleType, + dataBuf, elementSize, oracleType, indOCIHolders_, sizesP, 0, 0, 0, OCI_DEFAULT); if (res != OCI_SUCCESS) { @@ -244,10 +248,10 @@ void oracle_vector_use_type_backend::pre_use(indicator const *ind) char *pos = buf_; std::size_t const entrySize = 100; // arbitrary, but consistent - std::size_t const vecSize = v.size(); + std::size_t const vecSize = size(); for (std::size_t i = 0; i != vecSize; ++i) { - snprintf(pos, entrySize, "%" LL_FMT_FLAGS "d", v[i]); + snprintf(pos, entrySize, "%" LL_FMT_FLAGS "d", v[begin_ + i]); pos += entrySize; } } @@ -259,10 +263,10 @@ void oracle_vector_use_type_backend::pre_use(indicator const *ind) char *pos = buf_; std::size_t const entrySize = 100; // arbitrary, but consistent - std::size_t const vecSize = v.size(); + std::size_t const vecSize = size(); for (std::size_t i = 0; i != vecSize; ++i) { - snprintf(pos, entrySize, "%" LL_FMT_FLAGS "u", v[i]); + snprintf(pos, entrySize, "%" LL_FMT_FLAGS "u", v[begin_ + i]); pos += entrySize; } } @@ -273,26 +277,28 @@ void oracle_vector_use_type_backend::pre_use(indicator const *ind) std::vector &v(*vp); ub1* pos = reinterpret_cast(buf_); - std::size_t const vsize = v.size(); - for (std::size_t i = 0; i != vsize; ++i) + std::size_t const vecSize = size(); + for (std::size_t i = 0; i != vecSize; ++i) { - *pos++ = static_cast(100 + (1900 + v[i].tm_year) / 100); - *pos++ = static_cast(100 + v[i].tm_year % 100); - *pos++ = static_cast(v[i].tm_mon + 1); - *pos++ = static_cast(v[i].tm_mday); - *pos++ = static_cast(v[i].tm_hour + 1); - *pos++ = static_cast(v[i].tm_min + 1); - *pos++ = static_cast(v[i].tm_sec + 1); + std::tm & t = v[begin_ + i]; + + *pos++ = static_cast(100 + (1900 + t.tm_year) / 100); + *pos++ = static_cast(100 + t.tm_year % 100); + *pos++ = static_cast(t.tm_mon + 1); + *pos++ = static_cast(t.tm_mday); + *pos++ = static_cast(t.tm_hour + 1); + *pos++ = static_cast(t.tm_min + 1); + *pos++ = static_cast(t.tm_sec + 1); } } // then handle indicators if (ind != NULL) { - std::size_t const vsize = size(); - for (std::size_t i = 0; i != vsize; ++i, ++ind) + std::size_t const vecSize = size(); + for (std::size_t i = 0; i != vecSize; ++i) { - if (*ind == i_null) + if (ind[begin_ + i] == i_null) { indOCIHolderVec_[i] = -1; // null } @@ -305,8 +311,8 @@ void oracle_vector_use_type_backend::pre_use(indicator const *ind) else { // no indicators - treat all fields as OK - std::size_t const vsize = size(); - for (std::size_t i = 0; i != vsize; ++i, ++ind) + std::size_t const vecSize = size(); + for (std::size_t i = 0; i != vecSize; ++i) { indOCIHolderVec_[i] = 0; // value is OK } @@ -314,6 +320,27 @@ void oracle_vector_use_type_backend::pre_use(indicator const *ind) } std::size_t oracle_vector_use_type_backend::size() +{ + // as a special error-detection measure, check if the actual vector size + // was changed since the original bind (when it was stored in end_var_): + const std::size_t actual_size = full_size(); + if (actual_size != end_var_) + { + // ... and in that case return the actual size + return actual_size; + } + + if (end_ != NULL && *end_ != 0) + { + return *end_ - begin_; + } + else + { + return end_var_; + } +} + +std::size_t oracle_vector_use_type_backend::full_size() { std::size_t sz = 0; // dummy initialization to please the compiler switch (type_) @@ -373,9 +400,11 @@ std::size_t oracle_vector_use_type_backend::size() } break; - case x_statement: break; // not supported - case x_rowid: break; // not supported - case x_blob: break; // not supported + case x_xmltype: break; // not supported + case x_longstring: break; // not supported + case x_statement: break; // not supported + case x_rowid: break; // not supported + case x_blob: break; // not supported } return sz; diff --git a/src/backends/postgresql/CMakeLists.txt b/src/backends/postgresql/CMakeLists.txt index 80c329e66c..09b781abd9 100644 --- a/src/backends/postgresql/CMakeLists.txt +++ b/src/backends/postgresql/CMakeLists.txt @@ -11,6 +11,10 @@ include(CMakeDependentOption) +option(SOCI_POSTGRESQL_NOSINLGEROWMODE + "Do not use single row mode. PostgreSQL <9 portability." + OFF) + option(SOCI_POSTGRESQL_NOPARAMS "Do not use input parameters. PostgreSQL 7.x portability." OFF) @@ -35,6 +39,14 @@ if(SOCI_POSTGRESQL_NOPREPARE) add_definitions(-DSOCI_POSTGRESQL_NOPREPARE=1) endif() +if (POSTGRESQL_VERSION VERSION_LESS "9.0.0") + set(SOCI_POSTGRESQL_NOSINLGEROWMODE ON CACHE BOOL "Use single row mode for PostgreSQL 9+" FORCE) +endif() + +if(SOCI_POSTGRESQL_NOSINLGEROWMODE) + add_definitions(-DSOCI_POSTGRESQL_NOSINLGEROWMODE=1) +endif() + soci_backend(PostgreSQL DEPENDS PostgreSQL DESCRIPTION "SOCI backend for PostgreSQL" @@ -44,3 +56,4 @@ soci_backend(PostgreSQL boost_report_value(SOCI_POSTGRESQL_NOPARAMS) boost_report_value(SOCI_POSTGRESQL_NOBINDBYNAME) boost_report_value(SOCI_POSTGRESQL_NOPREPARE) +boost_report_value(SOCI_POSTGRESQL_NOSINLGEROWMODE) diff --git a/src/backends/postgresql/Makefile.basic b/src/backends/postgresql/Makefile.basic index b4dd9c5c87..473abfffdd 100644 --- a/src/backends/postgresql/Makefile.basic +++ b/src/backends/postgresql/Makefile.basic @@ -43,7 +43,7 @@ blob.o : blob.cpp error.o : error.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} -common.o : common.cpp +common.o : ../../core/common.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} factory.o : factory.cpp @@ -82,7 +82,7 @@ blob-s.o : blob.cpp error-s.o : error.cpp ${COMPILER} -c -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} -common-s.o : common.cpp +common-s.o : ../../core/common.cpp ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} factory-s.o : factory.cpp diff --git a/src/backends/postgresql/common.h b/src/backends/postgresql/common.h index 4a96d2078e..4da94fbaa7 100644 --- a/src/backends/postgresql/common.h +++ b/src/backends/postgresql/common.h @@ -112,9 +112,6 @@ T string_to_unsigned_integer(char const * buf) } } -// helper function for parsing datetime values -void parse_std_tm(char const * buf, std::tm & t); - // helper for vector operations template std::size_t get_vector_size(void * p) diff --git a/src/backends/postgresql/error.cpp b/src/backends/postgresql/error.cpp index e7b59fb901..172eca01c0 100644 --- a/src/backends/postgresql/error.cpp +++ b/src/backends/postgresql/error.cpp @@ -7,6 +7,8 @@ #define SOCI_POSTGRESQL_SOURCE #include "soci/postgresql/soci-postgresql.h" +#include "soci/callbacks.h" +#include "soci/connection-parameters.h" #include using namespace soci; @@ -14,9 +16,37 @@ using namespace soci::details; postgresql_soci_error::postgresql_soci_error( std::string const & msg, char const *sqlst) - : soci_error(msg) + : soci_error(msg), cat_(unknown) { std::memcpy(sqlstate_, sqlst, 5); + + if (std::memcmp(sqlst, "08", 2) == 0) + { + cat_ = connection_error; + } + else if (std::memcmp(sqlst, "42501", 5) == 0) + { + cat_ = no_privilege; + } + else if (std::memcmp(sqlst, "42", 2) == 0) + { + cat_ = invalid_statement; + } + else if (std::memcmp(sqlst, "02", 2) == 0) + { + cat_ = no_data; + } + else if (std::memcmp(sqlst, "23", 2) == 0) + { + cat_ = constraint_violation; + } + else if ((std::memcmp(sqlst, "53", 2) == 0) || + (std::memcmp(sqlst, "54", 2) == 0) || + (std::memcmp(sqlst, "58", 2) == 0) || + (std::memcmp(sqlst, "XX", 2) == 0)) + { + cat_ = system_error; + } } std::string postgresql_soci_error::sqlstate() const @@ -33,6 +63,8 @@ details::postgresql_result::check_for_errors(char const* errMsg) const bool details::postgresql_result::check_for_data(char const* errMsg) const { + std::string msg(errMsg); + ExecStatusType const status = PQresultStatus(result_); switch (status) { @@ -44,19 +76,74 @@ details::postgresql_result::check_for_data(char const* errMsg) const case PGRES_TUPLES_OK: return true; + case PGRES_FATAL_ERROR: + msg += " Fatal error."; + + if (PQstatus(sessionBackend_.conn_) == CONNECTION_BAD) + { + msg += " Connection failed."; + + // call the failover callback, if registered + + failover_callback * callback = sessionBackend_.failoverCallback_; + if (callback != NULL) + { + bool reconnected = false; + + try + { + callback->started(); + + bool retry = false; + std::string newTarget; + + callback->failed(retry, newTarget); + + if (retry) + { + connection_parameters parameters("postgresql", newTarget); + + sessionBackend_.clean_up(); + + sessionBackend_.connect(parameters); + + reconnected = true; + } + } + catch (...) + { + // ignore exceptions from user callbacks + } + + if (reconnected == false) + { + try + { + callback->aborted(); + } + catch (...) + { + // ignore exceptions from user callbacks + } + } + } + } + + break; + default: // Some of the other status codes are not really errors but we're // not prepared to handle them right now and shouldn't ever receive // them so throw nevertheless + break; } - std::string msg(errMsg); const char* const pqError = PQresultErrorMessage(result_); if (pqError && *pqError) { - msg += " "; - msg += pqError; + msg += " "; + msg += pqError; } const char* sqlstate = PQresultErrorField(result_, PG_DIAG_SQLSTATE); diff --git a/src/backends/postgresql/factory.cpp b/src/backends/postgresql/factory.cpp index 4deb5a5307..7744afa54c 100644 --- a/src/backends/postgresql/factory.cpp +++ b/src/backends/postgresql/factory.cpp @@ -7,6 +7,7 @@ #define SOCI_POSTGRESQL_SOURCE #include "soci/postgresql/soci-postgresql.h" +#include "soci/connection-parameters.h" #include "soci/backend-loader.h" #include // libpq @@ -23,11 +24,103 @@ using namespace soci; using namespace soci::details; +namespace // unnamed +{ + +// iterates the string pointed by i, searching for pairs of key value. +// it returns the position after the value +std::string::const_iterator get_key_value(std::string::const_iterator & i, + std::string::const_iterator const & end, + std::string & key, + std::string & value) +{ + bool in_value = false; + bool quoted = false; + + key.clear(); + value.clear(); + + while (i != end) + { + if (in_value == false) + { + if (*i == '=') + { + in_value = true; + if (i != end && *(i + 1) == '"') + { + quoted = true; + ++i; // jump over the quote + } + } + else if (!isspace(*i)) + { + key += *i; + } + } + else + { + if ((quoted == true && *i == '"') || (quoted == false && isspace(*i))) + { + return ++i; + } + else + { + value += *i; + } + } + ++i; + } + return i; +} + +// retrieves specific parameters from the +// uniform connect string +std::string chop_connect_string(std::string const & connectString, + bool & single_row_mode) +{ + std::string pruned_conn_string; + + single_row_mode = false; + + std::string key, value; + std::string::const_iterator i = connectString.begin(); + while (i != connectString.end()) + { + i = get_key_value(i, connectString.end(), key, value); + if (key == "singlerow" || key == "singlerows") + { + single_row_mode = (value == "true" || value == "yes"); + } + else + { + if (pruned_conn_string.empty() == false) + { + pruned_conn_string += ' '; + } + + pruned_conn_string += key + '=' + value; + } + } + + return pruned_conn_string; +} + +} // unnamed namespace + // concrete factory for Empty concrete strategies postgresql_session_backend * postgresql_backend_factory::make_session( connection_parameters const & parameters) const { - return new postgresql_session_backend(parameters); + bool single_row_mode; + + const std::string pruned_conn_string = + chop_connect_string(parameters.get_connect_string(), single_row_mode); + + connection_parameters pruned_parameters(parameters); + pruned_parameters.set_connect_string(pruned_conn_string); + + return new postgresql_session_backend(pruned_parameters, single_row_mode); } postgresql_backend_factory const soci::postgresql; diff --git a/src/backends/postgresql/row-id.cpp b/src/backends/postgresql/row-id.cpp index 6fb488ce23..8789969b37 100644 --- a/src/backends/postgresql/row-id.cpp +++ b/src/backends/postgresql/row-id.cpp @@ -30,8 +30,8 @@ using namespace soci::details; postgresql_rowid_backend::postgresql_rowid_backend( postgresql_session_backend & /* session */) + : value_(0) { - // nothing to do here } postgresql_rowid_backend::~postgresql_rowid_backend() diff --git a/src/backends/postgresql/session.cpp b/src/backends/postgresql/session.cpp index a5b23bba70..1adab44b1a 100644 --- a/src/backends/postgresql/session.cpp +++ b/src/backends/postgresql/session.cpp @@ -30,16 +30,25 @@ namespace // unnamed { // helper function for hardcoded queries -void hard_exec(PGconn * conn, char const * query, char const * errMsg) +void hard_exec(postgresql_session_backend & session_backend, + PGconn * conn, char const * query, char const * errMsg) { - postgresql_result(PQexec(conn, query)).check_for_errors(errMsg); + postgresql_result(session_backend, PQexec(conn, query)).check_for_errors(errMsg); } } // namespace unnamed postgresql_session_backend::postgresql_session_backend( - connection_parameters const& parameters) + connection_parameters const& parameters, bool single_row_mode) : statementCount_(0) +{ + single_row_mode_ = single_row_mode; + + connect(parameters); +} + +void postgresql_session_backend::connect( + connection_parameters const& parameters) { PGconn* conn = PQconnectdb(parameters.get_connect_string().c_str()); if (0 == conn || CONNECTION_OK != PQstatus(conn)) @@ -60,7 +69,7 @@ postgresql_session_backend::postgresql_session_backend( // case with the default value of 0. Use the maximal supported value, which // was 2 until 9.x and is 3 since it. int const version = PQserverVersion(conn); - hard_exec(conn, + hard_exec(*this, conn, version >= 90000 ? "SET extra_float_digits = 3" : "SET extra_float_digits = 2", "Cannot set extra_float_digits parameter"); @@ -75,17 +84,17 @@ postgresql_session_backend::~postgresql_session_backend() void postgresql_session_backend::begin() { - hard_exec(conn_, "BEGIN", "Cannot begin transaction."); + hard_exec(*this, conn_, "BEGIN", "Cannot begin transaction."); } void postgresql_session_backend::commit() { - hard_exec(conn_, "COMMIT", "Cannot commit transaction."); + hard_exec(*this, conn_, "COMMIT", "Cannot commit transaction."); } void postgresql_session_backend::rollback() { - hard_exec(conn_, "ROLLBACK", "Cannot rollback transaction."); + hard_exec(*this, conn_, "ROLLBACK", "Cannot rollback transaction."); } void postgresql_session_backend::deallocate_prepared_statement( @@ -93,7 +102,7 @@ void postgresql_session_backend::deallocate_prepared_statement( { const std::string & query = "DEALLOCATE " + statementName; - hard_exec(conn_, query.c_str(), + hard_exec(*this, conn_, query.c_str(), "Cannot deallocate prepared statement."); } @@ -123,7 +132,7 @@ std::string postgresql_session_backend::get_next_statement_name() postgresql_statement_backend * postgresql_session_backend::make_statement_backend() { - return new postgresql_statement_backend(*this); + return new postgresql_statement_backend(*this, single_row_mode_); } postgresql_rowid_backend * postgresql_session_backend::make_rowid_backend() diff --git a/src/backends/postgresql/standard-into-type.cpp b/src/backends/postgresql/standard-into-type.cpp index 3f54d8af78..93fc23c1a6 100644 --- a/src/backends/postgresql/standard-into-type.cpp +++ b/src/backends/postgresql/standard-into-type.cpp @@ -9,9 +9,11 @@ #include "soci/soci-platform.h" #include "soci/postgresql/soci-postgresql.h" #include "soci-cstrtod.h" +#include "soci-mktime.h" #include "common.h" #include "soci/rowid.h" #include "soci/blob.h" +#include "soci/type-wrappers.h" #include "soci-exchange-cast.h" #include // libpq #include @@ -150,6 +152,12 @@ void postgresql_standard_into_type_backend::post_fetch( bbe->oid_ = oid; } break; + case x_xmltype: + exchange_type_cast(data_).value.assign(buf); + break; + case x_longstring: + exchange_type_cast(data_).value.assign(buf); + break; default: throw soci_error("Into element used with non-supported type."); diff --git a/src/backends/postgresql/standard-use-type.cpp b/src/backends/postgresql/standard-use-type.cpp index 94dc99c1a1..177181fc3e 100644 --- a/src/backends/postgresql/standard-use-type.cpp +++ b/src/backends/postgresql/standard-use-type.cpp @@ -9,6 +9,7 @@ #include "soci/postgresql/soci-postgresql.h" #include "soci/blob.h" #include "soci/rowid.h" +#include "soci/type-wrappers.h" #include "soci/soci-platform.h" #include "soci-dtocstr.h" #include "soci-exchange-cast.h" @@ -70,11 +71,7 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind) } break; case x_stdstring: - { - std::string const& s = exchange_type_cast(data_); - buf_ = new char[s.size() + 1]; - std::strcpy(buf_, s.c_str()); - } + copy_from_string(exchange_type_cast(data_)); break; case x_short: { @@ -113,12 +110,7 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind) } break; case x_double: - { - std::string const s = double_to_cstring(exchange_type_cast(data_)); - - buf_ = new char[s.size() + 1]; - std::strcpy(buf_, s.c_str()); - } + copy_from_string(double_to_cstring(exchange_type_cast(data_))); break; case x_stdtm: { @@ -159,7 +151,13 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind) snprintf(buf_, bufSize, "%lu", bbe->oid_); } break; - + case x_xmltype: + copy_from_string(exchange_type_cast(data_).value); + break; + case x_longstring: + copy_from_string(exchange_type_cast(data_).value); + break; + default: throw soci_error("Use element used with non-supported type."); } @@ -198,3 +196,9 @@ void postgresql_standard_use_type_backend::clean_up() buf_ = NULL; } } + +void postgresql_standard_use_type_backend::copy_from_string(std::string const& s) +{ + buf_ = new char[s.size() + 1]; + std::strcpy(buf_, s.c_str()); +} diff --git a/src/backends/postgresql/statement.cpp b/src/backends/postgresql/statement.cpp index 4499305fbf..5c0aabe1be 100644 --- a/src/backends/postgresql/statement.cpp +++ b/src/backends/postgresql/statement.cpp @@ -25,13 +25,53 @@ using namespace soci; using namespace soci::details; -postgresql_statement_backend::postgresql_statement_backend( - postgresql_session_backend &session) - : session_(session) - , rowsAffectedBulk_(-1LL), justDescribed_(false) - , hasIntoElements_(false), hasVectorIntoElements_(false) - , hasUseElements_(false), hasVectorUseElements_(false) +namespace // unnamed { + +// used only with asynchronous operations in single-row mode + +void wait_until_operation_complete(postgresql_session_backend & session) +{ + while (true) + { + PGresult * result = PQgetResult(session.conn_); + if (result == NULL) + { + break; + } + else + { + postgresql_result r(session, result); + r.check_for_errors("Cannot execute asynchronous query in single-row mode"); + } + } +} + +void throw_soci_error(PGconn * conn, const char * msg) +{ + std::string description = msg; + description += ": "; + description += PQerrorMessage(conn); + + throw soci_error(description); +} + +} // unnamed namespace + +postgresql_statement_backend::postgresql_statement_backend( + postgresql_session_backend &session, bool single_row_mode) + : session_(session), single_row_mode_(single_row_mode), + result_(session, NULL), + rowsAffectedBulk_(-1LL), justDescribed_(false), + hasIntoElements_(false), hasVectorIntoElements_(false), + hasUseElements_(false), hasVectorUseElements_(false) +{ +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode) + { + throw soci_error("Single row mode not supported in this version of the library"); + } +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE } postgresql_statement_backend::~postgresql_statement_backend() @@ -185,10 +225,31 @@ void postgresql_statement_backend::prepare(std::string const & query, // if it fails to prepare it we can't DEALLOCATE it. std::string statementName = session_.get_next_statement_name(); - postgresql_result result( - PQprepare(session_.conn_, statementName.c_str(), - query_.c_str(), static_cast(names_.size()), NULL)); - result.check_for_errors("Cannot prepare statement."); +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + // prepare for single-row retrieval + + int result = PQsendPrepare(session_.conn_, statementName.c_str(), + query_.c_str(), static_cast(names_.size()), NULL); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot prepare statement in singlerow mode"); + } + + wait_until_operation_complete(session_); + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row query execution + + postgresql_result result(session_, + PQprepare(session_.conn_, statementName.c_str(), + query_.c_str(), static_cast(names_.size()), NULL)); + result.check_for_errors("Cannot prepare statement."); + } // Now it's safe to save this info. statementName_ = statementName; @@ -202,18 +263,29 @@ void postgresql_statement_backend::prepare(std::string const & query, statement_backend::exec_fetch_result postgresql_statement_backend::execute(int number) { +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_ && (number > 1)) + { + throw soci_error("Bulk operations are not supported with single-row mode."); + } +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + // If the statement was "just described", then we know that // it was actually executed with all the use elements // already bound and pre-used. This means that the result of the // query is already on the client side, so there is no need // to re-execute it. + // The optimization based on the existing results + // from the row description can be performed only once. + // If the same statement is re-executed, + // it will be *really* re-executed, without reusing existing data. if (justDescribed_ == false) { // This object could have been already filled with data before. clean_up(); - if (number > 1 && hasIntoElements_) + if ((number > 1) && hasIntoElements_) { throw soci_error( "Bulk use with single into elements is not supported."); @@ -295,27 +367,103 @@ postgresql_statement_backend::execute(int number) #ifdef SOCI_POSTGRESQL_NOPREPARE - result_.reset(PQexecParams(session_.conn_, query_.c_str(), - static_cast(paramValues.size()), - NULL, ¶mValues[0], NULL, NULL, 0)); +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + int result = PQsendQueryParams( + session_.conn_, query_.c_str(), + static_cast(paramValues.size()), + NULL, ¶mValues[0], NULL, NULL, 0); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot execute query in single-row mode"); + } + + result = PQsetSingleRowMode(session_.conn_); + if (result != 1) + { + throw_soci_error(session_.conn_, + "cannot set singlerow mode"); + } + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row execution + result_.reset(PQexecParams(session_.conn_, query_.c_str(), + static_cast(paramValues.size()), + NULL, ¶mValues[0], NULL, NULL, 0)); + } #else if (stType_ == st_repeatable_query) { // this query was separately prepared - result_.reset(PQexecPrepared(session_.conn_, - statementName_.c_str(), - static_cast(paramValues.size()), - ¶mValues[0], NULL, NULL, 0)); +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + int result = PQsendQueryPrepared(session_.conn_, + statementName_.c_str(), + static_cast(paramValues.size()), + ¶mValues[0], NULL, NULL, 0); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot execute prepared query in single-row mode"); + } + + result = PQsetSingleRowMode(session_.conn_); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot set singlerow mode"); + } + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row execution + + result_.reset(PQexecPrepared(session_.conn_, + statementName_.c_str(), + static_cast(paramValues.size()), + ¶mValues[0], NULL, NULL, 0)); + } } else // stType_ == st_one_time_query { // this query was not separately prepared and should // be executed as a one-time query - result_.reset(PQexecParams(session_.conn_, query_.c_str(), - static_cast(paramValues.size()), - NULL, ¶mValues[0], NULL, NULL, 0)); +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + int result = PQsendQueryParams(session_.conn_, query_.c_str(), + static_cast(paramValues.size()), + NULL, ¶mValues[0], NULL, NULL, 0); + if (result != 1) + { + throw_soci_error(session_.conn_, + "cannot execute query in single-row mode"); + } + + result = PQsetSingleRowMode(session_.conn_); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot set singlerow mode"); + } + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row execution + + result_.reset(PQexecParams(session_.conn_, query_.c_str(), + static_cast(paramValues.size()), + NULL, ¶mValues[0], NULL, NULL, 0)); + } } #endif // SOCI_POSTGRESQL_NOPREPARE @@ -352,34 +500,122 @@ postgresql_statement_backend::execute(int number) #ifdef SOCI_POSTGRESQL_NOPREPARE - result_.reset(PQexec(session_.conn_, query_.c_str())); +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + int result = PQsendQuery(session_.conn_, query_.c_str()); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot execute query in single-row mode"); + } + + result = PQsetSingleRowMode(session_.conn_); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot set single-row mode"); + } + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row execution + + result_.reset(PQexec(session_.conn_, query_.c_str())); + } #else if (stType_ == st_repeatable_query) { // this query was separately prepared - result_.reset(PQexecPrepared(session_.conn_, - statementName_.c_str(), 0, NULL, NULL, NULL, 0)); +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + int result = PQsendQueryPrepared(session_.conn_, + statementName_.c_str(), 0, NULL, NULL, NULL, 0); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot execute prepared query in single-row mode"); + } + + result = PQsetSingleRowMode(session_.conn_); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot set singlerow mode"); + } + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row execution + + result_.reset(PQexecPrepared(session_.conn_, + statementName_.c_str(), 0, NULL, NULL, NULL, 0)); + } } else // stType_ == st_one_time_query { - result_.reset(PQexec(session_.conn_, query_.c_str())); +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + int result = PQsendQuery(session_.conn_, query_.c_str()); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot execute query in single-row mode"); + } + + result = PQsetSingleRowMode(session_.conn_); + if (result != 1) + { + throw_soci_error(session_.conn_, + "Cannot set single-row mode"); + } + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row execution + + result_.reset(PQexec(session_.conn_, query_.c_str())); + } } #endif // SOCI_POSTGRESQL_NOPREPARE } } - else - { - // The optimization based on the existing results - // from the row description can be performed only once. - // If the same statement is re-executed, - // it will be *really* re-executed, without reusing existing data. - justDescribed_ = false; + bool process_result; +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + if (justDescribed_) + { + // reuse the result_ that was already filled when executing the query + // for the purpose of row describe + } + else + { + PGresult * res = PQgetResult(session_.conn_); + result_.reset(res); + } + + process_result = result_.check_for_data("Cannot execute query."); + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row execution + + process_result = result_.check_for_data("Cannot execute query."); } - if (result_.check_for_data("Cannot execute query.")) + justDescribed_ = false; + + if (process_result) { currentRow_ = 0; rowsToConsume_ = 0; @@ -405,41 +641,106 @@ postgresql_statement_backend::execute(int number) } else { - return ef_no_data; + return ef_no_data; } } statement_backend::exec_fetch_result postgresql_statement_backend::fetch(int number) { - // Note: This function does not actually fetch anything from anywhere +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_ && (number > 1)) + { + throw soci_error("Bulk operations are not supported with single-row mode."); + } +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + + // Note: + // In the multi-row mode this function does not actually fetch anything from anywhere // - the data was already retrieved from the server in the execute() // function, and the actual consumption of this data will take place // in the postFetch functions, called for each into element. // Here, we only prepare for this to happen (to emulate "the Oracle way"). + // In the single-row mode the fetch of single row of data is performed as expected. // forward the "cursor" from the last fetch currentRow_ += rowsToConsume_; if (currentRow_ >= numberOfRows_) { - // all rows were already consumed - return ef_no_data; +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + PGresult* res = PQgetResult(session_.conn_); + result_.reset(res); + + if (res == NULL) + { + return ef_no_data; + } + + currentRow_ = 0; + rowsToConsume_ = 0; + + numberOfRows_ = PQntuples(result_); + if (numberOfRows_ == 0) + { + return ef_no_data; + } + else + { + rowsToConsume_ = 1; + + return ef_success; + } + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row execution + + // all rows were already consumed + + return ef_no_data; + } } else { if (currentRow_ + number > numberOfRows_) { - rowsToConsume_ = numberOfRows_ - currentRow_; +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + rowsToConsume_ = 1; + + return ef_success; + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + // default multi-row execution + + rowsToConsume_ = numberOfRows_ - currentRow_; - // this simulates the behaviour of Oracle - // - when EOF is hit, we return ef_no_data even when there are - // actually some rows fetched - return ef_no_data; + // this simulates the behaviour of Oracle + // - when EOF is hit, we return ef_no_data even when there are + // actually some rows fetched + return ef_no_data; + } } else { - rowsToConsume_ = number; +#ifndef SOCI_POSTGRESQL_NOSINLGEROWMODE + if (single_row_mode_) + { + rowsToConsume_ = 1; + } + else +#endif // !SOCI_POSTGRESQL_NOSINLGEROWMODE + { + rowsToConsume_ = number; + } + return ef_success; } } @@ -512,9 +813,9 @@ void postgresql_statement_backend::describe_column(int colNum, data_type & type, case 2275: // cstring case 18: // char case 1042: // bpchar - case 142: // xml + case 142: // xml case 114: // json - case 17: // bytea + case 17: // bytea case 2950: // uuid type = dt_string; break; @@ -557,7 +858,9 @@ void postgresql_statement_backend::describe_column(int colNum, data_type & type, else { std::stringstream message; - message << "unknown data type with typelem: " << typeOid << " for colNum: " << colNum << " with name: " << PQfname(result_, pos); + message << "unknown data type with typelem: " << typeOid + << " for colNum: " << colNum + << " with name: " << PQfname(result_, pos); throw soci_error(message.str()); } } diff --git a/src/backends/postgresql/vector-into-type.cpp b/src/backends/postgresql/vector-into-type.cpp index df9481a015..b5b94a086c 100644 --- a/src/backends/postgresql/vector-into-type.cpp +++ b/src/backends/postgresql/vector-into-type.cpp @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -9,7 +9,9 @@ #include "soci/soci-platform.h" #include "soci/postgresql/soci-postgresql.h" #include "soci-cstrtod.h" +#include "soci-mktime.h" #include "common.h" +#include "soci/type-wrappers.h" #include // libpq #include #include @@ -28,12 +30,17 @@ using namespace soci::details; using namespace soci::details::postgresql; -void postgresql_vector_into_type_backend::define_by_pos( - int & position, void * data, exchange_type type) +void postgresql_vector_into_type_backend::define_by_pos_bulk( + int & position, void * data, exchange_type type, + std::size_t begin, std::size_t * end) { data_ = data; type_ = type; + begin_ = begin; + end_ = end; position_ = position++; + + end_var_ = full_size(); } void postgresql_vector_into_type_backend::pre_fetch() @@ -54,6 +61,16 @@ void set_invector_(void * p, int indx, T const & val) v[indx] = val; } +template +void set_invector_wrappers_(void * p, int indx, V const & val) +{ + std::vector * dest = + static_cast *>(p); + + std::vector & v = *dest; + v[indx].value = val; +} + } // namespace anonymous void postgresql_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) @@ -68,7 +85,7 @@ void postgresql_vector_into_type_backend::post_fetch(bool gotData, indicator * i int const endRow = statement_.currentRow_ + statement_.rowsToConsume_; - for (int curRow = statement_.currentRow_, i = 0; + for (int curRow = statement_.currentRow_, i = begin_; curRow != endRow; ++curRow, ++i) { // first, deal with indicators @@ -138,12 +155,18 @@ void postgresql_vector_into_type_backend::post_fetch(bool gotData, indicator * i case x_stdtm: { // attempt to parse the string and convert to std::tm - std::tm t; + std::tm t = std::tm(); parse_std_tm(buf, t); set_invector_(data_, i, t); } break; + case x_xmltype: + set_invector_wrappers_(data_, i, buf); + break; + case x_longstring: + set_invector_wrappers_(data_, i, buf); + break; default: throw soci_error("Into element used with non-supported type."); @@ -170,39 +193,78 @@ void resizevector_(void * p, std::size_t sz) void postgresql_vector_into_type_backend::resize(std::size_t sz) { - switch (type_) + if (user_ranges_) { - // simple cases - case x_char: - resizevector_(data_, sz); - break; - case x_short: - resizevector_(data_, sz); - break; - case x_integer: - resizevector_(data_, sz); - break; - case x_long_long: - resizevector_(data_, sz); - break; - case x_unsigned_long_long: - resizevector_(data_, sz); - break; - case x_double: - resizevector_(data_, sz); - break; - case x_stdstring: - resizevector_(data_, sz); - break; - case x_stdtm: - resizevector_(data_, sz); - break; - default: - throw soci_error("Into vector element used with non-supported type."); + // resize only in terms of user-provided ranges (below) } + else + { + switch (type_) + { + // simple cases + case x_char: + resizevector_(data_, sz); + break; + case x_short: + resizevector_(data_, sz); + break; + case x_integer: + resizevector_(data_, sz); + break; + case x_long_long: + resizevector_(data_, sz); + break; + case x_unsigned_long_long: + resizevector_(data_, sz); + break; + case x_double: + resizevector_(data_, sz); + break; + case x_stdstring: + resizevector_(data_, sz); + break; + case x_stdtm: + resizevector_(data_, sz); + break; + case x_xmltype: + resizevector_(data_, sz); + break; + case x_longstring: + resizevector_(data_, sz); + break; + default: + throw soci_error("Into vector element used with non-supported type."); + } + + end_var_ = sz; + } + + // resize ranges, either user-provided or internally managed + *end_ = begin_ + sz; } std::size_t postgresql_vector_into_type_backend::size() +{ + // as a special error-detection measure, check if the actual vector size + // was changed since the original bind (when it was stored in end_var_): + const std::size_t actual_size = full_size(); + if (actual_size != end_var_) + { + // ... and in that case return the actual size + return actual_size; + } + + if (end_ != NULL && *end_ != 0) + { + return *end_ - begin_; + } + else + { + return end_var_; + } +} + +std::size_t postgresql_vector_into_type_backend::full_size() { std::size_t sz = 0; // dummy initialization to please the compiler switch (type_) @@ -232,6 +294,12 @@ std::size_t postgresql_vector_into_type_backend::size() case x_stdtm: sz = get_vector_size(data_); break; + case x_xmltype: + sz = get_vector_size(data_); + break; + case x_longstring: + sz = get_vector_size(data_); + break; default: throw soci_error("Into vector element used with non-supported type."); } diff --git a/src/backends/postgresql/vector-use-type.cpp b/src/backends/postgresql/vector-use-type.cpp index a8809d71d9..5d0cb15715 100644 --- a/src/backends/postgresql/vector-use-type.cpp +++ b/src/backends/postgresql/vector-use-type.cpp @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -10,6 +10,7 @@ #include "soci/postgresql/soci-postgresql.h" #include "soci-dtocstr.h" #include "common.h" +#include "soci/type-wrappers.h" #include // libpq #include #include @@ -30,26 +31,46 @@ using namespace soci::details; using namespace soci::details::postgresql; -void postgresql_vector_use_type_backend::bind_by_pos(int & position, - void * data, exchange_type type) +void postgresql_vector_use_type_backend::bind_by_pos_bulk(int & position, + void * data, exchange_type type, + std::size_t begin, std::size_t * end) { data_ = data; type_ = type; + begin_ = begin; + end_ = end; position_ = position++; + + end_var_ = full_size(); } -void postgresql_vector_use_type_backend::bind_by_name( - std::string const & name, void * data, exchange_type type) +void postgresql_vector_use_type_backend::bind_by_name_bulk( + std::string const & name, void * data, exchange_type type, + std::size_t begin, std::size_t * end) { data_ = data; type_ = type; + begin_ = begin; + end_ = end; name_ = name; + + end_var_ = full_size(); } void postgresql_vector_use_type_backend::pre_use(indicator const * ind) { - std::size_t const vsize = size(); - for (size_t i = 0; i != vsize; ++i) + std::size_t vend; + + if (end_ != NULL && *end_ != 0) + { + vend = *end_; + } + else + { + vend = end_var_; + } + + for (size_t i = begin_; i != vend; ++i) { char * buf; @@ -158,6 +179,26 @@ void postgresql_vector_use_type_backend::pre_use(indicator const * ind) v[i].tm_hour, v[i].tm_min, v[i].tm_sec); } break; + case x_xmltype: + { + std::vector * pv + = static_cast *>(data_); + std::vector & v = *pv; + + buf = new char[v[i].value.size() + 1]; + std::strcpy(buf, v[i].value.c_str()); + } + break; + case x_longstring: + { + std::vector * pv + = static_cast *>(data_); + std::vector & v = *pv; + + buf = new char[v[i].value.size() + 1]; + std::strcpy(buf, v[i].value.c_str()); + } + break; default: throw soci_error( @@ -181,6 +222,27 @@ void postgresql_vector_use_type_backend::pre_use(indicator const * ind) } std::size_t postgresql_vector_use_type_backend::size() +{ + // as a special error-detection measure, check if the actual vector size + // was changed since the original bind (when it was stored in end_var_): + const std::size_t actual_size = full_size(); + if (actual_size != end_var_) + { + // ... and in that case return the actual size + return actual_size; + } + + if (end_ != NULL && *end_ != 0) + { + return *end_ - begin_; + } + else + { + return end_var_; + } +} + +std::size_t postgresql_vector_use_type_backend::full_size() { std::size_t sz = 0; // dummy initialization to please the compiler switch (type_) @@ -210,6 +272,12 @@ std::size_t postgresql_vector_use_type_backend::size() case x_stdtm: sz = get_vector_size(data_); break; + case x_xmltype: + sz = get_vector_size(data_); + break; + case x_longstring: + sz = get_vector_size(data_); + break; default: throw soci_error("Use vector element used with non-supported type."); } diff --git a/src/backends/sqlite3/Makefile.basic b/src/backends/sqlite3/Makefile.basic index 3b2101b8f5..6594dd31e1 100644 --- a/src/backends/sqlite3/Makefile.basic +++ b/src/backends/sqlite3/Makefile.basic @@ -1,28 +1,39 @@ # The following variable is specific to this backend and its correct # values might depend on your environment - feel free to set it accordingly. -SQLITE3INCLUDEDIR = +SQLITE3INCLUDEDIR = -I/usr/include +SQLITE3LIBDIR = -L/usr/lib +SQLITE3LIBS = -lsqlite3 # The rest of the Makefile is indepentent of the target environment. COMPILER = g++ CXXFLAGS = -Wall -pedantic -Wno-long-long -CXXFLAGSSO = ${CXXFLAGS} -fPIC -INCLUDEDIRS = -I../../core ${SQLITE3INCLUDEDIR} +SHARED_CXXFLAGS = ${CXXFLAGS} -fPIC +INCLUDEDIRS = -I../../../include -I../../../include/private ${SQLITE3INCLUDEDIR} + +SHARED_LIBDIRS = ${SQLITE3LIBDIR} +SHARED_LIBS = ${SQLITE3LIBS} ../../core/libsoci_core.a + +UNAME = $(shell uname) +ifeq ($(UNAME),Darwin) + SHARED_LINK_FLAGS = -dynamiclib -flat_namespace -undefined suppress +else + SHARED_LINK_FLAGS = -shared +endif OBJECTS = blob.o error.o factory.o row-id.o session.o standard-into-type.o \ standard-use-type.o statement.o vector-into-type.o vector-use-type.o \ common.o -OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ +SHARED_OBJECTS = blob-s.o factory-s.o row-id-s.o session-s.o \ standard-into-type-s.o standard-use-type-s.o statement-s.o \ vector-into-type-s.o vector-use-type-s.o common-s.o libsoci_sqlite3.a : ${OBJECTS} ar rv $@ $? - ranlib $@ rm *.o @@ -60,42 +71,43 @@ vector-use-type.o : vector-use-type.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} -shared : ${OBJECTSSO} - ${COMPILER} -shared -o libsoci_sqlite3.so ${OBJECTSSO} +shared : ${SHARED_OBJECTS} + ${COMPILER} ${SHARED_LINK_FLAGS} -o libsoci_sqlite3.so \ + ${SHARED_OBJECTS} ${SHARED_LIBDIRS} ${SHARED_LIBS} rm *.o blob-s.o : blob.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} error-s.o : error.cpp ${COMPILER} -c -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} common-s.o : common.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} factory-s.o : factory.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} row-id-s.o : row-id.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} session-s.o : session.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} standard-into-type-s.o : standard-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} standard-use-type-s.o : standard-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} statement-s.o : statement.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} vector-into-type-s.o : vector-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} vector-use-type-s.o : vector-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} + ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} clean : diff --git a/src/backends/sqlite3/blob.cpp b/src/backends/sqlite3/blob.cpp index 6cb6266cd4..0d9ca60d41 100644 --- a/src/backends/sqlite3/blob.cpp +++ b/src/backends/sqlite3/blob.cpp @@ -5,6 +5,7 @@ // http://www.boost.org/LICENSE_1_0.txt) // +#define SOCI_SQLITE3_SOURCE #include "soci/sqlite3/soci-sqlite3.h" #include diff --git a/src/backends/sqlite3/common.cpp b/src/backends/sqlite3/common.cpp deleted file mode 100644 index dc29320960..0000000000 --- a/src/backends/sqlite3/common.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// - -#include "soci/soci-platform.h" -#include "common.h" -#include "soci/soci-backend.h" -#include "soci-mktime.h" -// std -#include -#include - - -namespace // anonymous -{ - -// helper function for parsing decimal data (for std::tm) -long parse10(char const *&p1, char *&p2, char const* const msg) -{ - long v = std::strtol(p1, &p2, 10); - if (p2 != p1) - { - p1 = p2 + 1; - return v; - } - else - { - throw soci::soci_error(msg); - } -} - -} // namespace anonymous - - -void soci::details::sqlite3::parse_std_tm(char const *buf, std::tm &t) -{ - char const *p1 = buf; - char *p2 = 0; - - char const* const errMsg = "Cannot convert data to std::tm."; - - long year = parse10(p1, p2, errMsg); - long month = parse10(p1, p2, errMsg); - long day = parse10(p1, p2, errMsg); - - long hour = 0, minute = 0, second = 0; - if (*p2 != '\0') - { - // there is also the time of day available - hour = parse10(p1, p2, errMsg); - minute = parse10(p1, p2, errMsg); - second = parse10(p1, p2, errMsg); - } - - details::mktime_from_ymdhms(t, year, month, day, hour, minute, second); -} diff --git a/src/backends/sqlite3/common.h b/src/backends/sqlite3/common.h index f8d5275873..a2bf26a89c 100644 --- a/src/backends/sqlite3/common.h +++ b/src/backends/sqlite3/common.h @@ -18,9 +18,6 @@ namespace soci { namespace details { namespace sqlite3 { -// helper function for parsing datetime values -void parse_std_tm(char const *buf, std::tm &t); - // helper for vector operations template std::size_t get_vector_size(void *p) diff --git a/src/backends/sqlite3/row-id.cpp b/src/backends/sqlite3/row-id.cpp index 95eb565bdc..f7d84d6fb9 100644 --- a/src/backends/sqlite3/row-id.cpp +++ b/src/backends/sqlite3/row-id.cpp @@ -5,6 +5,7 @@ // http://www.boost.org/LICENSE_1_0.txt) // +#define SOCI_SQLITE3_SOURCE #include "soci/sqlite3/soci-sqlite3.h" #ifdef _MSC_VER @@ -16,8 +17,8 @@ using namespace soci::details; sqlite3_rowid_backend::sqlite3_rowid_backend( sqlite3_session_backend & /* session */) + : value_(0) { - // ... } sqlite3_rowid_backend::~sqlite3_rowid_backend() diff --git a/src/backends/sqlite3/session.cpp b/src/backends/sqlite3/session.cpp index 37eb62bb85..a84c4b87fd 100644 --- a/src/backends/sqlite3/session.cpp +++ b/src/backends/sqlite3/session.cpp @@ -5,7 +5,7 @@ // http://www.boost.org/LICENSE_1_0.txt) // - +#define SOCI_SQLITE3_SOURCE #include "soci/sqlite3/soci-sqlite3.h" #include "soci/connection-parameters.h" @@ -42,10 +42,10 @@ void check_sqlite_err(sqlite_api::sqlite3* conn, int res, char const* const errM { if (SQLITE_OK != res) { - sqlite3_close(conn); const char *zErrMsg = sqlite3_errmsg(conn); std::ostringstream ss; ss << errMsg << zErrMsg; + sqlite3_close(conn); // connection must be closed here throw sqlite3_soci_error(ss.str(), res); } } diff --git a/src/backends/sqlite3/standard-into-type.cpp b/src/backends/sqlite3/standard-into-type.cpp index 490187f4ee..eb7bf5cb20 100644 --- a/src/backends/sqlite3/standard-into-type.cpp +++ b/src/backends/sqlite3/standard-into-type.cpp @@ -5,12 +5,14 @@ // http://www.boost.org/LICENSE_1_0.txt) // +#define SOCI_SQLITE3_SOURCE #include "soci/soci-platform.h" #include "soci/sqlite3/soci-sqlite3.h" #include "soci/rowid.h" #include "common.h" #include "soci/blob.h" #include "soci-cstrtod.h" +#include "soci-mktime.h" #include "soci-exchange-cast.h" // std #include diff --git a/src/backends/sqlite3/standard-use-type.cpp b/src/backends/sqlite3/standard-use-type.cpp index edad17fc3e..f4c8f5f04b 100644 --- a/src/backends/sqlite3/standard-use-type.cpp +++ b/src/backends/sqlite3/standard-use-type.cpp @@ -5,6 +5,7 @@ // http://www.boost.org/LICENSE_1_0.txt) // +#define SOCI_SQLITE3_SOURCE #include "soci/soci-platform.h" #include "soci/sqlite3/soci-sqlite3.h" #include "soci/rowid.h" diff --git a/src/backends/sqlite3/statement.cpp b/src/backends/sqlite3/statement.cpp index 563b21f5af..9481b53bfb 100644 --- a/src/backends/sqlite3/statement.cpp +++ b/src/backends/sqlite3/statement.cpp @@ -4,6 +4,8 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // + +#define SOCI_SQLITE3_SOURCE #include "soci/sqlite3/soci-sqlite3.h" // std #include @@ -80,11 +82,16 @@ void sqlite3_statement_backend::reset_if_needed() { if (stmt_ && databaseReady_ == false) { - int const res = sqlite3_reset(stmt_); - if (SQLITE_OK == res) - { - databaseReady_ = true; - } + reset(); + } +} + +void sqlite3_statement_backend::reset() +{ + int const res = sqlite3_reset(stmt_); + if (SQLITE_OK == res) + { + databaseReady_ = true; } } @@ -177,6 +184,9 @@ sqlite3_statement_backend::load_rowset(int totalRows) col.buffer_.data_ = (col.buffer_.size_ > 0 ? new char[col.buffer_.size_] : NULL); memcpy(col.buffer_.data_, sqlite3_column_blob(stmt_, c), col.buffer_.size_); break; + + case dt_xml: + throw soci_error("XML data type is not supported"); } } } @@ -200,8 +210,10 @@ sqlite3_statement_backend::load_rowset(int totalRows) statement_backend::exec_fetch_result sqlite3_statement_backend::load_one() { - statement_backend::exec_fetch_result retVal = ef_success; + if( !databaseReady_ ) + return ef_no_data; + statement_backend::exec_fetch_result retVal = ef_success; int const res = sqlite3_step(stmt_); if (SQLITE_DONE == res) @@ -218,10 +230,9 @@ sqlite3_statement_backend::load_one() std::ostringstream ss; ss << "sqlite3_statement_backend::loadOne: " - << zErrMsg; + << zErrMsg; throw sqlite3_soci_error(ss.str(), res); } - return retVal; } @@ -233,6 +244,8 @@ sqlite3_statement_backend::bind_and_execute(int number) long long rowsAffectedBulkTemp = 0; + rowsAffectedBulk_ = -1; + int const rows = static_cast(useData_.size()); for (int row = 0; row < rows; ++row) { @@ -272,6 +285,9 @@ sqlite3_statement_backend::bind_and_execute(int number) case dt_blob: bindRes = sqlite3_bind_blob(stmt_, pos, col.buffer_.constData_, static_cast(col.buffer_.size_), NULL); break; + + case dt_xml: + throw soci_error("XML data type is not supported"); } } @@ -290,7 +306,8 @@ sqlite3_statement_backend::bind_and_execute(int number) return load_rowset(number); } - retVal = load_one(); //execute each bound line + databaseReady_=true; // Mark sqlite engine is ready to perform sqlite3_step + retVal = load_one(); // execute each bound line rowsAffectedBulkTemp += get_affected_rows(); } diff --git a/src/backends/sqlite3/vector-into-type.cpp b/src/backends/sqlite3/vector-into-type.cpp index 5ab62bf74c..349b0a90d4 100644 --- a/src/backends/sqlite3/vector-into-type.cpp +++ b/src/backends/sqlite3/vector-into-type.cpp @@ -9,6 +9,7 @@ #pragma warning(disable : 4512) #endif +#define SOCI_SQLITE3_SOURCE #include "soci-dtocstr.h" #include "soci-exchange-cast.h" #include "soci/blob.h" @@ -16,6 +17,7 @@ #include "soci/soci-platform.h" #include "soci/sqlite3/soci-sqlite3.h" #include "soci-cstrtod.h" +#include "soci-mktime.h" #include "common.h" // std #include @@ -77,6 +79,9 @@ void set_number_in_vector(void *p, int idx, const sqlite3_column &col) case dt_unsigned_long_long: set_in_vector(p, idx, static_cast(col.int64_)); break; + + case dt_xml: + throw soci_error("XML data type is not supported"); }; } @@ -147,6 +152,9 @@ void sqlite3_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) set_in_vector(data_, i, ss.str()[0]); break; } + + case dt_xml: + throw soci_error("XML data type is not supported"); }; break; } // x_char @@ -181,6 +189,9 @@ void sqlite3_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) set_in_vector(data_, i, ss.str()); break; } + + case dt_xml: + throw soci_error("XML data type is not supported"); }; break; } // x_stdstring @@ -214,7 +225,7 @@ void sqlite3_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) case dt_blob: { // attempt to parse the string and convert to std::tm - std::tm t; + std::tm t = std::tm(); parse_std_tm(col.buffer_.constData_, t); set_in_vector(data_, i, t); @@ -226,6 +237,9 @@ void sqlite3_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) case dt_long_long: case dt_unsigned_long_long: throw soci_error("Into element used with non-convertible type."); + + case dt_xml: + throw soci_error("XML data type is not supported"); }; break; } @@ -249,6 +263,9 @@ void sqlite3_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) case dt_long_long: case dt_unsigned_long_long: break; + + case dt_xml: + throw soci_error("XML data type is not supported"); } } } diff --git a/src/backends/sqlite3/vector-use-type.cpp b/src/backends/sqlite3/vector-use-type.cpp index d1cd5edfa3..1ae3ab1007 100644 --- a/src/backends/sqlite3/vector-use-type.cpp +++ b/src/backends/sqlite3/vector-use-type.cpp @@ -5,6 +5,7 @@ // http://www.boost.org/LICENSE_1_0.txt) // +#define SOCI_SQLITE3_SOURCE #include "soci-exchange-cast.h" #include "soci/soci-platform.h" #include "soci/sqlite3/soci-sqlite3.h" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b326aed684..3916cce4db 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -60,6 +60,12 @@ if (SOCI_SHARED) INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib CLEAN_DIRECT_OUTPUT 1) endif() + + target_include_directories(${SOCI_CORE_TARGET} + PUBLIC + $ + ) + endif() # This adds definitions to all build configurations. SOCI_DEBUG_POSTFIX is passed to soci library @@ -75,21 +81,28 @@ if (SOCI_STATIC) add_library(${SOCI_CORE_TARGET_STATIC} STATIC ${SOCI_CORE_HEADERS} ${SOCI_CORE_SOURCES}) - + # we still need to link against dl if we have it target_link_libraries (${SOCI_CORE_TARGET_STATIC} ${SOCI_CORE_DEPS_LIBS} ) - - set_target_properties(${SOCI_CORE_TARGET_STATIC} PROPERTIES OUTPUT_NAME ${SOCI_CORE_TARGET_OUTPUT_NAME} PREFIX "lib" CLEAN_DIRECT_OUTPUT 1) + + target_include_directories(${SOCI_CORE_TARGET_STATIC} + PUBLIC + $ + ) + endif() + + + # # Core installation # @@ -97,6 +110,7 @@ install(FILES ${SOCI_CORE_HEADERS} DESTINATION ${INCLUDEDIR}/${PROJECTNAMEL}) if (SOCI_SHARED) install(TARGETS ${SOCI_CORE_TARGET} + EXPORT SOCI RUNTIME DESTINATION ${BINDIR} LIBRARY DESTINATION ${LIBDIR} ARCHIVE DESTINATION ${LIBDIR}) @@ -104,11 +118,14 @@ endif() if (SOCI_STATIC) install(TARGETS ${SOCI_CORE_TARGET_STATIC} + EXPORT SOCI RUNTIME DESTINATION ${BINDIR} LIBRARY DESTINATION ${LIBDIR} ARCHIVE DESTINATION ${LIBDIR}) endif() +install(EXPORT SOCI NAMESPACE SOCI:: DESTINATION cmake) + # # Core configuration summary # diff --git a/src/core/Makefile.basic b/src/core/Makefile.basic index 8bc2e42f1f..bd5f78d869 100644 --- a/src/core/Makefile.basic +++ b/src/core/Makefile.basic @@ -20,7 +20,12 @@ shared : generated ${OBJS} ${COMPILER} -shared -o libsoci_core.so ${OBJS} rm *.o -generated : ../../include/private/soci_backends_config.h +generated : ../../include/soci/soci-config.h ../../include/private/soci_backends_config.h + +# Note: this file is generated without any configured variables, +# full configuration fill is generated by CMake. +../../include/soci/soci-config.h : ../../include/soci/soci-config.h.in + grep -v CONFIGURED_VARIABLES $? > $@ # Note: this file is generated with a basic search path, # full backends search path is generated by CMake. diff --git a/src/core/blob.cpp b/src/core/blob.cpp index 819ec30e03..bcb66f7bef 100644 --- a/src/core/blob.cpp +++ b/src/core/blob.cpp @@ -33,12 +33,24 @@ std::size_t blob::read(std::size_t offset, char *buf, std::size_t toRead) return backEnd_->read(offset, buf, toRead); } +std::size_t blob::read_from_start(char * buf, std::size_t toRead, + std::size_t offset) +{ + return backEnd_->read_from_start(buf, toRead, offset); +} + std::size_t blob::write( std::size_t offset, char const * buf, std::size_t toWrite) { return backEnd_->write(offset, buf, toWrite); } +std::size_t blob::write_from_start(const char * buf, std::size_t toWrite, + std::size_t offset) +{ + return backEnd_->write_from_start(buf, toWrite, offset); +} + std::size_t blob::append(char const * buf, std::size_t toWrite) { return backEnd_->append(buf, toWrite); diff --git a/src/backends/postgresql/common.cpp b/src/core/common.cpp similarity index 59% rename from src/backends/postgresql/common.cpp rename to src/core/common.cpp index 0445b02c78..ed8cfd52da 100644 --- a/src/backends/postgresql/common.cpp +++ b/src/core/common.cpp @@ -1,53 +1,60 @@ // // Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2017 Vadim Zeitlin. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci/soci-platform.h" -#include "soci/soci-backend.h" +#define SOCI_SOURCE +#include "soci/error.h" #include "soci-mktime.h" +#include #include #include -#include "common.h" namespace // anonymous { // helper function for parsing decimal data (for std::tm) -long parse10(char const * & p1, char * & p2, char const * msg) +int parse10(char const * & p1, char * & p2) { long v = std::strtol(p1, &p2, 10); if (p2 != p1) { + if (v < 0) + throw soci::soci_error("Negative date/time field component."); + + if (v > INT_MAX) + throw soci::soci_error("Out of range date/time field component."); + p1 = p2 + 1; - return v; + + // Cast is safe due to check above. + return static_cast(v); } else { - throw soci::soci_error(msg); + throw soci::soci_error("Cannot parse date/time field component."); + } } } // namespace anonymous - -void soci::details::postgresql::parse_std_tm(char const * buf, std::tm & t) +void soci::details::parse_std_tm(char const * buf, std::tm & t) { char const * p1 = buf; char * p2; char separator; - long a, b, c; - long year = 1900, month = 1, day = 1; - long hour = 0, minute = 0, second = 0; + int a, b, c; + int year = 1900, month = 1, day = 1; + int hour = 0, minute = 0, second = 0; - char const * errMsg = "Cannot convert data to std::tm."; - - a = parse10(p1, p2, errMsg); + a = parse10(p1, p2); separator = *p2; - b = parse10(p1, p2, errMsg); - c = parse10(p1, p2, errMsg); + b = parse10(p1, p2); + c = parse10(p1, p2); if (*p2 == ' ') { @@ -57,9 +64,9 @@ void soci::details::postgresql::parse_std_tm(char const * buf, std::tm & t) year = a; month = b; day = c; - hour = parse10(p1, p2, errMsg); - minute = parse10(p1, p2, errMsg); - second = parse10(p1, p2, errMsg); + hour = parse10(p1, p2); + minute = parse10(p1, p2); + second = parse10(p1, p2); } else { @@ -82,5 +89,5 @@ void soci::details::postgresql::parse_std_tm(char const * buf, std::tm & t) } } - details::mktime_from_ymdhms(t, year, month, day, hour, minute, second); + mktime_from_ymdhms(t, year, month, day, hour, minute, second); } diff --git a/src/core/connection-pool.cpp b/src/core/connection-pool.cpp index d4ffa01ddc..34a128e3a9 100644 --- a/src/core/connection-pool.cpp +++ b/src/core/connection-pool.cpp @@ -83,26 +83,6 @@ connection_pool::~connection_pool() delete pimpl_; } -session & connection_pool::at(std::size_t pos) -{ - if (pos >= pimpl_->sessions_.size()) - { - throw soci_error("Invalid pool position"); - } - - return *(pimpl_->sessions_[pos].second); -} - -std::size_t connection_pool::lease() -{ - std::size_t pos; - - // no timeout, so can't fail - try_lease(pos, -1); - - return pos; -} - bool connection_pool::try_lease(std::size_t & pos, int timeout) { struct timespec tm; @@ -266,26 +246,6 @@ connection_pool::~connection_pool() delete pimpl_; } -session & connection_pool::at(std::size_t pos) -{ - if (pos >= pimpl_->sessions_.size()) - { - throw soci_error("Invalid pool position"); - } - - return *(pimpl_->sessions_[pos].second); -} - -std::size_t connection_pool::lease() -{ - std::size_t pos; - - // no timeout, allow unlimited blocking - try_lease(pos, -1); - - return pos; -} - bool connection_pool::try_lease(std::size_t & pos, int timeout) { DWORD cc = WaitForSingleObject(pimpl_->sem_, @@ -341,3 +301,26 @@ void connection_pool::give_back(std::size_t pos) } #endif // _WIN32 + +session & connection_pool::at(std::size_t pos) +{ + if (pos >= pimpl_->sessions_.size()) + { + throw soci_error("Invalid pool position"); + } + + return *(pimpl_->sessions_[pos].second); +} + +std::size_t connection_pool::lease() +{ + // dummy default value avoids compiler warning, never leaks to client + std::size_t pos(0); + + // no timeout, so can't fail + try_lease(pos, -1); + + return pos; +} + + diff --git a/src/core/into-type.cpp b/src/core/into-type.cpp index f3124238be..7a41960835 100644 --- a/src/core/into-type.cpp +++ b/src/core/into-type.cpp @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -19,10 +19,19 @@ standard_into_type::~standard_into_type() void standard_into_type::define(statement_impl & st, int & position) { - backEnd_ = st.make_into_type_backend(); + if (backEnd_ == NULL) + { + backEnd_ = st.make_into_type_backend(); + } + backEnd_->define_by_pos(position, data_, type_); } +void standard_into_type::pre_exec(int num) +{ + backEnd_->pre_exec(num); +} + void standard_into_type::pre_fetch() { backEnd_->pre_fetch(); @@ -54,8 +63,24 @@ vector_into_type::~vector_into_type() void vector_into_type::define(statement_impl & st, int & position) { - backEnd_ = st.make_vector_into_type_backend(); - backEnd_->define_by_pos(position, data_, type_); + if (backEnd_ == NULL) + { + backEnd_ = st.make_vector_into_type_backend(); + } + + if (end_ != NULL) + { + backEnd_->define_by_pos_bulk(position, data_, type_, begin_, end_); + } + else + { + backEnd_->define_by_pos(position, data_, type_); + } +} + +void vector_into_type::pre_exec(int num) +{ + backEnd_->pre_exec(num); } void vector_into_type::pre_fetch() @@ -82,7 +107,7 @@ void vector_into_type::post_fetch(bool gotData, bool /* calledFromFetch */) void vector_into_type::resize(std::size_t sz) { - if (indVec_ != NULL) + if (indVec_ != NULL && end_ == NULL) { indVec_->resize(sz); } diff --git a/src/core/once-temp-type.cpp b/src/core/once-temp-type.cpp index 2b968e67e1..12f86bef29 100644 --- a/src/core/once-temp-type.cpp +++ b/src/core/once-temp-type.cpp @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -35,7 +35,7 @@ once_temp_type & once_temp_type::operator=(once_temp_type const & o) return *this; } -once_temp_type::~once_temp_type() SOCI_ONCE_TEMP_TYPE_NOEXCEPT +once_temp_type::~once_temp_type() SOCI_NOEXCEPT_FALSE { rcst_->dec_ref(); } @@ -45,3 +45,144 @@ once_temp_type & once_temp_type::operator,(into_type_ptr const & i) rcst_->exchange(i); return *this; } + +once_temp_type & once_temp_type::operator,(use_type_ptr const & u) +{ + rcst_->exchange(u); + return *this; +} + +ddl_type::ddl_type(session & s) + : s_(&s), rcst_(new ref_counted_statement(s)) +{ + // this is the beginning of new query + s.get_query_stream().str(""); +} + +ddl_type::ddl_type(const ddl_type & d) + :rcst_(d.rcst_) +{ + rcst_->inc_ref(); +} + +ddl_type & ddl_type::operator=(const ddl_type & d) +{ + d.rcst_->inc_ref(); + rcst_->dec_ref(); + rcst_ = d.rcst_; + + return *this; +} + +ddl_type::~ddl_type() SOCI_NOEXCEPT_FALSE +{ + rcst_->dec_ref(); +} + +void ddl_type::create_table(const std::string & tableName) +{ + rcst_->accumulate(s_->get_backend()->create_table(tableName)); +} + +void ddl_type::add_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale) +{ + rcst_->accumulate(s_->get_backend()->add_column( + tableName, columnName, dt, precision, scale)); +} + +void ddl_type::alter_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale) +{ + rcst_->accumulate(s_->get_backend()->alter_column( + tableName, columnName, dt, precision, scale)); +} + +void ddl_type::drop_column(const std::string & tableName, + const std::string & columnName) +{ + rcst_->accumulate(s_->get_backend()->drop_column( + tableName, columnName)); +} + +ddl_type & ddl_type::column(const std::string & columnName, data_type dt, + int precision, int scale) +{ + if (rcst_->get_need_comma()) + { + rcst_->accumulate(", "); + } + + rcst_->accumulate(columnName); + rcst_->accumulate(" "); + rcst_->accumulate( + s_->get_backend()->create_column_type(dt, precision, scale)); + + rcst_->set_need_comma(true); + + return *this; +} + +ddl_type & ddl_type::unique(const std::string & name, + const std::string & columnNames) +{ + if (rcst_->get_need_comma()) + { + rcst_->accumulate(", "); + } + + rcst_->accumulate(s_->get_backend()->constraint_unique( + name, columnNames)); + + rcst_->set_need_comma(true); + + return *this; +} + +ddl_type & ddl_type::primary_key(const std::string & name, + const std::string & columnNames) +{ + if (rcst_->get_need_comma()) + { + rcst_->accumulate(", "); + } + + rcst_->accumulate(s_->get_backend()->constraint_primary_key( + name, columnNames)); + + rcst_->set_need_comma(true); + + return *this; +} + +ddl_type & ddl_type::foreign_key(const std::string & name, + const std::string & columnNames, + const std::string & refTableName, + const std::string & refColumnNames) +{ + if (rcst_->get_need_comma()) + { + rcst_->accumulate(", "); + } + + rcst_->accumulate(s_->get_backend()->constraint_foreign_key( + name, columnNames, refTableName, refColumnNames)); + + rcst_->set_need_comma(true); + + return *this; +} + +ddl_type & ddl_type::operator()(const std::string & arbitrarySql) +{ + rcst_->accumulate(" " + arbitrarySql); + + return *this; +} + +void ddl_type::set_tail(const std::string & tail) +{ + rcst_->set_tail(tail); +} diff --git a/src/core/ref-counted-statement.cpp b/src/core/ref-counted-statement.cpp index 38a96b2edb..2fdc9fd718 100644 --- a/src/core/ref-counted-statement.cpp +++ b/src/core/ref-counted-statement.cpp @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -13,8 +13,7 @@ using namespace soci; using namespace soci::details; ref_counted_statement_base::ref_counted_statement_base(session& s) - : refCount_(1) - , session_(s) + : refCount_(1), session_(s), need_comma_(false) { } diff --git a/src/core/session.cpp b/src/core/session.cpp index 920cd7cf13..9b41954efa 100644 --- a/src/core/session.cpp +++ b/src/core/session.cpp @@ -231,13 +231,7 @@ std::string session::get_query() const } -#ifdef SOCI_CXX_C11 -void session::set_query_transformation_( std::unique_ptr &qtf) -#else -void session::set_query_transformation_( std::auto_ptr qtf) -#endif - - +void session::set_query_transformation_(cxx_details::auto_ptr& qtf) { if (isFromPool_) { @@ -365,6 +359,120 @@ bool session::get_last_insert_id(std::string const & sequence, long & value) return backEnd_->get_last_insert_id(*this, sequence, value); } +details::once_temp_type session::get_table_names() +{ + ensureConnected(backEnd_); + + return once << backEnd_->get_table_names_query(); +} + +details::prepare_temp_type session::prepare_table_names() +{ + ensureConnected(backEnd_); + + return prepare << backEnd_->get_table_names_query(); +} + +details::prepare_temp_type session::prepare_column_descriptions(std::string & table_name) +{ + ensureConnected(backEnd_); + + return prepare << backEnd_->get_column_descriptions_query(), use(table_name, "t"); +} + +ddl_type session::create_table(const std::string & tableName) +{ + ddl_type ddl(*this); + + ddl.create_table(tableName); + ddl.set_tail(")"); + + return ddl; +} + +void session::drop_table(const std::string & tableName) +{ + ensureConnected(backEnd_); + + once << backEnd_->drop_table(tableName); +} + +void session::truncate_table(const std::string & tableName) +{ + ensureConnected(backEnd_); + + once << backEnd_->truncate_table(tableName); +} + +ddl_type session::add_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale) +{ + ddl_type ddl(*this); + + ddl.add_column(tableName, columnName, dt, precision, scale); + + return ddl; +} + +ddl_type session::alter_column(const std::string & tableName, + const std::string & columnName, data_type dt, + int precision, int scale) +{ + ddl_type ddl(*this); + + ddl.alter_column(tableName, columnName, dt, precision, scale); + + return ddl; +} + +ddl_type session::drop_column(const std::string & tableName, + const std::string & columnName) +{ + ddl_type ddl(*this); + + ddl.drop_column(tableName, columnName); + + return ddl; +} + +std::string session::empty_blob() +{ + ensureConnected(backEnd_); + + return backEnd_->empty_blob(); +} + +std::string session::nvl() +{ + ensureConnected(backEnd_); + + return backEnd_->nvl(); +} + +std::string session::get_dummy_from_table() const +{ + ensureConnected(backEnd_); + + return backEnd_->get_dummy_from_table(); +} + +std::string session::get_dummy_from_clause() const +{ + std::string clause = get_dummy_from_table(); + if (!clause.empty()) + clause.insert(0, " from "); + + return clause; +} + +void session::set_failover_callback(failover_callback & callback) +{ + ensureConnected(backEnd_); + + backEnd_->set_failover_callback(callback, *this); +} + std::string session::get_backend_name() const { ensureConnected(backEnd_); diff --git a/src/core/soci-simple.cpp b/src/core/soci-simple.cpp index 99a2de1291..7960b0ef6b 100644 --- a/src/core/soci-simple.cpp +++ b/src/core/soci-simple.cpp @@ -582,6 +582,8 @@ bool name_exists_check_failed(statement_wrapper & wrapper, iterator const it = wrapper.use_blob.find(name); name_exists = (it != wrapper.use_blob.end()); } + case dt_xml: + // no support for xml break; } } @@ -642,7 +644,8 @@ bool name_exists_check_failed(statement_wrapper & wrapper, } break; case dt_blob: - // no support for bulk load + case dt_xml: + // no support for bulk and xml load break; } } @@ -1096,6 +1099,7 @@ SOCI_DECL void soci_into_resize_v(statement_handle st, int new_size) wrapper->into_dates_v[i].resize(new_size); break; case dt_blob: + case dt_xml: // no support for bulk blob break; } @@ -1494,7 +1498,7 @@ SOCI_DECL void soci_set_use_date(statement_handle st, char const * name, char co return; } - std::tm dt; + std::tm dt = std::tm(); bool const converted = string_to_date(val, dt, *wrapper); if (converted == false) { @@ -1697,7 +1701,7 @@ SOCI_DECL void soci_set_use_date_v(statement_handle st, return; } - std::tm dt; + std::tm dt = std::tm(); bool const converted = string_to_date(val, dt, *wrapper); if (converted == false) { @@ -1851,6 +1855,9 @@ SOCI_DECL void soci_prepare(statement_handle st, char const * query) wrapper->st.exchange( into(wrapper->into_blob[i]->blob_, wrapper->into_indicators[i])); break; + case dt_xml: + // no support for xml + break; } } } @@ -1883,7 +1890,8 @@ SOCI_DECL void soci_prepare(statement_handle st, char const * query) into(wrapper->into_dates_v[i], wrapper->into_indicators_v[i])); break; case dt_blob: - // no support for bulk blob + case dt_xml: + // no support for bulk blob and xml break; } } diff --git a/src/core/statement.cpp b/src/core/statement.cpp index 596bce1f99..ddf19789d5 100644 --- a/src/core/statement.cpp +++ b/src/core/statement.cpp @@ -313,6 +313,8 @@ bool statement_impl::execute(bool withDataExchange) num = static_cast(bindSize); } } + + pre_exec(num); statement_backend::exec_fetch_result res = backEnd_->execute(num); @@ -530,6 +532,27 @@ void statement_impl::truncate_intos() } } +void statement_impl::pre_exec(int num) +{ + std::size_t const isize = intos_.size(); + for (std::size_t i = 0; i != isize; ++i) + { + intos_[i]->pre_exec(num); + } + + std::size_t const ifrsize = intosForRow_.size(); + for (std::size_t i = 0; i != ifrsize; ++i) + { + intosForRow_[i]->pre_exec(num); + } + + std::size_t const usize = uses_.size(); + for (std::size_t i = 0; i != usize; ++i) + { + uses_[i]->pre_exec(num); + } +} + void statement_impl::pre_fetch() { std::size_t const isize = intos_.size(); diff --git a/src/core/use-type.cpp b/src/core/use-type.cpp index 2841563425..646a9e7db9 100644 --- a/src/core/use-type.cpp +++ b/src/core/use-type.cpp @@ -1,5 +1,5 @@ // -// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2004-2016 Maciej Sobczak, Stephen Hutton // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) @@ -27,6 +27,7 @@ void standard_use_type::bind(statement_impl & st, int & position) { backEnd_ = st.make_use_type_backend(); } + if (name_.empty()) { backEnd_->bind_by_pos(position, data_, type_, readOnly_); @@ -100,6 +101,14 @@ void standard_use_type::dump_value(std::ostream& os) const case x_blob: os << ""; return; + + case x_xmltype: + os << ""; + return; + + case x_longstring: + os << ""; + return; } // This is normally unreachable, but avoid throwing from here as we're @@ -107,6 +116,11 @@ void standard_use_type::dump_value(std::ostream& os) const os << ""; } +void standard_use_type::pre_exec(int num) +{ + backEnd_->pre_exec(num); +} + void standard_use_type::pre_use() { // Handle IN direction of parameters of SQL statements and procedures @@ -148,13 +162,28 @@ void vector_use_type::bind(statement_impl & st, int & position) { backEnd_ = st.make_vector_use_type_backend(); } + if (name_.empty()) { - backEnd_->bind_by_pos(position, data_, type_); + if (end_ != NULL) + { + backEnd_->bind_by_pos_bulk(position, data_, type_, begin_, end_); + } + else + { + backEnd_->bind_by_pos(position, data_, type_); + } } else { - backEnd_->bind_by_name(name_, data_, type_); + if (end_ != NULL) + { + backEnd_->bind_by_name_bulk(name_, data_, type_, begin_, end_); + } + else + { + backEnd_->bind_by_name(name_, data_, type_); + } } } @@ -164,6 +193,11 @@ void vector_use_type::dump_value(std::ostream& os) const os << ""; } +void vector_use_type::pre_exec(int num) +{ + backEnd_->pre_exec(num); +} + void vector_use_type::pre_use() { convert_to_base(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 28ee64af58..dd1b17745f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,6 +11,11 @@ colormsg(_HIBLUE_ "Configuring SOCI tests:") +# Request CATCH to disable all the C++11 features +if (NOT SOCI_CXX_C11) + add_definitions(-DCATCH_CONFIG_NO_CPP11) +endif() + include_directories( ${SOCI_SOURCE_DIR}/include/private ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/tests/catch.hpp b/tests/catch.hpp index 6b8dfb5ebd..f7681f49ea 100644 --- a/tests/catch.hpp +++ b/tests/catch.hpp @@ -1,6 +1,6 @@ /* - * CATCH v1.0 build 53 (master branch) - * Generated: 2014-08-20 08:08:19.533804 + * Catch v1.9.6 + * Generated: 2017-06-27 12:19:54.557875 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -13,31 +13,43 @@ #define TWOBLUECUBES_CATCH_HPP_INCLUDED +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + // #included from: internal/catch_suppress_warnings.h -#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED - #ifdef __clang__ -#pragma clang diagnostic ignored "-Wglobal-constructors" -#pragma clang diagnostic ignored "-Wvariadic-macros" -#pragma clang diagnostic ignored "-Wc99-extensions" -#pragma clang diagnostic ignored "-Wunused-variable" -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#pragma clang diagnostic ignored "-Wc++98-compat" -#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif #elif defined __GNUC__ -#pragma GCC diagnostic ignored "-Wvariadic-macros" -#pragma GCC diagnostic ignored "-Wunused-variable" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpadded" +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wparentheses" + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL #endif -#ifdef CATCH_CONFIG_MAIN -# define CATCH_CONFIG_RUNNER -#endif - -#ifdef CATCH_CONFIG_RUNNER +#ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN @@ -50,84 +62,127 @@ // #included from: catch_common.h #define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) - -#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr -#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) - -#include -#include -#include - // #included from: catch_compiler_capabilities.h #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED -// Much of the following code is based on Boost (1.53) +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? +// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported +// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? +// CATCH_CONFIG_CPP11_OVERRIDE : is override supported? +// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) +// CATCH_CONFIG_CPP11_SHUFFLE : is std::shuffle supported? +// CATCH_CONFIG_CPP11_TYPE_TRAITS : are type_traits and enable_if supported? + +// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 + +#ifdef __cplusplus + +# if __cplusplus >= 201103L +# define CATCH_CPP11_OR_GREATER +# endif + +# if __cplusplus >= 201402L +# define CATCH_CPP14_OR_GREATER +# endif + +#endif #ifdef __clang__ # if __has_feature(cxx_nullptr) -# define CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # if __has_feature(cxx_noexcept) -# define CATCH_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif +# if defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) +# endif + #endif // __clang__ +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) + +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# endif + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE + +#endif // __CYGWIN__ + //////////////////////////////////////////////////////////////////////////////// // Borland #ifdef __BORLANDC__ -#if (__BORLANDC__ > 0x582 ) -//#define CATCH_CONFIG_SFINAE // Not confirmed -#endif - #endif // __BORLANDC__ //////////////////////////////////////////////////////////////////////////////// // EDG #ifdef __EDG_VERSION__ -#if (__EDG_VERSION__ > 238 ) -//#define CATCH_CONFIG_SFINAE // Not confirmed -#endif - #endif // __EDG_VERSION__ //////////////////////////////////////////////////////////////////////////////// // Digital Mars #ifdef __DMC__ -#if (__DMC__ > 0x840 ) -//#define CATCH_CONFIG_SFINAE // Not confirmed -#endif - #endif // __DMC__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ -#if __GNUC__ < 3 +# if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif -#if (__GNUC_MINOR__ >= 96 ) -//#define CATCH_CONFIG_SFINAE -#endif - -#elif __GNUC__ >= 3 - -// #define CATCH_CONFIG_SFINAE // Taking this out completely for now - -#endif // __GNUC__ < 3 - -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) - -#define CATCH_CONFIG_CPP11_NULLPTR -#endif +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below #endif // __GNUC__ @@ -135,33 +190,147 @@ // Visual C++ #ifdef _MSC_VER -#if (_MSC_VER >= 1310 ) // (VC++ 7.0+) -//#define CATCH_CONFIG_SFINAE // Not confirmed +#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH + +#if (_MSC_VER >= 1600) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE +#define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS #endif #endif // _MSC_VER +//////////////////////////////////////////////////////////////////////////////// + // Use variadic macros if the compiler supports them #if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ ( defined __GNUC__ && __GNUC__ >= 3 ) || \ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) -#ifndef CATCH_CONFIG_NO_VARIADIC_MACROS -#define CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS + #endif +// Use __COUNTER__ if the compiler supports it +#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ + ( defined __GNUC__ && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3 )) ) || \ + ( defined __clang__ && __clang_major__ >= 3 ) + +#define CATCH_INTERNAL_CONFIG_COUNTER + #endif //////////////////////////////////////////////////////////////////////////////// // C++ language feature support -// detect language version: -#if (__cplusplus == 201103L) -# define CATCH_CPP11 -# define CATCH_CPP11_OR_GREATER -#elif (__cplusplus >= 201103L) -# define CATCH_CPP11_OR_GREATER +// catch all support for C++11 +#if defined(CATCH_CPP11_OR_GREATER) + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# endif + +# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) +# define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) +# define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) +# define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) +# define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS +# endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NULLPTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_IS_ENUM +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TUPLE +#endif +#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) +# define CATCH_CONFIG_VARIADIC_MACROS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_LONG_LONG +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_UNIQUE_PTR +#endif +// Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for +// analytics) because, at time of writing, __COUNTER__ is not properly handled by it. +// This does not affect compilation +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_SHUFFLE +#endif +# if defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_NO_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TYPE_TRAITS +# endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS #endif // noexcept support: @@ -173,11 +342,61 @@ # define CATCH_NOEXCEPT_IS(x) #endif +// nullptr support +#ifdef CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_NULL nullptr +#else +# define CATCH_NULL NULL +#endif + +// override support +#ifdef CATCH_CONFIG_CPP11_OVERRIDE +# define CATCH_OVERRIDE override +#else +# define CATCH_OVERRIDE +#endif + +// unique_ptr support +#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR +# define CATCH_AUTO_PTR( T ) std::unique_ptr +#else +# define CATCH_AUTO_PTR( T ) std::auto_ptr +#endif + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include + namespace Catch { + struct IConfig; + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + class NonCopyable { - NonCopyable( NonCopyable const& ); - void operator = ( NonCopyable const& ); +#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + protected: NonCopyable() {} virtual ~NonCopyable(); @@ -210,11 +429,14 @@ namespace Catch { } bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); bool contains( std::string const& s, std::string const& infix ); void toLowerInPlace( std::string& s ); std::string toLower( std::string const& s ); std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); struct pluralise { pluralise( std::size_t count, std::string const& label ); @@ -229,16 +451,17 @@ namespace Catch { SourceLineInfo(); SourceLineInfo( char const* _file, std::size_t _line ); - SourceLineInfo( SourceLineInfo const& other ); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SourceLineInfo(SourceLineInfo const& other) = default; SourceLineInfo( SourceLineInfo && ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo& operator = ( SourceLineInfo && ) = default; # endif bool empty() const; bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; - std::string file; + char const* file; std::size_t line; }; @@ -251,6 +474,9 @@ namespace Catch { void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as @@ -269,8 +495,6 @@ namespace Catch { #define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) #define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); -#include - namespace Catch { class NotImplementedException : public std::exception @@ -336,7 +560,7 @@ namespace Catch { template class Ptr { public: - Ptr() : m_p( NULL ){} + Ptr() : m_p( CATCH_NULL ){} Ptr( T* p ) : m_p( p ){ if( m_p ) m_p->addRef(); @@ -352,7 +576,7 @@ namespace Catch { void reset() { if( m_p ) m_p->release(); - m_p = NULL; + m_p = CATCH_NULL; } Ptr& operator = ( T* p ){ Ptr temp( p ); @@ -365,12 +589,11 @@ namespace Catch { return *this; } void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } - T* get() { return m_p; } - const T* get() const{ return m_p; } + T* get() const{ return m_p; } T& operator*() const { return *m_p; } T* operator->() const { return m_p; } - bool operator !() const { return m_p == NULL; } - operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + bool operator !() const { return m_p == CATCH_NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } private: T* m_p; @@ -404,10 +627,6 @@ namespace Catch { #pragma clang diagnostic pop #endif -#include -#include -#include - namespace Catch { class TestCase; @@ -467,9 +686,13 @@ namespace Catch { struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; - virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases ) const = 0; - + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + } namespace Catch { @@ -502,27 +725,32 @@ struct NameAndDesc { const char* description; }; +void registerTestCase + ( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + struct AutoReg { - AutoReg( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ); + AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); template - AutoReg( void (C::*method)(), - char const* className, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ) { - registerTestCase( new MethodTestCase( method ), - className, - nameAndDesc, - lineInfo ); - } + AutoReg + ( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { - void registerTestCase( ITestCase* testCase, - char const* className, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ); + registerTestCase + ( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } ~AutoReg(); @@ -531,49 +759,86 @@ private: void operator= ( AutoReg const& ); }; +void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ namespace{ \ - struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + struct TestName : ClassName{ \ void test(); \ }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ } \ - void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS #else /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + static void TestName() #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ namespace{ \ - struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + struct TestCaseName : ClassName{ \ void test(); \ }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ } \ - void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + void TestCaseName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS #endif @@ -603,7 +868,9 @@ namespace Catch { Exception = 0x100 | FailureBit, ThrewException = Exception | 1, - DidntThrowException = Exception | 2 + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit }; }; @@ -616,11 +883,11 @@ namespace Catch { // ResultDisposition::Flags enum struct ResultDisposition { enum Flags { - Normal = 0x00, + Normal = 0x01, - ContinueOnFailure = 0x01, // Failures fail test, but execution continues - FalseTest = 0x02, // Prefix expression with ! - SuppressFail = 0x04 // Failures are reported but do not fail the test + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test }; }; inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { @@ -640,27 +907,83 @@ namespace Catch { namespace Catch { + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct DecomposedExpression + { + virtual ~DecomposedExpression() {} + virtual bool isBinaryExpression() const { + return false; + } + virtual void reconstructExpression( std::string& dest ) const = 0; + + // Only simple binary comparisons can be decomposed. + // If more complex check is required then wrap sub-expressions in parentheses. + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator % ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& ); + + private: + DecomposedExpression& operator = (DecomposedExpression const&); + }; + struct AssertionInfo { AssertionInfo() {} - AssertionInfo( std::string const& _macroName, + AssertionInfo( char const * _macroName, SourceLineInfo const& _lineInfo, - std::string const& _capturedExpression, - ResultDisposition::Flags _resultDisposition ); + char const * _capturedExpression, + ResultDisposition::Flags _resultDisposition, + char const * _secondArg = ""); - std::string macroName; + char const * macroName; SourceLineInfo lineInfo; - std::string capturedExpression; + char const * capturedExpression; ResultDisposition::Flags resultDisposition; + char const * secondArg; }; struct AssertionResultData { - AssertionResultData() : resultType( ResultWas::Unknown ) {} + AssertionResultData() : decomposedExpression( CATCH_NULL ) + , resultType( ResultWas::Unknown ) + , negated( false ) + , parenthesized( false ) {} - std::string reconstructedExpression; + void negate( bool parenthesize ) { + negated = !negated; + parenthesized = parenthesize; + if( resultType == ResultWas::Ok ) + resultType = ResultWas::ExpressionFailed; + else if( resultType == ResultWas::ExpressionFailed ) + resultType = ResultWas::Ok; + } + + std::string const& reconstructExpression() const { + if( decomposedExpression != CATCH_NULL ) { + decomposedExpression->reconstructExpression( reconstructedExpression ); + if( parenthesized ) { + reconstructedExpression.insert( 0, 1, '(' ); + reconstructedExpression.append( 1, ')' ); + } + if( negated ) { + reconstructedExpression.insert( 0, 1, '!' ); + } + decomposedExpression = CATCH_NULL; + } + return reconstructedExpression; + } + + mutable DecomposedExpression const* decomposedExpression; + mutable std::string reconstructedExpression; std::string message; ResultWas::OfType resultType; + bool negated; + bool parenthesized; }; class AssertionResult { @@ -668,7 +991,7 @@ namespace Catch { AssertionResult(); AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); ~AssertionResult(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionResult( AssertionResult const& ) = default; AssertionResult( AssertionResult && ) = default; AssertionResult& operator = ( AssertionResult const& ) = default; @@ -687,6 +1010,8 @@ namespace Catch { std::string getMessage() const; SourceLineInfo getSourceInfo() const; std::string getTestMacroName() const; + void discardDecomposedExpression() const; + void expandDecomposedExpression() const; protected: AssertionInfo m_info; @@ -695,77 +1020,248 @@ namespace Catch { } // end namespace Catch +// #included from: catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template struct MatchAllOf; + template struct MatchAnyOf; + template struct MatchNotOf; + + class MatcherUntypedBase { + public: + std::string toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + private: + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ); + }; + + template + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + template + struct MatcherMethod { + virtual bool match( PtrT* arg ) const = 0; + }; + + template + struct MatcherBase : MatcherUntypedBase, MatcherMethod { + + MatchAllOf operator && ( MatcherBase const& other ) const; + MatchAnyOf operator || ( MatcherBase const& other ) const; + MatchNotOf operator ! () const; + }; + + template + struct MatchAllOf : MatcherBase { + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (!m_matchers[i]->match(arg)) + return false; + } + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + description += " and "; + description += m_matchers[i]->toString(); + } + description += " )"; + return description; + } + + MatchAllOf& operator && ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + template + struct MatchAnyOf : MatcherBase { + + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (m_matchers[i]->match(arg)) + return true; + } + return false; + } + virtual std::string describe() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + description += " or "; + description += m_matchers[i]->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf& operator || ( MatcherBase const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector const*> m_matchers; + }; + + template + struct MatchNotOf : MatcherBase { + + MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + return !m_underlyingMatcher.match( arg ); + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase const& m_underlyingMatcher; + }; + + template + MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { + return MatchAllOf() && *this && other; + } + template + MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { + return MatchAnyOf() || *this || other; + } + template + MatchNotOf MatcherBase::operator ! () const { + return MatchNotOf( *this ); + } + + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + // - deprecated: prefer ||, && and ! + template + inline Impl::MatchNotOf Not( Impl::MatcherBase const& underlyingMatcher ) { + return Impl::MatchNotOf( underlyingMatcher ); + } + template + inline Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { + return Impl::MatchAllOf() && m1 && m2; + } + template + inline Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { + return Impl::MatchAllOf() && m1 && m2 && m3; + } + template + inline Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { + return Impl::MatchAnyOf() || m1 || m2; + } + template + inline Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { + return Impl::MatchAnyOf() || m1 || m2 || m3; + } + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + namespace Catch { struct TestFailureException{}; template class ExpressionLhs; - struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; - struct CopyableStream { CopyableStream() {} CopyableStream( CopyableStream const& other ) { oss << other.oss.str(); } CopyableStream& operator=( CopyableStream const& other ) { - oss.str(""); + oss.str(std::string()); oss << other.oss.str(); return *this; } std::ostringstream oss; }; - class ResultBuilder { + class ResultBuilder : public DecomposedExpression { public: ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, - ResultDisposition::Flags resultDisposition ); + ResultDisposition::Flags resultDisposition, + char const* secondArg = "" ); + ~ResultBuilder(); template - ExpressionLhs operator->* ( T const& operand ); - ExpressionLhs operator->* ( bool value ); + ExpressionLhs operator <= ( T const& operand ); + ExpressionLhs operator <= ( bool value ); template ResultBuilder& operator << ( T const& value ) { - m_stream.oss << value; + m_stream().oss << value; return *this; } - template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); - template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); - ResultBuilder& setResultType( ResultWas::OfType result ); ResultBuilder& setResultType( bool result ); - ResultBuilder& setLhs( std::string const& lhs ); - ResultBuilder& setRhs( std::string const& rhs ); - ResultBuilder& setOp( std::string const& op ); - void endExpression(); + void endExpression( DecomposedExpression const& expr ); + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE; - std::string reconstructExpression() const; AssertionResult build() const; + AssertionResult build( DecomposedExpression const& expr ) const; void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); void captureResult( ResultWas::OfType resultType ); void captureExpression(); + void captureExpectedException( std::string const& expectedMessage ); + void captureExpectedException( Matchers::Impl::MatcherBase const& matcher ); + void handleResult( AssertionResult const& result ); void react(); bool shouldDebugBreak() const; bool allowThrows() const; + template + void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString ); + + void setExceptionGuard(); + void unsetExceptionGuard(); + private: AssertionInfo m_assertionInfo; AssertionResultData m_data; - struct ExprComponents { - ExprComponents() : testFalse( false ) {} - bool testFalse; - std::string lhs, rhs, op; - } m_exprComponents; - CopyableStream m_stream; + + static CopyableStream &m_stream() + { + static CopyableStream s; + return s; + } bool m_shouldDebugBreak; bool m_shouldThrow; + bool m_guardException; }; } // namespace Catch @@ -780,6 +1276,7 @@ namespace Catch { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) #endif #include @@ -820,37 +1317,37 @@ namespace Internal { template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs) { - return opCast( lhs ) == opCast( rhs ); + return bool( opCast( lhs ) == opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) != opCast( rhs ); + return bool( opCast( lhs ) != opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) < opCast( rhs ); + return bool( opCast( lhs ) < opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) > opCast( rhs ); + return bool( opCast( lhs ) > opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) >= opCast( rhs ); + return bool( opCast( lhs ) >= opCast( rhs ) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) <= opCast( rhs ); + return bool( opCast( lhs ) <= opCast( rhs ) ); } }; @@ -928,13 +1425,51 @@ namespace Internal { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + // long long to unsigned X + template bool compare( long long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // unsigned long long to X + template bool compare( unsigned long long lhs, int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long long (when comparing against NULL) + template bool compare( long long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } +#endif // CATCH_CONFIG_CPP11_LONG_LONG + #ifdef CATCH_CONFIG_CPP11_NULLPTR // pointer to nullptr_t (when comparing against nullptr) template bool compare( std::nullptr_t, T* rhs ) { - return Evaluator::evaluate( NULL, rhs ); + return Evaluator::evaluate( nullptr, rhs ); } template bool compare( T* lhs, std::nullptr_t ) { - return Evaluator::evaluate( lhs, NULL ); + return Evaluator::evaluate( lhs, nullptr ); } #endif // CATCH_CONFIG_CPP11_NULLPTR @@ -948,40 +1483,6 @@ namespace Internal { // #included from: catch_tostring.h #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED -// #included from: catch_sfinae.hpp -#define TWOBLUECUBES_CATCH_SFINAE_HPP_INCLUDED - -// Try to detect if the current compiler supports SFINAE - -namespace Catch { - - struct TrueType { - static const bool value = true; - typedef void Enable; - char sizer[1]; - }; - struct FalseType { - static const bool value = false; - typedef void Disable; - char sizer[2]; - }; - -#ifdef CATCH_CONFIG_SFINAE - - template struct NotABooleanExpression; - - template struct If : NotABooleanExpression {}; - template<> struct If : TrueType {}; - template<> struct If : FalseType {}; - - template struct SizedIf; - template<> struct SizedIf : TrueType {}; - template<> struct SizedIf : FalseType {}; - -#endif // CATCH_CONFIG_SFINAE - -} // end namespace Catch - #include #include #include @@ -1034,35 +1535,65 @@ inline id performOptionalSelector( id obj, SEL sel ) { #endif +#ifdef CATCH_CONFIG_CPP11_TUPLE +#include +#endif + +#ifdef CATCH_CONFIG_CPP11_IS_ENUM +#include +#endif + namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ); +std::string toString( unsigned long long value ); +#endif + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG & nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + namespace Detail { -// SFINAE is currently disabled by default for all compilers. -// If the non SFINAE version of IsStreamInsertable is ambiguous for you -// and your compiler supports SFINAE, try #defining CATCH_CONFIG_SFINAE -#ifdef CATCH_CONFIG_SFINAE - - template - class IsStreamInsertableHelper { - template struct TrueIfSizeable : TrueType {}; - - template - static TrueIfSizeable dummy(T2*); - static FalseType dummy(...); - - public: - typedef SizedIf type; - }; - - template - struct IsStreamInsertable : IsStreamInsertableHelper::type {}; - -#else + extern const std::string unprintableString; + #if !defined(CATCH_CONFIG_CPP11_STREAM_INSERTABLE_CHECK) struct BorgType { template BorgType( T const& ); }; + struct TrueType { char sizer[1]; }; + struct FalseType { char sizer[2]; }; + TrueType& testStreamable( std::ostream& ); FalseType testStreamable( FalseType ); @@ -1074,13 +1605,53 @@ namespace Detail { static T const&t; enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; }; +#else + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype( std::declval() << std::declval(), std::true_type() ); + template + static auto test(...) -> std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; #endif +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif template struct StringMakerBase { +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) template - static std::string convert( T const& ) { return "{?}"; } + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif }; template<> @@ -1102,9 +1673,6 @@ namespace Detail { } // end namespace Detail -template -std::string toString( T const& value ); - template struct StringMaker : Detail::StringMakerBase::value> {}; @@ -1114,7 +1682,7 @@ struct StringMaker { template static std::string convert( U* p ) { if( !p ) - return INTERNAL_CATCH_STRINGIFY( NULL ); + return "NULL"; else return Detail::rawMemoryToString( p ); } @@ -1124,7 +1692,7 @@ template struct StringMaker { static std::string convert( R C::* p ) { if( !p ) - return INTERNAL_CATCH_STRINGIFY( NULL ); + return "NULL"; else return Detail::rawMemoryToString( p ); } @@ -1135,12 +1703,59 @@ namespace Detail { std::string rangeToString( InputIterator first, InputIterator last ); } +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + template -struct StringMaker > { - static std::string convert( std::vector const& v ) { - return Detail::rangeToString( v.begin(), v.end() ); +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CONFIG_CPP11_TUPLE + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); } }; +#endif // CATCH_CONFIG_CPP11_TUPLE namespace Detail { template @@ -1161,44 +1776,15 @@ std::string toString( T const& value ) { return StringMaker::convert( value ); } -// Built in overloads - -std::string toString( std::string const& value ); -std::string toString( std::wstring const& value ); -std::string toString( const char* const value ); -std::string toString( char* const value ); -std::string toString( const wchar_t* const value ); -std::string toString( wchar_t* const value ); -std::string toString( int value ); -std::string toString( unsigned long value ); -std::string toString( unsigned int value ); -std::string toString( const double value ); -std::string toString( const float value ); -std::string toString( bool value ); -std::string toString( char value ); -std::string toString( signed char value ); -std::string toString( unsigned char value ); - -#ifdef CATCH_CONFIG_CPP11_NULLPTR -std::string toString( std::nullptr_t ); -#endif - -#ifdef __OBJC__ - std::string toString( NSString const * const& nsstring ); - std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); - std::string toString( NSObject* const& nsObject ); -#endif - namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ) { std::ostringstream oss; oss << "{ "; if( first != last ) { - oss << toString( *first ); - for( ++first ; first != last ; ++first ) { - oss << ", " << toString( *first ); - } + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); } oss << " }"; return oss.str(); @@ -1209,90 +1795,159 @@ std::string toString( std::nullptr_t ); namespace Catch { -// Wraps the LHS of an expression and captures the operator and RHS (if any) - -// wrapping them all in a ResultBuilder object -template -class ExpressionLhs { - ExpressionLhs& operator = ( ExpressionLhs const& ); -# ifdef CATCH_CPP11_OR_GREATER - ExpressionLhs& operator = ( ExpressionLhs && ) = delete; -# endif +template +class BinaryExpression; +template +class MatchExpression; + +// Wraps the LHS of an expression and overloads comparison operators +// for also capturing those and RHS (if any) +template +class ExpressionLhs : public DecomposedExpression { public: - ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} -# ifdef CATCH_CPP11_OR_GREATER - ExpressionLhs( ExpressionLhs const& ) = default; - ExpressionLhs( ExpressionLhs && ) = default; -# endif + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ), m_truthy(false) {} + + ExpressionLhs& operator = ( const ExpressionLhs& ); template - ResultBuilder& operator == ( RhsT const& rhs ) { + BinaryExpression + operator == ( RhsT const& rhs ) { return captureExpression( rhs ); } template - ResultBuilder& operator != ( RhsT const& rhs ) { + BinaryExpression + operator != ( RhsT const& rhs ) { return captureExpression( rhs ); } template - ResultBuilder& operator < ( RhsT const& rhs ) { + BinaryExpression + operator < ( RhsT const& rhs ) { return captureExpression( rhs ); } template - ResultBuilder& operator > ( RhsT const& rhs ) { + BinaryExpression + operator > ( RhsT const& rhs ) { return captureExpression( rhs ); } template - ResultBuilder& operator <= ( RhsT const& rhs ) { + BinaryExpression + operator <= ( RhsT const& rhs ) { return captureExpression( rhs ); } template - ResultBuilder& operator >= ( RhsT const& rhs ) { + BinaryExpression + operator >= ( RhsT const& rhs ) { return captureExpression( rhs ); } - ResultBuilder& operator == ( bool rhs ) { + BinaryExpression operator == ( bool rhs ) { return captureExpression( rhs ); } - ResultBuilder& operator != ( bool rhs ) { + BinaryExpression operator != ( bool rhs ) { return captureExpression( rhs ); } void endExpression() { - bool value = m_lhs ? true : false; + m_truthy = m_lhs ? true : false; m_rb - .setLhs( Catch::toString( value ) ) - .setResultType( value ) - .endExpression(); + .setResultType( m_truthy ) + .endExpression( *this ); } - // Only simple binary expressions are allowed on the LHS. - // If more complex compositions are required then place the sub expression in parentheses - template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); - template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); - template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); - template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); - template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); - template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + dest = Catch::toString( m_lhs ); + } private: template - ResultBuilder& captureExpression( RhsT const& rhs ) { - return m_rb - .setResultType( Internal::compare( m_lhs, rhs ) ) - .setLhs( Catch::toString( m_lhs ) ) - .setRhs( Catch::toString( rhs ) ) - .setOp( Internal::OperatorTraits::getName() ); + BinaryExpression captureExpression( RhsT& rhs ) const { + return BinaryExpression( m_rb, m_lhs, rhs ); + } + + template + BinaryExpression captureExpression( bool rhs ) const { + return BinaryExpression( m_rb, m_lhs, rhs ); } private: ResultBuilder& m_rb; T m_lhs; + bool m_truthy; +}; + +template +class BinaryExpression : public DecomposedExpression { +public: + BinaryExpression( ResultBuilder& rb, LhsT lhs, RhsT rhs ) + : m_rb( rb ), m_lhs( lhs ), m_rhs( rhs ) {} + + BinaryExpression& operator = ( BinaryExpression& ); + + void endExpression() const { + m_rb + .setResultType( Internal::compare( m_lhs, m_rhs ) ) + .endExpression( *this ); + } + + virtual bool isBinaryExpression() const CATCH_OVERRIDE { + return true; + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + std::string lhs = Catch::toString( m_lhs ); + std::string rhs = Catch::toString( m_rhs ); + char delim = lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ? ' ' : '\n'; + dest.reserve( 7 + lhs.size() + rhs.size() ); + // 2 for spaces around operator + // 2 for operator + // 2 for parentheses (conditionally added later) + // 1 for negation (conditionally added later) + dest = lhs; + dest += delim; + dest += Internal::OperatorTraits::getName(); + dest += delim; + dest += rhs; + } + +private: + ResultBuilder& m_rb; + LhsT m_lhs; + RhsT m_rhs; +}; + +template +class MatchExpression : public DecomposedExpression { +public: + MatchExpression( ArgT arg, MatcherT matcher, char const* matcherString ) + : m_arg( arg ), m_matcher( matcher ), m_matcherString( matcherString ) {} + + virtual bool isBinaryExpression() const CATCH_OVERRIDE { + return true; + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + std::string matcherAsString = m_matcher.toString(); + dest = Catch::toString( m_arg ); + dest += ' '; + if( matcherAsString == Detail::unprintableString ) + dest += m_matcherString; + else + dest += matcherAsString; + } + +private: + ArgT m_arg; + MatcherT m_matcher; + char const* m_matcherString; }; } // end namespace Catch @@ -1301,14 +1956,22 @@ private: namespace Catch { template - inline ExpressionLhs ResultBuilder::operator->* ( T const& operand ) { + inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { return ExpressionLhs( *this, operand ); } - inline ExpressionLhs ResultBuilder::operator->* ( bool value ) { + inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { return ExpressionLhs( *this, value ); } + template + inline void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher, + char const* matcherString ) { + MatchExpression expr( arg, matcher, matcherString ); + setResultType( matcher.match( arg ) ); + endExpression( expr ); + } + } // namespace Catch // #included from: catch_message.h @@ -1378,6 +2041,7 @@ namespace Catch { class AssertionResult; struct AssertionInfo; struct SectionInfo; + struct SectionEndInfo; struct MessageInfo; class ScopedMessageBuilder; struct Counts; @@ -1389,12 +2053,17 @@ namespace Catch { virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; - virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; virtual std::string getCurrentTestName() const = 0; virtual const AssertionResult* getLastResult() const = 0; + + virtual void exceptionEarlyReported() = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; }; IResultCapture& getResultCapture(); @@ -1407,11 +2076,19 @@ namespace Catch { #define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) -#define CATCH_PLATFORM_MAC +# define CATCH_PLATFORM_MAC #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -#define CATCH_PLATFORM_IPHONE +# define CATCH_PLATFORM_IPHONE +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) -#define CATCH_PLATFORM_WINDOWS +# define CATCH_PLATFORM_WINDOWS +# if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINES_NOMINMAX +# endif +# if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# endif #endif #include @@ -1426,27 +2103,36 @@ namespace Catch{ // The following code snippet based on: // http://cocoawithlove.com/2008/03/break-into-debugger.html - #ifdef DEBUG - #if defined(__ppc64__) || defined(__ppc__) - #define CATCH_BREAK_INTO_DEBUGGER() \ - if( Catch::isDebuggerActive() ) { \ - __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ - : : : "memory","r0","r3","r4" ); \ - } - #else - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} - #endif + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_TRAP() \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ) + #else + #define CATCH_TRAP() __asm__("int $3\n" : : ) #endif +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") + #else // Fall back to the generic way. + #include + + #define CATCH_TRAP() raise(SIGTRAP) + #endif #elif defined(_MSC_VER) - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } + #define CATCH_TRAP() __debugbreak() #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } + #define CATCH_TRAP() DebugBreak() #endif -#ifndef CATCH_BREAK_INTO_DEBUGGER -#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); #endif // #included from: catch_interfaces_runner.h @@ -1461,6 +2147,41 @@ namespace Catch { }; } +#if defined(CATCH_CONFIG_FAST_COMPILE) +/////////////////////////////////////////////////////////////////////////////// +// We can speedup compilation significantly by breaking into debugger lower in +// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER +// macro in each assertion +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +// This can potentially cause false negative, if the test code catches +// the exception before it propagates back up to the runner. +#define INTERNAL_CATCH_TEST_NO_TRY( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + __catchResult.setExceptionGuard(); \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + ( __catchResult <= expr ).endExpression(); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + __catchResult.unsetExceptionGuard(); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && static_cast( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look +// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +#define INTERNAL_CHECK_THAT_NO_TRY( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ + __catchResult.setExceptionGuard(); \ + __catchResult.captureMatch( arg, matcher, #matcher ); \ + __catchResult.unsetExceptionGuard(); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +#else /////////////////////////////////////////////////////////////////////////////// // In the event of a failure works out if the debugger needs to be invoked // and/or an exception thrown and takes appropriate action. @@ -1469,36 +2190,40 @@ namespace Catch { #define INTERNAL_CATCH_REACT( resultBuilder ) \ if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ resultBuilder.react(); +#endif /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ - ( __catchResult->*expr ).endExpression(); \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + ( __catchResult <= expr ).endExpression(); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ } \ catch( ... ) { \ - __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ + __catchResult.useActiveException( resultDisposition ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + } while( Catch::isTrue( false && static_cast( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ - INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ +#define INTERNAL_CATCH_IF( macroName, resultDisposition, expr ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ if( Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ - INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, expr ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ if( !Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, expr ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ - expr; \ + static_cast(expr); \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ @@ -1508,16 +2233,16 @@ namespace Catch { } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, matcher, expr ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \ if( __catchResult.allowThrows() ) \ try { \ - expr; \ + static_cast(expr); \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( ... ) { \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ + __catchResult.captureExpectedException( matcher ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ @@ -1525,12 +2250,12 @@ namespace Catch { } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr ", " #exceptionType, resultDisposition ); \ if( __catchResult.allowThrows() ) \ try { \ - expr; \ + static_cast(expr); \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( exceptionType ) { \ @@ -1546,7 +2271,7 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #ifdef CATCH_CONFIG_VARIADIC_MACROS - #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ + #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ @@ -1554,7 +2279,7 @@ namespace Catch { INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #else - #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ + #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, log ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << log + ::Catch::StreamEndStop(); \ @@ -1564,21 +2289,15 @@ namespace Catch { #endif /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_INFO( log, macroName ) \ +#define INTERNAL_CATCH_INFO( macroName, log ) \ Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ try { \ - std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ - __catchResult \ - .setLhs( Catch::toString( arg ) ) \ - .setRhs( matcherAsString == "{?}" ? #matcher : matcherAsString ) \ - .setOp( "matches" ) \ - .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ - __catchResult.captureExpression(); \ + __catchResult.captureMatch( arg, matcher, #matcher ); \ } catch( ... ) { \ __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ } \ @@ -1591,21 +2310,6 @@ namespace Catch { // #included from: catch_section_info.h #define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED -namespace Catch { - - struct SectionInfo { - SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description = std::string() ); - - std::string name; - std::string description; - SourceLineInfo lineInfo; - }; - -} // end namespace Catch - // #included from: catch_totals.hpp #define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED @@ -1636,6 +2340,9 @@ namespace Catch { bool allPassed() const { return failed == 0 && failedButOk == 0; } + bool allOk() const { + return failed == 0; + } std::size_t passed; std::size_t failed; @@ -1673,27 +2380,59 @@ namespace Catch { }; } -// #included from: catch_timer.h -#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED - -#ifdef CATCH_PLATFORM_WINDOWS -typedef unsigned long long uint64_t; -#else -#include -#endif +#include namespace Catch { + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) + : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef _MSC_VER + +namespace Catch { + typedef unsigned long long UInt64; +} +#else +#include +namespace Catch { + typedef uint64_t UInt64; +} +#endif + +namespace Catch { class Timer { public: Timer() : m_ticks( 0 ) {} void start(); - unsigned int getElapsedNanoseconds() const; + unsigned int getElapsedMicroseconds() const; unsigned int getElapsedMilliseconds() const; double getElapsedSeconds() const; private: - uint64_t m_ticks; + UInt64 m_ticks; }; } // namespace Catch @@ -1702,7 +2441,7 @@ namespace Catch { namespace Catch { - class Section { + class Section : NonCopyable { public: Section( SectionInfo const& info ); ~Section(); @@ -1711,15 +2450,6 @@ namespace Catch { operator bool() const; private: -#ifdef CATCH_CPP11_OR_GREATER - Section( Section const& ) = delete; - Section( Section && ) = delete; - Section& operator = ( Section const& ) = delete; - Section& operator = ( Section && ) = delete; -#else - Section( Section const& info ); - Section& operator = ( Section const& ); -#endif SectionInfo m_info; std::string m_name; @@ -1741,7 +2471,6 @@ namespace Catch { // #included from: internal/catch_generators.hpp #define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED -#include #include #include #include @@ -1855,7 +2584,7 @@ public: private: void move( CompositeGenerator& other ) { - std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_composed.insert( m_composed.end(), other.m_composed.begin(), other.m_composed.end() ); m_totalSize += other.m_totalSize; other.m_composed.clear(); } @@ -1922,6 +2651,8 @@ using namespace Generators; #define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED #include +#include + // #included from: catch_interfaces_registry_hub.h #define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED @@ -1935,20 +2666,25 @@ namespace Catch { struct IExceptionTranslator; struct IReporterRegistry; struct IReporterFactory; + struct ITagAliasRegistry; struct IRegistryHub { virtual ~IRegistryHub(); virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; }; struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); - virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; + virtual void registerListener( Ptr const& factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; }; IRegistryHub& getRegistryHub(); @@ -1958,14 +2694,16 @@ namespace Catch { } - namespace Catch { typedef std::string(*exceptionTranslateFunction)(); + struct IExceptionTranslator; + typedef std::vector ExceptionTranslators; + struct IExceptionTranslator { virtual ~IExceptionTranslator(); - virtual std::string translate() const = 0; + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; }; struct IExceptionTranslatorRegistry { @@ -1983,9 +2721,12 @@ namespace Catch { : m_translateFunction( translateFunction ) {} - virtual std::string translate() const { + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { try { - throw; + if( it == itEnd ) + throw; + else + return (*it)->translate( it+1, itEnd ); } catch( T& ex ) { return m_translateFunction( ex ); @@ -2006,10 +2747,12 @@ namespace Catch { } /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) // #included from: internal/catch_approx.hpp #define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED @@ -2017,6 +2760,10 @@ namespace Catch { #include #include +#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) +#include +#endif + namespace Catch { namespace Detail { @@ -2024,12 +2771,14 @@ namespace Detail { public: explicit Approx ( double value ) : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_margin( 0.0 ), m_scale( 1.0 ), m_value( value ) {} Approx( Approx const& other ) : m_epsilon( other.m_epsilon ), + m_margin( other.m_margin ), m_scale( other.m_scale ), m_value( other.m_value ) {} @@ -2038,16 +2787,102 @@ namespace Detail { return Approx( 0 ); } +#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) + + template ::value>::type> + Approx operator()( T value ) { + Approx approx( static_cast(value) ); + approx.epsilon( m_epsilon ); + approx.margin( m_margin ); + approx.scale( m_scale ); + return approx; + } + + template ::value>::type> + explicit Approx( T value ): Approx(static_cast(value)) + {} + + template ::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + auto lhs_v = double(lhs); + bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + (std::max)(std::fabs(lhs_v), std::fabs(rhs.m_value))); + if (relativeOK) { + return true; + } + return std::fabs(lhs_v - rhs.m_value) < rhs.m_margin; + } + + template ::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator != ( T lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>::type> + friend bool operator != ( Approx const& lhs, T rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>::type> + friend bool operator <= ( T lhs, Approx const& rhs ) { + return double(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator <= ( Approx const& lhs, T rhs ) { + return lhs.m_value < double(rhs) || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( T lhs, Approx const& rhs ) { + return double(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>::type> + friend bool operator >= ( Approx const& lhs, T rhs ) { + return lhs.m_value > double(rhs) || lhs == rhs; + } + + template ::value>::type> + Approx& epsilon( T newEpsilon ) { + m_epsilon = double(newEpsilon); + return *this; + } + + template ::value>::type> + Approx& margin( T newMargin ) { + m_margin = double(newMargin); + return *this; + } + + template ::value>::type> + Approx& scale( T newScale ) { + m_scale = double(newScale); + return *this; + } + +#else + Approx operator()( double value ) { Approx approx( value ); approx.epsilon( m_epsilon ); + approx.margin( m_margin ); approx.scale( m_scale ); return approx; } friend bool operator == ( double lhs, Approx const& rhs ) { // Thanks to Richard Harris for his help refining this formula - return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) ); + if (relativeOK) { + return true; + } + return std::fabs(lhs - rhs.m_value) < rhs.m_margin; } friend bool operator == ( Approx const& lhs, double rhs ) { @@ -2062,15 +2897,37 @@ namespace Detail { return !operator==( rhs, lhs ); } + friend bool operator <= ( double lhs, Approx const& rhs ) { + return lhs < rhs.m_value || lhs == rhs; + } + + friend bool operator <= ( Approx const& lhs, double rhs ) { + return lhs.m_value < rhs || lhs == rhs; + } + + friend bool operator >= ( double lhs, Approx const& rhs ) { + return lhs > rhs.m_value || lhs == rhs; + } + + friend bool operator >= ( Approx const& lhs, double rhs ) { + return lhs.m_value > rhs || lhs == rhs; + } + Approx& epsilon( double newEpsilon ) { m_epsilon = newEpsilon; return *this; } + Approx& margin( double newMargin ) { + m_margin = newMargin; + return *this; + } + Approx& scale( double newScale ) { m_scale = newScale; return *this; } +#endif std::string toString() const { std::ostringstream oss; @@ -2080,6 +2937,7 @@ namespace Detail { private: double m_epsilon; + double m_margin; double m_scale; double m_value; }; @@ -2092,229 +2950,151 @@ inline std::string toString( Detail::Approx const& value ) { } // end namespace Catch -// #included from: internal/catch_matchers.hpp -#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED +// #included from: internal/catch_matchers_string.h +#define TWOBLUECUBES_CATCH_MATCHERS_STRING_H_INCLUDED namespace Catch { namespace Matchers { - namespace Impl { - - template - struct Matcher : SharedImpl - { - typedef ExpressionT ExpressionType; - - virtual ~Matcher() {} - virtual Ptr clone() const = 0; - virtual bool match( ExpressionT const& expr ) const = 0; - virtual std::string toString() const = 0; - }; - - template - struct MatcherImpl : Matcher { - - virtual Ptr > clone() const { - return Ptr >( new DerivedT( static_cast( *this ) ) ); - } - }; - - namespace Generic { - - template - class AllOf : public MatcherImpl, ExpressionT> { - public: - - AllOf() {} - AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} - - AllOf& add( Matcher const& matcher ) { - m_matchers.push_back( matcher.clone() ); - return *this; - } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( !m_matchers[i]->match( expr ) ) - return false; - return true; - } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - oss << " and "; - oss << m_matchers[i]->toString(); - } - oss << " )"; - return oss.str(); - } - - private: - std::vector > > m_matchers; - }; - - template - class AnyOf : public MatcherImpl, ExpressionT> { - public: - - AnyOf() {} - AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} - - AnyOf& add( Matcher const& matcher ) { - m_matchers.push_back( matcher.clone() ); - return *this; - } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( m_matchers[i]->match( expr ) ) - return true; - return false; - } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - oss << " or "; - oss << m_matchers[i]->toString(); - } - oss << " )"; - return oss.str(); - } - - private: - std::vector > > m_matchers; - }; - - } namespace StdString { - inline std::string makeString( std::string const& str ) { return str; } - inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } - - struct Equals : MatcherImpl { - Equals( std::string const& str ) : m_str( str ){} - Equals( Equals const& other ) : m_str( other.m_str ){} - - virtual ~Equals(); - - virtual bool match( std::string const& expr ) const { - return m_str == expr; - } - virtual std::string toString() const { - return "equals: \"" + m_str + "\""; - } + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + CaseSensitive::Choice m_caseSensitivity; std::string m_str; }; - struct Contains : MatcherImpl { - Contains( std::string const& substr ) : m_substr( substr ){} - Contains( Contains const& other ) : m_substr( other.m_substr ){} + struct StringMatcherBase : MatcherBase { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + virtual std::string describe() const CATCH_OVERRIDE; - virtual ~Contains(); - - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) != std::string::npos; - } - virtual std::string toString() const { - return "contains: \"" + m_substr + "\""; - } - - std::string m_substr; + CasedString m_comparator; + std::string m_operation; }; - struct StartsWith : MatcherImpl { - StartsWith( std::string const& substr ) : m_substr( substr ){} - StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} - - virtual ~StartsWith(); - - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) == 0; - } - virtual std::string toString() const { - return "starts with: \"" + m_substr + "\""; - } - - std::string m_substr; + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; }; - struct EndsWith : MatcherImpl { - EndsWith( std::string const& substr ) : m_substr( substr ){} - EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} - - virtual ~EndsWith(); - - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) == expr.size() - m_substr.size(); - } - virtual std::string toString() const { - return "ends with: \"" + m_substr + "\""; - } - - std::string m_substr; - }; } // namespace StdString - } // namespace Impl // The following functions create the actual matcher objects. // This allows the types to be inferred - template - inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, - Impl::Matcher const& m2 ) { - return Impl::Generic::AllOf().add( m1 ).add( m2 ); - } - template - inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, - Impl::Matcher const& m2, - Impl::Matcher const& m3 ) { - return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); - } - template - inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, - Impl::Matcher const& m2 ) { - return Impl::Generic::AnyOf().add( m1 ).add( m2 ); - } - template - inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, - Impl::Matcher const& m2, - Impl::Matcher const& m3 ) { - return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// #included from: internal/catch_matchers_vector.h +#define TWOBLUECUBES_CATCH_MATCHERS_VECTOR_H_INCLUDED + +namespace Catch { +namespace Matchers { + + namespace Vector { + + template + struct ContainsElementMatcher : MatcherBase, T> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector const &v) const CATCH_OVERRIDE { + return std::find(v.begin(), v.end(), m_comparator) != v.end(); + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "Contains: " + Catch::toString( m_comparator ); + } + + T const& m_comparator; + }; + + template + struct ContainsMatcher : MatcherBase, std::vector > { + + ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const CATCH_OVERRIDE { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (size_t i = 0; i < m_comparator.size(); ++i) + if (std::find(v.begin(), v.end(), m_comparator[i]) == v.end()) + return false; + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + return "Contains: " + Catch::toString( m_comparator ); + } + + std::vector const& m_comparator; + }; + + template + struct EqualsMatcher : MatcherBase, std::vector > { + + EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector const &v) const CATCH_OVERRIDE { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + return "Equals: " + Catch::toString( m_comparator ); + } + std::vector const& m_comparator; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template + Vector::ContainsMatcher Contains( std::vector const& comparator ) { + return Vector::ContainsMatcher( comparator ); } - inline Impl::StdString::Equals Equals( std::string const& str ) { - return Impl::StdString::Equals( str ); + template + Vector::ContainsElementMatcher VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher( comparator ); } - inline Impl::StdString::Equals Equals( const char* str ) { - return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); - } - inline Impl::StdString::Contains Contains( std::string const& substr ) { - return Impl::StdString::Contains( substr ); - } - inline Impl::StdString::Contains Contains( const char* substr ) { - return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); - } - inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { - return Impl::StdString::StartsWith( substr ); - } - inline Impl::StdString::StartsWith StartsWith( const char* substr ) { - return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); - } - inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { - return Impl::StdString::EndsWith( substr ); - } - inline Impl::StdString::EndsWith EndsWith( const char* substr ) { - return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + + template + Vector::EqualsMatcher Equals( std::vector const& comparator ) { + return Vector::EqualsMatcher( comparator ); } } // namespace Matchers - -using namespace Matchers; - } // namespace Catch // #included from: internal/catch_interfaces_tag_alias_registry.h @@ -2328,7 +3108,7 @@ using namespace Matchers; namespace Catch { struct TagAlias { - TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + TagAlias( std::string const& _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} std::string tag; SourceLineInfo lineInfo; @@ -2350,12 +3130,12 @@ namespace Catch { template class Option { public: - Option() : nullableValue( NULL ) {} + Option() : nullableValue( CATCH_NULL ) {} Option( T const& _value ) : nullableValue( new( storage ) T( _value ) ) {} Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) {} ~Option() { @@ -2379,7 +3159,7 @@ namespace Catch { void reset() { if( nullableValue ) nullableValue->~T(); - nullableValue = NULL; + nullableValue = CATCH_NULL; } T& operator*() { return *nullableValue; } @@ -2391,17 +3171,27 @@ namespace Catch { return nullableValue ? *nullableValue : defaultValue; } - bool some() const { return nullableValue != NULL; } - bool none() const { return nullableValue == NULL; } + bool some() const { return nullableValue != CATCH_NULL; } + bool none() const { return nullableValue == CATCH_NULL; } - bool operator !() const { return nullableValue == NULL; } + bool operator !() const { return nullableValue == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( some() ); } private: - T* nullableValue; - char storage[sizeof(T)]; + T *nullableValue; + union { + char storage[sizeof(T)]; + + // These are here to force alignment for the storage + long double dummy1; + void (*dummy2)(); + long double dummy3; +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + long long dummy4; +#endif + }; }; } // end namespace Catch @@ -2441,7 +3231,8 @@ namespace Catch { IsHidden = 1 << 1, ShouldFail = 1 << 2, MayFail = 1 << 3, - Throws = 1 << 4 + Throws = 1 << 4, + NonPortable = 1 << 5 }; TestCaseInfo( std::string const& _name, @@ -2452,6 +3243,8 @@ namespace Catch { TestCaseInfo( TestCaseInfo const& other ); + friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); + bool isHidden() const; bool throws() const; bool okToFail() const; @@ -2564,7 +3357,7 @@ namespace Catch { inline size_t registerTestMethods() { size_t noTestMethods = 0; - int noClasses = objc_getClassList( NULL, 0 ); + int noClasses = objc_getClassList( CATCH_NULL, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); @@ -2597,64 +3390,67 @@ namespace Catch { namespace Impl { namespace NSStringMatchers { - template - struct StringHolder : MatcherImpl{ + struct StringHolder : MatcherBase{ StringHolder( NSString* substr ) : m_substr( [substr copy] ){} StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} StringHolder() { arcSafeRelease( m_substr ); } + virtual bool match( NSString* arg ) const CATCH_OVERRIDE { + return false; + } + NSString* m_substr; }; - struct Equals : StringHolder { + struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} - virtual bool match( ExpressionType const& str ) const { + virtual bool match( NSString* str ) const CATCH_OVERRIDE { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } - virtual std::string toString() const { + virtual std::string describe() const CATCH_OVERRIDE { return "equals string: " + Catch::toString( m_substr ); } }; - struct Contains : StringHolder { + struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} - virtual bool match( ExpressionType const& str ) const { + virtual bool match( NSString* str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } - virtual std::string toString() const { + virtual std::string describe() const CATCH_OVERRIDE { return "contains string: " + Catch::toString( m_substr ); } }; - struct StartsWith : StringHolder { + struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} - virtual bool match( ExpressionType const& str ) const { + virtual bool match( NSString* str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } - virtual std::string toString() const { + virtual std::string describe() const CATCH_OVERRIDE { return "starts with: " + Catch::toString( m_substr ); } }; - struct EndsWith : StringHolder { + struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} - virtual bool match( ExpressionType const& str ) const { + virtual bool match( NSString* str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } - virtual std::string toString() const { + virtual std::string describe() const CATCH_OVERRIDE { return "ends with: " + Catch::toString( m_substr ); } }; @@ -2694,7 +3490,30 @@ return @ desc; \ #endif -#ifdef CATCH_CONFIG_RUNNER +#ifdef CATCH_IMPL + +// !TBD: Move the leak detector code into a separate header +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include +class LeakDetector { +public: + LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +}; +#else +class LeakDetector {}; +#endif + +LeakDetector leakDetector; + // #included from: internal/catch_impl.hpp #define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED @@ -2706,7 +3525,7 @@ return @ desc; \ #pragma clang diagnostic ignored "-Wweak-vtables" #endif -// #included from: catch_runner.hpp +// #included from: ../catch_session.hpp #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED // #included from: internal/catch_commandline.hpp @@ -2731,6 +3550,69 @@ return @ desc; \ #pragma clang diagnostic ignored "-Wpadded" #endif +// #included from: catch_wildcard_pattern.hpp +#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_wildcard( NoWildcard ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~WildcardPattern(); + virtual bool matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard; + std::string m_pattern; + }; +} + #include #include @@ -2742,50 +3624,18 @@ namespace Catch { virtual bool matches( TestCaseInfo const& testCase ) const = 0; }; class NamePattern : public Pattern { - enum WildcardPosition { - NoWildcard = 0, - WildcardAtStart = 1, - WildcardAtEnd = 2, - WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd - }; - public: - NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { - if( startsWith( m_name, "*" ) ) { - m_name = m_name.substr( 1 ); - m_wildcard = WildcardAtStart; - } - if( endsWith( m_name, "*" ) ) { - m_name = m_name.substr( 0, m_name.size()-1 ); - m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); - } - } + NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} virtual ~NamePattern(); virtual bool matches( TestCaseInfo const& testCase ) const { - switch( m_wildcard ) { - case NoWildcard: - return m_name == toLower( testCase.name ); - case WildcardAtStart: - return endsWith( toLower( testCase.name ), m_name ); - case WildcardAtEnd: - return startsWith( toLower( testCase.name ), m_name ); - case WildcardAtBothEnds: - return contains( toLower( testCase.name ), m_name ); - } - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" -#endif - throw std::logic_error( "Unknown enum" ); -#ifdef __clang__ -#pragma clang diagnostic pop -#endif + return m_wildcardPattern.matches( toLower( testCase.name ) ); } private: - std::string m_name; - WildcardPosition m_wildcard; + WildcardPattern m_wildcardPattern; }; + class TagPattern : public Pattern { public: TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} @@ -2796,6 +3646,7 @@ namespace Catch { private: std::string m_tag; }; + class ExcludedPattern : public Pattern { public: ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} @@ -2810,10 +3661,11 @@ namespace Catch { bool matches( TestCaseInfo const& testCase ) const { // All patterns in a filter must match for the filter to be a match - for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { if( !(*it)->matches( testCase ) ) return false; - return true; + } + return true; } }; @@ -2843,11 +3695,12 @@ namespace Catch { namespace Catch { class TestSpecParser { - enum Mode{ None, Name, QuotedName, Tag }; + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; Mode m_mode; bool m_exclusion; std::size_t m_start, m_pos; std::string m_arg; + std::vector m_escapeChars; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; ITagAliasRegistry const* m_tagAliases; @@ -2860,6 +3713,7 @@ namespace Catch { m_exclusion = false; m_start = std::string::npos; m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) visitChar( m_arg[m_pos] ); if( m_mode == Name ) @@ -2878,6 +3732,7 @@ namespace Catch { case '~': m_exclusion = true; return; case '[': return startNewMode( Tag, ++m_pos ); case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); default: startNewMode( Name, m_pos ); break; } } @@ -2893,7 +3748,11 @@ namespace Catch { addPattern(); startNewMode( Tag, ++m_pos ); } + else if( c == '\\' ) + escape(); } + else if( m_mode == EscapedName ) + m_mode = Name; else if( m_mode == QuotedName && c == '"' ) addPattern(); else if( m_mode == Tag && c == ']' ) @@ -2903,10 +3762,19 @@ namespace Catch { m_mode = mode; m_start = start; } + void escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } template void addPattern() { std::string token = subString(); + for( size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); if( startsWith( token, "exclude:" ) ) { m_exclusion = true; token = token.substr( 8 ); @@ -2940,7 +3808,7 @@ namespace Catch { // #included from: catch_interfaces_config.h #define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED -#include +#include #include #include @@ -2962,6 +3830,16 @@ namespace Catch { Always, Never }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; class TestSpec; @@ -2979,37 +3857,80 @@ namespace Catch { virtual bool showInvisibles() const = 0; virtual ShowDurations::OrNot showDurations() const = 0; virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + }; } // #included from: catch_stream.h #define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED -#include +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wpadded" -#endif +#include namespace Catch { - class Stream { + class StreamBufBase : public std::streambuf { public: - Stream(); - Stream( std::streambuf* _streamBuf, bool _isOwned ); - void release(); + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} - std::streambuf* streamBuf; +#include +#include +#include +#include - private: - bool isOwned; +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + + struct IStream { + virtual ~IStream() CATCH_NOEXCEPT; + virtual std::ostream& stream() const = 0; + }; + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( std::string const& filename ); + virtual ~FileStream() CATCH_NOEXCEPT; + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + CoutStream(); + virtual ~CoutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class DebugOutStream : public IStream { + CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream(); + virtual ~DebugOutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; }; } #include #include #include -#include +#include #ifndef CATCH_CONFIG_CONSOLE_WIDTH #define CATCH_CONFIG_CONSOLE_WIDTH 80 @@ -3024,40 +3945,51 @@ namespace Catch { listTags( false ), listReporters( false ), listTestNamesOnly( false ), + listExtraInfo( false ), showSuccessfulTests( false ), shouldDebugBreak( false ), noThrow( false ), showHelp( false ), showInvisibles( false ), + filenamesAsTags( false ), abortAfter( -1 ), + rngSeed( 0 ), verbosity( Verbosity::Normal ), warnings( WarnAbout::Nothing ), - showDurations( ShowDurations::DefaultForReporter ) + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ), + useColour( UseColour::Auto ) {} bool listTests; bool listTags; bool listReporters; bool listTestNamesOnly; + bool listExtraInfo; bool showSuccessfulTests; bool shouldDebugBreak; bool noThrow; bool showHelp; bool showInvisibles; + bool filenamesAsTags; int abortAfter; + unsigned int rngSeed; Verbosity::Level verbosity; WarnAbout::What warnings; ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + UseColour::YesOrNo useColour; - std::string reporterName; std::string outputFilename; std::string name; std::string processName; + std::vector reporterNames; std::vector testsOrTags; + std::vector sectionsToRun; }; class Config : public SharedImpl { @@ -3068,12 +4000,11 @@ namespace Catch { public: Config() - : m_os( std::cout.rdbuf() ) {} Config( ConfigData const& data ) : m_data( data ), - m_os( std::cout.rdbuf() ) + m_stream( openStream() ) { if( !data.testsOrTags.empty() ) { TestSpecParser parser( ITagAliasRegistry::get() ); @@ -3083,14 +4014,7 @@ namespace Catch { } } - virtual ~Config() { - m_os.rdbuf( std::cout.rdbuf() ); - m_stream.release(); - } - - void setFilename( std::string const& filename ) { - m_data.outputFilename = filename; - } + virtual ~Config() {} std::string const& getFilename() const { return m_data.outputFilename ; @@ -3100,44 +4024,48 @@ namespace Catch { bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } bool listTags() const { return m_data.listTags; } bool listReporters() const { return m_data.listReporters; } + bool listExtraInfo() const { return m_data.listExtraInfo; } std::string getProcessName() const { return m_data.processName; } - bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + std::vector const& getReporterNames() const { return m_data.reporterNames; } + std::vector const& getSectionsToRun() const CATCH_OVERRIDE { return m_data.sectionsToRun; } - void setStreamBuf( std::streambuf* buf ) { - m_os.rdbuf( buf ? buf : std::cout.rdbuf() ); - } - - void useStream( std::string const& streamName ) { - Stream stream = createStream( streamName ); - setStreamBuf( stream.streamBuf ); - m_stream.release(); - m_stream = stream; - } - - std::string getReporterName() const { return m_data.reporterName; } - - int abortAfter() const { return m_data.abortAfter; } - - TestSpec const& testSpec() const { return m_testSpec; } + virtual TestSpec const& testSpec() const CATCH_OVERRIDE { return m_testSpec; } bool showHelp() const { return m_data.showHelp; } - bool showInvisibles() const { return m_data.showInvisibles; } // IConfig interface - virtual bool allowThrows() const { return !m_data.noThrow; } - virtual std::ostream& stream() const { return m_os; } - virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } - virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } - virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } - virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } + virtual bool allowThrows() const CATCH_OVERRIDE { return !m_data.noThrow; } + virtual std::ostream& stream() const CATCH_OVERRIDE { return m_stream->stream(); } + virtual std::string name() const CATCH_OVERRIDE { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const CATCH_OVERRIDE { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const CATCH_OVERRIDE { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const CATCH_OVERRIDE { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const CATCH_OVERRIDE { return m_data.runOrder; } + virtual unsigned int rngSeed() const CATCH_OVERRIDE { return m_data.rngSeed; } + virtual UseColour::YesOrNo useColour() const CATCH_OVERRIDE { return m_data.useColour; } + virtual bool shouldDebugBreak() const CATCH_OVERRIDE { return m_data.shouldDebugBreak; } + virtual int abortAfter() const CATCH_OVERRIDE { return m_data.abortAfter; } + virtual bool showInvisibles() const CATCH_OVERRIDE { return m_data.showInvisibles; } private: + + IStream const* openStream() { + if( m_data.outputFilename.empty() ) + return new CoutStream(); + else if( m_data.outputFilename[0] == '%' ) { + if( m_data.outputFilename == "%debug" ) + return new DebugOutStream(); + else + throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); + } + else + return new FileStream( m_data.outputFilename ); + } ConfigData m_data; - Stream m_stream; - mutable std::ostream m_os; + CATCH_AUTO_PTR( IStream const ) m_stream; TestSpec m_testSpec; }; @@ -3157,6 +4085,8 @@ namespace Catch { #define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { // #included from: ../external/clara.h +// Version 0.0.2.4 + // Only use header guard if we are not using an outer namespace #if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) @@ -3181,6 +4111,8 @@ namespace Catch { #include #include #include +#include +#include // Use optional outer namespace #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE @@ -3315,15 +4247,165 @@ namespace Tbc { #endif // TBC_TEXT_FORMAT_H_INCLUDED // ----------- end of #include from tbc_text_format.h ----------- -// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h +// ........... back in clara.h #undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE +// ----------- #included from clara_compilers.h ----------- + +#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED +#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CLARA_CONFIG_CPP11_OVERRIDE : is override supported? +// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) + +// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_ form +// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 + +#ifdef __clang__ + +#if __has_feature(cxx_nullptr) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#if __has_feature(cxx_noexcept) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(__cplusplus) && __cplusplus >= 201103L + +#define CLARA_CPP11_OR_GREATER + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) +#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE +#endif +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NULLPTR +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_UNIQUE_PTR +#endif + +// noexcept support: +#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) +#define CLARA_NOEXCEPT noexcept +# define CLARA_NOEXCEPT_IS(x) noexcept(x) +#else +#define CLARA_NOEXCEPT throw() +# define CLARA_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CLARA_CONFIG_CPP11_NULLPTR +#define CLARA_NULL nullptr +#else +#define CLARA_NULL NULL +#endif + +// override support +#ifdef CLARA_CONFIG_CPP11_OVERRIDE +#define CLARA_OVERRIDE override +#else +#define CLARA_OVERRIDE +#endif + +// unique_ptr support +#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR +# define CLARA_AUTO_PTR( T ) std::unique_ptr +#else +# define CLARA_AUTO_PTR( T ) std::auto_ptr +#endif + +#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// ----------- end of #include from clara_compilers.h ----------- +// ........... back in clara.h + #include -#include #include #include +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CLARA_PLATFORM_WINDOWS +#endif + // Use optional outer namespace #ifdef STITCH_CLARA_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE @@ -3372,9 +4454,12 @@ namespace Clara { inline void convertInto( std::string const& _source, std::string& _dest ) { _dest = _source; } + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } inline void convertInto( std::string const& _source, bool& _dest ) { std::string sourceLC = _source; - std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) _dest = true; else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) @@ -3382,23 +4467,15 @@ namespace Clara { else throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); } - inline void convertInto( bool _source, bool& _dest ) { - _dest = _source; - } - template - inline void convertInto( bool, T& ) { - throw std::runtime_error( "Invalid conversion" ); - } template struct IArgFunction { virtual ~IArgFunction() {} -# ifdef CATCH_CPP11_OR_GREATER +#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS IArgFunction() = default; IArgFunction( IArgFunction const& ) = default; -# endif +#endif virtual void set( ConfigT& config, std::string const& value ) const = 0; - virtual void setFlag( ConfigT& config ) const = 0; virtual bool takesArg() const = 0; virtual IArgFunction* clone() const = 0; }; @@ -3406,11 +4483,11 @@ namespace Clara { template class BoundArgFunction { public: - BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction() : functionObj( CLARA_NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} - BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { - IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; delete functionObj; functionObj = newFunctionObj; return *this; @@ -3420,13 +4497,10 @@ namespace Clara { void set( ConfigT& config, std::string const& value ) const { functionObj->set( config, value ); } - void setFlag( ConfigT& config ) const { - functionObj->setFlag( config ); - } bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { - return functionObj != NULL; + return functionObj != CLARA_NULL; } private: IArgFunction* functionObj; @@ -3435,7 +4509,6 @@ namespace Clara { template struct NullBinder : IArgFunction{ virtual void set( C&, std::string const& ) const {} - virtual void setFlag( C& ) const {} virtual bool takesArg() const { return true; } virtual IArgFunction* clone() const { return new NullBinder( *this ); } }; @@ -3446,9 +4519,6 @@ namespace Clara { virtual void set( C& p, std::string const& stringValue ) const { convertInto( stringValue, p.*member ); } - virtual void setFlag( C& p ) const { - convertInto( true, p.*member ); - } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } M C::* member; @@ -3461,11 +4531,6 @@ namespace Clara { convertInto( stringValue, value ); (p.*member)( value ); } - virtual void setFlag( C& p ) const { - typename RemoveConstRef::type value; - convertInto( true, value ); - (p.*member)( value ); - } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } void (C::*member)( M ); @@ -3479,9 +4544,6 @@ namespace Clara { if( value ) (p.*member)(); } - virtual void setFlag( C& p ) const { - (p.*member)(); - } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } void (C::*member)(); @@ -3496,9 +4558,6 @@ namespace Clara { if( value ) function( obj ); } - virtual void setFlag( C& p ) const { - function( p ); - } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } void (*function)( C& ); @@ -3512,11 +4571,6 @@ namespace Clara { convertInto( stringValue, value ); function( obj, value ); } - virtual void setFlag( C& obj ) const { - typename RemoveConstRef::type value; - convertInto( true, value ); - function( obj, value ); - } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } void (*function)( C&, T ); @@ -3524,8 +4578,20 @@ namespace Clara { } // namespace Detail - struct Parser { - Parser() : separators( " \t=:" ) {} + inline std::vector argsToVector( int argc, char const* const* const argv ) { + std::vector args( static_cast( argc ) ); + for( std::size_t i = 0; i < static_cast( argc ); ++i ) + args[i] = argv[i]; + + return args; + } + + class Parser { + enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; + Mode mode; + std::size_t from; + bool inQuotes; + public: struct Token { enum Type { Positional, ShortOpt, LongOpt }; @@ -3534,38 +4600,77 @@ namespace Clara { std::string data; }; - void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { + Parser() : mode( None ), from( 0 ), inQuotes( false ){} + + void parseIntoTokens( std::vector const& args, std::vector& tokens ) { const std::string doubleDash = "--"; - for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) - parseIntoTokens( argv[i] , tokens); + for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) + parseIntoTokens( args[i], tokens); } - void parseIntoTokens( std::string arg, std::vector& tokens ) const { - while( !arg.empty() ) { - Parser::Token token( Parser::Token::Positional, arg ); - arg = ""; - if( token.data[0] == '-' ) { - if( token.data.size() > 1 && token.data[1] == '-' ) { - token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); - } - else { - token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); - if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { - arg = "-" + token.data.substr( 1 ); - token.data = token.data.substr( 0, 1 ); - } - } - } - if( token.type != Parser::Token::Positional ) { - std::size_t pos = token.data.find_first_of( separators ); - if( pos != std::string::npos ) { - arg = token.data.substr( pos+1 ); - token.data = token.data.substr( 0, pos ); - } - } - tokens.push_back( token ); + + void parseIntoTokens( std::string const& arg, std::vector& tokens ) { + for( std::size_t i = 0; i < arg.size(); ++i ) { + char c = arg[i]; + if( c == '"' ) + inQuotes = !inQuotes; + mode = handleMode( i, c, arg, tokens ); + } + mode = handleMode( arg.size(), '\0', arg, tokens ); + } + Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + switch( mode ) { + case None: return handleNone( i, c ); + case MaybeShortOpt: return handleMaybeShortOpt( i, c ); + case ShortOpt: + case LongOpt: + case SlashOpt: return handleOpt( i, c, arg, tokens ); + case Positional: return handlePositional( i, c, arg, tokens ); + default: throw std::logic_error( "Unknown mode" ); } } - std::string separators; + + Mode handleNone( std::size_t i, char c ) { + if( inQuotes ) { + from = i; + return Positional; + } + switch( c ) { + case '-': return MaybeShortOpt; +#ifdef CLARA_PLATFORM_WINDOWS + case '/': from = i+1; return SlashOpt; +#endif + default: from = i; return Positional; + } + } + Mode handleMaybeShortOpt( std::size_t i, char c ) { + switch( c ) { + case '-': from = i+1; return LongOpt; + default: from = i; return ShortOpt; + } + } + + Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) + return mode; + + std::string optName = arg.substr( from, i-from ); + if( mode == ShortOpt ) + for( std::size_t j = 0; j < optName.size(); ++j ) + tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); + else if( mode == SlashOpt && optName.size() == 1 ) + tokens.push_back( Token( Token::ShortOpt, optName ) ); + else + tokens.push_back( Token( Token::LongOpt, optName ) ); + return None; + } + Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { + if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) + return mode; + + std::string data = arg.substr( from, i-from ); + tokens.push_back( Token( Token::Positional, data ) ); + return None; + } }; template @@ -3644,12 +4749,7 @@ namespace Clara { } }; - // NOTE: std::auto_ptr is deprecated in c++11/c++0x -#if defined(__cplusplus) && __cplusplus > 199711L - typedef std::unique_ptr ArgAutoPtr; -#else - typedef std::auto_ptr ArgAutoPtr; -#endif + typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; friend void addOptName( Arg& arg, std::string const& optName ) { @@ -3760,7 +4860,7 @@ namespace Clara { m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) { if( other.m_floatingArg.get() ) - m_floatingArg = ArgAutoPtr( new Arg( *other.m_floatingArg ) ); + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); } CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { @@ -3788,7 +4888,7 @@ namespace Clara { ArgBuilder operator[]( UnpositionalTag ) { if( m_floatingArg.get() ) throw std::logic_error( "Only one unpositional argument can be added" ); - m_floatingArg = ArgAutoPtr( new Arg() ); + m_floatingArg.reset( new Arg() ); ArgBuilder builder( m_floatingArg.get() ); return builder; } @@ -3873,21 +4973,21 @@ namespace Clara { return oss.str(); } - ConfigT parse( int argc, char const * const * argv ) const { + ConfigT parse( std::vector const& args ) const { ConfigT config; - parseInto( argc, argv, config ); + parseInto( args, config ); return config; } - std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { - std::string processName = argv[0]; + std::vector parseInto( std::vector const& args, ConfigT& config ) const { + std::string processName = args.empty() ? std::string() : args[0]; std::size_t lastSlash = processName.find_last_of( "/\\" ); if( lastSlash != std::string::npos ) processName = processName.substr( lastSlash+1 ); m_boundProcessName.set( config, processName ); std::vector tokens; Parser parser; - parser.parseIntoTokens( argc, argv, tokens ); + parser.parseIntoTokens( args, tokens ); return populate( tokens, config ); } @@ -3918,7 +5018,7 @@ namespace Clara { arg.boundField.set( config, tokens[++i].data ); } else { - arg.boundField.setFlag( config ); + arg.boundField.set( config, "true" ); } break; } @@ -3930,7 +5030,7 @@ namespace Clara { if( it == itEnd ) { if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) unusedTokens.push_back( token ); - else if( m_throwOnUnrecognisedTokens ) + else if( errors.empty() && m_throwOnUnrecognisedTokens ) errors.push_back( "unrecognised option: " + token.data ); } } @@ -4012,6 +5112,7 @@ STITCH_CLARA_CLOSE_NAMESPACE #endif #include +#include namespace Catch { @@ -4022,13 +5123,36 @@ namespace Catch { config.abortAfter = x; } inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); } + inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } inline void addWarning( ConfigData& config, std::string const& _warning ) { if( _warning == "NoAssertions" ) config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); else - throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); - + throw std::runtime_error( "Unrecognised warning: '" + _warning + '\'' ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + '\'' ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argument to --rng-seed should be the word 'time' or a number" ); + } } inline void setVerbosity( ConfigData& config, int level ) { // !TBD: accept strings? @@ -4039,6 +5163,21 @@ namespace Catch { ? ShowDurations::Always : ShowDurations::Never; } + inline void setUseColour( ConfigData& config, std::string const& value ) { + std::string mode = toLower( value ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); + } + inline void forceColour( ConfigData& config ) { + config.useColour = UseColour::Yes; + } inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { std::ifstream f( _filename.c_str() ); if( !f.is_open() ) @@ -4047,8 +5186,11 @@ namespace Catch { std::string line; while( std::getline( f, line ) ) { line = trim(line); - if( !line.empty() && !startsWith( line, "#" ) ) - addTestOrTags( config, "\"" + line + "\"," ); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + addTestOrTags( config, line + ',' ); + } } } @@ -4094,7 +5236,7 @@ namespace Catch { cli["-r"]["--reporter"] // .placeholder( "name[:filename]" ) .describe( "reporter to use (defaults to console)" ) - .bind( &ConfigData::reporterName, "name" ); + .bind( &addReporterName, "name" ); cli["-n"]["--name"] .describe( "suite name" ) @@ -4125,21 +5267,49 @@ namespace Catch { cli["-d"]["--durations"] .describe( "show test durations" ) - .bind( &setShowDurations, "yes/no" ); + .bind( &setShowDurations, "yes|no" ); cli["-f"]["--input-file"] .describe( "load test names to run from a file" ) .bind( &loadTestNamesFromFile, "filename" ); + cli["-#"]["--filenames-as-tags"] + .describe( "adds a tag for the filename" ) + .bind( &ConfigData::filenamesAsTags ); + + cli["-c"]["--section"] + .describe( "specify section to run" ) + .bind( &addSectionToRun, "section name" ); + // Less common commands which don't have a short form cli["--list-test-names-only"] .describe( "list all/matching test cases names only" ) .bind( &ConfigData::listTestNamesOnly ); + cli["--list-extra-info"] + .describe( "list all/matching test cases with more info" ) + .bind( &ConfigData::listExtraInfo ); + cli["--list-reporters"] .describe( "list all reporters" ) .bind( &ConfigData::listReporters ); + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output (deprecated)" ) + .bind( &forceColour ); + + cli["--use-colour"] + .describe( "should output be colourised" ) + .bind( &setUseColour, "yes|no" ); + return cli; } @@ -4187,19 +5357,16 @@ namespace Tbc { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), - width( consoleWidth-1 ), - tabChar( '\t' ) + width( consoleWidth-1 ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } - TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap - char tabChar; // If this char is seen the indent is changed to current pos }; class Text { @@ -4207,62 +5374,76 @@ namespace Tbc { Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { - std::string wrappableChars = " [({.,/|\\-"; - std::size_t indent = _attr.initialIndent != std::string::npos - ? _attr.initialIndent - : _attr.indent; - std::string remainder = _str; + const std::string wrappableBeforeChars = "[({<\t"; + const std::string wrappableAfterChars = "])}>-,./|\\"; + const std::string wrappableInsteadOfChars = " \n\r"; + std::string indent = _attr.initialIndent != std::string::npos + ? std::string( _attr.initialIndent, ' ' ) + : std::string( _attr.indent, ' ' ); + + typedef std::string::const_iterator iterator; + iterator it = _str.begin(); + const iterator strEnd = _str.end(); + + while( it != strEnd ) { - while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } - std::size_t tabPos = std::string::npos; - std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); - std::size_t pos = remainder.find_first_of( '\n' ); - if( pos <= width ) { - width = pos; - } - pos = remainder.find_last_of( _attr.tabChar, width ); - if( pos != std::string::npos ) { - tabPos = pos; - if( remainder[width] == '\n' ) - width--; - remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); - } - if( width == remainder.size() ) { - spliceLine( indent, remainder, width ); - } - else if( remainder[width] == '\n' ) { - spliceLine( indent, remainder, width ); - if( width <= 1 || remainder.size() != 1 ) - remainder = remainder.substr( 1 ); - indent = _attr.indent; - } - else { - pos = remainder.find_last_of( wrappableChars, width ); - if( pos != std::string::npos && pos > 0 ) { - spliceLine( indent, remainder, pos ); - if( remainder[0] == ' ' ) - remainder = remainder.substr( 1 ); + std::string suffix; + std::size_t width = (std::min)( static_cast( strEnd-it ), _attr.width-static_cast( indent.size() ) ); + iterator itEnd = it+width; + iterator itNext = _str.end(); + + iterator itNewLine = std::find( it, itEnd, '\n' ); + if( itNewLine != itEnd ) + itEnd = itNewLine; + + if( itEnd != strEnd ) { + bool foundWrapPoint = false; + iterator findIt = itEnd; + do { + if( wrappableAfterChars.find( *findIt ) != std::string::npos && findIt != itEnd ) { + itEnd = findIt+1; + itNext = findIt+1; + foundWrapPoint = true; + } + else if( findIt > it && wrappableBeforeChars.find( *findIt ) != std::string::npos ) { + itEnd = findIt; + itNext = findIt; + foundWrapPoint = true; + } + else if( wrappableInsteadOfChars.find( *findIt ) != std::string::npos ) { + itNext = findIt+1; + itEnd = findIt; + foundWrapPoint = true; + } + if( findIt == it ) + break; + else + --findIt; + } + while( !foundWrapPoint ); + + if( !foundWrapPoint ) { + // No good wrap char, so we'll break mid word and add a hyphen + --itEnd; + itNext = itEnd; + suffix = "-"; } else { - spliceLine( indent, remainder, width-1 ); - lines.back() += "-"; + while( itEnd > it && wrappableInsteadOfChars.find( *(itEnd-1) ) != std::string::npos ) + --itEnd; } - if( lines.size() == 1 ) - indent = _attr.indent; - if( tabPos != std::string::npos ) - indent += tabPos; } - } - } + lines.push_back( indent + std::string( it, itEnd ) + suffix ); - void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { - lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); - _remainder = _remainder.substr( _pos ); + if( indent.size() != _attr.indent ) + indent = std::string( _attr.indent, ' ' ); + it = itNext; + } } typedef std::vector::const_iterator const_iterator; @@ -4313,10 +5494,6 @@ namespace Catch { namespace Catch { - namespace Detail { - struct IColourImpl; - } - struct Colour { enum Code { None = 0, @@ -4362,7 +5539,6 @@ namespace Catch { static void use( Code _colourCode ); private: - static Detail::IColourImpl* impl(); bool m_moved; }; @@ -4376,23 +5552,22 @@ namespace Catch { #include #include #include -#include namespace Catch { struct ReporterConfig { - explicit ReporterConfig( Ptr const& _fullConfig ) + explicit ReporterConfig( Ptr const& _fullConfig ) : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} - ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} std::ostream& stream() const { return *m_stream; } - Ptr fullConfig() const { return m_fullConfig; } + Ptr fullConfig() const { return m_fullConfig; } private: std::ostream* m_stream; - Ptr m_fullConfig; + Ptr m_fullConfig; }; struct ReporterPreferences { @@ -4456,7 +5631,7 @@ namespace Catch } virtual ~AssertionStats(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionStats( AssertionStats const& ) = default; AssertionStats( AssertionStats && ) = default; AssertionStats& operator = ( AssertionStats const& ) = default; @@ -4479,7 +5654,7 @@ namespace Catch missingAssertions( _missingAssertions ) {} virtual ~SectionStats(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SectionStats( SectionStats const& ) = default; SectionStats( SectionStats && ) = default; SectionStats& operator = ( SectionStats const& ) = default; @@ -4506,7 +5681,7 @@ namespace Catch {} virtual ~TestCaseStats(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestCaseStats( TestCaseStats const& ) = default; TestCaseStats( TestCaseStats && ) = default; TestCaseStats& operator = ( TestCaseStats const& ) = default; @@ -4534,7 +5709,7 @@ namespace Catch {} virtual ~TestGroupStats(); -# ifdef CATCH_CPP11_OR_GREATER +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestGroupStats( TestGroupStats const& ) = default; TestGroupStats( TestGroupStats && ) = default; TestGroupStats& operator = ( TestGroupStats const& ) = default; @@ -4556,7 +5731,7 @@ namespace Catch {} virtual ~TestRunStats(); -# ifndef CATCH_CPP11_OR_GREATER +# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS TestRunStats( TestRunStats const& _other ) : runInfo( _other.runInfo ), totals( _other.totals ), @@ -4574,6 +5749,8 @@ namespace Catch bool aborting; }; + class MultipleReporters; + struct IStreamingReporter : IShared { virtual ~IStreamingReporter(); @@ -4592,27 +5769,37 @@ namespace Catch virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } }; - struct IReporterFactory { + struct IReporterFactory : IShared { virtual ~IReporterFactory(); virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; virtual std::string getDescription() const = 0; }; struct IReporterRegistry { - typedef std::map FactoryMap; + typedef std::map > FactoryMap; + typedef std::vector > Listeners; virtual ~IReporterRegistry(); - virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; }; + Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); + } #include @@ -4624,19 +5811,19 @@ namespace Catch { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) - std::cout << "Matching test cases:\n"; + Catch::cout() << "Matching test cases:\n"; else { - std::cout << "All available test cases:\n"; + Catch::cout() << "All available test cases:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::size_t matchedTests = 0; - TextAttributes nameAttr, tagsAttr; + TextAttributes nameAttr, descAttr, tagsAttr; nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + descAttr.setIndent( 4 ); tagsAttr.setIndent( 6 ); - std::vector matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { @@ -4647,15 +5834,22 @@ namespace Catch { : Colour::None; Colour colourGuard( colour ); - std::cout << Text( testCaseInfo.name, nameAttr ) << std::endl; + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( config.listExtraInfo() ) { + Catch::cout() << " " << testCaseInfo.lineInfo << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Text( description, descAttr ) << std::endl; + } if( !testCaseInfo.tags.empty() ) - std::cout << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; } if( !config.testSpec().hasFilters() ) - std::cout << pluralise( matchedTests, "test case" ) << "\n" << std::endl; + Catch::cout() << pluralise( matchedTests, "test case" ) << '\n' << std::endl; else - std::cout << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; + Catch::cout() << pluralise( matchedTests, "matching test case" ) << '\n' << std::endl; return matchedTests; } @@ -4664,14 +5858,19 @@ namespace Catch { if( !config.testSpec().hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); std::size_t matchedTests = 0; - std::vector matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); - std::cout << testCaseInfo.name << std::endl; + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.listExtraInfo() ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; } return matchedTests; } @@ -4697,16 +5896,15 @@ namespace Catch { inline std::size_t listTags( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) - std::cout << "Tags for matching test cases:\n"; + Catch::cout() << "Tags for matching test cases:\n"; else { - std::cout << "All available tags:\n"; + Catch::cout() << "All available tags:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::map tagCounts; - std::vector matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { @@ -4733,14 +5931,14 @@ namespace Catch { .setInitialIndent( 0 ) .setIndent( oss.str().size() ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); - std::cout << oss.str() << wrapper << "\n"; + Catch::cout() << oss.str() << wrapper << '\n'; } - std::cout << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; return tagCounts.size(); } inline std::size_t listReporters( Config const& /*config*/ ) { - std::cout << "Available reports:\n"; + Catch::cout() << "Available reporters:\n"; IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; std::size_t maxNameLen = 0; @@ -4752,19 +5950,19 @@ namespace Catch { .setInitialIndent( 0 ) .setIndent( 7+maxNameLen ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); - std::cout << " " + Catch::cout() << " " << it->first - << ":" + << ':' << std::string( maxNameLen - it->first.size() + 2, ' ' ) - << wrapper << "\n"; + << wrapper << '\n'; } - std::cout << std::endl; + Catch::cout() << std::endl; return factories.size(); } inline Option list( Config const& config ) { Option listedCount; - if( config.listTests() ) + if( config.listTests() || ( config.listExtraInfo() && !config.listTestNamesOnly() ) ) listedCount = listedCount.valueOr(0) + listTests( config ); if( config.listTestNamesOnly() ) listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); @@ -4777,144 +5975,577 @@ namespace Catch { } // end namespace Catch -// #included from: internal/catch_runner_impl.hpp +// #included from: internal/catch_run_context.hpp #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED // #included from: catch_test_case_tracker.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED -#include +#include #include #include +#include +#include + +CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS namespace Catch { -namespace SectionTracking { +namespace TestCaseTracking { - class TrackedSection { + struct NameAndLocation { + std::string name; + SourceLineInfo location; - typedef std::map TrackedSections; + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + }; + + struct ITracker : SharedImpl<> { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( Ptr const& child ) = 0; + virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; + }; + + class TrackerContext { - public: enum RunState { NotStarted, Executing, - ExecutingChildren, - Completed + CompletedCycle }; - TrackedSection( std::string const& name, TrackedSection* parent ) - : m_name( name ), m_runState( NotStarted ), m_parent( parent ) + Ptr m_rootTracker; + ITracker* m_currentTracker; + RunState m_runState; + + public: + + static TrackerContext& instance() { + static TrackerContext s_instance; + return s_instance; + } + + TrackerContext() + : m_currentTracker( CATCH_NULL ), + m_runState( NotStarted ) {} - RunState runState() const { return m_runState; } + ITracker& startRun(); - TrackedSection* findChild( std::string const& childName ) { - TrackedSections::iterator it = m_children.find( childName ); - return it != m_children.end() - ? &it->second - : NULL; + void endRun() { + m_rootTracker.reset(); + m_currentTracker = CATCH_NULL; + m_runState = NotStarted; } - TrackedSection* acquireChild( std::string const& childName ) { - if( TrackedSection* child = findChild( childName ) ) - return child; - m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); - return findChild( childName ); + + void startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; } - void enter() { - if( m_runState == NotStarted ) - m_runState = Executing; + void completeCycle() { + m_runState = CompletedCycle; } - void leave() { - for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); - it != itEnd; - ++it ) - if( it->second.runState() != Completed ) { - m_runState = ExecutingChildren; - return; - } - m_runState = Completed; + + bool completedCycle() const { + return m_runState == CompletedCycle; } - TrackedSection* getParent() { - return m_parent; + ITracker& currentTracker() { + return *m_currentTracker; } - bool hasChildren() const { + void setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + class TrackerHasName { + NameAndLocation m_nameAndLocation; + public: + TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} + bool operator ()( Ptr const& tracker ) { + return + tracker->nameAndLocation().name == m_nameAndLocation.name && + tracker->nameAndLocation().location == m_nameAndLocation.location; + } + }; + typedef std::vector > Children; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState; + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ), + m_runState( NotStarted ) + {} + virtual ~TrackerBase(); + + virtual NameAndLocation const& nameAndLocation() const CATCH_OVERRIDE { + return m_nameAndLocation; + } + virtual bool isComplete() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully; + } + virtual bool isOpen() const CATCH_OVERRIDE { + return m_runState != NotStarted && !isComplete(); + } + virtual bool hasChildren() const CATCH_OVERRIDE { return !m_children.empty(); } - private: - std::string m_name; - RunState m_runState; - TrackedSections m_children; - TrackedSection* m_parent; + virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { + m_children.push_back( child ); + } + virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) CATCH_OVERRIDE { + Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); + return( it != m_children.end() ) + ? it->get() + : CATCH_NULL; + } + virtual ITracker& parent() CATCH_OVERRIDE { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + virtual void openChild() CATCH_OVERRIDE { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } + virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } + + void open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + virtual void close() CATCH_OVERRIDE { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NotStarted: + case CompletedSuccessfully: + case Failed: + throw std::logic_error( "Illogical state" ); + + case NeedsAnotherRun: + break;; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + default: + throw std::logic_error( "Unexpected state" ); + } + moveToParent(); + m_ctx.completeCycle(); + } + virtual void fail() CATCH_OVERRIDE { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { + m_runState = NeedsAnotherRun; + } + private: + void moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void moveToThis() { + m_ctx.setCurrentTracker( this ); + } }; - class TestCaseTracker { + class SectionTracker : public TrackerBase { + std::vector m_filters; public: - TestCaseTracker( std::string const& testCaseName ) - : m_testCase( testCaseName, NULL ), - m_currentSection( &m_testCase ), - m_completedASectionThisRun( false ) - {} + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); - bool enterSection( std::string const& name ) { - TrackedSection* child = m_currentSection->acquireChild( name ); - if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) - return false; - - m_currentSection = child; - m_currentSection->enter(); - return true; - } - void leaveSection() { - m_currentSection->leave(); - m_currentSection = m_currentSection->getParent(); - assert( m_currentSection != NULL ); - m_completedASectionThisRun = true; - } - - bool currentSectionHasChildren() const { - return m_currentSection->hasChildren(); - } - bool isCompleted() const { - return m_testCase.runState() == TrackedSection::Completed; - } - - class Guard { - public: - Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { - m_tracker.enterTestCase(); + SectionTracker& parentSection = static_cast( *parent ); + addNextFilters( parentSection.m_filters ); } - ~Guard() { - m_tracker.leaveTestCase(); + } + virtual ~SectionTracker(); + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + SectionTracker* section = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = static_cast( childTracker ); } - private: - Guard( Guard const& ); - void operator = ( Guard const& ); - TestCaseTracker& m_tracker; - }; - - private: - void enterTestCase() { - m_currentSection = &m_testCase; - m_completedASectionThisRun = false; - m_testCase.enter(); - } - void leaveTestCase() { - m_testCase.leave(); + else { + section = new SectionTracker( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; } - TrackedSection m_testCase; - TrackedSection* m_currentSection; - bool m_completedASectionThisRun; + void tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void addInitialFilters( std::vector const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void addNextFilters( std::vector const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } }; -} // namespace SectionTracking + class IndexTracker : public TrackerBase { + int m_size; + int m_index; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ), + m_index( -1 ) + {} + virtual ~IndexTracker(); -using SectionTracking::TestCaseTracker; + virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } + + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + IndexTracker* tracker = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = static_cast( childTracker ); + } + else { + tracker = new IndexTracker( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int index() const { return m_index; } + + void moveNext() { + m_index++; + m_children.clear(); + } + + virtual void close() CATCH_OVERRIDE { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + }; + + inline ITracker& TrackerContext::startRun() { + m_rootTracker = new SectionTracker( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, CATCH_NULL ); + m_currentTracker = CATCH_NULL; + m_runState = Executing; + return *m_rootTracker; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; } // namespace Catch +CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition + inline void reportFatal( std::string const& message ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// +// #included from: catch_windows_h_proxy.h + +#define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED + +#ifdef CATCH_DEFINES_NOMINMAX +# define NOMINMAX +#endif +#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINES_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + + +# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct FatalConditionHandler { + void reset() {} + }; +} + +# else // CATCH_CONFIG_WINDOWS_SEH is defined + +namespace Catch { + + struct SignalDefs { DWORD id; const char* name; }; + extern SignalDefs signalDefs[]; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = CATCH_NULL; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + static void reset() { + if (isSet) { + // Unregister handler and restore the old guarantee + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = CATCH_NULL; + isSet = false; + } + } + + ~FatalConditionHandler() { + reset(); + } + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL; + +} // namespace Catch + +# endif // CATCH_CONFIG_WINDOWS_SEH + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace Catch { + struct FatalConditionHandler { + void reset() {} + }; +} + +# else // CATCH_CONFIG_POSIX_SIGNALS is defined + +#include + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)]; + static stack_t oldSigStack; + static char altStackMem[SIGSTKSZ]; + + static void handleSignal( int sig ) { + std::string name = ""; + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + SignalDefs &def = signalDefs[i]; + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = SIGSTKSZ; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { 0 }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { + reset(); + } + static void reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL); + } + // Return the old stack + sigaltstack(&oldSigStack, CATCH_NULL); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; + +} // namespace Catch + +# endif // CATCH_CONFIG_POSIX_SIGNALS + +#endif // not Windows + #include #include @@ -4952,15 +6583,13 @@ namespace Catch { public: - explicit RunContext( Ptr const& config, Ptr const& reporter ) - : m_runInfo( config->name() ), + explicit RunContext( Ptr const& _config, Ptr const& reporter ) + : m_runInfo( _config->name() ), m_context( getCurrentMutableContext() ), - m_activeTestCase( NULL ), - m_config( config ), + m_activeTestCase( CATCH_NULL ), + m_config( _config ), m_reporter( reporter ), - m_prevRunner( m_context.getRunner() ), - m_prevResultCapture( m_context.getResultCapture() ), - m_prevConfig( m_context.getConfig() ) + m_shouldReportUnexpected ( true ) { m_context.setRunner( this ); m_context.setConfig( m_config ); @@ -4970,10 +6599,6 @@ namespace Catch { virtual ~RunContext() { m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); - m_context.setRunner( m_prevRunner ); - m_context.setConfig( NULL ); - m_context.setResultCapture( m_prevResultCapture ); - m_context.setConfig( m_prevConfig ); } void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { @@ -4994,17 +6619,27 @@ namespace Catch { m_reporter->testCaseStarting( testInfo ); m_activeTestCase = &testCase; - m_testCaseTracker = TestCaseTracker( testInfo.name ); do { + ITracker& rootTracker = m_trackerContext.startRun(); + assert( rootTracker.isSectionTracker() ); + static_cast( rootTracker ).addInitialFilters( m_config->getSectionsToRun() ); do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( testInfo.name, testInfo.lineInfo ) ); runCurrentTest( redirectedCout, redirectedCerr ); } - while( !m_testCaseTracker->isCompleted() && !aborting() ); + while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); } + // !TBD: deprecated - this will be replaced by indexed trackers while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); + if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } m_totals.testCases += deltaTotals.testCases; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, @@ -5012,8 +6647,8 @@ namespace Catch { redirectedCerr, aborting() ) ); - m_activeTestCase = NULL; - m_testCaseTracker.reset(); + m_activeTestCase = CATCH_NULL; + m_testCaseTracker = CATCH_NULL; return deltaTotals; } @@ -5032,8 +6667,9 @@ namespace Catch { m_totals.assertions.failed++; } - if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) - m_messages.clear(); + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); // Reset working state m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); @@ -5045,11 +6681,10 @@ namespace Catch { Counts& assertions ) { - std::ostringstream oss; - oss << sectionInfo.name << "@" << sectionInfo.lineInfo; - - if( !m_testCaseTracker->enterSection( oss.str() ) ) + ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( sectionInfo.name, sectionInfo.lineInfo ) ); + if( !sectionTracker.isOpen() ) return false; + m_activeSections.push_back( §ionTracker ); m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; @@ -5060,30 +6695,40 @@ namespace Catch { return true; } bool testForMissingAssertions( Counts& assertions ) { - if( assertions.total() != 0 || - !m_config->warnAboutMissingAssertions() || - m_testCaseTracker->currentSectionHasChildren() ) + if( assertions.total() != 0 ) + return false; + if( !m_config->warnAboutMissingAssertions() ) + return false; + if( m_trackerContext.currentTracker().hasChildren() ) return false; m_totals.assertions.failed++; assertions.failed++; return true; } - virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { - if( std::uncaught_exception() ) { - m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); - return; - } - - Counts assertions = m_totals.assertions - prevAssertions; + virtual void sectionEnded( SectionEndInfo const& endInfo ) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); - m_testCaseTracker->leaveSection(); + if( !m_activeSections.empty() ) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } - m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); m_messages.clear(); } + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { + if( m_unfinishedSections.empty() ) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back( endInfo ); + } + virtual void pushScopedMessage( MessageInfo const& message ) { m_messages.push_back( message ); } @@ -5095,13 +6740,52 @@ namespace Catch { virtual std::string getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name - : ""; + : std::string(); } virtual const AssertionResult* getLastResult() const { return &m_lastResult; } + virtual void exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult; + tempResult.resultType = ResultWas::FatalErrorCondition; + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + getResultCapture().assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + std::string(), + std::string(), + false ) ); + m_totals.testCases.failed++; + testGroupEnded( std::string(), m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + public: // !TBD We need to do this another way! bool aborting() const { @@ -5116,19 +6800,21 @@ namespace Catch { m_reporter->sectionStarting( testCaseSection ); Counts prevAssertions = m_totals.assertions; double duration = 0; + m_shouldReportUnexpected = true; try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); - TestCaseTracker::Guard guard( *m_testCaseTracker ); + + seedRng( *m_config ); Timer timer; timer.start(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { - StreamRedirect coutRedir( std::cout, redirectedCout ); - StreamRedirect cerrRedir( std::cerr, redirectedCerr ); - m_activeTestCase->invoke(); + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); } else { - m_activeTestCase->invoke(); + invokeActiveTestCase(); } duration = timer.getElapsedSeconds(); } @@ -5136,20 +6822,14 @@ namespace Catch { // This just means the test was aborted due to failure } catch(...) { - ResultBuilder exResult( m_lastAssertionInfo.macroName.c_str(), - m_lastAssertionInfo.lineInfo, - m_lastAssertionInfo.capturedExpression.c_str(), - m_lastAssertionInfo.resultDisposition ); - exResult.useActiveException(); + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if (m_shouldReportUnexpected) { + makeUnexpectedResultBuilder().useActiveException(); + } } - // If sections ended prematurely due to an exception we stored their - // infos here so we can tear them down outside the unwind process. - for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), - itEnd = m_unfinishedSections.rend(); - it != itEnd; - ++it ) - sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); - m_unfinishedSections.clear(); + m_testCaseTracker->close(); + handleUnfinishedSections(); m_messages.clear(); Counts assertions = m_totals.assertions - prevAssertions; @@ -5165,32 +6845,48 @@ namespace Catch { m_reporter->sectionEnded( testCaseSectionStats ); } - private: - struct UnfinishedSections { - UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) - : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) - {} + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } - SectionInfo info; - Counts prevAssertions; - double durationInSeconds; - }; + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName, + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression, + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( *it ); + m_unfinishedSections.clear(); + } TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase; - Option m_testCaseTracker; + ITracker* m_testCaseTracker; + ITracker* m_currentSectionTracker; AssertionResult m_lastResult; Ptr m_config; Totals m_totals; Ptr m_reporter; std::vector m_messages; - IRunner* m_prevRunner; - IResultCapture* m_prevResultCapture; - Ptr m_prevConfig; AssertionInfo m_lastAssertionInfo; - std::vector m_unfinishedSections; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; + bool m_shouldReportUnexpected; }; IResultCapture& getResultCapture() { @@ -5211,24 +6907,25 @@ namespace Catch { struct Version { Version( unsigned int _majorVersion, unsigned int _minorVersion, - unsigned int _buildNumber, - char const* const _branchName ) - : majorVersion( _majorVersion ), - minorVersion( _minorVersion ), - buildNumber( _buildNumber ), - branchName( _branchName ) - {} + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); unsigned int const majorVersion; unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; unsigned int const buildNumber; - char const* const branchName; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); private: void operator=( Version const& ); }; - extern Version libraryVersion; + inline Version libraryVersion(); } #include @@ -5237,83 +6934,89 @@ namespace Catch { namespace Catch { - class Runner { + Ptr createReporter( std::string const& reporterName, Ptr const& config ) { + Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); + if( !reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + return reporter; + } - public: - Runner( Ptr const& config ) - : m_config( config ) - { - openStream(); - makeReporter(); + Ptr makeReporter( Ptr const& config ) { + std::vector reporters = config->getReporterNames(); + if( reporters.empty() ) + reporters.push_back( "console" ); + + Ptr reporter; + for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); + it != itEnd; + ++it ) + reporter = addReporter( reporter, createReporter( *it, config ) ); + return reporter; + } + Ptr addListeners( Ptr const& config, Ptr reporters ) { + IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); + for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); + it != itEnd; + ++it ) + reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); + return reporters; + } + + Totals runTests( Ptr const& config ) { + + Ptr iconfig = config.get(); + + Ptr reporter = makeReporter( config ); + reporter = addListeners( iconfig, reporter ); + + RunContext context( iconfig, reporter ); + + Totals totals; + + context.testGroupStarting( config->name(), 1, 1 ); + + TestSpec testSpec = config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector const& allTestCases = getAllTestCasesSorted( *iconfig ); + for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); + it != itEnd; + ++it ) { + if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) + totals += context.runTest( *it ); + else + reporter->skipTest( *it ); } - Totals runTests() { + context.testGroupEnded( iconfig->name(), totals, 1, 1 ); + return totals; + } - RunContext context( m_config.get(), m_reporter ); + void applyFilenamesAsTags( IConfig const& config ) { + std::vector const& tests = getAllTestCasesSorted( config ); + for(std::size_t i = 0; i < tests.size(); ++i ) { + TestCase& test = const_cast( tests[i] ); + std::set tags = test.tags; - Totals totals; + std::string filename = test.lineInfo.file; + std::string::size_type lastSlash = filename.find_last_of( "\\/" ); + if( lastSlash != std::string::npos ) + filename = filename.substr( lastSlash+1 ); - context.testGroupStarting( "", 1, 1 ); // deprecated? + std::string::size_type lastDot = filename.find_last_of( "." ); + if( lastDot != std::string::npos ) + filename = filename.substr( 0, lastDot ); - TestSpec testSpec = m_config->testSpec(); - if( !testSpec.hasFilters() ) - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests - - std::vector testCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); - - int testsRunForGroup = 0; - for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); - it != itEnd; - ++it ) { - testsRunForGroup++; - if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { - - if( context.aborting() ) - break; - - totals += context.runTest( *it ); - m_testsAlreadyRun.insert( *it ); - } - } - context.testGroupEnded( "", totals, 1, 1 ); - return totals; + tags.insert( "#" + filename ); + setTags( test, tags ); } + } - private: - void openStream() { - // Open output file, if specified - if( !m_config->getFilename().empty() ) { - m_ofs.open( m_config->getFilename().c_str() ); - if( m_ofs.fail() ) { - std::ostringstream oss; - oss << "Unable to open file: '" << m_config->getFilename() << "'"; - throw std::domain_error( oss.str() ); - } - m_config->setStreamBuf( m_ofs.rdbuf() ); - } - } - void makeReporter() { - std::string reporterName = m_config->getReporterName().empty() - ? "console" - : m_config->getReporterName(); - - m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); - if( !m_reporter ) { - std::ostringstream oss; - oss << "No reporter registered with name: '" << reporterName << "'"; - throw std::domain_error( oss.str() ); - } - } - - private: - Ptr m_config; - std::ofstream m_ofs; - Ptr m_reporter; - std::set m_testsAlreadyRun; - }; - - class Session { + class Session : NonCopyable { static bool alreadyInstantiated; public: @@ -5324,7 +7027,7 @@ namespace Catch { : m_cli( makeCommandLineParser() ) { if( alreadyInstantiated ) { std::string msg = "Only one instance of Catch::Session can ever be used"; - std::cerr << msg << std::endl; + Catch::cerr() << msg << std::endl; throw std::logic_error( msg ); } alreadyInstantiated = true; @@ -5334,21 +7037,16 @@ namespace Catch { } void showHelp( std::string const& processName ) { - std::cout << "\nCatch v" << libraryVersion.majorVersion << "." - << libraryVersion.minorVersion << " build " - << libraryVersion.buildNumber; - if( libraryVersion.branchName != std::string( "master" ) ) - std::cout << " (" << libraryVersion.branchName << " branch)"; - std::cout << "\n"; + Catch::cout() << "\nCatch v" << libraryVersion() << "\n"; - m_cli.usage( std::cout, processName ); - std::cout << "For more detail usage please see the project docs\n" << std::endl; + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } - int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { try { m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); - m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); if( m_configData.showHelp ) showHelp( m_configData.processName ); m_config.reset(); @@ -5356,11 +7054,12 @@ namespace Catch { catch( std::exception& ex ) { { Colour colourGuard( Colour::Red ); - std::cerr << "\nError(s) in input:\n" - << Text( ex.what(), TextAttributes().setIndent(2) ) - << "\n\n"; + Catch::cerr() + << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; } - m_cli.usage( std::cout, m_configData.processName ); + m_cli.usage( Catch::cout(), m_configData.processName ); return (std::numeric_limits::max)(); } return 0; @@ -5371,7 +7070,7 @@ namespace Catch { m_config.reset(); } - int run( int argc, char* const argv[] ) { + int run( int argc, char const* const* const argv ) { int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) @@ -5379,6 +7078,32 @@ namespace Catch { return returnCode; } + #if defined(WIN32) && defined(UNICODE) + int run( int argc, wchar_t const* const* const argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + if( returnCode == 0 ) + returnCode = run(); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } + #endif + int run() { if( m_configData.showHelp ) return 0; @@ -5386,16 +7111,20 @@ namespace Catch { try { config(); // Force config to be constructed - Runner runner( m_config ); + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); // Handle list request if( Option listed = list( config() ) ) return static_cast( *listed ); - return static_cast( runner.runTests().assertions.failed ); + return static_cast( runTests( m_config ).assertions.failed ); } catch( std::exception& ex ) { - std::cerr << ex.what() << std::endl; + Catch::cerr() << ex.what() << std::endl; return (std::numeric_limits::max)(); } } @@ -5414,7 +7143,6 @@ namespace Catch { m_config = new Config( m_configData ); return *m_config; } - private: Clara::CommandLine m_cli; std::vector m_unusedTokens; @@ -5435,65 +7163,126 @@ namespace Catch { #include #include #include -#include +#include namespace Catch { + struct RandomNumberGenerator { + typedef std::ptrdiff_t result_type; + + result_type operator()( result_type n ) const { return std::rand() % n; } + +#ifdef CATCH_CONFIG_CPP11_SHUFFLE + static constexpr result_type min() { return 0; } + static constexpr result_type max() { return 1000000; } + result_type operator()() const { return std::rand() % max(); } +#endif + template + static void shuffle( V& vector ) { + RandomNumberGenerator rng; +#ifdef CATCH_CONFIG_CPP11_SHUFFLE + std::shuffle( vector.begin(), vector.end(), rng ); +#else + std::random_shuffle( vector.begin(), vector.end(), rng ); +#endif + } + }; + + inline std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + { + seedRng( config ); + RandomNumberGenerator::shuffle( sorted ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); + it != itEnd; + ++it ) { + std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); + if( !prev.second ) { + std::ostringstream ss; + + ss << Colour( Colour::Red ) + << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << '\n' + << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + + throw std::runtime_error(ss.str()); + } + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) + if( matchTest( *it, testSpec, config ) ) + filtered.push_back( *it ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + class TestRegistry : public ITestCaseRegistry { public: - TestRegistry() : m_unnamedCount( 0 ) {} + TestRegistry() + : m_currentSortOrder( RunTests::InDeclarationOrder ), + m_unnamedCount( 0 ) + {} virtual ~TestRegistry(); virtual void registerTest( TestCase const& testCase ) { std::string name = testCase.getTestCaseInfo().name; - if( name == "" ) { + if( name.empty() ) { std::ostringstream oss; oss << "Anonymous test case " << ++m_unnamedCount; return registerTest( testCase.withName( oss.str() ) ); } - - if( m_functions.find( testCase ) == m_functions.end() ) { - m_functions.insert( testCase ); - m_functionsInOrder.push_back( testCase ); - if( !testCase.isHidden() ) - m_nonHiddenFunctions.push_back( testCase ); - } - else { - TestCase const& prev = *m_functions.find( testCase ); - { - Colour colourGuard( Colour::Red ); - std::cerr << "error: TEST_CASE( \"" << name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << testCase.getTestCaseInfo().lineInfo << std::endl; - } - exit(1); - } + m_functions.push_back( testCase ); } virtual std::vector const& getAllTests() const { - return m_functionsInOrder; + return m_functions; } + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); - virtual std::vector const& getAllNonHiddenTests() const { - return m_nonHiddenFunctions; - } - - virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases ) const { - for( std::vector::const_iterator it = m_functionsInOrder.begin(), - itEnd = m_functionsInOrder.end(); - it != itEnd; - ++it ) { - if( testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ) ) - matchingTestCases.push_back( *it ); + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); } + return m_sortedFunctions; } private: - - std::set m_functions; - std::vector m_functionsInOrder; - std::vector m_nonHiddenFunctions; + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder; + mutable std::vector m_sortedFunctions; size_t m_unnamedCount; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised }; /////////////////////////////////////////////////////////////////////////// @@ -5515,7 +7304,7 @@ namespace Catch { inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { std::string className = classOrQualifiedMethodName; - if( startsWith( className, "&" ) ) + if( startsWith( className, '&' ) ) { std::size_t lastColons = className.rfind( "::" ); std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); @@ -5526,29 +7315,38 @@ namespace Catch { return className; } - /////////////////////////////////////////////////////////////////////////// + void registerTestCase + ( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { - AutoReg::AutoReg( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ) { + getMutableRegistryHub().registerTest + ( makeTestCase + ( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); } - AutoReg::~AutoReg() {} + /////////////////////////////////////////////////////////////////////////// - void AutoReg::registerTestCase( ITestCase* testCase, - char const* classOrQualifiedMethodName, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ) { - - getMutableRegistryHub().registerTest - ( makeTestCase( testCase, - extractClassName( classOrQualifiedMethodName ), - nameAndDesc.name, - nameAndDesc.description, - lineInfo ) ); + AutoReg::AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCaseFunction( function, lineInfo, nameAndDesc ); } + AutoReg::~AutoReg() {} + } // end namespace Catch // #included from: catch_reporter_registry.hpp @@ -5562,27 +7360,32 @@ namespace Catch { public: - virtual ~ReporterRegistry() { - deleteAllValues( m_factories ); - } + virtual ~ReporterRegistry() CATCH_OVERRIDE {} - virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { FactoryMap::const_iterator it = m_factories.find( name ); if( it == m_factories.end() ) - return NULL; + return CATCH_NULL; return it->second->create( ReporterConfig( config ) ); } - void registerReporter( std::string const& name, IReporterFactory* factory ) { + void registerReporter( std::string const& name, Ptr const& factory ) { m_factories.insert( std::make_pair( name, factory ) ); } + void registerListener( Ptr const& factory ) { + m_listeners.push_back( factory ); + } - FactoryMap const& getFactories() const { + virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { return m_factories; } + virtual Listeners const& getListeners() const CATCH_OVERRIDE { + return m_listeners; + } private: FactoryMap m_factories; + Listeners m_listeners; }; } @@ -5610,13 +7413,13 @@ namespace Catch { #ifdef __OBJC__ // In Objective-C try objective-c exceptions first @try { - throw; + return tryTranslators(); } @catch (NSException *exception) { - return toString( [exception description] ); + return Catch::toString( [exception description] ); } #else - throw; + return tryTranslators(); #endif } catch( TestFailureException& ) { @@ -5632,20 +7435,15 @@ namespace Catch { return msg; } catch(...) { - return tryTranslators( m_translators.begin() ); + return "Unknown exception"; } } - std::string tryTranslators( std::vector::const_iterator it ) const { - if( it == m_translators.end() ) - return "Unknown exception"; - - try { - return (*it)->translate(); - } - catch(...) { - return tryTranslators( it+1 ); - } + std::string tryTranslators() const { + if( m_translators.empty() ) + throw; + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); } private: @@ -5653,6 +7451,26 @@ namespace Catch { }; } +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map m_registry; + }; + +} // end namespace Catch + namespace Catch { namespace { @@ -5665,36 +7483,46 @@ namespace Catch { public: // IRegistryHub RegistryHub() { } - virtual IReporterRegistry const& getReporterRegistry() const { + virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { return m_reporterRegistry; } - virtual ITestCaseRegistry const& getTestCaseRegistry() const { + virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { return m_testCaseRegistry; } - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { return m_exceptionTranslatorRegistry; } + virtual ITagAliasRegistry const& getTagAliasRegistry() const CATCH_OVERRIDE { + return m_tagAliasRegistry; + } public: // IMutableRegistryHub - virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerReporter( name, factory ); } - virtual void registerTest( TestCase const& testInfo ) { + virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerListener( factory ); + } + virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { m_testCaseRegistry.registerTest( testInfo ); } - virtual void registerTranslator( const IExceptionTranslator* translator ) { + virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { m_exceptionTranslatorRegistry.registerTranslator( translator ); } + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) CATCH_OVERRIDE { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } private: TestRegistry m_testCaseRegistry; ReporterRegistry m_reporterRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; }; // Single, global, instance inline RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = NULL; + static RegistryHub* theRegistryHub = CATCH_NULL; if( !theRegistryHub ) theRegistryHub = new RegistryHub(); return theRegistryHub; @@ -5709,7 +7537,7 @@ namespace Catch { } void cleanUp() { delete getTheRegistryHub(); - getTheRegistryHub() = NULL; + getTheRegistryHub() = CATCH_NULL; cleanUpContext(); } std::string translateActiveException() { @@ -5721,7 +7549,7 @@ namespace Catch { // #included from: catch_notimplemented_exception.hpp #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED -#include +#include namespace Catch { @@ -5745,21 +7573,9 @@ namespace Catch { // #included from: catch_stream.hpp #define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED -// #included from: catch_streambuf.h -#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED - -#include - -namespace Catch { - - class StreamBufBase : public std::streambuf { - public: - virtual ~StreamBufBase() CATCH_NOEXCEPT; - }; -} - #include #include +#include namespace Catch { @@ -5801,6 +7617,19 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////// + FileStream::FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << filename << '\''; + throw std::domain_error( oss.str() ); + } + } + + std::ostream& FileStream::stream() const { + return m_ofs; + } + struct OutputDebugWriter { void operator()( std::string const&str ) { @@ -5808,31 +7637,48 @@ namespace Catch { } }; - Stream::Stream() - : streamBuf( NULL ), isOwned( false ) + DebugOutStream::DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) {} - Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) - : streamBuf( _streamBuf ), isOwned( _isOwned ) - {} - - void Stream::release() { - if( isOwned ) { - delete streamBuf; - streamBuf = NULL; - isOwned = false; - } + std::ostream& DebugOutStream::stream() const { + return m_os; } + + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream::CoutStream() + : m_os( Catch::cout().rdbuf() ) + {} + + std::ostream& CoutStream::stream() const { + return m_os; + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif } namespace Catch { class Context : public IMutableContext { - Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} Context( Context const& ); void operator=( Context const& ); + public: + virtual ~Context() { + deleteAllValues( m_generatorsByTestName ); + } + public: // IContext virtual IResultCapture* getResultCapture() { return m_resultCapture; @@ -5872,10 +7718,10 @@ namespace Catch { std::string testName = getResultCapture()->getCurrentTestName(); std::map::const_iterator it = - m_generatorsByTestName.find( testName ); + m_generatorsByTestName.find( testName ); return it != m_generatorsByTestName.end() ? it->second - : NULL; + : CATCH_NULL; } IGeneratorsForTest& getGeneratorsForCurrentTest() { @@ -5896,7 +7742,7 @@ namespace Catch { }; namespace { - Context* currentContext = NULL; + Context* currentContext = CATCH_NULL; } IMutableContext& getCurrentMutableContext() { if( !currentContext ) @@ -5907,57 +7753,78 @@ namespace Catch { return getCurrentMutableContext(); } - Stream createStream( std::string const& streamName ) { - if( streamName == "stdout" ) return Stream( std::cout.rdbuf(), false ); - if( streamName == "stderr" ) return Stream( std::cerr.rdbuf(), false ); - if( streamName == "debug" ) return Stream( new StreamBufImpl, true ); - - throw std::domain_error( "Unknown stream: " + streamName ); - } - void cleanUpContext() { delete currentContext; - currentContext = NULL; + currentContext = CATCH_NULL; } } // #included from: catch_console_colour_impl.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED -namespace Catch { namespace Detail { - struct IColourImpl { - virtual ~IColourImpl() {} - virtual void use( Colour::Code _colourCode ) = 0; +// #included from: catch_errno_guard.hpp +#define TWOBLUECUBES_CATCH_ERRNO_GUARD_HPP_INCLUDED + +#include + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard():m_oldErrno(errno){} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; }; -}} -#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// +} -#ifndef NOMINMAX -#define NOMINMAX +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif #endif -#ifdef __AFXDLL -#include -#else -#include -#endif +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// namespace Catch { namespace { - class Win32ColourImpl : public Detail::IColourImpl { + class Win32ColourImpl : public IColourImpl { public: Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) { CONSOLE_SCREEN_BUFFER_INFO csbiInfo; GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); - originalAttributes = csbiInfo.wAttributes; + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); } virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { - case Colour::None: return setTextAttribute( originalAttributes ); + case Colour::None: return setTextAttribute( originalForegroundAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Red: return setTextAttribute( FOREGROUND_RED ); case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); @@ -5977,25 +7844,33 @@ namespace { private: void setTextAttribute( WORD _textAttribute ) { - SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); } HANDLE stdoutHandle; - WORD originalAttributes; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; }; - inline bool shouldUseColourForPlatform() { - return true; - } - - static Detail::IColourImpl* platformColourInstance() { + IColourImpl* platformColourInstance() { static Win32ColourImpl s_instance; - return &s_instance; + + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = !isDebuggerActive() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch -#else // Not Windows - assumed to be POSIX compatible ////////////////////////// +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// #include @@ -6006,7 +7881,7 @@ namespace { // Thanks to Adam Strzelecki for original contribution // (http://github.com/nanoant) // https://github.com/philsquared/Catch/pull/131 - class PosixColourImpl : public Detail::IColourImpl { + class PosixColourImpl : public IColourImpl { public: virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { @@ -6014,7 +7889,7 @@ namespace { case Colour::White: return setColour( "[0m" ); case Colour::Red: return setColour( "[0;31m" ); case Colour::Green: return setColour( "[0;32m" ); - case Colour::Blue: return setColour( "[0:34m" ); + case Colour::Blue: return setColour( "[0;34m" ); case Colour::Cyan: return setColour( "[0;36m" ); case Colour::Yellow: return setColour( "[0;33m" ); case Colour::Grey: return setColour( "[1;30m" ); @@ -6027,53 +7902,54 @@ namespace { case Colour::Bright: throw std::logic_error( "not a colour" ); } } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + private: void setColour( const char* _escapeCode ) { - std::cout << '\033' << _escapeCode; + Catch::cout() << '\033' << _escapeCode; } }; - inline bool shouldUseColourForPlatform() { - return isatty(STDOUT_FILENO); - } - - static Detail::IColourImpl* platformColourInstance() { - static PosixColourImpl s_instance; - return &s_instance; + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + Ptr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch -#endif // not Windows +#else // not Windows or ANSI /////////////////////////////////////////////// namespace Catch { - namespace { - struct NoColourImpl : Detail::IColourImpl { - void use( Colour::Code ) {} + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } - static IColourImpl* instance() { - static NoColourImpl s_instance; - return &s_instance; - } - }; - static bool shouldUseColour() { - return shouldUseColourForPlatform() && !isDebuggerActive(); - } - } +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } Colour::~Colour(){ if( !m_moved ) use( None ); } - void Colour::use( Code _colourCode ) { - impl()->use( _colourCode ); - } - Detail::IColourImpl* Colour::impl() { - return shouldUseColour() - ? platformColourInstance() - : NoColourImpl::instance(); + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); } } // end namespace Catch @@ -6157,14 +8033,16 @@ namespace Catch { namespace Catch { - AssertionInfo::AssertionInfo( std::string const& _macroName, + AssertionInfo::AssertionInfo( char const * _macroName, SourceLineInfo const& _lineInfo, - std::string const& _capturedExpression, - ResultDisposition::Flags _resultDisposition ) + char const * _capturedExpression, + ResultDisposition::Flags _resultDisposition, + char const * _secondArg) : macroName( _macroName ), lineInfo( _lineInfo ), capturedExpression( _capturedExpression ), - resultDisposition( _resultDisposition ) + resultDisposition( _resultDisposition ), + secondArg( _secondArg ) {} AssertionResult::AssertionResult() {} @@ -6191,24 +8069,30 @@ namespace Catch { } bool AssertionResult::hasExpression() const { - return !m_info.capturedExpression.empty(); + return m_info.capturedExpression[0] != 0; } bool AssertionResult::hasMessage() const { return !m_resultData.message.empty(); } + std::string capturedExpressionWithSecondArgument( char const * capturedExpression, char const * secondArg ) { + return (secondArg[0] == 0 || secondArg[0] == '"' && secondArg[1] == '"') + ? capturedExpression + : std::string(capturedExpression) + ", " + secondArg; + } + std::string AssertionResult::getExpression() const { if( isFalseTest( m_info.resultDisposition ) ) - return "!" + m_info.capturedExpression; + return '!' + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); else - return m_info.capturedExpression; + return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); } std::string AssertionResult::getExpressionInMacro() const { - if( m_info.macroName.empty() ) - return m_info.capturedExpression; + if( m_info.macroName[0] == 0 ) + return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); else - return m_info.macroName + "( " + m_info.capturedExpression + " )"; + return std::string(m_info.macroName) + "( " + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg) + " )"; } bool AssertionResult::hasExpandedExpression() const { @@ -6216,7 +8100,7 @@ namespace Catch { } std::string AssertionResult::getExpandedExpression() const { - return m_resultData.reconstructedExpression; + return m_resultData.reconstructExpression(); } std::string AssertionResult::getMessage() const { @@ -6230,15 +8114,25 @@ namespace Catch { return m_info.macroName; } + void AssertionResult::discardDecomposedExpression() const { + m_resultData.decomposedExpression = CATCH_NULL; + } + + void AssertionResult::expandDecomposedExpression() const { + m_resultData.reconstructExpression(); + } + } // end namespace Catch // #included from: catch_test_case_info.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED +#include + namespace Catch { inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { - if( tag == "." || + if( startsWith( tag, '.' ) || tag == "hide" || tag == "!hide" ) return TestCaseInfo::IsHidden; @@ -6248,25 +8142,23 @@ namespace Catch { return TestCaseInfo::ShouldFail; else if( tag == "!mayfail" ) return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; else return TestCaseInfo::None; } inline bool isReservedTag( std::string const& tag ) { - return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); } inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { if( isReservedTag( tag ) ) { - { - Colour colourGuard( Colour::Red ); - std::cerr - << "Tag name [" << tag << "] not allowed.\n" - << "Tag names starting with non alpha-numeric characters are reserved\n"; - } - { - Colour colourGuard( Colour::FileName ); - std::cerr << _lineInfo << std::endl; - } - exit(1); + std::ostringstream ss; + ss << Colour(Colour::Red) + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << Colour(Colour::FileName) + << _lineInfo << '\n'; + throw std::runtime_error(ss.str()); } } @@ -6292,14 +8184,15 @@ namespace Catch { } else { if( c == ']' ) { - enforceNotReservedTag( tag, _lineInfo ); - - inTag = false; - if( tag == "hide" || tag == "." ) + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) isHidden = true; - else - tags.insert( tag ); + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); tag.clear(); + inTag = false; } else tag += c; @@ -6314,6 +8207,21 @@ namespace Catch { return TestCase( _testCase, info ); } + void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) + { + testCaseInfo.tags = tags; + testCaseInfo.lcaseTags.clear(); + + std::ostringstream oss; + for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { + oss << '[' << *it << ']'; + std::string lcaseTag = toLower( *it ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.insert( lcaseTag ); + } + testCaseInfo.tagsAsString = oss.str(); + } + TestCaseInfo::TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, @@ -6322,18 +8230,10 @@ namespace Catch { : name( _name ), className( _className ), description( _description ), - tags( _tags ), lineInfo( _lineInfo ), properties( None ) { - std::ostringstream oss; - for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { - oss << "[" << *it << "]"; - std::string lcaseTag = toLower( *it ); - properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); - lcaseTags.insert( lcaseTag ); - } - tagsAsString = oss.str(); + setTags( *this, _tags ); } TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) @@ -6416,8 +8316,36 @@ namespace Catch { namespace Catch { - // These numbers are maintained by a script - Version libraryVersion( 1, 0, 53, "master" ); + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + inline Version libraryVersion() { + static Version version( 1, 9, 6, "", 0 ); + return version; + } + } // #included from: catch_message.hpp @@ -6450,7 +8378,9 @@ namespace Catch { {} ScopedMessage::~ScopedMessage() { - getResultCapture().popScopedMessage( m_info ); + if ( !std::uncaught_exception() ){ + getResultCapture().popScopedMessage(m_info); + } } } // end namespace Catch @@ -6501,6 +8431,7 @@ namespace Catch virtual void testCaseEnded( TestCaseStats const& testCaseStats ); virtual void testGroupEnded( TestGroupStats const& testGroupStats ); virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); private: Ptr m_legacyReporter; @@ -6574,6 +8505,8 @@ namespace Catch void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { m_legacyReporter->EndTesting( testRunStats.totals ); } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } } // #included from: catch_timer.hpp @@ -6584,30 +8517,32 @@ namespace Catch #endif #ifdef CATCH_PLATFORM_WINDOWS -#include + #else + #include + #endif namespace Catch { namespace { #ifdef CATCH_PLATFORM_WINDOWS - uint64_t getCurrentTicks() { - static uint64_t hz=0, hzo=0; + UInt64 getCurrentTicks() { + static UInt64 hz=0, hzo=0; if (!hz) { - QueryPerformanceFrequency((LARGE_INTEGER*)&hz); - QueryPerformanceCounter((LARGE_INTEGER*)&hzo); + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); } - uint64_t t; - QueryPerformanceCounter((LARGE_INTEGER*)&t); + UInt64 t; + QueryPerformanceCounter( reinterpret_cast( &t ) ); return ((t-hzo)*1000000)/hz; } #else - uint64_t getCurrentTicks() { + UInt64 getCurrentTicks() { timeval t; - gettimeofday(&t,NULL); - return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + gettimeofday(&t,CATCH_NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif } @@ -6615,14 +8550,14 @@ namespace Catch { void Timer::start() { m_ticks = getCurrentTicks(); } - unsigned int Timer::getElapsedNanoseconds() const { + unsigned int Timer::getElapsedMicroseconds() const { return static_cast(getCurrentTicks() - m_ticks); } unsigned int Timer::getElapsedMilliseconds() const { - return static_cast((getCurrentTicks() - m_ticks)/1000); + return static_cast(getElapsedMicroseconds()/1000); } double Timer::getElapsedSeconds() const { - return (getCurrentTicks() - m_ticks)/1000000.0; + return getElapsedMicroseconds()/1000000.0; } } // namespace Catch @@ -6633,19 +8568,31 @@ namespace Catch { // #included from: catch_common.hpp #define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED +#include +#include + namespace Catch { bool startsWith( std::string const& s, std::string const& prefix ) { - return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; } bool endsWith( std::string const& s, std::string const& suffix ) { - return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; } bool contains( std::string const& s, std::string const& infix ) { return s.find( infix ) != std::string::npos; } + char toLowerCh(char c) { + return static_cast( std::tolower( c ) ); + } void toLowerInPlace( std::string& s ) { - std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); } std::string toLower( std::string const& s ) { std::string lc = s; @@ -6657,7 +8604,21 @@ namespace Catch { std::string::size_type start = str.find_first_not_of( whitespaceChars ); std::string::size_type end = str.find_last_not_of( whitespaceChars ); - return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; } pluralise::pluralise( std::size_t count, std::string const& label ) @@ -6666,40 +8627,47 @@ namespace Catch { {} std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { - os << pluraliser.m_count << " " << pluraliser.m_label; + os << pluraliser.m_count << ' ' << pluraliser.m_label; if( pluraliser.m_count != 1 ) - os << "s"; + os << 's'; return os; } - SourceLineInfo::SourceLineInfo() : line( 0 ){} + SourceLineInfo::SourceLineInfo() : file(""), line( 0 ){} SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) : file( _file ), line( _line ) {} - SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) - : file( other.file ), - line( other.line ) - {} bool SourceLineInfo::empty() const { - return file.empty(); + return file[0] == '\0'; } bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { - return line == other.line && file == other.file; + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) + std::srand( config.rngSeed() ); + } + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); } std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { #ifndef __GNUG__ - os << info.file << "(" << info.line << ")"; + os << info.file << '(' << info.line << ')'; #else - os << info.file << ":" << info.line; + os << info.file << ':' << info.line; #endif return os; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { std::ostringstream oss; - oss << locationInfo << ": Internal Catch error: '" << message << "'"; + oss << locationInfo << ": Internal Catch error: '" << message << '\''; if( alwaysTrue() ) throw std::logic_error( oss.str() ); } @@ -6726,10 +8694,22 @@ namespace Catch { m_timer.start(); } +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17 +#endif Section::~Section() { - if( m_sectionIncluded ) - getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( m_sectionIncluded ) { + SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( std::uncaught_exception() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } } +#if defined(_MSC_VER) +#pragma warning(pop) +#endif // This indicates whether the section should be executed or not Section::operator bool() const { @@ -6741,8 +8721,6 @@ namespace Catch { // #included from: catch_debugger.hpp #define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED -#include - #ifdef CATCH_PLATFORM_MAC #include @@ -6780,8 +8758,8 @@ namespace Catch { // Call sysctl. size = sizeof(info); - if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { - std::cerr << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; return false; } @@ -6791,6 +8769,36 @@ namespace Catch { } } // namespace Catch +#elif defined(CATCH_PLATFORM_LINUX) + #include + #include + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch #elif defined(_MSC_VER) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { @@ -6812,7 +8820,7 @@ namespace Catch { #endif // Platform #ifdef CATCH_PLATFORM_WINDOWS - extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); + namespace Catch { void writeToDebugConsole( std::string const& text ) { ::OutputDebugStringA( text.c_str() ); @@ -6822,7 +8830,7 @@ namespace Catch { namespace Catch { void writeToDebugConsole( std::string const& text ) { // !TBD: Need a version for Mac/ XCode and other IDEs - std::cout << text; + Catch::cout() << text; } } #endif // Platform @@ -6834,7 +8842,11 @@ namespace Catch { namespace Detail { + const std::string unprintableString = "{?}"; + namespace { + const int hexThreshold = 255; + struct Endianness { enum Arch { Big, Little }; @@ -6884,7 +8896,7 @@ std::string toString( std::string const& value ) { } } } - return "\"" + s + "\""; + return '"' + s + '"'; } std::string toString( std::wstring const& value ) { @@ -6892,7 +8904,7 @@ std::string toString( std::wstring const& value ) { s.reserve( value.size() ); for(size_t i = 0; i < value.size(); ++i ) s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; - return toString( s ); + return Catch::toString( s ); } std::string toString( const char* const value ) { @@ -6905,31 +8917,32 @@ std::string toString( char* const value ) { std::string toString( const wchar_t* const value ) { - return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); } std::string toString( wchar_t* const value ) { - return Catch::toString( static_cast( value ) ); + return Catch::toString( static_cast( value ) ); } std::string toString( int value ) { std::ostringstream oss; oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; return oss.str(); } std::string toString( unsigned long value ) { std::ostringstream oss; - if( value > 8192 ) - oss << "0x" << std::hex << value; - else - oss << value; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; return oss.str(); } std::string toString( unsigned int value ) { - return toString( static_cast( value ) ); + return Catch::toString( static_cast( value ) ); } template @@ -6952,7 +8965,7 @@ std::string toString( const double value ) { return fpToString( value, 10 ); } std::string toString( const float value ) { - return fpToString( value, 5 ) + "f"; + return fpToString( value, 5 ) + 'f'; } std::string toString( bool value ) { @@ -6960,9 +8973,19 @@ std::string toString( bool value ) { } std::string toString( char value ) { - return value < ' ' - ? toString( static_cast( value ) ) - : Detail::makeString( value ); + if ( value == '\r' ) + return "'\\r'"; + if ( value == '\f' ) + return "'\\f'"; + if ( value == '\n' ) + return "'\\n'"; + if ( value == '\t' ) + return "'\\t'"; + if ( '\0' <= value && value < ' ' ) + return toString( static_cast( value ) ); + char chstr[] = "' '"; + chstr[1] = value; + return chstr; } std::string toString( signed char value ) { @@ -6973,6 +8996,23 @@ std::string toString( unsigned char value ) { return toString( static_cast( value ) ); } +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} +std::string toString( unsigned long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} +#endif + #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ) { return "nullptr"; @@ -6985,7 +9025,7 @@ std::string toString( std::nullptr_t ) { return "nil"; return "@" + toString([nsstring UTF8String]); } - std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + std::string toString( NSString * CATCH_ARC_STRONG & nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); @@ -7005,11 +9045,25 @@ namespace Catch { ResultBuilder::ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, - ResultDisposition::Flags resultDisposition ) - : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + ResultDisposition::Flags resultDisposition, + char const* secondArg ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition, secondArg ), m_shouldDebugBreak( false ), - m_shouldThrow( false ) - {} + m_shouldThrow( false ), + m_guardException( false ) + { + m_stream().oss.str(""); + } + + ResultBuilder::~ResultBuilder() { +#if defined(CATCH_CONFIG_FAST_COMPILE) + if ( m_guardException ) { + m_stream().oss << "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + captureResult( ResultWas::ThrewException ); + getCurrentContext().getResultCapture()->exceptionEarlyReported(); + } +#endif + } ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { m_data.resultType = result; @@ -7019,27 +9073,15 @@ namespace Catch { m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; return *this; } - ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { - m_exprComponents.lhs = lhs; - return *this; - } - ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { - m_exprComponents.rhs = rhs; - return *this; - } - ResultBuilder& ResultBuilder::setOp( std::string const& op ) { - m_exprComponents.op = op; - return *this; - } - void ResultBuilder::endExpression() { - m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); - captureExpression(); + void ResultBuilder::endExpression( DecomposedExpression const& expr ) { + AssertionResult result = build( expr ); + handleResult( result ); } void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { m_assertionInfo.resultDisposition = resultDisposition; - m_stream.oss << Catch::translateActiveException(); + m_stream().oss << Catch::translateActiveException(); captureResult( ResultWas::ThrewException ); } @@ -7048,18 +9090,56 @@ namespace Catch { captureExpression(); } + void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { + if( expectedMessage.empty() ) + captureExpectedException( Matchers::Impl::MatchAllOf() ); + else + captureExpectedException( Matchers::Equals( expectedMessage ) ); + } + + void ResultBuilder::captureExpectedException( Matchers::Impl::MatcherBase const& matcher ) { + + assert( !isFalseTest( m_assertionInfo.resultDisposition ) ); + AssertionResultData data = m_data; + data.resultType = ResultWas::Ok; + data.reconstructedExpression = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); + + std::string actualMessage = Catch::translateActiveException(); + if( !matcher.match( actualMessage ) ) { + data.resultType = ResultWas::ExpressionFailed; + data.reconstructedExpression = actualMessage; + } + AssertionResult result( m_assertionInfo, data ); + handleResult( result ); + } + void ResultBuilder::captureExpression() { AssertionResult result = build(); + handleResult( result ); + } + + void ResultBuilder::handleResult( AssertionResult const& result ) + { getResultCapture().assertionEnded( result ); if( !result.isOk() ) { if( getCurrentContext().getConfig()->shouldDebugBreak() ) m_shouldDebugBreak = true; - if( getCurrentContext().getRunner()->aborting() || m_assertionInfo.resultDisposition == ResultDisposition::Normal ) + if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) m_shouldThrow = true; } } + void ResultBuilder::react() { +#if defined(CATCH_CONFIG_FAST_COMPILE) + if (m_shouldDebugBreak) { + /////////////////////////////////////////////////////////////////// + // To inspect the state during test, you need to go one level up the callstack + // To go back to the test and change execution, jump over the throw statement + /////////////////////////////////////////////////////////////////// + CATCH_BREAK_INTO_DEBUGGER(); + } +#endif if( m_shouldThrow ) throw Catch::TestFailureException(); } @@ -7069,43 +9149,39 @@ namespace Catch { AssertionResult ResultBuilder::build() const { - assert( m_data.resultType != ResultWas::Unknown ); + return build( *this ); + } + // CAVEAT: The returned AssertionResult stores a pointer to the argument expr, + // a temporary DecomposedExpression, which in turn holds references to + // operands, possibly temporary as well. + // It should immediately be passed to handleResult; if the expression + // needs to be reported, its string expansion must be composed before + // the temporaries are destroyed. + AssertionResult ResultBuilder::build( DecomposedExpression const& expr ) const + { + assert( m_data.resultType != ResultWas::Unknown ); AssertionResultData data = m_data; - // Flip bool results if testFalse is set - if( m_exprComponents.testFalse ) { - if( data.resultType == ResultWas::Ok ) - data.resultType = ResultWas::ExpressionFailed; - else if( data.resultType == ResultWas::ExpressionFailed ) - data.resultType = ResultWas::Ok; + // Flip bool results if FalseTest flag is set + if( isFalseTest( m_assertionInfo.resultDisposition ) ) { + data.negate( expr.isBinaryExpression() ); } - data.message = m_stream.oss.str(); - data.reconstructedExpression = reconstructExpression(); - if( m_exprComponents.testFalse ) { - if( m_exprComponents.op == "" ) - data.reconstructedExpression = "!" + data.reconstructedExpression; - else - data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; - } + data.message = m_stream().oss.str(); + data.decomposedExpression = &expr; // for lazy reconstruction return AssertionResult( m_assertionInfo, data ); } - std::string ResultBuilder::reconstructExpression() const { - if( m_exprComponents.op == "" ) - return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; - else if( m_exprComponents.op == "matches" ) - return m_exprComponents.lhs + " " + m_exprComponents.rhs; - else if( m_exprComponents.op != "!" ) { - if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && - m_exprComponents.lhs.find("\n") == std::string::npos && - m_exprComponents.rhs.find("\n") == std::string::npos ) - return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; - else - return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; - } - else - return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; + + void ResultBuilder::reconstructExpression( std::string& dest ) const { + dest = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); + } + + void ResultBuilder::setExceptionGuard() { + m_guardException = true; + } + void ResultBuilder::unsetExceptionGuard() { + m_guardException = false; } } // end namespace Catch @@ -7113,30 +9189,6 @@ namespace Catch { // #included from: catch_tag_alias_registry.hpp #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED -// #included from: catch_tag_alias_registry.h -#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED - -#include - -namespace Catch { - - class TagAliasRegistry : public ITagAliasRegistry { - public: - virtual ~TagAliasRegistry(); - virtual Option find( std::string const& alias ) const; - virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; - void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - static TagAliasRegistry& get(); - - private: - std::map m_registry; - }; - -} // end namespace Catch - -#include -#include - namespace Catch { TagAliasRegistry::~TagAliasRegistry() {} @@ -7164,94 +9216,343 @@ namespace Catch { return expandedTestSpec; } - void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { - if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) { std::ostringstream oss; - oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + oss << Colour( Colour::Red ) + << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" + << Colour( Colour::FileName ) + << lineInfo << '\n'; throw std::domain_error( oss.str().c_str() ); } if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { std::ostringstream oss; - oss << "error: tag alias, \"" << alias << "\" already registered.\n" - << "\tFirst seen at " << find(alias)->lineInfo << "\n" - << "\tRedefined at " << lineInfo; + oss << Colour( Colour::Red ) + << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " + << Colour( Colour::Red ) << find(alias)->lineInfo << '\n' + << Colour( Colour::Red ) << "\tRedefined at " + << Colour( Colour::FileName) << lineInfo << '\n'; throw std::domain_error( oss.str().c_str() ); } } - TagAliasRegistry& TagAliasRegistry::get() { - static TagAliasRegistry instance; - return instance; - - } - ITagAliasRegistry::~ITagAliasRegistry() {} - ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { - try { - TagAliasRegistry::get().add( alias, tag, lineInfo ); - } - catch( std::exception& ex ) { - Colour colourGuard( Colour::Red ); - std::cerr << ex.what() << std::endl; - exit(1); - } + getMutableRegistryHub().registerTagAlias( alias, tag, lineInfo ); } } // end namespace Catch +// #included from: catch_matchers_string.hpp + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + +} // namespace Matchers +} // namespace Catch +// #included from: ../reporters/catch_reporter_multi.hpp +#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED + +namespace Catch { + +class MultipleReporters : public SharedImpl { + typedef std::vector > Reporters; + Reporters m_reporters; + +public: + void add( Ptr const& reporter ) { + m_reporters.push_back( reporter ); + } + +public: // IStreamingReporter + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporters[0]->getPreferences(); + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->noMatchingTestCases( spec ); + } + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunStarting( testRunInfo ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseStarting( testInfo ); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionStarting( sectionInfo ); + } + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + bool clearBuffer = false; + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + clearBuffer |= (*it)->assertionEnded( assertionStats ); + return clearBuffer; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionEnded( sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupEnded( testGroupStats ); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunEnded( testRunStats ); + } + + virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->skipTest( testInfo ); + } + + virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { + return this; + } + +}; + +Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { + Ptr resultingReporter; + + if( existingReporter ) { + MultipleReporters* multi = existingReporter->tryAsMulti(); + if( !multi ) { + multi = new MultipleReporters; + resultingReporter = Ptr( multi ); + if( existingReporter ) + multi->add( existingReporter ); + } + else + resultingReporter = existingReporter; + multi->add( additionalReporter ); + } + else + resultingReporter = additionalReporter; + + return resultingReporter; +} + +} // end namespace Catch + // #included from: ../reporters/catch_reporter_xml.hpp #define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED // #included from: catch_reporter_bases.hpp #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED +#include +#include +#include +#include + namespace Catch { + namespace { + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + } + struct StreamingReporterBase : SharedImpl { StreamingReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) - {} + { + m_reporterPrefs.shouldRedirectStdOut = false; + } - virtual ~StreamingReporterBase(); + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } - virtual void noMatchingTestCases( std::string const& ) {} + virtual ~StreamingReporterBase() CATCH_OVERRIDE; - virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { currentTestRunInfo = _testRunInfo; } - virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { currentGroupInfo = _groupInfo; } - virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { currentTestCaseInfo = _testInfo; } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_sectionStack.push_back( _sectionInfo ); } - virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { m_sectionStack.pop_back(); } - virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); - assert( m_sectionStack.empty() ); } - virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { currentGroupInfo.reset(); } - virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); } - Ptr m_config; + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; std::ostream& stream; LazyStat currentTestRunInfo; @@ -7259,6 +9560,7 @@ namespace Catch { LazyStat currentTestCaseInfo; std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; }; struct CumulativeReporterBase : SharedImpl { @@ -7293,12 +9595,12 @@ namespace Catch { struct BySectionInfo { BySectionInfo( SectionInfo const& other ) : m_other( other ) {} - BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} bool operator() ( Ptr const& node ) const { return node->stats.sectionInfo.lineInfo == m_other.lineInfo; } private: - void operator=( BySectionInfo const& ); + void operator=( BySectionInfo const& ); SectionInfo const& m_other; }; @@ -7309,15 +9611,21 @@ namespace Catch { CumulativeReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) - {} + { + m_reporterPrefs.shouldRedirectStdOut = false; + } ~CumulativeReporterBase(); - virtual void testRunStarting( TestRunInfo const& ) {} - virtual void testGroupStarting( GroupInfo const& ) {} + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } - virtual void testCaseStarting( TestCaseInfo const& ) {} + virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} + virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} - virtual void sectionStarting( SectionInfo const& sectionInfo ) { + virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); Ptr node; if( m_sectionStack.empty() ) { @@ -7342,21 +9650,27 @@ namespace Catch { m_deepestSection = node; } - virtual void assertionStarting( AssertionInfo const& ) {} + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} - virtual bool assertionEnded( AssertionStats const& assertionStats ) { + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& sectionNode = *m_sectionStack.back(); sectionNode.assertions.push_back( assertionStats ); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression( sectionNode.assertions.back().assertionResult ); return true; } - virtual void sectionEnded( SectionStats const& sectionStats ) { + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& node = *m_sectionStack.back(); node.stats = sectionStats; m_sectionStack.pop_back(); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { Ptr node = new TestCaseNode( testCaseStats ); assert( m_sectionStack.size() == 0 ); node->children.push_back( m_rootSection ); @@ -7367,12 +9681,12 @@ namespace Catch { m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { Ptr node = new TestGroupNode( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } - virtual void testRunEnded( TestRunStats const& testRunStats ) { + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { Ptr node = new TestRunNode( testRunStats ); node->children.swap( m_testGroups ); m_testRuns.push_back( node ); @@ -7380,7 +9694,16 @@ namespace Catch { } virtual void testRunEndedCumulative() = 0; - Ptr m_config; + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void prepareExpandedExpression( AssertionResult& result ) const { + if( result.isOk() ) + result.discardDecomposedExpression(); + else + result.expandDecomposedExpression(); + } + + Ptr m_config; std::ostream& stream; std::vector m_assertions; std::vector > > m_sections; @@ -7392,9 +9715,31 @@ namespace Catch { Ptr m_rootSection; Ptr m_deepestSection; std::vector > m_sectionStack; + ReporterPreferences m_reporterPrefs; }; + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { + return false; + } + }; + } // end namespace Catch // #included from: ../internal/catch_reporter_registrars.hpp @@ -7425,7 +9770,7 @@ namespace Catch { template class ReporterRegistrar { - class ReporterFactory : public IReporterFactory { + class ReporterFactory : public SharedImpl { // *** Please Note ***: // - If you end up here looking at a compiler error because it's trying to register @@ -7453,23 +9798,110 @@ namespace Catch { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; + + template + class ListenerRegistrar { + + class ListenerFactory : public SharedImpl { + + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + virtual std::string getDescription() const { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( new ListenerFactory() ); + } + }; } #define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } +// Deprecated - use the form without INTERNAL_ +#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } + // #included from: ../internal/catch_xmlwriter.hpp #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED #include -#include #include #include +#include namespace Catch { + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void encodeTo( std::ostream& os ) const { + + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t i = 0; i < m_str.size(); ++ i ) { + char c = m_str[i]; + switch( c ) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) + os << ">"; + else + os << c; + break; + + case '\"': + if( m_forWhat == ForAttributes ) + os << """; + else + os << c; + break; + + default: + // Escape control chars - based on contribution by @espenalb in PR #465 and + // by @mrpi PR #588 + if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) { + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast( c ); + } + else + os << c; + } + } + } + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + private: + std::string m_str; + ForWhat m_forWhat; + }; + class XmlWriter { public: @@ -7481,7 +9913,7 @@ namespace Catch { ScopedElement( ScopedElement const& other ) : m_writer( other.m_writer ){ - other.m_writer = NULL; + other.m_writer = CATCH_NULL; } ~ScopedElement() { @@ -7507,45 +9939,28 @@ namespace Catch { XmlWriter() : m_tagIsOpen( false ), m_needsNewline( false ), - m_os( &std::cout ) - {} + m_os( Catch::cout() ) + { + writeDeclaration(); + } XmlWriter( std::ostream& os ) : m_tagIsOpen( false ), m_needsNewline( false ), - m_os( &os ) - {} + m_os( os ) + { + writeDeclaration(); + } ~XmlWriter() { while( !m_tags.empty() ) endElement(); } -//# ifndef CATCH_CPP11_OR_GREATER -// XmlWriter& operator = ( XmlWriter const& other ) { -// XmlWriter temp( other ); -// swap( temp ); -// return *this; -// } -//# else -// XmlWriter( XmlWriter const& ) = default; -// XmlWriter( XmlWriter && ) = default; -// XmlWriter& operator = ( XmlWriter const& ) = default; -// XmlWriter& operator = ( XmlWriter && ) = default; -//# endif -// -// void swap( XmlWriter& other ) { -// std::swap( m_tagIsOpen, other.m_tagIsOpen ); -// std::swap( m_needsNewline, other.m_needsNewline ); -// std::swap( m_tags, other.m_tags ); -// std::swap( m_indent, other.m_indent ); -// std::swap( m_os, other.m_os ); -// } - XmlWriter& startElement( std::string const& name ) { ensureTagClosed(); newlineIfNecessary(); - stream() << m_indent << "<" << name; + m_os << m_indent << '<' << name; m_tags.push_back( name ); m_indent += " "; m_tagIsOpen = true; @@ -7562,35 +9977,33 @@ namespace Catch { newlineIfNecessary(); m_indent = m_indent.substr( 0, m_indent.size()-2 ); if( m_tagIsOpen ) { - stream() << "/>\n"; + m_os << "/>"; m_tagIsOpen = false; } else { - stream() << m_indent << "\n"; + m_os << m_indent << ""; } + m_os << std::endl; m_tags.pop_back(); return *this; } XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) { - stream() << " " << name << "=\""; - writeEncodedText( attribute ); - stream() << "\""; - } + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; return *this; } XmlWriter& writeAttribute( std::string const& name, bool attribute ) { - stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; return *this; } template XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - if( !name.empty() ) - stream() << " " << name << "=\"" << attribute << "\""; - return *this; + std::ostringstream oss; + oss << attribute; + return writeAttribute( name, oss.str() ); } XmlWriter& writeText( std::string const& text, bool indent = true ) { @@ -7598,8 +10011,8 @@ namespace Catch { bool tagWasOpen = m_tagIsOpen; ensureTagClosed(); if( tagWasOpen && indent ) - stream() << m_indent; - writeEncodedText( text ); + m_os << m_indent; + m_os << XmlEncode( text ); m_needsNewline = true; } return *this; @@ -7607,210 +10020,262 @@ namespace Catch { XmlWriter& writeComment( std::string const& text ) { ensureTagClosed(); - stream() << m_indent << ""; + m_os << m_indent << ""; m_needsNewline = true; return *this; } + void writeStylesheetRef( std::string const& url ) { + m_os << "\n"; + } + XmlWriter& writeBlankLine() { ensureTagClosed(); - stream() << "\n"; + m_os << '\n'; return *this; } - void setStream( std::ostream& os ) { - m_os = &os; + void ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } } private: XmlWriter( XmlWriter const& ); void operator=( XmlWriter const& ); - std::ostream& stream() { - return *m_os; - } - - void ensureTagClosed() { - if( m_tagIsOpen ) { - stream() << ">\n"; - m_tagIsOpen = false; - } + void writeDeclaration() { + m_os << "\n"; } void newlineIfNecessary() { if( m_needsNewline ) { - stream() << "\n"; + m_os << std::endl; m_needsNewline = false; } } - void writeEncodedText( std::string const& text ) { - static const char* charsToEncode = "<&\""; - std::string mtext = text; - std::string::size_type pos = mtext.find_first_of( charsToEncode ); - while( pos != std::string::npos ) { - stream() << mtext.substr( 0, pos ); - - switch( mtext[pos] ) { - case '<': - stream() << "<"; - break; - case '&': - stream() << "&"; - break; - case '\"': - stream() << """; - break; - } - mtext = mtext.substr( pos+1 ); - pos = mtext.find_first_of( charsToEncode ); - } - stream() << mtext; - } - bool m_tagIsOpen; bool m_needsNewline; std::vector m_tags; std::string m_indent; - std::ostream* m_os; + std::ostream& m_os; }; } + namespace Catch { - class XmlReporter : public SharedImpl { + class XmlReporter : public StreamingReporterBase { public: - XmlReporter( ReporterConfig const& config ) : m_config( config ), m_sectionDepth( 0 ) {} + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()), + m_sectionDepth( 0 ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + virtual ~XmlReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as an XML document"; } - virtual ~XmlReporter(); - private: // IReporter - - virtual bool shouldRedirectStdout() const { - return true; + virtual std::string getStylesheetRef() const { + return std::string(); } - virtual void StartTesting() { - m_xml.setStream( m_config.stream() ); + void writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + public: // StreamingReporterBase + + virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); m_xml.startElement( "Catch" ); - if( !m_config.fullConfig()->name().empty() ) - m_xml.writeAttribute( "name", m_config.fullConfig()->name() ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); } - virtual void EndTesting( const Totals& totals ) { - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", totals.assertions.passed ) - .writeAttribute( "failures", totals.assertions.failed ) - .writeAttribute( "expectedFailures", totals.assertions.failedButOk ); - m_xml.endElement(); - } - - virtual void StartGroup( const std::string& groupName ) { + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupStarting( groupInfo ); m_xml.startElement( "Group" ) - .writeAttribute( "name", groupName ); + .writeAttribute( "name", groupInfo.name ); } - virtual void EndGroup( const std::string&, const Totals& totals ) { - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", totals.assertions.passed ) - .writeAttribute( "failures", totals.assertions.failed ) - .writeAttribute( "expectedFailures", totals.assertions.failedButOk ); - m_xml.endElement(); + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); } - virtual void StartSection( const std::string& sectionName, const std::string& description ) { + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) - .writeAttribute( "name", trim( sectionName ) ) - .writeAttribute( "description", description ); - } - } - virtual void NoAssertionsInSection( const std::string& ) {} - virtual void NoAssertionsInTestCase( const std::string& ) {} - - virtual void EndSection( const std::string& /*sectionName*/, const Counts& assertions ) { - if( --m_sectionDepth > 0 ) { - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", assertions.passed ) - .writeAttribute( "failures", assertions.failed ) - .writeAttribute( "expectedFailures", assertions.failedButOk ); - m_xml.endElement(); + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); } } - virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) { - m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); - m_currentTestSuccess = true; - } + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } - virtual void Result( const Catch::AssertionResult& assertionResult ) { - if( !m_config.fullConfig()->includeSuccessfulResults() && assertionResult.getResultType() == ResultWas::Ok ) - return; + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { - if( assertionResult.hasExpression() ) { + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults ) { + // Print any info messages in tags. + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { m_xml.startElement( "Expression" ) - .writeAttribute( "success", assertionResult.succeeded() ) - .writeAttribute( "filename", assertionResult.getSourceInfo().file ) - .writeAttribute( "line", assertionResult.getSourceInfo().line ); + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); m_xml.scopedElement( "Original" ) - .writeText( assertionResult.getExpression() ); + .writeText( result.getExpression() ); m_xml.scopedElement( "Expanded" ) - .writeText( assertionResult.getExpandedExpression() ); - m_currentTestSuccess &= assertionResult.succeeded(); + .writeText( result.getExpandedExpression() ); } - switch( assertionResult.getResultType() ) { + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { case ResultWas::ThrewException: - m_xml.scopedElement( "Exception" ) - .writeAttribute( "filename", assertionResult.getSourceInfo().file ) - .writeAttribute( "line", assertionResult.getSourceInfo().line ) - .writeText( assertionResult.getMessage() ); - m_currentTestSuccess = false; + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); break; case ResultWas::Info: m_xml.scopedElement( "Info" ) - .writeText( assertionResult.getMessage() ); + .writeText( result.getMessage() ); break; case ResultWas::Warning: - m_xml.scopedElement( "Warning" ) - .writeText( assertionResult.getMessage() ); + // Warning will already have been written break; case ResultWas::ExplicitFailure: - m_xml.scopedElement( "Failure" ) - .writeText( assertionResult.getMessage() ); - m_currentTestSuccess = false; + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); break; - case ResultWas::Unknown: - case ResultWas::Ok: - case ResultWas::FailureBit: - case ResultWas::ExpressionFailed: - case ResultWas::Exception: - case ResultWas::DidntThrowException: + default: break; } - if( assertionResult.hasExpression() ) + + if( result.hasExpression() ) m_xml.endElement(); + + return true; } - virtual void Aborted() { - // !TBD + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } } - virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals&, const std::string&, const std::string& ) { - m_xml.scopedElement( "OverallResult" ).writeAttribute( "success", m_currentTestSuccess ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); m_xml.endElement(); } private: - ReporterConfig m_config; - bool m_currentTestSuccess; + Timer m_testCaseTimer; XmlWriter m_xml; int m_sectionDepth; }; + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + } // end namespace Catch // #included from: ../reporters/catch_reporter_junit.hpp @@ -7820,33 +10285,59 @@ namespace Catch { namespace Catch { + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + } + class JunitReporter : public CumulativeReporterBase { public: JunitReporter( ReporterConfig const& _config ) : CumulativeReporterBase( _config ), - xml( _config.stream() ) - {} + xml( _config.stream() ), + m_okToFail( false ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } - ~JunitReporter(); + virtual ~JunitReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } - virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = true; - return prefs; - } - - virtual void testRunStarting( TestRunInfo const& runInfo ) { + virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); } - virtual void testGroupStarting( GroupInfo const& groupInfo ) { + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { suiteTimer.start(); stdOutForSuite.str(""); stdErrForSuite.str(""); @@ -7854,25 +10345,28 @@ namespace Catch { CumulativeReporterBase::testGroupStarting( groupInfo ); } - virtual bool assertionEnded( AssertionStats const& assertionStats ) { - if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) + virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE { + m_okToFail = testCaseInfo.okToFail(); + } + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) unexpectedExceptions++; return CumulativeReporterBase::assertionEnded( assertionStats ); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { stdOutForSuite << testCaseStats.stdOut; stdErrForSuite << testCaseStats.stdErr; CumulativeReporterBase::testCaseEnded( testCaseStats ); } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { double suiteTime = suiteTimer.getElapsedSeconds(); CumulativeReporterBase::testGroupEnded( testGroupStats ); writeGroup( *m_testGroups.back(), suiteTime ); } - virtual void testRunEndedCumulative() { + virtual void testRunEndedCumulative() CATCH_OVERRIDE { xml.endElement(); } @@ -7888,7 +10382,7 @@ namespace Catch { xml.writeAttribute( "time", "" ); else xml.writeAttribute( "time", suiteTime ); - xml.writeAttribute( "timestamp", "tbd" ); // !TBD + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); // Write test cases for( TestGroupNode::ChildNodes::const_iterator @@ -7923,7 +10417,7 @@ namespace Catch { SectionNode const& sectionNode ) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) - name = rootName + "/" + name; + name = rootName + '/' + name; if( !sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || @@ -7937,7 +10431,7 @@ namespace Catch { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } - xml.writeAttribute( "time", toString( sectionNode.stats.durationInSeconds ) ); + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); writeAssertions( sectionNode ); @@ -7970,6 +10464,7 @@ namespace Catch { std::string elementName; switch( result.getResultType() ) { case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: elementName = "error"; break; case ResultWas::ExplicitFailure: @@ -8000,14 +10495,14 @@ namespace Catch { std::ostringstream oss; if( !result.getMessage().empty() ) - oss << result.getMessage() << "\n"; + oss << result.getMessage() << '\n'; for( std::vector::const_iterator it = stats.infoMessages.begin(), itEnd = stats.infoMessages.end(); it != itEnd; ++it ) if( it->type == ResultWas::Info ) - oss << it->message << "\n"; + oss << it->message << '\n'; oss << "at " << result.getSourceInfo(); xml.writeText( oss.str(), false ); @@ -8019,6 +10514,7 @@ namespace Catch { std::ostringstream stdOutForSuite; std::ostringstream stdErrForSuite; unsigned int unexpectedExceptions; + bool m_okToFail; }; INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) @@ -8028,7 +10524,8 @@ namespace Catch { // #included from: ../reporters/catch_reporter_console.hpp #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED -#include +#include +#include namespace Catch { @@ -8038,48 +10535,40 @@ namespace Catch { m_headerPrinted( false ) {} - virtual ~ConsoleReporter(); + virtual ~ConsoleReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as plain lines of text"; } - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + stream << "No test cases matched '" << spec << '\'' << std::endl; } - virtual void noMatchingTestCases( std::string const& spec ) { - stream << "No test cases matched '" << spec << "'" << std::endl; + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } - virtual void assertionStarting( AssertionInfo const& ) { - } - - virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { AssertionResult const& result = _assertionStats.assertionResult; - bool printInfoMessages = true; + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - // Drop out if result was successful and we're not printing those - if( !m_config->includeSuccessfulResults() && result.isOk() ) { - if( result.getResultType() != ResultWas::Warning ) - return false; - printInfoMessages = false; - } + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return false; lazyPrint(); - AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + AssertionPrinter printer( stream, _assertionStats, includeResults ); printer.print(); stream << std::endl; return true; } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } - virtual void sectionEnded( SectionStats const& _sectionStats ) { + virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); @@ -8089,32 +10578,29 @@ namespace Catch { stream << "\nNo assertions in test case"; stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } - if( m_headerPrinted ) { - if( m_config->showDurations() == ShowDurations::Always ) - stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; - m_headerPrinted = false; + if( m_config->showDurations() == ShowDurations::Always ) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; } - else { - if( m_config->showDurations() == ShowDurations::Always ) - stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + if( m_headerPrinted ) { + m_headerPrinted = false; } StreamingReporterBase::sectionEnded( _sectionStats ); } - virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } - virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; printTotals( _testGroupStats.totals ); - stream << "\n" << std::endl; + stream << '\n' << std::endl; } StreamingReporterBase::testGroupEnded( _testGroupStats ); } - virtual void testRunEnded( TestRunStats const& _testRunStats ) { + virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { printTotalsDivider( _testRunStats.totals ); printTotals( _testRunStats.totals ); stream << std::endl; @@ -8162,7 +10648,16 @@ namespace Catch { case ResultWas::ThrewException: colour = Colour::Error; passOrFail = "FAILED"; - messageLabel = "due to unexpected exception with message"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; break; case ResultWas::DidntThrowException: colour = Colour::Error; @@ -8197,13 +10692,13 @@ namespace Catch { printSourceInfo(); if( stats.totals.assertions.total() > 0 ) { if( result.isOk() ) - stream << "\n"; + stream << '\n'; printResultType(); printOriginalExpression(); printReconstructedExpression(); } else { - stream << "\n"; + stream << '\n'; } printMessage(); } @@ -8220,25 +10715,25 @@ namespace Catch { Colour colourGuard( Colour::OriginalExpression ); stream << " "; stream << result.getExpressionInMacro(); - stream << "\n"; + stream << '\n'; } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { stream << "with expansion:\n"; Colour colourGuard( Colour::ReconstructedExpression ); - stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << '\n'; } } void printMessage() const { if( !messageLabel.empty() ) - stream << messageLabel << ":" << "\n"; + stream << messageLabel << ':' << '\n'; for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); it != itEnd; ++it ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || it->type != ResultWas::Info ) - stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; + stream << Text( it->message, TextAttributes().setIndent(2) ) << '\n'; } } void printSourceInfo() const { @@ -8270,17 +10765,15 @@ namespace Catch { } } void lazyPrintRunInfo() { - stream << "\n" << getLineOfChars<'~'>() << "\n"; + stream << '\n' << getLineOfChars<'~'>() << '\n'; Colour colour( Colour::SecondaryText ); stream << currentTestRunInfo->name - << " is a Catch v" << libraryVersion.majorVersion << "." - << libraryVersion.minorVersion << " b" - << libraryVersion.buildNumber; - if( libraryVersion.branchName != std::string( "master" ) ) - stream << " (" << libraryVersion.branchName << ")"; - stream << " host application.\n" + << " is a Catch v" << libraryVersion() << " host application.\n" << "Run with -? for options\n\n"; + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + currentTestRunInfo.used = true; } void lazyPrintGroupInfo() { @@ -8303,22 +10796,22 @@ namespace Catch { printHeaderString( it->name, 2 ); } - SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; if( !lineInfo.empty() ){ - stream << getLineOfChars<'-'>() << "\n"; + stream << getLineOfChars<'-'>() << '\n'; Colour colourGuard( Colour::FileName ); - stream << lineInfo << "\n"; + stream << lineInfo << '\n'; } - stream << getLineOfChars<'.'>() << "\n" << std::endl; + stream << getLineOfChars<'.'>() << '\n' << std::endl; } void printClosedHeader( std::string const& _name ) { printOpenHeader( _name ); - stream << getLineOfChars<'.'>() << "\n"; + stream << getLineOfChars<'.'>() << '\n'; } void printOpenHeader( std::string const& _name ) { - stream << getLineOfChars<'-'>() << "\n"; + stream << getLineOfChars<'-'>() << '\n'; { Colour colourGuard( Colour::Headers ); printHeaderString( _name ); @@ -8335,7 +10828,7 @@ namespace Catch { i = 0; stream << Text( _string, TextAttributes() .setIndent( indent+i) - .setInitialIndent( indent ) ) << "\n"; + .setInitialIndent( indent ) ) << '\n'; } struct SummaryColumn { @@ -8350,9 +10843,9 @@ namespace Catch { std::string row = oss.str(); for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { while( it->size() < row.size() ) - *it = " " + *it; + *it = ' ' + *it; while( it->size() > row.size() ) - row = " " + row; + row = ' ' + row; } rows.push_back( row ); return *this; @@ -8368,12 +10861,12 @@ namespace Catch { if( totals.testCases.total() == 0 ) { stream << Colour( Colour::Warning ) << "No tests ran\n"; } - else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { stream << Colour( Colour::ResultSuccess ) << "All tests passed"; stream << " (" << pluralise( totals.assertions.passed, "assertion" ) << " in " - << pluralise( totals.testCases.passed, "test case" ) << ")" - << "\n"; + << pluralise( totals.testCases.passed, "test case" ) << ')' + << '\n'; } else { @@ -8408,10 +10901,10 @@ namespace Catch { else if( value != "0" ) { stream << Colour( Colour::LightGrey ) << " | "; stream << Colour( it->colour ) - << value << " " << it->label; + << value << ' ' << it->label; } } - stream << "\n"; + stream << '\n'; } static std::size_t makeRatio( std::size_t number, std::size_t total ) { @@ -8447,19 +10940,10 @@ namespace Catch { else { stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); } - stream << "\n"; + stream << '\n'; } void printSummaryDivider() { - stream << getLineOfChars<'-'>() << "\n"; - } - template - static char const* getLineOfChars() { - static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; - if( !*line ) { - memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); - line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; - } - return line; + stream << getLineOfChars<'-'>() << '\n'; } private: @@ -8494,11 +10978,10 @@ namespace Catch { } virtual void noMatchingTestCases( std::string const& spec ) { - stream << "No test cases matched '" << spec << "'" << std::endl; + stream << "No test cases matched '" << spec << '\'' << std::endl; } - virtual void assertionStarting( AssertionInfo const& ) { - } + virtual void assertionStarting( AssertionInfo const& ) {} virtual bool assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; @@ -8519,9 +11002,15 @@ namespace Catch { return true; } + virtual void sectionEnded(SectionStats const& _sectionStats) CATCH_OVERRIDE { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) { printTotals( _testRunStats.totals ); - stream << "\n" << std::endl; + stream << '\n' << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } @@ -8569,6 +11058,13 @@ namespace Catch { printExpressionWas(); printRemainingMessages(); break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; case ResultWas::DidntThrowException: printResultType( Colour::Error, failedString() ); printIssue( "expected exception, got none" ); @@ -8614,26 +11110,26 @@ namespace Catch { void printSourceInfo() const { Colour colourGuard( Colour::FileName ); - stream << result.getSourceInfo() << ":"; + stream << result.getSourceInfo() << ':'; } - void printResultType( Colour::Code colour, std::string passOrFail ) const { + void printResultType( Colour::Code colour, std::string const& passOrFail ) const { if( !passOrFail.empty() ) { { Colour colourGuard( colour ); - stream << " " << passOrFail; + stream << ' ' << passOrFail; } - stream << ":"; + stream << ':'; } } - void printIssue( std::string issue ) const { - stream << " " << issue; + void printIssue( std::string const& issue ) const { + stream << ' ' << issue; } void printExpressionWas() { if( result.hasExpression() ) { - stream << ";"; + stream << ';'; { Colour colour( dimColour() ); stream << " expression was:"; @@ -8644,7 +11140,7 @@ namespace Catch { void printOriginalExpression() const { if( result.hasExpression() ) { - stream << " " << result.getExpression(); + stream << ' ' << result.getExpression(); } } @@ -8660,7 +11156,7 @@ namespace Catch { void printMessage() { if ( itMessage != messages.end() ) { - stream << " '" << itMessage->message << "'"; + stream << " '" << itMessage->message << '\''; ++itMessage; } } @@ -8675,13 +11171,13 @@ namespace Catch { { Colour colourGuard( colour ); - stream << " with " << pluralise( N, "message" ) << ":"; + stream << " with " << pluralise( N, "message" ) << ':'; } for(; itMessage != itEnd; ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || itMessage->type != ResultWas::Info ) { - stream << " '" << itMessage->message << "'"; + stream << " '" << itMessage->message << '\''; if ( ++itMessage != itEnd ) { Colour colourGuard( dimColour() ); stream << " and"; @@ -8707,7 +11203,7 @@ namespace Catch { // - green: Passed [both/all] N tests cases with M assertions. std::string bothOrAll( std::size_t count ) const { - return count == 1 ? "" : count == 2 ? "both " : "all " ; + return count == 1 ? std::string() : count == 2 ? "both " : "all " ; } void printTotals( const Totals& totals ) const { @@ -8718,12 +11214,12 @@ namespace Catch { Colour colour( Colour::ResultError ); const std::string qualify_assertions_failed = totals.assertions.failed == totals.assertions.total() ? - bothOrAll( totals.assertions.failed ) : ""; + bothOrAll( totals.assertions.failed ) : std::string(); stream << "Failed " << bothOrAll( totals.testCases.failed ) << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << qualify_assertions_failed << - pluralise( totals.assertions.failed, "assertion" ) << "."; + pluralise( totals.assertions.failed, "assertion" ) << '.'; } else if( totals.assertions.total() == 0 ) { stream << @@ -8735,14 +11231,14 @@ namespace Catch { Colour colour( Colour::ResultError ); stream << "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " - "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; } else { Colour colour( Colour::ResultSuccess ); stream << "Passed " << bothOrAll( totals.testCases.passed ) << pluralise( totals.testCases.passed, "test case" ) << - " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + " with " << pluralise( totals.assertions.passed, "assertion" ) << '.'; } } }; @@ -8752,8 +11248,14 @@ namespace Catch { } // end namespace Catch namespace Catch { + // These are all here to avoid warnings about not having any out of line + // virtual methods NonCopyable::~NonCopyable() {} IShared::~IShared() {} + IStream::~IStream() CATCH_NOEXCEPT {} + FileStream::~FileStream() CATCH_NOEXCEPT {} + CoutStream::~CoutStream() CATCH_NOEXCEPT {} + DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} IContext::~IContext() {} IResultCapture::~IResultCapture() {} @@ -8787,19 +11289,21 @@ namespace Catch { FreeFunctionTestCase::~FreeFunctionTestCase() {} IGeneratorInfo::~IGeneratorInfo() {} IGeneratorsForTest::~IGeneratorsForTest() {} + WildcardPattern::~WildcardPattern() {} TestSpec::Pattern::~Pattern() {} TestSpec::NamePattern::~NamePattern() {} TestSpec::TagPattern::~TagPattern() {} TestSpec::ExcludedPattern::~ExcludedPattern() {} - - Matchers::Impl::StdString::Equals::~Equals() {} - Matchers::Impl::StdString::Contains::~Contains() {} - Matchers::Impl::StdString::StartsWith::~StartsWith() {} - Matchers::Impl::StdString::EndsWith::~EndsWith() {} + Matchers::Impl::MatcherUntypedBase::~MatcherUntypedBase() {} void Config::dummy() {} - INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter ) + namespace TestCaseTracking { + ITracker::~ITracker() {} + TrackerBase::~TrackerBase() {} + SectionTracker::~SectionTracker() {} + IndexTracker::~IndexTracker() {} + } } #ifdef __clang__ @@ -8814,9 +11318,16 @@ namespace Catch { #ifndef __OBJC__ +#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else // Standard C/C++ main entry point -int main (int argc, char * const argv[]) { - return Catch::Session().run( argc, argv ); +int main (int argc, char * argv[]) { +#endif + + int result = Catch::Session().run( argc, argv ); + return ( result < 0xff ? result : 0xff ); } #else // __OBJC__ @@ -8834,7 +11345,7 @@ int main (int argc, char * const argv[]) { [pool drain]; #endif - return result; + return ( result < 0xff ? result : 0xff ); } #endif // __OBJC__ @@ -8850,46 +11361,62 @@ int main (int argc, char * const argv[]) { // If this config identifier is defined then all CATCH macros are prefixed with CATCH_ #ifdef CATCH_CONFIG_PREFIX_ALL -#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) -#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#else +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#endif -#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) -#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) -#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) -#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) -#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) -#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) -#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) -#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) -#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) -#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) -#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) -#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) -#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) -#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) -#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) -#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) -#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#else +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) - #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) - #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #else #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) - #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) - #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) + #define CATCH_FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) #endif #define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) @@ -8906,55 +11433,72 @@ int main (int argc, char * const argv[]) { #define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) #define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif -#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) -#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) -#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) -#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) -#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else -#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) -#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) -#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) -#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) -#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) +#else +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#endif -#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) -#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) -#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) -#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) -#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) -#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) -#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) -#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) +#define CHECK( expr ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) -#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) -#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) -#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) -#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) -#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) -#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) -#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#else +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) #ifdef CATCH_CONFIG_VARIADIC_MACROS - #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) - #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) - #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) - #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) - #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) - #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #else - #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) +#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) - #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) - #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) + #define FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) #endif #define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) @@ -8975,11 +11519,11 @@ int main (int argc, char * const argv[]) { #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif -#define GIVEN( desc ) SECTION( " Given: " desc, "" ) -#define WHEN( desc ) SECTION( " When: " desc, "" ) -#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) -#define THEN( desc ) SECTION( " Then: " desc, "" ) -#define AND_THEN( desc ) SECTION( " And: " desc, "" ) +#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) +#define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) +#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) +#define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) +#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) using Catch::Detail::Approx; @@ -8988,9 +11532,13 @@ using Catch::Detail::Approx; #define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED #ifdef __clang__ -#pragma clang diagnostic pop +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif #elif defined __GNUC__ -#pragma GCC diagnostic pop +# pragma GCC diagnostic pop #endif #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED diff --git a/tests/common-tests.h b/tests/common-tests.h index dd3b26f20e..c899bc1944 100644 --- a/tests/common-tests.h +++ b/tests/common-tests.h @@ -10,7 +10,7 @@ #include "soci/soci.h" -#ifdef HAVE_BOOST +#ifdef SOCI_HAVE_BOOST // explicitly pull conversions for Boost's optional, tuple and fusion: #include #include "soci/boost-optional.h" @@ -19,13 +19,18 @@ #if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 #include "soci/boost-fusion.h" #endif // BOOST_VERSION -#endif // HAVE_BOOST +#endif // SOCI_HAVE_BOOST #include "soci-compiler.h" #define CATCH_CONFIG_RUNNER #include +#if defined(_MSC_VER) && (_MSC_VER < 1500) +#undef SECTION +#define SECTION(name) INTERNAL_CATCH_SECTION(name, "dummy-for-vc8") +#endif + #include #include #include @@ -110,7 +115,7 @@ public: class MyInt { public: - MyInt() {} + MyInt() : i_() {} MyInt(int i) : i_(i) {} void set(int i) { i_ = i; } int get() const { return i_; } @@ -326,6 +331,32 @@ public: virtual table_creator_base* table_creator_3(session&) const = 0; virtual table_creator_base* table_creator_4(session&) const = 0; + // Override this to return the table creator for a simple table containing + // an integer "id" column and CLOB "s" one. + // + // Returns null by default to indicate that CLOB is not supported. + virtual table_creator_base* table_creator_clob(session&) const { return NULL; } + + // Override this to return the table creator for a simple table containing + // an integer "id" column and XML "x" one. + // + // Returns null by default to indicate that XML is not supported. + virtual table_creator_base* table_creator_xml(session&) const { return NULL; } + + // Return the casts that must be used to convert the between the database + // XML type and the query parameters. + // + // By default no special casts are done. + virtual std::string to_xml(std::string const& x) const { return x; } + virtual std::string from_xml(std::string const& x) const { return x; } + + // Override this if the backend not only supports working with XML values + // (and so returns a non-null value from table_creator_xml()), but the + // database itself has real XML support instead of just allowing to store + // and retrieve XML as text. "Real" support means at least preventing the + // application from storing malformed XML in the database. + virtual bool has_real_xml_support() const { return false; } + // Override this if the backend doesn't handle floating point values // correctly, i.e. writing a value and reading it back doesn't return // *exactly* the same value. @@ -352,6 +383,11 @@ public: // whatever we do. virtual bool enable_std_char_padding(session&) const { return true; } + // Return the SQL expression giving the length of the specified string, + // i.e. "char_length(s)" in standard SQL but often "len(s)" or "length(s)" + // in practice and sometimes even worse (thanks Oracle). + virtual std::string sql_length(std::string const& s) const = 0; + virtual ~test_context_base() { the_test_context_ = NULL; @@ -477,7 +513,7 @@ protected: SOCI_NOT_COPYABLE(common_tests) }; -typedef std::auto_ptr auto_table_creator; +typedef cxx_details::auto_ptr auto_table_creator; // Define the test cases in their own namespace to avoid clashes with the test // cases defined in individual backend tests: as only line number is used for @@ -506,7 +542,7 @@ TEST_CASE_METHOD(common_tests, "Exception on not connected", "[core][exception]" CHECK_THROWS_AS(sql.get_last_insert_id(s, l), soci_error); } -TEST_CASE_METHOD(common_tests, "Basic functionality") +TEST_CASE_METHOD(common_tests, "Basic functionality", "[core][basics]") { soci::session sql(backEndFactory_, connectString_); @@ -518,6 +554,18 @@ TEST_CASE_METHOD(common_tests, "Basic functionality") int id; sql << "select id from soci_test", into(id); CHECK(id == 123); + + sql << "insert into soci_test (id) values (" << 234 << ")"; + sql << "insert into soci_test (id) values (" << 345 << ")"; + // Test prepare, execute, fetch correctness + statement st = (sql.prepare << "select id from soci_test", into(id)); + st.execute(); + int count = 0; + while(st.fetch()) + count++; + CHECK(count == 3 ); + bool fetchEnd = st.fetch(); // All the data has been read here so additional fetch must return false + CHECK(fetchEnd == false); } // "into" tests, type conversions, etc. @@ -582,7 +630,7 @@ TEST_CASE_METHOD(common_tests, "Use and into", "[core][into]") SECTION("Round trip works for date without time") { - std::tm nov15; + std::tm nov15 = std::tm(); nov15.tm_year = 105; nov15.tm_mon = 10; nov15.tm_mday = 15; @@ -592,7 +640,7 @@ TEST_CASE_METHOD(common_tests, "Use and into", "[core][into]") sql << "insert into soci_test(tm) values(:tm)", use(nov15); - std::tm t; + std::tm t = std::tm(); sql << "select tm from soci_test", into(t); CHECK(t.tm_year == 105); CHECK(t.tm_mon == 10); @@ -604,7 +652,7 @@ TEST_CASE_METHOD(common_tests, "Use and into", "[core][into]") SECTION("Round trip works for date with time") { - std::tm nov15; + std::tm nov15 = std::tm(); nov15.tm_year = 105; nov15.tm_mon = 10; nov15.tm_mday = 15; @@ -614,7 +662,7 @@ TEST_CASE_METHOD(common_tests, "Use and into", "[core][into]") sql << "insert into soci_test(tm) values(:tm)", use(nov15); - std::tm t; + std::tm t = std::tm(); sql << "select tm from soci_test", into(t); CHECK(t.tm_year == 105); CHECK(t.tm_mon == 10); @@ -646,7 +694,7 @@ TEST_CASE_METHOD(common_tests, "Use and into", "[core][into]") CHECK(ind == i_null); // additional test for NULL with std::tm - std::tm t; + std::tm t = std::tm(); sql << "select tm from soci_test", into(t, ind); CHECK(ind == i_null); @@ -905,9 +953,9 @@ TEST_CASE_METHOD(common_tests, "Repeated and bulk fetch", "[core][bulk]") st.execute(); while (st.fetch()) { - for (std::size_t i = 0; i != vec.size(); ++i) + for (std::size_t n = 0; n != vec.size(); ++n) { - CHECK(i2 == vec[i]); + CHECK(i2 == vec[n]); ++i2; } @@ -970,11 +1018,11 @@ TEST_CASE_METHOD(common_tests, "Repeated and bulk fetch", "[core][bulk]") int const rowsToTest = 100; double d = 0.0; - statement st = (sql.prepare << + statement sti = (sql.prepare << "insert into soci_test(d) values(:d)", use(d)); for (int i = 0; i != rowsToTest; ++i) { - st.execute(true); + sti.execute(true); d += 0.6; } @@ -1039,7 +1087,7 @@ TEST_CASE_METHOD(common_tests, "Repeated and bulk fetch", "[core][bulk]") CHECK(count == rowsToTest); { - std::tm t; + std::tm t = std::tm(); int i = 0; statement st = (sql.prepare << @@ -1210,11 +1258,11 @@ TEST_CASE_METHOD(common_tests, "Indicators vector", "[core][indicator][vector]") // create and populate the test table auto_table_creator tableCreator(tc_.table_creator_1(sql)); { - sql << "insert into soci_test(id, val) values(1, 10)"; - sql << "insert into soci_test(id, val) values(2, 11)"; - sql << "insert into soci_test(id, val) values(3, NULL)"; - sql << "insert into soci_test(id, val) values(4, NULL)"; - sql << "insert into soci_test(id, val) values(5, 12)"; + sql << "insert into soci_test(id, str, val) values(1, 'ten', 10)"; + sql << "insert into soci_test(id, str, val) values(2, 'elf', 11)"; + sql << "insert into soci_test(id, str, val) values(3, NULL, NULL)"; + sql << "insert into soci_test(id, str, val) values(4, NULL, NULL)"; + sql << "insert into soci_test(id, str, val) values(5, 'xii', 12)"; { std::vector vals(4); @@ -1231,6 +1279,15 @@ TEST_CASE_METHOD(common_tests, "Indicators vector", "[core][indicator][vector]") st.fetch(); CHECK(vals.size() == 1); CHECK(inds.size() == 1); + + std::vector strs(5); + sql << "select str from soci_test order by id", into(strs, inds); + REQUIRE(inds.size() == 5); + CHECK(inds[0] == i_ok); + CHECK(inds[1] == i_ok); + CHECK(inds[2] == i_null); + CHECK(inds[3] == i_null); + CHECK(inds[4] == i_ok); } } @@ -1314,7 +1371,7 @@ TEST_CASE_METHOD(common_tests, "Use type conversion", "[core][use]") SECTION("std::tm") { - std::tm t; + std::tm t = std::tm(); t.tm_year = 105; t.tm_mon = 10; t.tm_mday = 19; @@ -1323,7 +1380,7 @@ TEST_CASE_METHOD(common_tests, "Use type conversion", "[core][use]") t.tm_sec = 57; sql << "insert into soci_test(tm) values(:t)", use(t); - std::tm t2; + std::tm t2 = std::tm(); t2.tm_year = 0; t2.tm_mon = 0; t2.tm_mday = 0; @@ -1433,7 +1490,7 @@ TEST_CASE_METHOD(common_tests, "Use type conversion", "[core][use]") SECTION("const std::tm") { - std::tm t; + std::tm t = std::tm(); t.tm_year = 105; t.tm_mon = 10; t.tm_mday = 19; @@ -1443,7 +1500,7 @@ TEST_CASE_METHOD(common_tests, "Use type conversion", "[core][use]") std::tm const & ct = t; sql << "insert into soci_test(tm) values(:t)", use(ct); - std::tm t2; + std::tm t2 = std::tm(); t2.tm_year = 0; t2.tm_mon = 0; t2.tm_mday = 0; @@ -1685,7 +1742,7 @@ TEST_CASE_METHOD(common_tests, "Use vector", "[core][use][vector]") SECTION("std::tm") { std::vector v; - std::tm t; + std::tm t = std::tm(); t.tm_year = 105; t.tm_mon = 10; t.tm_mday = 26; @@ -1896,6 +1953,18 @@ TEST_CASE_METHOD(common_tests, "Transactions", "[core][transaction]") #ifndef SOCI_POSTGRESQL_NOPARAMS +std::tm generate_tm() +{ + std::tm t = std::tm(); + t.tm_year = 105; + t.tm_mon = 10; + t.tm_mday = 15; + t.tm_hour = 22; + t.tm_min = 14; + t.tm_sec = 17; + return t; +} + // test of use elements with indicators TEST_CASE_METHOD(common_tests, "Use with indicators", "[core][use][indicator]") { @@ -1905,24 +1974,28 @@ TEST_CASE_METHOD(common_tests, "Use with indicators", "[core][use][indicator]") indicator ind1 = i_ok; indicator ind2 = i_ok; + indicator ind3 = i_ok; int id = 1; int val = 10; - - sql << "insert into soci_test(id, val) values(:id, :val)", - use(id, ind1), use(val, ind2); + char const* insert = "insert into soci_test(id, val, tm) values(:id, :val, :tm)"; + sql << insert, use(id, ind1), use(val, ind2), use(generate_tm(), ind3); id = 2; val = 11; ind2 = i_null; - sql << "insert into soci_test(id, val) values(:id, :val)", - use(id, ind1), use(val, ind2); + std::tm tm = std::tm(); + ind3 = i_null; + + sql << "insert into soci_test(id, val, tm) values(:id, :val, :tm)", + use(id, ind1), use(val, ind2), use(tm, ind3); sql << "select val from soci_test where id = 1", into(val, ind2); CHECK(ind2 == i_ok); CHECK(val == 10); - sql << "select val from soci_test where id = 2", into(val, ind2); + sql << "select val, tm from soci_test where id = 2", into(val, ind2), into(tm, ind3); CHECK(ind2 == i_null); + CHECK(ind3 == i_null); std::vector ids; ids.push_back(3); @@ -1985,7 +2058,6 @@ TEST_CASE_METHOD(common_tests, "Dynamic row binding", "[core][dynamic]") // select into a row { - row r; statement st = (sql.prepare << "select * from soci_test", into(r)); st.execute(true); @@ -2011,9 +2083,7 @@ TEST_CASE_METHOD(common_tests, "Dynamic row binding", "[core][dynamic]") ASSERT_EQUAL_APPROX(r.get(0), 3.14); CHECK(r.get(1) == 123); CHECK(r.get(2) == "Johny"); - std::tm t = { 0 }; - t = r.get(3); - CHECK(t.tm_year == 105); + CHECK(r.get(3).tm_year == 105); // again, type char is visible as string CHECK_EQUAL_PADDED(r.get(4), "a"); @@ -2042,7 +2112,7 @@ TEST_CASE_METHOD(common_tests, "Dynamic row binding", "[core][dynamic]") double d; int i; std::string s; - std::tm t; + std::tm t = std::tm(); std::string c; r >> d >> i >> s >> t >> c; @@ -2063,7 +2133,6 @@ TEST_CASE_METHOD(common_tests, "Dynamic row binding", "[core][dynamic]") // additional test to check if the row object can be // reused between queries { - row r; sql << "select * from soci_test", into(r); CHECK(r.size() == 5); @@ -2584,7 +2653,7 @@ TEST_CASE_METHOD(common_tests, "Reading rows from rowset", "[core][row][rowset]" ASSERT_EQUAL_APPROX(r1.get(0), 3.14); CHECK(r1.get(1) == 123); CHECK(r1.get(2) == "Johny"); - std::tm t1 = { 0 }; + std::tm t1 = std::tm(); t1 = r1.get(3); CHECK(t1.tm_year == 105); CHECK_EQUAL_PADDED(r1.get(4), "a"); @@ -2783,14 +2852,27 @@ TEST_CASE_METHOD(common_tests, "Rowset expected exception", "[core][exception][r auto_table_creator tableCreator(tc_.table_creator_1(sql)); sql << "insert into soci_test(str) values('abc')"; + std::string troublemaker; CHECK_THROWS_AS( - std::string troublemaker; - rowset rs1 = (sql.prepare << "select str from soci_test", - into(troublemaker)), - soci_error + rowset((sql.prepare << "select str from soci_test", into(troublemaker))), + soci_error ); } +// functor for next test +struct THelper +{ + THelper() + : val_() + { + } + void operator()(int i) + { + val_ = i; + } + int val_; +}; + // test for handling NULL values with expected exception: // "Null value fetched and no indicator defined." TEST_CASE_METHOD(common_tests, "NULL expected exception", "[core][exception][null]") @@ -2805,17 +2887,8 @@ TEST_CASE_METHOD(common_tests, "NULL expected exception", "[core][exception][nul sql << "insert into soci_test(val) values(3)"; rowset rs = (sql.prepare << "select val from soci_test order by val asc"); - int tester = 0; - CHECK_THROWS_AS( - for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) - { - tester = *it; - }, - soci_error - ); - - (void)tester; + CHECK_THROWS_AS( std::for_each(rs.begin(), rs.end(), THelper()), soci_error ); } // This is like the first dynamic binding test but with rowset and iterators use @@ -2861,7 +2934,7 @@ TEST_CASE_METHOD(common_tests, "Dynamic binding with rowset", "[core][dynamic][t } } -#ifdef HAVE_BOOST +#ifdef SOCI_HAVE_BOOST // test for handling NULL values with boost::optional // (both into and use) @@ -3198,7 +3271,7 @@ TEST_CASE_METHOD(common_tests, "NULL with optional", "[core][boost][null]") } } -#endif // HAVE_BOOST +#endif // SOCI_HAVE_BOOST // connection and reconnection tests TEST_CASE_METHOD(common_tests, "Connection and reconnection", "[core][connect]") @@ -3266,7 +3339,7 @@ TEST_CASE_METHOD(common_tests, "Connection and reconnection", "[core][connect]") } -#ifdef HAVE_BOOST +#ifdef SOCI_HAVE_BOOST TEST_CASE_METHOD(common_tests, "Boost tuple", "[core][boost][tuple]") { @@ -3571,7 +3644,7 @@ TEST_CASE_METHOD(common_tests, "Boost date", "[core][boost][datetime]") { auto_table_creator tableCreator(tc_.table_creator_1(sql)); - std::tm nov15; + std::tm nov15 = std::tm(); nov15.tm_year = 105; nov15.tm_mon = 10; nov15.tm_mday = 15; @@ -3608,7 +3681,7 @@ TEST_CASE_METHOD(common_tests, "Boost date", "[core][boost][datetime]") sql << "insert into soci_test(tm) values(:tm)", use(bgd); - std::tm t; + std::tm t = std::tm(); sql << "select tm from soci_test", into(t); CHECK(t.tm_year == 108); @@ -3618,7 +3691,7 @@ TEST_CASE_METHOD(common_tests, "Boost date", "[core][boost][datetime]") } -#endif // HAVE_BOOST +#endif // SOCI_HAVE_BOOST // connection pool - simple sequential test, no multiple threads TEST_CASE_METHOD(common_tests, "Connection pool", "[core][connection][pool]") @@ -3804,29 +3877,40 @@ TEST_CASE_METHOD(common_tests, "Get affected rows", "[core][affected-rows]") return; } + for (int i = 0; i != 10; i++) { sql << "insert into soci_test(val) values(:val)", use(i); } + int step = 2; statement st1 = (sql.prepare << - "update soci_test set val = val + 1"); + "update soci_test set val = val + :step where val = 5", use(step, "step")); st1.execute(true); + CHECK(st1.get_affected_rows() == 1); - CHECK(st1.get_affected_rows() == 10); + // attempts to run the query again, no rows should be affected + st1.execute(true); + CHECK(st1.get_affected_rows() == 0); statement st2 = (sql.prepare << - "delete from soci_test where val <= 5"); + "update soci_test set val = val + 1"); st2.execute(true); - CHECK(st2.get_affected_rows() == 5); + CHECK(st2.get_affected_rows() == 10); statement st3 = (sql.prepare << - "update soci_test set val = val + 1"); + "delete from soci_test where val <= 5"); st3.execute(true); CHECK(st3.get_affected_rows() == 5); + statement st4 = (sql.prepare << + "update soci_test set val = val + 1"); + st4.execute(true); + + CHECK(st4.get_affected_rows() == 5); + std::vector v(5, 0); for (std::size_t i = 0; i < v.size(); ++i) { @@ -3834,17 +3918,17 @@ TEST_CASE_METHOD(common_tests, "Get affected rows", "[core][affected-rows]") } // test affected rows for bulk operations. - statement st4 = (sql.prepare << + statement st5 = (sql.prepare << "delete from soci_test where val = :v", use(v)); - st4.execute(true); + st5.execute(true); - CHECK(st4.get_affected_rows() == 5); + CHECK(st5.get_affected_rows() == 5); std::vector w(2, "1"); w[1] = "a"; // this invalid value may cause an exception. - statement st5 = (sql.prepare << + statement st6 = (sql.prepare << "insert into soci_test(val) values(:val)", use(w)); - try { st5.execute(true); } + try { st6.execute(true); } catch(...) {} // confirm the partial insertion. @@ -3852,9 +3936,16 @@ TEST_CASE_METHOD(common_tests, "Get affected rows", "[core][affected-rows]") sql << "select count(val) from soci_test", into(val); if(val != 0) { - // test the preserved 'number of rows - // affected' after a potential failure. - CHECK(st5.get_affected_rows() != 0); + // Notice that some ODBC drivers don't return the number of updated + // rows at all in the case of partially executed statement like this + // one, while MySQL ODBC driver wrongly returns 2 affected rows even + // though only one was actually inserted. + // + // So we can't check for "get_affected_rows() == val" here, it would + // fail in too many cases -- just check that the backend doesn't lie to + // us about no rows being affected at all (even if it just honestly + // admits that it has no idea by returning -1). + CHECK(st6.get_affected_rows() != 0); } } @@ -4043,6 +4134,22 @@ void check_for_exception_on_truncation(session& sql) } } +// And another helper for the test below. +void check_for_no_truncation(session& sql) +{ + const std::string str20 = "exactly of length 20"; + + sql << "delete from soci_test"; + + // Also check that there is no truncation when inserting a string of + // the same length as the column size. + CHECK_NOTHROW( (sql << "insert into soci_test(name) values(:s)", use(str20)) ); + + std::string s; + sql << "select name from soci_test", into(s); + CHECK( s == str20 ); +} + } // anonymous namespace TEST_CASE_METHOD(common_tests, "Truncation error", "[core][insert][truncate][exception]") @@ -4069,6 +4176,8 @@ TEST_CASE_METHOD(common_tests, "Truncation error", "[core][insert][truncate][exc tc_.on_after_ddl(sql); check_for_exception_on_truncation(sql); + + check_for_no_truncation(sql); } SECTION("Error given for varchar column") @@ -4077,6 +4186,8 @@ TEST_CASE_METHOD(common_tests, "Truncation error", "[core][insert][truncate][exc auto_table_creator tableCreator(tc_.table_creator_1(sql)); check_for_exception_on_truncation(sql); + + check_for_no_truncation(sql); } } @@ -4128,6 +4239,184 @@ TEST_CASE_METHOD(common_tests, "Blank padding", "[core][insert][exception]") CHECK(tvarchar == test1); } +TEST_CASE_METHOD(common_tests, "Select without table", "[core][select][dummy_from]") +{ + soci::session sql(backEndFactory_, connectString_); + + int plus17; + sql << ("select abs(-17)" + sql.get_dummy_from_clause()), + into(plus17); + + CHECK(plus17 == 17); +} + +TEST_CASE_METHOD(common_tests, "String length", "[core][string][length]") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::string s("123"); + REQUIRE_NOTHROW(( + sql << "insert into soci_test(str) values(:s)", use(s) + )); + + std::string sout; + size_t slen; + REQUIRE_NOTHROW(( + sql << "select str," + tc_.sql_length("str") + " from soci_test", + into(sout), into(slen) + )); + CHECK(slen == 3); + CHECK(sout.length() == 3); + CHECK(sout == s); + + sql << "delete from soci_test"; + + + std::vector v; + v.push_back("Hello"); + v.push_back(""); + v.push_back("whole of varchar(20)"); + + REQUIRE_NOTHROW(( + sql << "insert into soci_test(str) values(:s)", use(v) + )); + + std::vector vout(10); + // Although none of the strings here is really null, Oracle handles the + // empty string as being null, so to avoid an error about not providing + // the indicator when retrieving a null value, we must provide it here. + std::vector vind(10); + std::vector vlen(10); + + REQUIRE_NOTHROW(( + sql << "select str," + tc_.sql_length("str") + " from soci_test" + " order by " + tc_.sql_length("str"), + into(vout, vind), into(vlen) + )); + + REQUIRE(vout.size() == 3); + REQUIRE(vlen.size() == 3); + + CHECK(vlen[0] == 0); + CHECK(vout[0].length() == 0); + + CHECK(vlen[1] == 5); + CHECK(vout[1].length() == 5); + + CHECK(vlen[2] == 20); + CHECK(vout[2].length() == 20); +} + +// Helper function used in two tests below. +static std::string make_long_xml_string() +{ + std::string s; + s.reserve(6 + 200*26 + 7); + + s += ""; + for (int i = 0; i != 200; ++i) + { + s += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + } + s += ""; + + return s; +} + +TEST_CASE_METHOD(common_tests, "CLOB", "[core][clob]") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_clob(sql)); + if (!tableCreator.get()) + { + WARN("CLOB type not supported by the database, skipping the test."); + return; + } + + long_string s1; // empty + sql << "insert into soci_test(id, s) values (1, :s)", use(s1); + + long_string s2; + s2.value = "hello"; + sql << "select s from soci_test where id = 1", into(s2); + + CHECK(s2.value.size() == 0); + + s1.value = make_long_xml_string(); + + sql << "update soci_test set s = :s where id = 1", use(s1); + + sql << "select s from soci_test where id = 1", into(s2); + + CHECK(s2.value == s1.value); +} + +TEST_CASE_METHOD(common_tests, "XML", "[core][xml]") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_xml(sql)); + if (!tableCreator.get()) + { + WARN("XML type not supported by the database, skipping the test."); + return; + } + + int id = 1; + xml_type xml; + xml.value = make_long_xml_string(); + + sql << "insert into soci_test (id, x) values (:1, " + << tc_.to_xml(":2") + << ")", + use(id), use(xml); + + xml_type xml2; + + sql << "select " + << tc_.from_xml("x") + << " from soci_test where id = :1", + into(xml2), use(id); + + // The returned value doesn't need to be identical to the original one as + // string, only structurally equal as XML. In particular, extra whitespace + // can be added and this does happen with Oracle, for example, which adds + // an extra new line, so remove it if it's present. + if (!xml2.value.empty() && *xml2.value.rbegin() == '\n') + { + xml2.value.resize(xml2.value.length() - 1); + } + + CHECK(xml.value == xml2.value); + + sql << "update soci_test set x = null where id = :1", use(id); + + indicator ind; + sql << "select " + << tc_.from_xml("x") + << " from soci_test where id = :1", + into(xml2, ind), use(id); + + CHECK(ind == i_null); + + // Inserting malformed XML into an XML column must fail but some backends + // (e.g. Firebird) don't have real XML support, so exclude them from this + // test. + if (tc_.has_real_xml_support()) + { + xml.value = ""; + CHECK_THROWS_AS( + (sql << "insert into soci_test(id, x) values (2, " + + tc_.to_xml(":1") + ")", + use(xml) + ), soci_error + ); + } +} + } // namespace test_cases } // namespace tests diff --git a/tests/db2/test-db2.cpp b/tests/db2/test-db2.cpp index 9d73cc9650..082737fe8b 100644 --- a/tests/db2/test-db2.cpp +++ b/tests/db2/test-db2.cpp @@ -68,33 +68,38 @@ public: test_context(backend_factory const & pi_back_end, std::string const & pi_connect_string) : test_context_base(pi_back_end, pi_connect_string) {} - table_creator_base* table_creator_1(soci::session & pr_s) const + table_creator_base* table_creator_1(soci::session & pr_s) const SOCI_OVERRIDE { pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; return new table_creator_one(pr_s); } - table_creator_base* table_creator_2(soci::session & pr_s) const + table_creator_base* table_creator_2(soci::session & pr_s) const SOCI_OVERRIDE { pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; return new table_creator_two(pr_s); } - table_creator_base* table_creator_3(soci::session & pr_s) const + table_creator_base* table_creator_3(soci::session & pr_s) const SOCI_OVERRIDE { pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; return new table_creator_three(pr_s); } - table_creator_base* table_creator_4(soci::session& s) const + table_creator_base* table_creator_4(soci::session& s) const SOCI_OVERRIDE { return new table_creator_for_get_affected_rows(s); } - std::string to_date_time(std::string const & pi_datdt_string) const + std::string to_date_time(std::string const & pi_datdt_string) const SOCI_OVERRIDE { return "to_date('" + pi_datdt_string + "', 'YYYY-MM-DD HH24:MI:SS')"; } + + std::string sql_length(std::string const& s) const SOCI_OVERRIDE + { + return "length(" + s + ")"; + } }; diff --git a/tests/empty/test-empty.cpp b/tests/empty/test-empty.cpp index 7399dd0737..fd1f0b5601 100644 --- a/tests/empty/test-empty.cpp +++ b/tests/empty/test-empty.cpp @@ -63,12 +63,19 @@ TEST_CASE("Dummy test", "[empty]") sql << "Do what I want."; sql << "Do what I want " << 123 << " times."; - std::string query = "some query"; + char const* const query = "some query"; sql << query; + { + std::string squery = "some query"; + sql << squery; + } + int i = 7; sql << "insert", use(i); sql << "select", into(i); + sql << query, use(i); + sql << query, into(i); #if defined (__LP64__) || ( __WORDSIZE == 64 ) long int li = 9; @@ -83,6 +90,8 @@ TEST_CASE("Dummy test", "[empty]") indicator ind = i_ok; sql << "insert", use(i, ind); sql << "select", into(i, ind); + sql << query, use(i, ind); + sql << query, use(i, ind); std::vector numbers(100); sql << "insert", use(numbers); @@ -97,8 +106,14 @@ TEST_CASE("Dummy test", "[empty]") st.execute(); st.fetch(); } + { + statement st = (sql.prepare << query, into(i)); + st.execute(); + st.fetch(); + } { statement st = (sql.prepare << "select", into(i, ind)); + statement sq = (sql.prepare << query, into(i, ind)); } { statement st = (sql.prepare << "select", into(numbers)); @@ -108,9 +123,11 @@ TEST_CASE("Dummy test", "[empty]") } { statement st = (sql.prepare << "insert", use(i)); + statement sq = (sql.prepare << query, use(i)); } { statement st = (sql.prepare << "insert", use(i, ind)); + statement sq = (sql.prepare << query, use(i, ind)); } { statement st = (sql.prepare << "insert", use(numbers)); diff --git a/tests/firebird/test-firebird.cpp b/tests/firebird/test-firebird.cpp index 7347abd01b..3a39869adc 100644 --- a/tests/firebird/test-firebird.cpp +++ b/tests/firebird/test-firebird.cpp @@ -170,7 +170,9 @@ TEST_CASE("Firebird date and time", "[firebird][datetime]") sql.begin(); - std::tm t1, t2, t3; + std::tm t1 = std::tm(); + std::tm t2 = std::tm(); + std::tm t3 = std::tm(); std::time_t now = std::time(NULL); std::tm t = *std::localtime(&now); sql << "insert into test3(p1, p2, p3) " @@ -614,6 +616,23 @@ TEST_CASE("Firebird blobs", "[firebird][blob]") CHECK(ind==i_null); } + { + //create large blob + const int blobSize = 65536; //max segment size is 65535(unsigned short) + std::vector data(blobSize); + blob b(sql); + b.write(0, data.data(), blobSize); + sql << "insert into test7(id, img) values(3,?)", use(b); + + //now read blob back from database and make sure it has correct content and size + blob br(sql); + sql << "select img from test7 where id = 3", into(br); + std::vector data2(br.get_len()); + if(br.get_len()>0) + br.read(0, data2.data(), br.get_len()); + CHECK(data == data2); + } + sql << "drop table test7"; } @@ -1081,7 +1100,7 @@ TEST_CASE("Firebird string coercions", "[firebird][string]") { double a; - std::tm b, c, d; + std::tm b = std::tm(), c = std::tm(), d = std::tm(); sql << "select a, b, c, d from test12", into(a), into(b), into(c), into(d); CHECK(std::fabs(a - (-3.141)) < 0.000001); @@ -1255,6 +1274,28 @@ struct TableCreator4 : public tests::table_creator_base } }; +struct TableCreatorCLOB : public tests::table_creator_base +{ + TableCreatorCLOB(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, s blob sub_type text)"; + sql.commit(); + sql.begin(); + } +}; + +struct TableCreatorXML : public tests::table_creator_base +{ + TableCreatorXML(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, x blob sub_type text)"; + sql.commit(); + sql.begin(); + } +}; + class test_context : public tests::test_context_base { public: @@ -1263,35 +1304,50 @@ class test_context : public tests::test_context_base : test_context_base(backEnd, connectString) {} - tests::table_creator_base* table_creator_1(soci::session& s) const + tests::table_creator_base* table_creator_1(soci::session& s) const SOCI_OVERRIDE { return new TableCreator1(s); } - tests::table_creator_base* table_creator_2(soci::session& s) const + tests::table_creator_base* table_creator_2(soci::session& s) const SOCI_OVERRIDE { return new TableCreator2(s); } - tests::table_creator_base* table_creator_3(soci::session& s) const + tests::table_creator_base* table_creator_3(soci::session& s) const SOCI_OVERRIDE { return new TableCreator3(s); } - tests::table_creator_base* table_creator_4(soci::session& s) const + tests::table_creator_base* table_creator_4(soci::session& s) const SOCI_OVERRIDE { return new TableCreator4(s); } - std::string to_date_time(std::string const &datdt_string) const + tests::table_creator_base* table_creator_clob(soci::session& s) const SOCI_OVERRIDE + { + return new TableCreatorCLOB(s); + } + + tests::table_creator_base* table_creator_xml(soci::session& s) const SOCI_OVERRIDE + { + return new TableCreatorXML(s); + } + + std::string to_date_time(std::string const &datdt_string) const SOCI_OVERRIDE { return "'" + datdt_string + "'"; } - virtual void on_after_ddl(soci::session& sql) const + void on_after_ddl(soci::session& sql) const SOCI_OVERRIDE { sql.commit(); } + + std::string sql_length(std::string const& s) const SOCI_OVERRIDE + { + return "char_length(" + s + ")"; + } }; diff --git a/tests/mysql/test-mysql.cpp b/tests/mysql/test-mysql.cpp index 1f045e4d4a..8c73b52a1a 100644 --- a/tests/mysql/test-mysql.cpp +++ b/tests/mysql/test-mysql.cpp @@ -34,7 +34,7 @@ TEST_CASE("MySQL stored procedures", "[mysql][stored-procedure]") std::string version = mysql_get_server_info(sessionBackEnd->conn_); int v; std::istringstream iss(version); - if ((iss >> v) and v < 5) + if ((iss >> v) && v < 5) { WARN("MySQL server version " << v << " does not support stored procedures, skipping test."); @@ -361,9 +361,9 @@ TEST_CASE("MySQL number conversion", "[mysql][float][int]") TEST_CASE("MySQL datetime", "[mysql][datetime]") { soci::session sql(backEnd, connectString); - std::tm t; + std::tm t = std::tm(); sql << "select maketime(19, 54, 52)", into(t); - CHECK(t.tm_year == 100); + CHECK(t.tm_year == 0); CHECK(t.tm_mon == 0); CHECK(t.tm_mday == 1); CHECK(t.tm_hour == 19); @@ -520,7 +520,9 @@ TEST_CASE("MySQL get affected rows", "[mysql][affected-rows]") // The prepared statements should survive session::reconnect(). -TEST_CASE("MySQL statements after reconnect", "[mysql][connect]") +// However currently it doesn't and attempting to use it results in crashes due +// to accessing the already destroyed session backend, so disable this test. +TEST_CASE("MySQL statements after reconnect", "[mysql][connect][.]") { soci::session sql(backEnd, connectString); diff --git a/tests/mysql/test-mysql.h b/tests/mysql/test-mysql.h index 32037d0b02..6cca072777 100644 --- a/tests/mysql/test-mysql.h +++ b/tests/mysql/test-mysql.h @@ -60,39 +60,39 @@ public: std::string const &connectString) : test_context_base(backEnd, connectString) {} - table_creator_base* table_creator_1(soci::session& s) const + table_creator_base* table_creator_1(soci::session& s) const SOCI_OVERRIDE { return new table_creator_one(s); } - table_creator_base* table_creator_2(soci::session& s) const + table_creator_base* table_creator_2(soci::session& s) const SOCI_OVERRIDE { return new table_creator_two(s); } - table_creator_base* table_creator_3(soci::session& s) const + table_creator_base* table_creator_3(soci::session& s) const SOCI_OVERRIDE { return new table_creator_three(s); } - table_creator_base* table_creator_4(soci::session& s) const + table_creator_base* table_creator_4(soci::session& s) const SOCI_OVERRIDE { return new table_creator_for_get_affected_rows(s); } - std::string to_date_time(std::string const &datdt_string) const + std::string to_date_time(std::string const &datdt_string) const SOCI_OVERRIDE { return "\'" + datdt_string + "\'"; } - virtual bool has_fp_bug() const + bool has_fp_bug() const SOCI_OVERRIDE { // MySQL fails in the common test3() with "1.8000000000000000 != // 1.7999999999999998", so don't use exact doubles comparisons for it. return true; } - virtual bool has_transactions_support(soci::session& sql) const + bool has_transactions_support(soci::session& sql) const SOCI_OVERRIDE { sql << "drop table if exists soci_test"; sql << "create table soci_test (id int) engine=InnoDB"; @@ -103,7 +103,7 @@ public: return retv; } - virtual bool has_silent_truncate_bug(soci::session& sql) const + bool has_silent_truncate_bug(soci::session& sql) const SOCI_OVERRIDE { std::string sql_mode; sql << "select @@session.sql_mode", into(sql_mode); @@ -113,7 +113,7 @@ public: return sql_mode.find("STRICT_") == std::string::npos; } - virtual bool enable_std_char_padding(session& sql) const + bool enable_std_char_padding(soci::session& sql) const SOCI_OVERRIDE { // turn on standard right padding on mysql. This options is supported as of version 5.1.20 try @@ -127,6 +127,11 @@ public: return false; } } + + std::string sql_length(std::string const& s) const SOCI_OVERRIDE + { + return "char_length(" + s + ")"; + } }; #endif // SOCI_TESTS_MYSQL_H_INCLUDED diff --git a/tests/odbc/CMakeLists.txt b/tests/odbc/CMakeLists.txt index 80238763eb..2e87069817 100644 --- a/tests/odbc/CMakeLists.txt +++ b/tests/odbc/CMakeLists.txt @@ -35,12 +35,17 @@ soci_backend_test( SOURCE test-odbc-mysql.cpp ${SOCI_TESTS_COMMON} CONNSTR "test-mysql.dsn") +if(WIN32) + set(TEST_PGSQL_DSN "test-postgresql-win64.dsn") +else() + set(TEST_PGSQL_DSN "test-postgresql.dsn") +endif() soci_backend_test( NAME postgresql BACKEND ODBC DEPENDS ODBC SOURCE test-odbc-postgresql.cpp ${SOCI_TESTS_COMMON} - CONNSTR "test-postgresql.dsn") + CONNSTR ${TEST_PGSQL_DSN}) # TODO: DB2 backend is tested by Travis CI on dedicated VM, separate from ODBC, # in order to test DB2 with ODBC, it would be best to install DB2 driver only. diff --git a/tests/odbc/soci_test.mdb b/tests/odbc/soci_test.mdb new file mode 100644 index 0000000000000000000000000000000000000000..cf52bd9755f88dcf4284cfe31a27e6a5c3c07ae2 GIT binary patch literal 77824 zcmeI5U2Ggz701t=9k2IuX4k2rs!CN0m4XlraT=vnLBdVk2rN6U<3uQ`6yo*TvFq4Q zH*QF1g>9e`^npe~NKh43ZIMvlC{Q6m9}pDs01^>GqVPaKt&j>0Pk<^ay!_9dnVtQz zYh2&&MC*FGL zOr`&m^6VF$yIg(pnZ93K`tkLBuU`B0w4{!!tHkG%NjC%%=sSP`|9uO;mA+?3x3e%l>|tD1W14cNPq-LfCNZ@ z1W14cNMJh=c&ECV!!du#sx`N{A}5h^PlcvMF@ea^v7%(9h!BB>32?6FAdE-Dzk?bq^yocK}u)1w)Q{;yX@!;lv_bplb{CzS7&tBR0T6`gN}>}GH5g| zzH01@U?1@sx@uNu1qEZC7kEc zdrR0eiIEk&R%w0j2{|X_us0(|^v_|YE1$-ASBB&QR@du; zauGT!kQeOhVazMbW6(4yIotCPqzhOlRnwA@5&4|mbp^8?!d%_kJoKpc-Es_TX?tF# zFmnj!Ja%zwQ~#JuLsG%WlpL2!SYcAWgm(eLfsUAJRoeb5fTg}w^jNW1or76zM+con z%!&R`f;oCe^yb0F)ZStafK4n&_TLelho&IlKDK6+cP$yoT{a}n-kfYTB5oS z5rrrcFZw& z{!`Zb*YfoVw8{2Li*tXpPr72;Qty zEs?;S=-VRVIrt#MI&)miiAqd@IZ@EazTx8dh24Y7^j_;(7u%@GY@?0r|6#pxij0rc zc(XwQBtQZrKmsH{0wh2JB(M_+81v7l;vJvT*YE%9^WXNq>y^CQz1QY&(hq>7H!`Zw22rNB~JuQt4&J7L?&W_IN ze_=%vbk2;P8k{>>F3o=Cx} z$4wP_`J}VZqGfYRTyr_UR&uwN|F1Q+z-Fp=UkQZy{~n1Nd2Qg1 z_ZV&9&C}yVjjT3M5zv!}8aZ3MkDj2oAB~{+9}Ulz?xT^?hAOm`{@Snw1={e#@`0Xo z)Tn*e#QO??i;(~ckN^pg011!)36KB@kN^pYF;7R|`wrJH{{BCrApsH~0TLhq5+DH* zAOR8}0TLjA8%7}d<**yJfzDglHND>EDhN7{2`wT45+DH*AOR8}0TLhq5+DH*Ac0OJ z!2Exw-BH>@0wh2JBtQZrKmsH{0wh2Joj_pQ`~qixC(u+IH2;r}eDfdy5+DH*AOR8} z0TLhq5+DH*AORBSU;_02JNV|(G!h^I5+DH*AOR8}0TLhq5+DH*2no>t=UagUNPq-L zfCNZ@1W14cNPq-LfCM_9fH9xSJE55=dBrdLS^uZrthdLzlAp?VzWub21W14cNPq-L zfCNZ@1W14cww6Fqe^y)Y>2=@82eXfU{|DcBvp;%IVQ_F`4oK@`f2>d`uC4Gm6qWEs3RJb4yjN;Q z8}ovvcUg~uT=)OzU-5tFKkt9df83w;Pxy!YkNJE2wEu7K4ex60mjZ+FypjM3kN^pg z011!)36KB@kN^pgz>Ol{iaPPtsxK>jImsa0;JY^FP+e{4(on=LIHOVBt_{&Nb!kwg zsS7;?^@Hms23<+S7lIX`D{%N5x^!PgTDn%@$gdn|*lN=#_4S)Z1>e}`)vu^ql@|5Q KU21m2sQ&@2FZaCw literal 0 HcmV?d00001 diff --git a/tests/odbc/test-mssql.dsn b/tests/odbc/test-mssql.dsn index f96dc70119..5db0e2cf08 100644 --- a/tests/odbc/test-mssql.dsn +++ b/tests/odbc/test-mssql.dsn @@ -1,8 +1,10 @@ [ODBC] -DRIVER=SQL Native Client -UID=David +DRIVER=SQL Server Native Client 11.0 +UID=sa +PWD=Password12! +WSID=SOLARWIND +APP=SOCI Test Application +SERVER=(local)\SQL2014 DATABASE=soci_test -WSID=NANO -APP=Microsoft Data Access Components -Trusted_Connection=Yes -SERVER=localhost\SQLEXPRESS +Description=SQL on AppVeyor + diff --git a/tests/odbc/test-odbc-access.cpp b/tests/odbc/test-odbc-access.cpp index fa5b05db8f..2903996f79 100644 --- a/tests/odbc/test-odbc-access.cpp +++ b/tests/odbc/test-odbc-access.cpp @@ -16,23 +16,6 @@ using namespace soci; using namespace soci::tests; -#ifdef HAVE_BOOST -// It appears later versions of GCC arent happy with this - to be fixed properly -#if (__GNUC__ == 4 && (__GNUC_MINOR__ > 6)) || (__clang__ == 1) -#include - -namespace boost { - std::basic_ostream >& - operator<< (std::basic_ostream > & stream - , boost::optional const & value) - { - std::ostringstream oss; - return oss << "Currently not supported."; - } -} -#endif -#endif // HAVE_BOOST - std::string connectString; backend_factory const &backEnd = *soci::factory_odbc(); @@ -124,6 +107,11 @@ test_context(backend_factory const &backEnd, std::string const &connectString) { return "#" + datdt_string + "#"; } + + virtual std::string sql_length(std::string const& s) const + { + return "len(" + s + ")"; + } }; int main(int argc, char** argv) diff --git a/tests/odbc/test-odbc-db2.cpp b/tests/odbc/test-odbc-db2.cpp index 598ff32ec1..bcd771d9ba 100644 --- a/tests/odbc/test-odbc-db2.cpp +++ b/tests/odbc/test-odbc-db2.cpp @@ -93,6 +93,11 @@ public: { return "\'" + datdt_string + "\'"; } + + virtual std::string sql_length(std::string const& s) const + { + return "length(" + s + ")"; + } }; struct table_creator_bigint : table_creator_base diff --git a/tests/odbc/test-odbc-mssql.cpp b/tests/odbc/test-odbc-mssql.cpp index f4511c9ebe..6192605895 100644 --- a/tests/odbc/test-odbc-mssql.cpp +++ b/tests/odbc/test-odbc-mssql.cpp @@ -116,6 +116,24 @@ struct table_creator_for_get_affected_rows : table_creator_base } }; +struct table_creator_for_clob : table_creator_base +{ + table_creator_for_clob(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, s text)"; + } +}; + +struct table_creator_for_xml : table_creator_base +{ + table_creator_for_xml(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, x xml)"; + } +}; + // // Support for SOCI Common Tests // @@ -127,32 +145,47 @@ public: std::string const &connectString) : test_context_base(backEnd, connectString) {} - table_creator_base* table_creator_1(soci::session& s) const + table_creator_base* table_creator_1(soci::session& s) const SOCI_OVERRIDE { return new table_creator_one(s); } - table_creator_base* table_creator_2(soci::session& s) const + table_creator_base* table_creator_2(soci::session& s) const SOCI_OVERRIDE { return new table_creator_two(s); } - table_creator_base* table_creator_3(soci::session& s) const + table_creator_base* table_creator_3(soci::session& s) const SOCI_OVERRIDE { return new table_creator_three(s); } - table_creator_base * table_creator_4(soci::session& s) const + table_creator_base * table_creator_4(soci::session& s) const SOCI_OVERRIDE { return new table_creator_for_get_affected_rows(s); } - std::string to_date_time(std::string const &datdt_string) const + tests::table_creator_base* table_creator_clob(soci::session& s) const SOCI_OVERRIDE + { + return new table_creator_for_clob(s); + } + + tests::table_creator_base* table_creator_xml(soci::session& s) const SOCI_OVERRIDE + { + return new table_creator_for_xml(s); + } + + bool has_real_xml_support() const SOCI_OVERRIDE + { + return true; + } + + std::string to_date_time(std::string const &datdt_string) const SOCI_OVERRIDE { return "convert(datetime, \'" + datdt_string + "\', 120)"; } - virtual bool has_multiple_select_bug() const + bool has_multiple_select_bug() const SOCI_OVERRIDE { // MS SQL does support MARS (multiple active result sets) since 2005 // version, but this support needs to be explicitly enabled and is not @@ -160,6 +193,11 @@ public: // on the side of caution and suppose that it's not supported. return true; } + + std::string sql_length(std::string const& s) const SOCI_OVERRIDE + { + return "len(" + s + ")"; + } }; int main(int argc, char** argv) diff --git a/tests/odbc/test-odbc-postgresql.cpp b/tests/odbc/test-odbc-postgresql.cpp index 6ff33ec45e..af52648ffd 100644 --- a/tests/odbc/test-odbc-postgresql.cpp +++ b/tests/odbc/test-odbc-postgresql.cpp @@ -128,6 +128,24 @@ struct table_creator_for_get_affected_rows : table_creator_base } }; +struct table_creator_for_xml : table_creator_base +{ + table_creator_for_xml(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, x xml)"; + } +}; + +struct table_creator_for_clob : table_creator_base +{ + table_creator_for_clob(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, s text)"; + } +}; + // // Support for SOCI Common Tests // @@ -143,32 +161,47 @@ public: std::cout << "Using ODBC driver version " << m_verDriver << "\n"; } - table_creator_base * table_creator_1(soci::session& s) const + table_creator_base * table_creator_1(soci::session& s) const SOCI_OVERRIDE { return new table_creator_one(s); } - table_creator_base * table_creator_2(soci::session& s) const + table_creator_base * table_creator_2(soci::session& s) const SOCI_OVERRIDE { return new table_creator_two(s); } - table_creator_base * table_creator_3(soci::session& s) const + table_creator_base * table_creator_3(soci::session& s) const SOCI_OVERRIDE { return new table_creator_three(s); } - table_creator_base * table_creator_4(soci::session& s) const + table_creator_base * table_creator_4(soci::session& s) const SOCI_OVERRIDE { return new table_creator_for_get_affected_rows(s); } - std::string to_date_time(std::string const &datdt_string) const + table_creator_base* table_creator_xml(soci::session& s) const SOCI_OVERRIDE + { + return new table_creator_for_xml(s); + } + + table_creator_base* table_creator_clob(soci::session& s) const SOCI_OVERRIDE + { + return new table_creator_for_clob(s); + } + + bool has_real_xml_support() const SOCI_OVERRIDE + { + return true; + } + + std::string to_date_time(std::string const &datdt_string) const SOCI_OVERRIDE { return "timestamptz(\'" + datdt_string + "\')"; } - virtual bool has_fp_bug() const + bool has_fp_bug() const SOCI_OVERRIDE { // The bug with using insufficiently many digits for double values was // only fixed in 9.03.0400 version of the ODBC driver (see commit @@ -179,37 +212,50 @@ public: return !m_verDriver.is_initialized() || m_verDriver < odbc_version(9, 3, 400); } + std::string sql_length(std::string const& s) const SOCI_OVERRIDE + { + return "char_length(" + s + ")"; + } + private: odbc_version get_driver_version() const { - soci::session sql(get_backend_factory(), get_connect_string()); - odbc_session_backend* const - odbc_session = static_cast(sql.get_backend()); - if (!odbc_session) + try { - std::cerr << "Failed to get odbc_session_backend?\n"; + soci::session sql(get_backend_factory(), get_connect_string()); + odbc_session_backend* const + odbc_session = static_cast(sql.get_backend()); + if (!odbc_session) + { + std::cerr << "Failed to get odbc_session_backend?\n"; + return odbc_version(); + } + + char driver_ver[1024]; + SQLSMALLINT len = sizeof(driver_ver); + SQLRETURN rc = SQLGetInfo(odbc_session->hdbc_, SQL_DRIVER_VER, + driver_ver, len, &len); + if (soci::is_odbc_error(rc)) + { + std::cerr << "Retrieving ODBC driver version failed: " + << rc << "\n"; + return odbc_version(); + } + + odbc_version v; + if (!v.init_from_string(driver_ver)) + { + std::cerr << "Unknown ODBC driver version format: \"" + << driver_ver << "\"\n"; + } + + return v; + } + catch ( ... ) + { + // Failure getting the version is not fatal. return odbc_version(); } - - char driver_ver[1024]; - SQLSMALLINT len = sizeof(driver_ver); - SQLRETURN rc = SQLGetInfo(odbc_session->hdbc_, SQL_DRIVER_VER, - driver_ver, len, &len); - if (soci::is_odbc_error(rc)) - { - std::cerr << "Retrieving ODBC driver version failed: " - << rc << "\n"; - return odbc_version(); - } - - odbc_version v; - if (!v.init_from_string(driver_ver)) - { - std::cerr << "Unknown ODBC driver version format: \"" - << driver_ver << "\"\n"; - } - - return v; } odbc_version const m_verDriver; diff --git a/tests/odbc/test-postgresql-win64.dsn b/tests/odbc/test-postgresql-win64.dsn new file mode 100644 index 0000000000..334ff1d9b8 --- /dev/null +++ b/tests/odbc/test-postgresql-win64.dsn @@ -0,0 +1,9 @@ +[ODBC] +Description=DSN for SOCI ODBC connection to PostgreSQL(AppVeyor) +Driver=PostgreSQL ANSI(x64) +Server=localhost +Port=5432 +Database=soci_test +UID=postgres +PWD=Password12! +UnknownsAsLongVarchar=1 diff --git a/tests/odbc/test-postgresql.dsn b/tests/odbc/test-postgresql.dsn index cc54f6289d..9af148d2a7 100644 --- a/tests/odbc/test-postgresql.dsn +++ b/tests/odbc/test-postgresql.dsn @@ -1,8 +1,9 @@ [ODBC] -Description=DSN for SOCI ODBC connection to PostgreSQL +Description=DSN for SOCI ODBC connection to PostgreSQL(Travis-CI) Driver=PostgreSQL ANSI Server=localhost Port=5432 Database=soci_test UID=postgres -PWD= +PWD=Password12! +UnknownsAsLongVarchar=1 diff --git a/tests/oracle/test-oracle.cpp b/tests/oracle/test-oracle.cpp index 5a3f757c85..b699b22f91 100644 --- a/tests/oracle/test-oracle.cpp +++ b/tests/oracle/test-oracle.cpp @@ -118,48 +118,97 @@ struct blob_table_creator : public table_creator_base TEST_CASE("Oracle blob", "[oracle][blob]") { - soci::session sql(backEnd, connectString); - - blob_table_creator tableCreator(sql); - - char buf[] = "abcdefghijklmnopqrstuvwxyz"; - sql << "insert into soci_test (id, img) values (7, empty_blob())"; - { - blob b(sql); + session sql(backEnd, connectString); - oracle_session_backend *sessionBackEnd - = static_cast(sql.get_backend()); + blob_table_creator tableCreator(sql); - oracle_blob_backend *blobBackEnd - = static_cast(b.get_backend()); + char buf[] = "abcdefghijklmnopqrstuvwxyz"; + sql << "insert into soci_test (id, img) values (7, empty_blob())"; - OCILobDisableBuffering(sessionBackEnd->svchp_, - sessionBackEnd->errhp_, blobBackEnd->lobp_); + { + blob b(sql); - sql << "select img from soci_test where id = 7", into(b); - CHECK(b.get_len() == 0); + oracle_session_backend *sessionBackEnd + = static_cast(sql.get_backend()); - // note: blob offsets start from 1 - b.write(1, buf, sizeof(buf)); - CHECK(b.get_len() == sizeof(buf)); - b.trim(10); - CHECK(b.get_len() == 10); + oracle_blob_backend *blobBackEnd + = static_cast(b.get_backend()); - // append does not work (Oracle bug #886191 ?) - //b.append(buf, sizeof(buf)); - //CHECK(b.get_len() == sizeof(buf) + 10); - sql.commit(); + OCILobDisableBuffering(sessionBackEnd->svchp_, + sessionBackEnd->errhp_, blobBackEnd->lobp_); + + sql << "select img from soci_test where id = 7", into(b); + CHECK(b.get_len() == 0); + + // note: blob offsets start from 1 + b.write(1, buf, sizeof(buf)); + CHECK(b.get_len() == sizeof(buf)); + b.trim(10); + CHECK(b.get_len() == 10); + + // append does not work (Oracle bug #886191 ?) + //b.append(buf, sizeof(buf)); + //assert(b.get_len() == sizeof(buf) + 10); + sql.commit(); + } + + { + blob b(sql); + sql << "select img from soci_test where id = 7", into(b); + //assert(b.get_len() == sizeof(buf) + 10); + CHECK(b.get_len() == 10); + char buf2[100]; + b.read(1, buf2, 10); + CHECK(strncmp(buf2, "abcdefghij", 10) == 0); + } } + // additional sibling test for read_from_start and write_from_start { - blob b(sql); - sql << "select img from soci_test where id = 7", into(b); - //CHECK(b.get_len() == sizeof(buf) + 10); - CHECK(b.get_len() == 10); - char buf2[100]; - b.read(1, buf2, 10); - CHECK(strncmp(buf2, "abcdefghij", 10) == 0); + session sql(backEnd, connectString); + + blob_table_creator tableCreator(sql); + + char buf[] = "abcdefghijklmnopqrstuvwxyz"; + sql << "insert into soci_test (id, img) values (7, empty_blob())"; + + { + blob b(sql); + + oracle_session_backend *sessionBackEnd + = static_cast(sql.get_backend()); + + oracle_blob_backend *blobBackEnd + = static_cast(b.get_backend()); + + OCILobDisableBuffering(sessionBackEnd->svchp_, + sessionBackEnd->errhp_, blobBackEnd->lobp_); + + sql << "select img from soci_test where id = 7", into(b); + CHECK(b.get_len() == 0); + + // note: blob offsets start from 1 + b.write_from_start(buf, sizeof(buf)); + CHECK(b.get_len() == sizeof(buf)); + b.trim(10); + CHECK(b.get_len() == 10); + + // append does not work (Oracle bug #886191 ?) + //b.append(buf, sizeof(buf)); + //assert(b.get_len() == sizeof(buf) + 10); + sql.commit(); + } + + { + blob b(sql); + sql << "select img from soci_test where id = 7", into(b); + //assert(b.get_len() == sizeof(buf) + 10); + CHECK(b.get_len() == 10); + char buf2[100]; + b.read_from_start(buf2, 10); + CHECK(strncmp(buf2, "abcdefghij", 10) == 0); + } } } @@ -1062,6 +1111,316 @@ TEST_CASE("Oracle long long", "[oracle][longlong]") } } +// Test the DDL and metadata functionality +TEST_CASE("Oracle DDL with metadata", "[oracle][ddl]") +{ + soci::session sql(backEnd, connectString); + + // note: prepare_column_descriptions expects l-value + std::string ddl_t1 = "DDL_T1"; + std::string ddl_t2 = "DDL_T2"; + std::string ddl_t3 = "DDL_T3"; + + // single-expression variant: + sql.create_table(ddl_t1).column("I", soci::dt_integer).column("J", soci::dt_integer); + + // check whether this table was created: + + bool ddl_t1_found = false; + bool ddl_t2_found = false; + bool ddl_t3_found = false; + std::string table_name; + soci::statement st = (sql.prepare_table_names(), into(table_name)); + st.execute(); + while (st.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); + + // check whether ddl_t1 has the right structure: + + bool i_found = false; + bool j_found = false; + bool other_found = false; + soci::column_info ci; + soci::statement st1 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st1.execute(); + while (st1.fetch()) + { + if (ci.name == "I") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "J") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + j_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(other_found == false); + + // two more tables: + + // separately defined columns: + // (note: statement is executed when ddl object goes out of scope) + { + soci::ddl_type ddl = sql.create_table(ddl_t2); + ddl.column("I", soci::dt_integer); + ddl.column("J", soci::dt_integer); + ddl.column("K", soci::dt_integer)("not null"); + ddl.primary_key("t2_pk", "J"); + } + + sql.add_column(ddl_t1, "K", soci::dt_integer); + sql.add_column(ddl_t1, "BIG", soci::dt_string, 0); // "unlimited" length -> CLOB + sql.drop_column(ddl_t1, "I"); + + // or with constraint as in t2: + sql.add_column(ddl_t2, "M", soci::dt_integer)("not null"); + + // third table with a foreign key to the second one + { + soci::ddl_type ddl = sql.create_table(ddl_t3); + ddl.column("X", soci::dt_integer); + ddl.column("Y", soci::dt_integer); + ddl.foreign_key("t3_fk", "X", ddl_t2, "J"); + } + + // check if all tables were created: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + soci::statement st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found); + CHECK(ddl_t3_found); + + // check if ddl_t1 has the right structure (it was altered): + + i_found = false; + j_found = false; + bool k_found = false; + bool big_found = false; + other_found = false; + soci::statement st3 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st3.execute(); + while (st3.fetch()) + { + if (ci.name == "J") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + j_found = true; + } + else if (ci.name == "K") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + k_found = true; + } + else if (ci.name == "BIG") + { + CHECK(ci.type == soci::dt_string); + CHECK(ci.precision == 0); // "unlimited" for strings + big_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found == false); + CHECK(j_found); + CHECK(k_found); + CHECK(big_found); + CHECK(other_found == false); + + // check if ddl_t2 has the right structure: + + i_found = false; + j_found = false; + k_found = false; + bool m_found = false; + other_found = false; + soci::statement st4 = (sql.prepare_column_descriptions(ddl_t2), into(ci)); + st4.execute(); + while (st4.fetch()) + { + if (ci.name == "I") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "J") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); // primary key + j_found = true; + } + else if (ci.name == "K") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); + k_found = true; + } + else if (ci.name == "M") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); + m_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(k_found); + CHECK(m_found); + CHECK(other_found == false); + + sql.drop_table(ddl_t1); + sql.drop_table(ddl_t3); // note: this must be dropped before ddl_t2 + sql.drop_table(ddl_t2); + + // check if all tables were dropped: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found == false); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); + + int i = -1; + sql << "select length(" + sql.empty_blob() + ") from dual", into(i); + CHECK(i == 0); + sql << "select " + sql.nvl() + "(1, 2) from dual", into(i); + CHECK(i == 1); + sql << "select " + sql.nvl() + "(NULL, 2) from dual", into(i); + CHECK(i == 2); +} + +// Test the bulk iterators functionality +TEST_CASE("Bulk iterators", "[oracle][bulkiters]") +{ + soci::session sql(backEnd, connectString); + + sql << "create table t (i integer)"; + + // test bulk iterators with basic types + { + std::vector v; + v.push_back(10); + v.push_back(20); + v.push_back(30); + v.push_back(40); + v.push_back(50); + + std::size_t begin = 2; + std::size_t end = 5; + sql << "insert into t (i) values (:v)", soci::use(v, begin, end); + + v.clear(); + v.resize(20); + begin = 5; + end = 20; + sql << "select i from t", soci::into(v, begin, end); + + CHECK(end == 8); + for (std::size_t i = 0; i != 5; ++i) + { + CHECK(v[i] == 0); + } + CHECK(v[5] == 30); + CHECK(v[6] == 40); + CHECK(v[7] == 50); + for (std::size_t i = end; i != 20; ++i) + { + CHECK(v[i] == 0); + } + } + + sql << "delete from t"; + + // test bulk iterators with user types + { + std::vector v; + v.push_back(MyInt(10)); + v.push_back(MyInt(20)); + v.push_back(MyInt(30)); + v.push_back(MyInt(40)); + v.push_back(MyInt(50)); + + std::size_t begin = 2; + std::size_t end = 5; + sql << "insert into t (i) values (:v)", soci::use(v, begin, end); + + v.clear(); + for (std::size_t i = 0; i != 20; ++i) + { + v.push_back(MyInt(-1)); + } + + begin = 5; + end = 20; + sql << "select i from t", soci::into(v, begin, end); + + CHECK(end == 8); + for (std::size_t i = 0; i != 5; ++i) + { + CHECK(v[i].get() == -1); + } + CHECK(v[5].get() == 30); + CHECK(v[6].get() == 40); + CHECK(v[7].get() == 50); + for (std::size_t i = end; i != 20; ++i) + { + CHECK(v[i].get() == -1); + } + } + + sql << "drop table t"; +} + // // Support for soci Common Tests // @@ -1107,6 +1466,24 @@ struct table_creator_four : public table_creator_base } }; +struct table_creator_for_xml : table_creator_base +{ + table_creator_for_xml(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, x xmltype)"; + } +}; + +struct table_creator_for_clob : table_creator_base +{ + table_creator_for_clob(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, s clob)"; + } +}; + class test_context :public test_context_base { public: @@ -1114,30 +1491,65 @@ public: std::string const &connectString) : test_context_base(backEnd, connectString) {} - table_creator_base* table_creator_1(soci::session& s) const + table_creator_base* table_creator_1(soci::session& s) const SOCI_OVERRIDE { return new table_creator_one(s); } - table_creator_base* table_creator_2(soci::session& s) const + table_creator_base* table_creator_2(soci::session& s) const SOCI_OVERRIDE { return new table_creator_two(s); } - table_creator_base* table_creator_3(soci::session& s) const + table_creator_base* table_creator_3(soci::session& s) const SOCI_OVERRIDE { return new table_creator_three(s); } - table_creator_base* table_creator_4(soci::session& s) const + table_creator_base* table_creator_4(soci::session& s) const SOCI_OVERRIDE { return new table_creator_four(s); } - std::string to_date_time(std::string const &datdt_string) const + table_creator_base* table_creator_clob(soci::session& s) const SOCI_OVERRIDE + { + return new table_creator_for_clob(s); + } + + table_creator_base* table_creator_xml(soci::session& s) const SOCI_OVERRIDE + { + return new table_creator_for_xml(s); + } + + std::string to_xml(std::string const& x) const SOCI_OVERRIDE + { + return "xmltype(" + x + ")"; + } + + std::string from_xml(std::string const& x) const SOCI_OVERRIDE + { + // Notice that using just x.getCLOBVal() doesn't work, only + // table.x.getCLOBVal() or (x).getCLOBVal(), as used here, does. + return "(" + x + ").getCLOBVal()"; + } + + bool has_real_xml_support() const SOCI_OVERRIDE + { + return true; + } + + std::string to_date_time(std::string const &datdt_string) const SOCI_OVERRIDE { return "to_date('" + datdt_string + "', 'YYYY-MM-DD HH24:MI:SS')"; } + + std::string sql_length(std::string const& s) const SOCI_OVERRIDE + { + // Oracle treats empty strings as NULLs, but we want to return the + // length of 0 for them for consistency with the other backends, so use + // nvl() explicitly to achieve this. + return "nvl(length(" + s + "), 0)"; + } }; int main(int argc, char** argv) diff --git a/tests/postgresql/test-postgresql.cpp b/tests/postgresql/test-postgresql.cpp index b8fd1b27d8..9b0b4c86ec 100644 --- a/tests/postgresql/test-postgresql.cpp +++ b/tests/postgresql/test-postgresql.cpp @@ -195,41 +195,82 @@ struct blob_table_creator : public table_creator_base TEST_CASE("PostgreSQL blob", "[postgresql][blob]") { - soci::session sql(backEnd, connectString); - - blob_table_creator tableCreator(sql); - - char buf[] = "abcdefghijklmnopqrstuvwxyz"; - - sql << "insert into soci_test(id, img) values(7, lo_creat(-1))"; - - // in PostgreSQL, BLOB operations must be within transaction block - transaction tr(sql); - { - blob b(sql); + soci::session sql(backEnd, connectString); - sql << "select img from soci_test where id = 7", into(b); - CHECK(b.get_len() == 0); + blob_table_creator tableCreator(sql); - b.write(0, buf, sizeof(buf)); - CHECK(b.get_len() == sizeof(buf)); + char buf[] = "abcdefghijklmnopqrstuvwxyz"; - b.append(buf, sizeof(buf)); - CHECK(b.get_len() == 2 * sizeof(buf)); - } - { - blob b(sql); - sql << "select img from soci_test where id = 7", into(b); - CHECK(b.get_len() == 2 * sizeof(buf)); - char buf2[100]; - b.read(0, buf2, 10); - CHECK(std::strncmp(buf2, "abcdefghij", 10) == 0); + sql << "insert into soci_test(id, img) values(7, lo_creat(-1))"; + + // in PostgreSQL, BLOB operations must be within transaction block + transaction tr(sql); + + { + blob b(sql); + + sql << "select img from soci_test where id = 7", into(b); + CHECK(b.get_len() == 0); + + b.write(0, buf, sizeof(buf)); + CHECK(b.get_len() == sizeof(buf)); + + b.append(buf, sizeof(buf)); + CHECK(b.get_len() == 2 * sizeof(buf)); + } + { + blob b(sql); + sql << "select img from soci_test where id = 7", into(b); + CHECK(b.get_len() == 2 * sizeof(buf)); + char buf2[100]; + b.read(0, buf2, 10); + CHECK(std::strncmp(buf2, "abcdefghij", 10) == 0); + } + + unsigned long oid; + sql << "select img from soci_test where id = 7", into(oid); + sql << "select lo_unlink(" << oid << ")"; } - unsigned long oid; - sql << "select img from soci_test where id = 7", into(oid); - sql << "select lo_unlink(" << oid << ")"; + // additional sibling test for read_from_start and write_from_start + { + soci::session sql(backEnd, connectString); + + blob_table_creator tableCreator(sql); + + char buf[] = "abcdefghijklmnopqrstuvwxyz"; + + sql << "insert into soci_test(id, img) values(7, lo_creat(-1))"; + + // in PostgreSQL, BLOB operations must be within transaction block + transaction tr(sql); + + { + blob b(sql); + + sql << "select img from soci_test where id = 7", into(b); + CHECK(b.get_len() == 0); + + b.write_from_start(buf, sizeof(buf)); + CHECK(b.get_len() == sizeof(buf)); + + b.append(buf, sizeof(buf)); + CHECK(b.get_len() == 2 * sizeof(buf)); + } + { + blob b(sql); + sql << "select img from soci_test where id = 7", into(b); + CHECK(b.get_len() == 2 * sizeof(buf)); + char buf2[100]; + b.read_from_start(buf2, 10); + CHECK(std::strncmp(buf2, "abcdefghij", 10) == 0); + } + + unsigned long oid; + sql << "select img from soci_test where id = 7", into(oid); + sql << "select lo_unlink(" << oid << ")"; + } } struct longlong_table_creator : table_creator_base @@ -329,6 +370,31 @@ TEST_CASE("PostgreSQL boolean", "[postgresql][boolean]") CHECK(i2 == 1); } +struct uuid_table_creator : table_creator_base +{ + uuid_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val uuid)"; + } +}; + +// uuid test +TEST_CASE("PostgreSQL uuid", "[postgresql][uuid]") +{ + soci::session sql(backEnd, connectString); + + uuid_table_creator tableCreator(sql); + + std::string v1("cd2dcb78-3817-442e-b12a-17c7e42669a0"); + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::string v2; + sql << "select val from soci_test", into(v2); + + CHECK(v2 == v1); +} + // dynamic backend test -- currently skipped by default TEST_CASE("PostgreSQL dynamic backend", "[postgresql][backend][.]") { @@ -410,7 +476,7 @@ TEST_CASE("PostgreSQL datetime", "[postgresql][datetime]") soci::session sql(backEnd, connectString); std::string someDate = "2009-06-17 22:51:03.123"; - std::tm t1, t2, t3; + std::tm t1 = std::tm(), t2 = std::tm(), t3 = std::tm(); sql << "select :sd::date, :sd::time, :sd::timestamp", use(someDate, "sd"), into(t1), into(t2), into(t3); @@ -662,6 +728,316 @@ TEST_CASE("PostgreSQL ORM cast", "[postgresql][orm]") sql << "select :a::int", use(v); // Must not throw an exception! } +// Test the DDL and metadata functionality +TEST_CASE("PostgreSQL DDL with metadata", "[postgresql][ddl]") +{ + soci::session sql(backEnd, connectString); + + // note: prepare_column_descriptions expects l-value + std::string ddl_t1 = "ddl_t1"; + std::string ddl_t2 = "ddl_t2"; + std::string ddl_t3 = "ddl_t3"; + + // single-expression variant: + sql.create_table(ddl_t1).column("i", soci::dt_integer).column("j", soci::dt_integer); + + // check whether this table was created: + + bool ddl_t1_found = false; + bool ddl_t2_found = false; + bool ddl_t3_found = false; + std::string table_name; + soci::statement st = (sql.prepare_table_names(), into(table_name)); + st.execute(); + while (st.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); + + // check whether ddl_t1 has the right structure: + + bool i_found = false; + bool j_found = false; + bool other_found = false; + soci::column_info ci; + soci::statement st1 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st1.execute(); + while (st1.fetch()) + { + if (ci.name == "i") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "j") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + j_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(other_found == false); + + // two more tables: + + // separately defined columns: + // (note: statement is executed when ddl object goes out of scope) + { + soci::ddl_type ddl = sql.create_table(ddl_t2); + ddl.column("i", soci::dt_integer); + ddl.column("j", soci::dt_integer); + ddl.column("k", soci::dt_integer)("not null"); + ddl.primary_key("t2_pk", "j"); + } + + sql.add_column(ddl_t1, "k", soci::dt_integer); + sql.add_column(ddl_t1, "big", soci::dt_string, 0); // "unlimited" length -> text + sql.drop_column(ddl_t1, "i"); + + // or with constraint as in t2: + sql.add_column(ddl_t2, "m", soci::dt_integer)("not null"); + + // third table with a foreign key to the second one + { + soci::ddl_type ddl = sql.create_table(ddl_t3); + ddl.column("x", soci::dt_integer); + ddl.column("y", soci::dt_integer); + ddl.foreign_key("t3_fk", "x", ddl_t2, "j"); + } + + // check if all tables were created: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + soci::statement st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found); + CHECK(ddl_t3_found); + + // check if ddl_t1 has the right structure (it was altered): + + i_found = false; + j_found = false; + bool k_found = false; + bool big_found = false; + other_found = false; + soci::statement st3 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st3.execute(); + while (st3.fetch()) + { + if (ci.name == "j") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + j_found = true; + } + else if (ci.name == "k") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + k_found = true; + } + else if (ci.name == "big") + { + CHECK(ci.type == soci::dt_string); + CHECK(ci.precision == 0); // "unlimited" for strings + big_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found == false); + CHECK(j_found); + CHECK(k_found); + CHECK(big_found); + CHECK(other_found == false); + + // check if ddl_t2 has the right structure: + + i_found = false; + j_found = false; + k_found = false; + bool m_found = false; + other_found = false; + soci::statement st4 = (sql.prepare_column_descriptions(ddl_t2), into(ci)); + st4.execute(); + while (st4.fetch()) + { + if (ci.name == "i") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "j") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); // primary key + j_found = true; + } + else if (ci.name == "k") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); + k_found = true; + } + else if (ci.name == "m") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); + m_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(k_found); + CHECK(m_found); + CHECK(other_found == false); + + sql.drop_table(ddl_t1); + sql.drop_table(ddl_t3); // note: this must be dropped before ddl_t2 + sql.drop_table(ddl_t2); + + // check if all tables were dropped: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found == false); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); + + int i = -1; + sql << "select lo_unlink(" + sql.empty_blob() + ")", into(i); + CHECK(i == 1); + sql << "select " + sql.nvl() + "(1, 2)", into(i); + CHECK(i == 1); + sql << "select " + sql.nvl() + "(NULL, 2)", into(i); + CHECK(i == 2); +} + +// Test the bulk iterators functionality +TEST_CASE("Bulk iterators", "[postgresql][bulkiters]") +{ + soci::session sql(backEnd, connectString); + + sql << "create table t (i integer)"; + + // test bulk iterators with basic types + { + std::vector v; + v.push_back(10); + v.push_back(20); + v.push_back(30); + v.push_back(40); + v.push_back(50); + + std::size_t begin = 2; + std::size_t end = 5; + sql << "insert into t (i) values (:v)", soci::use(v, begin, end); + + v.clear(); + v.resize(20); + begin = 5; + end = 20; + sql << "select i from t", soci::into(v, begin, end); + + CHECK(end == 8); + for (std::size_t i = 0; i != 5; ++i) + { + CHECK(v[i] == 0); + } + CHECK(v[5] == 30); + CHECK(v[6] == 40); + CHECK(v[7] == 50); + for (std::size_t i = end; i != 20; ++i) + { + CHECK(v[i] == 0); + } + } + + sql << "delete from t"; + + // test bulk iterators with user types + { + std::vector v; + v.push_back(MyInt(10)); + v.push_back(MyInt(20)); + v.push_back(MyInt(30)); + v.push_back(MyInt(40)); + v.push_back(MyInt(50)); + + std::size_t begin = 2; + std::size_t end = 5; + sql << "insert into t (i) values (:v)", soci::use(v, begin, end); + + v.clear(); + for (std::size_t i = 0; i != 20; ++i) + { + v.push_back(MyInt(-1)); + } + + begin = 5; + end = 20; + sql << "select i from t", soci::into(v, begin, end); + + CHECK(end == 8); + for (std::size_t i = 0; i != 5; ++i) + { + CHECK(v[i].get() == -1); + } + CHECK(v[5].get() == 30); + CHECK(v[6].get() == 40); + CHECK(v[7].get() == 50); + for (std::size_t i = end; i != 20; ++i) + { + CHECK(v[i].get() == -1); + } + } + + sql << "drop table t"; +} + // // Support for soci Common Tests // @@ -709,6 +1085,24 @@ struct table_creator_for_get_affected_rows : table_creator_base } }; +struct table_creator_for_xml : table_creator_base +{ + table_creator_for_xml(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, x xml)"; + } +}; + +struct table_creator_for_clob : table_creator_base +{ + table_creator_for_clob(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, s text)"; + } +}; + // Common tests context class test_context : public test_context_base { @@ -717,35 +1111,55 @@ public: : test_context_base(backEnd, connectString) {} - table_creator_base* table_creator_1(soci::session& s) const + table_creator_base* table_creator_1(soci::session& s) const SOCI_OVERRIDE { return new table_creator_one(s); } - table_creator_base* table_creator_2(soci::session& s) const + table_creator_base* table_creator_2(soci::session& s) const SOCI_OVERRIDE { return new table_creator_two(s); } - table_creator_base* table_creator_3(soci::session& s) const + table_creator_base* table_creator_3(soci::session& s) const SOCI_OVERRIDE { return new table_creator_three(s); } - table_creator_base* table_creator_4(soci::session& s) const + table_creator_base* table_creator_4(soci::session& s) const SOCI_OVERRIDE { return new table_creator_for_get_affected_rows(s); } - std::string to_date_time(std::string const &datdt_string) const + table_creator_base* table_creator_xml(soci::session& s) const SOCI_OVERRIDE + { + return new table_creator_for_xml(s); + } + + table_creator_base* table_creator_clob(soci::session& s) const SOCI_OVERRIDE + { + return new table_creator_for_clob(s); + } + + bool has_real_xml_support() const SOCI_OVERRIDE + { + return true; + } + + std::string to_date_time(std::string const &datdt_string) const SOCI_OVERRIDE { return "timestamptz(\'" + datdt_string + "\')"; } - virtual bool has_fp_bug() const + bool has_fp_bug() const SOCI_OVERRIDE { return false; } + + std::string sql_length(std::string const& s) const SOCI_OVERRIDE + { + return "char_length(" + s + ")"; + } }; int main(int argc, char** argv) diff --git a/tests/sqlite3/Makefile.basic b/tests/sqlite3/Makefile.basic new file mode 100644 index 0000000000..697d67404d --- /dev/null +++ b/tests/sqlite3/Makefile.basic @@ -0,0 +1,12 @@ +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I../../include -I../../include/private -I.. -I/usr/include/sqlite3 +LIBDIRS = -L../../src/core -L../../src/backends/sqlite3 -L/usr/lib/x86_64-linux-gnu +LIBS = -lsoci_sqlite3 -lsoci_core -ldl -lsqlite3 + +test-sqlite3 : test-sqlite3.cpp + ${COMPILER} $? -o $@ ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + + +clean : + rm -f test-sqlite3 diff --git a/tests/sqlite3/test-sqlite3.cpp b/tests/sqlite3/test-sqlite3.cpp index 5b27981c1a..33878fbe8b 100644 --- a/tests/sqlite3/test-sqlite3.cpp +++ b/tests/sqlite3/test-sqlite3.cpp @@ -237,6 +237,19 @@ TEST_CASE("SQLite vector long long", "[sqlite][vector][longlong]") CHECK(v2[4] == 1000000000000LL); } +TEST_CASE("SQLite DDL wrappers", "[sqlite][ddl]") +{ + soci::session sql(backEnd, connectString); + + int i = -1; + sql << "select length(" + sql.empty_blob() + ")", into(i); + CHECK(i == 0); + sql << "select " + sql.nvl() + "(1, 2)", into(i); + CHECK(i == 1); + sql << "select " + sql.nvl() + "(NULL, 2)", into(i); + CHECK(i == 2); +} + struct table_creator_for_get_last_insert_id : table_creator_base { table_creator_for_get_last_insert_id(soci::session & sql) @@ -318,32 +331,32 @@ public: std::string const &connectString) : test_context_base(backEnd, connectString) {} - table_creator_base* table_creator_1(soci::session& s) const + table_creator_base* table_creator_1(soci::session& s) const SOCI_OVERRIDE { return new table_creator_one(s); } - table_creator_base* table_creator_2(soci::session& s) const + table_creator_base* table_creator_2(soci::session& s) const SOCI_OVERRIDE { return new table_creator_two(s); } - table_creator_base* table_creator_3(soci::session& s) const + table_creator_base* table_creator_3(soci::session& s) const SOCI_OVERRIDE { return new table_creator_three(s); } - table_creator_base* table_creator_4(soci::session& s) const + table_creator_base* table_creator_4(soci::session& s) const SOCI_OVERRIDE { return new table_creator_for_get_affected_rows(s); } - std::string to_date_time(std::string const &datdt_string) const + std::string to_date_time(std::string const &datdt_string) const SOCI_OVERRIDE { return "datetime(\'" + datdt_string + "\')"; } - virtual bool has_fp_bug() const + bool has_fp_bug() const SOCI_OVERRIDE { /* SQLite seems to be buggy when using text conversion, e.g.: @@ -361,11 +374,16 @@ public: return true; } - virtual bool enable_std_char_padding(soci::session& s) const + bool enable_std_char_padding(soci::session&) const SOCI_OVERRIDE { // SQLite does not support right padded char type. return false; } + + std::string sql_length(std::string const& s) const SOCI_OVERRIDE + { + return "length(" + s + ")"; + } }; int main(int argc, char** argv) diff --git a/valgrind.suppress b/valgrind.suppress new file mode 100644 index 0000000000..5c5d2fdd01 --- /dev/null +++ b/valgrind.suppress @@ -0,0 +1,9 @@ +# Reported as a leak in Travis CI Ubuntu 12.04.5 LTS builds. +{ + ignored libc NSS leak + Memcheck:Leak + fun:malloc + fun:nss_parse_service_list + fun:__nss_database_lookup +} + diff --git a/www/doc.html b/www/doc.html index 5fdfa04e29..2328afe624 100644 --- a/www/doc.html +++ b/www/doc.html @@ -31,7 +31,8 @@

SOCI documentation:

diff --git a/www/doc/index.html b/www/doc/index.html index 141815c3a2..eecab5930b 100644 --- a/www/doc/index.html +++ b/www/doc/index.html @@ -31,7 +31,8 @@

SOCI documentation: