diff --git a/.gitignore b/.gitignore index 405ab0d53b..609b4e844a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # General *.swp +*.kate-swp tags tmp @@ -8,6 +9,16 @@ _build* src/_build* src/build +# Files generated by CMake +Makefile +src/core/soci_backends_config.h + +# ... and the rest of CMake spam +CMakeFiles/ +CMakeCache.txt +CTestTestfile.cmake +cmake_install.cmake + # Visual Studio *.opensdf *.sdf @@ -22,3 +33,7 @@ src/build *.creator* *.files *.includes +CMakeLists.txt.user + +# Eclipse +/.project diff --git a/.travis.yml b/.travis.yml index 894f6a63f1..bfe6ff13cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,15 +14,20 @@ services: - postgresql env: - matrix: - - SOCI_TRAVIS_BACKEND=db2 - - SOCI_TRAVIS_BACKEND=empty - - SOCI_TRAVIS_BACKEND=firebird - - SOCI_TRAVIS_BACKEND=mysql - - SOCI_TRAVIS_BACKEND=odbc - - SOCI_TRAVIS_BACKEND=oracle - - SOCI_TRAVIS_BACKEND=postgresql - - SOCI_TRAVIS_BACKEND=sqlite3 + - 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 + +matrix: + fast_finish: true + allow_failures: + - env: SOCI_TRAVIS_BACKEND=postgression before_install: ./bin/ci/before_install.sh before_script: ./bin/ci/before_script.sh @@ -32,8 +37,8 @@ notifications: email: recipients: - soci-devel@lists.sourceforge.net - on_success: change # [always|never|change] # default: change - on_failure: always # [always|never|change] # default: always + on_success: change # [always|never|change] # default: change + on_failure: always # [always|never|change] # default: always irc: channels: diff --git a/src/AUTHORS b/AUTHORS similarity index 87% rename from src/AUTHORS rename to AUTHORS index 8f6c336433..0b4d5487a8 100644 --- a/src/AUTHORS +++ b/AUTHORS @@ -19,6 +19,8 @@ Viacheslav Naydenov We would like to thank you for your contributions - they allowed us to improve the quality of the SOCI library: +Adesmier +Alex Volanis Andrey Belobrov Andrey Utkin Andriy Gapon @@ -52,6 +54,7 @@ Matt Arsenault Matthieu Kermagoret Michael Davidsaver Mika Fischer +pacocamberos Paul Bondo Petr Vanek Philip Pemberton @@ -69,5 +72,7 @@ Tomasz Olszewski Vaclav Slavik xol -There are a lot of people that help to drive SOCI project forward, -if we have forgot to mention someone in here, send us an email! +There are a lot of people that help to drive SOCI project forward, +if we have forgot to mention someone in here, +if you would like to be listed by name instead of GitHub username, +send us an email! diff --git a/src/CHANGES b/CHANGES similarity index 88% rename from src/CHANGES rename to CHANGES index fcb2adc576..b66729305d 100644 --- a/src/CHANGES +++ b/CHANGES @@ -1,10 +1,55 @@ This file contains the history of changes in the SOCI library. +--- +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(). + +- Firebird +-- Add SOCI_FIREBIRD_EMBEDDED option to allow building with embedded library. +-- Throw an exception instead of truncating too long VARCHAR columns values. + +- ODBC/MS SQL +-- Fix inserting strings of length greater than 8000 bytes into database. + +- Oracle +-- Use SQLT_BDOUBLE for floating point values instead of SQLT_FLT. + +--- +Version 3.2.3 differs from 3.2.2 in the following ways: + +- Improved Boost Tuple & Fusion integration by using boost::fusion::foreach + to reference each member of a sequence. Breaks compatibility with Boost 1.35 (2008). +- Fixed linker error when building 64-bit target with Visual Studio. +- Fixed several issues with building using Cygwin and MinGW. +- Clarified documentation and examples on bulk operations. + +- MySQL +-- Fixed building against MySQL 3.23. + +- ODBC +- Improve readability of ODBC error messages. +- Fixed CMake configuration of ODBC backend for Visual Studio 64-bit targets. + +- Oracle +-- We've had to disable Oracle target in the Travis CI configuration until we + figure out how to setup Oracle on Travis CI directly. + Therefore, this release hasn't been extensively tested against Oracle. + +- PostgreSQL +-- Added support for UUID column type (tests and docs updated). + +- SQLite3 +-- Added sqlite3_soci_error exception as subclass of soci_error to provide useful + exposure of specific SQLite3 error codes (tests and docs updated). + --- Version 3.2.2 differs from 3.2.1 in the following ways: - Fixed once_temp_type destructor with noexcept(false) specifier for C++11 -- Fix uninitialized indicators in conversion_into_type and conversion_use_type specialisations +- Fix uninitialized indicators in conversion_into_type and conversion_use_type specialisations - Fixed placeholder matching for PostgreSQL-style casts with ORM - Fixed memory leaking in use binding in case of bind/unbind sequence - Fixed sscanf formatter for MinGW/MSVC in backends @@ -77,7 +122,7 @@ Version 3.2.1 differs from 3.2.0 in the following ways: --- Version 3.2.0 differs from 3.1.0 in the following ways: -- SOCI is now organization at GitHub +- SOCI is now organization at GitHub -- Git repository moved to https://github.com/SOCI/soci -- Opened new bug tracker (SF.net tracker is read-only) -- Opened Wiki for FAQ and development articles @@ -124,7 +169,7 @@ Version 3.2.0 differs from 3.1.0 in the following ways: -- Fixed issues in binding procedure IN/OUT parameters - PostgreSQL --- Add reading of BYTEA data into std::string (not fully-featured binary data support yet) +-- Add reading of BYTEA data into std::string (not fully-featured binary data support yet) -- Add JSON data type support available in PostgreSQL 9.2+ -- Fixed incorrect assertion in postgresql::get_error_details -- Fixed premature deallocation of prepared statements @@ -297,7 +342,7 @@ Version 2.0.1 differs from 2.0.0 in the following ways: - A bug fix to correctly handle std::tm in the Oracle backend. - A bug fix to correctly handle object relational mapping when - Values::set() and Values::get() are called where T is a + Values::set() and Values::get() are called where T is a TypeConversion-based type. --- diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..8d686b2f74 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,159 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2009-2013 Mateusz Loskot +# 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) +# +############################################################################### +# General settings +############################################################################### +cmake_minimum_required(VERSION 2.8.0 FATAL_ERROR) + +project(SOCI) + +############################################################################### +# SOCI CMake modules +############################################################################### + +# Path to additional CMake modules +set(CMAKE_MODULE_PATH ${SOCI_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) +set(CMAKE_MODULE_PATH ${SOCI_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) + +include(SociUtilities) +include(SociConfig) + +colormsg(_HIBLUE_ "Configuring SOCI:") + +############################################################################### +# SOCI version information +############################################################################### +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) + +############################################################################### +# 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) + +# from SociConfig.cmake +boost_report_value(SOCI_CXX_C11) + +# Put the libaries and binaries that get built into directories at the +# top of the build tree rather than in hard-to-find leaf +# directories. This simplifies manual testing and the use of the build +# tree rather than installed Boost libraries. +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +############################################################################### +# Find SOCI dependencies +############################################################################### + +set(SOCI_CORE_TARGET) +set(SOCI_CORE_TARGET_STATIC) +set(SOCI_CORE_DEPS_LIBS) + +include(SociDependencies) + +get_property(SOCI_INCLUDE_DIRS DIRECTORY ${CMAKE_SOURCE_DIR} + PROPERTY INCLUDE_DIRECTORIES) + +if(Threads_FOUND) + list(APPEND SOCI_CORE_DEPS_LIBS ${CMAKE_THREAD_LIBS_INIT}) +else() + message(FATAL_ERROR "No thread library found") +endif() + +if(NOT MSVC) + set(DL_FIND_QUIETLY TRUE) + find_package(DL) + if(DL_FOUND) + list(APPEND SOCI_CORE_DEPS_LIBS ${DL_LIBRARY}) + 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} + PROPERTY COMPILE_DEFINITIONS) + + list(APPEND SOCI_COMPILE_DEFINITIONS "HAVE_BOOST=1") + + 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") + endif() + + list(APPEND SOCI_INCLUDE_DIRS ${Boost_INCLUDE_DIRS}) + list(APPEND SOCI_CORE_INCLUDE_DIRS ${Boost_INCLUDE_DIRS}) + + set_directory_properties(PROPERTY COMPILE_DEFINITIONS "${SOCI_COMPILE_DEFINITIONS}") + + set_property(DIRECTORY ${SOCI_SOURCE_DIR} + PROPERTY COMPILE_DEFINITIONS "${SOCI_COMPILE_DEFINITIONS}") +endif() + +list(APPEND SOCI_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}) + +set_property(DIRECTORY ${CMAKE_SOURCE_DIR} + PROPERTY + INCLUDE_DIRECTORIES ${SOCI_INCLUDE_DIRS}) + +############################################################################### +# Installation +############################################################################### + +if(NOT DEFINED SOCI_LIBDIR) + if(APPLE OR CMAKE_SIZEOF_VOID_P EQUAL 4) + set(SOCI_LIBDIR "lib") + else() + set(SOCI_LIBDIR "lib64") + endif() +endif() + +set(BINDIR "bin" CACHE PATH "The directory to install binaries into.") +set(LIBDIR ${SOCI_LIBDIR} CACHE PATH "The directory to install libraries into.") +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 +############################################################################### +enable_testing() +# Configure for testing with dashboard submissions to CDash +#include(CTest) # disabled as unused + +# 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) + +add_subdirectory(src) +add_subdirectory(tests) + +message(STATUS "") + diff --git a/src/CTestConfig.cmake b/CTestConfig.cmake similarity index 100% rename from src/CTestConfig.cmake rename to CTestConfig.cmake diff --git a/src/LICENSE_1_0.txt b/LICENSE_1_0.txt similarity index 100% rename from src/LICENSE_1_0.txt rename to LICENSE_1_0.txt diff --git a/README.md b/README.md index 0262964528..5f93f8a9a6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Website: http://soci.sourceforge.net GitHub hosts SOCI source code repository, issues tracker and wiki: https://github.com/SOCI -Downloads and mailing lists at +Downloads and mailing lists at http://sourceforge.net/projects/soci/ Travis CI service at https://travis-ci.org/SOCI/soci @@ -23,18 +23,18 @@ Requirements Core: * C++ compiler -* Boost C++ Libraries (optional, headers only) +* Boost C++ Libraries (optional, headers and Boost.DateTime) Backend specific client libraries for: * DB2 * Firebird * MySQL -* ODBC andwith specific database driver +* ODBC with specific database driver * Oracle * PostgreSQL * SQLite 3 -See documentation at http://soci.sourceforge.net for details +See documentation at http://soci.sourceforge.net for details Brief History ------------- diff --git a/src/ideas.txt b/TODO similarity index 98% rename from src/ideas.txt rename to TODO index 37fb670bf3..40a828904c 100644 --- a/src/ideas.txt +++ b/TODO @@ -75,7 +75,7 @@ Additional pair based val/indicator interface? Consolidate iteration methods? most radical: do we still need Statement::fetch()? into()? (Rowset can currently be used for any query, supports indicators, -defaults, and no need to check for eNodata) +defaults, and no need to check for eNodata) --- ColumnProperties() more logically belongs to Rowset than to Row @@ -121,7 +121,7 @@ Sub-concepts: - joins are tricky - boolean operators (<,>,=,<=,=> and <>) and WHERE-like clause support as a query - + rowset rs = (s.prepare << "age > 28") // rows where field 'age' is less than 28 rowset rs = (s.prepare << "age <> 28") // rows where field 'age' is less or more than 28 rowset rs = (s.prepare << "firstname='John' AND age > 28") // multi-fields combined queries diff --git a/bin/ci/before_install.sh b/bin/ci/before_install.sh index 4bc98abb19..ebd5aeb700 100755 --- a/bin/ci/before_install.sh +++ b/bin/ci/before_install.sh @@ -1,4 +1,4 @@ -#!/bin/bash -e +#!/bin/bash # Run before_install actions for SOCI build at travis-ci.org # # Copyright (c) 2013 Mateusz Loskot @@ -6,8 +6,14 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 16126D3A3E5C1192 -sudo apt-get update -qq -sudo apt-get install -qq libboost-dev libboost-date-time-dev +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 before_install="${TRAVIS_BUILD_DIR}/bin/ci/before_install_${SOCI_TRAVIS_BACKEND}.sh" -[ -x ${before_install} ] && ${before_install} || echo "nothing to run" +if [ -x ${before_install} ]; then + echo "Running ${before_install}" + ${before_install} +fi diff --git a/bin/ci/before_install_firebird.sh b/bin/ci/before_install_firebird.sh index 9ec3214f4b..e03bfd4575 100755 --- a/bin/ci/before_install_firebird.sh +++ b/bin/ci/before_install_firebird.sh @@ -8,7 +8,7 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh sudo apt-get install -qq firebird2.5-super firebird2.5-dev # Configure Firebird server -# See: Non-interactive setup for travis-ci.org +# See: Non-interactive setup for travis-ci.org # http://tech.groups.yahoo.com/group/firebird-support/message/120883 #sudo dpkg-reconfigure -f noninteractive firebird2.5-super sudo sed /ENABLE_FIREBIRD_SERVER=/s/no/yes/ -i /etc/default/firebird2.5 diff --git a/bin/ci/before_install_oracle.sh b/bin/ci/before_install_oracle.sh index fdd1067501..5502e63d48 100755 --- a/bin/ci/before_install_oracle.sh +++ b/bin/ci/before_install_oracle.sh @@ -1,15 +1,123 @@ -#!/bin/bash -e -# Install Oracle client libraries for SOCI at travis-ci.org +#!/bin/bash +# Script performs non-interactive installation of Oracle XE 10g on Debian # -# Copyright (c) 2013 Mateusz Loskot +# 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 -sudo apt-get install -qq tar bzip2 libaio1 +# +# 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 +} -wget http://brzuchol.loskot.net/software/oracle/instantclient_11_2-linux-x64-mloskot.tar.bz2 -tar -jxf instantclient_11_2-linux-x64-mloskot.tar.bz2 -sudo mkdir -p /opt -sudo mv instantclient_11_2 /opt -sudo ln -s ${ORACLE_HOME}/libclntsh.so.11.1 ${ORACLE_HOME}/libclntsh.so -sudo ln -s ${ORACLE_HOME}/libocci.so.11.1 ${ORACLE_HOME}/libocci.so +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/before_script.sh b/bin/ci/before_script.sh index 4fe32bc924..681bbf9c85 100755 --- a/bin/ci/before_script.sh +++ b/bin/ci/before_script.sh @@ -1,4 +1,4 @@ -#!/bin/bash -e +#!/bin/bash # Run before_script actions for SOCI build at travis-ci.org # # Copyright (c) 2013 Mateusz Loskot @@ -6,4 +6,7 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh before_script="${TRAVIS_BUILD_DIR}/bin/ci/before_script_${SOCI_TRAVIS_BACKEND}.sh" -[ -x ${before_script} ] && ${before_script} || echo "nothing to run" +if [ -x ${before_script} ]; then + echo "Running ${before_script}" + ${before_script} +fi diff --git a/bin/ci/before_script_odbc.sh b/bin/ci/before_script_odbc.sh index 56495740d8..1bff332e5f 100755 --- a/bin/ci/before_script_odbc.sh +++ b/bin/ci/before_script_odbc.sh @@ -7,5 +7,5 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh mysql --version mysql -e 'create database soci_test;' -psql --version +psql --version psql -c 'create database soci_test;' -U postgres diff --git a/bin/ci/before_script_oracle.sh b/bin/ci/before_script_oracle.sh new file mode 100755 index 0000000000..91bac34143 --- /dev/null +++ b/bin/ci/before_script_oracle.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Sets up environment for p6psy backend Oracle at travis-ci.org +# +# Copyright (c) 2013 Peter Butkovic +# +# Modified by Mateusz Loskot +# 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 +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 + +echo "grant connect, resource to travis;" | \ +sqlplus -S -L sys/admin AS SYSDBA + +echo "grant create session, alter any procedure to travis;" | \ +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 +echo "grant select on sys.pending_trans$ to travis;" | \ +sqlplus -S -L sys/admin AS SYSDBA +echo "grant select on sys.dba_2pc_pending to travis;" | \ +sqlplus -S -L sys/admin AS SYSDBA +echo "grant execute on sys.dbms_system to travis;" | \ +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 + +# check connection as user for testing +echo "Connecting using travis/travis@XE" +echo "SELECT * FROM product_component_version;" | \ +sqlplus -S -L travis/travis@XE + diff --git a/bin/ci/before_script_postgresql.sh b/bin/ci/before_script_postgresql.sh index 20a2579a51..039a4a6e55 100755 --- a/bin/ci/before_script_postgresql.sh +++ b/bin/ci/before_script_postgresql.sh @@ -5,5 +5,5 @@ # source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh -psql --version +psql --version psql -c 'create database soci_test;' -U postgres diff --git a/bin/ci/common.sh b/bin/ci/common.sh index 0a643943da..feb15a771c 100644 --- a/bin/ci/common.sh +++ b/bin/ci/common.sh @@ -15,8 +15,6 @@ if [[ -f /sys/devices/system/cpu/online ]]; then # Calculates 1.5 times physical threads TCI_NUMTHREADS=$(( ( $(cut -f 2 -d '-' /sys/devices/system/cpu/online) + 1 ) * 15 / 10 )) fi -export ORACLE_HOME=/opt/instantclient_11_2 -export LD_LIBRARY_PATH=${ORACLE_HOME}:${LD_LIBRARY_PATH} # # Functions # @@ -27,10 +25,10 @@ tmstamp() run_make() { - [ $TCI_NUMTHREADS -gt 0 ] && make -j $TCI_NUMTHREADS ] || make + [ $TCI_NUMTHREADS -gt 0 ] && make -j $TCI_NUMTHREADS || make } run_test() { - ctest -V --output-on-failure . + ctest -V --output-on-failure "$@" . } diff --git a/bin/ci/oracle.sh b/bin/ci/oracle.sh new file mode 100644 index 0000000000..9cbfadd4d7 --- /dev/null +++ b/bin/ci/oracle.sh @@ -0,0 +1,14 @@ +# 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.sh b/bin/ci/script.sh index d982aee6e9..538dd53b40 100755 --- a/bin/ci/script.sh +++ b/bin/ci/script.sh @@ -1,4 +1,4 @@ -#!/bin/bash -e +#!/bin/bash # Run test script actions for SOCI build at travis-ci.org # # Copyright (c) 2013 Mateusz Loskot @@ -6,9 +6,11 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh # prepare build directory -builddir="${TRAVIS_BUILD_DIR}/src/_build" +builddir="${TRAVIS_BUILD_DIR}/_build" mkdir -p ${builddir} cd ${builddir} # build and run tests -${TRAVIS_BUILD_DIR}/bin/ci/script_${SOCI_TRAVIS_BACKEND}.sh +SCRIPT=${TRAVIS_BUILD_DIR}/bin/ci/script_${SOCI_TRAVIS_BACKEND}.sh +echo "Running ${SCRIPT}" +${SCRIPT} diff --git a/bin/ci/script_db2.sh b/bin/ci/script_db2.sh index ed174c154b..1120c0d458 100755 --- a/bin/ci/script_db2.sh +++ b/bin/ci/script_db2.sh @@ -7,6 +7,7 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh cmake \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ -DSOCI_DB2=ON \ @@ -18,7 +19,7 @@ cmake \ -DSOCI_POSTGRESQL=OFF \ -DSOCI_SQLITE3=OFF \ -DSOCI_DB2_TEST_CONNSTR:STRING="DSN=SOCITEST\;Uid=db2inst1\;Pwd=db2inst1" \ - .. + .. run_make run_test diff --git a/bin/ci/script_empty.sh b/bin/ci/script_empty.sh index a93e541eb9..af7982d694 100755 --- a/bin/ci/script_empty.sh +++ b/bin/ci/script_empty.sh @@ -6,6 +6,7 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh cmake \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ -DSOCI_DB2=OFF \ diff --git a/bin/ci/script_firebird.sh b/bin/ci/script_firebird.sh index 7c75fca7ad..1ad00f540d 100755 --- a/bin/ci/script_firebird.sh +++ b/bin/ci/script_firebird.sh @@ -6,6 +6,7 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh cmake \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ -DSOCI_DB2=OFF \ @@ -17,7 +18,7 @@ cmake \ -DSOCI_POSTGRESQL=OFF \ -DSOCI_SQLITE3=OFF \ -DSOCI_FIREBIRD_TEST_CONNSTR:STRING="service=LOCALHOST:/tmp/soci_test.fdb user=SYSDBA password=masterkey" \ - .. + .. run_make run_test diff --git a/bin/ci/script_mysql.sh b/bin/ci/script_mysql.sh index 58d57709b6..8589a21f4a 100755 --- a/bin/ci/script_mysql.sh +++ b/bin/ci/script_mysql.sh @@ -6,6 +6,7 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh cmake \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ -DSOCI_DB2=OFF \ @@ -17,7 +18,7 @@ cmake \ -DSOCI_POSTGRESQL=OFF \ -DSOCI_SQLITE3=OFF \ -DSOCI_MYSQL_TEST_CONNSTR:STRING="db=soci_test" \ - .. + .. run_make run_test diff --git a/bin/ci/script_odbc.sh b/bin/ci/script_odbc.sh index 79a655e788..5193857daf 100755 --- a/bin/ci/script_odbc.sh +++ b/bin/ci/script_odbc.sh @@ -5,7 +5,14 @@ # source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh +ODBC_TEST=${PWD}/../tests/odbc +if test ! -d ${ODBC_TEST}; then + echo "ERROR: '${ODBC_TEST}' directory not found" + exit 1 +fi + cmake \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ -DSOCI_DB2=OFF \ @@ -16,9 +23,11 @@ cmake \ -DSOCI_ORACLE=OFF \ -DSOCI_POSTGRESQL=OFF \ -DSOCI_SQLITE3=OFF \ - -DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="FILEDSN=${PWD}/../backends/odbc/test/test-postgresql.dsn;" \ - -DSOCI_ODBC_TEST_MYSQL_CONNSTR="FILEDSN=${PWD}/../backends/odbc/test/test-mysql.dsn;" \ + -DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="FILEDSN=${ODBC_TEST}/test-postgresql.dsn;" \ + -DSOCI_ODBC_TEST_MYSQL_CONNSTR="FILEDSN=${ODBC_TEST}/test-mysql.dsn;" \ .. run_make -run_test + +# Exclude the test which can't be run as there is no MS SQL server available. +run_test -E soci_odbc_test_mssql diff --git a/bin/ci/script_oracle.sh b/bin/ci/script_oracle.sh index 743476c893..f9863da425 100755 --- a/bin/ci/script_oracle.sh +++ b/bin/ci/script_oracle.sh @@ -4,15 +4,11 @@ # Copyright (c) 2013 Mateusz Loskot # source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh - -if [ "${CXX}" == "g++" ] -then - ORACLE_USER="soci_tester" -else - ORACLE_USER="soci_tester1" -fi +source ${TRAVIS_BUILD_DIR}/bin/ci/oracle.sh cmake \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ + -DWITH_BOOST=OFF \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ -DSOCI_DB2=OFF \ @@ -23,8 +19,8 @@ cmake \ -DSOCI_ORACLE=ON \ -DSOCI_POSTGRESQL=OFF \ -DSOCI_SQLITE3=OFF \ - -DSOCI_ORACLE_TEST_CONNSTR:STRING="service=brzuchol.loskot.net user=${ORACLE_USER} password=soci_secret" \ - .. + -DSOCI_ORACLE_TEST_CONNSTR:STRING="service=XE user=travis password=travis" \ + .. run_make run_test diff --git a/bin/ci/script_postgresql.sh b/bin/ci/script_postgresql.sh index 2a5194615e..892d74372e 100755 --- a/bin/ci/script_postgresql.sh +++ b/bin/ci/script_postgresql.sh @@ -6,6 +6,7 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh cmake \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ -DSOCI_DB2=OFF \ @@ -17,7 +18,7 @@ cmake \ -DSOCI_POSTGRESQL=ON \ -DSOCI_SQLITE3=OFF \ -DSOCI_POSTGRESQL_TEST_CONNSTR:STRING="dbname=soci_test user=postgres" \ - .. + .. run_make run_test diff --git a/bin/ci/script_postgression.sh b/bin/ci/script_postgression.sh new file mode 100755 index 0000000000..846bfcd2d6 --- /dev/null +++ b/bin/ci/script_postgression.sh @@ -0,0 +1,47 @@ +#!/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/bin/ci/script_sqlite3.sh b/bin/ci/script_sqlite3.sh index 1a3de1fe72..5dfc0d0027 100755 --- a/bin/ci/script_sqlite3.sh +++ b/bin/ci/script_sqlite3.sh @@ -6,6 +6,7 @@ source ${TRAVIS_BUILD_DIR}/bin/ci/common.sh cmake \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ -DSOCI_TESTS=ON \ -DSOCI_STATIC=OFF \ -DSOCI_DB2=OFF \ diff --git a/bin/vm/debian-oracle10g-install.sh b/bin/vm/debian-oracle10g-install.sh index 39e8e4be22..a96b98383f 100644 --- a/bin/vm/debian-oracle10g-install.sh +++ b/bin/vm/debian-oracle10g-install.sh @@ -28,7 +28,7 @@ function free_restore() # Install fake free # http://www.axelog.de/2010/02/7-oracle-ee-refused-to-install-into-openvz/ free_backup -cat <> /usr/bin/free +cat <> /usr/bin/free #!/bin/sh cat <<__eof total used free shared buffers cached diff --git a/build/README b/build/README deleted file mode 100644 index db968b7b2b..0000000000 --- a/build/README +++ /dev/null @@ -1,2 +0,0 @@ -This directory is dedicated for: -- other building systems contributed by SOCI team and users diff --git a/build/unix/build-core.tcl b/build/unix/build-core.tcl deleted file mode 100644 index aba8d8ec87..0000000000 --- a/build/unix/build-core.tcl +++ /dev/null @@ -1,37 +0,0 @@ -proc buildCore {} { - global CXXFLAGS - - puts "building static core" - - set cwd [pwd] - cd "../../src/core" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS" - } - - execute "ar cr libsoci_core.a [glob *.o]" - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/core/libsoci_core.a lib" - eval exec mkdir -p "include" - execute "cp [glob ../../src/core/*.h] include" -} - -proc buildCoreSo {} { - global CXXFLAGS SHARED - - puts "building shared core" - - set cwd [pwd] - cd "../../src/core" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS -fPIC" - } - - execute "g++ $SHARED -o libsoci_core.so [glob *.o]" - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/core/libsoci_core.so lib" - eval exec mkdir -p "include" - execute "cp [glob ../../src/core/*.h] include" -} diff --git a/build/unix/build-mysql.tcl b/build/unix/build-mysql.tcl deleted file mode 100644 index f1371ae519..0000000000 --- a/build/unix/build-mysql.tcl +++ /dev/null @@ -1,142 +0,0 @@ -source "local/parameters.tcl" - -proc findMySQL {} { - global mysqlInclude mysqlLib - - # candidate directories for local MySQL: - set includeDirs { - "/usr/local/include/mysql" - "/usr/include/mysql" - "/usr/local/include" - "/usr/include" - "/opt/local/include" - } - set libDirs { - "/usr/local/lib/mysql" - "/usr/lib/mysql" - "/usr/local/lib" - "/usr/lib" - "/opt/local/lib" - } - - if [info exists mysqlInclude] { - set includeDirs [list $mysqlInclude] - } - if [info exists mysqlLib] { - set libDirs [list $mysqlLib] - } - - set includeDir "" - foreach I $includeDirs { - set header "${I}/mysql.h" - if {[file exists $header]} { - set includeDir $I - break - } - } - if {$includeDir == ""} { - return {} - } - - set libDir "" - foreach L $libDirs { - set libraryA "${L}/libmysqlclient.a" - set librarySo "${L}/libmysqlclient.so" - if {[file exists $libraryA] || [file exists $librarySo]} { - set libDir $L - break - } - } - if {$libDir == ""} { - return {} - } - - return [list $includeDir $libDir] -} - -proc buildMySQL {} { - global CXXFLAGS - - puts "building static MySQL" - - set dirs [findMySQL] - if {$dirs == {}} { - puts "cannot find MySQL library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/mysql" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS -I../../core -I${includeDir}" - } - - execute "ar cr libsoci_mysql.a [glob *.o]" - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/backends/mysql/libsoci_mysql.a lib" - eval exec mkdir -p "include" - execute "cp ../../src/backends/mysql/soci-mysql.h include" -} - -proc buildMySQLSo {} { - global CXXFLAGS SHARED - - puts "building shared MySQL" - - set dirs [findMySQL] - if {$dirs == {}} { - puts "cannot find MySQL library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/mysql" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS -fPIC -I../../core -I${includeDir}" - } - - execute "g++ $SHARED -o libsoci_mysql.so [glob *.o] -L${libDir} -lmysqlclient -lz" - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/backends/mysql/libsoci_mysql.so lib" - eval exec mkdir -p "include" - execute "cp ../../src/backends/mysql/soci-mysql.h include" -} - -proc buildMySQLTest {} { - global CXXTESTFLAGS LDL - - puts "building MySQL test" - - set dirs [findMySQL] - if {$dirs == {}} { - puts "cannot find MySQL library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set dirs [findBoost] - if {$dirs == {}} { - puts "cannot find Boost library files, skipping this target" - return - } - - set boostIncludeDir [lindex $dirs 0] - set boostLibDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/mysql/test" - execute "g++ test-mysql.cpp -o test-mysql $CXXTESTFLAGS -I.. -I../../../core -I../../../core/test -I${includeDir} -I${boostIncludeDir} -L../../../../build/unix/lib -L${libDir} -L${boostLibDir} -lsoci_core -lsoci_mysql -lboost_date_time ${LDL} -lmysqlclient -lz" - cd $cwd - eval exec mkdir -p "tests" - execute "cp ../../src/backends/mysql/test/test-mysql tests" -} diff --git a/build/unix/build-oracle.tcl b/build/unix/build-oracle.tcl deleted file mode 100644 index ac59d0b287..0000000000 --- a/build/unix/build-oracle.tcl +++ /dev/null @@ -1,120 +0,0 @@ -proc findOracle {} { - global env oracleInclude oracleLib - - if {[info exists oracleInclude] && - [info exists oracleLib]} { - set includeDir $oracleInclude - set libDir $oracleLib - } else { - if {[info exists env(ORACLE_HOME)] == 0} { - puts "The ORACLE_HOME variable is not set." - return {} - } - - set ORACLE_HOME $env(ORACLE_HOME) - - set includeDir [file join $ORACLE_HOME "rdbms/public"] - set header [file join $includeDir "oci.h"] - if {[file exists $header] == 0} { - puts "ORACLE_HOME is strange." - return {} - } - - set libDir [file join $ORACLE_HOME "lib"] - set libraryA [file join $libDir "libclntsh.a"] - set librarySo [file join $libDir "libclntsh.so"] - if {([file exists $libraryA] == 0) && ([file exists $librarySo] == 0)} { - puts "ORACLE_HOME is strange." - return {} - } - } - - return [list $includeDir $libDir] -} - -proc buildOracle {} { - global CXXFLAGS - - puts "building static Oracle" - - set dirs [findOracle] - if {$dirs == {}} { - puts "cannot find Oracle library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/oracle" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS -I../../core -I${includeDir}" - } - - execute "ar cr libsoci_oracle.a [glob *.o]" - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/backends/oracle/libsoci_oracle.a lib" - eval exec mkdir -p "include" - execute "cp ../../src/backends/oracle/soci-oracle.h include" -} - -proc buildOracleSo {} { - global CXXFLAGS SHARED - - puts "building shared Oracle" - - set dirs [findOracle] - if {$dirs == {}} { - puts "cannot find Oracle library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/oracle" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS -fPIC -I../../core -I${includeDir}" - } - - execute "g++ $SHARED -o libsoci_oracle.so [glob *.o] -L${libDir} -lclntsh -lnnz10" - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/backends/oracle/libsoci_oracle.so lib" - eval exec mkdir -p "include" - execute "cp ../../src/backends/oracle/soci-oracle.h include" -} - -proc buildOracleTest {} { - global CXXTESTFLAGS LDL - - puts "building Oracle test" - - set dirs [findOracle] - if {$dirs == {}} { - puts "cannot find Oracle library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set dirs [findBoost] - if {$dirs == {}} { - puts "cannot find Boost library files, skipping this target" - return - } - - set boostIncludeDir [lindex $dirs 0] - set boostLibDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/oracle/test" - execute "g++ test-oracle.cpp -o test-oracle $CXXTESTFLAGS -I.. -I../../../core -I../../../core/test -I${includeDir} -I${boostIncludeDir} -L../../../../build/unix/lib -L${libDir} -L${boostLibDir} -lsoci_core -lsoci_oracle -lboost_date_time ${LDL} -lclntsh -lnnz10" - cd $cwd - eval exec mkdir -p "tests" - execute "cp ../../src/backends/oracle/test/test-oracle tests" -} diff --git a/build/unix/build-postgresql.tcl b/build/unix/build-postgresql.tcl deleted file mode 100644 index 49a4f939ad..0000000000 --- a/build/unix/build-postgresql.tcl +++ /dev/null @@ -1,148 +0,0 @@ -proc findPostgreSQL {} { - global postgresqlInclude postgresqlLib - - # candidate directories for local PostgreSQL: - set includeDirs { - "/usr/local/pgsql/include" - "/usr/local/postgresql/include" - "/usr/local/include/pgsql" - "/usr/local/include/postgresql" - "/usr/include/pgsql" - "/usr/include/postgresql" - "/usr/local/include" - "/usr/include" - "/opt/local/include" - } - set libDirs { - "/usr/local/pgsql/lib" - "/usr/local/postgresql/lib" - "/usr/local/lib/pgsql" - "/usr/local/lib/postgresql" - "/usr/lib/pgsql" - "/usr/lib/postgresql" - "/usr/local/lib" - "/usr/lib" - "/opt/local/lib" - } - - if [info exists postgresqlInclude] { - set includeDirs [list $postgresqlInclude] - } - if [info exists postgresqlLib] { - set libDirs [list $postgresqlLib] - } - - set includeDir "" - foreach I $includeDirs { - set header "${I}/libpq/libpq-fs.h" - if {[file exists $header]} { - set includeDir $I - break - } - } - if {$includeDir == ""} { - return {} - } - - set libDir "" - foreach L $libDirs { - set libraryA "${L}/libpq.a" - set librarySo "${L}/libpq.so" - if {[file exists $libraryA] || [file exists $librarySo]} { - set libDir $L - break - } - } - if {$libDir == ""} { - return {} - } - - return [list $includeDir $libDir] -} - -proc buildPostgreSQL {} { - global CXXFLAGS - - puts "building static PostgreSQL" - - set dirs [findPostgreSQL] - if {$dirs == {}} { - puts "cannot find PostgreSQL library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/postgresql" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS -I../../core -I${includeDir}" - } - - execute "ar cr libsoci_postgresql.a [glob *.o]" - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/backends/postgresql/libsoci_postgresql.a lib" - eval exec mkdir -p "include" - execute "cp ../../src/backends/postgresql/soci-postgresql.h include" -} - -proc buildPostgreSQLSo {} { - global CXXFLAGS SHARED - - puts "building shared PostgreSQL" - - set dirs [findPostgreSQL] - if {$dirs == {}} { - puts "cannot find PostgreSQL library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/postgresql" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS -fPIC -I../../core -I${includeDir}" - } - - execute "g++ $SHARED -o libsoci_postgresql.so [glob *.o] -L${libDir} -lpq" - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/backends/postgresql/libsoci_postgresql.so lib" - eval exec mkdir -p "include" - execute "cp ../../src/backends/postgresql/soci-postgresql.h include" -} - -proc buildPostgreSQLTest {} { - global CXXTESTFLAGS LDL - - puts "building PostgreSQL test" - - set dirs [findPostgreSQL] - if {$dirs == {}} { - puts "cannot find PostgreSQL library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set dirs [findBoost] - if {$dirs == {}} { - puts "cannot find Boost library files, skipping this target" - return - } - - set boostIncludeDir [lindex $dirs 0] - set boostLibDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/postgresql/test" - execute "g++ test-postgresql.cpp -o test-postgresql $CXXTESTFLAGS -I.. -I../../../core -I../../../core/test -I${includeDir} -I${boostIncludeDir} -L../../../../build/unix/lib -L${libDir} -L${boostLibDir} -lsoci_core -lsoci_postgresql -lboost_date_time ${LDL} -lpq" - cd $cwd - eval exec mkdir -p "tests" - execute "cp ../../src/backends/postgresql/test/test-postgresql tests" -} diff --git a/build/unix/build-sqlite3.tcl b/build/unix/build-sqlite3.tcl deleted file mode 100644 index a48d6516bd..0000000000 --- a/build/unix/build-sqlite3.tcl +++ /dev/null @@ -1,142 +0,0 @@ -proc findSqlite3 {} { - global Sqlite3Include Sqlite3Lib - - # candidate directories for local sqlite3: - set includeDirs { - "/usr/local/include" - "/usr/include" - "/opt/local/include" - } - set libDirs { - "/usr/local/lib" - "/usr/lib" - "/opt/local/lib" - } - - if [info exists Sqlite3Include] { - set includeDirs [list $Sqlite3Include] - } - if [info exists Sqlite3Lib] { - set libDirs [list $Sqlite3Lib] - } - - set includeDir "" - foreach I $includeDirs { - set header "${I}/sqlite3.h" - if {[file exists $header]} { - set includeDir $I - break - } - } - if {$includeDir == ""} { - return {} - } - - set libDir "" - foreach L $libDirs { - set libraryA "${L}/libsqlite3.a" - set librarySo "${L}/libsqlite3.so" - set libraryDl "${L}/libsqlite3.dylib" - if {[file exists $libraryA] || [file exists $librarySo] || [file exists $libraryDl]} { - set libDir $L - break - } - } - if {$libDir == ""} { - return {} - } - - return [list $includeDir $libDir] -} - -proc buildSqlite3 {} { - global CXXFLAGS tcl_platform - - puts "building static Sqlite3" - - set dirs [findSqlite3] - if {$dirs == {}} { - puts "cannot find Sqlite3 library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/sqlite3" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS -I../../core -I${includeDir}" - } - - execute "ar cr libsoci_sqlite3.a [glob *.o]" - if {$tcl_platform(os) == "Darwin"} { - # special case for Mac OS X - execute "ranlib libsoci_sqlite3.a" - } - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/backends/sqlite3/libsoci_sqlite3.a lib" - eval exec mkdir -p "include" - execute "cp ../../src/backends/sqlite3/soci-sqlite3.h include" - -} - -proc buildSqlite3So {} { - global CXXFLAGS SHARED - - puts "building shared Sqlite3" - - set dirs [findSqlite3] - if {$dirs == {}} { - puts "cannot find Sqlite3 library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/sqlite3" - foreach cppFile [glob "*.cpp"] { - execute "g++ -c $cppFile $CXXFLAGS -fPIC -I../../core -I${includeDir}" - } - - execute "g++ $SHARED -o libsoci_sqlite3.so [glob *.o] -L${libDir} -lsqlite3" - cd $cwd - eval exec mkdir -p "lib" - execute "cp ../../src/backends/sqlite3/libsoci_sqlite3.so lib" - eval exec mkdir -p "include" - execute "cp ../../src/backends/sqlite3/soci-sqlite3.h include" -} - -proc buildSqlite3Test {} { - global CXXTESTFLAGS LDL - - puts "building Sqlite3 test" - - set dirs [findSqlite3] - if {$dirs == {}} { - puts "cannot find Sqlite3 library files, skipping this target" - return - } - - set includeDir [lindex $dirs 0] - set libDir [lindex $dirs 1] - - set dirs [findBoost] - if {$dirs == {}} { - puts "cannot find Boost library files, skipping this target" - return - } - - set boostIncludeDir [lindex $dirs 0] - set boostLibDir [lindex $dirs 1] - - set cwd [pwd] - cd "../../src/backends/sqlite3/test" - execute "g++ test-sqlite3.cpp -o test-sqlite3 $CXXTESTFLAGS -I.. -I../../../core -I../../../core/test -I${includeDir} -I${boostIncludeDir} -L../../../../build/unix/lib -L${libDir} -L${boostLibDir} -lsoci_core -lsoci_sqlite3 -lboost_date_time ${LDL} -lsqlite3" - cd $cwd - eval exec mkdir -p "tests" - execute "cp ../../src/backends/sqlite3/test/test-sqlite3 tests" -} diff --git a/build/unix/build.tcl b/build/unix/build.tcl deleted file mode 100755 index 7ec3d32108..0000000000 --- a/build/unix/build.tcl +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/tclsh - -# some common compilation settings if you need to change them: - -if [info exists env(CXXFLAGS)] { - set CXXFLAGS $env(CXXFLAGS) -} else { - set CXXFLAGS "-Wall -pedantic -Wno-long-long -O2" -} - -set CXXTESTFLAGS "-O2" - -if {$tcl_platform(os) == "Darwin"} { - # special case for Mac OS X - set SHARED "-dynamiclib -flat_namespace -undefined suppress" -} else { - set SHARED "-shared" -} - -if {$tcl_platform(os) == "FreeBSD"} { - # FreeBSD does not have the libdl library, it is part of libc. - set LDL "" -} else { - set LDL "-ldl" -} - -source "execute.tcl" -source "find-boost.tcl" -source "build-core.tcl" -source "build-oracle.tcl" -source "build-postgresql.tcl" -source "build-mysql.tcl" - -proc printUsageAndExit {} { - puts "Usage:" - puts "$ ./build.tcl list-of-targets" - puts "" - puts "list of targets can contain any of:" - puts "core - the core part of the library (static version)" - puts "core-so - the core part of the library (shared version)" - puts "oracle - the static Oracle backend" - puts "oracle-so - the shared Oracle backend" - puts " Note: before building Oracle backend" - puts " set the ORACLE_HOME variable properly." - puts "postgresql - the static PostgreSQL backend" - puts "postgresql-so - the shared PostgreSQL backend" - puts "mysql - the static MySQL backend" - puts "mysql-so - the shared MySQL backend" - puts "" - puts "oracle-test - the test for Oracle" - puts "postgresql-test - the test for PostgreSQL" - puts "mysql-test - the test for MySQL" - puts " Note: build static core and backends first." - puts "" - puts "Examples:" - puts "" - puts "$ ./build.tcl core mysql" - puts "" - puts "$ ./build.tcl core postgresql postgresql-test" - puts "" - puts "After successful build the results are in include, lib and test directories." - puts "Move/copy the contents of these directories wherever you want." - exit -} - -if {$argc == 0 || $argv == "--help"} { - printUsageAndExit -} - -foreach target $argv { - switch -exact $target { - core buildCore - core-so buildCoreSo - oracle buildOracle - oracle-so buildOracleSo - oracle-test buildOracleTest - postgresql buildPostgreSQL - postgresql-so buildPostgreSQLSo - postgresql-test buildPostgreSQLTest - mysql buildMySQL - mysql-so buildMySQLSo - mysql-test buildMySQLTest - default { - puts "unknown target $target - skipping" - } - } -} diff --git a/build/unix/execute.tcl b/build/unix/execute.tcl deleted file mode 100644 index 90d5748572..0000000000 --- a/build/unix/execute.tcl +++ /dev/null @@ -1,14 +0,0 @@ -proc execute {command} { - puts $command - set result [catch {eval exec $command "2>@ stdout"} output] - if {$result != 0} { - puts "The last command did not execute properly:" - puts $output - puts "Please contact the SOCI team." - exit - } else { - if {$output != ""} { - puts $output - } - } -} diff --git a/build/unix/find-boost.tcl b/build/unix/find-boost.tcl deleted file mode 100644 index 2904ac3d78..0000000000 --- a/build/unix/find-boost.tcl +++ /dev/null @@ -1,47 +0,0 @@ -proc findBoost {} { - global rootBoost - - # candidate directories for local Boost: - set includeDirs { - "/usr/local/include" - "/usr/include" - "/opt/local/include" - } - set libDirs { - "/usr/local/lib" - "/usr/lib" - "/opt/local/lib" - } - - if [info exists rootBoost] { - set includeDirs [list $rootBoost] - set libDirs [list $rootBoost] - } - - set includeDir "" - foreach I $includeDirs { - set header "${I}/boost/version.hpp" - if {[file exists $header]} { - set includeDir $I - break - } - } - if {$includeDir == ""} { - return {} - } - - set libDir "" - foreach L $libDirs { - set libraryA "${L}/libboost_date_time.a" - set librarySo "${L}/libboost_date_time.so" - if {[file exists $libraryA] || [file exists $librarySo]} { - set libDir $L - break - } - } - if {$libDir == ""} { - return {} - } - - return [list $includeDir $libDir] -} diff --git a/build/unix/install.tcl b/build/unix/install.tcl deleted file mode 100644 index e11eda5bb8..0000000000 --- a/build/unix/install.tcl +++ /dev/null @@ -1,74 +0,0 @@ -set headerInstallPrefix "/usr/local/include/soci" -set libInstallPrefix "/usr/local/lib" -set sociVersion "3.0.0" -set sociMajor "3" - -source "execute.tcl" -source "local/parameters.tcl" - -if [info exists env(DESTDIR)] { - set DESTDIR $env(DESTDIR) - set headerInstallPrefix [file normalize ${DESTDIR}/${headerInstallPrefix}] - set libInstallPrefix [file normalize ${DESTDIR}/${libInstallPrefix}] -} - -set uninstallFile [open "local/uninstall.sh" "w"] - -if {[file exists $headerInstallPrefix] == 0} { - execute "mkdir -p $headerInstallPrefix" - puts $uninstallFile "rm -rf $headerInstallPrefix" -} - -foreach header [glob "include/*"] { - set tail [file tail $header] - puts "copying $tail to ${headerInstallPrefix}" - execute "cp $header $headerInstallPrefix" - puts $uninstallFile "rm -f ${headerInstallPrefix}/${tail}" -} - -if {[file exists $libInstallPrefix] == 0} { - execute "mkdir -p $libInstallPrefix" - puts $uninstallFile "rm -rf $libInstallPrefix" -} - -foreach lib [glob "lib/*.a"] { - set tail [file tail $lib] - puts "copying $tail to ${libInstallPrefix}" - execute "cp $lib $libInstallPrefix" - puts $uninstallFile "rm -f ${libInstallPrefix}/${tail}" -} - -set buildDir [pwd] -cd $libInstallPrefix -foreach lib [glob "${buildDir}/lib/*.so"] { - set rootName [file rootname [file tail $lib]] - set targetName "${rootName}-${sociVersion}.so" - set majorLink "${rootName}-${sociMajor}.so" - set link "${rootName}.so" - - puts "copying [file tail $lib] to ${targetName}" - execute "cp $lib $targetName" - puts $uninstallFile "rm -f ${libInstallPrefix}/${targetName}" - - puts "creating link ${majorLink}" - execute "ln -s $targetName [file tail $majorLink]" - puts $uninstallFile "rm -f ${libInstallPrefix}/${majorLink}" - - puts "creating ${link}" - execute "ln -s $targetName [file tail $link]" - puts $uninstallFile "rm -f ${libInstallPrefix}/${link}" -} - -close $uninstallFile - -puts "ldconfig ${libInstallPrefix}" -catch { eval exec "ldconfig ${libInstallPrefix}" } - -puts "" -puts "" -puts "Hint: the shared libraries were installed in $libInstallPrefix" -puts "- If you use dynamically loaded backends, then you might need to set" -puts " the SOCI_BACKENDS_PATH variable accordingly." -puts "" -puts "Hint: to remove all installed files and links run make uninstall" -puts "" diff --git a/build/unix/parse-parameters.tcl b/build/unix/parse-parameters.tcl deleted file mode 100644 index 574a6f60e6..0000000000 --- a/build/unix/parse-parameters.tcl +++ /dev/null @@ -1,60 +0,0 @@ -set paramsFile [open "local/parameters"] -set tclParamsFile [open "local/parameters.tcl" "w"] - -set line [gets $paramsFile] -while {![eof $paramsFile]} { - set pair [split $line "="] - if {[llength $pair] == 2} { - set name [lindex $pair 0] - set value [lindex $pair 1] - - switch -exact -- $name { - --include-prefix { - puts $tclParamsFile "set headerInstallPrefix $value" - puts "setting prefix for SOCI headers to $value" - } - --lib-prefix { - puts $tclParamsFile "set libInstallPrefix $value" - puts "setting prefix for SOCI libraries to $value" - } - --mysql-include { - puts $tclParamsFile "set mysqlInclude $value" - puts "setting include directory for MySQL to $value" - } - --mysql-lib { - puts $tclParamsFile "set mysqlLib $value" - puts "setting lib directory for MySQL to $value" - } - --oracle-include { - puts $tclParamsFile "set oracleInclude $value" - puts "setting include directory for Oracle to $value" - } - --oracle-lib { - puts $tclParamsFile "set oracleLib $value" - puts "setting lib directory for Oracle to $value" - } - --postgresql-include { - puts $tclParamsFile "set postgresqlInclude $value" - puts "setting include directory for PostgreSQL to $value" - } - --postgresql-lib { - puts $tclParamsFile "set postgresqlLib $value" - puts "setting lib directory for PostgreSQL to $value" - } - --boost-include { - puts $tclParamsFile "set boostInclude $value" - puts "setting Boost include directory to $value" - } - --boost-lib { - puts $tclParamsFile "set boostLib $value" - puts "setting Boost lib directory to $value" - } - default { - puts "unknown option: $name : skipping it!" - } - } - } - set line [gets $paramsFile] -} -close $paramsFile -close $tclParamsFile diff --git a/src/cmake/.gitignore b/cmake/.gitignore similarity index 100% rename from src/cmake/.gitignore rename to cmake/.gitignore diff --git a/src/cmake/CMakeLists.txt b/cmake/CMakeLists.txt similarity index 100% rename from src/cmake/CMakeLists.txt rename to cmake/CMakeLists.txt diff --git a/src/cmake/SociBackend.cmake b/cmake/SociBackend.cmake similarity index 52% rename from src/cmake/SociBackend.cmake rename to cmake/SociBackend.cmake index 5551086e3b..4c113de368 100644 --- a/src/cmake/SociBackend.cmake +++ b/cmake/SociBackend.cmake @@ -1,14 +1,14 @@ ################################################################################ # SociBackend.cmake - part of CMake configuration of SOCI library ################################################################################ -# Copyright (C) 2010 Mateusz Loskot +# Copyright (C) 2010-2013 Mateusz Loskot # # 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) ################################################################################ # Macros in this module: -# +# # soci_backend # - defines project of a database backend for SOCI library # @@ -16,10 +16,47 @@ # - defines test project of a database backend for SOCI library ################################################################################ +macro(soci_backend_deps_found NAME DEPS SUCCESS) + #message(STATUS "DEPS=${DEPS}") + + # Determine required dependencies + set(DEPS_INCLUDE_DIRS) + set(DEPS_LIBRARIES) + set(DEPS_DEFS) + set(DEPS_NOT_FOUND) + + # CMake 2.8+ syntax only: + #foreach(dep IN LISTS DEPS) + foreach(dep ${DEPS}) + soci_check_package_found(${dep} DEPEND_FOUND) + if(NOT DEPEND_FOUND) + 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}) + list(APPEND DEPS_LIBRARIES ${${DEPU}_LIBRARIES}) + list(APPEND DEPS_DEFS HAVE_${DEPU}=1) + endif() + endforeach() + + list(LENGTH DEPS_NOT_FOUND NOT_FOUND_COUNT) + + if (NOT_FOUND_COUNT GREATER 0) + set(${SUCCESS} False) + 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() + + #message(STATUS "soci_backend_deps_found: ${SUCCESS}=${${SUCCESS}}") +endmacro() + # Defines project of a database backend for SOCI library # # soci_backend(backendname -# HEADERS header1 header2 # DEPENDS dependency1 dependency2 # DESCRIPTION description # AUTHORS author1 author2 @@ -27,7 +64,7 @@ # macro(soci_backend NAME) parse_arguments(THIS_BACKEND - "HEADERS;DEPENDS;DESCRIPTION;AUTHORS;MAINTAINERS;" + "DEPENDS;DESCRIPTION;AUTHORS;MAINTAINERS;" "" ${ARGN}) @@ -43,36 +80,13 @@ macro(soci_backend NAME) option(${THIS_BACKEND_OPTION} "Attempt to build ${PROJECT_NAME} backend for ${NAME}" ON) - # Determine required dependencies - set(THIS_BACKEND_DEPENDS_INCLUDE_DIRS) - set(THIS_BACKEND_DEPENDS_LIBRARIES) - set(THIS_BACKEND_DEPENDS_DEFS) - set(DEPENDS_NOT_FOUND) - - # CMake 2.8+ syntax only: - #foreach(dep IN LISTS THIS_BACKEND_DEPENDS) - foreach(dep ${THIS_BACKEND_DEPENDS}) - - soci_check_package_found(${dep} DEPEND_FOUND) - if(NOT DEPEND_FOUND) - list(APPEND DEPENDS_NOT_FOUND ${dep}) - else() - string(TOUPPER "${dep}" DEPU) - list(APPEND THIS_BACKEND_DEPENDS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIR}) - list(APPEND THIS_BACKEND_DEPENDS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIRS}) - list(APPEND THIS_BACKEND_DEPENDS_LIBRARIES ${${DEPU}_LIBRARIES}) - list(APPEND THIS_BACKEND_DEPENDS_DEFS -DHAVE_${DEPU}=1) - endif() - endforeach() - - list(LENGTH DEPENDS_NOT_FOUND NOT_FOUND_COUNT) - - if (NOT_FOUND_COUNT GREATER 0) + 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:") - if (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 2.8) + if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 2.8) foreach(dep ${DEPENDS_NOT_FOUND}) colormsg(RED " ${dep}") endforeach() @@ -87,43 +101,56 @@ macro(soci_backend NAME) set(${THIS_BACKEND_OPTION} OFF) - else(NOT_FOUND_COUNT GREATER 0) + else() if(${THIS_BACKEND_OPTION}) - # Backend-specific include directories - list(APPEND THIS_BACKEND_DEPENDS_INCLUDE_DIRS ${SOCI_SOURCE_DIR}/core) - set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES - "${THIS_BACKEND_DEPENDS_INCLUDE_DIRS}") + get_directory_property(THIS_INCLUDE_DIRS INCLUDE_DIRECTORIES) + get_directory_property(THIS_COMPILE_DEFS COMPILE_DEFINITIONS) - # Backend-specific preprocessor definitions - add_definitions(${THIS_BACKEND_DEPENDS_DEFS}) + # Backend-specific depedencies + set(THIS_BACKEND_DEPENDS_INCLUDE_DIRS ${${NAMEU}_DEPS_INCLUDE_DIRS}) + set(THIS_BACKEND_DEPENDS_LIBRARIES ${${NAMEU}_DEPS_LIBRARIES}) + set(THIS_BACKEND_DEPENDS_DEFS ${${NAMEU}_DEPS_DEFS}) - # Backend installable headers and sources - if (NOT THIS_BACKEND_HEADERS) - file(GLOB THIS_BACKEND_HEADERS *.h) - endif() - file(GLOB THIS_BACKEND_SOURCES *.cpp) - set(THIS_BACKEND_HEADERS_VAR SOCI_${NAMEU}_HEADERS) - set(${THIS_BACKEND_HEADERS_VAR} ${THIS_BACKEND_HEADERS}) + # Collect include directories + list(APPEND THIS_INCLUDE_DIRS ${SOCI_SOURCE_DIR}/include/private) + list(APPEND THIS_INCLUDE_DIRS ${THIS_BACKEND_DEPENDS_INCLUDE_DIRS}) + # Collect compile definitions + list(APPEND THIS_COMPILE_DEFS ${THIS_BACKEND_DEPENDS_DEFS}) - # Group source files for IDE source explorers (e.g. Visual Studio) - source_group("Header Files" FILES ${THIS_BACKEND_HEADERS}) - source_group("Source Files" FILES ${THIS_BACKEND_SOURCES}) - source_group("CMake Files" FILES CMakeLists.txt) + set_directory_properties(PROPERTIES + INCLUDE_DIRECTORIES "${THIS_INCLUDE_DIRS}" + COMPILE_DEFINITIONS "${THIS_COMPILE_DEFS}") # Backend target + set(THIS_BACKEND_VAR SOCI_${NAMEU}) set(THIS_BACKEND_TARGET ${PROJECTNAMEL}_${NAMEL}) - set(THIS_BACKEND_TARGET_VAR SOCI_${NAMEU}_TARGET) + set(THIS_BACKEND_TARGET_VAR ${THIS_BACKEND_VAR}_TARGET) set(${THIS_BACKEND_TARGET_VAR} ${THIS_BACKEND_TARGET}) - - soci_target_output_name(${THIS_BACKEND_TARGET} ${THIS_BACKEND_TARGET_VAR}_OUTPUT_NAME) - set(THIS_BACKEND_TARGET_OUTPUT_NAME ${${THIS_BACKEND_TARGET_VAR}_OUTPUT_NAME}) - set(THIS_BACKEND_TARGET_OUTPUT_NAME_VAR ${THIS_BACKEND_TARGET_VAR}_OUTPUT_NAME) + soci_target_output_name(${THIS_BACKEND_TARGET} ${THIS_BACKEND_VAR}_OUTPUT_NAME) + + set(THIS_BACKEND_OUTPUT_NAME ${${THIS_BACKEND_VAR}_OUTPUT_NAME}) + set(THIS_BACKEND_OUTPUT_NAME_VAR ${THIS_BACKEND_VAR}_OUTPUT_NAME) + + set(${THIS_BACKEND_VAR}_COMPILE_DEFINITIONS ${THIS_COMPILE_DEFS}) + set(THIS_BACKEND_COMPILE_DEFINITIONS_VAR ${THIS_BACKEND_VAR}_COMPILE_DEFINITIONS) + + set(${THIS_BACKEND_VAR}_INCLUDE_DIRECTORIES ${THIS_INCLUDE_DIRS}) + set(THIS_BACKEND_INCLUDE_DIRECTORIES_VAR ${THIS_BACKEND_VAR}_INCLUDE_DIRECTORIES) + + # Backend installable headers and sources + file(GLOB THIS_BACKEND_HEADERS ${SOCI_SOURCE_DIR}/include/soci/${NAMEL}/*.h) + file(GLOB THIS_BACKEND_SOURCES *.cpp) + set(THIS_BACKEND_HEADERS_VAR SOCI_${NAMEU}_HEADERS) + set(${THIS_BACKEND_HEADERS_VAR} ${THIS_BACKEND_HEADERS}) + # Group source files for IDE source explorers (e.g. Visual Studio) + source_group("Header Files" FILES ${THIS_BACKEND_HEADERS}) + source_group("Source Files" FILES ${THIS_BACKEND_SOURCES}) + source_group("CMake Files" FILES CMakeLists.txt) # TODO: Extract as macros: soci_shared_lib_target and soci_static_lib_target --mloskot - # Shared library target if (SOCI_SHARED) add_library(${THIS_BACKEND_TARGET} @@ -132,19 +159,25 @@ macro(soci_backend NAME) ${THIS_BACKEND_HEADERS}) target_link_libraries(${THIS_BACKEND_TARGET} - ${SOCI_CORE_TARGET} - ${THIS_BACKEND_DEPENDS_LIBRARIES}) + ${SOCI_CORE_TARGET} + ${THIS_BACKEND_DEPENDS_LIBRARIES}) if(WIN32) - set_target_properties(${THIS_BACKEND_TARGET} + set_target_properties(${THIS_BACKEND_TARGET} PROPERTIES - OUTPUT_NAME ${THIS_BACKEND_TARGET_OUTPUT_NAME} + OUTPUT_NAME ${THIS_BACKEND_OUTPUT_NAME} DEFINE_SYMBOL SOCI_DLL) else() - set_target_properties(${THIS_BACKEND_TARGET} + set_target_properties(${THIS_BACKEND_TARGET} PROPERTIES SOVERSION ${${PROJECT_NAME}_SOVERSION} INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib) + + if(APPLE) + set_target_properties(${THIS_BACKEND_TARGET} + PROPERTIES + LINK_FLAGS "-Wl,-flat_namespace -Wl,-undefined -Wl,suppress") + endif() endif() set_target_properties(${THIS_BACKEND_TARGET} @@ -154,7 +187,7 @@ macro(soci_backend NAME) endif() # Static library target - if (SOCI_STATIC) + if(SOCI_STATIC) set(THIS_BACKEND_TARGET_STATIC ${THIS_BACKEND_TARGET}_static) add_library(${THIS_BACKEND_TARGET_STATIC} @@ -162,46 +195,50 @@ macro(soci_backend NAME) ${THIS_BACKEND_SOURCES} ${THIS_BACKEND_HEADERS}) + # Still need to link the libraries for tests to work + target_link_libraries (${THIS_BACKEND_TARGET_STATIC} + ${THIS_BACKEND_DEPENDS_LIBRARIES} + ) + set_target_properties(${THIS_BACKEND_TARGET_STATIC} - PROPERTIES - OUTPUT_NAME ${THIS_BACKEND_TARGET_OUTPUT_NAME} - PREFIX "lib" - CLEAN_DIRECT_OUTPUT 1) + PROPERTIES + OUTPUT_NAME ${THIS_BACKEND_OUTPUT_NAME} + PREFIX "lib" + CLEAN_DIRECT_OUTPUT 1) endif() # Backend installation install(FILES ${THIS_BACKEND_HEADERS} - DESTINATION - ${INCLUDEDIR}/${PROJECTNAMEL}/${NAMEL}) + DESTINATION + ${INCLUDEDIR}/${PROJECTNAMEL}/${NAMEL}) if (SOCI_SHARED) install(TARGETS ${THIS_BACKEND_TARGET} - RUNTIME DESTINATION ${BINDIR} - LIBRARY DESTINATION ${LIBDIR} - ARCHIVE DESTINATION ${LIBDIR}) + RUNTIME DESTINATION ${BINDIR} + LIBRARY DESTINATION ${LIBDIR} + ARCHIVE DESTINATION ${LIBDIR}) endif() if (SOCI_SHARED) install(TARGETS ${THIS_BACKEND_TARGET_STATIC} - RUNTIME DESTINATION ${BINDIR} - LIBRARY DESTINATION ${LIBDIR} - ARCHIVE DESTINATION ${LIBDIR}) + RUNTIME DESTINATION ${BINDIR} + LIBRARY DESTINATION ${LIBDIR} + ARCHIVE DESTINATION ${LIBDIR}) endif() - else() - colormsg(HIRED "${NAME}" RED "backend disabled, since") - endif() + else() + colormsg(HIRED "${NAME}" RED "backend disabled, since") + endif() - endif(NOT_FOUND_COUNT GREATER 0) + endif() boost_report_value(${THIS_BACKEND_OPTION}) if(${THIS_BACKEND_OPTION}) boost_report_value(${THIS_BACKEND_TARGET_VAR}) - boost_report_value(${THIS_BACKEND_TARGET_OUTPUT_NAME_VAR}) - boost_report_value(${THIS_BACKEND_HEADERS_VAR}) - - soci_report_directory_property(COMPILE_DEFINITIONS) + boost_report_value(${THIS_BACKEND_OUTPUT_NAME_VAR}) + boost_report_value(${THIS_BACKEND_COMPILE_DEFINITIONS_VAR}) + boost_report_value(${THIS_BACKEND_INCLUDE_DIRECTORIES_VAR}) endif() # LOG @@ -212,7 +249,6 @@ macro(soci_backend NAME) #message("DESCRIPTION: ${THIS_BACKEND_DESCRIPTION}") #message("AUTHORS: ${THIS_BACKEND_AUTHORS}") #message("MAINTAINERS: ${THIS_BACKEND_MAINTAINERS}") - #message("HEADERS: ${THIS_BACKEND_HEADERS}") #message("SOURCES: ${THIS_BACKEND_SOURCES}") #message("DEPENDS_LIBRARIES: ${THIS_BACKEND_DEPENDS_LIBRARIES}") #message("DEPENDS_INCLUDE_DIRS: ${THIS_BACKEND_DEPENDS_INCLUDE_DIRS}") @@ -243,7 +279,7 @@ endfunction(soci_backend_test_create_vcxproj_user) # # soci_backend_test(BACKEND mybackend SOURCE mytest1.cpp # NAME mytest1 -# CONNSTR "my test connection" +# CONNSTR "my test connection" # DEPENDS library1 library2) # macro(soci_backend_test) @@ -260,90 +296,90 @@ macro(soci_backend_test) # Test name if(THIS_TEST_NAME) - string(TOUPPER "${THIS_TEST_NAME}" NAMEU) - set(TEST_FULL_NAME SOCI_${BACKENDU}_TEST_${NAMEU}) - else() - set(TEST_FULL_NAME SOCI_${BACKENDU}_TEST) + string(TOUPPER "${THIS_TEST_NAME}" NAMEU) + set(TEST_FULL_NAME SOCI_${BACKENDU}_TEST_${NAMEU}) + else() + set(TEST_FULL_NAME SOCI_${BACKENDU}_TEST) + endif() + string(TOLOWER "${TEST_FULL_NAME}" TEST_TARGET) + string(TOUPPER "${TEST_FULL_NAME}" NAMEU) + + soci_backend_deps_found(${NAMEU} "${THIS_TEST_DEPENDS}" ${NAMEU}_DEPS_FOUND) + if(${NAMEU}_DEPS_FOUND) + get_directory_property(THIS_INCLUDE_DIRS INCLUDE_DIRECTORIES) + get_directory_property(THIS_COMPILE_DEFS COMPILE_DEFINITIONS) + + set(THIS_TEST_DEPENDS_INCLUDE_DIRS ${${NAMEU}_DEPS_INCLUDE_DIRS}) + set(THIS_TEST_DEPENDS_LIBRARIES ${${NAMEU}_DEPS_LIBRARIES}) + set(THIS_TEST_DEPENDS_DEFS ${${NAMEU}_DEPS_DEFS}) + + list(APPEND THIS_INCLUDE_DIRS ${THIS_TEST_DEPENDS_INCLUDE_DIRS}) + list(APPEND THIS_COMPILE_DEFS ${THIS_TEST_DEPENDS_DEFS}) + + set_directory_properties(PROPERTIES + 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") endif() set(TEST_CONNSTR_VAR ${TEST_FULL_NAME}_CONNSTR) set(${TEST_CONNSTR_VAR} "" CACHE STRING "Connection string for ${BACKENDU} test") - + if(NOT ${TEST_CONNSTR_VAR} AND THIS_TEST_CONNSTR) set(${TEST_CONNSTR_VAR} ${THIS_TEST_CONNSTR}) endif() boost_report_value(${TEST_CONNSTR_VAR}) - include_directories(${SOCI_SOURCE_DIR}/core/test) - include_directories(${SOCI_SOURCE_DIR}/backends/${BACKENDL}) + if( SOCI_SHARED ) + # Shared libraries test + add_executable(${TEST_TARGET} ${THIS_TEST_SOURCE}) - # TODO: Find more generic way of adding Boost to core and backend tests only. - # Ideally, from within Boost.cmake. - set(SOCI_TEST_DEPENDENCIES) - if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) - if(Boost_DATE_TIME_FOUND) - set(SOCI_TEST_DEPENDENCIES ${Boost_DATE_TIME_LIBRARY}) - add_definitions(-DHAVE_BOOST_DATE_TIME=1) - endif() - endif() + target_link_libraries(${TEST_TARGET} + ${SOCI_CORE_DEPS_LIBS} + ${THIS_TEST_DEPENDS_LIBRARIES} + soci_core + soci_${BACKENDL}) - string(TOLOWER "${TEST_FULL_NAME}" TEST_TARGET) + add_test(${TEST_TARGET} + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TARGET} + ${${TEST_CONNSTR_VAR}}) - set(TEST_HEADERS ${PROJECT_SOURCE_DIR}/core/test/common-tests.h) + soci_backend_test_create_vcxproj_user(${TEST_TARGET} "\"${${TEST_CONNSTR_VAR}}\"") - # Shared libraries test - add_executable(${TEST_TARGET} ${TEST_HEADERS} ${THIS_TEST_SOURCE}) - - target_link_libraries(${TEST_TARGET} - ${SOCI_CORE_TARGET} - ${SOCI_${BACKENDU}_TARGET} - ${${BACKENDU}_LIBRARIES} - ${SOCI_TEST_DEPENDENCIES}) - - add_test(${TEST_TARGET} - ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TARGET} - ${${TEST_CONNSTR_VAR}}) - - soci_backend_test_create_vcxproj_user(${TEST_TARGET} "\"${${TEST_CONNSTR_VAR}}\"") + # Ask make check to try to build tests first before executing them + add_dependencies(check ${TEST_TARGET}) + endif(SOCI_SHARED) # Static libraries test if(SOCI_STATIC) set(TEST_TARGET_STATIC ${TEST_TARGET}_static) - add_executable(${TEST_TARGET_STATIC} ${TEST_HEADERS} ${THIS_TEST_SOURCE}) + add_executable(${TEST_TARGET_STATIC} ${THIS_TEST_SOURCE}) target_link_libraries(${TEST_TARGET_STATIC} - ${SOCI_CORE_TARGET_STATIC} - ${SOCI_${BACKENDU}_TARGET}_static - ${${BACKENDU}_LIBRARIES} - ${SOCI_CORE_STATIC_DEPENDENCIES} - ${SOCI_TEST_DEPENDENCIES}) + ${SOCI_CORE_DEPS_LIBS} + ${THIS_TEST_DEPENDS_LIBRARIES} + soci_core_static + soci_${BACKENDL}_static) add_test(${TEST_TARGET_STATIC} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TARGET_STATIC} ${${TEST_CONNSTR_VAR}}) - + soci_backend_test_create_vcxproj_user(${TEST_TARGET_STATIC} "\"${${TEST_CONNSTR_VAR}}\"") + + # Ask make check to try to build tests first before executing them + add_dependencies(check ${TEST_TARGET_STATIC}) endif(SOCI_STATIC) - # Ask make check to try to build tests first before executing them - add_dependencies(check ${TEST_TARGET} ${TEST_TARGET_STATIC}) + # Group source files for IDE source explorers (e.g. Visual Studio) - source_group("Header Files" FILES ${TEST_HEADERS}) source_group("Source Files" FILES ${THIS_TEST_SOURCE}) source_group("CMake Files" FILES CMakeLists.txt) endif() - - # LOG - #message("NAME=${NAME}") - #message("THIS_TEST_NAME=${THIS_TEST_NAME}") - #message("THIS_TEST_BACKEND=${THIS_TEST_BACKEND}") - #message("THIS_TEST_CONNSTR=${THIS_TEST_CONNSTR}") - #message("THIS_TEST_SOURCE=${THIS_TEST_SOURCE}") - #message("THIS_TEST_OPTION=${THIS_TEST_OPTION}") - endmacro() diff --git a/src/cmake/SociConfig.cmake b/cmake/SociConfig.cmake similarity index 55% rename from src/cmake/SociConfig.cmake rename to cmake/SociConfig.cmake index fd301b221d..73610e0f2f 100644 --- a/src/cmake/SociConfig.cmake +++ b/cmake/SociConfig.cmake @@ -8,6 +8,28 @@ # http://www.boost.org/LICENSE_1_0.txt) ################################################################################ +include(CheckCXXSymbolExists) + +if(WIN32) + check_cxx_symbol_exists("_M_AMD64" "" SOCI_TARGET_ARCH_X64) + if(NOT RTC_ARCH_X64) + check_cxx_symbol_exists("_M_IX86" "" SOCI_TARGET_ARCH_X86) + endif(NOT RTC_ARCH_X64) + # add check for arm here + # see http://msdn.microsoft.com/en-us/library/b0084kay.aspx +else(WIN32) + check_cxx_symbol_exists("__i386__" "" SOCI_TARGET_ARCH_X86) + check_cxx_symbol_exists("__x86_64__" "" SOCI_TARGET_ARCH_X64) + check_cxx_symbol_exists("__arm__" "" SOCI_TARGET_ARCH_ARM) +endif(WIN32) + +# +# C++11 Option +# + +set (SOCI_CXX_C11 OFF CACHE BOOL "Build to the C++11 standard") + + # # Force compilation flags and set desired warnings level # @@ -19,35 +41,43 @@ if (MSVC) add_definitions(-D_CRT_NONSTDC_NO_WARNING) add_definitions(-D_SCL_SECURE_NO_WARNINGS) endif() - + 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") endif() - + else() set(SOCI_GCC_CLANG_COMMON_FLAGS - "-pedantic -ansi -Wall -Wpointer-arith -Wcast-align -Wcast-qual -Wfloat-equal -Wredundant-decls -Wno-long-long") + "-pedantic -Werror -Wall -Wpointer-arith -Wcast-align -Wcast-qual -Wfloat-equal -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) - - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC ${SOCI_GCC_CLANG_COMMON_FLAGS}") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC ${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} -std=gnu++98") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++98") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-variadic-macros") 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}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SOCI_GCC_CLANG_COMMON_FLAGS} ${SOCI_CXX_VERSION_FLAGS}") else() - message(FATAL_ERROR "CMake is unable to recognize compilation toolset to build SOCI for you!") + message(WARNING "Unknown toolset - using default flags to build SOCI") endif() endif() diff --git a/src/cmake/SociDependencies.cmake b/cmake/SociDependencies.cmake similarity index 93% rename from src/cmake/SociDependencies.cmake rename to cmake/SociDependencies.cmake index 3c12771a7d..a1014ed3bd 100644 --- a/src/cmake/SociDependencies.cmake +++ b/cmake/SociDependencies.cmake @@ -11,7 +11,7 @@ # http://www.boost.org/LICENSE_1_0.txt) ################################################################################ # Macros in this module: -# +# # soci_backend - defines a database backend for SOCI library # ################################################################################ @@ -19,6 +19,10 @@ # # List of SOCI dependncies # +set(SOCI_CORE_DEPENDENCIES + Threads + Boost) + set(SOCI_BACKENDS_DB_DEPENDENCIES MySQL ODBC @@ -28,13 +32,13 @@ set(SOCI_BACKENDS_DB_DEPENDENCIES Firebird DB2) -set(SOCI_BACKENDS_ALL_DEPENDENCIES - Boost +set(SOCI_ALL_DEPENDENCIES + ${SOCI_CORE_DEPENDENCIES} ${SOCI_BACKENDS_DB_DEPENDENCIES}) # # Perform checks -# +# colormsg(_HIBLUE_ "Looking for SOCI dependencies:") macro(boost_external_report NAME) @@ -46,7 +50,7 @@ macro(boost_external_report NAME) list(REMOVE_AT VARNAMES 0) # Test both, given original name and uppercase version too - if(NOT SUCCESS) + if(NOT SUCCESS) string(TOUPPER ${NAME} VARNAME) set(SUCCESS ${${VARNAME}_FOUND}) if(NOT SUCCESS) @@ -69,7 +73,7 @@ option(WITH_VALGRIND "Run tests under valgrind" OFF) # # Detect available dependencies # -foreach(external ${SOCI_BACKENDS_ALL_DEPENDENCIES}) +foreach(external ${SOCI_ALL_DEPENDENCIES}) string(TOUPPER "${external}" EXTERNAL) option(WITH_${EXTERNAL} "Attempt to find and configure ${external}" ON) if(WITH_${EXTERNAL}) diff --git a/src/cmake/SociUtilities.cmake b/cmake/SociUtilities.cmake similarity index 92% rename from src/cmake/SociUtilities.cmake rename to cmake/SociUtilities.cmake index 05e91532ff..404ef1abf8 100644 --- a/src/cmake/SociUtilities.cmake +++ b/cmake/SociUtilities.cmake @@ -5,7 +5,7 @@ ################################################################################ # Copyright (C) 2007 Douglas Gregor # Copyright (C) 2007 Troy Straszheim -# Copyright (C) 2010 Mateusz Loskot +# Copyright (C) 2010-2013 Mateusz Loskot # # Distributed under the Boost Software License, Version 1.0. # See accompanying file LICENSE_1_0.txt or copy at @@ -29,7 +29,7 @@ # occurs within a list of strings: # # list_contains(result string_to_find arg1 arg2 arg3 ... argn) -# +# # This macro sets the variable named by result equal to TRUE if # string_to_find is found anywhere in the following arguments. macro(list_contains var value) @@ -63,16 +63,16 @@ endmacro(cdr) # list of names, and the third argument is a list of options. Both of # these lists should be quoted. The rest of parse_arguments are # arguments from another macro to be parsed. -# -# parse_arguments(prefix arg_names options arg1 arg2...) -# +# +# parse_arguments(prefix arg_names options arg1 arg2...) +# # For each item in options, parse_arguments will create a variable with # that name, prefixed with prefix_. So, for example, if prefix is # MY_MACRO and options is OPTION1;OPTION2, then parse_arguments will # create the variables MY_MACRO_OPTION1 and MY_MACRO_OPTION2. These # variables will be set to true if the option exists in the command line # or false otherwise. -# +# # For each item in arg_names, parse_arguments will create a variable # with that name, prefixed with prefix_. Each variable will be filled # with the arguments that occur after the given arg_name is encountered @@ -109,8 +109,8 @@ macro(parse_arguments prefix arg_names option_names) set(${prefix}_${current_arg_name} ${current_arg_list}) endmacro(parse_arguments) -# Perform a reverse topological sort on the given LIST. -# +# Perform a reverse topological sort on the given LIST. +# # topological_sort(my_list "MY_" "_EDGES") # # LIST is the name of a variable containing a list of elements to be @@ -132,7 +132,7 @@ endmacro(parse_arguments) # using the following variables: # # MY_A_EDGES b -# MY_B_EDGES +# MY_B_EDGES # MY_C_EDGES a b # # With the involcation of topological_sort shown above and these @@ -153,7 +153,7 @@ function(topological_sort LIST PREFIX SUFFIX) # search from where. if (NOT FOUND_${UPPER_VERTEX}) # Push this vertex onto the stack with all of its outgoing edges - string(REPLACE ";" " " NEW_ELEMENT + string(REPLACE ";" " " NEW_ELEMENT "${VERTEX};${${PREFIX}${UPPER_VERTEX}${SUFFIX}}") list(APPEND STACK ${NEW_ELEMENT}) @@ -191,14 +191,14 @@ function(topological_sort LIST PREFIX SUFFIX) # Push the remaining edges for the current vertex onto the # stack - string(REPLACE ";" " " NEW_ELEMENT + string(REPLACE ";" " " NEW_ELEMENT "${SOURCE};${OUT_EDGES}") list(APPEND STACK ${NEW_ELEMENT}) # Setup the new source and outgoing edges set(SOURCE ${TARGET}) string(TOUPPER ${SOURCE} UPPER_SOURCE) - set(OUT_EDGES + set(OUT_EDGES ${${PREFIX}${UPPER_SOURCE}${SUFFIX}}) endif(NOT FOUND_${UPPER_TARGET}) @@ -275,19 +275,19 @@ function (colormsg) message(STATUS ${str}) endfunction() -# colormsg("Colors:" -# WHITE "white" GRAY "gray" GREEN "green" -# RED "red" YELLOW "yellow" BLUE "blue" MAG "mag" CYAN "cyan" -# _WHITE_ "white" _GRAY_ "gray" _GREEN_ "green" -# _RED_ "red" _YELLOW_ "yellow" _BLUE_ "blue" _MAG_ "mag" _CYAN_ "cyan" -# _HIWHITE_ "white" _HIGRAY_ "gray" _HIGREEN_ "green" -# _HIRED_ "red" _HIYELLOW_ "yellow" _HIBLUE_ "blue" _HIMAG_ "mag" _HICYAN_ "cyan" -# HIWHITE "white" HIGRAY "gray" HIGREEN "green" -# HIRED "red" HIYELLOW "yellow" HIBLUE "blue" HIMAG "mag" HICYAN "cyan" +# colormsg("Colors:" +# WHITE "white" GRAY "gray" GREEN "green" +# RED "red" YELLOW "yellow" BLUE "blue" MAG "mag" CYAN "cyan" +# _WHITE_ "white" _GRAY_ "gray" _GREEN_ "green" +# _RED_ "red" _YELLOW_ "yellow" _BLUE_ "blue" _MAG_ "mag" _CYAN_ "cyan" +# _HIWHITE_ "white" _HIGRAY_ "gray" _HIGREEN_ "green" +# _HIRED_ "red" _HIYELLOW_ "yellow" _HIBLUE_ "blue" _HIMAG_ "mag" _HICYAN_ "cyan" +# HIWHITE "white" HIGRAY "gray" HIGREEN "green" +# HIRED "red" HIYELLOW "yellow" HIBLUE "blue" HIMAG "mag" HICYAN "cyan" # "right?") # -# pretty-prints the value of a variable so that the +# pretty-prints the value of a variable so that the # equals signs align # function(boost_report_value NAME) @@ -296,7 +296,7 @@ function(boost_report_value NAME) #message(STATUS "boost_report_value: NAME=${NAME} (${varlen})") #message(STATUS "boost_report_value: \${NAME}=${${NAME}}") math(EXPR padding_len 40-${varlen}) - string(SUBSTRING " " + string(SUBSTRING " " 0 ${padding_len} varpadding) colormsg("${NAME}${varpadding} = ${${NAME}}") endfunction() @@ -309,16 +309,16 @@ function(trace NAME) 0 ${padding_len} varpadding) message("${NAME} ${varpadding} ${${NAME}}") endif() -endfunction() +endfunction() # -# pretty-prints the value of a variable so that the +# pretty-prints the value of a variable so that the # equals signs align # function(boost_report_pretty PRETTYNAME VARNAME) string(LENGTH "${PRETTYNAME}" varlen) math(EXPR padding_len 30-${varlen}) - string(SUBSTRING " " + string(SUBSTRING " " 0 ${padding_len} varpadding) message(STATUS "${PRETTYNAME}${varpadding} = ${${VARNAME}}") endfunction() @@ -348,7 +348,7 @@ macro(soci_check_package_found NAME SUCCESS) set(VARNAME_SUCCESS ${${VARNAME}_FOUND}) # Test both, given original name and uppercase version too - if(VARNAME_SUCCESS) + if(VARNAME_SUCCESS) set(${SUCCESS} TRUE) else() string(TOUPPER ${NAME} VARNAME) @@ -357,15 +357,17 @@ macro(soci_check_package_found NAME SUCCESS) set(${SUCCESS} TRUE) endif() endif() + + #message(STATUS "soci_check_package_found: ${SUCCESS}=${${SUCCESS}}") endmacro() # # Pretty-print of given property of current directory. # -macro(soci_report_directory_property PROPNAME) +function(soci_report_directory_property PROPNAME) get_directory_property(${PROPNAME} ${PROPNAME}) boost_report_value(${PROPNAME}) -endmacro() +endfunction() # # Scans the current directory and returns a list of subdirectories. diff --git a/src/cmake/SociVersion.cmake b/cmake/SociVersion.cmake similarity index 99% rename from src/cmake/SociVersion.cmake rename to cmake/SociVersion.cmake index e6758502d5..d9e277351c 100644 --- a/src/cmake/SociVersion.cmake +++ b/cmake/SociVersion.cmake @@ -8,7 +8,7 @@ # http://www.boost.org/LICENSE_1_0.txt) ################################################################################ # Macros in this module: -# +# # soci_version - defines version information for SOCI library # ################################################################################ diff --git a/src/cmake/dependencies/Boost.cmake b/cmake/dependencies/Boost.cmake similarity index 83% rename from src/cmake/dependencies/Boost.cmake rename to cmake/dependencies/Boost.cmake index c64af9eee3..366d32b748 100644 --- a/src/cmake/dependencies/Boost.cmake +++ b/cmake/dependencies/Boost.cmake @@ -1,10 +1,9 @@ set(Boost_FIND_QUIETLY TRUE) -set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_MULTITHREADED ON) find_package(Boost 1.33.1 COMPONENTS date_time) -if (NOT Boost_date_time_FOUND) +if (NOT Boost_DATE_TIME_FOUND) find_package(Boost 1.33.1) endif() diff --git a/src/cmake/dependencies/DB2.cmake b/cmake/dependencies/DB2.cmake similarity index 100% rename from src/cmake/dependencies/DB2.cmake rename to cmake/dependencies/DB2.cmake diff --git a/cmake/dependencies/Firebird.cmake b/cmake/dependencies/Firebird.cmake new file mode 100644 index 0000000000..b072702b0a --- /dev/null +++ b/cmake/dependencies/Firebird.cmake @@ -0,0 +1,9 @@ +option(SOCI_FIREBIRD_EMBEDDED "Use embedded library in Firebird backend" OFF) +boost_report_value(SOCI_FIREBIRD_EMBEDDED) + +set(Firebird_FIND_QUIETLY TRUE) + +find_package(Firebird) + +boost_external_report(Firebird INCLUDE_DIR LIBRARIES VERSION) + diff --git a/cmake/dependencies/MySQL.cmake b/cmake/dependencies/MySQL.cmake new file mode 100644 index 0000000000..5599b08858 --- /dev/null +++ b/cmake/dependencies/MySQL.cmake @@ -0,0 +1,5 @@ +set(MySQL_FIND_QUIETLY TRUE) + +find_package(MySQL) + +boost_external_report(MySQL INCLUDE_DIR LIBRARIES) diff --git a/src/cmake/dependencies/ODBC.cmake b/cmake/dependencies/ODBC.cmake similarity index 100% rename from src/cmake/dependencies/ODBC.cmake rename to cmake/dependencies/ODBC.cmake diff --git a/src/cmake/dependencies/Oracle.cmake b/cmake/dependencies/Oracle.cmake similarity index 100% rename from src/cmake/dependencies/Oracle.cmake rename to cmake/dependencies/Oracle.cmake diff --git a/src/cmake/dependencies/PostgreSQL.cmake b/cmake/dependencies/PostgreSQL.cmake similarity index 100% rename from src/cmake/dependencies/PostgreSQL.cmake rename to cmake/dependencies/PostgreSQL.cmake diff --git a/src/cmake/dependencies/SQLite3.cmake b/cmake/dependencies/SQLite3.cmake similarity index 100% rename from src/cmake/dependencies/SQLite3.cmake rename to cmake/dependencies/SQLite3.cmake diff --git a/cmake/dependencies/Threads.cmake b/cmake/dependencies/Threads.cmake new file mode 100644 index 0000000000..6a5487554b --- /dev/null +++ b/cmake/dependencies/Threads.cmake @@ -0,0 +1,4 @@ +set(Threads_FIND_QUIETLY TRUE) + +find_package(Threads) +boost_report_value(CMAKE_THREAD_LIBS_INIT) diff --git a/src/cmake/modules/FindDB2.cmake b/cmake/modules/FindDB2.cmake similarity index 95% rename from src/cmake/modules/FindDB2.cmake rename to cmake/modules/FindDB2.cmake index ba8b8165a2..2fd243efa7 100644 --- a/src/cmake/modules/FindDB2.cmake +++ b/cmake/modules/FindDB2.cmake @@ -26,7 +26,7 @@ if(UNIX) else() set(DB2_LIBDIRS "lib64") endif() - + set(DB2_FIND_INCLUDE_PATHS) set(DB2_FIND_LIB_PATHS) foreach(db2_install_path ${DB2_INSTALL_PATHS}) @@ -45,10 +45,10 @@ if(UNIX) endforeach(db2_install_path) elseif(WIN32) if (CMAKE_CL_64) # 64-bit build, DB2 64-bit installed - set(DB2_FIND_INCLUDE_PATHS $ENV{ProgramW6432}/IBM/SQLLIB/include) + set(DB2_FIND_INCLUDE_PATHS $ENV{ProgramW6432}/IBM/SQLLIB/include) set(DB2_FIND_LIB_PATHS $ENV{ProgramW6432}/IBM/SQLLIB/lib) else() # 32-bit build, DB2 64-bit or DB2 32-bit installed - + if(EXISTS "$ENV{ProgramW6432}/IBM/SQLLIB/lib") # On 64-bit Windows with DB2 64-bit installed: # LIB environment points to {DB2}/IBM/SQLLIB/lib with64-bit db2api.lib, @@ -56,8 +56,8 @@ elseif(WIN32) set(DB2_FIND_LIB_NO_LIB NO_SYSTEM_ENVIRONMENT_PATH) endif() - - set(DB2_FIND_INCLUDE_PATHS + + set(DB2_FIND_INCLUDE_PATHS $ENV{ProgramW6432}/IBM/SQLLIB/include $ENV{ProgramFiles}/IBM/SQLLIB/include) set(DB2_FIND_LIB_PATHS @@ -84,10 +84,6 @@ endif() if(DB2_INCLUDE_DIR AND DB2_LIBRARY_DIR) set(DB2_FOUND TRUE) - - include_directories(${DB2_INCLUDE_DIR}) - link_directories(${DB2_LIBRARY_DIR}) - endif() set(DB2_LIBRARIES ${DB2_LIBRARY}) diff --git a/src/cmake/modules/FindDL.cmake b/cmake/modules/FindDL.cmake similarity index 100% rename from src/cmake/modules/FindDL.cmake rename to cmake/modules/FindDL.cmake diff --git a/src/cmake/modules/FindFirebird.cmake b/cmake/modules/FindFirebird.cmake similarity index 85% rename from src/cmake/modules/FindFirebird.cmake rename to cmake/modules/FindFirebird.cmake index 4fa279df29..0e9b6784bf 100644 --- a/src/cmake/modules/FindFirebird.cmake +++ b/cmake/modules/FindFirebird.cmake @@ -14,10 +14,15 @@ find_path(FIREBIRD_INCLUDE_DIR ibase.h $ENV{ProgramFiles}/Firebird/*/include ) +if(SOCI_FIREBIRD_EMBEDDED) + set(FIREBIRD_LIB_NAMES fbembed) +else() + set(FIREBIRD_LIB_NAMES fbclient fbclient_ms) +endif() + find_library(FIREBIRD_LIBRARIES NAMES - fbclient - fbclient_ms + ${FIREBIRD_LIB_NAMES} PATHS /usr/lib $ENV{ProgramFiles}/Firebird/*/lib diff --git a/src/cmake/modules/FindMySQL.cmake b/cmake/modules/FindMySQL.cmake similarity index 94% rename from src/cmake/modules/FindMySQL.cmake rename to cmake/modules/FindMySQL.cmake index f9b3908503..76112f140a 100644 --- a/src/cmake/modules/FindMySQL.cmake +++ b/cmake/modules/FindMySQL.cmake @@ -23,6 +23,7 @@ if(WIN32) $ENV{MYSQL_DIR}/include $ENV{ProgramFiles}/MySQL/*/include $ENV{SystemDrive}/MySQL/*/include + $ENV{ProgramW6432}/MySQL/*/include ) else(WIN32) find_path(MYSQL_INCLUDE_DIR mysql.h @@ -54,41 +55,30 @@ if(WIN32) endif(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") # find_library(MYSQL_LIBRARIES NAMES mysqlclient - find_library(MYSQL_LIBRARIES NAMES libmysql - PATHS + set(MYSQL_LIB_PATHS $ENV{MYSQL_DIR}/lib/${binary_dist} $ENV{MYSQL_DIR}/libmysql/${build_dist} $ENV{MYSQL_DIR}/client/${build_dist} $ENV{ProgramFiles}/MySQL/*/lib/${binary_dist} $ENV{SystemDrive}/MySQL/*/lib/${binary_dist} + $ENV{MYSQL_DIR}/lib/opt + $ENV{MYSQL_DIR}/client/release + $ENV{ProgramFiles}/MySQL/*/lib/opt + $ENV{SystemDrive}/MySQL/*/lib/opt + $ENV{ProgramW6432}/MySQL/*/lib + ) + find_library(MYSQL_LIBRARIES NAMES mysqlclient libmysql + PATHS + ${MYSQL_LIB_PATHS} ) else(WIN32) # find_library(MYSQL_LIBRARIES NAMES mysqlclient - find_library(MYSQL_LIBRARIES NAMES libmysql - PATHS + set(MYSQL_LIB_PATHS $ENV{MYSQL_DIR}/libmysql_r/.libs $ENV{MYSQL_DIR}/lib $ENV{MYSQL_DIR}/lib/mysql /usr/local/mysql/lib /opt/mysql/mysql/lib - PATH_SUFFIXES - mysql - ) -endif(WIN32) - -if(WIN32) - set(MYSQL_LIB_PATHS - $ENV{MYSQL_DIR}/lib/opt - $ENV{MYSQL_DIR}/client/release - $ENV{ProgramFiles}/MySQL/*/lib/opt - $ENV{SystemDrive}/MySQL/*/lib/opt - ) - find_library(MYSQL_LIBRARIES NAMES mysqlclient - PATHS - ${MYSQL_LIB_PATHS} - ) -else(WIN32) - set(MYSQL_LIB_PATHS $ENV{MYSQL_DIR}/libmysql_r/.libs $ENV{MYSQL_DIR}/lib $ENV{MYSQL_DIR}/lib/mysql diff --git a/src/cmake/modules/FindODBC.cmake b/cmake/modules/FindODBC.cmake similarity index 65% rename from src/cmake/modules/FindODBC.cmake rename to cmake/modules/FindODBC.cmake index 85ecdcfe9d..fd3d9e36df 100644 --- a/src/cmake/modules/FindODBC.cmake +++ b/cmake/modules/FindODBC.cmake @@ -1,13 +1,13 @@ -# +# # Find the ODBC driver manager includes and library. -# +# # ODBC is an open standard for connecting to different databases in a # semi-vendor-independent fashion. First you install the ODBC driver # manager. Then you need a driver for each separate database you want # to connect to (unless a generic one works). VTK includes neither # the driver manager nor the vendor-specific drivers: you have to find # those yourself. -# +# # This module defines # ODBC_INCLUDE_DIR, where to find sql.h # ODBC_LIBRARIES, the libraries to link against to use ODBC @@ -27,26 +27,31 @@ find_path(ODBC_INCLUDE_DIR sql.h "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:/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." ) -find_library(ODBC_LIBRARY - NAMES iodbc odbc odbcinst odbc32 - PATHS - /usr/lib - /usr/lib/odbc - /usr/local/lib - /usr/local/lib/odbc - /usr/local/odbc/lib - "C:/Program Files (x86)/Windows Kits/8.0/Lib/win8/um/x86/" - "C:/Program Files (x86)/Microsoft SDKs/Windows/v7.0A/Lib" - "C:/Program Files/ODBC/lib" - "C:/ODBC/lib/debug" - DOC "Specify the ODBC driver manager library here." -) +if(MSVC) + # msvc knows where to find sdk libs + set(ODBC_LIBRARY odbc32) +else() + find_library(ODBC_LIBRARY + NAMES iodbc odbc odbcinst odbc32 + PATHS + /usr/lib + /usr/lib/odbc + /usr/local/lib + /usr/local/lib/odbc + /usr/local/odbc/lib + "C:/Program Files (x86)/Windows Kits/8.0/Lib/win8/um/x86/" + "C:/Program Files (x86)/Microsoft SDKs/Windows/v7.0A/Lib" + "C:/Program Files/ODBC/lib" + "C:/ODBC/lib/debug" + DOC "Specify the ODBC driver manager library here." + ) +endif() if(ODBC_LIBRARY) if(ODBC_INCLUDE_DIR) diff --git a/cmake/modules/FindOracle.cmake b/cmake/modules/FindOracle.cmake new file mode 100644 index 0000000000..1daf24c005 --- /dev/null +++ b/cmake/modules/FindOracle.cmake @@ -0,0 +1,76 @@ +############################################################################### +# +# CMake module to search for Oracle client library (OCI) +# +# On success, the macro sets the following variables: +# ORACLE_FOUND = if the library found +# ORACLE_LIBRARY = full path to the library +# ORACLE_LIBRARIES = full path to the library +# ORACLE_INCLUDE_DIR = where to find the library headers also defined, +# but not for general use are +# ORACLE_VERSION = version of library which was found, e.g. "1.2.5" +# +# Copyright (c) 2009-2013 Mateusz Loskot +# +# Developed with inspiration from Petr Vanek +# who wrote similar macro for TOra - http://torasql.com/ +# +# Module source: http://github.com/mloskot/workshop/tree/master/cmake/ +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +############################################################################### + +# First check for CMAKE variable +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) + +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 + +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 + +find_library(ORACLE_OCI_LIBRARY +NAMES ${ORACLE_OCI_NAMES} PATHS ${ORACLE_LIB_DIR}) +find_library(ORACLE_OCCI_LIBRARY +NAMES ${ORACLE_OCCI_NAMES} PATHS ${ORACLE_LIB_DIR}) +find_library(ORACLE_NNZ_LIBRARY +NAMES ${ORACLE_NNZ_NAMES} PATHS ${ORACLE_LIB_DIR}) + +set(ORACLE_LIBRARY +${ORACLE_OCI_LIBRARY} +${ORACLE_OCCI_LIBRARY} +${ORACLE_NNZ_LIBRARY}) + +if(NOT WIN32) +set(ORACLE_LIBRARY ${ORACLE_LIBRARY} ${ORACLE_CLNTSH_LIBRARY}) +endif(NOT WIN32) + +set(ORACLE_LIBRARIES ${ORACLE_LIBRARY}) + +# Handle the QUIETLY and REQUIRED arguments and set ORACLE_FOUND to TRUE +# if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(ORACLE DEFAULT_MSG ORACLE_LIBRARY ORACLE_INCLUDE_DIR) + +mark_as_advanced(ORACLE_INCLUDE_DIR ORACLE_LIBRARY) diff --git a/src/cmake/modules/FindPostgreSQL.cmake b/cmake/modules/FindPostgreSQL.cmake similarity index 97% rename from src/cmake/modules/FindPostgreSQL.cmake rename to cmake/modules/FindPostgreSQL.cmake index 8178418ccb..91e9cc20cd 100644 --- a/src/cmake/modules/FindPostgreSQL.cmake +++ b/cmake/modules/FindPostgreSQL.cmake @@ -31,7 +31,7 @@ if(PG_CONFIG) exec_program(${PG_CONFIG} ARGS "--includedir" - OUTPUT_VARIABLE PG_CONFIG_INCLUDEDIR) + OUTPUT_VARIABLE PG_CONFIG_INCLUDEDIR) exec_program(${PG_CONFIG} ARGS "--libdir" @@ -53,7 +53,7 @@ find_path(POSTGRESQL_INCLUDE_DIR libpq-fe.h find_library(POSTGRESQL_LIBRARIES NAMES pq libpq PATHS - ${PG_CONFIG_LIBDIR} + ${PG_CONFIG_LIBDIR} /usr/lib /usr/local/lib /usr/lib/postgresql diff --git a/src/cmake/modules/FindSQLite3.cmake b/cmake/modules/FindSQLite3.cmake similarity index 98% rename from src/cmake/modules/FindSQLite3.cmake rename to cmake/modules/FindSQLite3.cmake index 2efb4d4b60..1359bc79ab 100644 --- a/src/cmake/modules/FindSQLite3.cmake +++ b/cmake/modules/FindSQLite3.cmake @@ -46,7 +46,7 @@ find_library(SQLITE3_LIBRARY ${SQLITE_ROOT_DIR}/lib $ENV{OSGEO4W_ROOT}/lib) -set(SQLITE3_LIBRARIES +set(SQLITE3_LIBRARIES ${SQLITE3_LIBRARIES} ${SQLITE3_LIBRARY}) diff --git a/src/cmake/modules/FindSoci.cmake b/cmake/modules/FindSoci.cmake similarity index 100% rename from src/cmake/modules/FindSoci.cmake rename to cmake/modules/FindSoci.cmake diff --git a/src/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in b/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in similarity index 100% rename from src/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in rename to cmake/resources/vs2010-test-cmd-args.vcxproj.user.in diff --git a/doc/backends.html b/doc/backends.html deleted file mode 100644 index 869fd5ca6d..0000000000 --- a/doc/backends.html +++ /dev/null @@ -1,499 +0,0 @@ - - - - - - SOCI - backends - - - - - -

Backends reference

- -

This part of the documentation is provided for those who want to -write (and contribute!) their own backends. It is anyway recommended -that authors of new backend see the code of some existing backend for -hints on how things are really done.

- -

The backend interface is a set of base classes that the actual backends -are supposed to specialize. The main SOCI interface uses only the -interface and respecting the protocol (for example, the order of -function calls) described here. Note that both the interface and the -protocol 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 other -backends, where things are done differently and therefore have to be -adjusted, 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.

- -

All names are defined in either soci or soci::details -namespace.

- -
-// 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 };
-
-// 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) {}
-
-    char * str_;
-    std::size_t bufSize_;
-};
-
-// 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 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 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.

- -
-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 pre_fetch() = 0;
-    virtual void post_fetch(bool gotData, bool calledFromFetch, indicator* ind) = 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.

-
    -
  • 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 retrieved -some 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 for -each value retrieved from the database. If the given server supports -binary data transmission and the data format for the given type agrees -with what is used on the client machine, then these two functions need -not do anything; otherwise buffer management and data conversions -should go there.

- -
-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 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 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<T> -(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.

- -
-class standard_use_type_backend
-{
-public:
-    virtual ~standard_use_type_backend() {}
-
-    virtual void bind_by_pos(int& position,
-        void* data, exchange_type type, bool readOnly) = 0;
-    virtual void bind_by_name(std::string const& name,
-        void* data, exchange_type type, bool readOnly) = 0;
-
-    virtual void pre_use(indicator const* ind) = 0;
-    virtual void post_use(bool gotData, indicator* ind) = 0;
-
-    virtual void clean_up() = 0;
-};
-
- -

The standard_use_type_backend implements the interactions -with the simple (non-bulk) use elements, created and -destroyed by the statement object.

-
    -
  • 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.
  • -
  • 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.

- -
-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.

- -
-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
-    {
-        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.

-
    -
  • 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.
  • -
  • prepare - Called once with the text of the SQL -query. For servers that support explicit query preparation, this is the -place to do it.
  • -
  • execute - Called to execute the query; if number is -zero, the intent is not to exchange data with the user-provided objects -(into and use elements); positive values -mean the number of rows to exchange (more than 1 is used only for bulk -operations).
  • -
  • fetch - Called to fetch next bunch of rows; number -is positive and determines the requested number of rows (more than 1 is -used only for bulk operations).
  • -
  • get_affected_rows - Called to determine the actual -number of rows affected by data modifying statement.
  • -
  • get_number_of_rows - Called to determine the actual -number of rows retrieved by the previous call to execute -or fetch.
  • -
  • rewrite_for_procedure_call - Used when the procedure -is used instead of statement, to call the stored -procedure. This function should rewrite the SQL query (if necessary) to -the form that will allow to execute the given procedure. -
  • -
  • prepare_for_describe - Called once when the into -element is used with the row type, which means that -dynamic rowset description should be performed. It is supposed to do -whatever is needed to later describe the column properties and should -return the number of columns.
  • -
  • 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:

-
    -
  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. -
  3. 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. -
  4. -
- -
-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() {}
-
-    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() {}
-
-    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 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;
-};
-
- -

The object of the class derived from session_backend implements the -internals of the session object.

-
    -
  • begin, commit, rollback -- Forward-called when the same functions of session are -called by user.
  • -
  • 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() {}
-
-    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).

- -

The actual backend factory object is supposed to be provided by the -backend implementation and declared in its header file. In addition to this, -the factory_ABC function with the "C" calling convention -and returning the pointer to concrete factory object should be provided, -where ABC is the backend name.

- -

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;
-
-extern "C"
-{
-
-// for dynamic backend loading
-backend_factory const * factory_postgresql();
-
-} // 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.

- - - - - - - - - - - - - diff --git a/doc/backends/db2.html b/doc/backends/db2.html deleted file mode 100644 index d366518149..0000000000 --- a/doc/backends/db2.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - SOCI - DB2 Backend Reference - - - - - -

DB2 Backend Reference

- - - -

Prerequisites

-

Supported Versions

- -

The SOCI DB2 backend .

- -

Tested Platforms

- - - - - - - - - - - -
DB2 versionOperating SystemCompiler
-Linux PPC64GCC
9.1LinuxGCC
9.5LinuxGCC
9.7LinuxGCC
10.1LinuxGCC
10.1Windows 8Visual Studio 2012
- -

Required Client Libraries

- -

The SOCI DB2 backend requires IBM DB2 Call Level Interface (CLI) library.

- -

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
- -

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");
-
- -

SOCI Feature Support

-

Dynamic Binding

- -

TODO

- -

Binding by Name

- -

TODO

- -

Bulk Operations

- -

Supported, but with caution as it hasn't been extensively tested.

- -

Transactions

- -

Currently, not supported.

- -

BLOB Data Type

- -

Currently, not supported.

- -

Nested Statements

- -

Nesting statements are not processed by SOCI in any special way and -they work as implemented by the DB2 database.

- -

Stored Procedures

- -

Stored procedures are supported, with CALL statement.

- -

Acessing the native database API

- -

TODO

- -

Backend-specific extensions

- -

None.

- -

Configuration options

- -

None

- - - - diff --git a/doc/backends/firebird.html b/doc/backends/firebird.html deleted file mode 100644 index c1deafb20c..0000000000 --- a/doc/backends/firebird.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - SOCI - Firebird Backend Reference - - - - - -

Firebird Backend Reference

- - - -

Prerequisites

-

Supported Versions

- -

The SOCI Firebird backend is currently supported for use with Firebird 1.5.
-Other versions of Firebird may work as well, but they have not been tested by the SOCI team.

- -

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
- -

Required Client Libraries

- -

The Firebird backend requires Firebird's libfbclient client library.

- -

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:

- -
-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");
-
- -

The set of parameters used in the connection string for Firebird is:

-
    -
  • service
  • -
  • user
  • -
  • password
  • -
  • role
  • -
  • charset
  • -
-

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:

-
-int count;
-sql << "select count(*) from user_tables", into(count);
-
- -

(See the SOCI basics and exchanging data documentation for general information on using the Session class.)

- -

SOCI Feature Support

-

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.

- -

When calling Row::get<T>(), the type you should pass as T depends upon the underlying database type. For the Firebird backend, this type mapping is:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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 -currently not supported.

-

(See the dynamic resultset binding documentation for general information on using the Row class.)

- -

Binding by Name

- -

In addition to binding by position, the Firebird backend supports binding by 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.

- -

Bulk Operations

- -

The Firebird backend has full support for SOCI's bulk operations interface. This feature is also supported by emulation.

- -

Transactions

- -

Transactions are also fully -supported by the Firebird backend. In fact, there is always a transaction which is automatically commited in Session's destructor. -
See the Configuration options section for more details.

- -

BLOB Data Type

- -

The Firebird backend supports working with data stored in columns of type Blob, via SOCI's 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.

- -

RowID Data Type

- -

This feature is not supported by Firebird backend.

- -

Nested Statements

- -

This feature is not supported by Firebird backend.

- -

Stored Procedures

- -

Firebird stored procedures can be executed by using SOCI's Procedure class.

- -

Acessing the native database API

- -

SOCI provides access to underlying datbabase APIs via several getBackEnd() functions, as described in the beyond SOCI documentation.

- -

The Firebird backend provides the following concrete classes for navite API access:

- - - - - - - - - - - - - - - - - - - - - - - - -
Accessor FunctionConcrete Class
SessionBackEnd* Session::getBackEnd()FirebirdSessionBackEnd
StatementBackEnd* Statement::getBackEnd()FirebirdStatementBackEnd
BLOBBackEnd* BLOB::getBackEnd()FirebirdBLOBBackEnd
RowIDBackEnd* RowID::getBackEnd()FirebirdRowIDBackEnd
- - -

Backend-specific extensions

-

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.

- -

Configuration options

- -

The Firebird backend recognize the following configuration macros :

-
    -
  • SOCI_FIREBIRD_NORESTARTTRANSACTION - - Transactions will not be restarted automatically after commit() or rollback(). - The default is to restart transactions.
  • -
- - - - diff --git a/doc/backends/mysql.html b/doc/backends/mysql.html deleted file mode 100644 index 11cf0af15e..0000000000 --- a/doc/backends/mysql.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - - - SOCI - MySQL Backend Reference - - - - - -

MySQL Backend Reference

- - - -

Prerequisites

-

Supported Versions

- -

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.

- -

Tested Platforms

- - - - - - - - - -
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.

-

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

- -

To establish a connection to a MySQL server, create a session object -using the mysql backend factory together with a connection -string:

- -
-session sql(mysql, "db=test user=root password='Ala ma kota'");
-
-// or:
-session sql("mysql", "db=test user=root password='Ala ma kota'");
-
-// or:
-session sql("mysql://db=test user=root password='Ala ma kota'");
-
- -

The set of parameters used in the connection string for MySQL is:

-
    -
  • dbname or db or service - (required)
  • -
  • user
  • -
  • password or pass
  • -
  • host
  • -
  • port
  • -
  • unix_socket
  • -
  • sslca
  • -
  • sslcert
  • -
  • local_infile - should be 0 or 1, - 1 means MYSQL_OPT_LOCAL_INFILE will be set.
  • -
  • charset
  • -
- -

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);
-
- -

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

- -

SOCI Feature Support

-

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
- -

(See the dynamic resultset binding documentation for general information on using the Row class.)

- -

Binding by Name

- -

In addition to binding by -position, the MySQL backend supports -binding by 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 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

- -

The MySQL backend has full support for SOCI's bulk operations interface. This feature is also supported -by emulation.

- -

Transactions

- -

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

- -

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

- -

The rowid functionality is not supported by the MySQL backend.

- -

Nested Statements

- -

Nested statements are not supported by the MySQL backend.

- -

Stored Procedures

- -

MySQL version 5.0 and later supports two kinds of -stored routines: stored procedures and stored functions -(for details, please consult the -MySQL -documentation). Stored functions can be executed by using -SOCI's procedure class. -There is currently no support for stored procedures.

- -

Accessing the native database API

- -

SOCI provides access to underlying datbabase APIs via several get_backend() functions, as described in the Beyond SOCI 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
- - -

Backend-specific extensions

- -

None.

- -

Configuration options

- -

None.

- - - - diff --git a/doc/backends/odbc.html b/doc/backends/odbc.html deleted file mode 100644 index 12dcf4e538..0000000000 --- a/doc/backends/odbc.html +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - SOCI - ODBC Backend Reference - - - - - -

ODBC Backend Reference

- - - -

Prerequisites

-

Supported Versions

- -

The SOCI ODBC backend is supported for use with ODBC 3.

- -

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)
- -

Required Client Libraries

- -

The SOCI ODBC backend requires the ODBC client library.

- -

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");
-
- -

or simply:

- -
-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 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);
-
- -

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

- -

SOCI Feature Support

-

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<T>(), 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
- -

Not all ODBC drivers support all datatypes

- -

(See the dynamic resultset binding documentation for general information on using the row class.)

- -

Binding by Name

- -

In addition to binding by position, the ODBC backend supports binding by name, via an overload of the use() function:

- -
-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);
-
- -

Bulk Operations

- -

The ODBC backend has support for SOCI's bulk operations 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
- -

Transactions

- -

Transactions are also fully supported by the ODBC backend, provided that they are supported by the underlying database.

- -

BLOB Data Type

- -

Not currently supported

- -

RowID Data Type

- -

Not currently supported

- -

Nested Statements

- -

Not currently supported

- -

Stored Procedures

- -

Not currently supported

- -

Acessing the native database API

- -

SOCI provides access to underlying datbabase APIs via several getBackEnd() functions, as described in the beyond SOCI 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
- - -

Backend-specific extensions

-

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()
-{
-    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;
-    }
-}
-
- -

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

- -

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);
-
- - - - - diff --git a/doc/backends/oracle.html b/doc/backends/oracle.html deleted file mode 100644 index 48bd41c77e..0000000000 --- a/doc/backends/oracle.html +++ /dev/null @@ -1,260 +0,0 @@ - - - - - - SOCI - Oracle Backend Reference - - - - - -

Oracle Backend Reference

- - - -

Prerequisites

-

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

- - - - - - -
Oracle versionOperating SystemCompiler
10.2.0 (XE)RedHat 5g++ 4.3
- -

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
-
- -

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");
-
-// or:
-session sql("oracle", "service=orcl user=scott password=tiger");
-
-// or:
-session sql("oracle://service=orcl user=scott password=tiger");
-
- -

The set of parameters used in the connection string for Oracle is:

-
    -
  • service
  • -
  • user
  • -
  • password
  • -
  • mode (optional)
  • -
-

The first 3 of these parameters have to be provided as part of the connection string.

-

The mode parameter allows to specify the connection mode and can be any of:

-
    -
  • default (which is assumed if omitted)
  • -
  • sysdba
  • -
  • sysoper
  • -
- -

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);
-
- -

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

- -

SOCI Feature Support

- -

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<T>(), the type you should pass as T depends upon the underlying 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
- -

(See the dynamic resultset binding documentation for general information on using the row class.)

- -

Binding by Name

- -

In addition to binding by position, the Oracle backend supports binding by name, via an overload of the use() function:

- -
-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

- -

The Oracle backend has full support for SOCI's bulk operations interface.

- -

Transactions

- -

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

- -

The Oracle backend supports working with data stored in columns of type Blob, via SOCI's blob class.

- -

rowid Data Type

- -

Oracle rowid's are accessible via SOCI's rowid class.

- -

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();
-
-while (stInner.fetch())
-{
-    std::cout << name << '\n';
-}
-
- -

Stored Procedures

- -

Oracle stored procedures can be executed by using SOCI's procedure class.

- -

Acessing the native database API

- -

SOCI provides access to underlying datbabase APIs via several get_backend() functions, as described in the Beyond SOCI 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
- - -

Backend-specific extensions

-

eracle=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()
-{
-    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;
-    }
-}
-
- - - - diff --git a/doc/backends/postgresql.html b/doc/backends/postgresql.html deleted file mode 100644 index cee1ef3f87..0000000000 --- a/doc/backends/postgresql.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - SOCI - PostgreSQL Backend Reference - - - - - -

PostgreSQL Backend Reference

- - - -

Prerequisites

-

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

- - - - - - - - - -
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
- -

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
-
- -

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:

- -
-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 function from the libpq 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);
-
- -

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

- -

SOCI Feature Support

- -

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<T>(), 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
- -

(See the dynamic resultset binding documentation for general information on using the row class.)

- -

Binding by Name

- -

In addition to binding by position, the PostgreSQL backend supports binding by name, via an overload of the use() function:

- -
-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);
-
- -

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 section for details.

- -

Bulk Operations

- -

The PostgreSQL backend has full support for SOCI's bulk operations interface.

- -

Transactions

- -

Transactions are also fully supported by the PostgreSQL backend.

- -

blob Data Type

- -

The PostgreSQL backend supports working with data stored in columns of type Blob, via SOCI's blob class with the exception that trimming is not supported.

- -

rowid Data Type

- -

The concept of row identifier (OID in PostgreSQL) is supported via SOCI's rowid class.

- -

Nested Statements

- -

Nested statements are not supported by PostgreSQL backend.

- -

Stored Procedures

- -

PostgreSQL stored procedures can be executed by using SOCI's procedure class.

- -

Acessing the native database API

- -

SOCI provides access to underlying datbabase APIs via several get_backend() functions, as described in the beyond SOCI 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
- - -

Backend-specific extensions

- -

None.

- -

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.
  • -
- - - - - diff --git a/doc/backends/sqlite3.html b/doc/backends/sqlite3.html deleted file mode 100644 index 51221123a9..0000000000 --- a/doc/backends/sqlite3.html +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - SOCI - SQLite3 Backend Reference - - - - - -

SQLite3 Backend Reference

- - - -

Prerequisites

-

Supported Versions

- -

The SOCI SQLite3 backend is supported for use with SQLite3 >= 3.1

- -

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
- -

Required Client Libraries

- -

The SOCI SQLite3 backend requires SQLite3's libsqlite3 client library.

- -

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");
-
- -

The only option for the connection string is the name of the file to use as a database.

- -

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);
-
- -

(See the SOCI basics and exchanging data documentation for general information on using the session class.)

- -

SOCI Feature Support

-

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.

- -

When calling row::get<T>(), the type you should pass as T depends upon the underlying database type.

- -

For the SQLite3 backend, this type mapping is complicated by the fact the SQLite3 does not enforce types *, 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*dt_oubledouble
*int*dt_integerint
*char*dt_stringstd::string
*date*, *time*dt_datestd::tm
- -

* 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 documentation for general information on using the row class.)

- -

Binding by Name

- -

In addition to binding by position, the SQLite3 backend supports binding by name, via an overload of the use() function:

- -
-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]:

- -
-int i = 7;
-int j = 8;
-sql << "insert into t(x, y) values(?, ?)", use(i), use(j);
-
- -

Bulk Operations

- -

The SQLite3 backend has full support for SOCI's bulk operations interface. However, this support is emulated and is not native.

- -

Transactions

- -

Transactions are also fully supported by the SQLite3 backend.

- -

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

- -

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]

- -

Nested Statements

- -

Nested statements are not supported by SQLite3 backend.

- -

Stored Procedures

- -

Stored procedures are not supported by SQLite3 backend

- -

Acessing the native database API

- -

SOCI provides access to underlying datbabase APIs via several -get_backend() functions, as described in the -beyond SOCI 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
- - -

Backend-specific extensions

- -

None.

- -

Configuration options

- -

None

- - - - diff --git a/doc/beyond.html b/doc/beyond.html deleted file mode 100644 index 14a661e0d0..0000000000 --- a/doc/beyond.html +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - SOCI - 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.

- -

Getting the number of rows affected by an operation

- -

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);
-
-if ( !st.get_affected_rows() )
-{
-    ... investigate why no rows were modified ...
-}
-
- -
-

Portability note:

-This method is currently not supported by the Oracle backend. It is however -supported when using Oracle database via ODBC backend.

-
- - -

Working with 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.

- -

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);
-
-    // 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:

-These methods are currently only implemented in Firebird and ODBC backends.

-
- - -

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 -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.

- -
-blob b(sql);
-
-oracle_session_back_end * sessionBackEnd = static_cast<oracle_session_back_end *>(sql.get_back_end());
-oracle_blob_back_end * blobBackEnd = static_cast<oracle_blob_back_end *>(b.get_back_end());
-
-OCILobDisableBuffering(sessionBackEnd->svchp_, sessionBackEnd->errhp_, blobBackEnd->lobp_);
-
- -

The above code creates the blob object and uses two calls -to the get_back_end function (on both the session -and the blob objects) to get access to the actual backend -objects. Assuming that it is the "oracle" backend which -is in use, the downcasts allow to access all relevant low-level handles -and use them in the call -to the OCILobDisableBuffering function. This way, the -BLOB handle was configured in a way that the SOCI library alone would -not allow.

- -
-rowid rid(sql); // sql is a session object
-sql << "select oid from mytable where id = 7", into(rid);
-
-postgresql_rowid_back_end * rbe = static_cast<postgresql_rowid_back_end *>(rid.get_back_end());
-
-unsigned long oid = rbe->value_;
-
- -

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.

- - - - - - - - - - - - diff --git a/doc/boost.html b/doc/boost.html deleted file mode 100644 index dc441e8b73..0000000000 --- a/doc/boost.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - SOCI - integration with Boost - - - - - -

Integration with Boost

- -

The SOCI user code can be easily integrated with the Boost library thanks to the very flexible type conversion facility. There are three important Boost types that are supported out of the box.

- -

boost::optional<T>

- -

boost::optional<T> provides an alternative way to support the null data condition and as such relieves the user from necessity to handle separate indicator values.

-

The boost::optional<T> objects can be used everywhere where the regular user provided -values are expected.

-

Example:

- -
-boost::optional<string> 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<T> 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<T1, ...>

- -

boost::tuple<T1, ...> 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.

-

Example:

- -
-boost::tuple<string, string, int> person;
-
-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 can be also composed with boost::optional<T>:

- -
-boost::tuple<string, boost::optional<string>, int> person;
-
-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
-}
-
- -

boost::fusion::vector<T1, ...>

- -

The boost::fusion::vector types are supported in the same way as tuples.

- -

boost::gregorian::date

- -

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 <boost-optional.h>
-#include <boost-tuple.h>
-#include <boost-fusion.h>
-#include <boost-gregorian-date.h>
-
-

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.

-
- - - - - - - - - - - diff --git a/doc/connections.html b/doc/connections.html deleted file mode 100644 index d12167296a..0000000000 --- a/doc/connections.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - SOCI - connections - - - - - -

Connections and simple queries

- -

Connecting to the database

- -

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.

- -

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");
-
- -

Another example might be:

- -
-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:

- -
-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");
-
- -

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:

-
-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 <soci-odbc.h> to obtain the option name declaration. The existing options are described in the backend-specific part of the documentation. - -
-

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 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:

- -
-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.

- -

An alternative way to set up the session is to create it in the disconnected state and connect later:

- -
-session sql;
-
-// some time later:
-sql.open(postgresql, "dbname=mydb");
-
-// or:
-sql.open("postgresql://dbname=mydb");
-
-// 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 closed and reconnected, 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 for a detailed description of connection pools.

- -

It is possible to have many active sessions at the same -time, even using different backends.

- -
-

Portability note:

-

The following backend factories are currently (as of 3.1.0 release) available:

-
    -
  • mysql (requires #include "soci-mysql.h")
  • -
  • oracle (requires #include "soci-oracle.h")
  • -
  • postgresql (requires #include "soci-postgresql.h")
  • -
-

The following backends are also available, with various levels of completeness:

-
    -
  • sqlite3 (requires #include "soci-sqlite3.h")
  • -
  • odbc (requires #include "soci-odbc.h")
  • -
  • firebird (requires #include "soci-firebird.h")
  • -
-
- - - - - - - - - - - - - diff --git a/doc/errors.html b/doc/errors.html deleted file mode 100644 index 5c147af53f..0000000000 --- a/doc/errors.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - SOCI - 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()
-{
-    try
-    {
-        // regular code
-    }
-    catch (std::exception const & e)
-    {
-        cerr << "Bang! " << e.what() << endl;
-    }
-}
-
- -
-

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()
-{
-    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;
-    }
-}
-
-
- -
-

Portability note:

-

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()
-{
-    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;
-    }
-}
-
-
- -
-

Portability note:

-

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()
-{
-    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;
-    }
-}
-
-
- - - - - - - - - - - diff --git a/doc/exchange.html b/doc/exchange.html deleted file mode 100644 index de9628e727..0000000000 --- a/doc/exchange.html +++ /dev/null @@ -1,758 +0,0 @@ - - - - - - SOCI - exchanging data - - - - - -

Exchanging data

- - - -

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.

-
- -

Binding output data

- -

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 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. -
  3. there is a person with id = 7, but he has no name (his name is -null in the database table)
  4. -
  5. there is no such person
  6. -
- -

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<string> names(100);
-vector<indicator> 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:

- -
-vector<int> ids;
-vector<string> names;
-vector<indicator> 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 to learn -how the Boost.Optional library can be used to handle null data conditions -in a more natural way.

- -

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<char>
  • -
  • std::vector<short>
  • -
  • std::vector<int>
  • -
  • std::vector<unsigned long>
  • -
  • std::vector<long long>
  • -
  • std::vector<double>
  • -
  • std::vector<std::string>
  • -
  • std::vector<std::tm>
  • -
- -

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.

- -

Note that bulk operations are supported only for std::vectors of the types listed above.

- -

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 << "<row>" << 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<std::string>(i);
-        break;
-    case dt_double:
-        doc << r.get<double>(i);
-        break;
-    case dt_integer:
-        doc << r.get<int>(i);
-        break;
-    case dt_long_long:
-        doc << r.get<long long>(i);
-        break;
-    case dt_unsigned_long_long:
-        doc << r.get<unsigned long long>(i);
-        break;
-    case dt_date:
-        std::tm when = r.get<std::tm>(i);
-        doc << asctime(&when);
-        break;
-    }
-
-    doc << "</" << props.get_name() << '>' << std::endl;
-}
-doc << "</row>";
-
- -

The type T parameter that should be passed to -row::get<T>() depends on the SOCI data type that -is returned from -column_properties::get_data_type().

-

row::get<T>() -throws an exception of type -std::bad_cast if an incorrect type T is -requested.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOCI Data Typerow::get<T> specialization
dt_doubledouble
dt_integerint
dt_long_longlong long
dt_unsigned_long_longunsigned long long
dt_stringstd::string
dt_datestd::tm
- -

The mapping of underlying database column types to SOCI datatypes is database specific. -See the backend documentation for details.

- -

The row also provides access to indicators for each column:

- -
-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<std::string>(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:

- -
-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 -(see next page).

- -

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<MyInt>
-    {
-        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<MyInt> is enough -to enable the following:

- -
-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 for complete description. Use these as examples of conversions for more complext data types.

- -

Note also that user-defined datatypes are not supported with bulk data transfer.

- -

Another possibility to extend SOCI with custom data types is to use -the into_type<T> and use_type<T> -class templates, which specializations can be user-provided. These -specializations need to implement the interface defined by, -respectively, the into_type_base and use_type_base -classes.

- -

Note that when specializing these template classes the only convention -is that when the indicator -variable is used (see below), it should appear in the second position. -Please refer to the library source code to see how this is done for the -standard types.

- -

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<Person>
-    {
-        typedef values base_type;
-
-        static void from_base(values const & v, indicator /* ind */, Person & p)
-        {
-            p.id = v.get<int>("ID");
-            p.firstName = v.get<std::string>("FIRST_NAME");
-            p.lastName = v.get<std::string>("LAST_NAME");
-
-            // p.gender will be set to the default value "unknown"
-            // when the column is null:
-            p.gender = v.get<std::string>("GENDER", "unknown");
-
-            // alternatively, the indicator can be tested directly:
-            // if (v.indicator("GENDER") == i_null)
-            // {
-            //     p.gender = "unknown";
-            // }
-            // else
-            // {
-            //     p.gender = v.get<std::string>("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:

- -
-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.

-
- -

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:

-
    -
  1. 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.
  2. -
  3. The trim function is not currently available for -the PostgreSQL backend.
  4. -
-
- - - - - - - - - - - - - diff --git a/doc/index.html b/doc/index.html deleted file mode 100644 index 77f66368cf..0000000000 --- a/doc/index.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - SOCI - - - - - -

Documentation and tutorial

- - - -

The following (complete!) example is purposedly provided without any explanation.

- -
-#include "soci.h"
-#include "soci-oracle.h"
-#include <iostream>
-#include <istream>
-#include <ostream>
-#include <string>
-#include <exception>
-
-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/doc/installation.html b/doc/installation.html deleted file mode 100644 index 3855a8ea9d..0000000000 --- a/doc/installation.html +++ /dev/null @@ -1,466 +0,0 @@ - - - - - - SOCI - structure - - - - - -

Installation

- - - -

Requirements

- -

Below is an overall list of SOCI core:

- - -

and backend-specific dependencies:

- - -

Downloading

- -

Download package with latest release of the SOCI source code: soci-X.Y.Z, 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
- -

Building using CMake

- -

SOCI is configured to build using CMake 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) 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 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.

- - - - - - - - - - - - - - - - - - -
List of a few essential CMake variables
CMAKE_BUILD_TYPEstringSpecifies the build type for make based generators (see CMake help).
CMAKE_INSTALL_PREFIXpathInstall directory used by install command (see CMake help).
CMAKE_VERBOSE_MAKEFILEbooleanIf ON, create verbose makefile (see CMake help).
- - - - - - - - - - - - - - - - - - -
List of variables to control common SOCI features and dependencies
SOCI_STATICbooleanRequest to build static libraries, along with shared, of SOCI core and all successfully configured backends.
SOCI_TESTSbooleanRequest to build regression tests for SOCI core and all successfully configured backends.
WITH_BOOSTbooleanShould CMake try to detect Boost C++ Libraries. If ON, CMake will try to find Boost headers and binaries of Boost.Date_Time library.
- -

IBM DB2

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOCI DB2 backend configuration
WITH_DB2booleanShould CMake try to detect IBM DB2 Call Level Interface (CLI) library.
DB2_INCLUDE_DIRstringPath to DB2 CLI include directories where CMake should look for sqlcli1.h header.
DB2_LIBRARIESstringFull paths to db2 or db2api libraries to link SOCI against to enable the backend support.
SOCI_DB2booleanRequests to build DB2 backend. Automatically switched on, if WITH_DB2 is set to ON.
SOCI_DB2_TEST_CONNSTRstringSee DB2 backend refernece for details. Example: -DSOCI_DB2_TEST_CONNSTR:STRING="DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off"
- -

Firebird

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOCI Firebird backend configuration
WITH_FIREBIRDbooleanShould CMake try to detect Firebird client library.
FIREBIRD_INCLUDE_DIRstringPath to Firebird include directories where CMake should look for ibase.h header.
FIREBIRD_LIBRARIESstringFull paths to Firebird fbclient or fbclient_ms libraries to link SOCI against to enable the backend support.
SOCI_FIREBIRDbooleanRequests to build Firebird backend. Automatically switched on, if WITH_FIREBIRD is set to ON.
SOCI_FIREBIRD_TEST_CONNSTRstringSee Firebird backend refernece for details. Example: -DSOCI_FIREBIRD_TEST_CONNSTR:STRING="service=LOCALHOST:/tmp/soci_test.fdb user=SYSDBA password=masterkey"
- -

MySQL

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOCI MySQL backend configuration
WITH_MYSQLbooleanShould CMake try to detect mysqlclient libraries providing MySQL C API. Note, currently the mysql_config program is not being used.
MYSQL_DIRstringPath to MySQL installation root directory. CMake will scan subdirectories MYSQL_DIR/include and MYSQL_DIR/lib respectively for MySQL headers and libraries.
MYSQL_INCLUDE_DIRstringPath to MySQL include directory where CMake should look for mysql.h header.
MYSQL_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_MYSQLbooleanRequests to build MySQL backend. Automatically switched on, if WITH_MYSQL is set to ON.
SOCI_MYSQL_TEST_CONNSTRstringConnection string to MySQL test database. Format of the string is explained MySQL backend refernece. Example: -DSOCI_MYSQL_TEST_CONNSTR:STRING="db=mydb user=mloskot password=secret"
- -

ODBC

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOCI ODBC backend configuration
WITH_ODBCbooleanShould CMake try to detect ODBC libraries. On Unix systems, CMake tries to find unixODBC or iODBC implementations.
ODBC_INCLUDE_DIRstringPath to ODBC implementation include directories where CMake should look for sql.h header.
ODBC_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_ODBCbooleanRequests to build ODBC backend. Automatically switched on, if WITH_ODBC is set to ON.
SOCI_ODBC_TEST_{database}_CONNSTRstringODBC 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 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_ORACLEbooleanShould CMake try to detect Oracle Call Interface (OCI) libraries.
ORACLE_INCLUDE_DIRstringPath to Oracle include directory where CMake should look for oci.h header.
ORACLE_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_ORACLEbooleanRequests to build Oracle backend. Automatically switched on, if WITH_ORACLE is set to ON.
SOCI_ORACLE_TEST_CONNSTRstringConnection string to Oracle test database. Format of the string is explained Oracle backend refernece. Example: -DSOCI_ORACLE_TEST_CONNSTR:STRING="service=orcl user=scott password=tiger"
- -

PostgreSQL

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOCI PostgreSQL backend configuration
WITH_POSTGRESQLbooleanShould CMake try to detect PostgreSQL client interface libraries. SOCI relies on libpq C library.
POSTGRESQL_INCLUDE_DIRstringPath to PostgreSQL include directory where CMake should look for libpq-fe.h header.
POSTGRESQL_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_POSTGRESQLbooleanRequests to build PostgreSQL backend. Automatically switched on, if WITH_POSTGRESQL is set to ON.
SOCI_POSTGRESQL_TEST_CONNSTRbooleanConnection string to PostgreSQL test database. Format of the string is explained PostgreSQL backend refernece. Example: -DSOCI_POSTGRESQL_TEST_CONNSTR:STRING="dbname=mydb user=mloskot"
- -

SQLite 3

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOCI SQLite 3 backend configuration
WITH_SQLITE3booleanShould CMak try to detect SQLite C/C++ library. As bonus, the configuration tries OSGeo4W distribution if OSGEO4W_ROOT environment variable is set.
SQLITE_INCLUDE_DIRstringPath to SQLite 3 include directory where CMake should look for sqlite3.h header.
SQLITE_LIBRARIESstringFull paths to libraries to link SOCI against to enable the backend support.
SOCI_SQLITE3booleanRequests to build SQLite3 backend. Automatically switched on, if WITH_SQLITE3 is set to ON.
SOCI_SQLITE3_TEST_CONNSTRstringConnection string is simply a file path where SQLite3 test database will be created (e.g. /home/john/soci_test.db). Check SQLite3 backend refernece for details. Example: -DSOCI_SQLITE3_TEST_CONNSTR="my.db"
- -

Empty (sample backend)

- - - - - - - - - - - - - -
SOCI empty sample backend configuration
SOCI_EMPTYbooleanBuilds the sample backend called Empty. Always ON by default.
SOCI_EMPTY_TEST_CONNSTRstringConnection string used to run regression tests of the Empty backend. It is a dummy value. Example: -DSOCI_EMPTY_TEST_CONNSTR="dummy connection"
- -

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.

- -

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.

- -

Building using CMake on Unix

- -

Short version using GNU Make makefiles:

-
-$ mkdir build
-$ cd build
-$ cmake -G "Unix Makefiles" -DWITH_BOOST=OFF -DWITH_ORACLE=OFF (...) ../soci-X.Y.Z
-$ make
-$ make install
-
- -

Building using CMake on Windows

- -

Short version using Visual Studio 2010 and MSBuild:

- -
-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
-
- -

Building using classic Makefiles on Unix (deprecated)

- -

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.

- -

The classic set of Makefiles for Unix/Linux systems is provided for those users who need complete control over the whole process -and 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.

- -

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
-
- -
-

Note: -For each backend and its test program, the Makefile.basics -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.basics. -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

- -

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).

- -

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.

- -

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
-
- -

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

- -

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.3.2.2, libsoci_sqlite3.so.3.2.2, 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.

- -

- - - - - - - - - - - diff --git a/doc/interfaces.html b/doc/interfaces.html deleted file mode 100644 index 72e168ace9..0000000000 --- a/doc/interfaces.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - SOCI - integration with Boost - - - - - -

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

- -

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");
-
-int id = 123;
-string name;
-
-sql << "select name from persons where id = :id", into(name), use(id);
-
- -

Core

- -

The above example is equivalent to the following, more explicit sequence of calls:

-
-session sql("postgresql://dbname=mydb");
-
-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);
-
- -

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

- -

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>
-
-// ...
-session_handle sql = soci_create_session("postgresql://dbname=mydb");
-
-statement_handle st = soci_create_statement(sql);
-
-soci_use_int(st, "id");
-soci_set_use_int(st, "id", 123);
-
-int namePosition = soci_into_string(st);
-
-soci_prepare(st, "select name from persons where id = :id");
-
-soci_execute(st, true);
-
-char const * name = soci_get_into_string(st, namePosition);
-
-printf("name is %s\n", name);
-
-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.

-

See Simple client interface reference documentation for more details.

- -

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.

- - - - - - - - - - - - diff --git a/doc/languages/ada/concepts.html b/doc/languages/ada/concepts.html deleted file mode 100644 index 4880c5597a..0000000000 --- a/doc/languages/ada/concepts.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - SOCI-Ada - documentation - - - -

SOCI-Ada - manual

- -

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. -

-

-One of the main properties of the library is that the data objects which are bound for transfer to and from the database server are managed by the library itself and are not directly visible from the user code. This ensures that no aliasing of objects occurs between Ada and underlying C++ code, which makes the inter-language interface easier and more resilient to the differences in how compilers handle the linkage. As a direct result of this design choice, users of SOCI-Ada need to instruct the library to internally create all objects that will be subject to data transfer. -

-

-There are two kinds of objects that can be managed by the SOCI-Ada library: -

-
    -
  • Into elements, which are data objects that are transferred from the database to the user program as a result of executing a query. There are single into elements for binding single rows of results and vector into elements for binding whole bunches of data corresponding to whole result sets or their subranges. The into elements are identified by their position.
  • -
  • 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
  • -
  • SOCI.DB_Integer, which is defined by the library in terms of Interfaces.C.int
  • -
  • SOCI.DB_Long_Long_Integer, which is defined in terms of Interfaces.Integer_64
  • -
  • SOCI.DB_Long_Float, which is defined in terms of Interfaces.C.double
  • -
  • Ada.Calendar.Time
  • -
-

-Both into and use elements are managed for a single statement, which can be prepared once and executed once or many times, with data transfer handled during execution or fetch phase. -

-

-Statements can be managed explicitly, which is required if they are to be used repeteadly or when data transfer is needed or implicitly, which is a shorthand notation that is particularly useful with simple queries or DDL commands. -

-

-All statements are handled within the context of some session, which also supports transactions. -

-

-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. -

- - - - - - - - - - - diff --git a/doc/languages/ada/idioms.html b/doc/languages/ada/idioms.html deleted file mode 100644 index 07452d3812..0000000000 --- a/doc/languages/ada/idioms.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - SOCI-Ada - documentation - - - -

SOCI-Ada - manual

- -

Common 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. -

-

-The idioms below are provided as complete programs with the intent to make them more understandable and to give complete context of use for each idiom. The programs assume that the target database is PostgreSQL, but this can be changed by a different connection string in each place where the sessions are established. The programs use the Ada 2005 interface and some minor changes will be required to adapt them for Ada 95 compilers. -

- -

Single query without data transfer

-

-This type of query is useful for DDL commands and can be executed directly on the given session, without explicit statement management. -

-
-with SOCI;
-
-procedure My_Program is
-
-   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
-
-begin
-
-   SQL.Execute ("drop table some_table");
-
-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. -

-
- -

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. -

-
-with SOCI;
-with Ada.Text_IO;
-
-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;
-
-   Num_Of_Persons : SOCI.DB_Integer;
-
-begin
-
-   Pos := St.Into_Integer;
-   St.Prepare ("select count(*) from persons");
-   St.Execute (True);
-
-   Num_Of_Persons := St.Get_Into_Integer (Pos);
-
-   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. -

-
- -

Simple query with parameters and without results

-

-This type of query requires only use elements. -

-
-with SOCI;
-
-procedure My_Program is
-
-   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
-   St : SOCI.Statement := SOCI.Make_Statement (SQL);
-
-begin
-
-   St.Use_Integer ("increase");
-   St.Set_Use_Integer ("increase", 1000);
-
-   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. -

-
- -

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. -

-
-with SOCI;
-
-procedure My_Program is
-
-   SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database");
-   St : SOCI.Statement := SOCI.Make_Statement (SQL);
-
-begin
-
-   St.Use_String ("name");
-
-   St.Prepare ("insert into countries(country_name) values(:name)");
-
-   St.Set_Use_String ("name", "Poland");
-   St.Execute (True);
-
-   St.Set_Use_String ("name", "Switzerland");
-   St.Execute (True);
-
-   St.Set_Use_String ("name", "France");
-   St.Execute (True);
-
-end My_Program;
-
- -
-

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;
-
-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;
-
-   use type SOCI.Vector_Index;
-
-begin
-
-   St.Use_Vector_String ("name");
-
-   St.Use_Vectors_Resize (3);
-
-   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.Prepare ("insert into countries(country_name) values(:name)");
-   St.Execute (True);
-
-end My_Program;
-
- -
-

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:

-

-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;
-
-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;
-
-begin
-
-   Pos := St.Into_String;
-
-   St.Prepare ("select country_name from countries");
-   St.Execute;
-
-   while St.Fetch loop
-
-      Ada.Text_IO.Put_Line (St.Get_Into_String (Pos));
-
-   end loop;
-
-end My_Program;
-
- -
-

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;
-
-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;
-
-   Batch_Size : constant := 10;
-
-begin
-
-   Pos := St.Into_Vector_String;
-   St.Into_Vectors_Resize (Batch_Size);
-
-   St.Prepare ("select country_name from countries");
-   St.Execute;
-
-   while St.Fetch 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));
-
-      end loop;
-
-      St.Into_Vectors_Resize (Batch_Size);
-
-   end loop;
-
-end My_Program;
-
- -
-

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. -

-

-There is a tradeoff between efficiency and memory usage and this tradeoff is controlled by the requested batch size. Similarly to one of the examples above, there is no benefit from using batches bigger than tens of thousands of rows. -

-

-This type of query can have simple (not vectors) parameters that are fixed at execution time. -

-
- -
-

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/doc/languages/ada/index.html b/doc/languages/ada/index.html deleted file mode 100644 index dfa859b7c0..0000000000 --- a/doc/languages/ada/index.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - SOCI-Ada Language Binding - documentation - - - -

SOCI-Ada Language Binding - documentation

- -

-Introduction
-Compilation
-Concepts
-Common Idioms
-API Reference
-

- -
- -

Introduction

-

-SOCI-Ada is a database access library for Ada. -

-

-The library itself is a wrapper for the selected functionality of the SOCI library, which is a C++ database access library recognized for its high quality and innovative interface. -

-

-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
  • -
-

-Other backends exist in the SOCI Git repository and can be provided with future version of the library. -

- -

Compilation

-

-In order to use SOCI-Ada, compile the C++ parts first (core and required backends). -

-

-Note: SOCI header files are not needed to use SOCI-Ada, only compiled SOCI libraries (core and relevant backend) need to exist to build and use SOCI-Ada programs. -

-

-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. -

- - - - - - - - - - - diff --git a/doc/languages/ada/reference.html b/doc/languages/ada/reference.html deleted file mode 100644 index 42dc415bac..0000000000 --- a/doc/languages/ada/reference.html +++ /dev/null @@ -1,674 +0,0 @@ - - - - - SOCI-Ada - documentation - - - -

SOCI-Ada - manual

- -

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: -

- -
-
-   --
-   --  General exception related to database and library usage.
-   --
-
-   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.
-   --
-
-   type Session is tagged limited private;
-
-   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 Close (This : in out Session);
-
-   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: -

-
- -

-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. -

- -
-
-   --  Transaction management.
-
-   not overriding
-   procedure Start (This : in Session);
-
-   not overriding
-   procedure Commit (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);
-
-

-This operation allows to create implicit statement, prepare it for the given Query and execute it. -

- -
-
-   --
-   --  Connection pool management.
-   --
-
-   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 Close (This : in out Connection_Pool; Position : in Positive);
-
-   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 Opened and Closed 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:

-

-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 Sessions. -

-
- -
-
-   --
-   --  Statement.
-   --
-
-   type Statement (<>) is tagged limited private;
-
-   type Data_State is (Data_Null, Data_Not_Null);
-
-   type Into_Position is private;
-
-   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. -

-

-Into_Position is used to identify into elements. Vector_Index is used to name individual entries in vector into and use elements. -

- -
-
-   not overriding
-   function Make_Statement (Sess : in Session'Class) return Statement;
-
-   --  Ada 95 version:
-   --  procedure Make_Statement (This : in out Statement; Sess : in Session'Class);
-
-

-Construction function for creating Statement objects. The Statement is associated with one Session for its whole lifetime. -

- -
-
-   --  Statement preparation and execution.
-
-   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
-   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 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. -

-

-The Execute operations cause the statement to execute, which might be combined with data exchange if requested. The function version of this operation returns True if some data has been returned back from the database server. -

-

-The Fetch function is used to transfer next portion of data (a single row or a whole bunch) from the database server and returns True if some data has been fetched. If this function returns False it means that no new data will be ever fetched for this statement and indicates the end-of-row condition. -

-

-The Got_Data function returns True if the last execution or fetch resulted in some data being transmitted from the database server. -

- -
-
-   --
-   --  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.
-
-   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: -

-
    -
  • String
  • -
  • DB_Integer, defined above
  • -
  • DB_Long_Long_Integer, defined above
  • -
  • DB_Long_Float, defined above
  • -
  • Ada.Calendar.Time
  • -
- -
-
-   --  Creation of single into elements.
-
-   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_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;
-
-

-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:

-

-Simple into elements cannot be created together with vector into elements for the same statement. -

-
- -
-
-   --  Creation of vector into elements.
-
-   not overriding
-   function Into_Vector_String (This : in Statement) return Into_Position;
-
-   not overriding
-   function Into_Vector_Integer (This : in Statement) return Into_Position;
-
-   not overriding
-   function Into_Vector_Long_Long_Integer (This : in Statement) return Into_Position;
-
-   not overriding
-   function Into_Vector_Long_Float (This : in Statement) return Into_Position;
-
-   not overriding
-   function Into_Vector_Time (This : in Statement) return Into_Position;
-
-

-These functions instruct the library to create internal vector into elements of the relevant type. They return the position of the into element, which can be later used to identify it. -

- -
-

Note:

-

-Vector into elements are empty (they have size 0) after they are created and have to be resized before any data is written to them. -

-
- -
-

Note:

-

-Simple into elements cannot be created together with vector into elements for the same statement. -

-
- -
-
-   --  Inspection of single into elements.
-
-   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;
-
-   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;
-
-

-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.
-
-   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_Last_Index (This : in Statement) return Vector_Index;
-
-   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. -

-

-The Into_Vectors_First_Index returns the lowest index value for vector into elements (which is always 0, even if the vectors are empty). The Into_Vectors_Last_Index returns the last index of into vectors, and raises the CONSTRAINT_ERROR exception if the vectors are empty. -

-

-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;
-
-   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_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_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. -

- -
-
-   --  Creation of single use elements.
-
-   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_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_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. -

-

-Vector use elements cannot be created together with any into elements for the same statement. -

-
- -
-
-   --  Creation of vector use elements.
-
-   not overriding
-   procedure Use_Vector_String (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);
-
-

-These functions instruct the library to create internal vector 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. -

-

-Vector use elements cannot be created together with any into elements for the same statement. -

-
- -
-
-   --  Modifiers for single use elements.
-
-   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);
-
-   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);
-
-

-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. -

- -
-
-   --  Modifiers for vector use elements.
-
-   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_Last_Index (This : in Statement) return Vector_Index;
-
-   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. -

-

-The Use_Vectors_First_Index returns the lowest index value for vector use elements (which is always 0, even if the vectors are empty). The Use_Vectors_Last_Index returns the last index of use vectors, and raises the CONSTRAINT_ERROR exception if the vectors are empty. -

-

-The Use_Vectors_Resize procedure allows to change the size of all use vectors for the given statement. -

- -
-
-   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_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_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. -

- -
-
-   --  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_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_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_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. -

- - - - - - - - - - - diff --git a/doc/languages/ada/style.css b/doc/languages/ada/style.css deleted file mode 100644 index 7f6dc8ca6f..0000000000 --- a/doc/languages/ada/style.css +++ /dev/null @@ -1,67 +0,0 @@ -body -{ - background-color: white; - color: black; - margin-left: 100px; - margin-right: 100px; - font-family: arial, sans-serif; - font-size: small; -} - -div.note -{ - border-color: black; - border-style: dotted; - border-width: 1px; - margin-top: 20px; - padding-left: 20px; - padding-right: 20px; -} - -span.note -{ - font-size: large; - font-weight: bold; -} - -code -{ -/* font-weight: bold; */ - background-color: white; - color: #8B0000; -/* font-size: large; */ -} - -pre.example -{ - background-color: #F0F0F0; - color: black; - border-width: 1px; - border-style: dashed; - border-color: blue; - padding: 10px 10px 10px 10px; -} - -table.foot-links -{ - width: 100%; - padding-top: 20px; -} - -td.foot-link-left -{ - text-align: left; -} - -td.foot-link-right -{ - text-align: right; -} - -p.copyright -{ - border-top-color: black; - border-top-style: solid; - border-top-width: 1px; - padding-top: 5px; -} diff --git a/doc/multithreading.html b/doc/multithreading.html deleted file mode 100644 index a22a742ecc..0000000000 --- a/doc/multithreading.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - SOCI - 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.

- -

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:

- -
-// phase 1: preparation
-
-const size_t poolSize = 10;
-connection_pool pool(poolSize);
-
-for (size_t i = 0; i != poolSize; ++i)
-{
-    session & sql = pool.at(i);
-
-    sql.open("postgresql://dbname=mydb");
-}
-
-// phase 2: usage from working threads
-
-{
-    session sql(pool);
-
-    sql << "select something from somewhere...";
-
-} // session is returned to the pool automatically
-
- -

The connection_pool's constructor expects the size of the pool and internally creates an array of sessions 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.

- -

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 for details.

- - - - - - - - - - - diff --git a/doc/queries.html b/doc/queries.html deleted file mode 100644 index b8afd4725d..0000000000 --- a/doc/queries.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - SOCI - queries - - - - - -

Queries

- -

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";
-
- -

For shorter syntax, the following form is also allowed:

- -
-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<<):

- -
-string tableName = "persons";
-sql << "drop table " << tableName;
-
-int id = 123;
-sql << "delete from companies where id = " << id;
-
- -

Query transformation

- -

In SOCI 3.2.0, query transformation mechanism was introduced.

- -

Query transformation is specified as user-defined unary function or callable -function object with input parameter of type std::string which -returns object of type std::string as well.

- -

The query transformation function is registered for current database session -using dedicated session::set_query_transformation method. -Then, the transformation function is called with query string as argument -just before the query is sent to database backend for execution or for -preparation.

- -

For one-time statements, query transformation is performed before -each execution of statement. For prepared statements, query is transformed -only once, before preparation, regardless how many times it is executed.

- -

A few short examples how to use query transformation:

- -
  • defined as free function:
- -
-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:
- -
-struct order : std::unary_function<std::string, std::string>
-{
-    order(std::string const& by) : by_(by) {}
-
-    result_type operator()(argument_type query) const
-    {
-        return query + " ORDER BY " + by_;
-    }
-
-    std::string by_;
-};
-
-char const* query = "SELECT * FROM product";
-sql.set_query_transformation(order("price");
-sql << query;
-sql.set_query_transformation(order("id");
-sql << query;
-
- -
  • defined as lambda function (since C++11):
- -
-std::string dep = "sales";
-sql.set_query_transformation(
-    [&dep](std::string const& query) {
-        return query + " WHERE department = '" + dep + "'";
-});
-sql << "SELECT * FROM employee";
-
- -

Query transformations enable users with simple mechanism to apply extra -requirements to or interact with SQL statement being executed and that is -without changing the SQL statement itself which may be passed from different -parts of application.

- -

For example, the query transformation may be used to:

-
    -
  • modify or add clauses of SQL statements (i.e. WHERE - clause with new condition)
  • -
  • prefix table names with new schema to allow namespaces switch
  • -
  • validate SQL statements
  • -
  • perform sanitization checking for any unverified input
  • -
  • apply database-specific features like add optimization hints to SQL - statements (i.e. SELECT /*+RULE*/ A FROM C in Oracle 9)
  • -
- -

Query transformation mechanism can also be considered for similar uses as -prefix_with function from -SQLAlchemy Expressions API.

- - - - - - - - - - - - diff --git a/doc/rationale.html b/doc/rationale.html deleted file mode 100644 index deb5faab0e..0000000000 --- a/doc/rationale.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - SOCI - rationale - - - - - -

Rationale 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"?

- -

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?

- -

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);
-    /* ... */
-}
-
- -

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, specialized 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);
-
- -

(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)

- -

Apart from mimicking the Embedded SQL techniques in the regular, fully -standard C++ code, the above syntax has the following benefit: it is -minimal with respect to what has to -be said. Every single piece above is needed and expresses something -important, like:

-
    -
  • which session should be used (the client can be connected to many -databases at the same time) - here, the sql object -encapsulates the session,
  • -
  • what SQL query should be executed - here, it's the string -literal, but it could be also a std::string variable,
  • -
  • where to put the result - here, the local variable a -will receive the result.
  • -
- -

Everything else is just a couple of operators that allow to treat the -whole as a single expression. It's rather difficult to remove anything -from this example.

- -

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.

- -

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-unlike) -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?

- -

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");
-
-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
-
-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.

- -

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?

- -

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 -that allows to use boost::optional<T> to conveniently pack together -the data and the information about its state.

- -

Q: Overloaded comma operator is just obfuscation, I don't like it.

- -

Well, consider the following:

- -

"Send the query X to the server Y and put result into variable Z."

- -

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.

- -

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?

- -

We decided to use the Boost license, 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 plain -uncertainty) 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 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 -on the relevant SOCI page) to discuss any remaining concerns.

- - - - - - - - - - - diff --git a/doc/reference.html b/doc/reference.html deleted file mode 100644 index d265f75e26..0000000000 --- a/doc/reference.html +++ /dev/null @@ -1,963 +0,0 @@ - - - - - - SOCI - reference - - - - - -

Client interface reference

- - - -

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, but documented on the next page.

- -

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 };
-
-// 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 data_type type defines the basic SOCI data types. -User provided data types need to be associated with one of these basic -types.

- -

The indicator type defines the possible states of data.

- -

The soci_error type is used for error reporting.

- -

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);
-
-    ~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 begin();
-    void commit();
-    void rollback();
-
-    IT once;
-    IT prepare;
-
-    template <typename T> IT operator<<(T const & t);
-
-    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);
-
-    std::ostringstream & get_query_stream();
-
-    void set_log_stream(std::ostream * s);
-    std::ostream * get_log_stream() const;
-
-    std::string get_last_query() const;
-
-    void uppercase_column_names(bool forceToUpper);
-
-    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");
    -
    - 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";
    -
    -
  • -
  • 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));
    -
    -
  • -
  • operator<< that is a shortcut forwarder to the -equivalent operator of the once member. Example: -
    -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" section.
  • -
  • get_last_insert_id returns true if it could retrieve the - last value automatically generated by the database for an auto-incremented - field. Notice that although this method takes the table name, for some - databases, such as Microsoft SQL Server and SQLite, this value is actually - global, so you should attempt to retrieve it immediately after performing an - insertion.
  • -
  • get_query_stream provides direct access to the stream object that is used - to accumulate the query text and exists in particular to allow the user to imbue specific locale - to this stream.
  • -
  • 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_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.
  • -
- -

See Connections and simple queries for more -examples.

- -

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.

- -
-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
-};
-
- -

The methods of this class are:

-
    -
  • Default constructor is rarely used as it creates an uninitialized - object and the only way to initialize it later is to assign another, valid, - connection_parameters object to this one.
  • -
  • 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

- -

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();
-
-    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);
-};
-
- -

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.
  • -
  • at function that provides direct access to any given entry - in 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.
  • -
-

Note: calls to lease and give_back are automated by the -dedicated constructor of the session class, see above.

- -

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);
-
-    ~transaction();
-
-    void commit();
-    void rollback();
-
-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

- -

The function into is used for binding local output data -(in other words, it defines where the results of the query are stored).

- -
-template <typename T>
-IT into(T & t);
-
-template <typename T, typename T1>
-IT into(T & t, T1 p1);
-
-template <typename T>
-IT into(T & t, indicator & ind);
-
-template <typename T, typename T1>
-IT into(T & t, indicator & ind, T1 p1);
-
-template <typename T>
-IT into(T & t, std::vector<indicator> & ind);
-
- -

Example:

- -
-int count;
-sql << "select count(*) from person", into(count);
-
- -

See Binding local data -for more examples.

- -

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 <typename T>
-IT use(T & t);
-
-template <typename T, typename T1>
-IT use(T & t, T1 p1);
-
-template <typename T>
-IT use(T & t, indicator & ind);
-
-template <typename T, typename T1>
-IT use(T & t, indicator & ind, T1 p1);
-
-template <typename T>
-IT use(T & t, std::vector<indicator> const & ind);
-
-template <typename T, typename T1>
-IT use(T & t, std::vector<indicator> const & ind, T1 p1);
-
- -

Example:

- -
-int val = 7;
-sql << "insert into numbers(val) values(:val)", use(val);
-
- -

See Binding local data -for more examples.

- -

class statement

- -

The statement class encapsulates the prepared statement.

- -
-class statement
-{
-public:
-    statement(session & s);
-    statement(IT const & prep);
-    ~statement();
-
-    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 prepare(std::string const & query);
-    void define_and_bind();
-
-    bool execute(bool withDataExchange = false);
-    long long get_affected_rows();
-    bool fetch();
-
-    bool got_data() const;
-
-    void describe();
-    void set_row(row * r);
-    void exchange_for_rowset(IT const & i);
-
-    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);
    -
    -
  • -
  • Constructor accepting the result of using prepare -on the session object, see example provided above for the - session class.
  • -
  • Copy operations.
  • -
  • alloc function, which allocates necessary internal resources.
  • -
  • bind function, which is used to bind the values -object - this is used in the object-relational mapping and normally -called automatically.
  • -
  • exchange functions for registering the binding of local data - -they expect the result of calling the into or use -functions and are normally invoked automatically.
  • -
  • clean_up function for cleaning up resources, normally -called automatically.
  • -
  • prepare function for preparing the statement for -repeated execution.
  • -
  • define_and_bind function for actually executing the -registered bindings, normally called automatically.
  • -
  • execute function for executing the statement. If its -parameter is false then there is no data exchange with -locally bound variables (this form should be used if later fetch -of multiple rows is foreseen). Returns true if there was at least -one row of data returned.
  • -
  • 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.
  • -
  • 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.
  • -
- -

See Statement preparation and -repeated execution for example uses.

- -

Most of the functions from the statement class -interface are called automatically, but can be also used explicitly. -See Interfaces for the description of various way to use -this interface.

- -

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);
-
-    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 for -examples.

- -

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 <typename T>
-struct type_conversion
-{
-    typedef T base_type;
-
-    static void from_base(base_type const & in, indicator ind, T & out);
-
-    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.

- -

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();
-
-    void uppercase_column_names(bool forceToUpper);
-
-    std::size_t size() 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;
-
-    template <typename T>
-    T get(std::size_t pos) const;
-
-    template <typename T>
-    T get(std::size_t pos, T const & nullValue) const;
-
-    template <typename T>
-    T get(std::string const & name) const;
-
-    template <typename T>
-    T get(std::string const & name, T const & nullValue) const;
-
-    template <typename T>
-    row const & operator>>(T & value) const;
-
-    void skip(std::size_t num = 1) const;
-
-    void reset_get_counter() const
-};
-
- -

This class contains the following members:

-
    -
  • Default constructor that allows to declare a row -variable.
  • -
  • uppercase_column_names - see the same function in the session class.
  • -
  • size function that returns the number of columns in -the row.
  • -
  • get_indicator function that returns the indicator value -for the given column (column is specified by position - starting from 0 -- or by name).
  • -
  • get_properties function that returns the properties -of the column given by position (starting from 0) or by name.
  • -
  • get functions that return the value of the column -given by position or name. If the column contains null, then these -functions either return the provided "default" nullValue -or throw an exception.
  • -
  • operator>> for convenience stream-like -extraction interface. Subsequent calls to this function are equivalent -to calling get with increasing position parameter, -starting from the beginning.
  • -
  • skip and reset_get_counter allow to change the -order of data extraction for the above operator.
  • -
- -

See Dynamic resultset binding for -examples.

- -

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;
-};
-
- -

This class contains the following members:

-
    -
  • get_name function that returns the name of the column.
  • -
  • get_data_type that returns the type of the column.
  • -
- -

See Dynamic resultset binding for -examples.

- -

class values

- -

The values class encapsulates the data and type -information and is used for object-relational mapping.

- -
-class values
-{
-public:
-    values();
-
-    void uppercase_column_names(bool forceToUpper);
-
-    indicator get_indicator(std::size_t pos) const;
-    indicator get_indicator(std::string const & name) const;
-
-    template <typename T>
-    T get(std::size_t pos) const;
-
-    template <typename T>
-    T get(std::size_t pos, T const & nullValue) const;
-
-    template <typename T>
-    T get(std::string const & name) const;
-    
-    template <typename T>
-    T get(std::string const & name, T const & nullValue) const;
-
-    template <typename T>
-    values const & operator>>(T & value) const;
-
-    void skip(std::size_t num = 1) const;
-    void reset_get_counter() const;
-    
-    template <typename T>
-    void set(std::string const & name, T const & value, indicator indic = i_ok);
-
-    template <typename T>
-    void set(const T & value, indicator indic = i_ok);
-
-    template <typename T>
-    values & operator<<(T const & value);
-};
-
- -

This class contains the same members as the row class (with the same meaning) -plus:

-
    -
  • set function for storing values in named columns or in subsequent positions.
  • -
  • operator<< for convenience.
  • -
- -

See Object-relational mapping -for examples.

- -

class blob

- -

The blob class encapsulates the "large object" -functionality.

- -
-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);
-
-    details::blob_backend * get_backend();
-};
-
- -

This class contains the following members:

-
    -
  • Constructor associating the blob object with the session object.
  • -
  • get_len function that returns the size of the BLOB -object.
  • -
  • read function that reads the BLOB data into provided -buffer.
  • -
  • write function that writes the BLOB data from -provided buffer.
  • -
  • append function that appends to the existing BLOB -data.
  • -
  • trim function that truncates the existing data to -the new length.
  • -
  • get_backend function that returns the internal -pointer to -the concrete backend implementation of the BLOB object. This is -provided -for advanced users that need access to the functionality that is not -otherwise available.
  • -
- -

See Large objects (BLOBs) for more -discussion.

- -

class rowid

- -

The rowid class encapsulates the "row identifier" object.

- -
-class rowid
-{
-public:
-    explicit rowid(Session & s);
-    ~rowid();
-
-    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

- -

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;
-};
-
- -

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";
-
-session sql(factory, parameters);
-
- -

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.

-

The functionality of this interface is limited and in particular the -dynamic rowset description and type conversions are not supported in this release. -On the other hand, the important feature of this interface is that it does not -require passing pointers to data managed by the user, because all data is handled -at the SOCI side. This should make it easier to integrate SOCI with languages that -have constrained ability to understand the C type system.

-

Users of this interface need to explicitly #include <soci-simple.h>.

- -
-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);
-
-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.

- -
-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);
-
- -

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.

- -
-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_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.

-

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);
-
- -

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.

- -
-int  soci_into_get_size_v(statement_handle st);
-void soci_into_resize_v  (statement_handle st, int new_size);
-
- -

The functions above allow to get and set the size of vector into element.

- -

Note: the soci_into_resize_v always sets all into vectors in the given statement -to the same size, which guarantees that all vector into elements have equal size.

- -
-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);
-
-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_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);
-
- -

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);
-
- -

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_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".

- -
-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);
-
- -

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);
-
- -

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/doc/statements.html b/doc/statements.html deleted file mode 100644 index 81a3f3a8b9..0000000000 --- a/doc/statements.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - SOCI - statements, procedures and transactions - - - - - -

Statements, procedures and transactions

- - - -

Statement preparation and repeated execution

- -

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);
-}
-
- -

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 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);
-}
-
- -

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".

- -
-

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.

-
- -

Rowset and iterator-based access

- -

The rowset class provides an alternative means of executing queries and accessing results using STL-like iterator interface.

- -

The rowset_iterator type is compatible with requirements defined for input iterator category and is available via iterator and const_iterator definitions in the rowset class.

-

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:

- -
-rowset<int> rs = (sql.prepare << "select values from numbers");
-
-for (rowset<int>::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:

- -
-// person table has 4 columns
-
-rowset<row> rs = (sql.prepare << "select id, firstname, lastname, gender from person");
-
-// iteration through the resultset:
-for (rowset<row>::const_iterator it = rs.begin(); it != rs.end(); ++it)
-{
-    row const& row = *it;
-
-    // dynamic data extraction from each row:
-    cout << "Id: " << row.get<int>(0) << '\n'
-         << "Name: " << row.get<string>(1) << " " << row.get<string>(2) << '\n'
-         << "Gender: " << row.get<string>(3) << endl;
-}
-
- -

rowset_iterator can be used with standard algorithms as well:

- -
-rowset<string> rs = (sql.prepare << "select firstname from person");
-
-std::copy(rs.begin(), rs.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
-
- -

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.

- -

Bulk operations

- -

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:

- -
-// Example 3.
-const int BATCH_SIZE = 25;
-std::vector<int> ids;
-for (int i = 0; i != BATCH_SIZE; ++i)
-{
-    ids.push_back(i);
-}
-
-statement st = (sql.prepare <<
-                "insert into numbers(value) values(:val)",
-                use(ids));
-for (int i = 0; i != 4; ++i)
-{
-    st.execute(true);
-}
-
- -

(Of course, the size of the vector that will achieve optimum -performance will vary, depending on many environmental factors, such as -network speed.)

- -

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';
-}
-
- -

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.

- -

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<int> 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.

- -

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<int> valsOut(BATCH_SIZE);
-statement st = (sql.prepare <<
-                "select value from numbers",
-                into(valsOut));
-st.execute();
-while (st.fetch())
-{
-    std::vector<int>::iterator pos;
-    for(pos = valsOut.begin(); pos != valsOut.end(); ++pos)
-    {
-        cout << *pos << '\n';
-    }
-
-    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:

-
    -
  • After performing fetch(), the vector's size might -be less than requested, but fetch() -returning true means that there was at least one row retrieved.
  • -
  • It is forbidden to manually resize the vector to the size higher than it was initially (this -can cause the vector to reallocate its internal buffer and the library -can lose track of it).
  • -
- -

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.

- -
-

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.

- -
-

Portability note:

-

Different database servers have different policies with regard to the -implicit transaction management. Some of them start the implicit -transaction with the first DML statement and keep it open until -explicitly commited or rolled back (or closing the whole session). -Others will treat each statement as if it was a separate, auto-commited -transaction. For better compatibility, it is recommended to use the -above functions for explicit transaction management.

-
- -

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.

- - - - - - - - - - - - diff --git a/doc/structure.html b/doc/structure.html deleted file mode 100644 index 533ce2f2ed..0000000000 --- a/doc/structure.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - SOCI - structure - - - - - -

Structure

- -
-Library structure diagram -
- -

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 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.
  • -
- -

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:

-
-#include "soci.h"
-// other includes if necessary
-
-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 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/doc/structure.odg b/doc/structure.odg deleted file mode 100644 index 64aa0761c5..0000000000 Binary files a/doc/structure.odg and /dev/null differ diff --git a/doc/structure.png b/doc/structure.png deleted file mode 100644 index 1694cb6871..0000000000 Binary files a/doc/structure.png and /dev/null differ diff --git a/doc/style.css b/doc/style.css deleted file mode 100644 index 74cc190236..0000000000 --- a/doc/style.css +++ /dev/null @@ -1,118 +0,0 @@ -body -{ - background-color: white; - color: black; - margin-left: 100px; - margin-right: 100px; - font-family: arial, sans-serif; - font-size: small; -} - -p.banner -{ - font-size: x-large; - font-weight: bold; - font-stretch: expanded; - border-bottom-color: black; - border-bottom-style: solid; - border-bottom-width: 1px; - padding-bottom: 5px; -} - -div.navigation { } - -div.navigation-indented -{ - margin-left: 40px; -} - -div.diagram -{ - text-align: center; -} - -div.note -{ - border-color: black; - border-style: dotted; - border-width: 1px; - margin-top: 20px; - padding-left: 20px; - padding-right: 20px; -} - -span.note -{ - font-size: large; - font-weight: bold; -} - -code -{ -/* font-weight: bold; */ - background-color: white; - color: #8B0000; -/* font-size: large; */ -} - -pre.example -{ - background-color: #F0F0F0; - color: black; - border-width: 1px; - border-style: dashed; - border-color: blue; - padding: 10px 10px 10px 10px; -} - -table.foot-links -{ - width: 100%; - padding-top: 20px; -} - -td.foot-link-left -{ - text-align: left; -} - -td.foot-link-right -{ - text-align: right; -} - -table.cmake-variables -{ - width: 100%; - /*border: solid;*/ -} - -caption.cmake-variables -{ - background-color: #E0E0E0; - font-style: italic; - font-weight: bold; -} - -td.variable-name -{ - width: 25%; -} - -td.variable-type -{ - width: 7%; -} - -tr.header -{ - /* background-color: #F0F0F0; */ -} - -p.copyright -{ - border-top-color: black; - border-top-style: solid; - border-top-width: 1px; - padding-top: 5px; -} diff --git a/docs/backends.md b/docs/backends.md new file mode 100644 index 0000000000..84eb7b4f98 --- /dev/null +++ b/docs/backends.md @@ -0,0 +1,273 @@ +## 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. + +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 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. + + + // 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 }; + + // 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) {} + + char * str_; + std::size_t bufSize_; + }; + + // 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 `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 `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. + + 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 pre_fetch() = 0; + virtual void post_fetch(bool gotData, bool calledFromFetch, indicator* ind) = 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. + +* `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. + + 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 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 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. + + +* `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. +* `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. + + 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. + + 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 + { + 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. + +* `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. +* `prepare` - Called once with the text of the SQL query. For servers that support explicit query preparation, this is the place to do it. +* `execute` - Called to execute the query; if number is zero, the intent is not to exchange data with the user-provided objects (`into` and `use` elements); positive values mean the number of rows to exchange (more than 1 is used only for bulk operations). +* `fetch` - Called to fetch next bunch of rows; number is positive and determines the requested number of rows (more than 1 is used only for bulk operations). +* `get_affected_rows` - Called to determine the actual number of rows affected by data modifying statement. +* `get_number_of_rows` - Called to determine the actual number of rows retrieved by the previous call to `execute` or `fetch`. +* `rewrite_for_procedure_call` - Used when the `procedure` is used instead of `statement`, to call the stored procedure. This function should rewrite the SQL query (if necessary) to the form that will allow to execute the given procedure. +* `prepare_for_describe` - Called once when the `into` element is used with the `row` type, which means that dynamic rowset description should be performed. It is supposed to do whatever is needed to later describe the column properties and should return the number of columns. +* `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 + +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() {} + }; + +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() {} + + 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() {} + + 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 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; + }; + +The object of the class derived from `session_backend` implements the internals of the `session` object. + +* `begin`, `commit`, `rollback` - Forward-called when the same functions of `session` are called by user. +* `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() {} + + 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). + +The actual backend factory object is supposed to be provided by the backend implementation and declared in its header file. In addition to this, the `factory_ABC` function with the "C" calling convention and returning the pointer to concrete factory object should be provided, where `ABC` is the backend name. + +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; + + extern "C" + { + + // for dynamic backend loading + backend_factory const * factory_postgresql(); + + } // 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 diff --git a/docs/backends/db2.md b/docs/backends/db2.md new file mode 100644 index 0000000000..e24fabb8ba --- /dev/null +++ b/docs/backends/db2.md @@ -0,0 +1,90 @@ +## 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) + +### Prerequisites +#### Supported Versions + +The SOCI DB2 backend. + +#### Tested Platforms + + + + + + + + + + + +
DB2 versionOperating SystemCompiler
-Linux PPC64GCC
9.1LinuxGCC
9.5LinuxGCC
9.7LinuxGCC
10.1LinuxGCC
10.1Windows 8Visual Studio 2012
+ +#### Required Client Libraries + +The SOCI DB2 backend requires IBM DB2 Call Level Interface (CLI) library. + +#### 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 + +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"); + +### SOCI Feature Support + +#### Dynamic Binding + +TODO + +#### Bulk Operations + +Supported, but with caution as it hasn't been extensively tested. + +#### Transactions + +Currently, not supported. + +#### BLOB Data Type + +Currently, not supported. + +#### Nested Statements + +Nesting statements are not processed by SOCI in any special way and they work as implemented by the DB2 database. + +#### Stored Procedures + +Stored procedures are supported, with CALL statement. + +### Acessing the native database API + +TODO + +### Backend-specific extensions + +None. + +### Configuration options + +None. \ No newline at end of file diff --git a/docs/backends/firebird.md b/docs/backends/firebird.md new file mode 100644 index 0000000000..f1a92bd15a --- /dev/null +++ b/docs/backends/firebird.md @@ -0,0 +1,200 @@ +## 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) + +### Prerequisites + +#### 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. + +#### 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
+ +#### Required Client Libraries + +The Firebird backend requires Firebird's `libfbclient` client library. + +### 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: + + 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"); + +The set of parameters used in the connection string for Firebird is: + +* service +* user +* password +* role +* charset + +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: + + 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 + +#### 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. + +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 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 +currently not supported. + +(See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `Row` class.) + + +#### 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: + + 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. + +#### Bulk Operations + +The Firebird backend has full support for SOCI's [bulk operations](../statements.html#bulk) interface. This feature is also supported by emulation. + +#### 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. + +#### BLOB Data Type + +The Firebird backend supports working with data stored in columns of type Blob, via SOCI's `[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. + +#### RowID Data Type + +This feature is not supported by Firebird backend. + +#### Nested Statements + +This feature is not supported by Firebird backend. + +#### Stored Procedures + +Firebird stored procedures can be executed by using SOCI's [Procedure](../statements.html#procedures) class. + + +### 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. + +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()
+ +### Backend-specific extensions + +#### 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 diff --git a/doc/backends/index.html b/docs/backends/index.md similarity index 69% rename from doc/backends/index.html rename to docs/backends/index.md index fc1735cc4e..cbb8cec5ee 100644 --- a/doc/backends/index.html +++ b/docs/backends/index.md @@ -1,21 +1,10 @@ - - - - - - SOCI - Existing Backends - +## Existing backends and supported platforms - - +### Supported Features -

Existing backends and supported platforms

+(Follow the links to learn more about each backend.) -

Supported Features

- -

(Follow the links to learn more about each backend.)

- - +
@@ -62,7 +51,7 @@ + (with servers that support them, usually >= 4.0) @@ -110,17 +99,3 @@
YES YES YES - (with servers that support them, usually >= 4.0) YES YES YES
- - - - - - - - - - - - diff --git a/docs/backends/mysql.md b/docs/backends/mysql.md new file mode 100644 index 0000000000..0b289de17a --- /dev/null +++ b/docs/backends/mysql.md @@ -0,0 +1,204 @@ +## 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) + + + +### Prerequisites + +#### Supported Versions + +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. + +#### Tested Platforms + + + + + + + + + +
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. + +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 + +To establish a connection to a MySQL server, create a `session` object using the `mysql` backend factory together with a connection string: + + session sql(mysql, "db=test user=root password='Ala ma kota'"); + + // or: + session sql("mysql", "db=test user=root password='Ala ma kota'"); + + // or: + session sql("mysql://db=test user=root password='Ala ma kota'"); + +The set of parameters used in the connection string for MySQL is: + +* `dbname` or `db` or `service` (required) +* `user` +* `password` or `pass` +* `host` +* `port` +* `unix_socket` +* `sslca` +* `sslcert` +* `local_infile` - should be `0` or `1`, `1` means `MYSQL_OPT_LOCAL_INFILE` will be set. +* `charset` + +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); + + +(See the [SOCI basics]("../basics.html) and [exchanging data](../exchange.html) documentation for general information on using the `session` class.) + + +### SOCI Feature Support + +#### 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
+ +(See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `Row` class.) + +#### 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: + + int id = 7; + sql << "select name from person where id = :id", use(id, "id") + +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 + +[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 + +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 + +The `rowid` functionality is not supported by the MySQL backend. + +#### Nested Statements + +Nested statements are not supported by the MySQL backend. + +#### 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 + +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
+ +### Backend-specific extensions + +None. + +### Configuration options + +None. \ No newline at end of file diff --git a/docs/backends/odbc.md b/docs/backends/odbc.md new file mode 100644 index 0000000000..4d56979ac9 --- /dev/null +++ b/docs/backends/odbc.md @@ -0,0 +1,252 @@ +## 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) + + +### Prerequisites + +#### Supported Versions + +The SOCI ODBC backend is supported for use with ODBC 3. + +#### 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)
+ +#### Required Client Libraries + +The SOCI ODBC backend requires the ODBC client library. + +### 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"); + +or simply: + + 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. + +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); + +(See the [SOCI basics](../basics.html) and [exchanging data](../exchange.html) documentation for general information on using the `session` class.) + +### SOCI Feature Support + +#### 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
+ +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 + +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") + +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); + +#### 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
+ +#### 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 + +Not currently supported. + +#### RowID Data Type + +Not currently supported. + +#### Nested Statements + +Not currently supported. + +#### Stored Procedures + +Not currently supported. + +### 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. + +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
+ +### Backend-specific extensions + +#### 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() + { + 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; + } + } + +#### 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 + +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 diff --git a/docs/backends/oracle.md b/docs/backends/oracle.md new file mode 100644 index 0000000000..dbe33ce169 --- /dev/null +++ b/docs/backends/oracle.md @@ -0,0 +1,219 @@ +## 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) + + +### Prerequisites + +#### 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 + + + + + + +
Oracle versionOperating SystemCompiler
10.2.0 (XE)RedHat 5g++ 4.3
+ + +#### 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 + +#### 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"); + + // or: + session sql("oracle", "service=orcl user=scott password=tiger"); + + // or: + session sql("oracle://service=orcl user=scott password=tiger"); + +The set of parameters used in the connection string for Oracle is: + +* `service` +* `user` +* `password` +* `mode` (optional) + +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); + +(See the [SOCI basics](../basics.html) and [exchanging data](../exchange.html) documentation for general information on using the `session` class.)# + +### SOCI Feature Support + +#### 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
+ + +(See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `row` class.) + +#### 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") + +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 + +The Oracle backend has full support for SOCI's [bulk operations](../statements.html#bulk) interface. + +#### 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 + +The Oracle backend supports working with data stored in columns of type Blob, via SOCI's [blob](../exchange.html#blob) class. + +#### rowid Data Type + +Oracle rowid's are accessible via SOCI's [rowid](../reference.html#rowid) class. + +#### 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(); + + while (stInner.fetch()) + { + std::cout << name << '\n'; + } + +#### Stored Procedures + +Oracle stored procedures can be executed by using SOCI's [procedure](../statements.html#procedures) class. + +### Acessing the native database API + +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
+ +### Backend-specific extensions + +#### 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() + { + 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 diff --git a/docs/backends/postgresql.md b/docs/backends/postgresql.md new file mode 100644 index 0000000000..e6f9985b2d --- /dev/null +++ b/docs/backends/postgresql.md @@ -0,0 +1,204 @@ +## 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) + +### Prerequisites + +#### 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 + + + + + + + + + +
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
+ +#### 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 + +#### 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: + + + 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. + +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); + +(See the [exchanging data](../basics.html">SOCI basics and SOCI Feature Support + +#### 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: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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
+ +(See the [dynamic resultset binding](../exchange.html#dynamic) documentation for general information on using the `row` class.) + +#### 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") + +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); + +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 + +The PostgreSQL backend has full support for SOCI's [bulk operations](../statements.html#bulk) interface. + +#### Transactions + +[Transactions](../statements.html#transactions) are also fully supported by the PostgreSQL backend. + +#### 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 + +The concept of row identifier (OID in PostgreSQL) is supported via SOCI's [rowid](../reference.html#rowid) class. + +#### Nested Statements + +Nested statements are not supported by PostgreSQL backend. + +#### Stored Procedures + +PostgreSQL stored procedures can be executed by using SOCI's [procedure](../statements.html#procedures) class. + +### Acessing the native database API + +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
+ + +### Backend-specific extensions + +#### 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 + +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 diff --git a/docs/backends/sqlite3.md b/docs/backends/sqlite3.md new file mode 100644 index 0000000000..409905fc74 --- /dev/null +++ b/docs/backends/sqlite3.md @@ -0,0 +1,200 @@ +## 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) + + +### Prerequisites + +#### Supported Versions + +The SOCI SQLite3 backend is supported for use with SQLite3 >= 3.1 + +#### 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
+ +#### Required Client Libraries + +The SOCI SQLite3 backend requires SQLite3's `libsqlite3` client library. + +#### 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"); + + // or: + + session sql("sqlite3", "db=db.sqlite timeout=2 shared_cache=true"); + +The set of parameters used in the connection string for SQLite is: + +* `dbname` or `db` +* `timeout` - set sqlite busy timeout (in seconds) ([link](http://www.sqlite.org/c3ref/busy_timeout.html) +* `synchronous` - set the pragma synchronous flag ([link](http://www.sqlite.org/pragma.html#pragma_synchronous)) +* `shared_cache` - should be `true` ([link](http://www.sqlite.org/c3ref/enable_shared_cache.html)) + +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); + +(See the [SOCI basics](../basics.html) and [exchanging data](../exchange.html) documentation for general information on using the `session` class.) + +### SOCI Feature Support + +#### 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. + +When calling `row::get()`, the type you should pass as T depends upon the underlying database type. + +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
+ +[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 + +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") + +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); + +#### 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](../statements.html#transactions) are also fully supported by the SQLite3 backend. + +#### 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 + +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 are not supported by SQLite3 backend. + +#### Stored Procedures + +Stored procedures are not supported by SQLite3 backend + +### Acessing the native database API + +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
+ +### Backend-specific extensions + +#### 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 + +None \ No newline at end of file diff --git a/docs/beyond.md b/docs/beyond.md new file mode 100644 index 0000000000..55767b60d1 --- /dev/null +++ b/docs/beyond.md @@ -0,0 +1,75 @@ +## 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. + +### Getting the number of rows affected by an operation + +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); + + if ( !st.get_affected_rows() ) + { + ... investigate why no rows were modified ... + } + +--- +#####Portability note: + +This method is currently not supported by the Oracle backend. It is however +supported when using Oracle database via ODBC backend. +--- + +### Working with 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`. + +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); + + // 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: +These methods are currently only implemented in Firebird, MySQL, ODBC, PostgreSQL and SQLite3 backends. +--- + + +### 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 +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. + + 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()); + + 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. + +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 diff --git a/docs/boost.md b/docs/boost.md new file mode 100644 index 0000000000..d5f4acf478 --- /dev/null +++ b/docs/boost.md @@ -0,0 +1,78 @@ +## Integration with Boost + +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. + +####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. + +The `boost::optional` objects can be used everywhere where the regular user provided values are expected. + +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 + } + + +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` 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; + + 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 can be also composed with `boost::optional` + + boost::tuple, int> person; + + 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 + } + +####boost::fusion::vector + +The `boost::fusion::vector` types are supported in the same way as tuples. + + +####boost::gregorian::date + +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 new file mode 100644 index 0000000000..2f61585d79 --- /dev/null +++ b/docs/connections.md @@ -0,0 +1,90 @@ +## Connections and simple queries + +### Connecting to the database + +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. + +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"); + +Another example might be: + + 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: + + 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"); + +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: + + 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 <soci-odbc.h>` to obtain the option name declaration. The existing options are described in the backend-specific part of the documentation. + + +### 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 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: + + 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. + +An alternative way to set up the session is to create it in the disconnected state and connect later: + + session sql; + + // some time later: + sql.open(postgresql, "dbname=mydb"); + + // or: + sql.open("postgresql://dbname=mydb"); + + // 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. + +See also the page devoted to [multithreading](multithreading.html) for a detailed description of connection pools. + +It is possible to have many active `session`s at the same time, even using different backends. + +### Portability note + +The following backend factories are currently (as of 3.1.0 release) available: + +* [mysql](backends/mysql.html) (requires `#include "soci-mysql.h"`) +* [oracle](backends/oracle.html) (requires `#include "soci-oracle.h"`) +* [postgresql](backends/postgresql.html) (requires `#include "soci-postgresql.h"`) + +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 diff --git a/docs/errors.md b/docs/errors.md new file mode 100644 index 0000000000..4e3ce67e83 --- /dev/null +++ b/docs/errors.md @@ -0,0 +1,82 @@ +## 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() + { + try + { + // regular code + } + catch (std::exception const & e) + { + cerr << "Bang! " << e.what() << endl; + } + } + +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() + { + 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; + } + } + +#### Portability note: + +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() + { + 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; + } + } + +#### Portability note: + +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() + { + 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 diff --git a/docs/exchange.md b/docs/exchange.md new file mode 100644 index 0000000000..0e90aa6e4c --- /dev/null +++ b/docs/exchange.md @@ -0,0 +1,536 @@ +## 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/index.md b/docs/index.md new file mode 100644 index 0000000000..405da98449 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,71 @@ +# Documentation and tutorial + +* [Structure](structure.html) +* [Installation](installation.html) +* [Errors](errors.html) +* [Connections](connections.html) +* [Queries](queries.html) +* [Exchanging data](exchange.html) +* [Statements, procedures and transactions](statements.html) +* [Multithreading and SOCI](multithreading.html) +* [Integration with Boost](boost.html) +* [Interfaces](interfaces.html) +* [Beyond standard SQL](beyond.html) +* [Client interface reference](interfaces.html) +* [Backends reference](backends.html) +* [Rationale FAQ](rationale.html) +* [Ada language binding](languages/ada.html) +* [Existing backends and supported platforms](backends/index.html) + +The following (complete!) example is purposedly provided without any explanation. + + #include "soci.h" + #include "soci-oracle.h" + #include + #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'; + } + } \ No newline at end of file diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000000..6721625148 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,213 @@ +## 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 + +Below is an overall list of SOCI core: + +* C++ compiler: [GCC](http://gcc.gnu.org/), [Microsoft Visual C++](http://msdn.microsoft.com/en-us/visualc), [LLVM/clang](http://clang.llvm.org/) +* [CMake](http://www.cmake.org) 2.8+ - in order to use build configuration for CMake +* [Boost C++ Libraries](http://www.boost.org): DateTime, Fusion, Optional, Preprocessor, Tuple + +and backend-specific dependencies: + +* [DB2 Call Level Interface (CLI)](http://pic.dhe.ibm.com/infocenter/db2luw/v10r1/topic/com.ibm.swg.im.dbclient.install.doc/doc/c0023452.html) +* [Firebird client library](http://www.firebirdsql.org/manual/ufb-cs-clientlib.html) +* [mysqlclient](http://dev.mysql.com/doc/refman/5.6/en/c.html) - C API to MySQL +* ODBC (Open Database Connectivity) implementation: [Microsoft ODBC](http://msdn.microsoft.com/en-us/library/windows/desktop/ms710252.aspx) [iODBC](http://www.iodbc.org/), [unixODBC](http://www.unixodbc.org/) +* [Oracle Call Interface (OCI)](http://www.oracle.com/technetwork/database/features/oci/index.html) +* [libpq](http://www.postgresql.org/docs/8.4/static/libpq.html) - C API to PostgreSQL +* [SQLite 3](http://www.sqlite.org/) library + +### Downloading + +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 + +### Building using 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. + +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. + +#### List of a few essential CMake variables + +* 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)). + +#### List of variables to control common SOCI features and dependencies + +* 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. + +#### IBM DB2 + +#### SOCI DB2 backend 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"` + + + +#### Firebird + +##### SOCI Firebird backend configuration + +* 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"` + +#### 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"` + +#### 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"` + +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. + +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. + +#### Building using CMake on Unix + +Short version using GNU Make makefiles: + + $ mkdir build + $ cd build + $ cmake -G "Unix Makefiles" -DWITH_BOOST=OFF -DWITH_ORACLE=OFF (...) ../soci-X.Y.Z + $ make + $ make install + + +#### Building using CMake on Windows + + +Short version using Visual Studio 2010 and MSBuild: + + 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 + + +### Building using classic Makefiles on Unix (deprecated) + +*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.* + +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. + +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 + + +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 + +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). + +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. + +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 + +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 + +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 diff --git a/docs/interfaces.md b/docs/interfaces.md new file mode 100644 index 0000000000..ebc56a9f4e --- /dev/null +++ b/docs/interfaces.md @@ -0,0 +1,74 @@ +## 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 + +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"); + + int id = 123; + string name; + + sql << "select name from persons where id = :id", into(name), use(id); + +#### Core + +The above example is equivalent to the following, more explicit sequence of calls: + + session sql("postgresql://dbname=mydb"); + + 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); + + +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 + +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> + + // ... + session_handle sql = soci_create_session("postgresql://dbname=mydb"); + + statement_handle st = soci_create_statement(sql); + + soci_use_int(st, "id"); + soci_set_use_int(st, "id", 123); + + int namePosition = soci_into_string(st); + + soci_prepare(st, "select name from persons where id = :id"); + + soci_execute(st, true); + + char const * name = soci_get_into_string(st, namePosition); + + printf("name is %s\n", name); + + 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. + +See [Simple client interface](/reference.html#simpleclient) reference documentation for more details. + +#### 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 diff --git a/docs/languages/ada/concepts.md b/docs/languages/ada/concepts.md new file mode 100644 index 0000000000..7a7c66abbb --- /dev/null +++ b/docs/languages/ada/concepts.md @@ -0,0 +1,32 @@ +# SOCI-Ada - manual + +## 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. + +One of the main properties of the library is that the data objects which are bound for transfer to and from the database server are managed by the library itself and are not directly visible from the user code. This ensures that no aliasing of objects occurs between Ada and underlying C++ code, which makes the inter-language interface easier and more resilient to the differences in how compilers handle the linkage. As a direct result of this design choice, users of SOCI-Ada need to instruct the library to internally create all objects that will be subject to data transfer. + +There are two kinds of objects that can be managed by the SOCI-Ada library: + +* *Into elements*, which are data objects that are transferred from the database to the user program as a result of executing a query. There are single into elements for binding single rows of results and vector into elements for binding whole bunches of data corresponding to whole result sets or their subranges. The into elements are identified by their *position*. + +* *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` +* `SOCI.DB_Integer`, which is defined by the library in terms of `Interfaces.C.int` +* `SOCI.DB_Long_Long_Integer`, which is defined in terms of `Interfaces.Integer_64` +* `SOCI.DB_Long_Float`, which is defined in terms of `Interfaces.C.double` +* `Ada.Calendar.Time` + +Both into and use elements are managed for a single *statement*, which can be prepared once and executed once or many times, with data transfer handled during execution or fetch phase. + +Statements can be managed explicitly, which is required if they are to be used repeteadly or when data transfer is needed or implicitly, which is a shorthand notation that is particularly useful with simple queries or DDL commands. + +All statements are handled within the context of some *session*, which also supports *transactions*. + +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 diff --git a/docs/languages/ada/idioms.md b/docs/languages/ada/idioms.md new file mode 100644 index 0000000000..034e6ab2eb --- /dev/null +++ b/docs/languages/ada/idioms.md @@ -0,0 +1,268 @@ +# SOCI-Ada - manual + +## Common 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. + +The idioms below are provided as *complete programs* with the intent to make them more understandable and to give complete context of use for each idiom. The programs assume that the target database is PostgreSQL, but this can be changed by a different connection string in each place where the sessions are established. The programs use the Ada 2005 interface and some minor changes will be required to adapt them for Ada 95 compilers. + +## Single query without data transfer + +This type of query is useful for DDL commands and can be executed directly on the given session, without explicit statement management. + + + with SOCI; + + procedure My_Program is + + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); + + begin + + SQL.Execute ("drop table some_table"); + + 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. + +
+ + +## 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. + + + with SOCI; + with Ada.Text_IO; + + 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; + + Num_Of_Persons : SOCI.DB_Integer; + + begin + + Pos := St.Into_Integer; + St.Prepare ("select count(*) from persons"); + St.Execute (True); + + Num_Of_Persons := St.Get_Into_Integer (Pos); + + 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. + +
+ + +## Simple query with parameters and without results + +This type of query requires only use elements. + + with SOCI; + + procedure My_Program is + + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); + St : SOCI.Statement := SOCI.Make_Statement (SQL); + + begin + + St.Use_Integer ("increase"); + St.Set_Use_Integer ("increase", 1000); + + 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. + +
+ + +## 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. + + + with SOCI; + + procedure My_Program is + + SQL : SOCI.Session := SOCI.Make_Session ("postgresql://dbname=my_database"); + St : SOCI.Statement := SOCI.Make_Statement (SQL); + + begin + + St.Use_String ("name"); + + St.Prepare ("insert into countries(country_name) values(:name)"); + + St.Set_Use_String ("name", "Poland"); + St.Execute (True); + + St.Set_Use_String ("name", "Switzerland"); + St.Execute (True); + + St.Set_Use_String ("name", "France"); + St.Execute (True); + + end My_Program; + +
+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; + + 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; + + use type SOCI.Vector_Index; + + begin + + St.Use_Vector_String ("name"); + + St.Use_Vectors_Resize (3); + + 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.Prepare ("insert into countries(country_name) values(:name)"); + St.Execute (True); + + end My_Program; + +
+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: + +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; + + 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; + + begin + + Pos := St.Into_String; + + St.Prepare ("select country_name from countries"); + St.Execute; + + while St.Fetch loop + + Ada.Text_IO.Put_Line (St.Get_Into_String (Pos)); + + end loop; + + end My_Program; + +
+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; + + 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; + + Batch_Size : constant := 10; + + begin + + Pos := St.Into_Vector_String; + St.Into_Vectors_Resize (Batch_Size); + + St.Prepare ("select country_name from countries"); + St.Execute; + + while St.Fetch 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)); + + end loop; + + St.Into_Vectors_Resize (Batch_Size); + + end loop; + + end My_Program; + + +##### 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. + +There is a tradeoff between efficiency and memory usage and this tradeoff is controlled by the requested batch size. Similarly to one of the examples above, there is no benefit from using batches bigger than tens of thousands of rows. + +This type of query can have simple (not vectors) parameters that are fixed at execution time. + + +##### 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 new file mode 100644 index 0000000000..4fd7921b5b --- /dev/null +++ b/docs/languages/ada/index.md @@ -0,0 +1,39 @@ +# SOCI-Ada Language Binding - documentation + +* [Introduction](#introduction) +* [Compilation](#compilation) +* [Concepts](concepts.html) +* [Common Idioms](idioms.html) +* [API Reference](reference.html) + +## Introduction + +SOCI-Ada is a database access library for Ada. + +The library itself is a wrapper for the selected functionality of the SOCI library, which is a C++ database access library recognized for its high quality and innovative interface. + +The SOCI-Ada library offers the following features to the Ada community: + +* Modular design based on dynamic backend loading. Thanks to this feature, new backends implemented within the context of the main SOCI project are immediately available for Ada programmers without any additional work. A large community of C++ users can help ensure that the new backends are well tested in a variety of environments and usage scenarios. +* Native backends for major database servers ensure optimal performance and minimize configuration overhead and complexity that is usually associated with other database access methods. +* Direct support for bulk operations allow to achieve high performance with queries that operate on large data sets. +* Very liberal open-source license ([Boost, accepted by Open Source Initiative](http://www.opensource.org/licenses/bsl1.0.html)) that encourages both commercial and non-commercial use. +* Easy to use and compact interface. + +Currently the following database servers are directly supported via their native interfaces: + + * Oracle + * PostgreSQL + * MySQL + +Other backends exist in the SOCI Git repository and can be provided with future version of the library. + +## Compilation + +In order to use SOCI-Ada, compile the C++ parts first (core and required backends). + +*Note:* SOCI header files are not needed to use SOCI-Ada, only compiled SOCI libraries (core and relevant backend) need to exist to build and use SOCI-Ada programs. + +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 diff --git a/docs/languages/ada/reference.md b/docs/languages/ada/reference.md new file mode 100644 index 0000000000..9650eb9921 --- /dev/null +++ b/docs/languages/ada/reference.md @@ -0,0 +1,523 @@ +# SOCI-Ada - manual + +## 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: + + + -- + -- General exception related to database and library usage. + -- + + 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. + -- + + type Session is tagged limited private; + + 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 Close (This : in out Session); + + 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) + +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. + + + -- Transaction management. + + not overriding + procedure Start (This : in Session); + + not overriding + procedure Commit (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); + +This operation allows to create implicit statement, prepare it for the given `Query` and execute it. + + -- + -- Connection pool management. + -- + + 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 Close (This : in out Connection_Pool; Position : in Positive); + + 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: + +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. + -- + + type Statement (<>) is tagged limited private; + + type Data_State is (Data_Null, Data_Not_Null); + + type Into_Position is private; + + 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. + + + -- Statement preparation and execution. + + 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 + 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 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. + +The `Execute` operations cause the statement to execute, which might be combined with data exchange if requested. The function version of this operation returns `True` if some data has been returned back from the database server. + +The `Fetch` function is used to transfer next portion of data (a single row or a whole bunch) from the database server and returns `True` if some data has been fetched. If this function returns `False` it means that no new data will be ever fetched for this statement and indicates the end-of-row condition. + +The `Got_Data` function returns `True` if the last execution or fetch resulted in some data being transmitted from the database server. + + + -- + -- 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. + + 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: + +* `String` +* `DB_Integer`, defined above +* `DB_Long_Long_Integer`, defined above +* `DB_Long_Float`, defined above +* `Ada.Calendar.Time` + + -- Creation of single into elements. + + 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_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; + + +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: + +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. + +--- + + -- Inspection of single into elements. + + 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; + + 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; + +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. + + 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_Last_Index (This : in Statement) return Vector_Index; + + 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. + +The `Into_Vectors_First_Index` returns the lowest index value for vector into elements (which is always `0`, even if the vectors are empty). The `Into_Vectors_Last_Index` returns the last index of into vectors, and raises the `CONSTRAINT_ERROR` exception if the vectors are empty. + +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; + + 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_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_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. + + + -- Creation of single use elements. + + 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_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_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. + +Vector use elements cannot be created together with any into elements for the same statement. + +--- + + + -- Creation of vector use elements. + + not overriding + procedure Use_Vector_String (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); + + +These functions instruct the library to create internal vector 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. + +Vector use elements cannot be created together with any into elements for the same statement. + +--- + + -- Modifiers for single use elements. + + 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); + + 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); + +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`. + + + -- Modifiers for vector use elements. + + 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_Last_Index (This : in Statement) return Vector_Index; + + 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. + +The `Use_Vectors_First_Index` returns the lowest index value for vector use elements (which is always `0`, even if the vectors are empty). The `Use_Vectors_Last_Index` returns the last index of use vectors, and raises the `CONSTRAINT_ERROR` exception if the vectors are empty. + +The `Use_Vectors_Resize` procedure allows to change the size of all use vectors for the given statement. + + + 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_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_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`. + + + -- 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_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_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_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 diff --git a/docs/multithreading.md b/docs/multithreading.md new file mode 100644 index 0000000000..41e147b066 --- /dev/null +++ b/docs/multithreading.md @@ -0,0 +1,38 @@ +## 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. + +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: + + + // phase 1: preparation + + const size_t poolSize = 10; + connection_pool pool(poolSize); + + for (size_t i = 0; i != poolSize; ++i) + { + session & sql = pool.at(i); + + sql.open("postgresql://dbname=mydb"); + } + + // phase 2: usage from working threads + + { + session sql(pool); + + sql << "select something from somewhere..."; + + } // 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. + +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. + +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 diff --git a/docs/queries.md b/docs/queries.md new file mode 100644 index 0000000000..d8d3efe71e --- /dev/null +++ b/docs/queries.md @@ -0,0 +1,84 @@ +## Queries + +### 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"; + +For shorter syntax, the following form is also allowed: + + 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<<`): + + + string tableName = "persons"; + sql << "drop table " << tableName; + + int id = 123; + sql << "delete from companies where id = " << id; + +### Query transformation + +In SOCI 3.2.0, query transformation mechanism was introduced. + +Query transformation is specified as user-defined unary function or callable function object with input parameter of type `std::string` which returns object of type `std::string` as well. + +The query transformation function is registered for current database session using dedicated `session::set_query_transformation` method. Then, the transformation function is called with query string as argument just before the query is sent to database backend for execution or for preparation. + +For one-time statements, query transformation is performed before each execution of statement. For prepared statements, query is transformed only once, before preparation, regardless how many times it is executed. + +A few short examples how to use query transformation: + +*defined as free function:* + + 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:* + + struct order : std::unary_function> 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 + + 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. + +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? + +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. + +Well, consider the following: + +"Send the query X to the server Y *and* put result into variable Z." + +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. + +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? + +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 diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 0000000000..cc8d8e706e --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,704 @@ +## Client interface 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. + +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 + +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 }; + + // 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 `data_type` type defines the basic SOCI data types. User provided data types need to be associated with one of these basic types. + +The `indicator` type defines the possible states of data. + +The `soci_error` type is used for error reporting. + + +### 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); + + ~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 begin(); + void commit(); + void rollback(); + + *IT* once; + *IT* prepare; + + template *IT* operator<<(T const & t); + + 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); + + std::ostringstream & get_query_stream(); + + void set_log_stream(std::ostream * s); + std::ostream * get_log_stream() const; + + std::string get_last_query() const; + + void uppercase_column_names(bool forceToUpper); + + 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"); + +* 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"; + +* `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)); + +`operator<<` that is a shortcut forwarder to the equivalent operator of the `once` member. Example: + + 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. +* `get_last_insert_id` returns true if it could retrieve the last value automatically generated by the database for an auto-incremented field. Notice that although this method takes the table name, for some databases, such as Microsoft SQL Server and SQLite, this value is actually global, so you should attempt to retrieve it immediately after performing an insertion. +* `get_query_stream` provides direct access to the stream object that is used to accumulate the query text and exists in particular to allow the user to imbue specific locale to this stream. +* `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_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. + +See [Connections and simple queries](basics.html) for more examples. + +### 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. + + + 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 + }; + + +The methods of this class are: + +* Default constructor is rarely used as it creates an uninitialized object and the only way to initialize it later is to assign another, valid, connection_parameters object to this one. +* 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 + +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(); + + 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); + }; + +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. +* `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 + +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); + + ~transaction(); + + void commit(); + void rollback(); + + 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 + +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); + + template + IT into(T & t, T1 p1); + + template + IT into(T & t, indicator & ind); + + template + IT into(T & t, indicator & ind, T1 p1); + + template + IT into(T & t, std::vector & ind); + +Example: + + int count; + sql << "select count(*) from person", into(count); + +See [Binding local dat](exchange.html#bind_local) for more examples + +### 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); + + template + *IT* use(T & t, T1 p1); + + template + *IT* use(T & t, indicator & ind); + + 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); + + +Example: + + 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 + +The `statement` class encapsulates the prepared statement. + + class statement + { + public: + statement(session & s); + statement(*IT* const & prep); + ~statement(); + + 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 prepare(std::string const & query); + void define_and_bind(); + + bool execute(bool withDataExchange = false); + long long get_affected_rows(); + bool fetch(); + + bool got_data() const; + + void describe(); + void set_row(row * r); + void exchange_for_rowset(*IT* const & i); + + 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); + +* Constructor accepting the result of using `prepare` on the `session` object, see example provided above for the `session` class. +* Copy operations. +* `alloc` function, which allocates necessary internal resources. +* `bind` function, which is used to bind the `values` object - this is used in the object-relational mapping and normally called automatically. +* exchange functions for registering the binding of local data - they expect the result of calling the `into` or `use` functions and are normally invoked automatically. +* `clean_up` function for cleaning up resources, normally called automatically. +* `bind_clean_up` function for cleaning up any bound references. It allows to keep statement in cache and reuse it later with new references by calling `exchange` for each new bind variable. +* `prepare` function for preparing the statement for repeated execution. +* `define_and_bind` function for actually executing the registered bindings, normally called automatically. +* `execute` function for executing the statement. If its parameter is `false` then there is no data exchange with locally bound variables (this form should be used if later `fetch` of multiple rows is foreseen). Returns `true` if there was at least one row of data returned. +* `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. +* `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. + +See [Statement preparation and repeated execution](statements.html#preparation) for example uses. + +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 + +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); + + 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 + +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; + + static void from_base(base_type const & in, indicator ind, T & out); + + 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 + +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(); + + void uppercase_column_names(bool forceToUpper); + + std::size_t size() 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; + + template + T get(std::size_t pos) 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, T const & nullValue) const; + + template + row const & operator>>(T & value) const; + + void skip(std::size_t num = 1) const; + + void reset_get_counter() const + }; + +This class contains the following members: + +* Default constructor that allows to declare a `row` variable. +* `uppercase_column_names` - see the same function in the `session` class. +* `size` function that returns the number of columns in the row. +* `get_indicator` function that returns the indicator value for the given column (column is specified by position - starting from 0 - or by name). +* `get_properties` function that returns the properties of the column given by position (starting from 0) or by name. +* `get` functions that return the value of the column given by position or name. If the column contains null, then these functions either return the provided "default" `nullValue` or throw an exception. +* `operator>>` for convenience stream-like extraction interface. Subsequent calls to this function are equivalent to calling `get` with increasing position parameter, starting from the beginning. +* `skip` and `reset_get_counter` allow to change the order of data extraction for the above operator. + +See [Dynamic resultset binding](exchange.html#dynamic) for examples. + +### 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; + }; + +This class contains the following members: + +* `get_name` function that returns the name of the column. +* `get_data_type` that returns the type of the column. + +See [Dynamic resultset binding](exchange.html#dynamic) for examples. + +### class values + +The `values` class encapsulates the data and type information and is used for object-relational mapping. + + class values + { + public: + values(); + + void uppercase_column_names(bool forceToUpper); + + 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, T const & nullValue) const; + + template + T get(std::string const & name) const; + + template + T get(std::string const & name, T const & nullValue) const; + + template + values const & operator>>(T & value) 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(const T & value, indicator indic = i_ok); + + template + values & operator<<(T const & value); + }; + +This class contains the same members as the `row` class (with the same meaning) plus: + +* `set` function for storing values in named columns or in subsequent positions. +* `operator<<` for convenience. + +See [Object-relational mapping](exchange.html#object_relational) for examples. + +### class blob + +The `blob` class encapsulates the "large object" functionality. + + 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); + + details::blob_backend * get_backend(); + }; + +This class contains the following members: + +* Constructor associating the `blob` object with the `session` object. +* `get_len` function that returns the size of the BLOB object. +* `read` function that reads the BLOB data into provided buffer. +* `write` function that writes the BLOB data from provided buffer. +* `append` function that appends to the existing BLOB data. +* `trim` function that truncates the existing data to the new length. +* `get_backend` function that returns the internal pointer to the concrete backend implementation of the BLOB object. This is provided for advanced users that need access to the functionality that is not otherwise available. + +See [Large objects (BLOBs)](exchange.html#blob) for more discussion. + +### class rowid + +The `rowid` class encapsulates the "row identifier" object. + + class rowid + { + public: + explicit rowid(Session & s); + ~rowid(); + + 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 + +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; + }; + +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"; + + session sql(factory, parameters); + +### 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. + +The functionality of this interface is limited and in particular the dynamic rowset description and type conversions are not supported in this release. On the other hand, the important feature of this interface is that it does not require passing pointers to data managed by the user, because all data is handled at the SOCI side. This should make it easier to integrate SOCI with languages that have constrained ability to understand the C type system. + +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); + + 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); + +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. + + + 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_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. + +For easy error testing, functions `soci_blob_read`, `soci_blob_write`, `soci_blob_append`, and `soci_blob_trim` return `-1` in case of error and `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_blob`, which returns `NULL` if it was not possible to create an internal object representing the blob. + + + 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); + +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. + + + 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); + +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); + +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); + + 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); + + 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); + +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); + +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); + + 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`". + + 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); + +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/statements.md b/docs/statements.md new file mode 100644 index 0000000000..cb4a9fb1dd --- /dev/null +++ b/docs/statements.md @@ -0,0 +1,311 @@ +## Statements, procedures and transactions + +* [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 + +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); + } + + +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 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); + } + +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`". + +#####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. + + +### Rowset and iterator-based access + +The `rowset` class provides an alternative means of executing queries and accessing results using STL-like iterator interface. + +The `rowset_iterator` type is compatible with requirements defined for input iterator category and is available via `iterator` and `const_iterator` definitions in the `rowset` class. + +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`: + + + rowset rs = (sql.prepare << "select values from numbers"); + + 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): + + + // person table has 4 columns + + 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; + + // 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; + } + +`rowset_iterator` can be used with standard algorithms as well: + + + rowset rs = (sql.prepare << "select firstname from person"); + + std::copy(rs.begin(), rs.end(), std::ostream_iterator(std::cout, "\n")); + +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. + +### Bulk operations + +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. + +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`: + + + // 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); + } + +Given batch size is 25, this example should insert 4 x 25 = 100 records. + +(Of course, the size of the vector that will achieve optimum performance will vary, depending on many environmental factors, such as network speed.) + +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'; + } + +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`. + +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); + +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()) + { + std::vector::iterator pos; + for(pos = valsOut.begin(); pos != valsOut.end(); ++pos) + { + cout << *pos << '\n'; + } + + 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: + +* After performing `fetch()`, the vector's size might be *less* than requested, but `fetch()` returning true means that there was *at least one* row retrieved. +* It is forbidden to manually resize the vector to the size *higher* than it was initially (this can cause the vector to reallocate its internal buffer and the library can lose track of it). + +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 + +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`. + + sql << "CREATE TABLE test(a INTEGER)"; + + { + // prepare statement + soci::statement stmt = (db.prepare << "INSERT INTO numbers(value) VALUES(:val)"); + + { + // first insert + int a0 = 0; + + // 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(); + } + } + + { + 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; + } + + +#####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 diff --git a/docs/structure.md b/docs/structure.md new file mode 100644 index 0000000000..ee6e63f4ed --- /dev/null +++ b/docs/structure.md @@ -0,0 +1,27 @@ +## 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 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. + +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: + + + #include "soci.h" + // other includes if necessary + + 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 diff --git a/include/private/README.md b/include/private/README.md new file mode 100644 index 0000000000..7d46ec5872 --- /dev/null +++ b/include/private/README.md @@ -0,0 +1,5 @@ +# soci/include/private + +Private headers do not define any parts of public interface, +are not installed in user's filesystem. +Private headers only define common features used internally. diff --git a/src/backends/firebird/common.h b/include/private/firebird/common.h similarity index 72% rename from src/backends/firebird/common.h rename to include/private/firebird/common.h index a97c3eeb12..636090724d 100644 --- a/src/backends/firebird/common.h +++ b/include/private/firebird/common.h @@ -8,7 +8,7 @@ #ifndef SOCI_FIREBIRD_COMMON_H_INCLUDED #define SOCI_FIREBIRD_COMMON_H_INCLUDED -#include "soci-firebird.h" +#include "soci/firebird/soci-firebird.h" #include #include #include @@ -40,7 +40,7 @@ void setTextParam(char const * s, std::size_t size, char * buf_, std::string getTextParam(XSQLVAR const *var); template -const char *str2dec(const char * s, IntType &out, int &scale) +const char *str2dec(const char * s, IntType &out, short &scale) { int sign = 1; if ('+' == *s) @@ -65,7 +65,7 @@ const char *str2dec(const char * s, IntType &out, int &scale) int d = *s - '0'; if (d < 0 || d > 9) return s; - res = res * 10 + d * sign; + res = res * 10 + static_cast(d * sign); if (1 == sign) { if (res < out) @@ -82,19 +82,45 @@ const char *str2dec(const char * s, IntType &out, int &scale) return s; } +template +inline +T round_for_isc(T value) +{ + return value; +} + +inline +double round_for_isc(double value) +{ + // Unfortunately all the rounding functions are C99 and so are not supported + // by MSVC, so do it manually. + return value < 0 ? value - 0.5 : value + 0.5; +} + +//helper template to generate proper code based on compile time type check +template struct cond_to_isc {}; +template<> struct cond_to_isc +{ + static void checkInteger(short scale, short type) + { + if( scale >= 0 && (type == SQL_SHORT || type == SQL_LONG || type == SQL_INT64) ) + throw soci_error("Can't convert non-integral value to integral column type"); + } +}; +template<> struct cond_to_isc +{ + static void checkInteger(short scale,short type) { SOCI_UNUSED(scale) SOCI_UNUSED(type) } +}; + template -void to_isc(void * val, XSQLVAR * var, int x_scale = 0) +void to_isc(void * val, XSQLVAR * var, short x_scale = 0) { T1 value = *reinterpret_cast(val); short scale = var->sqlscale + x_scale; short type = var->sqltype & ~1; long long divisor = 1, multiplier = 1; - if ((std::numeric_limits::is_integer == false) && scale >= 0 && - (type == SQL_SHORT || type == SQL_LONG || type == SQL_INT64)) - { - throw soci_error("Can't convert non-integral value to integral column type"); - } + cond_to_isc::is_integer>::checkInteger(scale,type); for (int i = 0; i > scale; --i) multiplier *= 10; @@ -105,19 +131,19 @@ void to_isc(void * val, XSQLVAR * var, int x_scale = 0) { case SQL_SHORT: { - short tmp = static_cast(value*multiplier/divisor); + short tmp = static_cast(round_for_isc(value*multiplier)/divisor); std::memcpy(var->sqldata, &tmp, sizeof(short)); } break; case SQL_LONG: { - int tmp = static_cast(value*multiplier/divisor); + int tmp = static_cast(round_for_isc(value*multiplier)/divisor); std::memcpy(var->sqldata, &tmp, sizeof(int)); } break; case SQL_INT64: { - long long tmp = static_cast(value*multiplier/divisor); + long long tmp = static_cast(round_for_isc(value*multiplier)/divisor); std::memcpy(var->sqldata, &tmp, sizeof(long long)); } break; @@ -141,7 +167,7 @@ void to_isc(void * val, XSQLVAR * var, int x_scale = 0) template void parse_decimal(void * val, XSQLVAR * var, const char * s) { - int scale; + short scale; UIntType t1; IntType t2; if (!*str2dec(s, t1, scale)) @@ -174,6 +200,22 @@ std::string format_decimal(const void *sqldata, int sqlscale) return r + std::string(sqlscale, '0'); } + +template struct cond_from_isc {}; +template<> struct cond_from_isc { + static void checkInteger(short scale) + { + std::ostringstream msg; + msg << "Can't convert value with scale " << -scale + << " to integral type"; + throw soci_error(msg.str()); + } +}; +template<> struct cond_from_isc +{ + static void checkInteger(short scale) { SOCI_UNUSED(scale) } +}; + template T1 from_isc(XSQLVAR * var) { @@ -182,14 +224,7 @@ T1 from_isc(XSQLVAR * var) if (scale < 0) { - if (std::numeric_limits::is_integer) - { - std::ostringstream msg; - msg << "Can't convert value with scale " << -scale - << " to integral type"; - throw soci_error(msg.str()); - } - + cond_from_isc::is_integer>::checkInteger(scale); for (int i = 0; i > scale; --i) { tens *= 10; diff --git a/src/backends/firebird/error-firebird.h b/include/private/firebird/error-firebird.h similarity index 94% rename from src/backends/firebird/error-firebird.h rename to include/private/firebird/error-firebird.h index 37ec3f5c58..b793d83a2e 100644 --- a/src/backends/firebird/error-firebird.h +++ b/include/private/firebird/error-firebird.h @@ -8,7 +8,7 @@ #ifndef SOCI_FIREBIRD_ERROR_H_INCLUDED #define SOCI_FIREBIRD_ERROR_H_INCLUDED -#include "soci-firebird.h" +#include "soci/firebird/soci-firebird.h" #include namespace soci diff --git a/include/private/soci-compiler.h b/include/private/soci-compiler.h new file mode 100644 index 0000000000..42e52b5903 --- /dev/null +++ b/include/private/soci-compiler.h @@ -0,0 +1,39 @@ +// +// Copyright (C) 2015 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) +// + +#ifndef SOCI_PRIVATE_SOCI_COMPILER_H_INCLUDED +#define SOCI_PRIVATE_SOCI_COMPILER_H_INCLUDED + +#include "soci-cpp.h" + +// CHECK_GCC(major,minor) evaluates to 1 when using g++ of at least this +// version or 0 when using g++ of lesser version or not using g++ at all. +#if defined(__GNUC__) && defined(__GNUC_MINOR__) +# define CHECK_GCC(major, minor) \ + ((__GNUC__ > (major)) \ + || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))) +#else +# define CHECK_GCC(major, minor) 0 +#endif + +// GCC_WARNING_{SUPPRESS,RESTORE} macros can be used to bracket the code +// producing a specific warning to disable it. +// +// They only work with g++ 4.6+ or clang, warnings are not disabled for earlier +// g++ versions. +#if defined(__clang__) || CHECK_GCC(4, 6) +# define GCC_WARNING_SUPPRESS(x) \ + _Pragma (SOCI_STRINGIZE(GCC diagnostic push)) \ + _Pragma (SOCI_STRINGIZE(GCC diagnostic ignored SOCI_STRINGIZE(SOCI_CONCAT(-W,x)))) +# define GCC_WARNING_RESTORE(x) \ + _Pragma (SOCI_STRINGIZE(GCC diagnostic pop)) +#else /* gcc < 4.6 or not gcc and not clang at all */ +# define GCC_WARNING_SUPPRESS(x) +# define GCC_WARNING_RESTORE(x) +#endif + +#endif // SOCI_PRIVATE_SOCI_COMPILER_H_INCLUDED diff --git a/include/private/soci-cpp.h b/include/private/soci-cpp.h new file mode 100644 index 0000000000..71121247a7 --- /dev/null +++ b/include/private/soci-cpp.h @@ -0,0 +1,30 @@ +// +// Copyright (C) 2015 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) +// + +#ifndef SOCI_PRIVATE_SOCI_CPP_H_INCLUDED +#define SOCI_PRIVATE_SOCI_CPP_H_INCLUDED + +// Some very common preprocessor helpers. + +// SOCI_CONCAT() pastes together two tokens after expanding them. +#define SOCI_CONCAT_IMPL(x, y) x ## y +#define SOCI_CONCAT(x, y) SOCI_CONCAT_IMPL(x, y) + +// SOCI_STRINGIZE() makes a string of its argument after expanding it. +#define SOCI_STRINGIZE_IMPL(x) #x +#define SOCI_STRINGIZE(x) SOCI_STRINGIZE_IMPL(x) + +// SOCI_MAKE_UNIQUE_NAME() creates a uniquely named identifier with the given +// prefix. +// +// It uses __COUNTER__ macro to avoid problems with broken __LINE__ in MSVC +// when using "Edit and Continue" (/ZI) option as there are no compilers known +// to work with SOCI and not support it. If one such is ever discovered, we +// should use __LINE__ for it instead. +#define SOCI_MAKE_UNIQUE_NAME(name) SOCI_CONCAT(name, __COUNTER__) + +#endif // SOCI_PRIVATE_SOCI_CPP_H_INCLUDED diff --git a/include/private/soci-cstrtod.h b/include/private/soci-cstrtod.h new file mode 100644 index 0000000000..63b88f2baf --- /dev/null +++ b/include/private/soci-cstrtod.h @@ -0,0 +1,80 @@ +// +// Copyright (C) 2014 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) +// + +#ifndef SOCI_PRIVATE_SOCI_CSTRTOD_H_INCLUDED +#define SOCI_PRIVATE_SOCI_CSTRTOD_H_INCLUDED + +#include "soci/error.h" + +#include +#include + +namespace soci +{ + +namespace details +{ + +// Locale-independent, i.e. always using "C" locale, function for converting +// strings to numbers. +// +// The string must contain a floating point number in "C" locale, i.e. using +// point as decimal separator, and nothing but it. If it does, the converted +// number is returned, otherwise an exception is thrown. +inline +double cstring_to_double(char const* s) +{ + // Unfortunately there is no clean way to parse a number in C locale + // without this hack: normally, using std::istringstream with classic + // locale should work, but some standard library implementations are buggy + // and handle non-default locale in thread-unsafe way, by changing the + // global C locale which is unacceptable as it introduces subtle bugs in + // multi-thread programs. So we rely on just the standard C functions and + // try to make them work by tweaking the input into the form appropriate + // for the current locale. + + // First try with the original input. + char* end; + double d = strtod(s, &end); + + bool parsedOK; + if (*end == '.') + { + // Parsing may have stopped because the current locale uses something + // different from the point as decimal separator, retry with a comma. + // + // In principle, values other than point or comma are possible but they + // don't seem to be used in practice, so for now keep things simple. + size_t const bufSize = strlen(s) + 1; + char* const buf = new char[bufSize]; + strcpy(buf, s); + buf[end - s] = ','; + d = strtod(buf, &end); + parsedOK = end != buf && *end == '\0'; + delete [] buf; + } + else + { + // Notice that we must detect false positives as well: parsing a string + // using decimal comma should fail when using this function. + parsedOK = end != s && *end == '\0' && !strchr(s, ','); + } + + if (!parsedOK) + { + throw soci_error(std::string("Cannot convert data: string \"") + s + "\" " + "is not a number."); + } + + return d; +} + +} // namespace details + +} // namespace soci + +#endif // SOCI_PRIVATE_SOCI_CSTRTOD_H_INCLUDED diff --git a/include/private/soci-dtocstr.h b/include/private/soci-dtocstr.h new file mode 100644 index 0000000000..bcf9098f07 --- /dev/null +++ b/include/private/soci-dtocstr.h @@ -0,0 +1,58 @@ +// +// Copyright (C) 2014 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) +// + +#ifndef SOCI_PRIVATE_SOCI_DTOCSTR_H_INCLUDED +#define SOCI_PRIVATE_SOCI_DTOCSTR_H_INCLUDED + +#include "soci/soci-platform.h" +#include "soci/error.h" + +#include +#include + +namespace soci +{ + +namespace details +{ + +// Locale-independent, i.e. always using "C" locale, function for converting +// floating point number to string. +// +// The resulting string will contain the floating point number in "C" locale, +// i.e. will always use point as decimal separator independently of the current +// locale. +inline +std::string double_to_cstring(double d) +{ + // See comments in cstring_to_double() in soci-cstrtod.h, we're dealing + // with the same issues here. + + static size_t const bufSize = 32; + char buf[bufSize]; + snprintf(buf, bufSize, "%.20g", d); + + // Replace any commas which can be used as decimal separator with points. + for (char* p = buf; *p != '\0'; p++ ) + { + if (*p == ',') + { + *p = '.'; + + // There can be at most one comma in this string anyhow. + break; + } + } + + return buf; +} + +} // namespace details + +} // namespace soci + +#endif // SOCI_PRIVATE_SOCI_DTOCSTR_H_INCLUDED diff --git a/include/private/soci-exchange-cast.h b/include/private/soci-exchange-cast.h new file mode 100644 index 0000000000..6b28c5bf4e --- /dev/null +++ b/include/private/soci-exchange-cast.h @@ -0,0 +1,84 @@ +// +// Copyright (C) 2015 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) +// + +#ifndef SOCI_EXCHANGE_CAST_H_INCLUDED +#define SOCI_EXCHANGE_CAST_H_INCLUDED + +#include "soci/soci-backend.h" + +#include + +namespace soci +{ + +namespace details +{ + +// cast the given non-null untyped pointer to its corresponding type +template struct exchange_type_traits; + +template <> +struct exchange_type_traits +{ + typedef char value_type; +}; + +template <> +struct exchange_type_traits +{ + typedef std::string value_type; +}; + +template <> +struct exchange_type_traits +{ + typedef short value_type; +}; + +template <> +struct exchange_type_traits +{ + typedef int value_type; +}; + +template <> +struct exchange_type_traits +{ + typedef long long value_type; +}; + +template <> +struct exchange_type_traits +{ + typedef unsigned long long value_type; +}; + +template <> +struct exchange_type_traits +{ + typedef double value_type; +}; + +template <> +struct exchange_type_traits +{ + typedef std::tm value_type; +}; + +// exchange_type_traits not defined for x_statement, x_rowid and x_blob here. + +template +typename exchange_type_traits::value_type& exchange_type_cast(void *data) +{ + return *static_cast::value_type*>(data); +} + +} // namespace details + +} // namespace soci + +#endif // SOCI_EXCHANGE_CAST_H_INCLUDED diff --git a/include/private/soci-mktime.h b/include/private/soci-mktime.h new file mode 100644 index 0000000000..1b383d8389 --- /dev/null +++ b/include/private/soci-mktime.h @@ -0,0 +1,46 @@ +// +// Copyright (C) 2015 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) +// + +#ifndef SOCI_PRIVATE_SOCI_MKTIME_H_INCLUDED +#define SOCI_PRIVATE_SOCI_MKTIME_H_INCLUDED + +// Not because we also want to get timegm() if available. +#include + +namespace soci +{ + +namespace details +{ + +// Fill the provided struct tm with the values corresponding to the given date +// in UTC. +// +// Notice that both years and months are normal human 1-based values here and +// not 1900 or 0-based as in struct tm itself. +inline +void +mktime_from_ymdhms(tm& t, + int year, int month, int day, + int hour, int minute, int second) +{ + t.tm_isdst = -1; + t.tm_year = year - 1900; + t.tm_mon = month - 1; + t.tm_mday = day; + t.tm_hour = hour; + t.tm_min = minute; + t.tm_sec = second; + + mktime(&t); +} + +} // namespace details + +} // namespace soci + +#endif // SOCI_PRIVATE_SOCI_MKTIME_H_INCLUDED diff --git a/include/private/soci-static-assert.h b/include/private/soci-static-assert.h new file mode 100644 index 0000000000..ecb79b2acd --- /dev/null +++ b/include/private/soci-static-assert.h @@ -0,0 +1,19 @@ +// +// Copyright (C) 2015 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) +// + +#ifndef SOCI_PRIVATE_STATIC_ASSERT_H_INCLUDED +#define SOCI_PRIVATE_STATIC_ASSERT_H_INCLUDED + +#include "soci-cpp.h" + +// This is a simple approximation for C++11 static_assert: generate a +// compile-time error if the given expression evaluates to 0 and make the +// identifier (not string!) msg appear in the error message. +#define SOCI_STATIC_ASSERT(expr) \ + struct SOCI_MAKE_UNIQUE_NAME(SociAssert) { unsigned msg: expr; } + +#endif // SOCI_PRIVATE_STATIC_ASSERT_H_INCLUDED diff --git a/src/core/backend-loader.h b/include/soci/backend-loader.h similarity index 97% rename from src/core/backend-loader.h rename to include/soci/backend-loader.h index e4453abc63..f7dc6fa78c 100644 --- a/src/core/backend-loader.h +++ b/include/soci/backend-loader.h @@ -8,7 +8,7 @@ #ifndef SOCI_BACKEND_LOADER_H_INCLUDED #define SOCI_BACKEND_LOADER_H_INCLUDED -#include "soci-backend.h" +#include "soci/soci-backend.h" // std #include #include diff --git a/include/soci/bind-values.h b/include/soci/bind-values.h new file mode 100644 index 0000000000..3945636b3c --- /dev/null +++ b/include/soci/bind-values.h @@ -0,0 +1,196 @@ +#ifndef SOCI_BIND_VALUES_H_INCLUDED +#define SOCI_BIND_VALUES_H_INCLUDED + +#include "exchange-traits.h" +#include "into-type.h" +#include "into.h" +#include "soci-backend.h" +#include "use-type.h" +#include "use.h" + + +#ifdef HAVE_BOOST +# include +# include +#endif // HAVE_BOOST +#include + +namespace soci +{ +namespace details +{ + +class use_type_vector: public std::vector +{ +public: + ~use_type_vector() + { + for(iterator iter = begin(), _end = end(); + iter != _end; iter++) + delete *iter; + } + + void exchange(use_type_ptr const& u) { push_back(u.get()); u.release(); } + + template + void exchange(use_container const &uc) + { +#ifdef HAVE_BOOST + exchange_(uc, (typename boost::fusion::traits::is_sequence::type *)NULL); +#else + exchange_(uc, NULL); +#endif // HAVE_BOOST + } + +private: +#ifdef HAVE_BOOST + template + struct use_sequence + { + use_sequence(use_type_vector &_p, Indicator &_ind) + :p(_p), ind(_ind) {} + + template + void operator()(T2 &t2) const + { + p.exchange(use(t2, ind)); + } + + use_type_vector &p; + Indicator &ind; + private: + SOCI_NOT_COPYABLE(use_sequence) + }; + + template + struct use_sequence + { + use_sequence(use_type_vector &_p) + :p(_p) {} + + template + void operator()(T2 &t2) const + { + p.exchange(use(t2)); + } + + use_type_vector &p; + private: + SOCI_NOT_COPYABLE(use_sequence) + }; + + template + void exchange_(use_container const &uc, boost::mpl::true_ * /* fusion sequence */) + { + boost::fusion::for_each(uc.t, use_sequence(*this, uc.ind)); + } + + template + void exchange_(use_container const &uc, boost::mpl::true_ * /* fusion sequence */) + { + boost::fusion::for_each(uc.t, use_sequence(*this)); + } + +#endif // HAVE_BOOST + + template + void exchange_(use_container const &uc, ...) + { exchange(do_use(uc.t, uc.ind, uc.name, typename details::exchange_traits::type_family())); } + + template + void exchange_(use_container const &uc, ...) + { exchange(do_use(uc.t, uc.name, typename details::exchange_traits::type_family())); } + + template + void exchange_(use_container const &uc, ...) + { exchange(do_use(uc.t, uc.ind, uc.name, typename details::exchange_traits::type_family())); } + + template + void exchange_(use_container const &uc, ...) + { exchange(do_use(uc.t, uc.name, typename details::exchange_traits::type_family())); } +}; + +class into_type_vector: public std::vector +{ +public: + ~into_type_vector() + { + for(iterator iter = begin(), _end = end(); + iter != _end; iter++) + delete *iter; + } + + void exchange(into_type_ptr const& i) { push_back(i.get()); i.release(); } + + template + void exchange(into_container const &ic) + { +#ifdef HAVE_BOOST + exchange_(ic, (typename boost::fusion::traits::is_sequence::type *)NULL); +#else + exchange_(ic, NULL); +#endif // HAVE_BOOST + } + +private: +#ifdef HAVE_BOOST + template + struct into_sequence + { + into_sequence(into_type_vector &_p, Indicator &_ind) + :p(_p), ind(_ind) {} + + template + void operator()(T2 &t2) const + { + p.exchange(into(t2, ind)); + } + + into_type_vector &p; + Indicator &ind; + private: + SOCI_NOT_COPYABLE(into_sequence) + }; + + template + struct into_sequence + { + into_sequence(into_type_vector &_p) + :p(_p) {} + + template + void operator()(T2 &t2) const + { + p.exchange(into(t2)); + } + + into_type_vector &p; + private: + SOCI_NOT_COPYABLE(into_sequence) + }; + + template + void exchange_(into_container const &ic, boost::mpl::true_ * /* fusion sequence */) + { + boost::fusion::for_each(ic.t, into_sequence(*this, ic.ind)); + } + + template + void exchange_(into_container const &ic, boost::mpl::true_ * /* fusion sequence */) + { + boost::fusion::for_each(ic.t, into_sequence(*this)); + } +#endif // HAVE_BOOST + + template + void exchange_(into_container const &ic, ...) + { exchange(do_into(ic.t, ic.ind, typename details::exchange_traits::type_family())); } + + template + void exchange_(into_container const &ic, ...) + { exchange(do_into(ic.t, typename details::exchange_traits::type_family())); } +}; + +} // namespace details +}// namespace soci +#endif // SOCI_BIND_VALUES_H_INCLUDED diff --git a/src/core/blob-exchange.h b/include/soci/blob-exchange.h similarity index 93% rename from src/core/blob-exchange.h rename to include/soci/blob-exchange.h index 03f2258ba0..a9e8679bc0 100644 --- a/src/core/blob-exchange.h +++ b/include/soci/blob-exchange.h @@ -8,9 +8,9 @@ #ifndef SOCI_BLOB_EXCHANGE_H_INCLUDED #define SOCI_BLOB_EXCHANGE_H_INCLUDED -#include "blob.h" -#include "into-type.h" -#include "use-type.h" +#include "soci/blob.h" +#include "soci/into-type.h" +#include "soci/use-type.h" // std #include @@ -49,6 +49,7 @@ template <> struct exchange_traits { typedef basic_type_tag type_family; + enum { x_type = x_blob }; }; } // namespace details diff --git a/src/core/blob.h b/include/soci/blob.h similarity index 96% rename from src/core/blob.h rename to include/soci/blob.h index 16159ff6ad..50ac0692a1 100644 --- a/src/core/blob.h +++ b/include/soci/blob.h @@ -8,7 +8,7 @@ #ifndef SOCI_BLOB_H_INCLUDED #define SOCI_BLOB_H_INCLUDED -#include "soci-config.h" +#include "soci/soci-platform.h" // std #include diff --git a/include/soci/boost-fusion.h b/include/soci/boost-fusion.h new file mode 100644 index 0000000000..f235b18431 --- /dev/null +++ b/include/soci/boost-fusion.h @@ -0,0 +1,28 @@ +// +// Copyright (C) 2004-2008 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) +// + +#ifndef SOCI_BOOST_FUSION_H_INCLUDED +#define SOCI_BOOST_FUSION_H_INCLUDED + +#ifndef SOCI_MAX_FUSION_SEQUENCE_LENGTH +#define SOCI_MAX_FUSION_SEQUENCE_LENGTH 10 +#endif + +#include "values.h" +#include "type-conversion-traits.h" +// boost +#include +#include +#include +#include +#include +#include +#include +#include + + +#endif // SOCI_BOOST_FUSION_H_INCLUDED diff --git a/src/core/boost-gregorian-date.h b/include/soci/boost-gregorian-date.h similarity index 96% rename from src/core/boost-gregorian-date.h rename to include/soci/boost-gregorian-date.h index cfa3c81d23..66321c5291 100644 --- a/src/core/boost-gregorian-date.h +++ b/include/soci/boost-gregorian-date.h @@ -8,7 +8,7 @@ #ifndef SOCI_BOOST_GREGORIAN_DATE_H_INCLUDED #define SOCI_BOOST_GREGORIAN_DATE_H_INCLUDED -#include "type-conversion-traits.h" +#include "soci/type-conversion-traits.h" // boost #include #include diff --git a/src/core/boost-optional.h b/include/soci/boost-optional.h similarity index 78% rename from src/core/boost-optional.h rename to include/soci/boost-optional.h index 2f6699a35e..9e42611f4c 100644 --- a/src/core/boost-optional.h +++ b/include/soci/boost-optional.h @@ -8,13 +8,19 @@ #ifndef SOCI_BOOST_OPTIONAL_H_INCLUDED #define SOCI_BOOST_OPTIONAL_H_INCLUDED -#include "type-conversion-traits.h" +#include "soci/type-conversion-traits.h" // boost #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 > @@ -52,4 +58,8 @@ 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/boost-tuple.h b/include/soci/boost-tuple.h new file mode 100644 index 0000000000..37547367b4 --- /dev/null +++ b/include/soci/boost-tuple.h @@ -0,0 +1,18 @@ +// +// Copyright (C) 2004-2008 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) +// + +#ifndef SOCI_BOOST_TUPLE_H_INCLUDED +#define SOCI_BOOST_TUPLE_H_INCLUDED + +#include "values.h" +#include "type-conversion-traits.h" + +// boost +#include +#include + +#endif // SOCI_BOOST_TUPLE_H_INCLUDED diff --git a/src/core/connection-parameters.h b/include/soci/connection-parameters.h similarity index 95% rename from src/core/connection-parameters.h rename to include/soci/connection-parameters.h index 86483a33a2..ba0eef936d 100644 --- a/src/core/connection-parameters.h +++ b/include/soci/connection-parameters.h @@ -8,7 +8,7 @@ #ifndef SOCI_CONNECTION_PARAMETERS_H_INCLUDED #define SOCI_CONNECTION_PARAMETERS_H_INCLUDED -#include "soci-config.h" +#include "soci/soci-platform.h" #include #include @@ -59,7 +59,7 @@ private: std::string connectString_; // We store all the values as strings for simplicity. - typedef std::map Options; + typedef std::map Options; Options options_; }; diff --git a/src/core/connection-pool.h b/include/soci/connection-pool.h similarity index 95% rename from src/core/connection-pool.h rename to include/soci/connection-pool.h index b20c5cf7fe..ae38b29235 100644 --- a/src/core/connection-pool.h +++ b/include/soci/connection-pool.h @@ -8,7 +8,7 @@ #ifndef SOCI_CONNECTION_POOL_H_INCLUDED #define SOCI_CONNECTION_POOL_H_INCLUDED -#include "soci-config.h" +#include "soci/soci-platform.h" // std #include diff --git a/src/backends/db2/soci-db2.h b/include/soci/db2/soci-db2.h similarity index 94% rename from src/backends/db2/soci-db2.h rename to include/soci/db2/soci-db2.h index 246e7eee76..3043096bce 100644 --- a/src/backends/db2/soci-db2.h +++ b/include/soci/db2/soci-db2.h @@ -24,7 +24,7 @@ # define SOCI_DB2_DECL #endif -#include "soci-backend.h" +#include #include #include @@ -46,6 +46,17 @@ namespace soci BOUND_BY_NAME, BOUND_BY_POSITION }; + + inline SQLPOINTER int_as_ptr(int n) + { + union + { + SQLPOINTER p; + int n; + } u; + u.n = n; + return u.p; + } }} static const std::size_t maxBuffer = 1024 * 1024 * 1024; //CLI limit is about 3 GB, but 1GB should be enough @@ -54,11 +65,11 @@ class 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() { }; - - //We have to extract error information before exception throwing, cause CLI handles could be broken at the construction time + + //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); - - SQLRETURN errorCode; + + SQLRETURN errorCode; }; struct db2_statement_backend; @@ -184,12 +195,13 @@ struct SOCI_DB2_DECL db2_statement_backend : details::statement_backend long long get_affected_rows(); int get_number_of_rows(); + std::string get_parameter_name(int index) const; std::string rewrite_for_procedure_call(std::string const& query); int prepare_for_describe(); void describe_column(int colNum, data_type& dtype, std::string& columnName); - std::size_t column_size(int col); + size_t column_size(int col); db2_standard_into_type_backend* make_into_type_backend(); db2_standard_use_type_backend* make_use_type_backend(); @@ -200,7 +212,7 @@ struct SOCI_DB2_DECL db2_statement_backend : details::statement_backend SQLHANDLE hStmt; std::string query_; - std::vector names; + std::vector names_; bool hasVectorUseElements; SQLUINTEGER numRowsFetched; details::db2::binding_method use_binding_method_; diff --git a/src/backends/empty/soci-empty.h b/include/soci/empty/soci-empty.h similarity index 98% rename from src/backends/empty/soci-empty.h rename to include/soci/empty/soci-empty.h index ae63fe47a7..2b3f2215eb 100644 --- a/src/backends/empty/soci-empty.h +++ b/include/soci/empty/soci-empty.h @@ -23,7 +23,7 @@ # define SOCI_EMPTY_DECL #endif -#include "soci-backend.h" +#include #include #include @@ -116,6 +116,7 @@ struct SOCI_EMPTY_DECL empty_statement_backend : details::statement_backend long long get_affected_rows(); int get_number_of_rows(); + std::string get_parameter_name(int index) const; std::string rewrite_for_procedure_call(std::string const& query); diff --git a/include/soci/error.h b/include/soci/error.h new file mode 100644 index 0000000000..4036095545 --- /dev/null +++ b/include/soci/error.h @@ -0,0 +1,50 @@ +// +// Copyright (C) 2004-2008 Maciej Sobczak +// Copyright (C) 2015 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) +// + +#ifndef SOCI_ERROR_H_INCLUDED +#define SOCI_ERROR_H_INCLUDED + +#include "soci/soci-platform.h" +// std +#include +#include + +namespace soci +{ + +class SOCI_DECL soci_error : public std::runtime_error +{ +public: + explicit soci_error(std::string const & msg); + + soci_error(soci_error const& e); + soci_error& operator=(soci_error const& e); + + virtual ~soci_error() throw(); + + // 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(); + + // 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 + // first call adding the lowest level context and the last one -- the + // highest level context. + void add_context(std::string const& context); + +private: + // Optional extra information (currently just the context data). + class soci_error_extra_info* info_; +}; + +} // namespace soci + +#endif // SOCI_ERROR_H_INCLUDED diff --git a/src/core/exchange-traits.h b/include/soci/exchange-traits.h similarity index 97% rename from src/core/exchange-traits.h rename to include/soci/exchange-traits.h index 9f21838ead..8bb9bf1356 100644 --- a/src/core/exchange-traits.h +++ b/include/soci/exchange-traits.h @@ -8,8 +8,8 @@ #ifndef SOCI_EXCHANGE_TRAITS_H_INCLUDED #define SOCI_EXCHANGE_TRAITS_H_INCLUDED -#include "type-conversion-traits.h" -#include "soci-backend.h" +#include "soci/type-conversion-traits.h" +#include "soci/soci-backend.h" // std #include #include diff --git a/src/backends/firebird/soci-firebird.h b/include/soci/firebird/soci-firebird.h similarity index 95% rename from src/backends/firebird/soci-firebird.h rename to include/soci/firebird/soci-firebird.h index 4435ccea3d..bcb02db0ec 100644 --- a/src/backends/firebird/soci-firebird.h +++ b/include/soci/firebird/soci-firebird.h @@ -28,7 +28,7 @@ #ifdef _WIN32 #include // To understand and/or/not on MSVC9 #endif -#include +#include #include // FireBird #include #include @@ -185,6 +185,7 @@ struct firebird_statement_backend : details::statement_backend virtual long long get_affected_rows(); virtual int get_number_of_rows(); + virtual std::string get_parameter_name(int index) const; virtual std::string rewrite_for_procedure_call(std::string const &query); @@ -218,7 +219,7 @@ protected: long long rowsAffectedBulk_; // number of rows affected by the last bulk operation virtual void exchangeData(bool gotData, int row); - virtual void prepareSQLDA(XSQLDA ** sqldap, int size = 10); + virtual void prepareSQLDA(XSQLDA ** sqldap, short size = 10); virtual void rewriteQuery(std::string const & query, std::vector & buffer); virtual void rewriteParameters(std::string const & src, @@ -237,13 +238,6 @@ protected: bool procedure_; }; -struct firebird_rowid_backend : details::rowid_backend -{ - firebird_rowid_backend(firebird_session_backend &session); - - ~firebird_rowid_backend(); -}; - struct firebird_blob_backend : details::blob_backend { firebird_blob_backend(firebird_session_backend &session); @@ -313,16 +307,21 @@ struct firebird_session_backend : details::session_backend void cleanUp(); virtual firebird_statement_backend * make_statement_backend(); - virtual firebird_rowid_backend * make_rowid_backend(); + virtual details::rowid_backend* make_rowid_backend(); virtual firebird_blob_backend * make_blob_backend(); - virtual void setDPBOption(int const option, std::string const & value); - bool get_option_decimals_as_strings() { return decimals_as_strings_; } + // Returns the pointer to the current transaction handle, starting a new + // transaction if necessary. + // + // The returned pointer should + isc_tr_handle* current_transaction(); + isc_db_handle dbhp_; + +private: isc_tr_handle trhp_; - std::string dpb_; bool decimals_as_strings_; }; diff --git a/src/core/into-type.h b/include/soci/into-type.h similarity index 97% rename from src/core/into-type.h rename to include/soci/into-type.h index 990b7937c3..43fbee9a51 100644 --- a/src/core/into-type.h +++ b/include/soci/into-type.h @@ -8,9 +8,9 @@ #ifndef SOCI_INTO_TYPE_H_INCLUDED #define SOCI_INTO_TYPE_H_INCLUDED -#include "soci-backend.h" -#include "type-ptr.h" -#include "exchange-traits.h" +#include "soci/soci-backend.h" +#include "soci/type-ptr.h" +#include "soci/exchange-traits.h" // std #include #include diff --git a/src/core/into.h b/include/soci/into.h similarity index 50% rename from src/core/into.h rename to include/soci/into.h index 242f512219..be85f58125 100644 --- a/src/core/into.h +++ b/include/soci/into.h @@ -8,9 +8,9 @@ #ifndef SOCI_INTO_H_INCLUDED #define SOCI_INTO_H_INCLUDED -#include "into-type.h" -#include "exchange-traits.h" -#include "type-conversion.h" +#include "soci/into-type.h" +#include "soci/exchange-traits.h" +#include "soci/type-conversion.h" // std #include #include @@ -22,26 +22,43 @@ namespace soci // these helpers work with both basic and user-defined types thanks to // the tag-dispatching, as defined in exchange_traits template -template -details::into_type_ptr into(T & t) +namespace details { - return details::do_into(t, - typename details::exchange_traits::type_family()); -} +template +struct into_container +{ + into_container(T &_t, Indicator &_ind) + : t(_t), ind(_ind) {} + + T &t; + Indicator &ind; +private: + SOCI_NOT_ASSIGNABLE(into_container) +}; + +typedef void no_indicator; +template +struct into_container +{ + into_container(T &_t) + : t(_t) {} + + T &t; +private: + SOCI_NOT_ASSIGNABLE(into_container) +}; + +} // namespace details template -details::into_type_ptr into(T & t, indicator & ind) -{ - return details::do_into(t, ind, - typename details::exchange_traits::type_family()); -} +details::into_container + into(T &t) +{ return details::into_container(t); } -template -details::into_type_ptr into(T & t, std::vector & ind) -{ - return details::do_into(t, ind, - typename details::exchange_traits::type_family()); -} +template +details::into_container + into(T &t, Indicator &ind) +{ return details::into_container(t, ind); } // for char buffer with run-time size information template diff --git a/src/backends/mysql/soci-mysql.h b/include/soci/mysql/soci-mysql.h similarity index 97% rename from src/backends/mysql/soci-mysql.h rename to include/soci/mysql/soci-mysql.h index 77848da79b..e51fb0f3ad 100644 --- a/src/backends/mysql/soci-mysql.h +++ b/include/soci/mysql/soci-mysql.h @@ -24,7 +24,7 @@ # define SOCI_MYSQL_DECL #endif -#include "soci-backend.h" +#include #ifdef _WIN32 #include // SOCKET #endif // _WIN32 @@ -60,7 +60,7 @@ struct mysql_standard_into_type_backend : details::standard_into_type_backend virtual void clean_up(); mysql_statement_backend &statement_; - + void *data_; details::exchange_type type_; int position_; @@ -83,7 +83,7 @@ struct mysql_vector_into_type_backend : details::vector_into_type_backend virtual void clean_up(); mysql_statement_backend &statement_; - + void *data_; details::exchange_type type_; int position_; @@ -105,7 +105,7 @@ struct mysql_standard_use_type_backend : details::standard_use_type_backend virtual void clean_up(); mysql_statement_backend &statement_; - + void *data_; details::exchange_type type_; int position_; @@ -130,7 +130,7 @@ struct mysql_vector_use_type_backend : details::vector_use_type_backend virtual void clean_up(); mysql_statement_backend &statement_; - + void *data_; details::exchange_type type_; int position_; @@ -153,6 +153,7 @@ struct mysql_statement_backend : details::statement_backend virtual long long get_affected_rows(); virtual int get_number_of_rows(); + virtual std::string get_parameter_name(int index) const; virtual std::string rewrite_for_procedure_call(std::string const &query); @@ -166,9 +167,9 @@ struct mysql_statement_backend : details::statement_backend virtual mysql_vector_use_type_backend * make_vector_use_type_backend(); mysql_session_backend &session_; - + MYSQL_RES *result_; - + // The query is split into chunks, separated by the named parameters; // e.g. for "SELECT id FROM ttt WHERE name = :foo AND gender = :bar" // we will have query chunks "SELECT id FROM ttt WHERE name = ", @@ -177,11 +178,11 @@ struct mysql_statement_backend : details::statement_backend std::vector names_; // list of names for named binds long long rowsAffectedBulk_; // number of rows affected by the last bulk operation - + int numberOfRows_; // number of rows retrieved from the server int currentRow_; // "current" row number to consume in postFetch int rowsToConsume_; // number of rows to be consumed in postFetch - + bool justDescribed_; // to optimize row description with immediately // following actual statement execution @@ -193,7 +194,7 @@ struct mysql_statement_backend : details::statement_backend bool hasVectorIntoElements_; bool hasUseElements_; bool hasVectorUseElements_; - + // the following maps are used for finding data buffers according to // use elements specified by the user @@ -238,6 +239,8 @@ struct mysql_session_backend : details::session_backend virtual void commit(); virtual void rollback(); + virtual bool get_last_insert_id(session&, std::string const&, long&); + virtual std::string get_backend_name() const { return "mysql"; } void clean_up(); @@ -245,7 +248,7 @@ struct mysql_session_backend : details::session_backend virtual mysql_statement_backend * make_statement_backend(); virtual mysql_rowid_backend * make_rowid_backend(); virtual mysql_blob_backend * make_blob_backend(); - + MYSQL *conn_; }; diff --git a/include/soci/noreturn.h b/include/soci/noreturn.h new file mode 100644 index 0000000000..26d8cc7b33 --- /dev/null +++ b/include/soci/noreturn.h @@ -0,0 +1,22 @@ +// +// Copyright (C) 2015 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) +// + +#ifndef SOCI_NORETURN_H_INCLUDED +#define SOCI_NORETURN_H_INCLUDED + +// Define a portable SOCI_NORETURN macro. +// +// TODO-C++11: Use [[noreturn]] attribute. +#if defined(__GNUC__) +# define SOCI_NORETURN __attribute__((noreturn)) void +#elif defined(_MSC_VER) +# define SOCI_NORETURN __declspec(noreturn) void +#else +# define SOCI_NORETURN void +#endif + +#endif // SOCI_NORETURN_H_INCLUDED diff --git a/src/backends/odbc/soci-odbc.h b/include/soci/odbc/soci-odbc.h similarity index 92% rename from src/backends/odbc/soci-odbc.h rename to include/soci/odbc/soci-odbc.h index c820e8d55e..df4d739c7c 100644 --- a/src/backends/odbc/soci-odbc.h +++ b/include/soci/odbc/soci-odbc.h @@ -23,10 +23,11 @@ # define SOCI_ODBC_DECL #endif +#include "soci/soci-platform.h" #include -#include +#include +#include #if defined(_MSC_VER) || defined(__MINGW32__) -#include #include #endif #include // ODBC @@ -35,10 +36,17 @@ namespace soci { - // TODO: Do we want to make it a part of public interface? --mloskot 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; + + // 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) + { + return reinterpret_cast(const_cast(s.c_str())); + } } // Option allowing to specify the "driver completion" parameter of @@ -69,6 +77,8 @@ protected: }; odbc_statement_backend &statement_; +private: + SOCI_NOT_COPYABLE(odbc_standard_type_backend_base) }; struct odbc_standard_into_type_backend : details::standard_into_type_backend, @@ -93,6 +103,8 @@ struct odbc_standard_into_type_backend : details::standard_into_type_backend, int position_; SQLSMALLINT odbcType_; SQLLEN valueLen_; +private: + SOCI_NOT_COPYABLE(odbc_standard_into_type_backend) }; struct odbc_vector_into_type_backend : details::vector_into_type_backend, @@ -212,6 +224,7 @@ struct odbc_statement_backend : details::statement_backend virtual long long get_affected_rows(); virtual int get_number_of_rows(); + virtual std::string get_parameter_name(int index) const; virtual std::string rewrite_for_procedure_call(std::string const &query); @@ -282,6 +295,7 @@ struct odbc_session_backend : details::session_backend virtual std::string get_backend_name() const { return "odbc"; } + void configure_connection(); void reset_transaction(); void clean_up(); @@ -325,7 +339,24 @@ public: odbc_soci_error(SQLSMALLINT htype, SQLHANDLE hndl, std::string const & msg) - : soci_error(msg) + : soci_error(interpret_odbc_error(htype, hndl, msg)) + { + } + + SQLCHAR const * odbc_error_code() const + { + return sqlstate_; + } + SQLINTEGER native_error_code() const + { + return sqlcode_; + } + SQLCHAR const * odbc_error_message() const + { + return message_; + } +private: + std::string interpret_odbc_error(SQLSMALLINT htype, SQLHANDLE hndl, std::string const& msg) { const char* socierror = NULL; @@ -369,19 +400,11 @@ public: sqlcode_ = 0; } - } - SQLCHAR const * odbc_error_code() const - { - return reinterpret_cast(sqlstate_); - } - SQLINTEGER native_error_code() const - { - return sqlcode_; - } - SQLCHAR const * odbc_error_message() const - { - return reinterpret_cast(message_); + std::ostringstream ss; + ss << "Error " << msg << ": " << message_ << " (SQL state " << sqlstate_ << ")"; + + return ss.str(); } }; diff --git a/src/core/once-temp-type.h b/include/soci/once-temp-type.h similarity index 81% rename from src/core/once-temp-type.h rename to include/soci/once-temp-type.h index abcc2de85b..bbe94e3c89 100644 --- a/src/core/once-temp-type.h +++ b/include/soci/once-temp-type.h @@ -8,8 +8,8 @@ #ifndef SOCI_ONCE_TEMP_TYPE_H_INCLUDED #define SOCI_ONCE_TEMP_TYPE_H_INCLUDED -#include "ref-counted-statement.h" -#include "prepare-temp-type.h" +#include "soci/ref-counted-statement.h" +#include "soci/prepare-temp-type.h" #if __cplusplus >= 201103L #define SOCI_ONCE_TEMP_TYPE_NOEXCEPT noexcept(false) @@ -35,7 +35,7 @@ public: once_temp_type(session & s); 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; template @@ -46,7 +46,18 @@ 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) + { + rcst_->exchange(ic); + return *this; + } + template + once_temp_type &operator,(use_container const &uc) + { + rcst_->exchange(uc); + return *this; + } private: ref_counted_statement * rcst_; diff --git a/src/backends/oracle/soci-oracle.h b/include/soci/oracle/soci-oracle.h similarity index 96% rename from src/backends/oracle/soci-oracle.h rename to include/soci/oracle/soci-oracle.h index c6ffb60a12..2396ef8a3e 100644 --- a/src/backends/oracle/soci-oracle.h +++ b/include/soci/oracle/soci-oracle.h @@ -23,7 +23,7 @@ # define SOCI_ORACLE_DECL #endif -#include +#include #include // OCI #include @@ -188,6 +188,7 @@ struct oracle_statement_backend : details::statement_backend virtual long long get_affected_rows(); virtual int get_number_of_rows(); + virtual std::string get_parameter_name(int index) const; virtual std::string rewrite_for_procedure_call(std::string const &query); @@ -264,6 +265,11 @@ struct oracle_session_backend : details::session_backend bool get_option_decimals_as_strings() { return decimals_as_strings_; } + // Return either SQLT_FLT or SQLT_BDOUBLE as the type to use when binding + // values of C type "double" (the latter is preferable but might not be + // always available). + ub2 get_double_sql_type() const; + OCIEnv *envhp_; OCIServer *srvhp_; OCIError *errhp_; diff --git a/src/backends/postgresql/soci-postgresql.h b/include/soci/postgresql/soci-postgresql.h similarity index 97% rename from src/backends/postgresql/soci-postgresql.h rename to include/soci/postgresql/soci-postgresql.h index 6742c6416c..49eb427e14 100644 --- a/src/backends/postgresql/soci-postgresql.h +++ b/include/soci/postgresql/soci-postgresql.h @@ -24,14 +24,10 @@ # define SOCI_POSTGRESQL_DECL #endif -#include +#include #include #include -#ifdef _MSC_VER -#pragma warning(disable:4512 4511) -#endif - namespace soci { @@ -114,9 +110,7 @@ private: PGresult* result_; - // This class can't be copied as it owns result_ which can't be duplicated. - postgresql_result(postgresql_result const &); - postgresql_result& operator=(postgresql_result const &); + SOCI_NOT_COPYABLE(postgresql_result) }; } // namespace details @@ -231,6 +225,7 @@ struct postgresql_statement_backend : details::statement_backend virtual long long get_affected_rows(); virtual int get_number_of_rows(); + virtual std::string get_parameter_name(int index) const; virtual std::string rewrite_for_procedure_call(std::string const & query); diff --git a/src/core/prepare-temp-type.h b/include/soci/prepare-temp-type.h similarity index 66% rename from src/core/prepare-temp-type.h rename to include/soci/prepare-temp-type.h index c3d8a0172d..52c183d4e9 100644 --- a/src/core/prepare-temp-type.h +++ b/include/soci/prepare-temp-type.h @@ -8,9 +8,10 @@ #ifndef SOCI_PREPARE_TEMP_TYPE_INCLUDED #define SOCI_PREPARE_TEMP_TYPE_INCLUDED -#include "into-type.h" -#include "use-type.h" -#include "ref-counted-prepare-info.h" +#include "soci/into-type.h" +#include "soci/use-type.h" +#include "soci/use.h" +#include "soci/ref-counted-prepare-info.h" namespace soci { @@ -36,10 +37,23 @@ public: } prepare_temp_type & operator,(into_type_ptr const & i); - prepare_temp_type & operator,(use_type_ptr const & u); + + template + prepare_temp_type &operator,(into_container const &ic) + { + rcpi_->exchange(ic); + return *this; + } + template + prepare_temp_type &operator,(use_container const &uc) + { + rcpi_->exchange(uc); + return *this; + } ref_counted_prepare_info * get_prepare_info() const { return rcpi_; } + private: ref_counted_prepare_info * rcpi_; }; diff --git a/src/core/procedure.h b/include/soci/procedure.h similarity index 98% rename from src/core/procedure.h rename to include/soci/procedure.h index 92a4396e40..4edbf58de7 100644 --- a/src/core/procedure.h +++ b/include/soci/procedure.h @@ -8,7 +8,7 @@ #ifndef SOCI_PROCEDURE_H_INCLUDED #define SOCI_PROCEDURE_H_INCLUDED -#include "statement.h" +#include "soci/statement.h" namespace soci { diff --git a/src/core/query_transformation.h b/include/soci/query_transformation.h similarity index 95% rename from src/core/query_transformation.h rename to include/soci/query_transformation.h index 0e184b0e04..ff3d4fe9ca 100644 --- a/src/core/query_transformation.h +++ b/include/soci/query_transformation.h @@ -8,7 +8,7 @@ #ifndef SOCI_QUERY_TRANSFORMATION_H_INCLUDED #define SOCI_QUERY_TRANSFORMATION_H_INCLUDED -#include "soci-config.h" +#include "soci/soci-platform.h" #include #include @@ -18,7 +18,7 @@ namespace soci namespace details { -// Query transformation is a mechanism that enables user to apply +// Query transformation is a mechanism that enables user to apply // any string-to-string transformation to SQL statement just // before it is executed. // Transformation procedure is specified by user, @@ -27,7 +27,7 @@ namespace details // unary function takes any type converible-to std::string // and returns std::string. -class query_transformation_function +class query_transformation_function : public std::unary_function { public: diff --git a/src/core/ref-counted-prepare-info.h b/include/soci/ref-counted-prepare-info.h similarity index 65% rename from src/core/ref-counted-prepare-info.h rename to include/soci/ref-counted-prepare-info.h index 5339bf3027..59af69dd96 100644 --- a/src/core/ref-counted-prepare-info.h +++ b/include/soci/ref-counted-prepare-info.h @@ -8,7 +8,8 @@ #ifndef SOCI_REF_COUNTED_PREPARE_INFO_INCLUDED #define SOCI_REF_COUNTED_PREPARE_INFO_INCLUDED -#include "ref-counted-statement.h" +#include "soci/bind-values.h" +#include "soci/ref-counted-statement.h" // std #include #include @@ -32,11 +33,19 @@ class ref_counted_prepare_info : public ref_counted_statement_base public: ref_counted_prepare_info(session& s) : ref_counted_statement_base(s) - , session_(s) {} - void exchange(into_type_ptr const& i); - void exchange(use_type_ptr const& u); + void exchange(use_type_ptr const& u) { uses_.exchange(u); } + + template + void exchange(use_container const &uc) + { uses_.exchange(uc); } + + void exchange(into_type_ptr const& i) { intos_.exchange(i); } + + template + void exchange(into_container const &ic) + { intos_.exchange(ic); } void final_action(); @@ -44,10 +53,8 @@ private: friend class statement_impl; friend class procedure_impl; - session& session_; - - std::vector intos_; - std::vector uses_; + into_type_vector intos_; + use_type_vector uses_; std::string get_query() const; }; diff --git a/src/core/ref-counted-statement.h b/include/soci/ref-counted-statement.h similarity index 81% rename from src/core/ref-counted-statement.h rename to include/soci/ref-counted-statement.h index 78374d0653..cf3407d480 100644 --- a/src/core/ref-counted-statement.h +++ b/include/soci/ref-counted-statement.h @@ -8,9 +8,9 @@ #ifndef SOCI_REF_COUNTED_STATEMENT_H_INCLUDED #define SOCI_REF_COUNTED_STATEMENT_H_INCLUDED -#include "statement.h" -#include "into-type.h" -#include "use-type.h" +#include "soci/statement.h" +#include "soci/into-type.h" +#include "soci/use-type.h" // std #include @@ -25,7 +25,7 @@ class SOCI_DECL ref_counted_statement_base { public: ref_counted_statement_base(session& s); - + virtual ~ref_counted_statement_base() {} virtual void final_action() = 0; @@ -62,9 +62,7 @@ protected: session & session_; private: - // noncopyable - ref_counted_statement_base(ref_counted_statement_base const&); - ref_counted_statement_base& operator=(ref_counted_statement_base const&); + SOCI_NOT_COPYABLE(ref_counted_statement_base) }; // this class is supposed to be a vehicle for the "once" statements @@ -75,11 +73,11 @@ public: ref_counted_statement(session & s) : ref_counted_statement_base(s), st_(s) {} - void exchange(into_type_ptr const & i) { st_.exchange(i); } - void exchange(use_type_ptr const & u) { st_.exchange(u); } - virtual void final_action(); + template + void exchange(T &t) { st_.exchange(t); } + private: statement st_; }; diff --git a/src/core/row-exchange.h b/include/soci/row-exchange.h similarity index 91% rename from src/core/row-exchange.h rename to include/soci/row-exchange.h index 5f4763674e..deb32da6e6 100644 --- a/src/core/row-exchange.h +++ b/include/soci/row-exchange.h @@ -8,10 +8,10 @@ #ifndef SOCI_INTO_ROW_H_INCLUDED #define SOCI_INTO_ROW_H_INCLUDED -#include "into-type.h" -#include "exchange-traits.h" -#include "row.h" -#include "statement.h" +#include "soci/into-type.h" +#include "soci/exchange-traits.h" +#include "soci/row.h" +#include "soci/statement.h" // std #include @@ -62,6 +62,8 @@ private: virtual void convert_from_base() {} row & r_; + + SOCI_NOT_COPYABLE(into_type) }; template <> diff --git a/src/core/row.h b/include/soci/row.h similarity index 86% rename from src/core/row.h rename to include/soci/row.h index f1aa90525f..02cb60eff3 100644 --- a/src/core/row.h +++ b/include/soci/row.h @@ -8,11 +8,10 @@ #ifndef SOCI_ROW_H_INCLUDED #define SOCI_ROW_H_INCLUDED -#include "type-holder.h" -#include "soci-backend.h" -#include "type-conversion.h" +#include "soci/type-holder.h" +#include "soci/soci-backend.h" +#include "soci/type-conversion.h" // std -#include #include #include #include @@ -40,7 +39,7 @@ private: class SOCI_DECL row { -public: +public: row(); ~row(); @@ -65,22 +64,18 @@ public: template T get(std::size_t pos) const { - assert(holders_.size() >= pos + 1); - typedef typename type_conversion::base_type base_type; - base_type const& baseVal = holders_[pos]->get(); + base_type const& baseVal = holders_.at(pos)->get(); T ret; - type_conversion::from_base(baseVal, *indicators_[pos], ret); + type_conversion::from_base(baseVal, *indicators_.at(pos), ret); return ret; } template T get(std::size_t pos, T const &nullValue) const { - assert(holders_.size() >= pos + 1); - - if (i_null == *indicators_[pos]) + if (i_null == *indicators_.at(pos)) { return nullValue; } @@ -127,9 +122,7 @@ public: } private: - // copy not supported - row(row const &); - void operator=(row const &); + SOCI_NOT_COPYABLE(row) std::size_t find_column(std::string const& name) const; diff --git a/src/core/rowid-exchange.h b/include/soci/rowid-exchange.h similarity index 92% rename from src/core/rowid-exchange.h rename to include/soci/rowid-exchange.h index 614f6ade02..2d1a85914b 100644 --- a/src/core/rowid-exchange.h +++ b/include/soci/rowid-exchange.h @@ -8,10 +8,10 @@ #ifndef SOCI_ROWID_EXCHANGE_H_INCLUDED #define SOCI_ROWID_EXCHANGE_H_INCLUDED -#include "rowid.h" -#include "into-type.h" -#include "use-type.h" -#include "exchange-traits.h" +#include "soci/rowid.h" +#include "soci/into-type.h" +#include "soci/use-type.h" +#include "soci/exchange-traits.h" // std #include diff --git a/src/core/rowid.h b/include/soci/rowid.h similarity index 95% rename from src/core/rowid.h rename to include/soci/rowid.h index 78685cdc46..f045eaf1ef 100644 --- a/src/core/rowid.h +++ b/include/soci/rowid.h @@ -8,7 +8,7 @@ #ifndef SOCI_ROWID_H_INCLUDED #define SOCI_ROWID_H_INCLUDED -#include "soci-config.h" +#include "soci/soci-platform.h" namespace soci { diff --git a/src/core/rowset.h b/include/soci/rowset.h similarity index 87% rename from src/core/rowset.h rename to include/soci/rowset.h index a2b7926f86..3c835731cf 100644 --- a/src/core/rowset.h +++ b/include/soci/rowset.h @@ -8,7 +8,7 @@ #ifndef SOCI_ROWSET_H_INCLUDED #define SOCI_ROWSET_H_INCLUDED -#include "statement.h" +#include "soci/statement.h" // std #include #include @@ -41,16 +41,12 @@ public: rowset_iterator(statement & st, T & define) : st_(&st), define_(&define) { - assert(0 != st_); - assert(0 != define_); - assert(0 != st_->get_backend()); - // Fetch first row to properly initialize iterator ++(*this); } - + // Access operators - + reference operator*() const { return (*define_); @@ -60,13 +56,13 @@ public: { return &(operator*()); } - + // Iteration operators rowset_iterator & operator++() { // Fetch next row from dataset - + if (st_->fetch() == false) { // Set iterator to non-derefencable state (pass-the-end) @@ -119,9 +115,6 @@ public: rowset_impl(details::prepare_temp_type const & prep) : refs_(1), st_(new statement(prep)), define_(new T()) { - assert(0 != st_.get()); - assert(0 != define_.get()); - st_->exchange_for_rowset(into(*define_)); st_->execute(); } @@ -154,13 +147,14 @@ 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_; - - // Non-copyable - rowset_impl(rowset_impl const &); - rowset_impl & operator=(rowset_impl const &); - +#endif + SOCI_NOT_COPYABLE(rowset_impl) }; // class rowset_impl } // namespace details @@ -179,34 +173,26 @@ public: typedef T value_type; typedef rowset_iterator iterator; typedef rowset_iterator const_iterator; - + // this is a conversion constructor rowset(details::prepare_temp_type const& prep) : pimpl_(new details::rowset_impl(prep)) { - assert(0 != pimpl_); } rowset(rowset const & other) : pimpl_(other.pimpl_) { - assert(0 != pimpl_); - pimpl_->incRef(); } ~rowset() { - assert(0 != pimpl_); - pimpl_->decRef(); } rowset& operator=(rowset const& rhs) { - assert(0 != pimpl_); - assert(0 != rhs.pimpl_); - if (&rhs != this) { rhs.pimpl_->incRef(); @@ -218,15 +204,11 @@ public: const_iterator begin() const { - assert(0 != pimpl_); - return pimpl_->begin(); } - + const_iterator end() const { - assert(0 != pimpl_); - return pimpl_->end(); } diff --git a/src/core/session.h b/include/soci/session.h similarity index 90% rename from src/core/session.h rename to include/soci/session.h index ff521b0a30..ce46e7e06a 100644 --- a/src/core/session.h +++ b/include/soci/session.h @@ -8,9 +8,9 @@ #ifndef SOCI_SESSION_H_INCLUDED #define SOCI_SESSION_H_INCLUDED -#include "once-temp-type.h" -#include "query_transformation.h" -#include "connection-parameters.h" +#include "soci/once-temp-type.h" +#include "soci/query_transformation.h" +#include "soci/connection-parameters.h" // std #include @@ -40,7 +40,13 @@ 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 + + public: session(); @@ -77,11 +83,14 @@ public: template void set_query_transformation(T callback) { - std::auto_ptr qtf(new details::query_transformation(callback)); - set_query_transformation_(qtf); - assert(qtf.get() == NULL); - } +#ifdef SOCI_CXX_C11 + std::unique_ptr qtf(new details::query_transformation(callback)); +#else + std::auto_ptr qtf(new details::query_transformation(callback)); +#endif + set_query_transformation_(qtf); + } // support for basic logging void set_log_stream(std::ostream * s); @@ -123,8 +132,7 @@ public: details::blob_backend * make_blob_backend(); private: - session(session const &); - session& operator=(session const &); + SOCI_NOT_COPYABLE(session) std::ostringstream query_stream_; details::query_transformation_function* query_transformation_; diff --git a/src/core/soci-backend.h b/include/soci/soci-backend.h similarity index 84% rename from src/core/soci-backend.h rename to include/soci/soci-backend.h index 75edee4ad2..0f7513f6b1 100644 --- a/src/core/soci-backend.h +++ b/include/soci/soci-backend.h @@ -8,8 +8,8 @@ #ifndef SOCI_BACKEND_H_INCLUDED #define SOCI_BACKEND_H_INCLUDED -#include "soci-config.h" -#include "error.h" +#include "soci/soci-platform.h" +#include "soci/error.h" // std #include #include @@ -21,7 +21,7 @@ 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_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long, dt_blob }; // the enum type for indicator variables @@ -71,9 +71,7 @@ public: virtual void clean_up() = 0; private: - // noncopyable - standard_into_type_backend(standard_into_type_backend const&); - standard_into_type_backend& operator=(standard_into_type_backend const&); + SOCI_NOT_COPYABLE(standard_into_type_backend) }; class vector_into_type_backend @@ -94,9 +92,7 @@ public: virtual void clean_up() = 0; private: - // noncopyable - vector_into_type_backend(vector_into_type_backend const&); - vector_into_type_backend& operator=(vector_into_type_backend const&); + SOCI_NOT_COPYABLE(vector_into_type_backend) }; // polymorphic use type backend @@ -118,9 +114,7 @@ public: virtual void clean_up() = 0; private: - // noncopyable - standard_use_type_backend(standard_use_type_backend const&); - standard_use_type_backend& operator=(standard_use_type_backend const&); + SOCI_NOT_COPYABLE(standard_use_type_backend) }; class vector_use_type_backend @@ -140,9 +134,7 @@ public: virtual void clean_up() = 0; private: - // noncopyable - vector_use_type_backend(vector_use_type_backend const&); - vector_use_type_backend& operator=(vector_use_type_backend const&); + SOCI_NOT_COPYABLE(vector_use_type_backend) }; // polymorphic statement backend @@ -170,6 +162,8 @@ public: virtual long long get_affected_rows() = 0; virtual int get_number_of_rows() = 0; + virtual std::string get_parameter_name(int index) const = 0; + virtual std::string rewrite_for_procedure_call(std::string const& query) = 0; virtual int prepare_for_describe() = 0; @@ -182,9 +176,7 @@ public: virtual vector_use_type_backend* make_vector_use_type_backend() = 0; private: - // noncopyable - statement_backend(statement_backend const&); - statement_backend& operator=(statement_backend const&); + SOCI_NOT_COPYABLE(statement_backend) }; // polymorphic RowID backend @@ -212,9 +204,7 @@ public: virtual void trim(std::size_t newLen) = 0; private: - // noncopyable - blob_backend(blob_backend const&); - blob_backend& operator=(blob_backend const&); + SOCI_NOT_COPYABLE(blob_backend) }; // polymorphic session backend @@ -251,9 +241,7 @@ public: virtual blob_backend* make_blob_backend() = 0; private: - // noncopyable - session_backend(session_backend const&); - session_backend& operator=(session_backend const&); + SOCI_NOT_COPYABLE(session_backend) }; } // namespace details diff --git a/include/soci/soci-config.h b/include/soci/soci-config.h new file mode 100644 index 0000000000..9b26d48fd9 --- /dev/null +++ b/include/soci/soci-config.h @@ -0,0 +1,13 @@ +// +// Copyright (C) 2004-2008 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) +// + +#ifndef SOCICONFIG_H_INCLUDED +#define SOCICONFIG_H_INCLUDED + +#include "soci/soci-platform.h" + +#endif // SOCICONFIG_H_INCLUDED diff --git a/include/soci/soci-platform.h b/include/soci/soci-platform.h new file mode 100644 index 0000000000..52369100cc --- /dev/null +++ b/include/soci/soci-platform.h @@ -0,0 +1,99 @@ +// +// Copyright (C) 2006-2008 Mateusz Loskot +// 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_PLATFORM_H_INCLUDED +#define SOCI_PLATFORM_H_INCLUDED + +//disable MSVC deprecated warnings +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) || defined(__MINGW32__) +#define LL_FMT_FLAGS "I64" +#else +#define LL_FMT_FLAGS "ll" +#endif + +// Portability hacks for Microsoft Visual C++ compiler +#ifdef _MSC_VER +#include + +//Disables warnings about STL objects need to have dll-interface and/or +//base class must have dll interface +#pragma warning(disable:4251 4275) + + +// Define if you have the vsnprintf variants. +#if _MSC_VER < 1500 +# define vsnprintf _vsnprintf +#endif + +// Define if you have the snprintf variants. +#define snprintf _snprintf + +// Define if you have the strtoll and strtoull variants. +#if _MSC_VER < 1300 +# error "Visual C++ versions prior 1300 don't support _strtoi64 and _strtoui64" +#elif _MSC_VER >= 1300 && _MSC_VER < 1800 +namespace std { + inline long long strtoll(char const* str, char** str_end, int base) + { + return _strtoi64(str, str_end, base); + } + + inline unsigned long long strtoull(char const* str, char** str_end, int base) + { + return _strtoui64(str, str_end, base); + } +} +#endif // _MSC_VER < 1800 +#endif // _MSC_VER + +#if defined(__CYGWIN__) || defined(__MINGW32__) +#include +namespace std { + using ::strtoll; + using ::strtoull; +} +#endif + +//define DLL import/export on WIN32 +#ifdef _WIN32 +# ifdef SOCI_DLL +# ifdef SOCI_SOURCE +# define SOCI_DECL __declspec(dllexport) +# else +# define SOCI_DECL __declspec(dllimport) +# endif // SOCI_SOURCE +# endif // SOCI_DLL +#endif // _WIN32 +// +// If SOCI_DECL isn't defined yet define it now +#ifndef SOCI_DECL +# define SOCI_DECL +#endif + +#define SOCI_NOT_ASSIGNABLE(classname) \ + classname& operator=(const classname&); + +#define SOCI_NOT_COPYABLE(classname) \ + classname(const classname&); \ + SOCI_NOT_ASSIGNABLE(classname) + +#define SOCI_UNUSED(x) (void)x; + + + +#endif // SOCI_PLATFORM_H_INCLUDED diff --git a/src/core/soci-simple.h b/include/soci/soci-simple.h similarity index 85% rename from src/core/soci-simple.h rename to include/soci/soci-simple.h index 57e5101cd0..5eb1e6b5bd 100644 --- a/src/core/soci-simple.h +++ b/include/soci/soci-simple.h @@ -8,7 +8,7 @@ #ifndef SOCI_SIMPLE_H_INCLUDED #define SOCI_SIMPLE_H_INCLUDED -#include "soci-config.h" +#include "soci/soci-platform.h" #ifdef __cplusplus extern "C" @@ -28,6 +28,23 @@ SOCI_DECL void soci_rollback(session_handle s); SOCI_DECL int soci_session_state(session_handle s); SOCI_DECL char const * soci_session_error_message(session_handle s); + +// blob + +typedef void *blob_handle; +SOCI_DECL blob_handle soci_create_blob(session_handle s); +SOCI_DECL void soci_destroy_blob(blob_handle b); + +SOCI_DECL int soci_blob_get_len(blob_handle b); +SOCI_DECL int soci_blob_read(blob_handle b, int offset, char *buf, int toRead); +SOCI_DECL int soci_blob_write(blob_handle b, int offset, char const *buf, int toWrite); +SOCI_DECL int soci_blob_append(blob_handle b, char const *buf, int toWrite); +SOCI_DECL int soci_blob_trim(blob_handle b, int newLen); + +SOCI_DECL int soci_blob_state(blob_handle b); +SOCI_DECL char const * soci_blob_error_message(blob_handle b); + + // statement typedef void * statement_handle; @@ -40,6 +57,7 @@ SOCI_DECL int soci_into_int (statement_handle st); SOCI_DECL int soci_into_long_long(statement_handle st); SOCI_DECL int soci_into_double (statement_handle st); SOCI_DECL int soci_into_date (statement_handle st); +SOCI_DECL int soci_into_blob (statement_handle st); // vector versions SOCI_DECL int soci_into_string_v (statement_handle st); @@ -55,6 +73,7 @@ SOCI_DECL int soci_get_into_int (statement_handle st, int position SOCI_DECL long long soci_get_into_long_long(statement_handle st, int position); SOCI_DECL double soci_get_into_double (statement_handle st, int position); SOCI_DECL char const * soci_get_into_date (statement_handle st, int position); +SOCI_DECL blob_handle soci_get_into_blob (statement_handle st, int position); // positional (re)size of vectors SOCI_DECL int soci_into_get_size_v(statement_handle st); @@ -75,6 +94,7 @@ SOCI_DECL void soci_use_int (statement_handle st, char const * name); SOCI_DECL void soci_use_long_long(statement_handle st, char const * name); SOCI_DECL void soci_use_double (statement_handle st, char const * name); SOCI_DECL void soci_use_date (statement_handle st, char const * name); +SOCI_DECL void soci_use_blob (statement_handle st, char const * name); // vector versions SOCI_DECL void soci_use_string_v (statement_handle st, char const * name); @@ -91,6 +111,7 @@ SOCI_DECL void soci_set_use_int (statement_handle st, char const * name, in SOCI_DECL void soci_set_use_long_long(statement_handle st, char const * name, long long val); SOCI_DECL void soci_set_use_double (statement_handle st, char const * name, double val); SOCI_DECL void soci_set_use_date (statement_handle st, char const * name, char const * val); +SOCI_DECL void soci_set_use_blob (statement_handle st, char const * name, blob_handle blob); // positional (re)size of vectors SOCI_DECL int soci_use_get_size_v(statement_handle st); @@ -118,6 +139,7 @@ SOCI_DECL int soci_get_use_int (statement_handle st, char const * SOCI_DECL long long soci_get_use_long_long(statement_handle st, char const * name); SOCI_DECL double soci_get_use_double (statement_handle st, char const * name); SOCI_DECL char const * soci_get_use_date (statement_handle st, char const * name); +SOCI_DECL blob_handle soci_get_use_blob (statement_handle st, char const * name); // statement preparation and execution diff --git a/include/soci/soci.h b/include/soci/soci.h new file mode 100644 index 0000000000..d370a0ea06 --- /dev/null +++ b/include/soci/soci.h @@ -0,0 +1,56 @@ +// +// Copyright (C) 2004-2008 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) +// + +#ifndef SOCI_H_INCLUDED +#define SOCI_H_INCLUDED + +// namespace soci +#include "soci/soci-platform.h" +#include "soci/backend-loader.h" +#include "soci/blob.h" +#include "soci/blob-exchange.h" +#include "soci/connection-pool.h" +#include "soci/error.h" +#include "soci/exchange-traits.h" +#include "soci/into.h" +#include "soci/into-type.h" +#include "soci/once-temp-type.h" +#include "soci/prepare-temp-type.h" +#include "soci/procedure.h" +#include "soci/ref-counted-prepare-info.h" +#include "soci/ref-counted-statement.h" +#include "soci/row.h" +#include "soci/row-exchange.h" +#include "soci/rowid.h" +#include "soci/rowid-exchange.h" +#include "soci/rowset.h" +#include "soci/session.h" +#include "soci/soci-backend.h" +#include "soci/statement.h" +#include "soci/transaction.h" +#include "soci/type-conversion.h" +#include "soci/type-conversion-traits.h" +#include "soci/type-holder.h" +#include "soci/type-ptr.h" +#include "soci/unsigned-types.h" +#include "soci/use.h" +#include "soci/use-type.h" +#include "soci/values.h" +#include "soci/values-exchange.h" + +// namespace boost +#ifdef SOCI_USE_BOOST +#include +#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 +#include "soci/boost-fusion.h" +#endif // BOOST_VERSION +#include "soci/boost-optional.h" +#include "soci/boost-tuple.h" +#include "soci/boost-gregorian-date.h" +#endif // SOCI_USE_BOOST + +#endif // SOCI_H_INCLUDED diff --git a/src/backends/sqlite3/soci-sqlite3.h b/include/soci/sqlite3/soci-sqlite3.h similarity index 86% rename from src/backends/sqlite3/soci-sqlite3.h rename to include/soci/sqlite3/soci-sqlite3.h index 53dee2f05e..9b01079896 100644 --- a/src/backends/sqlite3/soci-sqlite3.h +++ b/include/soci/sqlite3/soci-sqlite3.h @@ -25,7 +25,7 @@ #include #include -#include "soci-backend.h" +#include // Disable flood of nonsense warnings generated for SQLite #ifdef _MSC_VER @@ -56,6 +56,17 @@ typedef void (*sqlite3_destructor_type)(void*); namespace soci { +class sqlite3_soci_error : public soci_error +{ +public: + sqlite3_soci_error(std::string const & msg, int result); + + int result() const; + +private: + int result_; +}; + struct sqlite3_statement_backend; struct sqlite3_standard_into_type_backend : details::standard_into_type_backend { @@ -102,8 +113,7 @@ struct sqlite3_vector_into_type_backend : details::vector_into_type_backend struct sqlite3_standard_use_type_backend : details::standard_use_type_backend { - sqlite3_standard_use_type_backend(sqlite3_statement_backend &st) - : statement_(st), buf_(0) {} + sqlite3_standard_use_type_backend(sqlite3_statement_backend &st); virtual void bind_by_pos(int &position, void *data, details::exchange_type type, bool readOnly); @@ -117,11 +127,10 @@ struct sqlite3_standard_use_type_backend : details::standard_use_type_backend sqlite3_statement_backend &statement_; - void *data_; - details::exchange_type type_; - int position_; - std::string name_; - char *buf_; + void *data_; // pointer to used data: soci::use(myvariable) --> data_ = &myvariable + details::exchange_type type_; // type of data_ + int position_; // binding position + std::string name_; // binding name }; struct sqlite3_vector_use_type_backend : details::vector_use_type_backend @@ -150,15 +159,38 @@ struct sqlite3_vector_use_type_backend : details::vector_use_type_backend struct sqlite3_column { - std::string data_; bool isNull_; - char * blobBuf_; - std::size_t blobSize_; + data_type type_; + + union + { + struct + { + std::size_t size_; + union + { + const char *constData_; + char *data_; + }; + } buffer_; + + int int32_; + sqlite_api::sqlite3_int64 int64_; + double double_; + }; }; typedef std::vector sqlite3_row; typedef std::vector sqlite3_recordset; + +struct sqlite3_column_info +{ + data_type type_; + std::string name_; +}; +typedef std::vector sqlite3_column_info_list; + struct sqlite3_session_backend; struct sqlite3_statement_backend : details::statement_backend { @@ -175,6 +207,7 @@ struct sqlite3_statement_backend : details::statement_backend virtual long long get_affected_rows(); virtual int get_number_of_rows(); + virtual std::string get_parameter_name(int index) const; virtual std::string rewrite_for_procedure_call(std::string const &query); @@ -194,6 +227,8 @@ struct sqlite3_statement_backend : details::statement_backend bool databaseReady_; bool boundByName_; bool boundByPos_; + sqlite3_column_info_list columns_; + long long rowsAffectedBulk_; // number of rows affected by the last bulk operation @@ -229,6 +264,7 @@ struct sqlite3_blob_backend : details::blob_backend sqlite3_session_backend &session_; std::size_t set_data(char const *buf, std::size_t toWrite); + const char *get_buffer() const { return buf_; } private: char *buf_; @@ -245,6 +281,8 @@ struct sqlite3_session_backend : details::session_backend virtual void commit(); virtual void rollback(); + virtual bool get_last_insert_id(session&, std::string const&, long&); + virtual std::string get_backend_name() const { return "sqlite3"; } void clean_up(); diff --git a/src/core/statement.h b/include/soci/statement.h similarity index 69% rename from src/core/statement.h rename to include/soci/statement.h index e639a84e4a..0791877f6f 100644 --- a/src/core/statement.h +++ b/include/soci/statement.h @@ -8,15 +8,17 @@ #ifndef SOCI_STATEMENT_H_INCLUDED #define SOCI_STATEMENT_H_INCLUDED -#include "into-type.h" -#include "into.h" -#include "use-type.h" -#include "soci-backend.h" -#include "row.h" +#include "soci/bind-values.h" +#include "soci/into-type.h" +#include "soci/into.h" +#include "soci/noreturn.h" +#include "soci/use-type.h" +#include "soci/use.h" +#include "soci/soci-backend.h" +#include "soci/row.h" // std #include #include -#include #include namespace soci @@ -41,9 +43,20 @@ public: void alloc(); void bind(values & v); - void exchange(into_type_ptr const & i); - void exchange(use_type_ptr const & u); + + void exchange(into_type_ptr const & i) { intos_.exchange(i); } + template + void exchange(into_container const &ic) + { intos_.exchange(ic); } + + void exchange(use_type_ptr const & u) { uses_.exchange(u); } + template + void exchange(use_container const &uc) + { uses_.exchange(uc); } + + void clean_up(); + void bind_clean_up(); void prepare(std::string const & query, statement_type eType = st_repeatable_query); @@ -54,7 +67,10 @@ public: bool fetch(); void describe(); void set_row(row * r); - void exchange_for_rowset(into_type_ptr const & i); + void exchange_for_rowset(into_type_ptr const & i) { exchange_for_rowset_(i); } + template + void exchange_for_rowset(into_container const &ic) + { exchange_for_rowset_(ic); } // for diagnostics and advanced users // (downcast it to expected back-end statement class) @@ -73,11 +89,16 @@ public: std::string rewrite_for_procedure_call(std::string const & query); protected: - std::vector intos_; - std::vector uses_; + into_type_vector intos_; + use_type_vector uses_; std::vector indicators_; private: + // Call this method from a catch clause (only!) to rethrow the exception + // after adding the context in which it happened, including the provided + // description of the operation that failed, the SQL query and, if + // applicable, its parameters. + SOCI_NORETURN rethrow_current_exception_with_context(char const* operation); int refCount_; @@ -85,12 +106,33 @@ private: std::size_t fetchSize_; std::size_t initialFetchSize_; std::string query_; - std::map namedUses_; - std::vector intosForRow_; + into_type_vector intosForRow_; int definePositionForRow_; - void exchange_for_row(into_type_ptr const & i); + template + void exchange_for_rowset_(Into const &i) + { + if (intos_.empty() == false) + { + throw soci_error("Explicit into elements not allowed with rowset."); + } + + intos_.exchange(i); + + int definePosition = 1; + for(into_type_vector::iterator iter = intos_.begin(), + end = intos_.end(); + iter != end; iter++) + { (*iter)->define(*this, definePosition); } + definePositionForRow_ = definePosition; + } + + + template + void exchange_for_row(into_container const &ic) + { intosForRow_.exchange(ic); } + void exchange_for_row(into_type_ptr const & i) { intosForRow_.exchange(i); } void define_for_row(); template @@ -118,10 +160,7 @@ private: soci::details::statement_backend * backEnd_; - // The type is noncopyable. - statement_impl(statement_impl const &); - statement_impl& operator=(statement_impl const &); - + SOCI_NOT_COPYABLE(statement_impl) }; } // namespace details @@ -153,9 +192,14 @@ public: void alloc() { impl_->alloc(); } void bind(values & v) { impl_->bind(v); } - void exchange(details::into_type_ptr const & i); - void exchange(details::use_type_ptr const & u); + void exchange(details::into_type_ptr const & i) { impl_->exchange(i); } + template + void exchange(details::into_container const &ic) { impl_->exchange(ic); } + void exchange(details::use_type_ptr const & u) { impl_->exchange(u); } + template + void exchange(details::use_container const &uc) { impl_->exchange(uc); } void clean_up() { impl_->clean_up(); } + void bind_clean_up() { impl_->bind_clean_up(); } void prepare(std::string const & query, details::statement_type eType = details::st_repeatable_query) @@ -186,6 +230,13 @@ public: void describe() { impl_->describe(); } void set_row(row * r) { impl_->set_row(r); } + + template + void exchange_for_rowset(details::into_container const & ic) + { + impl_->exchange_for_rowset(ic); + } + void exchange_for_rowset(details::into_type_ptr const & i) { impl_->exchange_for_rowset(i); diff --git a/src/core/transaction.h b/include/soci/transaction.h similarity index 76% rename from src/core/transaction.h rename to include/soci/transaction.h index 3b5f8ee335..c55b865f8b 100644 --- a/src/core/transaction.h +++ b/include/soci/transaction.h @@ -8,8 +8,8 @@ #ifndef SOCI_TRANSACTION_H_INCLUDED #define SOCI_TRANSACTION_H_INCLUDED -#include "session.h" -#include "soci-config.h" +#include "soci/soci-platform.h" +#include "soci/session.h" namespace soci { @@ -28,9 +28,7 @@ private: bool handled_; session& sql_; - // Disable copying - transaction(transaction const& other); - transaction& operator=(transaction const& other); + SOCI_NOT_COPYABLE(transaction) }; } // namespace soci diff --git a/src/core/type-conversion-traits.h b/include/soci/type-conversion-traits.h similarity index 96% rename from src/core/type-conversion-traits.h rename to include/soci/type-conversion-traits.h index 8ebbb3bf7d..e334071eaf 100644 --- a/src/core/type-conversion-traits.h +++ b/include/soci/type-conversion-traits.h @@ -8,7 +8,7 @@ #ifndef SOCI_TYPE_CONVERSION_TRAITS_H_INCLUDED #define SOCI_TYPE_CONVERSION_TRAITS_H_INCLUDED -#include "soci-backend.h" +#include "soci/soci-backend.h" namespace soci { diff --git a/src/core/type-conversion.h b/include/soci/type-conversion.h similarity index 96% rename from src/core/type-conversion.h rename to include/soci/type-conversion.h index a20eeebe83..c5b208352f 100644 --- a/src/core/type-conversion.h +++ b/include/soci/type-conversion.h @@ -8,11 +8,10 @@ #ifndef SOCI_TYPE_CONVERSION_H_INCLUDED #define SOCI_TYPE_CONVERSION_H_INCLUDED -#include "type-conversion-traits.h" -#include "into-type.h" -#include "use-type.h" +#include "soci/type-conversion-traits.h" +#include "soci/into-type.h" +#include "soci/use-type.h" // std -#include #include #include #include @@ -48,16 +47,14 @@ public: , ownInd_() , ind_(ownInd_) { - assert(ownInd_ == ind_); } - + conversion_into_type(T & value, indicator & ind) : into_type(details::base_value_holder::val_, ind) , value_(value) , ownInd_(ind) // unused, just keep the pair of indicator(s) consistent , ind_(ind) { - assert(ownInd_ == ind_); } @@ -76,6 +73,8 @@ private: // in any case, ind_ refers to some valid indicator // and can be used by conversion routines indicator & ind_; + + SOCI_NOT_COPYABLE(conversion_into_type) }; // Automatically create use_type from a type_conversion @@ -95,12 +94,10 @@ public: , ind_(ownInd_) , readOnly_(false) { - assert(ownInd_ == ind_); - // TODO: likely to be removed (SHA: c166625a28f7c907318134f625ff5acea7d9a1f8) //convert_to_base(); } - + conversion_use_type(T const & value, std::string const & name = std::string()) : use_type(details::base_value_holder::val_, ownInd_, name) , value_(const_cast(value)) @@ -108,12 +105,10 @@ public: , ind_(ownInd_) , readOnly_(true) { - assert(ownInd_ == ind_); - // TODO: likely to be removed (SHA: c166625a28f7c907318134f625ff5acea7d9a1f8) //convert_to_base(); } - + conversion_use_type(T & value, indicator & ind, std::string const & name = std::string()) : use_type(details::base_value_holder::val_, ind, name) @@ -124,7 +119,7 @@ public: // TODO: likely to be removed (SHA: c166625a28f7c907318134f625ff5acea7d9a1f8) //convert_to_base(); } - + conversion_use_type(T const & value, indicator & ind, std::string const & name = std::string()) : use_type(details::base_value_holder::val_, ind, name) @@ -168,6 +163,8 @@ private: indicator & ind_; bool readOnly_; + + SOCI_NOT_COPYABLE(conversion_use_type) }; // this class is used to ensure correct order of construction @@ -200,7 +197,6 @@ public: , ownInd_() , ind_(ownInd_) { - assert(ownInd_ == ind_); } conversion_into_type(std::vector & value, std::vector & ind) @@ -247,6 +243,8 @@ private: // in any case, ind_ refers to some valid vector of indicators // and can be used by conversion routines std::vector & ind_; + + SOCI_NOT_COPYABLE(conversion_into_type) }; @@ -272,7 +270,6 @@ public: , ownInd_() , ind_(ownInd_) { - assert(ownInd_ == ind_); } conversion_use_type(std::vector & value, @@ -318,6 +315,8 @@ private: // in any case, ind_ refers to some valid vector of indicators // and can be used by conversion routines std::vector & ind_; + + SOCI_NOT_COPYABLE(conversion_use_type) }; template diff --git a/src/core/type-holder.h b/include/soci/type-holder.h similarity index 100% rename from src/core/type-holder.h rename to include/soci/type-holder.h diff --git a/src/core/type-ptr.h b/include/soci/type-ptr.h similarity index 100% rename from src/core/type-ptr.h rename to include/soci/type-ptr.h diff --git a/src/core/unsigned-types.h b/include/soci/unsigned-types.h similarity index 98% rename from src/core/unsigned-types.h rename to include/soci/unsigned-types.h index e9a02c64ab..0d496a4627 100644 --- a/src/core/unsigned-types.h +++ b/include/soci/unsigned-types.h @@ -8,7 +8,7 @@ #ifndef SOCI_UNSIGNED_TYPES_H_INCLUDED #define SOCI_UNSIGNED_TYPES_H_INCLUDED -#include "type-conversion-traits.h" +#include "soci/type-conversion-traits.h" #include namespace soci diff --git a/src/core/use-type.h b/include/soci/use-type.h similarity index 93% rename from src/core/use-type.h rename to include/soci/use-type.h index 6230cd9b16..8762cfbbd9 100644 --- a/src/core/use-type.h +++ b/include/soci/use-type.h @@ -8,11 +8,13 @@ #ifndef SOCI_USE_TYPE_H_INCLUDED #define SOCI_USE_TYPE_H_INCLUDED -#include "soci-backend.h" -#include "type-ptr.h" -#include "exchange-traits.h" +#include "soci/soci-platform.h" +#include "soci/soci-backend.h" +#include "soci/type-ptr.h" +#include "soci/exchange-traits.h" // std #include +#include #include #include @@ -28,6 +30,8 @@ public: virtual ~use_type_base() {} 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_use() = 0; virtual void post_use(bool gotData) = 0; virtual void clean_up() = 0; @@ -70,7 +74,8 @@ public: virtual ~standard_use_type(); virtual void bind(statement_impl & st, int & position); - std::string get_name() const { return name_; } + virtual std::string get_name() const { return name_; } + virtual void dump_value(std::ostream& os) const; virtual void * get_data() { return data_; } // conversion hook (from arbitrary user type to base type) @@ -120,6 +125,8 @@ public: 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(); @@ -146,17 +153,17 @@ public: : standard_use_type(&t, static_cast(exchange_traits::x_type), false, name) {} - + use_type(T const& t, std::string const& name = std::string()) : standard_use_type(const_cast(&t), static_cast(exchange_traits::x_type), true, name) {} - + use_type(T& t, indicator& ind, std::string const& name = std::string()) : standard_use_type(&t, static_cast(exchange_traits::x_type), ind, false, name) {} - + use_type(T const& t, indicator& ind, std::string const& name = std::string()) : standard_use_type(const_cast(&t), static_cast(exchange_traits::x_type), ind, false, name) @@ -171,18 +178,18 @@ public: : vector_use_type(&v, static_cast(exchange_traits::x_type), 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& 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 const& v, std::vector const& ind, std::string const& name = std::string()) : vector_use_type(const_cast *>(&v), diff --git a/include/soci/use.h b/include/soci/use.h new file mode 100644 index 0000000000..f97263723a --- /dev/null +++ b/include/soci/use.h @@ -0,0 +1,78 @@ +// +// Copyright (C) 2004-2008 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) +// + +#ifndef SOCI_USE_H_INCLUDED +#define SOCI_USE_H_INCLUDED + +#include "soci/use-type.h" +#include "soci/exchange-traits.h" +#include "soci/type-conversion.h" +#include "soci/soci-backend.h" + +namespace soci +{ + +namespace details +{ +template +struct use_container +{ + use_container(T &_t, Indicator &_ind, const std::string &_name) + : t(_t), ind(_ind), name(_name) {} + + T &t; + Indicator &ind; + const std::string &name; +private: + SOCI_NOT_ASSIGNABLE(use_container) +}; + +typedef void no_indicator; +template +struct use_container +{ + use_container(T &_t, const std::string &_name) + : t(_t), name(_name) {} + + T &t; + const std::string &name; +private: + SOCI_NOT_ASSIGNABLE(use_container) +}; + +} // namespace details + +template +details::use_container use(T &t, const std::string &name = std::string()) +{ return details::use_container(t, name); } + +template +details::use_container use(T const &t, const std::string &name = std::string()) +{ return details::use_container(t, name); } + +template +details::use_container use(T &t, indicator & ind, std::string const &name = std::string()) +{ return details::use_container(t, ind, name); } + +template +details::use_container use(T const &t, indicator & ind, std::string const &name = std::string()) +{ return details::use_container(t, ind, name); } + +// vector containers +template +details::use_container > + use(T &t, std::vector & ind, const std::string &name = std::string()) +{ return details::use_container >(t, ind, name); } + +template +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); } + +} // namespace soci + +#endif // SOCI_USE_H_INCLUDED diff --git a/src/core/values-exchange.h b/include/soci/values-exchange.h similarity index 76% rename from src/core/values-exchange.h rename to include/soci/values-exchange.h index b9d533a304..1d2fe5f6ee 100644 --- a/src/core/values-exchange.h +++ b/include/soci/values-exchange.h @@ -8,12 +8,13 @@ #ifndef SOCI_VALUES_EXCHANGE_H_INCLUDED #define SOCI_VALUES_EXCHANGE_H_INCLUDED -#include "values.h" -#include "into-type.h" -#include "use-type.h" -#include "row-exchange.h" +#include "soci/values.h" +#include "soci/into-type.h" +#include "soci/use-type.h" +#include "soci/row-exchange.h" // std #include +#include #include #include @@ -53,6 +54,32 @@ public: st.bind(v_); } + virtual std::string get_name() const + { + std::ostringstream oss; + + oss << "("; + + std::size_t const num_columns = v_.get_number_of_columns(); + for (std::size_t n = 0; n < num_columns; ++n) + { + if (n != 0) + oss << ", "; + + oss << v_.get_properties(n).get_name(); + } + + oss << ")"; + + return oss.str(); + } + + virtual void dump_value(std::ostream& os) const + { + // TODO: Dump all columns. + os << ""; + } + virtual void post_use(bool /*gotData*/) { v_.reset_get_counter(); @@ -71,6 +98,8 @@ public: private: values & v_; + + SOCI_NOT_COPYABLE(use_type) }; // this is not supposed to be used - no support for bulk ORM @@ -88,7 +117,7 @@ public: into_type(values & v) : into_type(v.get_row()), v_(v) {} - + into_type(values & v, indicator & ind) : into_type(v.get_row(), ind), v_(v) {} @@ -100,6 +129,8 @@ public: private: values & v_; + + SOCI_NOT_COPYABLE(into_type) }; // this is not supposed to be used - no support for bulk ORM diff --git a/src/core/values.h b/include/soci/values.h similarity index 98% rename from src/core/values.h rename to include/soci/values.h index d17cb33246..f2868574c8 100644 --- a/src/core/values.h +++ b/include/soci/values.h @@ -8,9 +8,9 @@ #ifndef SOCI_VALUES_H_INCLUDED #define SOCI_VALUES_H_INCLUDED -#include "statement.h" -#include "into-type.h" -#include "use-type.h" +#include "soci/statement.h" +#include "soci/into-type.h" +#include "soci/use-type.h" // std #include #include @@ -97,7 +97,7 @@ public: { return row_ != NULL ? row_->get(name) : get_from_uses(name); } - + template T get(std::string const & name, T const & nullValue) const { @@ -161,7 +161,7 @@ public: currentPos_ = 0; } } - + template void set(std::string const & name, T const & value, indicator indic = i_ok) { @@ -230,6 +230,11 @@ public: uppercaseColumnNames_ = forceToUpper; } + std::size_t get_number_of_columns() const + { + return row_ ? row_->size() : 0; + } + column_properties const& get_properties(std::size_t pos) const; column_properties const& get_properties(std::string const &name) const; @@ -312,7 +317,7 @@ private: return * row_; } - + // this is called by Statement::bind(values) void add_unused(details::use_type_base * u, indicator * i) { diff --git a/src/core/version.h b/include/soci/version.h similarity index 86% rename from src/core/version.h rename to include/soci/version.h index 10179047dd..2de32c7ad3 100644 --- a/src/core/version.h +++ b/include/soci/version.h @@ -10,6 +10,8 @@ #ifndef SOCI_VERSION_HPP #define SOCI_VERSION_HPP +// When updating the version here, don't forget to update it in CMakeLists.txt! + // // Caution, this is the only SOCI header that is guarenteed // to change with every SOCI release, including this header @@ -20,13 +22,13 @@ // SOCI_VERSION / 100 % 1000 is the minor version // SOCI_VERSION / 100000 is the major version -#define SOCI_VERSION 300203 +#define SOCI_VERSION 400000 // // SOCI_LIB_VERSION must be defined to be the same as SOCI_VERSION // but as a *string* in the form "x_y[_z]" where x is the major version // number, y is the minor version number, and z is the patch level if not 0. -#define SOCI_LIB_VERSION "3_2_3" +#define SOCI_LIB_VERSION "4_0_0" #endif // SOCI_VERSION_HPP diff --git a/src/languages/ada/postgresql_client.gpr b/languages/ada/postgresql_client.gpr similarity index 100% rename from src/languages/ada/postgresql_client.gpr rename to languages/ada/postgresql_client.gpr diff --git a/src/languages/ada/soci-mysql.ads b/languages/ada/soci-mysql.ads similarity index 100% rename from src/languages/ada/soci-mysql.ads rename to languages/ada/soci-mysql.ads diff --git a/src/languages/ada/soci-oracle.ads b/languages/ada/soci-oracle.ads similarity index 100% rename from src/languages/ada/soci-oracle.ads rename to languages/ada/soci-oracle.ads diff --git a/src/languages/ada/soci-postgresql.ads b/languages/ada/soci-postgresql.ads similarity index 100% rename from src/languages/ada/soci-postgresql.ads rename to languages/ada/soci-postgresql.ads diff --git a/src/languages/ada/soci.adb b/languages/ada/soci.adb similarity index 100% rename from src/languages/ada/soci.adb rename to languages/ada/soci.adb diff --git a/src/languages/ada/soci.ads b/languages/ada/soci.ads similarity index 100% rename from src/languages/ada/soci.ads rename to languages/ada/soci.ads diff --git a/src/languages/ada/soci_ada.gpr b/languages/ada/soci_ada.gpr similarity index 100% rename from src/languages/ada/soci_ada.gpr rename to languages/ada/soci_ada.gpr diff --git a/src/languages/ada/soci_core.gpr b/languages/ada/soci_core.gpr similarity index 100% rename from src/languages/ada/soci_core.gpr rename to languages/ada/soci_core.gpr diff --git a/src/languages/ada/soci_postgresql.gpr b/languages/ada/soci_postgresql.gpr similarity index 100% rename from src/languages/ada/soci_postgresql.gpr rename to languages/ada/soci_postgresql.gpr diff --git a/src/languages/ada/std_cpp.gpr b/languages/ada/std_cpp.gpr similarity index 100% rename from src/languages/ada/std_cpp.gpr rename to languages/ada/std_cpp.gpr diff --git a/src/languages/ada/test/postgresql_test.adb b/languages/ada/test/postgresql_test.adb similarity index 100% rename from src/languages/ada/test/postgresql_test.adb rename to languages/ada/test/postgresql_test.adb diff --git a/src/languages/ada/test/postgresql_test.gpr b/languages/ada/test/postgresql_test.gpr similarity index 100% rename from src/languages/ada/test/postgresql_test.gpr rename to languages/ada/test/postgresql_test.gpr diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 08810d066f..472ae864c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,104 +2,13 @@ # # This file is part of CMake configuration for SOCI library # -# Copyright (C) 2009-2013 Mateusz Loskot +# Copyright (C) 2013 Mateusz Loskot # 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) # ############################################################################### -# General settings -############################################################################### -cmake_minimum_required(VERSION 2.8.0 FATAL_ERROR) - -project(SOCI) - -############################################################################### -# SOCI CMake modules -############################################################################### - -# Path to additional CMake modules -set(CMAKE_MODULE_PATH ${SOCI_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) -set(CMAKE_MODULE_PATH ${SOCI_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) - -include(SociUtilities) - -colormsg(_HIBLUE_ "Configuring SOCI:") - -############################################################################### -# SOCI version information -############################################################################### -include(SociVersion) - -soci_version(MAJOR 3 MINOR 2 PATCH 2) - -############################################################################### -# Build features and variants -############################################################################### -include(SociSystemInfo) -include(SociConfig) - -boost_report_value(SOCI_PLATFORM_NAME) -boost_report_value(SOCI_COMPILER_NAME) - -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) - -# Put the libaries and binaries that get built into directories at the -# top of the build tree rather than in hard-to-find leaf -# directories. This simplifies manual testing and the use of the build -# tree rather than installed Boost libraries. -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -############################################################################### -# Find SOCI dependencies -############################################################################### - -set(SOCI_CORE_DEPENDENCIES) - -include(SociDependencies) - -#message("${SOCI_CORE_DEPENDENCIES}") - -############################################################################### -# Installation -############################################################################### - -if(APPLE OR CMAKE_SIZEOF_VOID_P EQUAL 4) - set(SOCI_LIBDIR "lib") -else() - set(SOCI_LIBDIR "lib64") -endif() - -set(BINDIR "bin" CACHE PATH "The directory to install binaries into.") -set(LIBDIR ${SOCI_LIBDIR} CACHE PATH "The directory to install libraries into.") -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 -############################################################################### -enable_testing() -# Configure for testing with dashboard submissions to CDash -#include(CTest) # disabled as unused - -# Define "make check" as alias for "make test" -add_custom_target(check COMMAND ctest) - -############################################################################### -# Build configured components -############################################################################### -include(SociBackend) - -include_directories(${SOCI_SOURCE_DIR}/core) +include_directories(${SOCI_SOURCE_DIR}/include/private) add_subdirectory(core) add_subdirectory(backends) diff --git a/src/README b/src/README deleted file mode 100644 index 3666448e48..0000000000 --- a/src/README +++ /dev/null @@ -1,35 +0,0 @@ -SOCI - The C++ Database Access Library. - -Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton, - David Courtney, Pawel Aleksander Fedorynski, - Rafal Bobrowski, Mateusz Loskot - -The homepage of the SOCI library is: -http://soci.sourceforge.net/ - ---- - -You should see the following files and directories here: - -- README - This file. -- LICENSE_1_0.txt - Full text of the Boost license. -- CHANGES - The description of changes. - -- contrib - Acknowledgements for contributions. - -- doc/ - Documentation. - -- build/ - Additional build scripts and configuration files - for various compilation systems. - -- src/ - The source code of the library. - -- src/core/ - The main part of the SOCI library. - -- src/backends/ - Directory containing implementations and tests for - all available backends. - - -Please see the doc/structure.html file for compilation instructions. - ---- diff --git a/src/TODO b/src/TODO deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/backends/CMakeLists.txt b/src/backends/CMakeLists.txt index 25837519c3..a2bfee21eb 100644 --- a/src/backends/CMakeLists.txt +++ b/src/backends/CMakeLists.txt @@ -8,7 +8,7 @@ # http://www.boost.org/LICENSE_1_0.txt) # ############################################################################### -colormsg(_HIBLUE_ "Configuring SOCI database backends:") +colormsg(_HIBLUE_ "Configuring SOCI backend libraries:") # First, we'll investigate what can be found from database engines foreach(dep ${SOCI_BACKENDS_DB_DEPENDENCIES}) diff --git a/src/backends/db2/CMakeLists.txt b/src/backends/db2/CMakeLists.txt index 66191cb79c..7913d7920f 100644 --- a/src/backends/db2/CMakeLists.txt +++ b/src/backends/db2/CMakeLists.txt @@ -10,9 +10,5 @@ ############################################################################### soci_backend(DB2 DEPENDS DB2 - HEADERS soci-db2.h - DESCRIPTION "SOCI backend for DB2 Universal Database" - AUTHORS "Denis Chapligin" + DESCRIPTION "SOCI backend for DB2" AUTHORS "Denis Chapligin" MAINTAINERS "Denis Chapligin") - -add_subdirectory(test) diff --git a/src/backends/db2/blob.cpp b/src/backends/db2/blob.cpp index a4453485a7..6c3fcb0d21 100644 --- a/src/backends/db2/blob.cpp +++ b/src/backends/db2/blob.cpp @@ -7,7 +7,7 @@ // #define SOCI_DB2_SOURCE -#include "soci-db2.h" +#include "soci/db2/soci-db2.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/db2/factory.cpp b/src/backends/db2/factory.cpp index a94bb1e5fd..03e12d6a20 100644 --- a/src/backends/db2/factory.cpp +++ b/src/backends/db2/factory.cpp @@ -7,8 +7,8 @@ // #define SOCI_DB2_SOURCE -#include "soci-db2.h" -#include +#include "soci/db2/soci-db2.h" +#include "soci/backend-loader.h" using namespace soci; using namespace soci::details; diff --git a/src/backends/db2/row-id.cpp b/src/backends/db2/row-id.cpp index 1833ffaf61..4e51503bdf 100644 --- a/src/backends/db2/row-id.cpp +++ b/src/backends/db2/row-id.cpp @@ -7,7 +7,7 @@ // #define SOCI_DB2_SOURCE -#include "soci-db2.h" +#include "soci/db2/soci-db2.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/db2/session.cpp b/src/backends/db2/session.cpp index 2c82213f05..52576a7c85 100644 --- a/src/backends/db2/session.cpp +++ b/src/backends/db2/session.cpp @@ -7,8 +7,8 @@ // #define SOCI_DB2_SOURCE -#include "soci-db2.h" -#include +#include "soci/db2/soci-db2.h" +#include "soci/connection-parameters.h" #ifdef _MSC_VER #pragma warning(disable:4355) @@ -74,7 +74,7 @@ void db2_session_backend::parseConnectString(std::string const & connectString) } if (!processingString.empty()) { parseKeyVal(processingString); - } + } } db2_session_backend::db2_session_backend( @@ -89,7 +89,7 @@ db2_session_backend::db2_session_backend( if (cliRC != SQL_SUCCESS) { throw db2_soci_error("Error while allocating the enironment handle",cliRC); } - + cliRC = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc); if (cliRC != SQL_SUCCESS) { std::string msg=db2_soci_error::sqlState("Error while allocating the connection handle",SQL_HANDLE_ENV,hEnv); diff --git a/src/backends/db2/standard-into-type.cpp b/src/backends/db2/standard-into-type.cpp index f6f3dd1746..97e6db6ea8 100644 --- a/src/backends/db2/standard-into-type.cpp +++ b/src/backends/db2/standard-into-type.cpp @@ -7,7 +7,9 @@ // #define SOCI_DB2_SOURCE -#include "soci-db2.h" +#include "soci/db2/soci-db2.h" +#include "soci-exchange-cast.h" +#include "soci-mktime.h" #include "common.h" #include @@ -37,7 +39,7 @@ void db2_standard_into_type_backend::define_by_pos( cType = 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 = statement_.column_size(this->position); + size = static_cast(statement_.column_size(this->position)); size = size > details::db2::cli_max_buffer ? details::db2::cli_max_buffer : size; size++; buf = new char[size]; @@ -127,33 +129,26 @@ void db2_standard_into_type_backend::post_fetch( // only std::string and std::tm need special handling if (type == x_char) { - char *c = static_cast(data); - *c = buf[0]; + exchange_type_cast(data) = buf[0]; } if (type == x_stdstring) { - std::string *s = static_cast(data); - *s = buf; - if (s->size() >= (details::db2::cli_max_buffer - 1)) + std::string& s = exchange_type_cast(data); + s = buf; + if (s.size() >= (details::db2::cli_max_buffer - 1)) { throw soci_error("Buffer size overflow; maybe got too large string"); } } else if (type == x_stdtm) { - std::tm *t = static_cast(data); + std::tm& t = exchange_type_cast(data); TIMESTAMP_STRUCT * ts = reinterpret_cast(buf); - t->tm_isdst = -1; - t->tm_year = ts->year - 1900; - t->tm_mon = ts->month - 1; - t->tm_mday = ts->day; - t->tm_hour = ts->hour; - t->tm_min = ts->minute; - t->tm_sec = ts->second; - // normalize and compute the remaining fields - std::mktime(t); + details::mktime_from_ymdhms(t, + ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second); } } } diff --git a/src/backends/db2/standard-use-type.cpp b/src/backends/db2/standard-use-type.cpp index f0b739c35f..ff3dfb968c 100644 --- a/src/backends/db2/standard-use-type.cpp +++ b/src/backends/db2/standard-use-type.cpp @@ -6,7 +6,9 @@ // http://www.boost.org/LICENSE_1_0.txt) #define SOCI_DB2_SOURCE -#include "soci-db2.h" +#include "soci/soci-platform.h" +#include "soci/db2/soci-db2.h" +#include "soci-exchange-cast.h" #include #include #include @@ -55,8 +57,7 @@ void *db2_standard_use_type_backend::prepare_for_bind( cType = SQL_C_CHAR; size = sizeof(char) + 1; buf = new char[size]; - char *c = static_cast(data); - buf[0] = *c; + buf[0] = exchange_type_cast(data); buf[1] = '\0'; ind = SQL_NTS; } @@ -65,12 +66,12 @@ void *db2_standard_use_type_backend::prepare_for_bind( { // TODO: No textual value is assigned here! - std::string* s = static_cast(data); + std::string const& s = exchange_type_cast(data); sqlType = SQL_LONGVARCHAR; cType = SQL_C_CHAR; - size = s->size() + 1; + size = static_cast(s.size()) + 1; buf = new char[size]; - strncpy(buf, s->c_str(), size); + strncpy(buf, s.c_str(), size); ind = SQL_NTS; } break; @@ -79,7 +80,7 @@ void *db2_standard_use_type_backend::prepare_for_bind( sqlType = SQL_TIMESTAMP; cType = SQL_C_TIMESTAMP; buf = new char[sizeof(TIMESTAMP_STRUCT)]; - std::tm *t = static_cast(data); + std::tm const& t = exchange_type_cast(data); data = buf; size = 19; // This number is not the size in bytes, but the number // of characters in the date if it was written out @@ -87,12 +88,12 @@ void *db2_standard_use_type_backend::prepare_for_bind( TIMESTAMP_STRUCT * ts = reinterpret_cast(buf); - 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->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; } break; @@ -135,8 +136,8 @@ void db2_standard_use_type_backend::bind_by_name( int position = -1; int count = 1; - for (std::vector::iterator it = statement_.names.begin(); - it != statement_.names.end(); ++it) + for (std::vector::iterator it = statement_.names_.begin(); + it != statement_.names_.end(); ++it) { if (*it == name) { diff --git a/src/backends/db2/statement.cpp b/src/backends/db2/statement.cpp index 5f57decdfc..1e15559b00 100644 --- a/src/backends/db2/statement.cpp +++ b/src/backends/db2/statement.cpp @@ -7,17 +7,12 @@ // #define SOCI_DB2_SOURCE -#include "soci-db2.h" +#include "soci/db2/soci-db2.h" #include -#ifdef _MSC_VER -#pragma warning(disable:4355) -#endif - using namespace soci; using namespace soci::details; - db2_statement_backend::db2_statement_backend(db2_session_backend &session) : session_(session),hasVectorUseElements(false),use_binding_method_(details::db2::BOUND_BY_NONE) { @@ -102,7 +97,7 @@ void db2_statement_backend::prepare(std::string const & query , } else // end of name { - names.push_back(name); + names_.push_back(name); name.clear(); std::ostringstream ss; ss << '?'; @@ -117,7 +112,7 @@ void db2_statement_backend::prepare(std::string const & query , if (state == in_name) { - names.push_back(name); + names_.push_back(name); std::ostringstream ss; ss << '?'; query_ += ss.str(); @@ -171,7 +166,7 @@ db2_statement_backend::fetch(int number ) numRowsFetched = 0; SQLSetStmtAttr(hStmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0); - SQLSetStmtAttr(hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)number, 0); + SQLSetStmtAttr(hStmt, SQL_ATTR_ROW_ARRAY_SIZE, db2::int_as_ptr(number), 0); SQLSetStmtAttr(hStmt, SQL_ATTR_ROWS_FETCHED_PTR, &numRowsFetched, 0); SQLRETURN cliRC = SQLFetch(hStmt); @@ -200,7 +195,7 @@ long long db2_statement_backend::get_affected_rows() } else if (rows == -1) { - throw soci_error("Error getting affected row count: statement did not perform an update, insert, delete, or merge"); + throw soci_error("Error getting affected row count: statement did not perform an update, insert, delete, or merge"); } return rows; @@ -211,6 +206,11 @@ int db2_statement_backend::get_number_of_rows() return numRowsFetched; } +std::string db2_statement_backend::get_parameter_name(int index) const +{ + return names_.at(index); +} + std::string db2_statement_backend::rewrite_for_procedure_call( std::string const &query) { @@ -278,7 +278,7 @@ SQLCHAR colNameBuffer[2048]; } } -std::size_t db2_statement_backend::column_size(int col) { +size_t db2_statement_backend::column_size(int col) { SQLCHAR colNameBuffer[2048]; SQLSMALLINT colNameBufferOverflow; SQLSMALLINT dataType; @@ -296,7 +296,7 @@ std::size_t db2_statement_backend::column_size(int col) { throw db2_soci_error(db2_soci_error::sqlState("Error while detecting column size",SQL_HANDLE_STMT,hStmt),cliRC); } - return colSize; + return colSize; } db2_standard_into_type_backend * db2_statement_backend::make_into_type_backend() diff --git a/src/backends/db2/test/test-db2.cpp b/src/backends/db2/test/test-db2.cpp deleted file mode 100644 index 471e336da6..0000000000 --- a/src/backends/db2/test/test-db2.cpp +++ /dev/null @@ -1,454 +0,0 @@ -// -// Copyright (C) 2011-2013 Denis Chapligin -// 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.h" -#include "soci-db2.h" -#include "common-tests.h" -#include -#include -#include -#include -#include - -using namespace soci; -using namespace soci::tests; - -std::string connectString; -backend_factory const &backEnd = *soci::factory_db2(); - - -// -// Support for soci Common Tests -// - -struct table_creator_one : public table_creator_base -{ - table_creator_one(session & sql) - : table_creator_base(sql) - { - sql << "CREATE TABLE SOCI_TEST(ID INTEGER, VAL SMALLINT, C CHAR, STR VARCHAR(20), SH SMALLINT, UL NUMERIC(20), D DOUBLE, " - "TM TIMESTAMP, I1 INTEGER, I2 INTEGER, I3 INTEGER, NAME VARCHAR(20))"; - } -}; - -struct table_creator_two : public table_creator_base -{ - table_creator_two(session & sql) - : table_creator_base(sql) - { - sql << "CREATE TABLE SOCI_TEST(NUM_FLOAT DOUBLE, NUM_INT INTEGER, NAME VARCHAR(20), SOMETIME TIMESTAMP, CHR CHAR)"; - } -}; - -struct table_creator_three : public table_creator_base -{ - table_creator_three(session & sql) - : table_creator_base(sql) - { - sql << "CREATE TABLE SOCI_TEST(NAME VARCHAR(100) NOT NULL, PHONE VARCHAR(15))"; - } -}; - -struct table_creator_for_get_affected_rows : table_creator_base -{ - table_creator_for_get_affected_rows(session & sql) - : table_creator_base(sql) - { - sql << "CREATE TABLE SOCI_TEST(VAL INTEGER)"; - } -}; - -class test_context :public test_context_base -{ -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(session & pr_s) const - { - pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; - return new table_creator_one(pr_s); - } - - table_creator_base* table_creator_2(session & pr_s) const - { - pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; - return new table_creator_two(pr_s); - } - - table_creator_base* table_creator_3(session & pr_s) const - { - pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; - return new table_creator_three(pr_s); - } - - table_creator_base* table_creator_4(session& s) const - { - return new table_creator_for_get_affected_rows(s); - } - - std::string to_date_time(std::string const & pi_datdt_string) const - { - return "to_date('" + pi_datdt_string + "', 'YYYY-MM-DD HH24:MI:SS')"; - } -}; - - -// -// Additional tests to exercise the DB2 backend -// - -void test1() -{ - { - session sql(backEnd, connectString); - - sql << "SELECT CURRENT TIMESTAMP FROM SYSIBM.SYSDUMMY1"; - sql << "SELECT " << 123 << " FROM SYSIBM.SYSDUMMY1"; - - std::string query = "CREATE TABLE DB2INST1.SOCI_TEST (ID BIGINT,DATA VARCHAR(8))"; - sql << query; - - { - const int i = 7; - sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(i,"id"); - int j = 0; - sql << "select id from db2inst1.SOCI_TEST where id=7", into(j); - assert(j == i); - } - - { - const long int li = 9; - sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(li,"id"); - long int lj = 0;; - sql << "select id from db2inst1.SOCI_TEST where id=9", into(lj); - assert(lj == li); - } - - { - const long long ll = 11; - sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(ll,"id"); - long long lj = 0; - sql << "select id from db2inst1.SOCI_TEST where id=11", into(lj); - assert(lj == ll); - } - - { - const int i = 13; - indicator i_ind = i_ok; - sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(i,i_ind,"id"); - int j = 0; - indicator j_ind = i_null; - sql << "select id from db2inst1.SOCI_TEST where id=13", into(j,j_ind); - assert(j == i); - assert(j_ind == i_ok); - } - - { - std::vector numbers(100); - for (int i = 0 ; i < 100 ; i++) - { - numbers[i] = i + 1000; - } - sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(numbers,"id"); - sql << "select id from db2inst1.SOCI_TEST where id >= 1000 and id < 2000 order by id", into(numbers); - for (int i = 0 ; i < 100 ; i++) - { - assert(numbers[i] == i + 1000); - } - } - - { - std::vector numbers(100); - std::vector inds(100); - for (int i = 0 ; i < 100 ; i++) - { - numbers[i] = i + 2000; - inds[i] = i_ok; - } - sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(numbers,inds,"id"); - for (int i = 0 ; i < 100 ; i++) - { - numbers[i] = 0; - inds[i] = i_null; - } - sql << "select id from db2inst1.SOCI_TEST where id >= 2000 and id < 3000 order by id", into(numbers,inds); - for (int i = 0 ; i < 100 ; i++) - { - assert(numbers[i] == i + 2000); - assert(inds[i] == i_ok); - } - } - - { - int i = 0; - statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST where id < 1000", into(i)); - st.execute(); - st.fetch(); - assert (i == 7); - st.fetch(); - assert (i == 9); - st.fetch(); - assert (i == 11); - st.fetch(); - assert (i == 13); - } - - { - int i = 0; - indicator i_ind = i_null; - std::string d; - indicator d_ind = i_ok; - statement st = (sql.prepare << "select id, data from db2inst1.SOCI_TEST where id = 13", into(i, i_ind), into(d, d_ind)); - st.execute(); - st.fetch(); - assert (i == 13); - assert (i_ind == i_ok); - assert (d_ind == i_null); - } - - { - std::vector numbers(100); - for (int i = 0 ; i < 100 ; i++) - { - numbers[i] = 0; - } - statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST where id >= 1000 order by id", into(numbers)); - st.execute(); - st.fetch(); - for (int i = 0 ; i < 100 ; i++) - { - assert(numbers[i] == i + 1000); - } - st.fetch(); - for (int i = 0 ; i < 100 ; i++) - { - assert(numbers[i] == i + 2000); - } - } - - { - std::vector numbers(100); - std::vector inds(100); - for (int i = 0 ; i < 100 ; i++) - { - numbers[i] = 0; - inds[i] = i_null; - } - statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST where id >= 1000 order by id", into(numbers, inds)); - st.execute(); - st.fetch(); - for (int i = 0 ; i < 100 ; i++) - { - assert(numbers[i] == i + 1000); - assert(inds[i] == i_ok); - } - st.fetch(); - for (int i = 0 ; i < 100 ; i++) - { - assert(numbers[i] == i + 2000); - assert(inds[i] == i_ok); - } - } - - { - // XXX: what is the purpose of this test?? what is the expected value? - int i = 0; - statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(i)); - } - - { - // XXX: what is the purpose of this test?? what is the expected value? - int i = 0; - indicator ind = i_ok; - statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(i, ind)); - } - - { - // XXX: what is the purpose of this test?? what is the expected value? - std::vector numbers(100); - statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(numbers)); - } - - { - // XXX: what is the purpose of this test?? what is the expected value? - std::vector numbers(100); - std::vector inds(100); - statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(numbers, inds)); - } - - sql<<"DROP TABLE DB2INST1.SOCI_TEST"; - - sql.commit(); - } - - std::cout << "test 1 passed" << std::endl; -} - -void test2() { - { - session sql(backEnd, connectString); - - std::string query = "CREATE TABLE DB2INST1.SOCI_TEST (ID BIGINT,DATA VARCHAR(8),DT TIMESTAMP)"; - sql << query; - - { - int i = 7; - std::string n("test"); - sql << "insert into db2inst1.SOCI_TEST (id,data) values (:id,:name)", use(i,"id"),use(n,"name"); - int j; - std::string m; - sql << "select id,data from db2inst1.SOCI_TEST where id=7", into(j),into(m); - assert (j == i); - assert (m == n); - } - - { - int i = 8; - sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(i,"id"); - int j; - std::string m; - indicator ind = i_ok; - sql << "select id,data from db2inst1.SOCI_TEST where id=8", into(j),into(m,ind); - assert(j == i); - assert(ind==i_null); - } - - { - std::tm dt; - sql << "select current timestamp from sysibm.sysdummy1",into(dt); - sql << "insert into db2inst1.SOCI_TEST (dt) values (:dt)",use(dt,"dt"); - std::tm dt2; - sql << "select dt from db2inst1.SOCI_TEST where dt is not null", into(dt2); - assert(dt2.tm_year == dt.tm_year && dt2.tm_mon == dt.tm_mon && dt2.tm_mday == dt.tm_mday && - dt2.tm_hour == dt.tm_hour && dt2.tm_min == dt.tm_min && dt2.tm_sec == dt.tm_sec); - } - - sql<<"DROP TABLE DB2INST1.SOCI_TEST"; - sql.commit(); - } - - std::cout << "test 2 passed" << std::endl; -} - -void test3() { - { - session sql(backEnd, connectString); - int i; - - std::string query = "CREATE TABLE DB2INST1.SOCI_TEST (ID BIGINT,DATA VARCHAR(8),DT TIMESTAMP)"; - sql << query; - - std::vector ids(100); - std::vector data(100); - std::vector dts(100); - for (int i = 0; i < 100; i++) - { - ids[i] = 1000000000LL + i; - data[i] = "test"; - dts[i].tm_year = 112; - dts[i].tm_mon = 7; - dts[i].tm_mday = 17; - dts[i].tm_hour = 0; - dts[i].tm_min = 0; - dts[i].tm_sec = i % 60; - } - - sql << "insert into db2inst1.SOCI_TEST (id, data, dt) values (:id, :data, :dt)", - use(ids, "id"), use(data,"data"), use(dts, "dt"); - - i = 0; - rowset rs = (sql.prepare<<"SELECT ID, DATA, DT FROM DB2INST1.SOCI_TEST"); - for (rowset::const_iterator it = rs.begin(); it != rs.end(); it++) - { - const row & r = *it; - const long long id = r.get(0); - const std::string data = r.get(1); - const std::tm dt = r.get(2); - - assert(id == 1000000000LL + i); - assert(data == "test"); - assert(dt.tm_year == 112); - assert(dt.tm_mon == 7); - assert(dt.tm_mday == 17); - assert(dt.tm_hour == 0); - assert(dt.tm_min == 0); - assert(dt.tm_sec == i % 60); - - i += 1; - } - - sql<<"DROP TABLE DB2INST1.SOCI_TEST"; - sql.commit(); - } - - std::cout << "test 3 passed" << std::endl; -} - - -int main(int argc, char** argv) -{ - -#ifdef _MSC_VER - // Redirect errors, unrecoverable problems, and assert() failures to STDERR, - // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. - // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); -#endif //_MSC_VER - - if (argc == 2) - { - connectString = argv[1]; - } - else - { - std::cout << "usage: " << argv[0] - << " connectstring\n" - << "example: " << argv[0] - << " \'DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off\'\n"; - std::exit(1); - } - - test_context tc(backEnd, connectString); - common_tests tests(tc); - tests.run(); - - try - { - std::cout<<"\nSOCI DB2 Tests:\n\n"; - - session sql(backEnd, connectString); - - try - { - // attempt to delete the test table from previous runs - sql << "DROP TABLE DB2INST1.SOCI_TEST"; - } - catch (soci_error const & e) - { - // if the table didn't exist, then proceed - } - - test1(); - test2(); - test3(); - // ... - - std::cout << "\nOK, all tests passed.\n\n"; - - return EXIT_SUCCESS; - } - catch (std::exception const & e) - { - std::cout << e.what() << '\n'; - } - - return EXIT_FAILURE; -} diff --git a/src/backends/db2/vector-into-type.cpp b/src/backends/db2/vector-into-type.cpp index e41f34e41e..262e52835b 100644 --- a/src/backends/db2/vector-into-type.cpp +++ b/src/backends/db2/vector-into-type.cpp @@ -7,7 +7,8 @@ // #define SOCI_DB2_SOURCE -#include "soci-db2.h" +#include "soci/db2/soci-db2.h" +#include "soci-mktime.h" #include #include #include @@ -209,20 +210,10 @@ void db2_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) std::size_t const vsize = v.size(); for (std::size_t i = 0; i != vsize; ++i) { - std::tm t; - TIMESTAMP_STRUCT * ts = reinterpret_cast(pos); - t.tm_isdst = -1; - t.tm_year = ts->year - 1900; - t.tm_mon = ts->month - 1; - t.tm_mday = ts->day; - t.tm_hour = ts->hour; - t.tm_min = ts->minute; - t.tm_sec = ts->second; - - // normalize and compute the remaining fields - std::mktime(&t); - v[i] = t; + details::mktime_from_ymdhms(v[i], + ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second); pos += colSize; } } diff --git a/src/backends/db2/vector-use-type.cpp b/src/backends/db2/vector-use-type.cpp index 05a810464d..e29dc63970 100644 --- a/src/backends/db2/vector-use-type.cpp +++ b/src/backends/db2/vector-use-type.cpp @@ -7,7 +7,8 @@ // #define SOCI_DB2_SOURCE -#include "soci-db2.h" +#include "soci/soci-platform.h" +#include "soci/db2/soci-db2.h" #include #include #include @@ -195,7 +196,7 @@ void db2_vector_use_type_backend::bind_helper(int &position, void *data, details prepare_for_bind(data, size, sqlType, cType); SQLINTEGER arraySize = (SQLINTEGER)indVec.size(); - SQLSetStmtAttr(statement_.hStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)arraySize, 0); + SQLSetStmtAttr(statement_.hStmt, SQL_ATTR_PARAMSET_SIZE, db2::int_as_ptr(arraySize), 0); SQLRETURN cliRC = SQLBindParameter(statement_.hStmt, static_cast(position++), SQL_PARAM_INPUT, cType, sqlType, size, 0, @@ -231,8 +232,8 @@ void db2_vector_use_type_backend::bind_by_name( } statement_.use_binding_method_ = details::db2::BOUND_BY_NAME; - for (std::vector::iterator it = statement_.names.begin(); - it != statement_.names.end(); ++it) + for (std::vector::iterator it = statement_.names_.begin(); + it != statement_.names_.end(); ++it) { if (*it == name) { diff --git a/src/backends/empty/CMakeLists.txt b/src/backends/empty/CMakeLists.txt index be3942a68a..b09185b58b 100644 --- a/src/backends/empty/CMakeLists.txt +++ b/src/backends/empty/CMakeLists.txt @@ -9,9 +9,6 @@ # ############################################################################### soci_backend(Empty - HEADERS soci-empty.h - DESCRIPTION "SOCI backend skeleton for development of new backends" + DESCRIPTION "SOCI backend skeleton for new backends development" AUTHORS "Maciej Sobczak, Stephen Hutton" MAINTAINERS "Maciej Sobczak") - -add_subdirectory(test) diff --git a/src/backends/empty/blob.cpp b/src/backends/empty/blob.cpp index fac33c801e..06639959e5 100644 --- a/src/backends/empty/blob.cpp +++ b/src/backends/empty/blob.cpp @@ -6,7 +6,7 @@ // #define SOCI_EMPTY_SOURCE -#include "soci-empty.h" +#include "soci/empty/soci-empty.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/empty/factory.cpp b/src/backends/empty/factory.cpp index ed744dcb30..f932623f35 100644 --- a/src/backends/empty/factory.cpp +++ b/src/backends/empty/factory.cpp @@ -6,8 +6,8 @@ // #define SOCI_EMPTY_SOURCE -#include "soci-empty.h" -#include +#include "soci/empty/soci-empty.h" +#include "soci/backend-loader.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/empty/row-id.cpp b/src/backends/empty/row-id.cpp index 35265e6c34..637c624fd7 100644 --- a/src/backends/empty/row-id.cpp +++ b/src/backends/empty/row-id.cpp @@ -6,7 +6,7 @@ // #define SOCI_EMPTY_SOURCE -#include "soci-empty.h" +#include "soci/empty/soci-empty.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/empty/session.cpp b/src/backends/empty/session.cpp index 8195473821..48521d5f61 100644 --- a/src/backends/empty/session.cpp +++ b/src/backends/empty/session.cpp @@ -6,7 +6,7 @@ // #define SOCI_EMPTY_SOURCE -#include "soci-empty.h" +#include "soci/empty/soci-empty.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/empty/standard-into-type.cpp b/src/backends/empty/standard-into-type.cpp index 677f2324ff..a907f11ee9 100644 --- a/src/backends/empty/standard-into-type.cpp +++ b/src/backends/empty/standard-into-type.cpp @@ -6,7 +6,7 @@ // #define SOCI_EMPTY_SOURCE -#include "soci-empty.h" +#include "soci/empty/soci-empty.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/empty/standard-use-type.cpp b/src/backends/empty/standard-use-type.cpp index 4a9786e459..cd93ff9053 100644 --- a/src/backends/empty/standard-use-type.cpp +++ b/src/backends/empty/standard-use-type.cpp @@ -6,7 +6,7 @@ // #define SOCI_EMPTY_SOURCE -#include "soci-empty.h" +#include "soci/empty/soci-empty.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/empty/statement.cpp b/src/backends/empty/statement.cpp index 702a7ab4b1..87f9fda9fc 100644 --- a/src/backends/empty/statement.cpp +++ b/src/backends/empty/statement.cpp @@ -6,7 +6,7 @@ // #define SOCI_EMPTY_SOURCE -#include "soci-empty.h" +#include "soci/empty/soci-empty.h" #ifdef _MSC_VER #pragma warning(disable:4355) @@ -63,6 +63,12 @@ int empty_statement_backend::get_number_of_rows() return 1; } +std::string empty_statement_backend::get_parameter_name(int /* index */) const +{ + // ... + return std::string(); +} + std::string empty_statement_backend::rewrite_for_procedure_call( std::string const &query) { diff --git a/src/backends/empty/test/.gitignore b/src/backends/empty/test/.gitignore deleted file mode 100644 index 8e2773204c..0000000000 --- a/src/backends/empty/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test_empty diff --git a/src/backends/empty/test/CMakeLists.txt b/src/backends/empty/test/CMakeLists.txt deleted file mode 100644 index bc1ddd963a..0000000000 --- a/src/backends/empty/test/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -############################################################################### -# -# This file is part of CMake configuration for SOCI library -# -# Copyright (C) 2010 Mateusz Loskot -# 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) -# -############################################################################### -soci_backend_test( - BACKEND Empty - SOURCE test-empty.cpp - CONNSTR "dummy") \ No newline at end of file diff --git a/src/backends/empty/test/Makefile.basic b/src/backends/empty/test/Makefile.basic deleted file mode 100644 index 5d049b0fe1..0000000000 --- a/src/backends/empty/test/Makefile.basic +++ /dev/null @@ -1,13 +0,0 @@ -COMPILER = g++ -CXXFLAGS = -Wall -pedantic -Wno-long-long -INCLUDEDIRS = -I.. -I../../../core -LIBDIRS = -L.. -L../../../core -LIBS = -lsoci_core -lsoci_empty -ldl - - -test-empty : test-empty.cpp - ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} - - -clean : - rm -f test-empty diff --git a/src/backends/empty/vector-into-type.cpp b/src/backends/empty/vector-into-type.cpp index 84830dd0e5..45ee98012f 100644 --- a/src/backends/empty/vector-into-type.cpp +++ b/src/backends/empty/vector-into-type.cpp @@ -6,11 +6,7 @@ // #define SOCI_EMPTY_SOURCE -#include "soci-empty.h" - -#ifdef _MSC_VER -#pragma warning(disable:4355) -#endif +#include "soci/empty/soci-empty.h" using namespace soci; using namespace soci::details; diff --git a/src/backends/empty/vector-use-type.cpp b/src/backends/empty/vector-use-type.cpp index 154d1da9ff..ab5e558512 100644 --- a/src/backends/empty/vector-use-type.cpp +++ b/src/backends/empty/vector-use-type.cpp @@ -6,7 +6,7 @@ // #define SOCI_EMPTY_SOURCE -#include "soci-empty.h" +#include "soci/empty/soci-empty.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/firebird/CMakeLists.txt b/src/backends/firebird/CMakeLists.txt index 7e34aaad7f..cbf702813e 100644 --- a/src/backends/firebird/CMakeLists.txt +++ b/src/backends/firebird/CMakeLists.txt @@ -11,9 +11,6 @@ soci_backend(Firebird DEPENDS Firebird - HEADERS soci-firebird.h common.h - DESCRIPTION "SOCI backend for Firebird database engine" + DESCRIPTION "SOCI backend for Firebird" AUTHORS "Rafał Bobrowski" MAINTAINERS "Viacheslav Naydenov") - -add_subdirectory(test) diff --git a/src/backends/firebird/Makefile.basic b/src/backends/firebird/Makefile.basic index 6258469d7c..5c0ffd15c7 100644 --- a/src/backends/firebird/Makefile.basic +++ b/src/backends/firebird/Makefile.basic @@ -1,7 +1,7 @@ # The following variable is specific to this backend and its correct # values might depend on your environment - feel free to set it accordingly. -FIREBIRDINCLUDEDIR = -I/usr/local/firebird/include +FIREBIRDINCLUDEDIR = -I/usr/local/firebird/include # The rest of the Makefile is indepentent of the target environment. @@ -24,9 +24,9 @@ libsoci_firebird.a : ${OBJECTS} ar rv $@ $? rm *.o -soci-firebird.o : soci-firebird.cpp +soci-firebird.o : soci-firebird.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - + blob.o : blob.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} diff --git a/src/backends/firebird/blob.cpp b/src/backends/firebird/blob.cpp index 11907a5182..2193e0239f 100644 --- a/src/backends/firebird/blob.cpp +++ b/src/backends/firebird/blob.cpp @@ -6,8 +6,8 @@ // #define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" -#include "error-firebird.h" +#include "soci/firebird/soci-firebird.h" +#include "firebird/error-firebird.h" using namespace soci; using namespace soci::details::firebird; @@ -139,8 +139,8 @@ void firebird_blob_backend::open() ISC_STATUS stat[20]; - if (isc_open_blob2(stat, &session_.dbhp_, &session_.trhp_, &bhp_, - &bid_, 0, NULL)) + if (isc_open_blob2(stat, &session_.dbhp_, session_.current_transaction(), + &bhp_, &bid_, 0, NULL)) { bhp_ = 0L; throw_iscerror(stat); @@ -238,7 +238,7 @@ void firebird_blob_backend::save() } // create new blob - if (isc_create_blob(stat, &session_.dbhp_, &session_.trhp_, + if (isc_create_blob(stat, &session_.dbhp_, session_.current_transaction(), &bhp_, &bid_)) { throw_iscerror(stat); diff --git a/src/backends/firebird/common.cpp b/src/backends/firebird/common.cpp index 263ea85982..a3e777f86d 100644 --- a/src/backends/firebird/common.cpp +++ b/src/backends/firebird/common.cpp @@ -5,8 +5,9 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "common.h" -#include +#include "soci/soci-platform.h" +#include "firebird/common.h" +#include "soci/soci-backend.h" #include // FireBird #include #include @@ -90,44 +91,49 @@ void tmDecode(short type, void * src, std::tm * dst) void setTextParam(char const * s, std::size_t size, char * buf_, XSQLVAR * var) { - //std::cerr << "setTextParam: var->sqltype=" << var->sqltype << std::endl; - short sz = 0; - if (size < static_cast(var->sqllen)) - { - sz = static_cast(size); - } - else - { - sz = var->sqllen; - } + int const sqltype = var->sqltype & ~1; - if ((var->sqltype & ~1) == SQL_VARYING) + if (sqltype == SQL_VARYING || sqltype == SQL_TEXT) { - std::memcpy(buf_, &sz, sizeof(short)); - std::memcpy(buf_ + sizeof(short), s, sz); - } - else if ((var->sqltype & ~1) == SQL_TEXT) - { - std::memcpy(buf_, s, sz); - if (sz < var->sqllen) + if (size > static_cast(var->sqllen)) { - std::memset(buf_+sz, ' ', var->sqllen - sz); + std::ostringstream msg; + msg << "Value \"" << s << "\" is too long (" + << size << " bytes) to be stored in column of size " + << var->sqllen << " bytes"; + throw soci_error(msg.str()); + } + + short const sz = static_cast(size); + + if (sqltype == SQL_VARYING) + { + std::memcpy(buf_, &sz, sizeof(short)); + std::memcpy(buf_ + sizeof(short), s, sz); + } + else // sqltype == SQL_TEXT + { + std::memcpy(buf_, s, sz); + if (sz < var->sqllen) + { + std::memset(buf_+sz, ' ', var->sqllen - sz); + } } } - else if ((var->sqltype & ~1) == SQL_SHORT) + else if (sqltype == SQL_SHORT) { parse_decimal(buf_, var, s); } - else if ((var->sqltype & ~1) == SQL_LONG) + else if (sqltype == SQL_LONG) { parse_decimal(buf_, var, s); } - else if ((var->sqltype & ~1) == SQL_INT64) + else if (sqltype == SQL_INT64) { parse_decimal(buf_, var, s); } - else if ((var->sqltype & ~1) == SQL_TIMESTAMP - || (var->sqltype & ~1) == SQL_TYPE_DATE) + else if (sqltype == SQL_TIMESTAMP + || sqltype == SQL_TYPE_DATE) { unsigned short year, month, day, hour, min, sec; if (std::sscanf(s, "%hu-%hu-%hu %hu:%hu:%hu", @@ -154,7 +160,7 @@ void setTextParam(char const * s, std::size_t size, char * buf_, std::memcpy(buf_, &t, sizeof(t)); tmEncode(var->sqltype, &t, buf_); } - else if ((var->sqltype & ~1) == SQL_TYPE_TIME) + else if (sqltype == SQL_TYPE_TIME) { unsigned short hour, min, sec; if (std::sscanf(s, "%hu:%hu:%hu", &hour, &min, &sec) != 3) diff --git a/src/backends/firebird/error-firebird.cpp b/src/backends/firebird/error-firebird.cpp index bf834ab325..5fa122565e 100644 --- a/src/backends/firebird/error-firebird.cpp +++ b/src/backends/firebird/error-firebird.cpp @@ -6,8 +6,8 @@ // #define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" -#include "error-firebird.h" +#include "soci/firebird/soci-firebird.h" +#include "firebird/error-firebird.h" #include #include diff --git a/src/backends/firebird/factory.cpp b/src/backends/firebird/factory.cpp index cea362b81d..a45106a7b8 100644 --- a/src/backends/firebird/factory.cpp +++ b/src/backends/firebird/factory.cpp @@ -6,8 +6,8 @@ // #define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" -#include +#include "soci/firebird/soci-firebird.h" +#include "soci/backend-loader.h" using namespace soci; diff --git a/src/backends/firebird/row-id.cpp b/src/backends/firebird/row-id.cpp deleted file mode 100644 index d791c9e284..0000000000 --- a/src/backends/firebird/row-id.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski -// 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) -// - -#define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" - -using namespace soci; - -firebird_rowid_backend::firebird_rowid_backend(firebird_session_backend & /* session */) -{ - // Unsupported in Firebird backend - throw soci_error("RowIDs are not supported"); -} - -firebird_rowid_backend::~firebird_rowid_backend() -{ -} diff --git a/src/backends/firebird/session.cpp b/src/backends/firebird/session.cpp index 8b353909c5..b83d7f5c9d 100644 --- a/src/backends/firebird/session.cpp +++ b/src/backends/firebird/session.cpp @@ -6,9 +6,9 @@ // #define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" -#include "error-firebird.h" -#include "session.h" +#include "soci/firebird/soci-firebird.h" +#include "firebird/error-firebird.h" +#include "soci/session.h" #include #include #include @@ -196,6 +196,20 @@ bool getISCConnectParameter(std::map const & m, std::s } } +void setDPBOption(std::string& dpb, int const option, std::string const & value) +{ + + if (dpb.empty()) + { + dpb.append(1, static_cast(isc_dpb_version1)); + } + + // now we are adding new option + dpb.append(1, static_cast(option)); + dpb.append(1, static_cast(value.size())); + dpb.append(value); +} + } // namespace anonymous firebird_session_backend::firebird_session_backend( @@ -210,24 +224,25 @@ firebird_session_backend::firebird_session_backend( std::string param; // preparing connection options + std::string dpb; if (getISCConnectParameter(params, "user", param)) { - setDPBOption(isc_dpb_user_name, param); + setDPBOption(dpb, isc_dpb_user_name, param); } if (getISCConnectParameter(params, "password", param)) { - setDPBOption(isc_dpb_password, param); + setDPBOption(dpb, isc_dpb_password, param); } if (getISCConnectParameter(params, "role", param)) { - setDPBOption(isc_dpb_sql_role_name, param); + setDPBOption(dpb, isc_dpb_sql_role_name, param); } if (getISCConnectParameter(params, "charset", param)) { - setDPBOption(isc_dpb_lc_ctype, param); + setDPBOption(dpb, isc_dpb_lc_ctype, param); } if (getISCConnectParameter(params, "service", param) == false) @@ -238,7 +253,7 @@ firebird_session_backend::firebird_session_backend( // connecting data base if (isc_attach_database(stat, static_cast(param.size()), const_cast(param.c_str()), &dbhp_, - static_cast(dpb_.size()), const_cast(dpb_.c_str()))) + static_cast(dpb.size()), const_cast(dpb.c_str()))) { throw_iscerror(stat); } @@ -247,16 +262,11 @@ firebird_session_backend::firebird_session_backend( { decimals_as_strings_ = param == "1" || param == "Y" || param == "y"; } - // starting transaction - begin(); } void firebird_session_backend::begin() { - // Transaction is always started in ctor, because Firebird can't work - // without active transaction. - // Transaction will be automatically commited in cleanUp method. if (trhp_ == 0) { ISC_STATUS stat[stat_size]; @@ -272,20 +282,6 @@ firebird_session_backend::~firebird_session_backend() cleanUp(); } -void firebird_session_backend::setDPBOption(int const option, std::string const & value) -{ - - if (dpb_.size() == 0) - { - dpb_.append(1, static_cast(isc_dpb_version1)); - } - - // now we are adding new option - dpb_.append(1, static_cast(option)); - dpb_.append(1, static_cast(value.size())); - dpb_.append(value); -} - void firebird_session_backend::commit() { ISC_STATUS stat[stat_size]; @@ -299,11 +295,6 @@ void firebird_session_backend::commit() trhp_ = 0; } - -#ifndef SOCI_FIREBIRD_NORESTARTTRANSACTION - begin(); -#endif - } void firebird_session_backend::rollback() @@ -319,11 +310,14 @@ void firebird_session_backend::rollback() trhp_ = 0; } +} -#ifndef SOCI_FIREBIRD_NORESTARTTRANSACTION +isc_tr_handle* firebird_session_backend::current_transaction() +{ + // It will do nothing if we're already inside a transaction. begin(); -#endif + return &trhp_; } void firebird_session_backend::cleanUp() @@ -364,9 +358,9 @@ firebird_statement_backend * firebird_session_backend::make_statement_backend() return new firebird_statement_backend(*this); } -firebird_rowid_backend * firebird_session_backend::make_rowid_backend() +details::rowid_backend* firebird_session_backend::make_rowid_backend() { - return new firebird_rowid_backend(*this); + throw soci_error("RowIDs are not supported"); } firebird_blob_backend * firebird_session_backend::make_blob_backend() diff --git a/src/backends/firebird/standard-into-type.cpp b/src/backends/firebird/standard-into-type.cpp index 98a4c7ce87..30e754dedf 100644 --- a/src/backends/firebird/standard-into-type.cpp +++ b/src/backends/firebird/standard-into-type.cpp @@ -6,9 +6,10 @@ // #define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" -#include "common.h" -#include +#include "soci/firebird/soci-firebird.h" +#include "soci-exchange-cast.h" +#include "firebird/common.h" +#include "soci/soci.h" using namespace soci; using namespace soci::details; @@ -70,46 +71,36 @@ void firebird_standard_into_type_backend::exchangeData() { // simple cases case x_char: - *reinterpret_cast(data_) = getTextParam(var)[0]; + exchange_type_cast(data_) = getTextParam(var)[0]; break; case x_short: - { - short t = from_isc(var); - *reinterpret_cast(data_) = t; - } + exchange_type_cast(data_) = from_isc(var); break; case x_integer: - { - int t = from_isc(var); - *reinterpret_cast(data_) = t; - } + exchange_type_cast(data_) = from_isc(var); break; case x_long_long: - { - long long t = from_isc(var); - *reinterpret_cast(data_) = t; - } + exchange_type_cast(data_) = from_isc(var); break; case x_double: - { - double t = from_isc(var); - *reinterpret_cast(data_) = t; - } + exchange_type_cast(data_) = from_isc(var); break; // cases that require adjustments and buffer management case x_stdstring: - *(reinterpret_cast(data_)) = getTextParam(var); + exchange_type_cast(data_) = getTextParam(var); break; case x_stdtm: - tmDecode(var->sqltype, - buf_, static_cast(data_)); + { + std::tm& t = exchange_type_cast(data_); + tmDecode(var->sqltype, buf_, &t); - // isc_decode_timestamp() used by tmDecode() incorrectly sets - // tm_isdst to 0 in the struct that it creates, see - // http://tracker.firebirdsql.org/browse/CORE-3877, work around it - // by pretending the DST is actually unknown. - static_cast(data_)->tm_isdst = -1; + // isc_decode_timestamp() used by tmDecode() incorrectly sets + // tm_isdst to 0 in the struct that it creates, see + // http://tracker.firebirdsql.org/browse/CORE-3877, work around it + // by pretending the DST is actually unknown. + t.tm_isdst = -1; + } break; // cases that require special handling @@ -140,7 +131,7 @@ void firebird_standard_into_type_backend::clean_up() delete [] buf_; buf_ = NULL; } - std::vector::iterator it = + std::vector::iterator it = std::find(statement_.intos_.begin(), statement_.intos_.end(), this); if (it != statement_.intos_.end()) statement_.intos_.erase(it); diff --git a/src/backends/firebird/standard-use-type.cpp b/src/backends/firebird/standard-use-type.cpp index 58a2fd80ad..5b216fa828 100644 --- a/src/backends/firebird/standard-use-type.cpp +++ b/src/backends/firebird/standard-use-type.cpp @@ -6,9 +6,10 @@ // #define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" -#include "common.h" -#include +#include "soci/firebird/soci-firebird.h" +#include "soci-exchange-cast.h" +#include "firebird/common.h" +#include "soci/soci.h" using namespace soci; using namespace soci::details; @@ -104,7 +105,7 @@ void firebird_standard_use_type_backend::exchangeData() switch (type_) { case x_char: - setTextParam(static_cast(data_), 1, buf_, var); + setTextParam(&exchange_type_cast(data_), 1, buf_, var); break; case x_short: to_isc(data_, var); @@ -121,13 +122,12 @@ void firebird_standard_use_type_backend::exchangeData() case x_stdstring: { - std::string *tmp = static_cast(data_); - setTextParam(tmp->c_str(), tmp->size(), buf_, var); + std::string const& tmp = exchange_type_cast(data_); + setTextParam(tmp.c_str(), tmp.size(), buf_, var); } break; case x_stdtm: - tmEncode(var->sqltype, - static_cast(data_), buf_); + tmEncode(var->sqltype, &exchange_type_cast(data_), buf_); break; // cases that require special handling @@ -175,7 +175,7 @@ void firebird_standard_use_type_backend::clean_up() delete [] buf_; buf_ = NULL; } - std::vector::iterator it = + std::vector::iterator it = std::find(statement_.uses_.begin(), statement_.uses_.end(), this); if (it != statement_.uses_.end()) statement_.uses_.erase(it); diff --git a/src/backends/firebird/statement.cpp b/src/backends/firebird/statement.cpp index e00d1461b9..f7ee8c947a 100644 --- a/src/backends/firebird/statement.cpp +++ b/src/backends/firebird/statement.cpp @@ -6,8 +6,8 @@ // #define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" -#include "error-firebird.h" +#include "soci/firebird/soci-firebird.h" +#include "firebird/error-firebird.h" #include #include #include @@ -18,11 +18,11 @@ using namespace soci::details::firebird; firebird_statement_backend::firebird_statement_backend(firebird_session_backend &session) : session_(session), stmtp_(0), sqldap_(NULL), sqlda2p_(NULL), - boundByName_(false), boundByPos_(false), rowsFetched_(0), endOfRowSet_(false), rowsAffectedBulk_(-1LL), + boundByName_(false), boundByPos_(false), rowsFetched_(0), endOfRowSet_(false), rowsAffectedBulk_(-1LL), intoType_(eStandard), useType_(eStandard), procedure_(false) {} -void firebird_statement_backend::prepareSQLDA(XSQLDA ** sqldap, int size) +void firebird_statement_backend::prepareSQLDA(XSQLDA ** sqldap, short size) { if (*sqldap != NULL) { @@ -53,13 +53,13 @@ void firebird_statement_backend::clean_up() ISC_STATUS stat[stat_size]; - if (stmtp_ != NULL) + if (stmtp_ != 0) { if (isc_dsql_free_statement(stat, &stmtp_, DSQL_drop)) { throw_iscerror(stat); } - stmtp_ = NULL; + stmtp_ = 0; } if (sqldap_ != NULL) @@ -165,7 +165,7 @@ namespace if (res_buffer[0] == isc_info_sql_stmt_type) { length = isc_vax_integer(res_buffer+1, 2); - stype = isc_vax_integer(res_buffer+3, length); + stype = isc_vax_integer(res_buffer+3, static_cast(length)); } else { @@ -226,7 +226,7 @@ void firebird_statement_backend::rewriteQuery( } // prepare temporary statement - if (isc_dsql_prepare(stat, &(session_.trhp_), &tmpStmtp, 0, + if (isc_dsql_prepare(stat, session_.current_transaction(), &tmpStmtp, 0, &tmpQuery[0], SQL_DIALECT_V6, sqldap_)) { throw_iscerror(stat); @@ -302,7 +302,7 @@ void firebird_statement_backend::prepare(std::string const & query, ISC_STATUS stat[stat_size]; // prepare real statement - if (isc_dsql_prepare(stat, &(session_.trhp_), &stmtp_, 0, + if (isc_dsql_prepare(stat, session_.current_transaction(), &stmtp_, 0, &queryBuffer[0], SQL_DIALECT_V6, sqldap_)) { throw_iscerror(stat); @@ -423,7 +423,7 @@ firebird_statement_backend::execute(int number) } // then execute query - if (isc_dsql_execute(stat, &session_.trhp_, &stmtp_, SQL_DIALECT_V6, t)) + if (isc_dsql_execute(stat, session_.current_transaction(), &stmtp_, SQL_DIALECT_V6, t)) { // preserve the number of rows affected so far. rowsAffectedBulk_ = rowsAffectedBulkTemp; @@ -442,7 +442,7 @@ firebird_statement_backend::execute(int number) else { // use elements aren't vectors - if (isc_dsql_execute(stat, &session_.trhp_, &stmtp_, SQL_DIALECT_V6, t)) + if (isc_dsql_execute(stat, session_.current_transaction(), &stmtp_, SQL_DIALECT_V6, t)) { throw_iscerror(stat); } @@ -492,7 +492,8 @@ firebird_statement_backend::fetch(int number) rowsFetched_ = 0; for (int i = 0; i < number; ++i) { - long fetch_stat = isc_dsql_fetch(stat, &stmtp_, SQL_DIALECT_V6, sqldap_); + ISC_STATUS const + fetch_stat = isc_dsql_fetch(stat, &stmtp_, SQL_DIALECT_V6, sqldap_); // there is more data to read if (fetch_stat == 0) @@ -610,7 +611,7 @@ long long firebird_statement_backend::get_affected_rows() int len = isc_vax_integer(p, 2); p += 2; - row_count += isc_vax_integer(p, len); + row_count += isc_vax_integer(p, static_cast(len)); p += len; } break; @@ -631,6 +632,19 @@ int firebird_statement_backend::get_number_of_rows() return rowsFetched_; } +std::string firebird_statement_backend::get_parameter_name(int index) const +{ + for (std::map::const_iterator i = names_.begin(); + i != names_.end(); + ++i) + { + if (i->second == index) + return i->first; + } + + return std::string(); +} + std::string firebird_statement_backend::rewrite_for_procedure_call( std::string const &query) { diff --git a/src/backends/firebird/test/Makefile.basic b/src/backends/firebird/test/Makefile.basic deleted file mode 100644 index b2313ad07f..0000000000 --- a/src/backends/firebird/test/Makefile.basic +++ /dev/null @@ -1,22 +0,0 @@ -# The following variable is specific to this backend and its correct -# values might depend on your environment - feel free to set it accordingly. - -FIREBIRDINCLUDEDIR = -I/usr/local/firebird/include -FIREBIRDLIBDIR = -L/usr/local/firebird/lib -FIREBIRDLIBS = -lfbclient -lpthread - -# The rest of the Makefile is indepentent of the target environment. - -COMPILER = g++ -CXXFLAGS = -Wall -pedantic -Wno-long-long -INCLUDEDIRS = -I.. -I../../../core ${FIREBIRDINCLUDEDIR} -LIBDIRS = -L.. -L../../../core ${FIREBIRDLIBDIR} -LIBS = -lsoci_core -lsoci_firebird -ldl ${FIREBIRDLIBS} - - -test-firebird : test-firebird.cpp - ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} - - -clean : - rm -f *.o test-firebird diff --git a/src/backends/firebird/vector-into-type.cpp b/src/backends/firebird/vector-into-type.cpp index b7233a96f7..2de719ab9d 100644 --- a/src/backends/firebird/vector-into-type.cpp +++ b/src/backends/firebird/vector-into-type.cpp @@ -6,8 +6,8 @@ // #define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" -#include "common.h" +#include "soci/firebird/soci-firebird.h" +#include "firebird/common.h" using namespace soci; using namespace soci::details; @@ -201,7 +201,7 @@ void firebird_vector_into_type_backend::clean_up() delete [] buf_; buf_ = NULL; } - std::vector::iterator it = + std::vector::iterator it = std::find(statement_.intos_.begin(), statement_.intos_.end(), this); if (it != statement_.intos_.end()) statement_.intos_.erase(it); diff --git a/src/backends/firebird/vector-use-type.cpp b/src/backends/firebird/vector-use-type.cpp index c8cd4f6447..392c0e5d67 100644 --- a/src/backends/firebird/vector-use-type.cpp +++ b/src/backends/firebird/vector-use-type.cpp @@ -6,8 +6,8 @@ // #define SOCI_FIREBIRD_SOURCE -#include "soci-firebird.h" -#include "common.h" +#include "soci/firebird/soci-firebird.h" +#include "firebird/common.h" using namespace soci; using namespace soci::details; @@ -200,7 +200,7 @@ void firebird_vector_use_type_backend::clean_up() delete [] buf_; buf_ = NULL; } - std::vector::iterator it = + std::vector::iterator it = std::find(statement_.uses_.begin(), statement_.uses_.end(), this); if (it != statement_.uses_.end()) statement_.uses_.erase(it); diff --git a/src/backends/mysql/CMakeLists.txt b/src/backends/mysql/CMakeLists.txt index 54cbb78843..b2dcc9ceb3 100644 --- a/src/backends/mysql/CMakeLists.txt +++ b/src/backends/mysql/CMakeLists.txt @@ -10,9 +10,6 @@ ############################################################################### soci_backend(MySQL DEPENDS MySQL - HEADERS soci-mysql.h common.h - DESCRIPTION "SOCI backend for MySQL database engine" + DESCRIPTION "SOCI backend for MySQL" AUTHORS "Pawel Aleksander Fedorynski" MAINTAINERS "Pawel Aleksander Fedorynski") - -add_subdirectory(test) diff --git a/src/backends/mysql/blob.cpp b/src/backends/mysql/blob.cpp index bd440c6a7a..127e78a0cd 100644 --- a/src/backends/mysql/blob.cpp +++ b/src/backends/mysql/blob.cpp @@ -7,7 +7,7 @@ // #define SOCI_MYSQL_SOURCE -#include "soci-mysql.h" +#include "soci/mysql/soci-mysql.h" #include #ifdef _MSC_VER diff --git a/src/backends/mysql/common.cpp b/src/backends/mysql/common.cpp index 8721f31fc2..4c1ae7dea7 100644 --- a/src/backends/mysql/common.cpp +++ b/src/backends/mysql/common.cpp @@ -6,7 +6,8 @@ // #include "common.h" -#include "soci-backend.h" +#include "soci/soci-backend.h" +#include "soci-mktime.h" #include #include #include @@ -64,22 +65,14 @@ void soci::details::mysql::parse_std_tm(char const *buf, std::tm &t) second = parse10(p1, p2, errMsg); } - t.tm_isdst = -1; - t.tm_year = year - 1900; - t.tm_mon = month - 1; - t.tm_mday = day; - t.tm_hour = hour; - t.tm_min = minute; - t.tm_sec = second; - - std::mktime(&t); + details::mktime_from_ymdhms(t, year, month, day, hour, minute, second); } -char * soci::details::mysql::quote(MYSQL * conn, const char *s, int len) +char * soci::details::mysql::quote(MYSQL * conn, const char *s, size_t len) { char *retv = new char[2 * len + 3]; retv[0] = '\''; - int len_esc = mysql_real_escape_string(conn, retv + 1, s, len); + int len_esc = mysql_real_escape_string(conn, retv + 1, s, static_cast(len)); retv[len_esc + 1] = '\''; retv[len_esc + 2] = '\0'; diff --git a/src/backends/mysql/common.h b/src/backends/mysql/common.h index 989bd6ff83..eae0d92743 100644 --- a/src/backends/mysql/common.h +++ b/src/backends/mysql/common.h @@ -8,10 +8,13 @@ #ifndef SOCI_MYSQL_COMMON_H_INCLUDED #define SOCI_MYSQL_COMMON_H_INCLUDED -#include "soci-mysql.h" +#include "soci/mysql/soci-mysql.h" +#include "soci-cstrtod.h" +#include "soci-compiler.h" // std #include #include +#include #include #include @@ -39,7 +42,13 @@ template bool is_infinity_or_nan(T x) { T y = x - x; + + // We really need exact floating point comparison here. + GCC_WARNING_SUPPRESS(float-equal) + return (y != y); + + GCC_WARNING_RESTORE(float-equal) } template @@ -51,13 +60,21 @@ void parse_num(char const *buf, T &x) { throw soci_error("Cannot convert data."); } +} + +inline +void parse_num(char const *buf, double &x) +{ + x = cstring_to_double(buf); + if (is_infinity_or_nan(x)) { - throw soci_error("Cannot convert data."); + throw soci_error(std::string("Cannot convert data: string \"") + buf + + "\" is not a finite number."); } } // helper for escaping strings -char * quote(MYSQL * conn, const char *s, int len); +char * quote(MYSQL * conn, const char *s, size_t len); // helper for vector operations template diff --git a/src/backends/mysql/factory.cpp b/src/backends/mysql/factory.cpp index 2940f0ab14..ee41340237 100644 --- a/src/backends/mysql/factory.cpp +++ b/src/backends/mysql/factory.cpp @@ -7,8 +7,8 @@ // #define SOCI_MYSQL_SOURCE -#include "soci-mysql.h" -#include +#include "soci/mysql/soci-mysql.h" +#include "soci/backend-loader.h" #include #ifdef _MSC_VER diff --git a/src/backends/mysql/row-id.cpp b/src/backends/mysql/row-id.cpp index 14177d0f94..2121327202 100644 --- a/src/backends/mysql/row-id.cpp +++ b/src/backends/mysql/row-id.cpp @@ -7,7 +7,7 @@ // #define SOCI_MYSQL_SOURCE -#include "soci-mysql.h" +#include "soci/mysql/soci-mysql.h" #ifdef _MSC_VER #pragma warning(push) diff --git a/src/backends/mysql/session.cpp b/src/backends/mysql/session.cpp index 20b70bca66..e503c80185 100644 --- a/src/backends/mysql/session.cpp +++ b/src/backends/mysql/session.cpp @@ -7,8 +7,8 @@ // #define SOCI_MYSQL_SOURCE -#include "soci-mysql.h" -#include +#include "soci/mysql/soci-mysql.h" +#include "soci/connection-parameters.h" // std #include #include @@ -199,7 +199,7 @@ void parse_connect_string(const string & connectString, throw soci_error(err); } *port = std::atoi(val.c_str()); - if (port < 0) + if (*port < 0) { throw soci_error(err); } @@ -272,6 +272,18 @@ void parse_connect_string(const string & connectString, } // namespace anonymous + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wuninitialized" +#endif + +#if defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ > 6) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + + mysql_session_backend::mysql_session_backend( connection_parameters const & parameters) { @@ -320,7 +332,11 @@ mysql_session_backend::mysql_session_backend( db_p ? db.c_str() : NULL, port_p ? port : 0, unix_socket_p ? unix_socket.c_str() : NULL, +#ifdef CLIENT_MULTI_RESULTS CLIENT_FOUND_ROWS | CLIENT_MULTI_RESULTS) == NULL) +#else + CLIENT_FOUND_ROWS) == NULL) +#endif { string errMsg = mysql_error(conn_); unsigned int errNum = mysql_errno(conn_); @@ -329,6 +345,16 @@ mysql_session_backend::mysql_session_backend( } } +#if defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ > 6) +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + + mysql_session_backend::~mysql_session_backend() { clean_up(); @@ -364,6 +390,14 @@ void mysql_session_backend::rollback() hard_exec(conn_, "ROLLBACK"); } +bool mysql_session_backend::get_last_insert_id( + session & /* s */, std::string const & /* table */, long & value) +{ + value = static_cast(mysql_insert_id(conn_)); + + return true; +} + void mysql_session_backend::clean_up() { if (conn_ != NULL) diff --git a/src/backends/mysql/standard-into-type.cpp b/src/backends/mysql/standard-into-type.cpp index 835c35982f..9984e0141f 100644 --- a/src/backends/mysql/standard-into-type.cpp +++ b/src/backends/mysql/standard-into-type.cpp @@ -7,11 +7,11 @@ // #define SOCI_MYSQL_SOURCE -#include "soci-mysql.h" -#include +#include "soci/mysql/soci-mysql.h" +#include "soci/soci-platform.h" #include "common.h" +#include "soci-exchange-cast.h" // std -#include #include #include #include @@ -49,7 +49,7 @@ void mysql_standard_into_type_backend::post_fetch( // no need to do anything (fetch() will return false) return; } - + if (gotData) { int pos = position_ - 1; @@ -78,56 +78,34 @@ void mysql_standard_into_type_backend::post_fetch( switch (type_) { case x_char: - { - char *dest = static_cast(data_); - *dest = *buf; - } + exchange_type_cast(data_) = *buf; break; case x_stdstring: { - std::string *dest = static_cast(data_); + std::string& dest = exchange_type_cast(data_); unsigned long * lengths = mysql_fetch_lengths(statement_.result_); - dest->assign(buf, lengths[pos]); + dest.assign(buf, lengths[pos]); } break; case x_short: - { - short *dest = static_cast(data_); - parse_num(buf, *dest); - } + parse_num(buf, exchange_type_cast(data_)); break; case x_integer: - { - int *dest = static_cast(data_); - parse_num(buf, *dest); - } + parse_num(buf, exchange_type_cast(data_)); break; case x_long_long: - { - long long *dest = static_cast(data_); - parse_num(buf, *dest); - } + parse_num(buf, exchange_type_cast(data_)); break; case x_unsigned_long_long: - { - unsigned long long *dest = - static_cast(data_); - parse_num(buf, *dest); - } + parse_num(buf, exchange_type_cast(data_)); break; case x_double: - { - double *dest = static_cast(data_); - parse_num(buf, *dest); - } + parse_num(buf, exchange_type_cast(data_)); break; case x_stdtm: - { - // attempt to parse the string and convert to std::tm - std::tm *dest = static_cast(data_); - parse_std_tm(buf, *dest); - } + // attempt to parse the string and convert to std::tm + parse_std_tm(buf, exchange_type_cast(data_)); break; default: throw soci_error("Into element used with non-supported type."); diff --git a/src/backends/mysql/standard-use-type.cpp b/src/backends/mysql/standard-use-type.cpp index 04eb32a341..608339a847 100644 --- a/src/backends/mysql/standard-use-type.cpp +++ b/src/backends/mysql/standard-use-type.cpp @@ -7,19 +7,17 @@ // #define SOCI_MYSQL_SOURCE -#include "soci-mysql.h" +#include "soci/mysql/soci-mysql.h" #include "common.h" -#include +#include "soci/soci-platform.h" +#include "soci-dtocstr.h" +#include "soci-exchange-cast.h" // std #include #include #include #include -#ifdef _MSC_VER -#pragma warning(disable:4355) -#endif - using namespace soci; using namespace soci::details; using namespace soci::details::mysql; @@ -55,15 +53,15 @@ void mysql_standard_use_type_backend::pre_use(indicator const *ind) { case x_char: { - char buf[] = { *static_cast(data_), '\0' }; + char buf[] = { exchange_type_cast(data_), '\0' }; buf_ = quote(statement_.session_.conn_, buf, 1); } break; case x_stdstring: { - std::string *s = static_cast(data_); + std::string const& s = exchange_type_cast(data_); buf_ = quote(statement_.session_.conn_, - s->c_str(), s->size()); + s.c_str(), s.size()); } break; case x_short: @@ -72,7 +70,7 @@ void mysql_standard_use_type_backend::pre_use(indicator const *ind) = std::numeric_limits::digits10 + 3; buf_ = new char[bufSize]; snprintf(buf_, bufSize, "%d", - static_cast(*static_cast(data_))); + static_cast(exchange_type_cast(data_))); } break; case x_integer: @@ -80,7 +78,7 @@ void mysql_standard_use_type_backend::pre_use(indicator const *ind) std::size_t const bufSize = std::numeric_limits::digits10 + 3; buf_ = new char[bufSize]; - snprintf(buf_, bufSize, "%d", *static_cast(data_)); + snprintf(buf_, bufSize, "%d", exchange_type_cast(data_)); } break; case x_long_long: @@ -88,7 +86,7 @@ void mysql_standard_use_type_backend::pre_use(indicator const *ind) std::size_t const bufSize = std::numeric_limits::digits10 + 3; buf_ = new char[bufSize]; - snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "d", *static_cast(data_)); + snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "d", exchange_type_cast(data_)); } break; case x_unsigned_long_long: @@ -97,23 +95,23 @@ void mysql_standard_use_type_backend::pre_use(indicator const *ind) = std::numeric_limits::digits10 + 3; buf_ = new char[bufSize]; snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "u", - *static_cast(data_)); + exchange_type_cast(data_)); } break; case x_double: { - if (is_infinity_or_nan(*static_cast(data_))) { + double const d = exchange_type_cast(data_); + if (is_infinity_or_nan(d)) { throw soci_error( "Use element used with infinity or NaN, which are " "not supported by the MySQL server."); } - std::size_t const bufSize = 100; - buf_ = new char[bufSize]; + std::string const s = double_to_cstring(d); - snprintf(buf_, bufSize, "%.20g", - *static_cast(data_)); + buf_ = new char[s.size() + 1]; + std::strcpy(buf_, s.c_str()); } break; case x_stdtm: @@ -121,11 +119,11 @@ void mysql_standard_use_type_backend::pre_use(indicator const *ind) std::size_t const bufSize = 22; buf_ = new char[bufSize]; - std::tm *t = static_cast(data_); + std::tm const& t = exchange_type_cast(data_); snprintf(buf_, bufSize, "\'%d-%02d-%02d %02d:%02d:%02d\'", - t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, - t->tm_hour, t->tm_min, t->tm_sec); + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, + t.tm_hour, t.tm_min, t.tm_sec); } break; default: diff --git a/src/backends/mysql/statement.cpp b/src/backends/mysql/statement.cpp index f686160b5a..0d0eb843ff 100644 --- a/src/backends/mysql/statement.cpp +++ b/src/backends/mysql/statement.cpp @@ -7,14 +7,9 @@ // #define SOCI_MYSQL_SOURCE -#include "soci-mysql.h" +#include "soci/mysql/soci-mysql.h" #include #include -//#include - -#ifdef _MSC_VER -#pragma warning(disable:4355) -#endif using namespace soci; using namespace soci::details; @@ -23,7 +18,7 @@ using std::string; mysql_statement_backend::mysql_statement_backend( mysql_session_backend &session) - : session_(session), result_(NULL), + : session_(session), result_(NULL), rowsAffectedBulk_(-1LL), justDescribed_(false), hasIntoElements_(false), hasVectorIntoElements_(false), hasUseElements_(false), hasVectorUseElements_(false) @@ -37,7 +32,7 @@ void mysql_statement_backend::alloc() void mysql_statement_backend::clean_up() { - // 'reset' the value for a + // 'reset' the value for a // potential new execution. rowsAffectedBulk_ = -1; @@ -145,7 +140,7 @@ mysql_statement_backend::execute(int number) if (justDescribed_ == false) { clean_up(); - + if (number > 1 && hasIntoElements_) { throw soci_error( @@ -158,7 +153,7 @@ mysql_statement_backend::execute(int number) { numberOfExecutions = hasUseElements_ ? 1 : number; } - + std::string query; if (not useByPosBuffers_.empty() or not useByNameBuffers_.empty()) { @@ -237,7 +232,7 @@ mysql_statement_backend::execute(int number) // bulk operation //std::cerr << "bulk operation:\n" << query << std::endl; if (0 != mysql_real_query(session_.conn_, query.c_str(), - query.size())) + static_cast(query.size()))) { // preserve the number of rows affected so far. rowsAffectedBulk_ = rowsAffectedBulkTemp; @@ -270,7 +265,7 @@ mysql_statement_backend::execute(int number) //std::cerr << query << std::endl; if (0 != mysql_real_query(session_.conn_, query.c_str(), - query.size())) + static_cast(query.size()))) { throw mysql_soci_error(mysql_error(session_.conn_), mysql_errno(session_.conn_)); @@ -338,7 +333,7 @@ mysql_statement_backend::fetch(int number) // 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"). - + // forward the "cursor" from the last fetch currentRow_ += rowsToConsume_; @@ -380,6 +375,11 @@ int mysql_statement_backend::get_number_of_rows() return numberOfRows_ - currentRow_; } +std::string mysql_statement_backend::get_parameter_name(int index) const +{ + return names_.at(index); +} + std::string mysql_statement_backend::rewrite_for_procedure_call( std::string const &query) { diff --git a/src/backends/mysql/test/.gitignore b/src/backends/mysql/test/.gitignore deleted file mode 100644 index 4a78f40aeb..0000000000 --- a/src/backends/mysql/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test_mysql diff --git a/src/backends/mysql/test/Makefile.basic b/src/backends/mysql/test/Makefile.basic deleted file mode 100644 index 0792406b2e..0000000000 --- a/src/backends/mysql/test/Makefile.basic +++ /dev/null @@ -1,22 +0,0 @@ -# The following variables are specific to this backend and their correct -# values might depend on your environment - feel free to set it accordingly. - -MYSQLLIBDIR = -L/usr/lib/mysql -MYSQLLIBS = -lmysqlclient -lz -MYSQLINCLUDEDIR = -I/usr/include/mysql - -# The rest of the Makefile is independent of the target environment. - -COMPILER = g++ -CXXFLAGS = -Wall -pedantic -Wno-long-long -INCLUDEDIRS = -I.. -I../../../core ${MYSQLINCLUDEDIR} -LIBDIRS = -L.. -L../../../core ${MYSQLLIBDIR} -LIBS = -lsoci_core -lsoci_mysql -ldl ${MYSQLLIBS} - - -test-mysql : test-mysql.cpp - ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} - - -clean : - rm -f test-mysql diff --git a/src/backends/mysql/vector-into-type.cpp b/src/backends/mysql/vector-into-type.cpp index 93412f150c..09c86be489 100644 --- a/src/backends/mysql/vector-into-type.cpp +++ b/src/backends/mysql/vector-into-type.cpp @@ -7,21 +7,16 @@ // #define SOCI_MYSQL_SOURCE -#include "soci-mysql.h" +#include "soci/mysql/soci-mysql.h" #include "common.h" -#include +#include "soci/soci-platform.h" #include #include -#ifdef _MSC_VER -#pragma warning(disable:4355) -#endif - using namespace soci; using namespace soci::details; using namespace soci::details::mysql; - void mysql_vector_into_type_backend::define_by_pos( int &position, void *data, exchange_type type) { @@ -79,7 +74,7 @@ void mysql_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) } ind[i] = i_null; - + // no need to convert data if it is null, go to next row continue; } diff --git a/src/backends/mysql/vector-use-type.cpp b/src/backends/mysql/vector-use-type.cpp index 4a4935ad98..86c59c5dff 100644 --- a/src/backends/mysql/vector-use-type.cpp +++ b/src/backends/mysql/vector-use-type.cpp @@ -7,9 +7,10 @@ // #define SOCI_MYSQL_SOURCE -#include "soci-mysql.h" +#include "soci/mysql/soci-mysql.h" #include "common.h" -#include +#include "soci/soci-platform.h" +#include "soci-dtocstr.h" // std #include #include @@ -21,10 +22,6 @@ #include #include -#ifdef _MSC_VER -#pragma warning(disable:4355) -#endif - using namespace soci; using namespace soci::details; using namespace soci::details::mysql; @@ -144,10 +141,10 @@ void mysql_vector_use_type_backend::pre_use(indicator const *ind) "not supported by the MySQL server."); } - std::size_t const bufSize = 100; - buf = new char[bufSize]; + std::string const s = double_to_cstring(v[i]); - snprintf(buf, bufSize, "%.20g", v[i]); + buf = new char[s.size() + 1]; + std::strcpy(buf, s.c_str()); } break; case x_stdtm: diff --git a/src/backends/odbc/CMakeLists.txt b/src/backends/odbc/CMakeLists.txt index adb8117faf..ce01953934 100644 --- a/src/backends/odbc/CMakeLists.txt +++ b/src/backends/odbc/CMakeLists.txt @@ -10,9 +10,6 @@ ############################################################################### soci_backend(ODBC DEPENDS ODBC - HEADERS soci-odbc.h utility.h DESCRIPTION "SOCI backend for ODBC" AUTHORS "Maciej Sobczak, Stephen Hutton, David Courtney" MAINTAINERS "Vadim Zeitlin, Mateusz Loskot, Maciej Sobczak") - -add_subdirectory(test) diff --git a/src/backends/odbc/blob.cpp b/src/backends/odbc/blob.cpp index cb5a3fd801..851c250667 100644 --- a/src/backends/odbc/blob.cpp +++ b/src/backends/odbc/blob.cpp @@ -6,7 +6,7 @@ // #define SOCI_ODBC_SOURCE -#include "soci-odbc.h" +#include "soci/odbc/soci-odbc.h" using namespace soci; using namespace soci::details; diff --git a/src/backends/odbc/factory.cpp b/src/backends/odbc/factory.cpp index 27f0d04d80..60c4e62374 100644 --- a/src/backends/odbc/factory.cpp +++ b/src/backends/odbc/factory.cpp @@ -6,8 +6,8 @@ // #define SOCI_ODBC_SOURCE -#include "soci-odbc.h" -#include +#include "soci/odbc/soci-odbc.h" +#include "soci/backend-loader.h" using namespace soci; using namespace soci::details; diff --git a/src/backends/odbc/row-id.cpp b/src/backends/odbc/row-id.cpp index 0461980ecd..62331ddd95 100644 --- a/src/backends/odbc/row-id.cpp +++ b/src/backends/odbc/row-id.cpp @@ -6,7 +6,7 @@ // #define SOCI_ODBC_SOURCE -#include "soci-odbc.h" +#include "soci/odbc/soci-odbc.h" using namespace soci; using namespace soci::details; diff --git a/src/backends/odbc/session.cpp b/src/backends/odbc/session.cpp index b61a3075c3..9a8cefb434 100644 --- a/src/backends/odbc/session.cpp +++ b/src/backends/odbc/session.cpp @@ -6,8 +6,9 @@ // #define SOCI_ODBC_SOURCE -#include "soci-odbc.h" -#include "session.h" +#include "soci/soci-platform.h" +#include "soci/odbc/soci-odbc.h" +#include "soci/session.h" #include @@ -33,8 +34,7 @@ odbc_session_backend::odbc_session_backend( rc = SQLSetEnvAttr(henv_, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_ENV, henv_, - "Setting ODBC version"); + throw odbc_soci_error(SQL_HANDLE_ENV, henv_, "setting ODBC version 3"); } // Allocate connection handle @@ -42,7 +42,7 @@ odbc_session_backend::odbc_session_backend( if (is_odbc_error(rc)) { throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, - "Allocating connection handle"); + "allocating connection handle"); } SQLCHAR outConnString[1024]; @@ -74,20 +74,66 @@ odbc_session_backend::odbc_session_backend( std::string const & connectString = parameters.get_connect_string(); rc = SQLDriverConnect(hdbc_, hwnd_for_prompt, - (SQLCHAR *)connectString.c_str(), + sqlchar_cast(connectString), (SQLSMALLINT)connectString.size(), outConnString, 1024, &strLength, static_cast(completion)); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, - "Error Connecting to database"); + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "connecting to database"); } connection_string_.assign((const char*)outConnString, strLength); reset_transaction(); + + configure_connection(); +} + +void odbc_session_backend::configure_connection() +{ + if ( get_database_product() == prod_postgresql ) + { + // Increase the number of digits used for floating point values to + // ensure that the conversions to/from text round trip correctly, which + // is not the case with the default value of 0. Use the maximal + // supported value, which was 2 until 9.x and is 3 since it. + + char product_ver[1024]; + SQLSMALLINT len = sizeof(product_ver); + SQLRETURN rc = SQLGetInfo(hdbc_, SQL_DBMS_VER, product_ver, len, &len); + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, henv_, + "getting PostgreSQL ODBC driver version"); + } + + // The returned string is of the form "##.##.#### ...", but we don't + // need to parse it fully, we just need the major version which, + // conveniently, comes first. + unsigned major_ver = 0; + if (std::sscanf(product_ver, "%u", &major_ver) != 1) + { + throw soci_error("DBMS version \"" + std::string(product_ver) + + "\" in unrecognizable format."); + } + + odbc_statement_backend st(*this); + st.alloc(); + + std::string const q(major_ver >= 9 ? "SET extra_float_digits = 3" + : "SET extra_float_digits = 2"); + rc = SQLExecDirect(st.hstmt_, sqlchar_cast(q), static_cast(q.size())); + + st.clean_up(); + + if (is_odbc_error(rc)) + { + throw odbc_soci_error(SQL_HANDLE_DBC, henv_, + "setting extra_float_digits for PostgreSQL"); + } + } } odbc_session_backend::~odbc_session_backend() @@ -101,8 +147,7 @@ void odbc_session_backend::begin() (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0 ); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, - "Begin Transaction"); + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "beginning transaction"); } } @@ -111,8 +156,7 @@ void odbc_session_backend::commit() SQLRETURN rc = SQLEndTran(SQL_HANDLE_DBC, hdbc_, SQL_COMMIT); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, - "Committing"); + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "committing transaction"); } reset_transaction(); } @@ -122,8 +166,7 @@ void odbc_session_backend::rollback() SQLRETURN rc = SQLEndTran(SQL_HANDLE_DBC, hdbc_, SQL_ROLLBACK); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, - "Rolling back"); + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "rolling back transaction"); } reset_transaction(); } @@ -216,8 +259,7 @@ void odbc_session_backend::reset_transaction() (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0 ); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, - "Set Auto Commit"); + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "enabling auto commit"); } } @@ -227,22 +269,19 @@ void odbc_session_backend::clean_up() SQLRETURN rc = SQLDisconnect(hdbc_); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, - "SQLDisconnect"); + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "disconnecting"); } rc = SQLFreeHandle(SQL_HANDLE_DBC, hdbc_); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, - "SQLFreeHandle DBC"); + throw odbc_soci_error(SQL_HANDLE_DBC, hdbc_, "freeing connection"); } rc = SQLFreeHandle(SQL_HANDLE_ENV, henv_); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_ENV, henv_, - "SQLFreeHandle ENV"); + throw odbc_soci_error(SQL_HANDLE_ENV, henv_, "freeing environment"); } } @@ -274,7 +313,7 @@ odbc_session_backend::get_database_product() if (is_odbc_error(rc)) { throw odbc_soci_error(SQL_HANDLE_DBC, henv_, - "SQLGetInfo(SQL_DBMS_NAME)"); + "getting ODBC driver name"); } if (strcmp(product_name, "Firebird") == 0) diff --git a/src/backends/odbc/standard-into-type.cpp b/src/backends/odbc/standard-into-type.cpp index c59d9b49ae..b79c043c37 100644 --- a/src/backends/odbc/standard-into-type.cpp +++ b/src/backends/odbc/standard-into-type.cpp @@ -6,8 +6,10 @@ // #define SOCI_ODBC_SOURCE -#include -#include "soci-odbc.h" +#include "soci/soci-platform.h" +#include "soci/odbc/soci-odbc.h" +#include "soci-exchange-cast.h" +#include "soci-mktime.h" #include #include // sscanf() @@ -22,7 +24,6 @@ void odbc_standard_into_type_backend::define_by_pos( position_ = position++; SQLUINTEGER size = 0; - switch (type_) { case x_char: @@ -35,7 +36,7 @@ void odbc_standard_into_type_backend::define_by_pos( 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 = statement_.column_size(position_); + size = static_cast(statement_.column_size(position_)); size = size > odbc_max_buffer_length ? odbc_max_buffer_length : size; size++; buf_ = new char[size]; @@ -101,8 +102,9 @@ void odbc_standard_into_type_backend::define_by_pos( static_cast(odbcType_), data, size, &valueLen_); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, - "into type pre_fetch"); + std::ostringstream ss; + ss << "binding output column #" << position_; + throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, ss.str()); } } @@ -146,46 +148,39 @@ void odbc_standard_into_type_backend::post_fetch( // only std::string and std::tm need special handling if (type_ == x_char) { - char *c = static_cast(data_); - *c = buf_[0]; + exchange_type_cast(data_) = buf_[0]; } - if (type_ == x_stdstring) + else if (type_ == x_stdstring) { - std::string *s = static_cast(data_); - *s = buf_; - if (s->size() >= (odbc_max_buffer_length - 1)) + std::string& s = exchange_type_cast(data_); + s = buf_; + if (s.size() >= (odbc_max_buffer_length - 1)) { throw soci_error("Buffer size overflow; maybe got too large string"); } } else if (type_ == x_stdtm) { - std::tm *t = static_cast(data_); + std::tm& t = exchange_type_cast(data_); TIMESTAMP_STRUCT * ts = reinterpret_cast(buf_); - t->tm_isdst = -1; - t->tm_year = ts->year - 1900; - t->tm_mon = ts->month - 1; - t->tm_mday = ts->day; - t->tm_hour = ts->hour; - t->tm_min = ts->minute; - t->tm_sec = ts->second; - // normalize and compute the remaining fields - std::mktime(t); + details::mktime_from_ymdhms(t, + ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second); } else if (type_ == x_long_long && use_string_for_bigint()) { - long long *ll = static_cast(data_); - if (sscanf(buf_, "%" LL_FMT_FLAGS "d", ll) != 1) + long long& ll = exchange_type_cast(data_); + if (sscanf(buf_, "%" LL_FMT_FLAGS "d", &ll) != 1) { throw soci_error("Failed to parse the returned 64-bit integer value"); } } else if (type_ == x_unsigned_long_long && use_string_for_bigint()) { - unsigned long long *ll = static_cast(data_); - if (sscanf(buf_, "%" LL_FMT_FLAGS "u", ll) != 1) + unsigned long long& ll = exchange_type_cast(data_); + if (sscanf(buf_, "%" LL_FMT_FLAGS "u", &ll) != 1) { throw soci_error("Failed to parse the returned 64-bit integer value"); } diff --git a/src/backends/odbc/standard-use-type.cpp b/src/backends/odbc/standard-use-type.cpp index 233c6c1e64..4a47b22882 100644 --- a/src/backends/odbc/standard-use-type.cpp +++ b/src/backends/odbc/standard-use-type.cpp @@ -4,8 +4,9 @@ // http://www.boost.org/LICENSE_1_0.txt) #define SOCI_ODBC_SOURCE -#include -#include "soci-odbc.h" +#include "soci/soci-platform.h" +#include "soci/odbc/soci-odbc.h" +#include "soci-exchange-cast.h" #include #include #include @@ -15,12 +16,6 @@ using namespace soci; using namespace soci::details; -#ifdef _MSC_VER -#pragma warning(disable:4996) -#define snprintf _snprintf -#endif - - void* odbc_standard_use_type_backend::prepare_for_bind( SQLLEN &size, SQLSMALLINT &sqlType, SQLSMALLINT &cType) { @@ -45,7 +40,7 @@ void* odbc_standard_use_type_backend::prepare_for_bind( size = max_bigint_length; buf_ = new char[size]; snprintf(buf_, size, "%" LL_FMT_FLAGS "d", - *static_cast(data_)); + exchange_type_cast(data_)); indHolder_ = SQL_NTS; } else // Normal case, use ODBC support. @@ -63,7 +58,7 @@ void* odbc_standard_use_type_backend::prepare_for_bind( size = max_bigint_length; buf_ = new char[size]; snprintf(buf_, size, "%" LL_FMT_FLAGS "u", - *static_cast(data_)); + exchange_type_cast(data_)); indHolder_ = SQL_NTS; } else // Normal case, use ODBC support. @@ -84,25 +79,34 @@ void* odbc_standard_use_type_backend::prepare_for_bind( cType = SQL_C_CHAR; size = 2; buf_ = new char[size]; - buf_[0] = *static_cast(data_); + buf_[0] = exchange_type_cast(data_); buf_[1] = '\0'; indHolder_ = SQL_NTS; break; case x_stdstring: { - std::string* s = static_cast(data_); + std::string const& s = exchange_type_cast(data_); sqlType = SQL_VARCHAR; cType = SQL_C_CHAR; - size = s->size(); + size = s.size(); buf_ = new char[size+1]; - memcpy(buf_, s->c_str(), size); + 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 */; + } } break; case x_stdtm: { - std::tm *t = static_cast(data_); + std::tm const& t = exchange_type_cast(data_); sqlType = SQL_TIMESTAMP; cType = SQL_C_TIMESTAMP; @@ -113,12 +117,12 @@ void* odbc_standard_use_type_backend::prepare_for_bind( TIMESTAMP_STRUCT * ts = reinterpret_cast(buf_); - 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->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; } break; @@ -219,8 +223,9 @@ void odbc_standard_use_type_backend::pre_use(indicator const *ind) if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, - "Binding"); + std::ostringstream ss; + ss << "binding input parameter #" << position_; + throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, ss.str()); } // then handle indicators diff --git a/src/backends/odbc/statement.cpp b/src/backends/odbc/statement.cpp index ae688bee5c..5dcc034b58 100644 --- a/src/backends/odbc/statement.cpp +++ b/src/backends/odbc/statement.cpp @@ -6,18 +6,11 @@ // #define SOCI_ODBC_SOURCE -#include "soci-odbc.h" +#include "soci/odbc/soci-odbc.h" #include #include #include -#ifdef _MSC_VER -// disables the warning about converting int to void*. This is a 64 bit compatibility -// warning, but odbc requires the value to be converted on this line -// SQLSetStmtAttr(hstmt_, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)number, 0); -#pragma warning(disable:4312) -#endif - using namespace soci; using namespace soci::details; @@ -38,7 +31,7 @@ void odbc_statement_backend::alloc() if (is_odbc_error(rc)) { throw odbc_soci_error(SQL_HANDLE_DBC, session_.hdbc_, - "Allocating statement"); + "allocating statement"); } } @@ -131,11 +124,12 @@ void odbc_statement_backend::prepare(std::string const & query, query_ += "?"; } - SQLRETURN rc = SQLPrepare(hstmt_, (SQLCHAR*)query_.c_str(), (SQLINTEGER)query_.size()); + SQLRETURN rc = SQLPrepare(hstmt_, sqlchar_cast(query_), (SQLINTEGER)query_.size()); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, - query_.c_str()); + std::ostringstream ss; + ss << "preparing query \"" << query_ << "\""; + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, ss.str()); } } @@ -148,15 +142,15 @@ odbc_statement_backend::execute(int number) { SQLSetStmtAttr(hstmt_, SQL_ATTR_PARAMS_PROCESSED_PTR, &rows_processed, 0); } - + // if we are called twice for the same statement we need to close the open // cursor or an "invalid cursor state" error will occur on execute SQLCloseCursor(hstmt_); - + SQLRETURN rc = SQLExecute(hstmt_); if (is_odbc_error(rc)) { - // If executing bulk operation a partial + // If executing bulk operation a partial // number of rows affected may be available. if (hasVectorUseElements_) { @@ -177,12 +171,14 @@ 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_, - "Statement Execute"); + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, "executing statement"); } - // We should preserve the number of rows affected here - // where we know for sure that a bulk operation was executed. - else + else if (hasVectorUseElements_) + { + // We already have the number of rows, no need to do anything. + rowsAffected_ = rows_processed; + } + else // We need to retrieve the number of rows affected explicitly. { rowsAffected_ = 0; @@ -192,7 +188,7 @@ odbc_statement_backend::execute(int number) if (is_odbc_error(rc)) { throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, - "Getting number of affected rows"); + "getting number of affected rows"); } rowsAffected_ += res; } @@ -229,8 +225,7 @@ odbc_statement_backend::fetch(int number) if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, - "Statement Fetch"); + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, "fetching data"); } return ef_success; @@ -243,7 +238,12 @@ long long odbc_statement_backend::get_affected_rows() int odbc_statement_backend::get_number_of_rows() { - return numRowsFetched_; + return static_cast(numRowsFetched_); +} + +std::string odbc_statement_backend::get_parameter_name(int index) const +{ + return names_.at(index); } std::string odbc_statement_backend::rewrite_for_procedure_call( @@ -276,8 +276,9 @@ void odbc_statement_backend::describe_column(int colNum, data_type & type, if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, - "describe Column"); + std::ostringstream ss; + ss << "getting description of column at position " << colNum; + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, ss.str()); } char const *name = reinterpret_cast(colNameBuffer); @@ -330,8 +331,9 @@ std::size_t odbc_statement_backend::column_size(int colNum) if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, - "column size"); + std::ostringstream ss; + ss << "getting size of column at position " << colNum; + throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, ss.str()); } return colSize; diff --git a/src/backends/odbc/test/Makefile.basic b/src/backends/odbc/test/Makefile.basic deleted file mode 100644 index d21f9a8cff..0000000000 --- a/src/backends/odbc/test/Makefile.basic +++ /dev/null @@ -1,24 +0,0 @@ -# The following variable is specific to this backend and its correct -# values might depend on your environment - feel free to set it accordingly. - -ODBCINCLUDEDIR = -I/usr/include -ODBCLIBDIR = -L/usr/lib -ODBCLIBS = -lodbc -lodbcinst - -# The rest of the Makefile is independent of the target environment. - -COMPILER = g++ -CXXFLAGS = -Wall -pedantic -Wno-long-long -INCLUDEDIRS = -I.. -I../../../core ${ODBCINCLUDEDIR} -LIBDIRS = -L.. -L../../../core ${ODBCLIBDIR} -LIBS = -lsoci_core -lsoci_odbc -ldl ${ODBCLIBS} - - -test-odbc-mssql: test-odbc-mssql.cpp - ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} - -test-odbc-access: test-odbc-access.cpp - ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} - -clean : - rm -f test-odbc-access.exe test-odbc-mssql.exe diff --git a/src/backends/odbc/test/makefile.msvc b/src/backends/odbc/test/makefile.msvc deleted file mode 100644 index 1426f83755..0000000000 --- a/src/backends/odbc/test/makefile.msvc +++ /dev/null @@ -1,19 +0,0 @@ -ODBCINCLUDEDIR="C:\Program Files\Microsoft Platform SDK\Include" -ODBCLIBDIR="C:\Program Files\Microsoft Platform SDK\Lib" - -COMPILER = cl -CXXFLAGS = /nologo /EHsc -CXXFLAGSSO = $(CXXFLAGS) -INCLUDEDIRS = /I.. /I..\..\..\core /I..\..\..\core\test /I$(ODBCINCLUDEDIR) -LIBS = ..\..\..\core\soci-core.lib ..\soci-odbc.lib $(ODBCLIBDIR)\uuid.lib $(ODBCLIBDIR)\odbc32.lib - -mssql: test-odbc-mssql.cpp - $(COMPILER) $? $(CXXFLAGS) $(INCLUDEDIRS) $(LIBS) - -access: test-odbc-access.cpp - $(COMPILER) $? $(CXXFLAGS) $(INCLUDEDIRS) $(LIBS) - -clean: - del *.exe - del *.obj - diff --git a/src/backends/odbc/test/test-odbc-mssql.cpp b/src/backends/odbc/test/test-odbc-mssql.cpp deleted file mode 100644 index ebce24aa15..0000000000 --- a/src/backends/odbc/test/test-odbc-mssql.cpp +++ /dev/null @@ -1,148 +0,0 @@ -// -// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney -// 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.h" -#include "soci-odbc.h" -#include "common-tests.h" -#include -#include -#include -#include -#include - -using namespace soci; -using namespace soci::tests; - -std::string connectString; -backend_factory const &backEnd = *soci::factory_odbc(); - -// DDL Creation objects for common tests -struct table_creator_one : public table_creator_base -{ - table_creator_one(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(id integer, val integer, c char, " - "str varchar(20), sh smallint, ul numeric(20), d float, " - "tm datetime, i1 integer, i2 integer, i3 integer, " - "name varchar(20))"; - } -}; - -struct table_creator_two : public table_creator_base -{ - table_creator_two(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(num_float float, num_int integer," - " name varchar(20), sometime datetime, chr char)"; - } -}; - -struct table_creator_three : public table_creator_base -{ - table_creator_three(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(name varchar(100) not null, " - "phone varchar(15))"; - } -}; - -struct table_creator_for_get_affected_rows : table_creator_base -{ - table_creator_for_get_affected_rows(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val integer)"; - } -}; - -// -// Support for SOCI Common Tests -// - -class test_context : public test_context_base -{ -public: - test_context(backend_factory const &backEnd, - std::string const &connectString) - : test_context_base(backEnd, connectString) {} - - table_creator_base* table_creator_1(session& s) const - { - return new table_creator_one(s); - } - - table_creator_base* table_creator_2(session& s) const - { - return new table_creator_two(s); - } - - table_creator_base* table_creator_3(session& s) const - { - return new table_creator_three(s); - } - - table_creator_base * table_creator_4(session& s) const - { - return new table_creator_for_get_affected_rows(s); - } - - std::string to_date_time(std::string const &datdt_string) const - { - return "convert(datetime, \'" + datdt_string + "\', 120)"; - } -}; - -int main(int argc, char** argv) -{ -#ifdef _MSC_VER - // Redirect errors, unrecoverable problems, and assert() failures to STDERR, - // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. - // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); -#endif //_MSC_VER - - if (argc == 2) - { - connectString = argv[1]; - } - else - { - connectString = "FILEDSN=./test-mssql.dsn"; - } - try - { - std::cout << "\nSOCI ODBC with MS SQL Server Tests:\n\n"; - - test_context tc(backEnd, connectString); - common_tests tests(tc); - tests.run(); - - std::cout << "\nOK, all tests passed.\n\n"; - return EXIT_SUCCESS; - } - catch (soci::odbc_soci_error const & e) - { - std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl - << "Native Error Code: " << e.native_error_code() << std::endl - << "SOCI Message: " << e.what() << std::endl - << "ODBC Message: " << e.odbc_error_message() << std::endl; - } - catch (soci::soci_error const & e) - { - std::cout << "SOCIERROR: " << e.what() << '\n'; - } - catch (std::exception const & e) - { - std::cout << "STD::EXECEPTION " << e.what() << '\n'; - } - return EXIT_FAILURE; -} \ No newline at end of file diff --git a/src/backends/odbc/test/test-odbc-mysql.cpp b/src/backends/odbc/test/test-odbc-mysql.cpp deleted file mode 100644 index 2750d35858..0000000000 --- a/src/backends/odbc/test/test-odbc-mysql.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// -// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney -// 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.h" -#include "soci-odbc.h" -#include "common-tests.h" -#include -#include -#include -#include -#include - -using namespace soci; -using namespace soci::tests; - -std::string connectString; -backend_factory const &backEnd = *soci::factory_odbc(); - -// DDL Creation objects for common tests -struct table_creator_one : public table_creator_base -{ - table_creator_one(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(id integer, val integer, c char, " - "str varchar(20), sh int2, ul numeric(20), d float8, " - "tm datetime, i1 integer, i2 integer, i3 integer, " - "name varchar(20))"; - } -}; - -struct table_creator_two : public table_creator_base -{ - table_creator_two(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(num_float float8, num_int integer," - " name varchar(20), sometime datetime, chr char)"; - } -}; - -struct table_creator_three : public table_creator_base -{ - table_creator_three(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(name varchar(100) not null, " - "phone varchar(15))"; - } -}; - -struct table_creator_for_get_affected_rows : table_creator_base -{ - table_creator_for_get_affected_rows(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val integer)"; - } -}; - -// -// Support for SOCI Common Tests -// - -class test_context : public test_context_base -{ -public: - test_context(backend_factory const &backEnd, - std::string const &connectString) - : test_context_base(backEnd, connectString) {} - - table_creator_base * table_creator_1(session& s) const - { - return new table_creator_one(s); - } - - table_creator_base * table_creator_2(session& s) const - { - return new table_creator_two(s); - } - - table_creator_base * table_creator_3(session& s) const - { - return new table_creator_three(s); - } - - table_creator_base * table_creator_4(session& s) const - { - return new table_creator_for_get_affected_rows(s); - } - - std::string to_date_time(std::string const &datdt_string) const - { - return "\'" + datdt_string + "\'"; - } -}; - -int main(int argc, char** argv) -{ -#ifdef _MSC_VER - // Redirect errors, unrecoverable problems, and assert() failures to STDERR, - // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. - // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); -#endif //_MSC_VER - - if (argc == 2) - { - connectString = argv[1]; - } - else - { - connectString = "FILEDSN=./test-mysql.dsn"; - } - try - { - std::cout << "\nSOCI ODBC with MySQL Tests:\n\n"; - - test_context tc(backEnd, connectString); - common_tests tests(tc); - tests.run(); - - std::cout << "\nOK, all tests passed.\n\n"; - return EXIT_SUCCESS; - } - catch (soci::odbc_soci_error const & e) - { - std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl - << "Native Error Code: " << e.native_error_code() << std::endl - << "SOCI Message: " << e.what() << std::endl - << "ODBC Message: " << e.odbc_error_message() << std::endl; - } - catch (std::exception const & e) - { - std::cout << "STD::EXECEPTION " << e.what() << '\n'; - } - return EXIT_FAILURE; -} diff --git a/src/backends/odbc/test/test-odbc-postgresql.cpp b/src/backends/odbc/test/test-odbc-postgresql.cpp deleted file mode 100644 index 1422ce1631..0000000000 --- a/src/backends/odbc/test/test-odbc-postgresql.cpp +++ /dev/null @@ -1,146 +0,0 @@ -// -// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney -// 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.h" -#include "soci-odbc.h" -#include "common-tests.h" -#include -#include -#include -#include -#include - -using namespace soci; -using namespace soci::tests; - -std::string connectString; -backend_factory const &backEnd = *soci::factory_odbc(); - -// DDL Creation objects for common tests -struct table_creator_one : public table_creator_base -{ - table_creator_one(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(id integer, val integer, c char, " - "str varchar(20), sh int2, ul numeric(20), d float8, " - "tm timestamp, i1 integer, i2 integer, i3 integer, " - "name varchar(20))"; - } -}; - -struct table_creator_two : public table_creator_base -{ - table_creator_two(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(num_float float8, num_int integer," - " name varchar(20), sometime timestamp, chr char)"; - } -}; - -struct table_creator_three : public table_creator_base -{ - table_creator_three(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(name varchar(100) not null, " - "phone varchar(15))"; - } -}; - -struct table_creator_for_get_affected_rows : table_creator_base -{ - table_creator_for_get_affected_rows(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val integer)"; - } -}; - -// -// Support for SOCI Common Tests -// - -class test_context : public test_context_base -{ -public: - test_context(backend_factory const &backEnd, - std::string const &connectString) - : test_context_base(backEnd, connectString) {} - - table_creator_base * table_creator_1(session& s) const - { - return new table_creator_one(s); - } - - table_creator_base * table_creator_2(session& s) const - { - return new table_creator_two(s); - } - - table_creator_base * table_creator_3(session& s) const - { - return new table_creator_three(s); - } - - table_creator_base * table_creator_4(session& s) const - { - return new table_creator_for_get_affected_rows(s); - } - - std::string to_date_time(std::string const &datdt_string) const - { - return "timestamptz(\'" + datdt_string + "\')"; - } - -}; - -int main(int argc, char** argv) -{ - -#ifdef _MSC_VER - // Redirect errors, unrecoverable problems, and assert() failures to STDERR, - // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. - // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); -#endif //_MSC_VER - - if (argc == 2) - { - connectString = argv[1]; - } - else - { - connectString = "FILEDSN=./test-postgresql.dsn"; - } - try - { - std::cout << "\nSOCI ODBC with PostgreSQL Tests:\n\n"; - - test_context tc(backEnd, connectString); - common_tests tests(tc); - tests.run(); - - std::cout << "\nOK, all tests passed.\n\n"; - return EXIT_SUCCESS; - } - catch (soci::odbc_soci_error const & e) - { - std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl - << "Native Error Code: " << e.native_error_code() << std::endl - << "SOCI Message: " << e.what() << std::endl - << "ODBC Message: " << e.odbc_error_message() << std::endl; - } - catch (std::exception const & e) - { - std::cout << "STD::EXECEPTION " << e.what() << '\n'; - } - return EXIT_FAILURE; -} diff --git a/src/backends/odbc/vector-into-type.cpp b/src/backends/odbc/vector-into-type.cpp index 73c715888f..04fb31671c 100644 --- a/src/backends/odbc/vector-into-type.cpp +++ b/src/backends/odbc/vector-into-type.cpp @@ -6,9 +6,10 @@ // #define SOCI_ODBC_SOURCE -#include "soci-odbc.h" -#include -#include +#include "soci/soci-platform.h" +#include "soci/odbc/soci-odbc.h" +#include "soci-mktime.h" +#include "soci-static-assert.h" #include #include #include @@ -55,7 +56,7 @@ void odbc_vector_into_type_backend::define_by_pos( { odbcType_ = SQL_C_SLONG; size = sizeof(SQLINTEGER); - assert(sizeof(SQLINTEGER) == sizeof(int)); + SOCI_STATIC_ASSERT(sizeof(SQLINTEGER) == sizeof(int)); std::vector *vp = static_cast *>(data); std::vector &v(*vp); prepare_indicators(v.size()); @@ -177,13 +178,14 @@ void odbc_vector_into_type_backend::define_by_pos( case x_blob: break; // not supported } - SQLRETURN rc + SQLRETURN rc = SQLBindCol(statement_.hstmt_, static_cast(position++), odbcType_, static_cast(data), size, indHolders_); if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, - "vector into type define by pos"); + std::ostringstream ss; + ss << "binding output vector column #" << position; + throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, ss.str()); } } @@ -238,20 +240,10 @@ void odbc_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) std::size_t const vsize = v.size(); for (std::size_t i = 0; i != vsize; ++i) { - std::tm t; - TIMESTAMP_STRUCT * ts = reinterpret_cast(pos); - t.tm_isdst = -1; - t.tm_year = ts->year - 1900; - t.tm_mon = ts->month - 1; - t.tm_mday = ts->day; - t.tm_hour = ts->hour; - t.tm_min = ts->minute; - t.tm_sec = ts->second; - - // normalize and compute the remaining fields - std::mktime(&t); - v[i] = t; + details::mktime_from_ymdhms(v[i], + ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second); pos += colSize_; } } diff --git a/src/backends/odbc/vector-use-type.cpp b/src/backends/odbc/vector-use-type.cpp index ad9ea65f97..75f2da1091 100644 --- a/src/backends/odbc/vector-use-type.cpp +++ b/src/backends/odbc/vector-use-type.cpp @@ -6,9 +6,9 @@ // #define SOCI_ODBC_SOURCE -#include "soci-odbc.h" -#include -#include +#include "soci/soci-platform.h" +#include "soci/odbc/soci-odbc.h" +#include "soci-static-assert.h" #include #include #include @@ -57,7 +57,7 @@ void odbc_vector_use_type_backend::prepare_for_bind(void *&data, SQLUINTEGER &si sqlType = SQL_INTEGER; cType = SQL_C_SLONG; size = sizeof(SQLINTEGER); - assert(sizeof(SQLINTEGER) == sizeof(int)); + SOCI_STATIC_ASSERT(sizeof(SQLINTEGER) == sizeof(int)); std::vector *vp = static_cast *>(data); std::vector &v(*vp); prepare_indicators(v.size()); @@ -230,8 +230,9 @@ void odbc_vector_use_type_backend::bind_helper(int &position, void *data, exchan if (is_odbc_error(rc)) { - throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, - "Error while binding value to column"); + std::ostringstream ss; + ss << "binding input vector parameter #" << position; + throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_, ss.str()); } } diff --git a/src/backends/oracle/CMakeLists.txt b/src/backends/oracle/CMakeLists.txt index 954dce0051..632ca61790 100644 --- a/src/backends/oracle/CMakeLists.txt +++ b/src/backends/oracle/CMakeLists.txt @@ -10,9 +10,6 @@ ############################################################################### soci_backend(Oracle DEPENDS Oracle - HEADERS soci-oracle.h error.h DESCRIPTION "SOCI backend for Oracle 10+" AUTHORS "Maciej Sobczak, Stephen Hutton" MAINTAINERS "Maciej Sobczak") - -add_subdirectory(test) diff --git a/src/backends/oracle/Makefile.basic b/src/backends/oracle/Makefile.basic index 85e4df2e29..31b861f5e4 100644 --- a/src/backends/oracle/Makefile.basic +++ b/src/backends/oracle/Makefile.basic @@ -1,14 +1,14 @@ # The following variable is specific to this backend and its correct # values might depend on your environment - feel free to set it accordingly. -ORACLEINCLUDEDIR = -I/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/rdbms/public +ORACLEINCLUDEDIR = -I${ORACLE_HOME}/rdbms/public # 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 ${ORACLEINCLUDEDIR} +INCLUDEDIRS = -I../../../include -I../../../include/private ${ORACLEINCLUDEDIR} OBJECTS = blob.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 \ @@ -22,9 +22,9 @@ libsoci_oracle.a : ${OBJECTS} ar rv $@ $? rm *.o -soci-oracle.o : soci-oracle.cpp +soci-oracle.o : soci-oracle.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - + blob.o : blob.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} diff --git a/src/backends/oracle/blob.cpp b/src/backends/oracle/blob.cpp index 2636e665ee..d9c90fd601 100644 --- a/src/backends/oracle/blob.cpp +++ b/src/backends/oracle/blob.cpp @@ -5,9 +5,9 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci-oracle.h" +#include "soci/oracle/soci-oracle.h" #include "error.h" -#include "statement.h" +#include "soci/statement.h" #include #include #include diff --git a/src/backends/oracle/error.cpp b/src/backends/oracle/error.cpp index 12b72bcdea..3f2f2dcc0a 100644 --- a/src/backends/oracle/error.cpp +++ b/src/backends/oracle/error.cpp @@ -6,7 +6,7 @@ // #define SOCI_ORACLE_SOURCE -#include "soci-oracle.h" +#include "soci/oracle/soci-oracle.h" #include "error.h" #include #include diff --git a/src/backends/oracle/error.h b/src/backends/oracle/error.h index 2dbe9214ed..57f172554a 100644 --- a/src/backends/oracle/error.h +++ b/src/backends/oracle/error.h @@ -8,7 +8,7 @@ #ifndef SOCI_ORACLE_ERROR_H_INCLUDED #define SOCI_ORACLE_ERROR_H_INCLUDED -#include "soci-oracle.h" +#include "soci/oracle/soci-oracle.h" namespace soci { diff --git a/src/backends/oracle/factory.cpp b/src/backends/oracle/factory.cpp index 7c80989670..8dfd453866 100644 --- a/src/backends/oracle/factory.cpp +++ b/src/backends/oracle/factory.cpp @@ -6,9 +6,9 @@ // #define SOCI_ORACLE_SOURCE -#include "soci-oracle.h" -#include -#include +#include "soci/oracle/soci-oracle.h" +#include "soci/connection-parameters.h" +#include "soci/backend-loader.h" #include #include #include @@ -22,51 +22,70 @@ using namespace soci; using namespace soci::details; +// 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 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) { - // transform the connect string into a sequence of tokens - // separated by spaces, this is done by replacing each first '=' - // in each original token with space - // note: each original token is a key=value pair and only the first - // '=' there is replaced with space, so that potential '=' signs - // in the value part are left intact - - std::string tmp; - bool in_value = false; - for (std::string::const_iterator i = connectString.begin(), - end = connectString.end(); i != end; ++i) - { - if (*i == '=' && in_value == false) - { - // this is the first '=' in the key=value pair - tmp += ' '; - in_value = true; - } - else - { - tmp += *i; - if (*i == ' ' || *i == '\t') - { - // follow with the next key=value pair - in_value = false; - } - } - } - serviceName.clear(); userName.clear(); password.clear(); mode = OCI_DEFAULT; decimals_as_strings = false; - std::istringstream iss(tmp); std::string key, value; - while (iss >> key >> value) + std::string::const_iterator i = connectString.begin(); + while (i != connectString.end()) { + i = get_key_value(i, connectString.end(), key, value); if (key == "service") { serviceName = value; diff --git a/src/backends/oracle/row-id.cpp b/src/backends/oracle/row-id.cpp index 966576ce65..f0584b7a62 100644 --- a/src/backends/oracle/row-id.cpp +++ b/src/backends/oracle/row-id.cpp @@ -7,7 +7,7 @@ // #define SOCI_ORACLE_SOURCE -#include "soci-oracle.h" +#include "soci/oracle/soci-oracle.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/oracle/session.cpp b/src/backends/oracle/session.cpp index b408d3cb59..cfe1ad1269 100644 --- a/src/backends/oracle/session.cpp +++ b/src/backends/oracle/session.cpp @@ -6,7 +6,7 @@ // #define SOCI_ORACLE_SOURCE -#include "soci-oracle.h" +#include "soci/oracle/soci-oracle.h" #include "error.h" #include #include @@ -214,3 +214,18 @@ oracle_blob_backend * oracle_session_backend::make_blob_backend() { return new oracle_blob_backend(*this); } + +ub2 oracle_session_backend::get_double_sql_type() const +{ + // SQLT_BDOUBLE avoids unnecessary conversions which is better from both + // performance and correctness point of view as it avoids rounding + // problems, however it's only available starting in Oracle 10.1, so + // normally we should do run-time Oracle version detection here, but for + // now just assume that if we use new headers (i.e. have high enough + // compile-time version), then the run-time is at least as high. +#ifdef SQLT_BDOUBLE + return SQLT_BDOUBLE; +#else + return SQLT_FLT; +#endif +} diff --git a/src/backends/oracle/standard-into-type.cpp b/src/backends/oracle/standard-into-type.cpp index d12a93e88e..527c7dd95d 100644 --- a/src/backends/oracle/standard-into-type.cpp +++ b/src/backends/oracle/standard-into-type.cpp @@ -6,12 +6,14 @@ // #define SOCI_ORACLE_SOURCE -#include "soci-oracle.h" -#include "blob.h" +#include "soci/oracle/soci-oracle.h" +#include "soci/blob.h" #include "error.h" -#include "rowid.h" -#include "statement.h" -#include +#include "soci/rowid.h" +#include "soci/statement.h" +#include "soci/soci-platform.h" +#include "soci-exchange-cast.h" +#include "soci-mktime.h" #include #include #include @@ -76,7 +78,7 @@ void oracle_standard_into_type_backend::define_by_pos( size = sizeof(int); break; case x_double: - oracleType = SQLT_FLT; + oracleType = statement_.session_.get_double_sql_type(); size = sizeof(double); break; @@ -176,44 +178,39 @@ void oracle_standard_into_type_backend::post_fetch( { if (indOCIHolder_ != -1) { - std::string *s = static_cast(data_); - *s = buf_; + exchange_type_cast(data_) = buf_; } } else if (type_ == x_long_long) { if (indOCIHolder_ != -1) { - long long *v = static_cast(data_); - *v = std::strtoll(buf_, NULL, 10); + exchange_type_cast(data_) = std::strtoll(buf_, NULL, 10); } } else if (type_ == x_unsigned_long_long) { if (indOCIHolder_ != -1) { - unsigned long long *v = static_cast(data_); - *v = std::strtoull(buf_, NULL, 10); + exchange_type_cast(data_) = std::strtoull(buf_, NULL, 10); } } else if (type_ == x_stdtm) { if (indOCIHolder_ != -1) { - std::tm *t = static_cast(data_); + std::tm& t = exchange_type_cast(data_); ub1 *pos = reinterpret_cast(buf_); - t->tm_isdst = -1; - t->tm_year = (*pos++ - 100) * 100; - t->tm_year += *pos++ - 2000; - t->tm_mon = *pos++ - 1; - t->tm_mday = *pos++; - t->tm_hour = *pos++ - 1; - t->tm_min = *pos++ - 1; - t->tm_sec = *pos++ - 1; - - // normalize and compute the remaining fields - std::mktime(t); + int year = (*pos++ - 100) * 100; + year += *pos++ - 100; + int const month = *pos++; + int const day = *pos++; + int const hour = *pos++ - 1; + int const minute = *pos++ - 1; + int const second = *pos++ - 1; + + details::mktime_from_ymdhms(t, year, month, day, hour, minute, second); } } else if (type_ == x_statement) diff --git a/src/backends/oracle/standard-use-type.cpp b/src/backends/oracle/standard-use-type.cpp index 8e42b720b3..aff764c85d 100644 --- a/src/backends/oracle/standard-use-type.cpp +++ b/src/backends/oracle/standard-use-type.cpp @@ -6,12 +6,17 @@ // #define soci_ORACLE_SOURCE -#include "soci-oracle.h" -#include "blob.h" +#include "soci/oracle/soci-oracle.h" +#include "soci/blob.h" #include "error.h" -#include "rowid.h" -#include "statement.h" -#include +#include "soci/rowid.h" +#include "soci/statement.h" +#include "soci/soci-platform.h" + +#include "soci-compiler.h" +#include "soci-exchange-cast.h" +#include "soci-mktime.h" + #include #include #include @@ -64,7 +69,7 @@ void oracle_standard_use_type_backend::prepare_for_bind( } break; case x_double: - oracleType = SQLT_FLT; + oracleType = statement_.session_.get_double_sql_type(); size = sizeof(double); if (readOnly) { @@ -206,64 +211,64 @@ void oracle_standard_use_type_backend::pre_use(indicator const *ind) case x_char: if (readOnly_) { - buf_[0] = *static_cast(data_); + buf_[0] = exchange_type_cast(data_); } break; case x_short: if (readOnly_) { - *static_cast(static_cast(buf_)) = *static_cast(data_); + exchange_type_cast(buf_) = exchange_type_cast(data_); } break; case x_integer: if (readOnly_) { - *static_cast(static_cast(buf_)) = *static_cast(data_); + exchange_type_cast(buf_) = exchange_type_cast(data_); } break; case x_long_long: { size_t const size = 100; // arbitrary, but consistent with prepare_for_bind - snprintf(buf_, size, "%" LL_FMT_FLAGS "d", *static_cast(data_)); + snprintf(buf_, size, "%" LL_FMT_FLAGS "d", exchange_type_cast(data_)); } break; case x_unsigned_long_long: { size_t const size = 100; // arbitrary, but consistent with prepare_for_bind - snprintf(buf_, size, "%" LL_FMT_FLAGS "u", *static_cast(data_)); + snprintf(buf_, size, "%" LL_FMT_FLAGS "u", exchange_type_cast(data_)); } break; case x_double: if (readOnly_) { - *static_cast(static_cast(buf_)) = *static_cast(data_); + exchange_type_cast(buf_) = exchange_type_cast(data_); } break; case x_stdstring: { - std::string *s = static_cast(data_); + std::string const& s = exchange_type_cast(data_); // 4000 is Oracle max VARCHAR2 size; 32768 is max LONG size std::size_t const bufSize = 32769; - std::size_t const sSize = s->size(); + std::size_t const sSize = s.size(); std::size_t const toCopy = sSize < bufSize -1 ? sSize + 1 : bufSize - 1; - strncpy(buf_, s->c_str(), toCopy); + strncpy(buf_, s.c_str(), toCopy); buf_[toCopy] = '\0'; } break; case x_stdtm: { - std::tm *t = static_cast(data_); + std::tm const& t = exchange_type_cast(data_); ub1* pos = reinterpret_cast(buf_); - *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); + *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); } break; case x_statement: @@ -306,7 +311,7 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) case x_char: if (readOnly_) { - const char original = *static_cast(data_); + const char original = exchange_type_cast(data_); const char bound = buf_[0]; if (original != bound) @@ -318,8 +323,8 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) case x_short: if (readOnly_) { - const short original = *static_cast(data_); - const short bound = *static_cast(static_cast(buf_)); + const short original = exchange_type_cast(data_); + const short bound = exchange_type_cast(buf_); if (original != bound) { @@ -330,8 +335,8 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) case x_integer: if (readOnly_) { - const int original = *static_cast(data_); - const int bound = *static_cast(static_cast(buf_)); + const int original = exchange_type_cast(data_); + const int bound = exchange_type_cast(buf_); if (original != bound) { @@ -342,7 +347,7 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) case x_long_long: if (readOnly_) { - long long const original = *static_cast(data_); + long long const original = exchange_type_cast(data_); long long const bound = std::strtoll(buf_, NULL, 10); if (original != bound) @@ -354,7 +359,7 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) case x_unsigned_long_long: if (readOnly_) { - unsigned long long const original = *static_cast(data_); + unsigned long long const original = exchange_type_cast(data_); unsigned long long const bound = std::strtoull(buf_, NULL, 10); if (original != bound) @@ -366,18 +371,24 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) case x_double: if (readOnly_) { - const double original = *static_cast(data_); - const double bound = *static_cast(static_cast(buf_)); + const double original = exchange_type_cast(data_); + const double bound = exchange_type_cast(buf_); + + // Exact comparison is fine here, they are really supposed to + // be exactly the same. + GCC_WARNING_SUPPRESS(float-equal) if (original != bound) { throw soci_error("Attempted modification of const use element"); } + + GCC_WARNING_RESTORE(float-equal) } break; case x_stdstring: { - std::string & original = *static_cast(data_); + std::string& original = exchange_type_cast(data_); if (original != buf_) { if (readOnly_) @@ -393,18 +404,19 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) break; case x_stdtm: { - std::tm & original = *static_cast(data_); + std::tm& original = exchange_type_cast(data_); + + ub1 *pos = reinterpret_cast(buf_); + int year = (*pos++ - 100) * 100; + year += *pos++ - 100; + int const month = *pos++; + int const day = *pos++; + int const hour = *pos++ - 1; + int const minute = *pos++ - 1; + int const second = *pos++ - 1; std::tm bound; - ub1 *pos = reinterpret_cast(buf_); - bound.tm_isdst = -1; - bound.tm_year = (*pos++ - 100) * 100; - bound.tm_year += *pos++ - 2000; - bound.tm_mon = *pos++ - 1; - bound.tm_mday = *pos++; - bound.tm_hour = *pos++ - 1; - bound.tm_min = *pos++ - 1; - bound.tm_sec = *pos++ - 1; + details::mktime_from_ymdhms(bound, year, month, day, hour, minute, second); if (original.tm_year != bound.tm_year || original.tm_mon != bound.tm_mon || @@ -420,9 +432,6 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind) else { original = bound; - - // normalize and compute the remaining fields - std::mktime(&original); } } } diff --git a/src/backends/oracle/statement.cpp b/src/backends/oracle/statement.cpp index 843e889d93..b80a270c7f 100644 --- a/src/backends/oracle/statement.cpp +++ b/src/backends/oracle/statement.cpp @@ -7,9 +7,9 @@ #define soci_ORACLE_SOURCE -#include "soci-oracle.h" +#include "soci/oracle/soci-oracle.h" #include "error.h" -#include +#include "soci/soci-backend.h" #include #include #include @@ -147,6 +147,12 @@ int oracle_statement_backend::get_number_of_rows() return rows; } +std::string oracle_statement_backend::get_parameter_name(int /* index */) const +{ + // TODO: How to get the parameter names from the query we prepared? + return std::string(); +} + std::string oracle_statement_backend::rewrite_for_procedure_call( std::string const &query) { @@ -181,10 +187,6 @@ int oracle_statement_backend::prepare_for_describe() void oracle_statement_backend::describe_column(int colNum, data_type &type, std::string &columnName) { - int size; - int precision; - int scale; - ub2 dbtype; text* dbname; ub4 nameLength; @@ -266,9 +268,6 @@ void oracle_statement_backend::describe_column(int colNum, data_type &type, } columnName.assign(dbname, dbname + nameLength); - size = static_cast(dbsize); - precision = static_cast(dbprec); - scale = static_cast(dbscale); switch (dbtype) { @@ -277,14 +276,14 @@ void oracle_statement_backend::describe_column(int colNum, data_type &type, type = dt_string; break; case SQLT_NUM: - if (scale > 0) + if (dbscale > 0) { if (session_.get_option_decimals_as_strings()) type = dt_string; else type = dt_double; } - else if (precision <= std::numeric_limits::digits10) + else if (dbprec <= std::numeric_limits::digits10) { type = dt_integer; } diff --git a/src/backends/oracle/test/Makefile.basic b/src/backends/oracle/test/Makefile.basic deleted file mode 100644 index c0caf3b79a..0000000000 --- a/src/backends/oracle/test/Makefile.basic +++ /dev/null @@ -1,23 +0,0 @@ -# The following variable is specific to this backend and its correct -# values might depend on your environment - feel free to set it accordingly. - -ORACLEINCLUDEDIR = -I/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/rdbms/public -ORACLELIBDIR = -L/usr/lib/oracle/xe/app/oracle/product/10.2.0/server/lib -ORACLELIBS = -lclntsh -lnnz10 - -# The rest of the Makefile is indepentent of the target environment. - -COMPILER = g++ -CXXFLAGS = -Wall -pedantic -Wno-long-long -INCLUDEDIRS = -I.. -I../../../core -I../../../core/test ${ORACLEINCLUDEDIR} -LIBDIRS = -L.. -L../../../core ${ORACLELIBDIR} -LIBS = -lsoci_core -lsoci_oracle -ldl -lboost_date_time ${ORACLELIBS} - - -test-oracle : test-oracle.cpp -#../../../core/test/common-tests.h - ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} - - -clean : - rm -f *.o test-oracle diff --git a/src/backends/oracle/vector-into-type.cpp b/src/backends/oracle/vector-into-type.cpp index 031d498c03..cb40d1b02d 100644 --- a/src/backends/oracle/vector-into-type.cpp +++ b/src/backends/oracle/vector-into-type.cpp @@ -6,10 +6,11 @@ // #define soci_ORACLE_SOURCE -#include "soci-oracle.h" -#include "statement.h" +#include "soci/oracle/soci-oracle.h" +#include "soci/statement.h" #include "error.h" -#include +#include "soci/soci-platform.h" +#include "soci-mktime.h" #include #include #include @@ -83,7 +84,7 @@ void oracle_vector_into_type_backend::define_by_pos( break; case x_double: { - oracleType = SQLT_FLT; + oracleType = statement_.session_.get_double_sql_type(); size = sizeof(double); std::vector *vp = static_cast *>(data); std::vector &v(*vp); @@ -253,20 +254,15 @@ void oracle_vector_into_type_backend::post_fetch(bool gotData, indicator *ind) } else { - std::tm t; - t.tm_isdst = -1; + int year = (*pos++ - 100) * 100; + year += *pos++ - 100; + int const month = *pos++; + int const day = *pos++; + int const hour = *pos++ - 1; + int const minute = *pos++ - 1; + int const second = *pos++ - 1; - t.tm_year = (*pos++ - 100) * 100; - t.tm_year += *pos++ - 2000; - t.tm_mon = *pos++ - 1; - t.tm_mday = *pos++; - t.tm_hour = *pos++ - 1; - t.tm_min = *pos++ - 1; - t.tm_sec = *pos++ - 1; - - // normalize and compute the remaining fields - std::mktime(&t); - v[i] = t; + details::mktime_from_ymdhms(v[i], year, month, day, hour, minute, second); } } } diff --git a/src/backends/oracle/vector-use-type.cpp b/src/backends/oracle/vector-use-type.cpp index 24c71f0a77..4a78455fac 100644 --- a/src/backends/oracle/vector-use-type.cpp +++ b/src/backends/oracle/vector-use-type.cpp @@ -6,9 +6,9 @@ // #define soci_ORACLE_SOURCE -#include "soci-oracle.h" +#include "soci/oracle/soci-oracle.h" #include "error.h" -#include +#include "soci/soci-platform.h" #include #include #include @@ -73,7 +73,7 @@ void oracle_vector_use_type_backend::prepare_for_bind( break; case x_double: { - oracleType = SQLT_FLT; + oracleType = statement_.session_.get_double_sql_type(); size = sizeof(double); std::vector *vp = static_cast *>(data); std::vector &v(*vp); diff --git a/src/backends/postgresql/CMakeLists.txt b/src/backends/postgresql/CMakeLists.txt index 65c96b7405..80c329e66c 100644 --- a/src/backends/postgresql/CMakeLists.txt +++ b/src/backends/postgresql/CMakeLists.txt @@ -28,24 +28,19 @@ if(SOCI_POSTGRESQL_NOPARAMS) endif() if(SOCI_POSTGRESQL_NOBINDBYNAME) -message("X") add_definitions(-DSOCI_POSTGRESQL_NOBINDBYNAME=1) endif() if(SOCI_POSTGRESQL_NOPREPARE) -message("Y") add_definitions(-DSOCI_POSTGRESQL_NOPREPARE=1) endif() soci_backend(PostgreSQL DEPENDS PostgreSQL - HEADERS soci-postgresql.h common.h - DESCRIPTION "SOCI backend for PostgreSQL database engine" + DESCRIPTION "SOCI backend for PostgreSQL" AUTHORS "Maciej Sobczak, Stephen Hutton" MAINTAINERS "Mateusz Loskot") boost_report_value(SOCI_POSTGRESQL_NOPARAMS) boost_report_value(SOCI_POSTGRESQL_NOBINDBYNAME) boost_report_value(SOCI_POSTGRESQL_NOPREPARE) - -add_subdirectory(test) diff --git a/src/backends/postgresql/Makefile.basic b/src/backends/postgresql/Makefile.basic index 01a70fca70..b4dd9c5c87 100644 --- a/src/backends/postgresql/Makefile.basic +++ b/src/backends/postgresql/Makefile.basic @@ -1,7 +1,7 @@ # The following variable is specific to this backend and its correct # values might depend on your environment - feel free to set it accordingly. -PGSQLINCLUDEDIR = -I/usr/include +PGSQLINCLUDEDIR = -I/usr/include/postgresql PGSQLLIBDIR = -L/usr/lib PGSQLLIBS = -lpq @@ -10,7 +10,7 @@ PGSQLLIBS = -lpq COMPILER = g++ CXXFLAGS = -Wall -pedantic -Wno-long-long SHARED_CXXFLAGS = ${CXXFLAGS} -fPIC -INCLUDEDIRS = -I../../core ${PGSQLINCLUDEDIR} +INCLUDEDIRS = -I../../../include -I../../../include/private ${PGSQLINCLUDEDIR} SHARED_LIBDIRS = ${PGSQLLIBDIR} SHARED_LIBS = ${PGSQLLIBS} ../../core/libsoci_core.a diff --git a/src/backends/postgresql/blob.cpp b/src/backends/postgresql/blob.cpp index f78205982f..1a4deed53a 100644 --- a/src/backends/postgresql/blob.cpp +++ b/src/backends/postgresql/blob.cpp @@ -6,7 +6,7 @@ // #define SOCI_POSTGRESQL_SOURCE -#include "soci-postgresql.h" +#include "soci/postgresql/soci-postgresql.h" #include // libpq #include #include diff --git a/src/backends/postgresql/common.cpp b/src/backends/postgresql/common.cpp index d06e8c830c..0445b02c78 100644 --- a/src/backends/postgresql/common.cpp +++ b/src/backends/postgresql/common.cpp @@ -5,8 +5,9 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include -#include +#include "soci/soci-platform.h" +#include "soci/soci-backend.h" +#include "soci-mktime.h" #include #include #include "common.h" @@ -81,31 +82,5 @@ void soci::details::postgresql::parse_std_tm(char const * buf, std::tm & t) } } - t.tm_isdst = -1; - t.tm_year = year - 1900; - t.tm_mon = month - 1; - t.tm_mday = day; - t.tm_hour = hour; - t.tm_min = minute; - t.tm_sec = second; - - std::mktime(&t); -} - -double soci::details::postgresql::string_to_double(char const * buf) -{ - double t; - int n; - int const converted = sscanf(buf, "%lf%n", &t, &n); - if (converted == 1 && static_cast(n) == strlen(buf)) - { - // successfully converted to double - // and no other characters were found in the buffer - - return t; - } - else - { - throw soci_error("Cannot convert data."); - } + details::mktime_from_ymdhms(t, year, month, day, hour, minute, second); } diff --git a/src/backends/postgresql/common.h b/src/backends/postgresql/common.h index 1f4d78db9a..4a96d2078e 100644 --- a/src/backends/postgresql/common.h +++ b/src/backends/postgresql/common.h @@ -8,7 +8,7 @@ #ifndef SOCI_POSTGRESQL_COMMON_H_INCLUDED #define SOCI_POSTGRESQL_COMMON_H_INCLUDED -#include "soci-postgresql.h" +#include "soci/postgresql/soci-postgresql.h" #include #include #include @@ -112,9 +112,6 @@ T string_to_unsigned_integer(char const * buf) } } -// helper function for parsing doubles -double string_to_double(char const * buf); - // helper function for parsing datetime values void parse_std_tm(char const * buf, std::tm & t); diff --git a/src/backends/postgresql/error.cpp b/src/backends/postgresql/error.cpp index ae674f051a..e7b59fb901 100644 --- a/src/backends/postgresql/error.cpp +++ b/src/backends/postgresql/error.cpp @@ -6,10 +6,8 @@ // #define SOCI_POSTGRESQL_SOURCE -#include "soci-postgresql.h" -#include "error.h" +#include "soci/postgresql/soci-postgresql.h" #include -#include using namespace soci; using namespace soci::details; @@ -18,7 +16,6 @@ postgresql_soci_error::postgresql_soci_error( std::string const & msg, char const *sqlst) : soci_error(msg) { - assert(std::strlen(sqlst) == 5); std::memcpy(sqlstate_, sqlst, 5); } diff --git a/src/backends/postgresql/factory.cpp b/src/backends/postgresql/factory.cpp index 90550080c3..4deb5a5307 100644 --- a/src/backends/postgresql/factory.cpp +++ b/src/backends/postgresql/factory.cpp @@ -6,8 +6,8 @@ // #define SOCI_POSTGRESQL_SOURCE -#include "soci-postgresql.h" -#include +#include "soci/postgresql/soci-postgresql.h" +#include "soci/backend-loader.h" #include // libpq #ifdef SOCI_POSTGRESQL_NOPARAMS diff --git a/src/backends/postgresql/row-id.cpp b/src/backends/postgresql/row-id.cpp index 442371ce31..6fb488ce23 100644 --- a/src/backends/postgresql/row-id.cpp +++ b/src/backends/postgresql/row-id.cpp @@ -6,7 +6,7 @@ // #define SOCI_POSTGRESQL_SOURCE -#include "soci-postgresql.h" +#include "soci/postgresql/soci-postgresql.h" #include // libpq #include #include diff --git a/src/backends/postgresql/session.cpp b/src/backends/postgresql/session.cpp index 74d128cbf7..a5b23bba70 100644 --- a/src/backends/postgresql/session.cpp +++ b/src/backends/postgresql/session.cpp @@ -6,9 +6,10 @@ // #define SOCI_POSTGRESQL_SOURCE -#include "soci-postgresql.h" -#include "session.h" -#include +#include "soci/soci-platform.h" +#include "soci/postgresql/soci-postgresql.h" +#include "soci/session.h" +#include "soci/connection-parameters.h" #include // libpq #include #include @@ -22,13 +23,20 @@ #endif // SOCI_POSTGRESQL_NOBINDBYNAME #endif // SOCI_POSTGRESQL_NOPARAMS -#ifdef _MSC_VER -#pragma warning(disable:4355 4996) -#endif - using namespace soci; using namespace soci::details; +namespace // unnamed +{ + +// helper function for hardcoded queries +void hard_exec(PGconn * conn, char const * query, char const * errMsg) +{ + postgresql_result(PQexec(conn, query)).check_for_errors(errMsg); +} + +} // namespace unnamed + postgresql_session_backend::postgresql_session_backend( connection_parameters const& parameters) : statementCount_(0) @@ -47,6 +55,16 @@ postgresql_session_backend::postgresql_session_backend( throw soci_error(msg); } + // Increase the number of digits used for floating point values to ensure + // that the conversions to/from text round trip correctly, which is not the + // 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, + version >= 90000 ? "SET extra_float_digits = 3" + : "SET extra_float_digits = 2", + "Cannot set extra_float_digits parameter"); + conn_ = conn; } @@ -55,17 +73,6 @@ postgresql_session_backend::~postgresql_session_backend() clean_up(); } -namespace // unnamed -{ - -// helper function for hardcoded queries -void hard_exec(PGconn * conn, char const * query, char const * errMsg) -{ - postgresql_result(PQexec(conn, query)).check_for_errors(errMsg); -} - -} // namespace unnamed - void postgresql_session_backend::begin() { hard_exec(conn_, "BEGIN", "Cannot begin transaction."); diff --git a/src/backends/postgresql/standard-into-type.cpp b/src/backends/postgresql/standard-into-type.cpp index e6dd581036..3f54d8af78 100644 --- a/src/backends/postgresql/standard-into-type.cpp +++ b/src/backends/postgresql/standard-into-type.cpp @@ -6,11 +6,13 @@ // #define SOCI_POSTGRESQL_SOURCE -#include -#include "soci-postgresql.h" +#include "soci/soci-platform.h" +#include "soci/postgresql/soci-postgresql.h" +#include "soci-cstrtod.h" #include "common.h" -#include "rowid.h" -#include "blob.h" +#include "soci/rowid.h" +#include "soci/blob.h" +#include "soci-exchange-cast.h" #include // libpq #include #include @@ -87,53 +89,29 @@ void postgresql_standard_into_type_backend::post_fetch( switch (type_) { case x_char: - { - char * dest = static_cast(data_); - *dest = *buf; - } + exchange_type_cast(data_) = *buf; break; case x_stdstring: - { - std::string * dest = static_cast(data_); - dest->assign(buf); - } + exchange_type_cast(data_) = buf; break; case x_short: - { - short * dest = static_cast(data_); - *dest = string_to_integer(buf); - } + exchange_type_cast(data_) = string_to_integer(buf); break; case x_integer: - { - int * dest = static_cast(data_); - *dest = string_to_integer(buf); - } + exchange_type_cast(data_) = string_to_integer(buf); break; case x_long_long: - { - long long * dest = static_cast(data_); - *dest = string_to_integer(buf); - } + exchange_type_cast(data_) = string_to_integer(buf); break; case x_unsigned_long_long: - { - unsigned long long * dest = static_cast(data_); - *dest = string_to_unsigned_integer(buf); - } + exchange_type_cast(data_) = string_to_unsigned_integer(buf); break; case x_double: - { - double * dest = static_cast(data_); - *dest = string_to_double(buf); - } + exchange_type_cast(data_) = cstring_to_double(buf); break; case x_stdtm: - { - // attempt to parse the string and convert to std::tm - std::tm * dest = static_cast(data_); - parse_std_tm(buf, *dest); - } + // attempt to parse the string and convert to std::tm + parse_std_tm(buf, exchange_type_cast(data_)); break; case x_rowid: { diff --git a/src/backends/postgresql/standard-use-type.cpp b/src/backends/postgresql/standard-use-type.cpp index 8e401513f2..94dc99c1a1 100644 --- a/src/backends/postgresql/standard-use-type.cpp +++ b/src/backends/postgresql/standard-use-type.cpp @@ -6,10 +6,12 @@ // #define SOCI_POSTGRESQL_SOURCE -#include "soci-postgresql.h" -#include "blob.h" -#include "rowid.h" -#include +#include "soci/postgresql/soci-postgresql.h" +#include "soci/blob.h" +#include "soci/rowid.h" +#include "soci/soci-platform.h" +#include "soci-dtocstr.h" +#include "soci-exchange-cast.h" #include // libpq #include #include @@ -24,11 +26,6 @@ #endif // SOCI_POSTGRESQL_NOBINDBYNAME #endif // SOCI_POSTGRESQL_NOPARAMS -#ifdef _MSC_VER -#pragma warning(disable:4355 4996) -#define snprintf _snprintf -#endif - using namespace soci; using namespace soci::details; @@ -68,15 +65,15 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind) case x_char: { buf_ = new char[2]; - buf_[0] = *static_cast(data_); + buf_[0] = exchange_type_cast(data_); buf_[1] = '\0'; } break; case x_stdstring: { - std::string * s = static_cast(data_); - buf_ = new char[s->size() + 1]; - std::strcpy(buf_, s->c_str()); + std::string const& s = exchange_type_cast(data_); + buf_ = new char[s.size() + 1]; + std::strcpy(buf_, s.c_str()); } break; case x_short: @@ -85,7 +82,7 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind) = std::numeric_limits::digits10 + 3; buf_ = new char[bufSize]; snprintf(buf_, bufSize, "%d", - static_cast(*static_cast(data_))); + static_cast(exchange_type_cast(data_))); } break; case x_integer: @@ -94,7 +91,7 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind) = std::numeric_limits::digits10 + 3; buf_ = new char[bufSize]; snprintf(buf_, bufSize, "%d", - *static_cast(data_)); + exchange_type_cast(data_)); } break; case x_long_long: @@ -103,7 +100,7 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind) = std::numeric_limits::digits10 + 3; buf_ = new char[bufSize]; snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "d", - *static_cast(data_)); + exchange_type_cast(data_)); } break; case x_unsigned_long_long: @@ -112,18 +109,15 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind) = std::numeric_limits::digits10 + 2; buf_ = new char[bufSize]; snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "u", - *static_cast(data_)); + exchange_type_cast(data_)); } break; case x_double: { - // no need to overengineer it (KISS)... + std::string const s = double_to_cstring(exchange_type_cast(data_)); - std::size_t const bufSize = 100; - buf_ = new char[bufSize]; - - snprintf(buf_, bufSize, "%.20g", - *static_cast(data_)); + buf_ = new char[s.size() + 1]; + std::strcpy(buf_, s.c_str()); } break; case x_stdtm: @@ -131,10 +125,10 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind) std::size_t const bufSize = 20; buf_ = new char[bufSize]; - std::tm * t = static_cast(data_); + std::tm const& t = exchange_type_cast(data_); snprintf(buf_, bufSize, "%d-%02d-%02d %02d:%02d:%02d", - t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, - t->tm_hour, t->tm_min, t->tm_sec); + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, + t.tm_hour, t.tm_min, t.tm_sec); } break; case x_rowid: diff --git a/src/backends/postgresql/statement.cpp b/src/backends/postgresql/statement.cpp index b93014ce54..4499305fbf 100644 --- a/src/backends/postgresql/statement.cpp +++ b/src/backends/postgresql/statement.cpp @@ -6,10 +6,9 @@ // #define SOCI_POSTGRESQL_SOURCE -#include "soci-postgresql.h" -#include +#include "soci/postgresql/soci-postgresql.h" +#include "soci/soci-platform.h" #include // libpq -#include #include #include #include @@ -23,10 +22,6 @@ #endif // SOCI_POSTGRESQL_NOBINDBYNAME #endif // SOCI_POSTGRESQL_NOPARAMS -#ifdef _MSC_VER -#pragma warning(disable:4355) -#endif - using namespace soci; using namespace soci::details; @@ -64,10 +59,10 @@ void postgresql_statement_backend::alloc() void postgresql_statement_backend::clean_up() { - // 'reset' the value for a + // 'reset' the value for a // potential new execution. rowsAffectedBulk_ = -1; - + // nothing to do here } @@ -181,10 +176,13 @@ void postgresql_statement_backend::prepare(std::string const & query, if (stType == st_repeatable_query) { - assert(statementName_.empty()); + if (!statementName_.empty()) + { + throw soci_error("Shouldn't already have a prepared statement."); + } // Holding the name temporarily in this var because - // if it fails to prepare it we can't DEALLOCATE it. + // if it fails to prepare it we can't DEALLOCATE it. std::string statementName = session_.get_next_statement_name(); postgresql_result result( @@ -330,10 +328,10 @@ postgresql_statement_backend::execute(int number) // preserve the number of rows affected so far. rowsAffectedBulk_ = rowsAffectedBulkTemp; - + result_.check_for_errors("Cannot execute query."); - - rowsAffectedBulkTemp += get_affected_rows(); + + rowsAffectedBulkTemp += get_affected_rows(); } } rowsAffectedBulk_ = rowsAffectedBulkTemp; @@ -473,6 +471,11 @@ int postgresql_statement_backend::get_number_of_rows() return numberOfRows_ - currentRow_; } +std::string postgresql_statement_backend::get_parameter_name(int index) const +{ + return names_.at(index); +} + std::string postgresql_statement_backend::rewrite_for_procedure_call( std::string const & query) { @@ -512,6 +515,7 @@ void postgresql_statement_backend::describe_column(int colNum, data_type & type, case 142: // xml case 114: // json case 17: // bytea + case 2950: // uuid type = dt_string; break; @@ -541,7 +545,7 @@ void postgresql_statement_backend::describe_column(int colNum, data_type & type, case 20: // int8 type = dt_long_long; break; - + default: { int form = PQfformat(result_, pos); diff --git a/src/backends/postgresql/test/.gitignore b/src/backends/postgresql/test/.gitignore deleted file mode 100644 index b97b4c72ce..0000000000 --- a/src/backends/postgresql/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test_postgresql diff --git a/src/backends/postgresql/test/Makefile.basic b/src/backends/postgresql/test/Makefile.basic deleted file mode 100644 index 9c94275e6c..0000000000 --- a/src/backends/postgresql/test/Makefile.basic +++ /dev/null @@ -1,22 +0,0 @@ -# The following variable is specific to this backend and its correct -# values might depend on your environment - feel free to set it accordingly. - -PGSQLINCLUDEDIR = -I/usr/include -PGSQLLIBDIR = -L/usr/lib -PGSQLLIBS = -lpq - -# The rest of the Makefile is indepentent of the target environment. - -COMPILER = g++ -CXXFLAGS = -Wall -pedantic -Wno-long-long -INCLUDEDIRS = -I.. -I../../../core/test -I../../../core ${PGSQLINCLUDEDIR} -LIBDIRS = -L.. -L../../../core ${PGSQLLIBDIR} -LIBS = -lsoci_core -lsoci_postgresql -ldl ${PGSQLLIBS} - - -test-postgresql : test-postgresql.cpp - ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} - - -clean : - rm -f test-postgresql diff --git a/src/backends/postgresql/test/test-postgresql.cpp b/src/backends/postgresql/test/test-postgresql.cpp deleted file mode 100644 index e43042509b..0000000000 --- a/src/backends/postgresql/test/test-postgresql.cpp +++ /dev/null @@ -1,847 +0,0 @@ -// -// Copyright (C) 2004-2008 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.h" -#include "soci-postgresql.h" -#include "common-tests.h" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace soci; -using namespace soci::tests; - -std::string connectString; -backend_factory const &backEnd = *soci::factory_postgresql(); - -// Postgres-specific tests - -struct oid_table_creator : public table_creator_base -{ - oid_table_creator(session& sql) - : table_creator_base(sql) - { - sql << "create table soci_test (" - " id integer," - " name varchar(100)" - ") with oids"; - } -}; - -// ROWID test -// Note: in PostgreSQL, there is no ROWID, there is OID. -// It is still provided as a separate type for "portability", -// whatever that means. -void test1() -{ - try - { - session sql(backEnd, connectString); - - oid_table_creator tableCreator(sql); - - sql << "insert into soci_test(id, name) values(7, \'John\')"; - - rowid rid(sql); - sql << "select oid from soci_test where id = 7", into(rid); - - int id; - std::string name; - -#ifndef SOCI_POSTGRESQL_NOPARAMS - - sql << "select id, name from soci_test where oid = :rid", - into(id), into(name), use(rid); - -#else - // Older PostgreSQL does not support use elements. - - postgresql_rowid_backend *rbe - = static_cast(rid.get_backend()); - - unsigned long oid = rbe->value_; - - sql << "select id, name from soci_test where oid = " << oid, - into(id), into(name); - -#endif // SOCI_POSTGRESQL_NOPARAMS - - assert(id == 7); - assert(name == "John"); - - // Must not cause the application to crash. - statement st(sql); - st.prepare(""); // Throws an exception in some versions. - } - catch(...) - { - } - std::cout << "test 1 passed" << std::endl; -} - -// function call test -class function_creator : function_creator_base -{ -public: - - function_creator(session & sql) - : function_creator_base(sql) - { - // before a language can be used it must be defined - // if it has already been defined then an error will occur - try { sql << "create language plpgsql"; } - catch (soci_error const &) {} // ignore if error - -#ifndef SOCI_POSTGRESQL_NOPARAMS - - sql << - "create or replace function soci_test(msg varchar) " - "returns varchar as $$ " - "declare x int := 1;" - "begin " - " return msg; " - "end $$ language plpgsql"; -#else - - sql << - "create or replace function soci_test(varchar) " - "returns varchar as \' " - "declare x int := 1;" - "begin " - " return $1; " - "end \' language plpgsql"; -#endif - } - -protected: - - std::string drop_statement() - { - return "drop function soci_test(varchar)"; - } -}; - -void test2() -{ - { - session sql(backEnd, connectString); - - function_creator functionCreator(sql); - - std::string in("my message"); - std::string out; - -#ifndef SOCI_POSTGRESQL_NOPARAMS - - statement st = (sql.prepare << - "select soci_test(:input)", - into(out), - use(in, "input")); - -#else - // Older PostgreSQL does not support use elements. - - statement st = (sql.prepare << - "select soci_test(\'" << in << "\')", - into(out)); - -#endif // SOCI_POSTGRESQL_NOPARAMS - - st.execute(true); - assert(out == in); - - // explicit procedure syntax - { - std::string in("my message2"); - std::string out; - -#ifndef SOCI_POSTGRESQL_NOPARAMS - - procedure proc = (sql.prepare << - "soci_test(:input)", - into(out), use(in, "input")); - -#else - // Older PostgreSQL does not support use elements. - - procedure proc = (sql.prepare << - "soci_test(\'" << in << "\')", into(out)); - -#endif // SOCI_POSTGRESQL_NOPARAMS - - proc.execute(true); - assert(out == in); - } - } - - std::cout << "test 2 passed" << std::endl; -} - -// BLOB test -struct blob_table_creator : public table_creator_base -{ - blob_table_creator(session & sql) - : table_creator_base(sql) - { - sql << - "create table soci_test (" - " id integer," - " img oid" - ")"; - } -}; - -void test3() -{ - { - 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); - assert(b.get_len() == 0); - - b.write(0, buf, sizeof(buf)); - assert(b.get_len() == sizeof(buf)); - - b.append(buf, sizeof(buf)); - assert(b.get_len() == 2 * sizeof(buf)); - } - { - blob b(sql); - sql << "select img from soci_test where id = 7", into(b); - assert(b.get_len() == 2 * sizeof(buf)); - char buf2[100]; - b.read(0, buf2, 10); - assert(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 << ")"; - } - - std::cout << "test 3 passed" << std::endl; -} - -struct longlong_table_creator : table_creator_base -{ - longlong_table_creator(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val int8)"; - } -}; - -// long long test -void test4() -{ - { - session sql(backEnd, connectString); - - longlong_table_creator tableCreator(sql); - - long long v1 = 1000000000000LL; - assert(v1 / 1000000 == 1000000); - - sql << "insert into soci_test(val) values(:val)", use(v1); - - long long v2 = 0LL; - sql << "select val from soci_test", into(v2); - - assert(v2 == v1); - } - - // vector - { - session sql(backEnd, connectString); - - longlong_table_creator tableCreator(sql); - - std::vector v1; - v1.push_back(1000000000000LL); - v1.push_back(1000000000001LL); - v1.push_back(1000000000002LL); - v1.push_back(1000000000003LL); - v1.push_back(1000000000004LL); - - sql << "insert into soci_test(val) values(:val)", use(v1); - - std::vector v2(10); - sql << "select val from soci_test order by val desc", into(v2); - - assert(v2.size() == 5); - assert(v2[0] == 1000000000004LL); - assert(v2[1] == 1000000000003LL); - assert(v2[2] == 1000000000002LL); - assert(v2[3] == 1000000000001LL); - assert(v2[4] == 1000000000000LL); - } - - std::cout << "test 4 passed" << std::endl; -} - -// unsigned long long test -void test4ul() -{ - { - session sql(backEnd, connectString); - - longlong_table_creator tableCreator(sql); - - unsigned long long v1 = 1000000000000ULL; - assert(v1 / 1000000 == 1000000); - - sql << "insert into soci_test(val) values(:val)", use(v1); - - unsigned long long v2 = 0ULL; - sql << "select val from soci_test", into(v2); - - assert(v2 == v1); - } -} - -struct boolean_table_creator : table_creator_base -{ - boolean_table_creator(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val boolean)"; - } -}; - -void test5() -{ - { - session sql(backEnd, connectString); - - boolean_table_creator tableCreator(sql); - - int i1 = 0; - - sql << "insert into soci_test(val) values(:val)", use(i1); - - int i2 = 7; - sql << "select val from soci_test", into(i2); - - assert(i2 == i1); - - sql << "update soci_test set val = true"; - sql << "select val from soci_test", into(i2); - assert(i2 == 1); - } - - std::cout << "test 5 passed" << std::endl; -} - -// dynamic backend test -void test6() -{ - try - { - session sql("nosuchbackend://" + connectString); - assert(false); - } - catch (soci_error const & e) - { - assert(e.what() == std::string("Failed to open: libsoci_nosuchbackend.so")); - } - - { - dynamic_backends::register_backend("pgsql", backEnd); - - std::vector backends = dynamic_backends::list_all(); - assert(backends.size() == 1); - assert(backends[0] == "pgsql"); - - { - session sql("pgsql://" + connectString); - } - - dynamic_backends::unload("pgsql"); - - backends = dynamic_backends::list_all(); - assert(backends.empty()); - } - - { - session sql("postgresql://" + connectString); - } - - std::cout << "test 6 passed" << std::endl; -} - -void test7() -{ - { - session sql(backEnd, connectString); - - int i; - sql << "select 123", into(i); - assert(i == 123); - - try - { - sql << "select 'ABC'", into (i); - assert(false); - } - catch (soci_error const & e) - { - assert(e.what() == std::string("Cannot convert data.")); - } - } - - std::cout << "test 7 passed" << std::endl; -} - -void test8() -{ - { - session sql(backEnd, connectString); - - assert(sql.get_backend_name() == "postgresql"); - } - - std::cout << "test 8 passed" << std::endl; -} - -// test for double-colon cast in SQL expressions -void test9() -{ - { - session sql(backEnd, connectString); - - int a = 123; - int b = 0; - sql << "select :a::integer", use(a), into(b); - assert(b == a); - } - - std::cout << "test 9 passed" << std::endl; -} - -// test for date, time and timestamp parsing -void test10() -{ - { - session sql(backEnd, connectString); - - std::string someDate = "2009-06-17 22:51:03.123"; - std::tm t1, t2, t3; - - sql << "select :sd::date, :sd::time, :sd::timestamp", - use(someDate, "sd"), into(t1), into(t2), into(t3); - - // t1 should contain only the date part - assert(t1.tm_year == 2009 - 1900); - assert(t1.tm_mon == 6 - 1); - assert(t1.tm_mday == 17); - assert(t1.tm_hour == 0); - assert(t1.tm_min == 0); - assert(t1.tm_sec == 0); - - // t2 should contain only the time of day part - assert(t2.tm_year == 0); - assert(t2.tm_mon == 0); - assert(t2.tm_mday == 1); - assert(t2.tm_hour == 22); - assert(t2.tm_min == 51); - assert(t2.tm_sec == 3); - - // t3 should contain all information - assert(t3.tm_year == 2009 - 1900); - assert(t3.tm_mon == 6 - 1); - assert(t3.tm_mday == 17); - assert(t3.tm_hour == 22); - assert(t3.tm_min == 51); - assert(t3.tm_sec == 3); - } - - std::cout << "test 10 passed" << std::endl; -} - -// test for number of affected rows - -struct table_creator_for_test11 : table_creator_base -{ - table_creator_for_test11(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val integer)"; - } -}; - -void test11() -{ - { - session sql(backEnd, connectString); - - table_creator_for_test11 tableCreator(sql); - - for (int i = 0; i != 10; i++) - { - sql << "insert into soci_test(val) values(:val)", use(i); - } - - statement st1 = (sql.prepare << - "update soci_test set val = val + 1"); - st1.execute(false); - - assert(st1.get_affected_rows() == 10); - - statement st2 = (sql.prepare << - "delete from soci_test where val <= 5"); - st2.execute(false); - - assert(st2.get_affected_rows() == 5); - } - - std::cout << "test 11 passed" << std::endl; -} - -// test INSERT INTO ... RETURNING syntax - -struct table_creator_for_test12 : table_creator_base -{ - table_creator_for_test12(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(sid serial, txt text)"; - } -}; - -void test12() -{ - { - session sql(backEnd, connectString); - - table_creator_for_test12 tableCreator(sql); - - std::vector ids(10); - for (std::size_t i = 0; i != ids.size(); i++) - { - long sid(0); - std::string txt("abc"); - sql << "insert into soci_test(txt) values(:txt) returning sid", use(txt, "txt"), into(sid); - ids[i] = sid; - } - - std::vector ids2(ids.size()); - sql << "select sid from soci_test order by sid", into(ids2); - assert(std::equal(ids.begin(), ids.end(), ids2.begin())); - } - - std::cout << "test 12 passed" << std::endl; -} - -struct bytea_table_creator : public table_creator_base -{ - bytea_table_creator(session& sql) - : table_creator_base(sql) - { - sql << "drop table if exists soci_test;"; - sql << "create table soci_test ( val bytea null )"; - } -}; - -void test_bytea() -{ - { - session sql(backEnd, connectString); - bytea_table_creator tableCreator(sql); - - int v = 0x0A0B0C0D; - unsigned char* b = reinterpret_cast(&v); - std::string data; - std::copy(b, b + sizeof(v), std::back_inserter(data)); - { - - sql << "insert into soci_test(val) values(:val)", use(data); - - // 1) into string, no Oid mapping - std::string bin1; - sql << "select val from soci_test", into(bin1); - assert(bin1 == "\\x0d0c0b0a"); - - // 2) Oid-to-dt_string mapped - row r; - sql << "select * from soci_test", into(r); - - assert(r.size() == 1); - column_properties const& props = r.get_properties(0); - assert(props.get_data_type() == soci::dt_string); - std::string bin2 = r.get(0); - assert(bin2 == "\\x0d0c0b0a"); - } - } - std::cout << "test bytea passed" << std::endl; -} - -// json -struct table_creator_json : public table_creator_base -{ - table_creator_json(session& sql) - : table_creator_base(sql) - { - sql << "drop table if exists soci_json_test;"; - sql << "create table soci_json_test(data json)"; - } -}; - -// Return 9,2 for 9.2.3 -typedef std::pair server_version; - -server_version get_postgresql_version(session& sql) -{ - std::string version; - std::pair result; - sql << "select version()",into(version); - if (sscanf(version.c_str(),"PostgreSQL %i.%i", &result.first, &result.second) < 2) - { - throw std::runtime_error("Failed to retrieve PostgreSQL version number"); - } - return result; -} - -// Test JSON. Only valid for PostgreSQL Server 9.2++ -void test_json() -{ - session sql(backEnd, connectString); - server_version version = get_postgresql_version(sql); - if ( version >= server_version(9,2)) - { - bool exception = false; - std::string result; - std::string valid_input = "{\"tool\":\"soci\",\"result\":42}"; - std::string invalid_input = "{\"tool\":\"other\",\"result\":invalid}"; - - table_creator_json tableCreator(sql); - - sql << "insert into soci_json_test (data) values(:data)",use(valid_input); - sql << "select data from soci_json_test",into(result); - assert(result == valid_input); - - try - { - sql << "insert into soci_json_test (data) values(:data)",use(invalid_input); - } - catch(soci_error& e) - { - (void)e; - exception = true; - } - assert(exception); - std::cout << "test json passed" << std::endl; - } - else - { - std::cout << "test json skipped (PostgreSQL >= 9.2 required, found " << version.first << "." << version.second << ")" << std::endl; - } -} - -struct table_creator_text : public table_creator_base -{ - table_creator_text(session& sql) : table_creator_base(sql) - { - sql << "drop table if exists soci_test;"; - sql << "create table soci_test(name varchar(20))"; - } -}; - -// Test deallocate_prepared_statement called for non-existing statement -// which creation failed due to invalid SQL syntax. -// https://github.com/SOCI/soci/issues/116 -void test_statement_prepare_failure() -{ - { - session sql(backEnd, connectString); - table_creator_text tableCreator(sql); - - try - { - // types mismatch should lead to PQprepare failure - statement get_trades = - (sql.prepare - << "select * from soci_test where name=9999"); - assert(false); - } - catch(soci_error const& e) - { - std::string const msg(e.what()); - // poor-man heuristics - assert(msg.find("prepared statement") == std::string::npos); - assert(msg.find("operator does not exist") != std::string::npos); - } - } - std::cout << "test_statement_prepare_failure passed" << std::endl; -} - -// Test the support of PostgreSQL-style casts with ORM -void test_orm_cast() -{ - session sql(backEnd, connectString); - values v; - v.set("a", 1); - sql << "select :a::int", use(v); // Must not throw an exception! -} - -// -// Support for soci Common Tests -// - -// DDL Creation objects for common tests -struct table_creator_one : public table_creator_base -{ - table_creator_one(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(id integer, val integer, c char, " - "str varchar(20), sh int2, ul numeric(20), d float8, " - "tm timestamp, i1 integer, i2 integer, i3 integer, " - "name varchar(20))"; - } -}; - -struct table_creator_two : public table_creator_base -{ - table_creator_two(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(num_float float8, num_int integer," - " name varchar(20), sometime timestamp, chr char)"; - } -}; - -struct table_creator_three : public table_creator_base -{ - table_creator_three(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(name varchar(100) not null, " - "phone varchar(15))"; - } -}; - -struct table_creator_for_get_affected_rows : table_creator_base -{ - table_creator_for_get_affected_rows(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val integer)"; - } -}; - -// Common tests context -class test_context : public test_context_base -{ -public: - test_context(backend_factory const &backEnd, std::string const &connectString) - : test_context_base(backEnd, connectString) - {} - - table_creator_base* table_creator_1(session& s) const - { - return new table_creator_one(s); - } - - table_creator_base* table_creator_2(session& s) const - { - return new table_creator_two(s); - } - - table_creator_base* table_creator_3(session& s) const - { - return new table_creator_three(s); - } - - table_creator_base* table_creator_4(session& s) const - { - return new table_creator_for_get_affected_rows(s); - } - - std::string to_date_time(std::string const &datdt_string) const - { - return "timestamptz(\'" + datdt_string + "\')"; - } -}; - -int main(int argc, char** argv) -{ - -#ifdef _MSC_VER - // Redirect errors, unrecoverable problems, and assert() failures to STDERR, - // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. - // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); -#endif //_MSC_VER - - if (argc == 2) - { - connectString = argv[1]; - } - else - { - std::cout << "usage: " << argv[0] - << " connectstring\n" - << "example: " << argv[0] - << " \'connect_string_for_PostgreSQL\'\n"; - return EXIT_FAILURE; - } - - try - { - test_context tc(backEnd, connectString); - common_tests tests(tc); - tests.run(); - - std::cout << "\nSOCI PostgreSQL Tests:\n\n"; - test1(); - test2(); - test3(); - test4(); - test4ul(); - test5(); - //test6(); - std::cout << "test 6 skipped (dynamic backend)\n"; - test7(); - test8(); - test9(); - test10(); - test11(); - test12(); - test_bytea(); - test_json(); - test_statement_prepare_failure(); - test_orm_cast(); - - std::cout << "\nOK, all tests passed.\n\n"; - - return EXIT_SUCCESS; - } - catch (std::exception const & e) - { - std::cout << e.what() << '\n'; - } - return EXIT_FAILURE; -} diff --git a/src/backends/postgresql/vector-into-type.cpp b/src/backends/postgresql/vector-into-type.cpp index a875d30125..df9481a015 100644 --- a/src/backends/postgresql/vector-into-type.cpp +++ b/src/backends/postgresql/vector-into-type.cpp @@ -6,11 +6,11 @@ // #define SOCI_POSTGRESQL_SOURCE -#include -#include "soci-postgresql.h" +#include "soci/soci-platform.h" +#include "soci/postgresql/soci-postgresql.h" +#include "soci-cstrtod.h" #include "common.h" #include // libpq -#include #include #include #include @@ -131,7 +131,7 @@ void postgresql_vector_into_type_backend::post_fetch(bool gotData, indicator * i break; case x_double: { - double const val = string_to_double(buf); + double const val = cstring_to_double(buf); set_invector_(data_, i, val); } break; @@ -170,8 +170,6 @@ void resizevector_(void * p, std::size_t sz) void postgresql_vector_into_type_backend::resize(std::size_t sz) { - assert(sz < 10u*std::numeric_limits::max()); // Not a strong constraint, for debugging only. Notice my fix is even worse - switch (type_) { // simple cases diff --git a/src/backends/postgresql/vector-use-type.cpp b/src/backends/postgresql/vector-use-type.cpp index 00d47216a0..a8809d71d9 100644 --- a/src/backends/postgresql/vector-use-type.cpp +++ b/src/backends/postgresql/vector-use-type.cpp @@ -6,8 +6,9 @@ // #define SOCI_POSTGRESQL_SOURCE -#include -#include "soci-postgresql.h" +#include "soci/soci-platform.h" +#include "soci/postgresql/soci-postgresql.h" +#include "soci-dtocstr.h" #include "common.h" #include // libpq #include @@ -23,10 +24,6 @@ #endif // SOCI_POSTGRESQL_NOBINDBYNAME #endif // SOCI_POSTGRESQL_NOPARAMS -#ifdef _MSC_VER -#pragma warning(disable:4355 4996) -#define snprintf _snprintf -#endif using namespace soci; using namespace soci::details; @@ -137,16 +134,14 @@ void postgresql_vector_use_type_backend::pre_use(indicator const * ind) break; case x_double: { - // no need to overengineer it (KISS)... - std::vector * pv = static_cast *>(data_); std::vector & v = *pv; - std::size_t const bufSize = 100; - buf = new char[bufSize]; + std::string const s = double_to_cstring(v[i]); - snprintf(buf, bufSize, "%.20g", v[i]); + buf = new char[s.size() + 1]; + std::strcpy(buf, s.c_str()); } break; case x_stdtm: diff --git a/src/backends/sqlite3/CMakeLists.txt b/src/backends/sqlite3/CMakeLists.txt index 99c42e5af3..cd90b7ae20 100644 --- a/src/backends/sqlite3/CMakeLists.txt +++ b/src/backends/sqlite3/CMakeLists.txt @@ -11,9 +11,6 @@ soci_backend(SQLite3 DEPENDS SQLite3 - HEADERS soci-sqlite3.h common.h - DESCRIPTION "SOCI backend for SQLite 3 database engine" + DESCRIPTION "SOCI backend for SQLite 3" AUTHORS "Maciej Sobczak, Stephen Hutton, David Courtney" MAINTAINERS "Maciej Sobczak, Mateusz Loskot") - -add_subdirectory(test) diff --git a/src/backends/sqlite3/Makefile.basic b/src/backends/sqlite3/Makefile.basic index 374234224d..3b2101b8f5 100644 --- a/src/backends/sqlite3/Makefile.basic +++ b/src/backends/sqlite3/Makefile.basic @@ -11,7 +11,7 @@ CXXFLAGSSO = ${CXXFLAGS} -fPIC INCLUDEDIRS = -I../../core ${SQLITE3INCLUDEDIR} -OBJECTS = blob.o factory.o row-id.o session.o standard-into-type.o \ +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 @@ -29,6 +29,9 @@ libsoci_sqlite3.a : ${OBJECTS} blob.o : blob.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} +error.o : error.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + common.o : common.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} @@ -64,6 +67,9 @@ shared : ${OBJECTSSO} blob-s.o : blob.cpp ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} +error-s.o : error.cpp + ${COMPILER} -c -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} + common-s.o : common.cpp ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} diff --git a/src/backends/sqlite3/blob.cpp b/src/backends/sqlite3/blob.cpp index 834477ffac..6cb6266cd4 100644 --- a/src/backends/sqlite3/blob.cpp +++ b/src/backends/sqlite3/blob.cpp @@ -5,7 +5,8 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci-sqlite3.h" +#include "soci/sqlite3/soci-sqlite3.h" + #include #include diff --git a/src/backends/sqlite3/common.cpp b/src/backends/sqlite3/common.cpp index 4e5d279db7..dc29320960 100644 --- a/src/backends/sqlite3/common.cpp +++ b/src/backends/sqlite3/common.cpp @@ -5,9 +5,10 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include +#include "soci/soci-platform.h" #include "common.h" -#include "soci-backend.h" +#include "soci/soci-backend.h" +#include "soci-mktime.h" // std #include #include @@ -54,14 +55,5 @@ void soci::details::sqlite3::parse_std_tm(char const *buf, std::tm &t) second = parse10(p1, p2, errMsg); } - t.tm_isdst = -1; - t.tm_year = year - 1900; - t.tm_mon = month - 1; - t.tm_mday = day; - t.tm_hour = hour; - t.tm_min = minute; - t.tm_sec = second; - - std::mktime(&t); + 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 72aeb97ae6..f8d5275873 100644 --- a/src/backends/sqlite3/common.h +++ b/src/backends/sqlite3/common.h @@ -8,7 +8,7 @@ #ifndef SOCI_SQLITE3_COMMON_H_INCLUDED #define SOCI_SQLITE3_COMMON_H_INCLUDED -#include +#include "soci/error.h" #include #include #include diff --git a/src/backends/sqlite3/error.cpp b/src/backends/sqlite3/error.cpp new file mode 100644 index 0000000000..ba24da0136 --- /dev/null +++ b/src/backends/sqlite3/error.cpp @@ -0,0 +1,23 @@ +// +// Copyright 2014 SimpliVT Corporation +// 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) +// + +#define SOCI_SQLITE3_SOURCE +#include "soci/sqlite3/soci-sqlite3.h" +#include + +using namespace soci; + +sqlite3_soci_error::sqlite3_soci_error( + std::string const & msg, int result) + : soci_error(msg), result_(result) +{ +} + +int sqlite3_soci_error::result() const +{ + return result_; +} diff --git a/src/backends/sqlite3/factory.cpp b/src/backends/sqlite3/factory.cpp index 02691f5975..f69ec81c2e 100644 --- a/src/backends/sqlite3/factory.cpp +++ b/src/backends/sqlite3/factory.cpp @@ -6,8 +6,8 @@ // #define SOCI_SQLITE3_SOURCE -#include "soci-sqlite3.h" -#include +#include "soci/sqlite3/soci-sqlite3.h" +#include "soci/backend-loader.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/sqlite3/row-id.cpp b/src/backends/sqlite3/row-id.cpp index b0593fbb13..95eb565bdc 100644 --- a/src/backends/sqlite3/row-id.cpp +++ b/src/backends/sqlite3/row-id.cpp @@ -5,7 +5,7 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci-sqlite3.h" +#include "soci/sqlite3/soci-sqlite3.h" #ifdef _MSC_VER #pragma warning(disable:4355) diff --git a/src/backends/sqlite3/session.cpp b/src/backends/sqlite3/session.cpp index 7f953f38f2..37eb62bb85 100644 --- a/src/backends/sqlite3/session.cpp +++ b/src/backends/sqlite3/session.cpp @@ -6,9 +6,9 @@ // -#include "soci-sqlite3.h" +#include "soci/sqlite3/soci-sqlite3.h" -#include +#include "soci/connection-parameters.h" #include #include @@ -34,7 +34,7 @@ void execude_hardcoded(sqlite_api::sqlite3* conn, char const* const query, char std::ostringstream ss; ss << errMsg << " " << zErrMsg; sqlite3_free(zErrMsg); - throw soci_error(ss.str()); + throw sqlite3_soci_error(ss.str(), res); } } @@ -42,10 +42,11 @@ 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; - throw soci_error(ss.str()); + throw sqlite3_soci_error(ss.str(), res); } } @@ -81,7 +82,7 @@ sqlite3_session_backend::sqlite3_session_backend( quotedVal = quotedVal + " " + val; std::string keepspace; std::getline(ssconn, keepspace, ' '); - } + } val = quotedVal; } @@ -140,6 +141,14 @@ void sqlite3_session_backend::rollback() execude_hardcoded(conn_, "ROLLBACK", "Cannot rollback transaction."); } +bool sqlite3_session_backend::get_last_insert_id( + session & /* s */, std::string const & /* table */, long & value) +{ + value = static_cast(sqlite3_last_insert_rowid(conn_)); + + return true; +} + void sqlite3_session_backend::clean_up() { sqlite3_close(conn_); diff --git a/src/backends/sqlite3/standard-into-type.cpp b/src/backends/sqlite3/standard-into-type.cpp index ce2febe1e4..490187f4ee 100644 --- a/src/backends/sqlite3/standard-into-type.cpp +++ b/src/backends/sqlite3/standard-into-type.cpp @@ -5,11 +5,13 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include -#include "soci-sqlite3.h" -#include "rowid.h" +#include "soci/soci-platform.h" +#include "soci/sqlite3/soci-sqlite3.h" +#include "soci/rowid.h" #include "common.h" -#include "blob.h" +#include "soci/blob.h" +#include "soci-cstrtod.h" +#include "soci-exchange-cast.h" // std #include #include @@ -68,94 +70,101 @@ void sqlite3_standard_into_type_backend::post_fetch(bool gotData, } } - const char *buf = reinterpret_cast( - sqlite3_column_text(statement_.stmt_,pos)); - - if (buf == NULL) - { - buf = ""; - } - switch (type_) { - case x_char: + case x_char: { - char *dest = static_cast(data_); - *dest = *buf; + const char *buf = reinterpret_cast( + sqlite3_column_text(statement_.stmt_, pos) + ); + const int bytes = sqlite3_column_bytes(statement_.stmt_, pos); + exchange_type_cast(data_) = (bytes > 0 ? buf[0] : '\0'); + break; } - break; - case x_stdstring: + + case x_stdstring: { - std::string *dest = static_cast(data_); - dest->assign(buf); + const char *buf = reinterpret_cast( + sqlite3_column_text(statement_.stmt_, pos) + ); + const int bytes = sqlite3_column_bytes(statement_.stmt_, pos); + exchange_type_traits::value_type &out + = exchange_type_cast(data_); + out.assign(buf, bytes); + break; } - break; - case x_short: + + case x_short: + exchange_type_cast(data_) + = static_cast::value_type >( + sqlite3_column_int(statement_.stmt_, pos) + ); + break; + + case x_integer: + exchange_type_cast(data_) + = static_cast::value_type >( + sqlite3_column_int(statement_.stmt_, pos) + ); + break; + + case x_long_long: + exchange_type_cast(data_) + = static_cast::value_type >( + sqlite3_column_int64(statement_.stmt_, pos) + ); + break; + + case x_unsigned_long_long: + exchange_type_cast(data_) + = static_cast::value_type >( + sqlite3_column_int64(statement_.stmt_, pos) + ); + break; + + case x_double: + exchange_type_cast(data_) + = static_cast::value_type >( + sqlite3_column_double(statement_.stmt_, pos) + ); + break; + + case x_stdtm: { - short *dest = static_cast(data_); - long val = std::strtol(buf, NULL, 10); - *dest = static_cast(val); + const char *buf = reinterpret_cast( + sqlite3_column_text(statement_.stmt_, pos) + ); + parse_std_tm((buf ? buf : ""), exchange_type_cast(data_)); + break; } - break; - case x_integer: - { - int *dest = static_cast(data_); - long val = std::strtol(buf, NULL, 10); - *dest = static_cast(val); - } - break; - case x_long_long: - { - long long* dest = static_cast(data_); - *dest = std::strtoll(buf, NULL, 10); - } - break; - case x_unsigned_long_long: - { - unsigned long long* dest = static_cast(data_); - *dest = string_to_unsigned_integer(buf); - } - break; - case x_double: - { - double *dest = static_cast(data_); - double val = strtod(buf, NULL); - *dest = static_cast(val); - } - break; - case x_stdtm: - { - // attempt to parse the string and convert to std::tm - std::tm *dest = static_cast(data_); - parse_std_tm(buf, *dest); - } - break; - case x_rowid: + + case x_rowid: { // RowID is internally identical to unsigned long - rowid *rid = static_cast(data_); sqlite3_rowid_backend *rbe = static_cast(rid->get_backend()); - long long val = std::strtoll(buf, NULL, 10); - rbe->value_ = static_cast(val); + rbe->value_ = static_cast(sqlite3_column_int64(statement_.stmt_, pos)); + break; } - break; - case x_blob: + + case x_blob: { blob *b = static_cast(data_); sqlite3_blob_backend *bbe = static_cast(b->get_backend()); - buf = reinterpret_cast(sqlite3_column_blob( - statement_.stmt_, - pos)); + const char *buf + = reinterpret_cast( + sqlite3_column_blob(statement_.stmt_, pos) + ); int len = sqlite3_column_bytes(statement_.stmt_, pos); bbe->set_data(buf, len); + break; } - break; - default: - throw soci_error("Into element used with non-supported type."); + + default: + throw soci_error("Into element used with non-supported type."); } } } diff --git a/src/backends/sqlite3/standard-use-type.cpp b/src/backends/sqlite3/standard-use-type.cpp index 0006fcf641..edad17fc3e 100644 --- a/src/backends/sqlite3/standard-use-type.cpp +++ b/src/backends/sqlite3/standard-use-type.cpp @@ -5,10 +5,12 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci-sqlite3.h" -#include -#include "rowid.h" -#include "blob.h" +#include "soci/soci-platform.h" +#include "soci/sqlite3/soci-sqlite3.h" +#include "soci/rowid.h" +#include "soci/blob.h" +#include "soci-dtocstr.h" +#include "soci-exchange-cast.h" // std #include #include @@ -18,14 +20,16 @@ #include #include -#ifdef _MSC_VER -#pragma warning(disable:4355 4996) -#define snprintf _snprintf // TODO: use soci-platform.h -#endif - using namespace soci; using namespace soci::details; +sqlite3_standard_use_type_backend::sqlite3_standard_use_type_backend( + sqlite3_statement_backend &st) + : statement_(st), data_(NULL), type_(x_integer), position_(-1) +{ +} + + void sqlite3_standard_use_type_backend::bind_by_pos(int& position, void* data, exchange_type type, bool /*readOnly*/) { @@ -77,129 +81,99 @@ void sqlite3_standard_use_type_backend::pre_use(indicator const * ind) statement_.useData_[0].resize(position_); } + sqlite3_column &col = statement_.useData_[0][pos]; + if (ind != NULL && *ind == i_null) { - statement_.useData_[0][pos].isNull_ = true; - statement_.useData_[0][pos].data_ = ""; - statement_.useData_[0][pos].blobBuf_ = 0; - statement_.useData_[0][pos].blobSize_ = 0; + col.isNull_ = true; + return; } - else + + col.isNull_ = false; + + // allocate and fill the buffer with text-formatted client data + switch (type_) { - // allocate and fill the buffer with text-formatted client data - switch (type_) - { case x_char: - { - buf_ = new char[2]; - buf_[0] = *static_cast(data_); - buf_[1] = '\0'; - } + col.type_ = dt_string; + col.buffer_.constData_ = &exchange_type_cast(data_); + col.buffer_.size_ = 1; break; + case x_stdstring: - { - std::string *s = static_cast(data_); - buf_ = new char[s->size() + 1]; - std::strcpy(buf_, s->c_str()); - } + { + const std::string &s = exchange_type_cast(data_); + col.type_ = dt_string; + col.buffer_.constData_ = s.c_str(); + col.buffer_.size_ = s.size(); break; + } + case x_short: - { - std::size_t const bufSize - = std::numeric_limits::digits10 + 3; - buf_ = new char[bufSize]; - snprintf(buf_, bufSize, "%d", - static_cast(*static_cast(data_))); - } + col.type_ = dt_integer; + col.int32_ = exchange_type_cast(data_); break; + case x_integer: - { - std::size_t const bufSize - = std::numeric_limits::digits10 + 3; - buf_ = new char[bufSize]; - snprintf(buf_, bufSize, "%d", - *static_cast(data_)); - } + col.type_ = dt_integer; + col.int32_ = exchange_type_cast(data_); break; + case x_long_long: - { - std::size_t const bufSize - = std::numeric_limits::digits10 + 3; - buf_ = new char[bufSize]; - snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "d", - *static_cast(data_)); - } + col.type_ = dt_long_long; + col.int64_ = exchange_type_cast(data_); break; + case x_unsigned_long_long: - { - std::size_t const bufSize - = std::numeric_limits::digits10 + 2; - buf_ = new char[bufSize]; - snprintf(buf_, bufSize, "%" LL_FMT_FLAGS "u", - *static_cast(data_)); - } + col.type_ = dt_long_long; + col.int64_ = exchange_type_cast(data_); break; + case x_double: - { - // no need to overengineer it (KISS)... - - std::size_t const bufSize = 100; - buf_ = new char[bufSize]; - - snprintf(buf_, bufSize, "%.20g", - *static_cast(data_)); - } + col.type_ = dt_double; + col.double_ = exchange_type_cast(data_); break; + case x_stdtm: - { - std::size_t const bufSize = 20; - buf_ = new char[bufSize]; + { + col.type_ = dt_date; + static const size_t bufSize = 20; + std::tm &t = exchange_type_cast(data_); - std::tm *t = static_cast(data_); - snprintf(buf_, bufSize, "%d-%02d-%02d %02d:%02d:%02d", - t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, - t->tm_hour, t->tm_min, t->tm_sec); - } + col.buffer_.data_ = new char[bufSize]; + col.buffer_.size_ + = snprintf( + col.buffer_.data_, bufSize, "%d-%02d-%02d %02d:%02d:%02d", + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, + t.tm_hour, t.tm_min, t.tm_sec + ); break; + } + case x_rowid: - { - // RowID is internally identical to unsigned long + { + col.type_ = dt_long_long; + // RowID is internally identical to unsigned long + rowid *rid = static_cast(data_); + sqlite3_rowid_backend *rbe = static_cast(rid->get_backend()); - rowid *rid = static_cast(data_); - sqlite3_rowid_backend *rbe = -static_cast(rid->get_backend()); - - std::size_t const bufSize - = std::numeric_limits::digits10 + 2; - buf_ = new char[bufSize]; - - snprintf(buf_, bufSize, "%lu", rbe->value_); - } + col.int64_ = rbe->value_; break; + } + case x_blob: - { - blob *b = static_cast(data_); - sqlite3_blob_backend *bbe = - static_cast(b->get_backend()); + { + col.type_ = dt_blob; + blob *b = static_cast(data_); + sqlite3_blob_backend *bbe = static_cast(b->get_backend()); - std::size_t len = bbe->get_len(); - buf_ = new char[len]; - bbe->read(0, buf_, len); - statement_.useData_[0][pos].blobBuf_ = buf_; - statement_.useData_[0][pos].blobSize_ = len; - } + col.buffer_.constData_ = bbe->get_buffer(); + col.buffer_.size_ = bbe->get_len(); break; + } + default: throw soci_error("Use element used with non-supported type."); - } - - statement_.useData_[0][pos].isNull_ = false; - if (type_ != x_blob) - { - statement_.useData_[0][pos].blobBuf_ = 0; - statement_.useData_[0][pos].blobSize_ = 0; - statement_.useData_[0][pos].data_ = buf_; - } } } @@ -225,9 +199,14 @@ void sqlite3_standard_use_type_backend::post_use( void sqlite3_standard_use_type_backend::clean_up() { - if (buf_ != NULL) - { - delete [] buf_; - buf_ = NULL; - } + if (type_ != x_stdtm) + return; + + sqlite3_column &col = statement_.useData_[0][position_ - 1]; + + if (col.isNull_ || !col.buffer_.data_) + return; + + delete[] col.buffer_.data_; + col.buffer_.data_ = NULL; } diff --git a/src/backends/sqlite3/statement.cpp b/src/backends/sqlite3/statement.cpp index 9192a4c4d9..563b21f5af 100644 --- a/src/backends/sqlite3/statement.cpp +++ b/src/backends/sqlite3/statement.cpp @@ -4,12 +4,15 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci-sqlite3.h" +#include "soci/sqlite3/soci-sqlite3.h" // std #include +#include #include #include +#include + #ifdef _MSC_VER #pragma warning(disable:4355) #endif @@ -66,7 +69,7 @@ void sqlite3_statement_backend::prepare(std::string const & query, std::ostringstream ss; ss << "sqlite3_statement_backend::prepare: " << zErrMsg; - throw soci_error(ss.str()); + throw sqlite3_soci_error(ss.str(), res); } databaseReady_ = true; } @@ -90,8 +93,22 @@ statement_backend::exec_fetch_result sqlite3_statement_backend::load_rowset(int totalRows) { statement_backend::exec_fetch_result retVal = ef_success; - int numCols = -1; + int i = 0; + int numCols = 0; + + // just a hack because in some case, describe() is not called, so columns_ is empty + if (columns_.empty()) + { + numCols = sqlite3_column_count(stmt_); + data_type type; + std::string name; + for (int c = 1; c <= numCols; ++c) + describe_column(c, type, name); + } + else + numCols = static_cast(columns_.size()); + if (!databaseReady_) { @@ -101,6 +118,11 @@ sqlite3_statement_backend::load_rowset(int totalRows) { // make the vector big enough to hold the data we need dataCache_.resize(totalRows); + for (sqlite3_recordset::iterator it = dataCache_.begin(), + end = dataCache_.end(); it != end; ++it) + { + (*it).resize(numCols); + } for (i = 0; i < totalRows && databaseReady_; ++i) { @@ -114,38 +136,57 @@ sqlite3_statement_backend::load_rowset(int totalRows) } else if (SQLITE_ROW == res) { - // only need to set the number of columns once - if (-1 == numCols) - { - numCols = sqlite3_column_count(stmt_); - for (sqlite3_recordset::iterator it = dataCache_.begin(), - end = dataCache_.end(); it != end; ++it) - { - (*it).resize(numCols); - } - } for (int c = 0; c < numCols; ++c) { - char const* buf = - reinterpret_cast(sqlite3_column_text(stmt_, c)); - bool isNull = false; - if (0 == buf) + const sqlite3_column_info &coldef = columns_[c]; + sqlite3_column &col = dataCache_[i][c]; + + if (sqlite3_column_type(stmt_, c) == SQLITE_NULL) { - isNull = true; - buf = ""; + col.isNull_ = true; + continue; + } + + col.isNull_ = false; + col.type_ = coldef.type_; + + switch (coldef.type_) + { + case dt_string: + case dt_date: + col.buffer_.size_ = sqlite3_column_bytes(stmt_, c); + col.buffer_.data_ = new char[col.buffer_.size_+1]; + memcpy(col.buffer_.data_, sqlite3_column_text(stmt_, c), col.buffer_.size_+1); + break; + + case dt_double: + col.double_ = sqlite3_column_double(stmt_, c); + break; + + case dt_integer: + col.int32_ = sqlite3_column_int(stmt_, c); + break; + + case dt_long_long: + case dt_unsigned_long_long: + col.int64_ = sqlite3_column_int64(stmt_, c); + break; + + case dt_blob: + col.buffer_.size_ = sqlite3_column_bytes(stmt_, c); + 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; } - dataCache_[i][c].data_ = buf; - dataCache_[i][c].isNull_ = isNull; } } else { - clean_up(); char const* zErrMsg = sqlite3_errmsg(session_.conn_); std::ostringstream ss; ss << "sqlite3_statement_backend::loadRS: " << zErrMsg; - throw soci_error(ss.str()); + throw sqlite3_soci_error(ss.str(), res); } } } @@ -173,14 +214,12 @@ sqlite3_statement_backend::load_one() } else { - clean_up(); - char const* zErrMsg = sqlite3_errmsg(session_.conn_); std::ostringstream ss; ss << "sqlite3_statement_backend::loadOne: " << zErrMsg; - throw soci_error(ss.str()); + throw sqlite3_soci_error(ss.str(), res); } return retVal; @@ -203,31 +242,44 @@ sqlite3_statement_backend::bind_and_execute(int number) for (int pos = 1; pos <= totalPositions; ++pos) { int bindRes = SQLITE_OK; - const sqlite3_column& curCol = useData_[row][pos-1]; - if (curCol.isNull_) + const sqlite3_column &col = useData_[row][pos-1]; + if (col.isNull_) { bindRes = sqlite3_bind_null(stmt_, pos); } - else if (curCol.blobBuf_) - { - bindRes = sqlite3_bind_blob(stmt_, pos, - curCol.blobBuf_, - static_cast(curCol.blobSize_), - SQLITE_STATIC); - } else { - bindRes = sqlite3_bind_text(stmt_, pos, - curCol.data_.c_str(), - static_cast(curCol.data_.length()), - SQLITE_STATIC); + switch (col.type_) + { + case dt_string: + case dt_date: + bindRes = sqlite3_bind_text(stmt_, pos, col.buffer_.constData_, static_cast(col.buffer_.size_), NULL); + break; + + case dt_double: + bindRes = sqlite3_bind_double(stmt_, pos, col.double_); + break; + + case dt_integer: + bindRes = sqlite3_bind_int(stmt_, pos, col.int32_); + break; + + case dt_long_long: + case dt_unsigned_long_long: + bindRes = sqlite3_bind_int64(stmt_, pos, col.int64_); + break; + + case dt_blob: + bindRes = sqlite3_bind_blob(stmt_, pos, col.buffer_.constData_, static_cast(col.buffer_.size_), NULL); + break; + } } if (SQLITE_OK != bindRes) { // preserve the number of rows affected so far. rowsAffectedBulk_ = rowsAffectedBulkTemp; - throw soci_error("Failure to bind on bulk operations"); + throw sqlite3_soci_error("Failure to bind on bulk operations", bindRes); } } @@ -241,6 +293,7 @@ sqlite3_statement_backend::bind_and_execute(int number) retVal = load_one(); //execute each bound line rowsAffectedBulkTemp += get_affected_rows(); } + rowsAffectedBulk_ = rowsAffectedBulkTemp; return retVal; } @@ -280,7 +333,11 @@ sqlite3_statement_backend::execute(int number) statement_backend::exec_fetch_result sqlite3_statement_backend::fetch(int number) { - return load_rowset(number); + if (number > 1) + return load_rowset(number); + else + return load_one(); + } long long sqlite3_statement_backend::get_affected_rows() @@ -297,6 +354,29 @@ int sqlite3_statement_backend::get_number_of_rows() return static_cast(dataCache_.size()); } +std::string sqlite3_statement_backend::get_parameter_name(int index) const +{ + // Notice that SQLite host parameters are counted from 1, not 0. + char const* name = sqlite3_bind_parameter_name(stmt_, index + 1); + if (!name) + return std::string(); + + // SQLite returns parameters with the leading colon which is inconsistent + // with the other backends, so get rid of it as well several other + // characters which can be used for named parameters with SQLite. + switch (*name) + { + case ':': + case '?': + case '@': + case '$': + name++; + break; + } + + return name; +} + std::string sqlite3_statement_backend::rewrite_for_procedure_call( std::string const &query) { @@ -308,18 +388,82 @@ int sqlite3_statement_backend::prepare_for_describe() return sqlite3_column_count(stmt_); } +typedef std::map sqlite3_data_type_map; +static sqlite3_data_type_map get_data_type_map() +{ + sqlite3_data_type_map m; + + // dt_blob + m["blob"] = dt_blob; + + // dt_date + m["date"] = dt_date; + m["time"] = dt_date; + m["datetime"] = dt_date; + + // dt_double + m["decimal"] = dt_double; + m["double"] = dt_double; + m["double precision"] = dt_double; + m["float"] = dt_double; + m["number"] = dt_double; + m["numeric"] = dt_double; + m["real"] = dt_double; + + // dt_integer + m["boolean"] = dt_integer; + m["int"] = dt_integer; + m["integer"] = dt_integer; + m["int2"] = dt_integer; + m["mediumint"] = dt_integer; + m["smallint"] = dt_integer; + m["tinyint"] = dt_integer; + + // dt_long_long + m["bigint"] = dt_long_long; + m["int8"] = dt_long_long; + + // dt_string + m["char"] = dt_string; + m["character"] = dt_string; + m["clob"] = dt_string; + m["native character"] = dt_string; + m["nchar"] = dt_string; + m["nvarchar"] = dt_string; + m["text"] = dt_string; + m["varchar"] = dt_string; + m["varying character"] = dt_string; + + // dt_unsigned_long_long + m["unsigned big int"] = dt_unsigned_long_long; + + + return m; +} + void sqlite3_statement_backend::describe_column(int colNum, data_type & type, std::string & columnName) { - columnName = sqlite3_column_name(stmt_, colNum-1); + static const sqlite3_data_type_map dataTypeMap = get_data_type_map(); + + if (columns_.size() < (size_t)colNum) + columns_.resize(colNum); + sqlite3_column_info &coldef = columns_[colNum - 1]; + + if (!coldef.name_.empty()) + { + columnName = coldef.name_; + type = coldef.type_; + return; + } + + coldef.name_ = columnName = sqlite3_column_name(stmt_, colNum - 1); // This is a hack, but the sqlite3 type system does not // have a date or time field. Also it does not reliably // id other data types. It has a tendency to see everything // as text. sqlite3_column_decltype returns the text that is // used in the create table statement - bool typeFound = false; - char const* declType = sqlite3_column_decltype(stmt_, colNum-1); if ( declType == NULL ) @@ -330,59 +474,18 @@ void sqlite3_statement_backend::describe_column(int colNum, data_type & type, std::string dt = declType; + // remove extra characters for example "(20)" in "varchar(20)" + std::string::iterator siter = std::find_if(dt.begin(), dt.end(), std::not1(std::ptr_fun(isalnum))); + if (siter != dt.end()) + dt.resize(siter - dt.begin()); + // do all comparisons in lower case std::transform(dt.begin(), dt.end(), dt.begin(), tolower); - if (dt.find("time", 0) != std::string::npos) - { - type = dt_date; - typeFound = true; - } - if (dt.find("date", 0) != std::string::npos) - { - type = dt_date; - typeFound = true; - } - - if (dt.find("int8", 0) != std::string::npos || dt.find("bigint", 0) != std::string::npos) - { - type = dt_long_long; - typeFound = true; - } - else if (dt.find("unsigned big int", 0) != std::string::npos) - { - type = dt_unsigned_long_long; - typeFound = true; - } - else if (dt.find("int", 0) != std::string::npos) - { - type = dt_integer; - typeFound = true; - } - - if (dt.find("float", 0) != std::string::npos || dt.find("double", 0) != std::string::npos) - { - type = dt_double; - typeFound = true; - } - if (dt.find("text", 0) != std::string::npos) - { - type = dt_string; - typeFound = true; - } - if (dt.find("char", 0) != std::string::npos) - { - type = dt_string; - typeFound = true; - } - if (dt.find("boolean", 0) != std::string::npos) - { - type = dt_integer; - typeFound = true; - } - - if (typeFound) + sqlite3_data_type_map::const_iterator iter = dataTypeMap.find(dt); + if (iter != dataTypeMap.end()) { + coldef.type_ = type = iter->second; return; } @@ -409,6 +512,7 @@ void sqlite3_statement_backend::describe_column(int colNum, data_type & type, type = dt_string; break; } + coldef.type_ = type; sqlite3_reset(stmt_); } diff --git a/src/backends/sqlite3/test/.gitignore b/src/backends/sqlite3/test/.gitignore deleted file mode 100644 index 35d7969fdb..0000000000 --- a/src/backends/sqlite3/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test_sqlite3 -test_sqlite3.db diff --git a/src/backends/sqlite3/test/Makefile.basic b/src/backends/sqlite3/test/Makefile.basic deleted file mode 100644 index c2eb3ed788..0000000000 --- a/src/backends/sqlite3/test/Makefile.basic +++ /dev/null @@ -1,23 +0,0 @@ -# The following variable is specific to this backend and its correct -# values might depend on your environment - feel free to set it accordingly. - -BOOSTINCLUDEDIR = -SQLITE3INCLUDEDIR = -SQLITE3LIBDIR = -L/usr/lib -SQLITE3LIBS = -lsqlite3 - -# The rest of the Makefile is independent of the target environment. - -COMPILER = g++ -CXXFLAGS = -Wall -pedantic -Wno-long-long -INCLUDEDIRS = -I.. -I../../../core -I../../../core/test ${SQLITE3INCLUDEDIR} ${BOOSTINCLUDEDIR} -LIBDIRS = -L.. -L../../../core ${SQLITE3LIBDIR} -LIBS = -lsoci_core -lsoci_sqlite3 -ldl ${SQLITE3LIBS} - - -test-sqlite3 : test-sqlite3.cpp - ${COMPILER} -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} - - -clean : - rm -f test-sqlite3 diff --git a/src/backends/sqlite3/test/test-sqlite3.cpp b/src/backends/sqlite3/test/test-sqlite3.cpp deleted file mode 100644 index ae257d9d24..0000000000 --- a/src/backends/sqlite3/test/test-sqlite3.cpp +++ /dev/null @@ -1,395 +0,0 @@ -// -// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney -// 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.h" -#include "soci-sqlite3.h" -#include "common-tests.h" -#include -#include -#include -#include -#include -#include -#include - -using namespace soci; -using namespace soci::tests; - -std::string connectString; -backend_factory const &backEnd = *soci::factory_sqlite3(); - -// ROWID test -// In sqlite3 the row id can be called ROWID, _ROWID_ or oid -void test1() -{ - { - session sql(backEnd, connectString); - - try { sql << "drop table test1"; } - catch (soci_error const &) {} // ignore if error - - sql << - "create table test1 (" - " id integer," - " name varchar(100)" - ")"; - - sql << "insert into test1(id, name) values(7, \'John\')"; - - rowid rid(sql); - sql << "select oid from test1 where id = 7", into(rid); - - int id; - std::string name; - - sql << "select id, name from test1 where oid = :rid", - into(id), into(name), use(rid); - - assert(id == 7); - assert(name == "John"); - - sql << "drop table test1"; - } - - std::cout << "test 1 passed" << std::endl; -} - -// BLOB test -struct blob_table_creator : public table_creator_base -{ - blob_table_creator(session & sql) - : table_creator_base(sql) - { - sql << - "create table soci_test (" - " id integer," - " img blob" - ")"; - } -}; - -void test2() -{ - { - session sql(backEnd, connectString); - - blob_table_creator tableCreator(sql); - - char buf[] = "abcdefghijklmnopqrstuvwxyz"; - - sql << "insert into soci_test(id, img) values(7, '')"; - - { - blob b(sql); - - sql << "select img from soci_test where id = 7", into(b); - assert(b.get_len() == 0); - - b.write(0, buf, sizeof(buf)); - assert(b.get_len() == sizeof(buf)); - sql << "update soci_test set img=? where id = 7", use(b); - - b.append(buf, sizeof(buf)); - assert(b.get_len() == 2 * sizeof(buf)); - sql << "insert into soci_test(id, img) values(8, ?)", use(b); - } - { - blob b(sql); - sql << "select img from soci_test where id = 8", into(b); - assert(b.get_len() == 2 * sizeof(buf)); - char buf2[100]; - b.read(0, buf2, 10); - assert(std::strncmp(buf2, "abcdefghij", 10) == 0); - - sql << "select img from soci_test where id = 7", into(b); - assert(b.get_len() == sizeof(buf)); - - } - } - - std::cout << "test 2 passed" << std::endl; -} - -// This test was put in to fix a problem that occurs when there are both -// into and use elements in the same query and one of them (into) binds -// to a vector object. - -struct test3_table_creator : table_creator_base -{ - test3_table_creator(session & sql) : table_creator_base(sql) - { - sql << "create table soci_test( id integer, name varchar, subname varchar);"; - } -}; - -void test3() -{ - { - session sql(backEnd, connectString); - - test3_table_creator tableCreator(sql); - - sql << "insert into soci_test(id,name,subname) values( 1,'john','smith')"; - sql << "insert into soci_test(id,name,subname) values( 2,'george','vals')"; - sql << "insert into soci_test(id,name,subname) values( 3,'ann','smith')"; - sql << "insert into soci_test(id,name,subname) values( 4,'john','grey')"; - sql << "insert into soci_test(id,name,subname) values( 5,'anthony','wall')"; - - { - std::vector v(10); - - statement s(sql.prepare << "Select id from soci_test where name = :name"); - - std::string name = "john"; - - s.exchange(use(name, "name")); - s.exchange(into(v)); - - s.define_and_bind(); - s.execute(true); - - assert(v.size() == 2); - } - } - std::cout << "test 3 passed" << std::endl; -} - - -// Test case from Amnon David 11/1/2007 -// I've noticed that table schemas in SQLite3 can sometimes have typeless -// columns. One (and only?) example is the sqlite_sequence that sqlite -// creates for autoincrement . Attempting to traverse this table caused -// SOCI to crash. I've made the following code change in statement.cpp to -// create a workaround: - -struct test4_table_creator : table_creator_base -{ - test4_table_creator(session & sql) : table_creator_base(sql) - { - sql << "create table soci_test (col INTEGER PRIMARY KEY AUTOINCREMENT, name char)"; - } -}; - -void test4() -{ - { - // we need to have an table that uses autoincrement to test this. - session sql(backEnd, connectString); - - test4_table_creator tableCreator(sql); - - sql << "insert into soci_test(name) values('john')"; - sql << "insert into soci_test(name) values('james')"; - - { - int key; - std::string name; - sql << "select * from soci_test", into(key), into(name); - assert(name == "john"); - - rowset rs = (sql.prepare << "select * from sqlite_sequence"); - rowset::const_iterator it = rs.begin(); - row const& r1 = (*it); - assert(r1.get(0) == "soci_test"); - assert(r1.get(1) == "2"); - } - } - std::cout << "test 4 passed" << std::endl; -} - -struct longlong_table_creator : table_creator_base -{ - longlong_table_creator(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val number(20))"; - } -}; - -// long long test -void test5() -{ - { - session sql(backEnd, connectString); - - longlong_table_creator tableCreator(sql); - - long long v1 = 1000000000000LL; - assert(v1 / 1000000 == 1000000); - - sql << "insert into soci_test(val) values(:val)", use(v1); - - long long v2 = 0LL; - sql << "select val from soci_test", into(v2); - - assert(v2 == v1); - } - - // vector - { - session sql(backEnd, connectString); - - longlong_table_creator tableCreator(sql); - - std::vector v1; - v1.push_back(1000000000000LL); - v1.push_back(1000000000001LL); - v1.push_back(1000000000002LL); - v1.push_back(1000000000003LL); - v1.push_back(1000000000004LL); - - sql << "insert into soci_test(val) values(:val)", use(v1); - - std::vector v2(10); - sql << "select val from soci_test order by val desc", into(v2); - - assert(v2.size() == 5); - assert(v2[0] == 1000000000004LL); - assert(v2[1] == 1000000000003LL); - assert(v2[2] == 1000000000002LL); - assert(v2[3] == 1000000000001LL); - assert(v2[4] == 1000000000000LL); - } - - std::cout << "test 5 passed" << std::endl; -} - -// DDL Creation objects for common tests -struct table_creator_one : public table_creator_base -{ - table_creator_one(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(id integer, val integer, c char, " - "str varchar(20), sh smallint, ul numeric(20), d float, " - "tm datetime, i1 integer, i2 integer, i3 integer, " - "name varchar(20))"; - } -}; - -struct table_creator_two : public table_creator_base -{ - table_creator_two(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(num_float float, num_int integer," - " name varchar(20), sometime datetime, chr char)"; - } -}; - -struct table_creator_three : public table_creator_base -{ - table_creator_three(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(name varchar(100) not null, " - "phone varchar(15))"; - } -}; - -// Originally, submitted to SQLite3 backend and later moved to common test. -// Test commit b394d039530f124802d06c3b1a969c3117683152 -// Author: Mika Fischer -// Date: Thu Nov 17 13:28:07 2011 +0100 -// Implement get_affected_rows for SQLite3 backend -struct table_creator_for_get_affected_rows : table_creator_base -{ - table_creator_for_get_affected_rows(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val integer)"; - } -}; - -// -// Support for SOCI Common Tests -// - -class test_context : public test_context_base -{ -public: - test_context(backend_factory const &backEnd, - std::string const &connectString) - : test_context_base(backEnd, connectString) {} - - table_creator_base* table_creator_1(session& s) const - { - return new table_creator_one(s); - } - - table_creator_base* table_creator_2(session& s) const - { - return new table_creator_two(s); - } - - table_creator_base* table_creator_3(session& s) const - { - return new table_creator_three(s); - } - - table_creator_base* table_creator_4(session& s) const - { - return new table_creator_for_get_affected_rows(s); - } - - std::string to_date_time(std::string const &datdt_string) const - { - return "datetime(\'" + datdt_string + "\')"; - } -}; - -int main(int argc, char** argv) -{ - -#ifdef _MSC_VER - // Redirect errors, unrecoverable problems, and assert() failures to STDERR, - // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. - // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); -#endif //_MSC_VER - - if (argc == 2) - { - connectString = argv[1]; - } - else - { - // If no file name is specfied then work in-memory - connectString = ":memory:"; - } - - try - { - test_context tc(backEnd, connectString); - common_tests tests(tc); - tests.run(); - - std::cout << "\nSOCI sqlite3 Tests:\n\n"; - - test1(); - test2(); - test3(); - test4(); - test5(); - - std::cout << "\nOK, all tests passed.\n\n"; - - return EXIT_SUCCESS; - } - catch (soci::soci_error const & e) - { - std::cout << "SOCIERROR: " << e.what() << '\n'; - } - catch (std::exception const & e) - { - std::cout << "EXCEPTION: " << e.what() << '\n'; - } - - return EXIT_FAILURE; -} diff --git a/src/backends/sqlite3/vector-into-type.cpp b/src/backends/sqlite3/vector-into-type.cpp index 1c85615b04..5ab62bf74c 100644 --- a/src/backends/sqlite3/vector-into-type.cpp +++ b/src/backends/sqlite3/vector-into-type.cpp @@ -5,23 +5,31 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include -#include "soci-sqlite3.h" +#ifdef _MSC_VER +#pragma warning(disable : 4512) +#endif + +#include "soci-dtocstr.h" +#include "soci-exchange-cast.h" +#include "soci/blob.h" +#include "soci/rowid.h" +#include "soci/soci-platform.h" +#include "soci/sqlite3/soci-sqlite3.h" +#include "soci-cstrtod.h" #include "common.h" // std -#include #include #include #include #include +#include #include -using namespace soci; -using namespace soci::details; -using namespace soci::details::sqlite3; +namespace soci +{ void sqlite3_vector_into_type_backend::define_by_pos( - int& position, void* data, exchange_type type) + int& position, void* data, details::exchange_type type) { data_ = data; type_ = type; @@ -39,17 +47,46 @@ namespace // anonymous template void set_in_vector(void* p, int indx, T const& val) { - assert(NULL != p); - - std::vector* dest = static_cast*>(p); - std::vector& v = *dest; + std::vector &v = *static_cast*>(p); v[indx] = val; } +template +void set_number_in_vector(void *p, int idx, const sqlite3_column &col) +{ + using namespace details; + using namespace details::sqlite3; + + switch (col.type_) + { + case dt_date: + case dt_string: + case dt_blob: + set_in_vector(p, idx, string_to_integer(col.buffer_.size_ > 0 ? col.buffer_.constData_ : "")); + break; + + case dt_double: + set_in_vector(p, idx, static_cast(col.double_)); + break; + + case dt_integer: + set_in_vector(p, idx, static_cast(col.int32_)); + break; + + case dt_long_long: + case dt_unsigned_long_long: + set_in_vector(p, idx, static_cast(col.int64_)); + break; + }; +} + } // namespace anonymous void sqlite3_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) { + using namespace details; + using namespace details::sqlite3; + if (!gotData) { // no data retrieved @@ -59,10 +96,9 @@ void sqlite3_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) int const endRow = static_cast(statement_.dataCache_.size()); for (int i = 0; i < endRow; ++i) { - sqlite3_column const& curCol = - statement_.dataCache_[i][position_-1]; + sqlite3_column &col = statement_.dataCache_[i][position_-1]; - if (curCol.isNull_) + if (col.isNull_) { if (ind == NULL) { @@ -71,81 +107,157 @@ void sqlite3_vector_into_type_backend::post_fetch(bool gotData, indicator * ind) } ind[i] = i_null; - // no need to convert data if it is null, go to next row + // nothing to do for null value, go to next row continue; } - else - { - if (ind != NULL) - { - ind[i] = i_ok; - } - } - const char * buf = curCol.data_.c_str(); - - // set buf to a null string if a null pointer is returned - if (buf == NULL) - { - buf = ""; - } + if (ind != NULL) + ind[i] = i_ok; + // conversion switch (type_) { - case x_char: - set_in_vector(data_, i, *buf); - break; - case x_stdstring: - set_in_vector(data_, i, buf); - break; - case x_short: + case x_char: { - short const val = string_to_integer(buf); - set_in_vector(data_, i, val); - } - break; - case x_integer: - { - int const val = string_to_integer(buf); - set_in_vector(data_, i, val); - } - break; - case x_long_long: - { - long long const val = string_to_integer(buf); - set_in_vector(data_, i, val); - } - break; - case x_unsigned_long_long: - { - unsigned long long const val - = string_to_unsigned_integer(buf); - set_in_vector(data_, i, val); - } - break; - case x_double: - { - double const val = strtod(buf, NULL); - set_in_vector(data_, i, val); - } - break; - case x_stdtm: - { - // attempt to parse the string and convert to std::tm - std::tm t; - parse_std_tm(buf, t); + switch (col.type_) + { + case dt_date: + case dt_string: + case dt_blob: + set_in_vector(data_, i, (col.buffer_.size_ > 0 ? col.buffer_.constData_[0] : '\0')); + break; - set_in_vector(data_, i, t); + case dt_double: + set_in_vector(data_, i, double_to_cstring(col.double_)[0]); + break; + + case dt_integer: + { + std::ostringstream ss; + ss << col.int32_; + set_in_vector(data_, i, ss.str()[0]); + break; + } + + case dt_long_long: + case dt_unsigned_long_long: + { + std::ostringstream ss; + ss << col.int64_; + set_in_vector(data_, i, ss.str()[0]); + break; + } + }; + break; + } // x_char + + case x_stdstring: + { + switch (col.type_) + { + case dt_date: + case dt_string: + case dt_blob: + set_in_vector(data_, i, std::string(col.buffer_.constData_, col.buffer_.size_)); + break; + + case dt_double: + set_in_vector(data_, i, double_to_cstring(col.double_)); + break; + + case dt_integer: + { + std::ostringstream ss; + ss << col.int32_; + set_in_vector(data_, i, ss.str()); + break; + } + + case dt_long_long: + case dt_unsigned_long_long: + { + std::ostringstream ss; + ss << col.int64_; + set_in_vector(data_, i, ss.str()); + break; + } + }; + break; + } // x_stdstring + + case x_short: + set_number_in_vector::value_type>(data_, i, col); + break; + + case x_integer: + set_number_in_vector::value_type>(data_, i, col); + break; + + case x_long_long: + set_number_in_vector::value_type>(data_, i, col); + break; + + case x_unsigned_long_long: + set_number_in_vector::value_type>(data_, i, col); + break; + + case x_double: + set_number_in_vector::value_type>(data_, i, col); + break; + + case x_stdtm: + { + switch (col.type_) + { + case dt_date: + case dt_string: + case dt_blob: + { + // attempt to parse the string and convert to std::tm + std::tm t; + parse_std_tm(col.buffer_.constData_, t); + + set_in_vector(data_, i, t); + break; + } + + case dt_double: + case dt_integer: + case dt_long_long: + case dt_unsigned_long_long: + throw soci_error("Into element used with non-convertible type."); + }; + break; } - break; - default: - throw soci_error("Into element used with non-supported type."); + + default: + throw soci_error("Into element used with non-supported type."); + } + + // cleanup data + switch (col.type_) + { + case dt_date: + case dt_string: + case dt_blob: + delete[] col.buffer_.data_; + col.buffer_.data_ = NULL; + break; + + case dt_double: + case dt_integer: + case dt_long_long: + case dt_unsigned_long_long: + break; } } } void sqlite3_vector_into_type_backend::resize(std::size_t sz) { + using namespace details; + using namespace details::sqlite3; + switch (type_) { // simple cases @@ -180,6 +292,9 @@ void sqlite3_vector_into_type_backend::resize(std::size_t sz) std::size_t sqlite3_vector_into_type_backend::size() { + using namespace details; + using namespace details::sqlite3; + std::size_t sz = 0; // dummy initialization to please the compiler switch (type_) { @@ -217,5 +332,6 @@ std::size_t sqlite3_vector_into_type_backend::size() void sqlite3_vector_into_type_backend::clean_up() { - // ... } + +} // namespace soci diff --git a/src/backends/sqlite3/vector-use-type.cpp b/src/backends/sqlite3/vector-use-type.cpp index 67a3534e79..d1cd5edfa3 100644 --- a/src/backends/sqlite3/vector-use-type.cpp +++ b/src/backends/sqlite3/vector-use-type.cpp @@ -5,8 +5,10 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci-sqlite3.h" -#include +#include "soci-exchange-cast.h" +#include "soci/soci-platform.h" +#include "soci/sqlite3/soci-sqlite3.h" +#include "soci-dtocstr.h" #include "common.h" // std #include @@ -15,10 +17,6 @@ #include #include -#ifdef _MSC_VER -#pragma warning(disable:4355 4996) -#define snprintf _snprintf // TODO: use soci-platform.h -#endif using namespace soci; using namespace soci::details; @@ -73,146 +71,88 @@ void sqlite3_vector_use_type_backend::pre_use(indicator const * ind) // make sure that useData can hold enough rows if (statement_.useData_.size() != vsize) - { statement_.useData_.resize(vsize); - } int const pos = position_ - 1; for (size_t i = 0; i != vsize; ++i) { - char *buf = 0; - // make sure that each row can accomodate the number of columns if (statement_.useData_[i].size() < static_cast(position_)) - { statement_.useData_[i].resize(position_); - } + + sqlite3_column &col = statement_.useData_[i][pos]; // the data in vector can be either i_ok or i_null if (ind != NULL && ind[i] == i_null) { - statement_.useData_[i][pos].isNull_ = true; - statement_.useData_[i][pos].data_ = ""; - statement_.useData_[i][pos].blobBuf_ = 0; - statement_.useData_[i][pos].blobSize_ = 0; + col.isNull_ = true; + col.buffer_.data_ = NULL; + continue; } - else + + col.isNull_ = false; + + switch (type_) { - // allocate and fill the buffer with text-formatted client data - switch (type_) - { case x_char: - { - std::vector *pv - = static_cast *>(data_); - std::vector &v = *pv; - - buf = new char[2]; - buf[0] = v[i]; - buf[1] = '\0'; - } + col.type_ = dt_string; + col.buffer_.constData_ = &(*static_cast::value_type> *>(data_))[i]; + col.buffer_.size_ = 1; break; + case x_stdstring: - { - std::vector *pv - = static_cast *>(data_); - std::vector &v = *pv; - - buf = new char[v[i].size() + 1]; - std::strcpy(buf, v[i].c_str()); - } + { + std::string &s = (*static_cast::value_type> *>(data_))[i]; + col.type_ = dt_string; + col.buffer_.constData_ = s.c_str(); + col.buffer_.size_ = s.size(); break; + } + case x_short: - { - std::vector *pv - = static_cast *>(data_); - std::vector &v = *pv; - - std::size_t const bufSize - = std::numeric_limits::digits10 + 3; - buf = new char[bufSize]; - snprintf(buf, bufSize, "%d", static_cast(v[i])); - } + col.type_ = dt_integer; + col.int32_ = (*static_cast::value_type> *>(data_))[i]; break; + case x_integer: - { - std::vector *pv - = static_cast *>(data_); - std::vector &v = *pv; - - std::size_t const bufSize - = std::numeric_limits::digits10 + 3; - buf = new char[bufSize]; - snprintf(buf, bufSize, "%d", v[i]); - } + col.type_ = dt_integer; + col.int32_ = (*static_cast::value_type> *>(data_))[i]; break; + case x_long_long: - { - std::vector *pv - = static_cast *>(data_); - std::vector &v = *pv; - - std::size_t const bufSize - = std::numeric_limits::digits10 + 3; - buf = new char[bufSize]; - snprintf(buf, bufSize, "%" LL_FMT_FLAGS "d", v[i]); - } + col.type_ = dt_long_long; + col.int64_ = (*static_cast::value_type> *>(data_))[i]; break; + case x_unsigned_long_long: - { - std::vector* pv - = static_cast*>(data_); - std::vector& v = *pv; - - std::size_t const bufSize - = std::numeric_limits::digits10 + 2; - buf = new char[bufSize]; - snprintf(buf, bufSize, "%" LL_FMT_FLAGS "u", v[i]); - } + col.type_ = dt_long_long; + col.int64_ = (*static_cast::value_type> *>(data_))[i]; break; + case x_double: - { - // no need to overengineer it (KISS)... - - std::vector *pv - = static_cast *>(data_); - std::vector &v = *pv; - - std::size_t const bufSize = 100; - buf = new char[bufSize]; - - snprintf(buf, bufSize, "%.20g", v[i]); - } + col.type_ = dt_double; + col.double_ = (*static_cast::value_type> *>(data_))[i]; break; + case x_stdtm: - { - std::vector *pv - = static_cast *>(data_); - std::vector &v = *pv; + { + std::tm &tm = (*static_cast::value_type> *>(data_))[i]; + static const size_t bufSize = 20; - std::size_t const bufSize = 20; - buf = new char[bufSize]; - - snprintf(buf, bufSize, "%d-%02d-%02d %02d:%02d:%02d", - v[i].tm_year + 1900, v[i].tm_mon + 1, v[i].tm_mday, - v[i].tm_hour, v[i].tm_min, v[i].tm_sec); - } + col.type_ = dt_date; + col.buffer_.data_ = new char[bufSize]; + col.buffer_.size_ + = snprintf(col.buffer_.data_, bufSize, "%d-%02d-%02d %02d:%02d:%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec + ); break; + } + default: throw soci_error( "Use vector element used with non-supported type."); - } - - statement_.useData_[i][pos].isNull_ = false; - statement_.useData_[i][pos].data_ = buf; - statement_.useData_[i][pos].blobBuf_ = 0; - statement_.useData_[i][pos].blobSize_ = 0; - } - - if (buf) - { - delete [] buf; } } } @@ -256,5 +196,19 @@ std::size_t sqlite3_vector_use_type_backend::size() void sqlite3_vector_use_type_backend::clean_up() { - // ... + if (type_ != x_stdtm) + return; + + int const pos = position_ - 1; + + for (sqlite3_recordset::iterator iter = statement_.useData_.begin(), last = statement_.useData_.end(); + iter != last; ++iter) + { + sqlite3_column &col = (*iter)[pos]; + + if (col.isNull_) + continue; + + delete[] col.buffer_.data_; + } } diff --git a/src/cmake/SociSystemInfo.cmake b/src/cmake/SociSystemInfo.cmake deleted file mode 100644 index 35ad28b5c2..0000000000 --- a/src/cmake/SociSystemInfo.cmake +++ /dev/null @@ -1,82 +0,0 @@ -################################################################################ -# SociSystemInfo.cmake - part of CMake configuration of SOCI library -# -# Based on idea taken from http://code.google.com/p/softart/ project -################################################################################ -# Copyright (C) 2010 Mateusz Loskot -# -# 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) -################################################################################ -# The following variables are defined: -# SOCI_COMPILER_NAME - name of compiler toolset, follows Boost toolset naming. -# SOCI_PLATFORM_NAME - target platform name: x64, x86 or win32 -# -# Based on the Pre-defined Compiler Macros -# http://sourceforge.net/p/predef/wiki/Compilers/ -################################################################################ - -set(SOCI_COMPILER_NAME) -set(SOCI_PLATFORM_NAME) - -if(MINGW OR UNIX) - exec_program(${CMAKE_C_COMPILER} ARGS -dumpversion OUTPUT_VARIABLE GCC_VERSION) - string(REPLACE "." "" GCC_VERSION_STR_FULL ${GCC_VERSION}) - string(REGEX MATCH "[0-9]+\\.[0-9]+" GCC_VERSION_MAJOR_MINOR ${GCC_VERSION}) -endif() - -if(WIN32) - if(MSVC) - if(MSVC_VERSION EQUAL 1200) - set(SOCI_COMPILER_NAME "msvc-6.0") - endif() - if(MSVC_VERSION EQUAL 1300) - set(SOCI_COMPILER_NAME "msvc-7.0") - endif() - if(MSVC_VERSION EQUAL 1310) - set(SOCI_COMPILER_NAME "msvc-7.1") # Visual Studio 2003 - endif() - if(MSVC_VERSION EQUAL 1400) - set(SOCI_COMPILER_NAME "msvc-8.0") # Visual Studio 2005 - endif() - if(MSVC_VERSION EQUAL 1500) - set(SOCI_COMPILER_NAME "msvc-9.0") # Visual Studio 2008 - endif() - if(MSVC_VERSION EQUAL 1600) - set(SOCI_COMPILER_NAME "msvc-10.0") # Visual Studio 2010 - endif() - if(MSVC_VERSION EQUAL 1700) - set(SOCI_COMPILER_NAME "msvc-11.0") # Visual Studio 2012 - endif() - endif(MSVC) - - if(MINGW) - set(SOCI_COMPILER_NAME "mingw-${GCC_VERSION}") - endif( MINGW ) - - if(CMAKE_GENERATOR MATCHES "Win64") - set(SOCI_PLATFORM_NAME "x64") - else() - set(SOCI_PLATFORM_NAME "win32") - endif() -endif(WIN32) - -if(UNIX) - set(SOCI_COMPILER_NAME "gcc-${GCC_VERSION}") - if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") - set(SOCI_PLATFORM_NAME "x64") - else() - set(SOCI_PLATFORM_NAME "x86") - endif() -endif(UNIX) - -if(NOT SOCI_COMPILER_NAME) - colormsg(_RED_ "WARNING:") - colormsg(RED "Could not determine compiler toolset name to set SOCI_COMPILER_NAME variable.") -endif() - -if(NOT SOCI_PLATFORM_NAME) - colormsg(_RED_ "WARNING:") - colormsg(RED "Could not determine platform name to set SOCI_PLATFORM_NAME variable.") -endif() diff --git a/src/cmake/dependencies/Firebird.cmake b/src/cmake/dependencies/Firebird.cmake deleted file mode 100644 index 73c8f598e1..0000000000 --- a/src/cmake/dependencies/Firebird.cmake +++ /dev/null @@ -1,6 +0,0 @@ -set(Firebird_FIND_QUIETLY TRUE) - -find_package(Firebird) - -boost_external_report(Firebird INCLUDE_DIR LIBRARIES VERSION) - diff --git a/src/cmake/dependencies/MySQL.cmake b/src/cmake/dependencies/MySQL.cmake deleted file mode 100644 index 9cac7a2241..0000000000 --- a/src/cmake/dependencies/MySQL.cmake +++ /dev/null @@ -1,10 +0,0 @@ -set(MySQL_FIND_QUIETLY TRUE) - -find_package(MySQL) - -boost_external_report(MySQL INCLUDE_DIR LIBRARIES) - -#if(MYSQL_FOUND) -# include_directories(${MYSQL_INCLUDE_DIR}) -# add_definitions(-DHAVE_MYSQL) -#endif() \ No newline at end of file diff --git a/src/cmake/dependencies/Threads.cmake b/src/cmake/dependencies/Threads.cmake deleted file mode 100644 index 953bb9757d..0000000000 --- a/src/cmake/dependencies/Threads.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(Threads_FIND_QUIETLY TRUE) - -find_package(Threads) -message(STATUS "X: ${Threads_FOUND}") -boost_external_report(Threads LIBRARIES) diff --git a/src/cmake/modules/FindOracle.cmake b/src/cmake/modules/FindOracle.cmake deleted file mode 100644 index e407967d23..0000000000 --- a/src/cmake/modules/FindOracle.cmake +++ /dev/null @@ -1,75 +0,0 @@ -############################################################################### -# -# CMake module to search for Oracle client library (OCI) -# -# On success, the macro sets the following variables: -# ORACLE_FOUND = if the library found -# ORACLE_LIBRARY = full path to the library -# ORACLE_LIBRARIES = full path to the library -# ORACLE_INCLUDE_DIR = where to find the library headers also defined, -# but not for general use are -# ORACLE_VERSION = version of library which was found, e.g. "1.2.5" -# -# Copyright (c) 2009-2013 Mateusz Loskot -# -# Developed with inspiration from Petr Vanek -# who wrote similar macro for TOra - http://torasql.com/ -# -# Module source: http://github.com/mloskot/workshop/tree/master/cmake/ -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# -############################################################################### - -# If ORACLE_HOME not defined, assume Oracle libraries not available -if(DEFINED ENV{ORACLE_HOME}) - - set(ORACLE_HOME $ENV{ORACLE_HOME}) - 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 - - set(ORACLE_OCI_NAMES clntsh libclntsh oci) # Dirty trick might help on OSX, see issues/89 - set(ORACLE_OCCI_NAMES libocci occi oraocci10 oraocci11) - 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 - - find_library(ORACLE_OCI_LIBRARY - NAMES ${ORACLE_OCI_NAMES} PATHS ${ORACLE_LIB_DIR}) - find_library(ORACLE_OCCI_LIBRARY - NAMES ${ORACLE_OCCI_NAMES} PATHS ${ORACLE_LIB_DIR}) - find_library(ORACLE_NNZ_LIBRARY - NAMES ${ORACLE_NNZ_NAMES} PATHS ${ORACLE_LIB_DIR}) - - set(ORACLE_LIBRARY - ${ORACLE_OCI_LIBRARY} - ${ORACLE_OCCI_LIBRARY} - ${ORACLE_NNZ_LIBRARY}) - - if(NOT WIN32) - set(ORACLE_LIBRARY ${ORACLE_LIBRARY} ${ORACLE_CLNTSH_LIBRARY}) - endif(NOT WIN32) - - set(ORACLE_LIBRARIES ${ORACLE_LIBRARY}) - -endif(DEFINED ENV{ORACLE_HOME}) - -# Handle the QUIETLY and REQUIRED arguments and set ORACLE_FOUND to TRUE -# if all listed variables are TRUE -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ORACLE DEFAULT_MSG ORACLE_LIBRARY ORACLE_INCLUDE_DIR) - -mark_as_advanced(ORACLE_INCLUDE_DIR ORACLE_LIBRARY) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 043f42a7ce..b326aed684 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2,61 +2,26 @@ # # This file is part of CMake configuration for SOCI library # -# Copyright (C) 2009-2010 Mateusz Loskot +# Copyright (C) 2009-2013 Mateusz Loskot # 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) # ############################################################################### - colormsg(_HIBLUE_ "Configuring SOCI core library:") -# Core dependencies -set(SOCI_CORE_DEPENDENCIES) +# Set INCLUDE_DIRECTORIES +get_directory_property(SOCI_CORE_INCLUDE_DIRS INCLUDE_DIRECTORIES) +list(APPEND SOCI_CORE_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}) +set_directory_properties(PROPERTIES + INCLUDE_DIRECTORIES "${SOCI_CORE_INCLUDE_DIRS}") -include(FindThreads) - -if(Threads_FOUND OR CMAKE_USE_WIN32_THREADS_INIT OR CMAKE_THREAD_LIBS_INIT) - list(APPEND SOCI_CORE_DEPENDENCIES ${CMAKE_THREAD_LIBS_INIT}) -else() - message(FATAL_ERROR "No thread library found") -endif() - -if(NOT MSVC) - set(DL_FIND_QUIETLY TRUE) - find_package(DL) - if(DL_FOUND) - list(APPEND SOCI_CORE_DEPENDENCIES ${DL_LIBRARY}) - set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES ${DL_INCLUDE_DIR}) - add_definitions(-DHAVE_DL=1) - endif() -endif() - -if(Boost_FOUND) - - get_property(SOCI_COMPILE_DEFINITIONS - DIRECTORY ${CMAKE_SOURCE_DIR} - PROPERTY COMPILE_DEFINITIONS) - - list(APPEND SOCI_COMPILE_DEFINITIONS "HAVE_BOOST=1") - - if(Boost_DATE_TIME_FOUND) - list(APPEND SOCI_CORE_DEPENDENCIES ${Boost_DATE_TIME_LIBRARY}) - list(APPEND SOCI_COMPILE_DEFINITIONS "HAVE_BOOST_DATE_TIME=1") - endif() - - set_property(DIRECTORY ${CMAKE_SOURCE_DIR} - PROPERTY COMPILE_DEFINITIONS ${SOCI_COMPILE_DEFINITIONS}) - - set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) - - set_property(DIRECTORY ${CMAKE_SOURCE_DIR} - PROPERTY INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) - -endif() +# Configure backend loader to also use default install directory. +configure_file(soci_backends_config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/soci_backends_config.h) # Core source files -file(GLOB SOCI_CORE_HEADERS *.h) +file(GLOB SOCI_CORE_HEADERS ${SOCI_SOURCE_DIR}/include/soci/*.h) file(GLOB SOCI_CORE_SOURCES *.cpp) # Group source files for IDE source explorers (e.g. Visual Studio) @@ -66,22 +31,19 @@ source_group("CMake Files" FILES CMakeLists.txt) # Core targets configuration string(TOLOWER "${PROJECT_NAME}" PROJECTNAMEL) +#this command will update parent scope variable +set(SOCI_CORE_TARGET ${PROJECTNAMEL}_core PARENT_SCOPE) set(SOCI_CORE_TARGET ${PROJECTNAMEL}_core) soci_target_output_name(${SOCI_CORE_TARGET} SOCI_CORE_TARGET_OUTPUT_NAME) -# Configure SOCI backend loader to also use default install directory. -set_directory_properties(PROPERTIES INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}) -set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}) -configure_file(soci_backends_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/soci_backends_config.h) - # # Core shared library # if (SOCI_SHARED) add_library(${SOCI_CORE_TARGET} SHARED ${SOCI_CORE_HEADERS} ${SOCI_CORE_SOURCES}) - target_link_libraries(${SOCI_CORE_TARGET} ${SOCI_CORE_DEPENDENCIES}) + target_link_libraries(${SOCI_CORE_TARGET} ${SOCI_CORE_DEPS_LIBS}) if(WIN32) set_target_properties(${SOCI_CORE_TARGET} @@ -100,8 +62,10 @@ if (SOCI_SHARED) endif() endif() +# This adds definitions to all build configurations. SOCI_DEBUG_POSTFIX is passed to soci library add_definitions(-DSOCI_LIB_PREFIX="${CMAKE_SHARED_LIBRARY_PREFIX}soci_" - -DSOCI_LIB_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}") + -DSOCI_LIB_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}" + -DSOCI_DEBUG_POSTFIX="${CMAKE_DEBUG_POSTFIX}") # # Core static library @@ -109,7 +73,15 @@ add_definitions(-DSOCI_LIB_PREFIX="${CMAKE_SHARED_LIBRARY_PREFIX}soci_" if (SOCI_STATIC) set(SOCI_CORE_TARGET_STATIC ${SOCI_CORE_TARGET}_static) - add_library(${SOCI_CORE_TARGET_STATIC} STATIC ${SOCI_CORE_HEADERS} ${SOCI_CORE_SOURCES}) + 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 @@ -142,13 +114,9 @@ endif() # boost_report_value(SOCI_CORE_TARGET) boost_report_value(SOCI_CORE_TARGET_OUTPUT_NAME) -boost_report_value(SOCI_CORE_DEPENDENCIES) +boost_report_value(SOCI_CORE_DEPS_LIBS) +boost_report_value(SOCI_CORE_INCLUDE_DIRS) boost_report_value(WITH_BOOST) soci_report_directory_property(COMPILE_DEFINITIONS) -# Export core target name to make it visible by backends -set(SOCI_CORE_TARGET ${SOCI_CORE_TARGET} PARENT_SCOPE) -if (SOCI_STATIC) - set(SOCI_CORE_TARGET_STATIC ${SOCI_CORE_TARGET_STATIC} PARENT_SCOPE) -endif() -set(SOCI_CORE_STATIC_DEPENDENCIES ${SOCI_CORE_DEPENDENCIES} PARENT_SCOPE) +message(STATUS "") diff --git a/src/core/Makefile.basic b/src/core/Makefile.basic index f24fa2f1ab..8bc2e42f1f 100644 --- a/src/core/Makefile.basic +++ b/src/core/Makefile.basic @@ -1,6 +1,6 @@ COMPILER = g++ CXXFLAGS = -Wall -pedantic -Wno-long-long -INCLUDEDIRS = +INCLUDEDIRS = -I../../include -I../../include/private BACKENDLOADERDEFS = -DSOCI_LIB_PREFIX=\"libsoci_\" -DSOCI_LIB_SUFFIX=\".so\" @@ -8,18 +8,25 @@ OBJS = session.o statement.o row.o values.o \ into-type.o use-type.o \ blob.o rowid.o procedure.o ref-counted-prepare-info.o ref-counted-statement.o \ once-temp-type.o prepare-temp-type.o error.o transaction.o backend-loader.o \ - connection-pool.o soci-simple.o + connection-pool.o connection-parameters.o soci-simple.o -libsoci_core.a : ${OBJS} - ar rv $@ $? +libsoci_core.a : generated ${OBJS} + ar rv $@ ${OBJS} rm *.o -shared : ${OBJS} - ${COMPILER} -fPIC -c $? ${CXXFLAGS} ${INCLUDEDIRS} - ${COMPILER} -shared -o libsoci_core.so ${OBJS} +shared : generated ${OBJS} + ${COMPILER} -fPIC -c ${OBJS} ${CXXFLAGS} ${INCLUDEDIRS} + ${COMPILER} -shared -o libsoci_core.so ${OBJS} rm *.o +generated : ../../include/private/soci_backends_config.h + +# Note: this file is generated with a basic search path, +# full backends search path is generated by CMake. +../../include/private/soci_backends_config.h : soci_backends_config.h.in + echo '#define DEFAULT_BACKENDS_PATH "."' > $@ + session.o : session.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} @@ -37,7 +44,7 @@ into-type.o : into-type.cpp use-type.o : use-type.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - + blob.o : blob.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} @@ -71,9 +78,12 @@ backend-loader.o : backend-loader.cpp connection-pool.o : connection-pool.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} +connection-parameters.o : connection-parameters.cpp + ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} + soci-simple.o : soci-simple.cpp ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} clean : - rm -f libsoci_core.a libsoci_core.so + rm -f libsoci_core.a libsoci_core.so ../../include/private/soci_backends_config.h diff --git a/src/core/backend-loader.cpp b/src/core/backend-loader.cpp index ad228cdf31..456b2a7588 100644 --- a/src/core/backend-loader.cpp +++ b/src/core/backend-loader.cpp @@ -6,9 +6,9 @@ // #define SOCI_SOURCE -#include "backend-loader.h" -#include "error.h" -#include +#include "soci/soci-platform.h" +#include "soci/backend-loader.h" +#include "soci/error.h" #include #include #include @@ -42,7 +42,11 @@ typedef HMODULE soci_handler_t; #define DLSYM(x, y) GetProcAddress(x, y) #ifdef SOCI_ABI_VERSION -#define LIBNAME(x) (SOCI_LIB_PREFIX + x + "_" SOCI_ABI_VERSION SOCI_LIB_SUFFIX) + #ifndef NDEBUG + #define LIBNAME(x) (SOCI_LIB_PREFIX + x + "_" SOCI_ABI_VERSION SOCI_DEBUG_POSTFIX SOCI_LIB_SUFFIX) + #else + #define LIBNAME(x) (SOCI_LIB_PREFIX + x + "_" SOCI_ABI_VERSION SOCI_LIB_SUFFIX) + #endif #else #define LIBNAME(x) (SOCI_LIB_PREFIX + x + SOCI_LIB_SUFFIX) #endif // SOCI_ABI_VERSION @@ -243,7 +247,7 @@ void do_register_backend(std::string const & name, std::string const & shared_ob // unload the existing handler if it's already loaded do_unload(name); - + backend_factory const* f = entry(); info new_entry; @@ -274,8 +278,6 @@ backend_factory const& dynamic_backends::get(std::string const& name) i = factories_.find(name); - assert(i != factories_.end()); - return *(i->second.factory_); } @@ -300,7 +302,7 @@ SOCI_DECL void dynamic_backends::register_backend( // unload the existing handler if it's already loaded do_unload(name); - + info new_entry; new_entry.factory_ = &factory; diff --git a/src/core/blob.cpp b/src/core/blob.cpp index 91c779d21f..819ec30e03 100644 --- a/src/core/blob.cpp +++ b/src/core/blob.cpp @@ -6,8 +6,8 @@ // #define SOCI_SOURCE -#include "blob.h" -#include "session.h" +#include "soci/blob.h" +#include "soci/session.h" #include diff --git a/src/core/boost-fusion.h b/src/core/boost-fusion.h deleted file mode 100644 index 5d18c8cc71..0000000000 --- a/src/core/boost-fusion.h +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (C) 2004-2008 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) -// - -#ifndef SOCI_BOOST_FUSION_H_INCLUDED -#define SOCI_BOOST_FUSION_H_INCLUDED - -#ifndef SOCI_MAX_FUSION_SEQUENCE_LENGTH -#define SOCI_MAX_FUSION_SEQUENCE_LENGTH 10 -#endif - -#include "values.h" -#include "type-conversion-traits.h" -// boost -#include -#include -#include -#include -#include -#include -#include -#include - -namespace soci -{ -namespace detail -{ - -template -struct type_conversion; - -#define SOCI_READ_FROM_BASE(z, k, data) \ - >> boost::fusion::at_c(out) -/**/ - -#define SOCI_READ_TO_BASE(z, k, data) \ - << boost::fusion::at_c(in) -/**/ - -#define SOCI_TYPE_CONVERSION_FUSION(z, k, data) \ - template \ - struct type_conversion \ - { \ - typedef values base_type; \ - \ - static void from_base(base_type const & in, indicator /*ind*/, Seq & out) \ - { \ - in \ - BOOST_PP_REPEAT(k, SOCI_READ_FROM_BASE, BOOST_PP_EMPTY) \ - ; \ - } \ - \ - static void to_base(Seq & in, base_type & out, indicator & /*ind*/) \ - { \ - out \ - BOOST_PP_REPEAT(k, SOCI_READ_TO_BASE, BOOST_PP_EMPTY) \ - ; \ - } \ - }; -/**/ - -BOOST_PP_REPEAT_FROM_TO(1, BOOST_PP_ADD(SOCI_MAX_FUSION_SEQUENCE_LENGTH, 1), SOCI_TYPE_CONVERSION_FUSION, BOOST_PP_EMPTY) - -#undef SOCI_TYPE_CONVERSION_FUSION -#undef SOCI_READ_FROM_BASE -#undef SOCI_READ_TO_BASE - -} // namespace detail - -template -struct type_conversion - >::type > -{ - typedef values base_type; - -private: - typedef typename boost::fusion::result_of::size::type size; - typedef detail::type_conversion converter; - -public: - static void from_base(base_type const & in, indicator ind, T& out) - { - converter::from_base( in, ind, out ); - } - - static void to_base(T& in, base_type & out, indicator & ind) - { - converter::to_base( in, out, ind ); - } -}; - -} // namespace soci - -#endif // SOCI_BOOST_FUSION_H_INCLUDED diff --git a/src/core/boost-tuple.h b/src/core/boost-tuple.h deleted file mode 100644 index 7cf8240f66..0000000000 --- a/src/core/boost-tuple.h +++ /dev/null @@ -1,324 +0,0 @@ -// -// Copyright (C) 2004-2008 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) -// - -#ifndef SOCI_BOOST_TUPLE_H_INCLUDED -#define SOCI_BOOST_TUPLE_H_INCLUDED - -#include "values.h" -#include "type-conversion-traits.h" -// boost -#include - -#if defined(BOOST_VERSION) && BOOST_VERSION < 103500 - -namespace soci -{ - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out); - } - - static void to_base(boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in); - } -}; - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out) - >> boost::tuples::get<1>(out); - } - - static void to_base(boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in) - << boost::tuples::get<1>(in); - } -}; - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out) - >> boost::tuples::get<1>(out) - >> boost::tuples::get<2>(out); - } - - static void to_base(boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in) - << boost::tuples::get<1>(in) - << boost::tuples::get<2>(in); - } -}; - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out) - >> boost::tuples::get<1>(out) - >> boost::tuples::get<2>(out) - >> boost::tuples::get<3>(out); - } - - static void to_base(boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in) - << boost::tuples::get<1>(in) - << boost::tuples::get<2>(in) - << boost::tuples::get<3>(in); - } -}; - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out) - >> boost::tuples::get<1>(out) - >> boost::tuples::get<2>(out) - >> boost::tuples::get<3>(out) - >> boost::tuples::get<4>(out); - } - - static void to_base(boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in) - << boost::tuples::get<1>(in) - << boost::tuples::get<2>(in) - << boost::tuples::get<3>(in) - << boost::tuples::get<4>(in); - } -}; - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out) - >> boost::tuples::get<1>(out) - >> boost::tuples::get<2>(out) - >> boost::tuples::get<3>(out) - >> boost::tuples::get<4>(out) - >> boost::tuples::get<5>(out); - } - - static void to_base(boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in) - << boost::tuples::get<1>(in) - << boost::tuples::get<2>(in) - << boost::tuples::get<3>(in) - << boost::tuples::get<4>(in) - << boost::tuples::get<5>(in); - } -}; - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out) - >> boost::tuples::get<1>(out) - >> boost::tuples::get<2>(out) - >> boost::tuples::get<3>(out) - >> boost::tuples::get<4>(out) - >> boost::tuples::get<5>(out) - >> boost::tuples::get<6>(out); - } - - static void to_base(boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in) - << boost::tuples::get<1>(in) - << boost::tuples::get<2>(in) - << boost::tuples::get<3>(in) - << boost::tuples::get<4>(in) - << boost::tuples::get<5>(in) - << boost::tuples::get<6>(in); - } -}; - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out) - >> boost::tuples::get<1>(out) - >> boost::tuples::get<2>(out) - >> boost::tuples::get<3>(out) - >> boost::tuples::get<4>(out) - >> boost::tuples::get<5>(out) - >> boost::tuples::get<6>(out) - >> boost::tuples::get<7>(out); - } - - static void to_base(boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in) - << boost::tuples::get<1>(in) - << boost::tuples::get<2>(in) - << boost::tuples::get<3>(in) - << boost::tuples::get<4>(in) - << boost::tuples::get<5>(in) - << boost::tuples::get<6>(in) - << boost::tuples::get<7>(in); - } -}; - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out) - >> boost::tuples::get<1>(out) - >> boost::tuples::get<2>(out) - >> boost::tuples::get<3>(out) - >> boost::tuples::get<4>(out) - >> boost::tuples::get<5>(out) - >> boost::tuples::get<6>(out) - >> boost::tuples::get<7>(out) - >> boost::tuples::get<8>(out); - } - - static void to_base(boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in) - << boost::tuples::get<1>(in) - << boost::tuples::get<2>(in) - << boost::tuples::get<3>(in) - << boost::tuples::get<4>(in) - << boost::tuples::get<5>(in) - << boost::tuples::get<6>(in) - << boost::tuples::get<7>(in) - << boost::tuples::get<8>(in); - } -}; - -template -struct type_conversion > -{ - typedef values base_type; - - static void from_base(base_type const & in, indicator ind, - boost::tuple & out) - { - in - >> boost::tuples::get<0>(out) - >> boost::tuples::get<1>(out) - >> boost::tuples::get<2>(out) - >> boost::tuples::get<3>(out) - >> boost::tuples::get<4>(out) - >> boost::tuples::get<5>(out) - >> boost::tuples::get<6>(out) - >> boost::tuples::get<7>(out) - >> boost::tuples::get<8>(out) - >> boost::tuples::get<9>(out); - } - - static void to_base( - boost::tuple & in, - base_type & out, indicator & ind) - { - out - << boost::tuples::get<0>(in) - << boost::tuples::get<1>(in) - << boost::tuples::get<2>(in) - << boost::tuples::get<3>(in) - << boost::tuples::get<4>(in) - << boost::tuples::get<5>(in) - << boost::tuples::get<6>(in) - << boost::tuples::get<7>(in) - << boost::tuples::get<8>(in) - << boost::tuples::get<9>(in); - } -}; - -} // namespace soci - -#else // BOOST_VERSION >= 103500 -# include "boost-fusion.h" -# include -#endif - -#endif // SOCI_BOOST_TUPLE_H_INCLUDED diff --git a/src/core/connection-parameters.cpp b/src/core/connection-parameters.cpp index 30d3e2fa4a..1b5947071f 100644 --- a/src/core/connection-parameters.cpp +++ b/src/core/connection-parameters.cpp @@ -6,9 +6,9 @@ // #define SOCI_SOURCE -#include "connection-parameters.h" -#include "soci-backend.h" -#include "backend-loader.h" +#include "soci/connection-parameters.h" +#include "soci/soci-backend.h" +#include "soci/backend-loader.h" using namespace soci; diff --git a/src/core/connection-pool.cpp b/src/core/connection-pool.cpp index 265e21dad3..d4ffa01ddc 100644 --- a/src/core/connection-pool.cpp +++ b/src/core/connection-pool.cpp @@ -6,9 +6,9 @@ // #define SOCI_SOURCE -#include "connection-pool.h" -#include "error.h" -#include "session.h" +#include "soci/connection-pool.h" +#include "soci/error.h" +#include "soci/session.h" #include #include @@ -97,9 +97,8 @@ std::size_t connection_pool::lease() { std::size_t pos; - // no timeout - bool const success = try_lease(pos, -1); - assert(success); + // no timeout, so can't fail + try_lease(pos, -1); return pos; } @@ -157,7 +156,18 @@ bool connection_pool::try_lease(std::size_t & pos, int timeout) pthread_mutex_unlock(&(pimpl_->mtx_)); - return cc == 0; + if (cc != 0) + { + // we can only fail if timeout expired + if (timeout < 0) + { + throw soci_error("Getting connection from the pool unexpectedly failed"); + } + + return false; + } + + return true; } void connection_pool::give_back(std::size_t pos) @@ -270,13 +280,8 @@ std::size_t connection_pool::lease() { std::size_t pos; - // no timeout - bool const success = try_lease(pos, -1); - assert(success); - if (!success) - { - // TODO: anything to report? --mloskot - } + // no timeout, allow unlimited blocking + try_lease(pos, -1); return pos; } @@ -291,11 +296,10 @@ bool connection_pool::try_lease(std::size_t & pos, int timeout) EnterCriticalSection(&(pimpl_->mtx_)); - bool const success = pimpl_->find_free(pos); - assert(success); - if (!success) + if (!pimpl_->find_free(pos)) { - // TODO: anything to report? --mloskot + // this should be impossible + throw soci_error("Getting connection from the pool unexpectedly failed"); } pimpl_->sessions_[pos].first = false; diff --git a/src/core/error.cpp b/src/core/error.cpp index b16e2ca280..3a83df5358 100644 --- a/src/core/error.cpp +++ b/src/core/error.cpp @@ -1,5 +1,6 @@ // // Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton +// Copyright (C) 2015 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) @@ -7,11 +8,140 @@ #define SOCI_SOURCE -#include "error.h" +#include "soci/error.h" -using namespace soci; +#include +#include + +namespace soci +{ + +class soci_error_extra_info +{ +public: + soci_error_extra_info() + { + } + + // Default copy ctor, assignment operator and dtor are fine. + + char const* get_full_message(std::string const& message) + { + if (full_message_.empty()) + { + full_message_ = message; + + if (!contexts_.empty()) + { + // This is a hack, but appending the extra context to the + // message looks much better if we remove the full stop at its + // end first. + if (*full_message_.rbegin() == '.') + full_message_.erase(full_message_.size() - 1); + + // Now do append all the extra context we have. + typedef std::vector::const_iterator iter_type; + for (iter_type i = contexts_.begin(); i != contexts_.end(); ++i) + { + full_message_ += " "; + full_message_ += *i; + } + + // It seems better to always terminate the full message with a + // full stop, even if the original error message didn't have it + // (and if it had, we just restore the one we chopped off). + full_message_ += "."; + } + } + + return full_message_.c_str(); + } + + void add_context(std::string const& context) + { + full_message_.clear(); + contexts_.push_back(context); + } + +private: + // The full error message, we need to store it as a string as we return a + // pointer to its contents from get_full_message(). + std::string full_message_; + + // If non-empty, contains extra context for this exception, e.g. + // information about the SQL statement that resulted in it, with the top + // element corresponding to the most global context. + std::vector contexts_; +}; + +namespace +{ + +// Make a safe, even in presence of exceptions, heap-allocated copy of the +// given object if it's non-null (otherwise just return null pointer). +soci_error_extra_info *make_safe_copy(soci_error_extra_info* info) +{ + try + { + return info ? new soci_error_extra_info(*info) : NULL; + } + catch (...) + { + // Copy ctor of an exception class shouldn't throw to avoid program + // termination, so it's better to lose the extra information than allow + // an exception to except from here. + return NULL; + } +} + +} // anonymous namespace soci_error::soci_error(std::string const & msg) : std::runtime_error(msg) { + info_ = NULL; } + +soci_error::soci_error(soci_error const& e) + : std::runtime_error(e) +{ + info_ = make_safe_copy(e.info_); +} + +soci_error& soci_error::operator=(soci_error const& e) +{ + std::runtime_error::operator=(e); + + delete info_; + info_ = make_safe_copy(e.info_); + + return *this; +} + +soci_error::~soci_error() throw() +{ + delete info_; +} + +std::string soci_error::get_error_message() const +{ + return std::runtime_error::what(); +} + +char const* soci_error::what() const throw() +{ + if (info_) + return info_->get_full_message(get_error_message()); + + return std::runtime_error::what(); +} + +void soci_error::add_context(std::string const& context) +{ + if (!info_) + info_ = new soci_error_extra_info(); + + info_->add_context(context); +} + +} // namespace soci diff --git a/src/core/error.h b/src/core/error.h deleted file mode 100644 index 3e69375773..0000000000 --- a/src/core/error.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (C) 2004-2008 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_ERROR_H_INCLUDED -#define SOCI_ERROR_H_INCLUDED - -#include "soci-config.h" -// std -#include -#include - -namespace soci -{ - -class SOCI_DECL soci_error : public std::runtime_error -{ -public: - explicit soci_error(std::string const & msg); -}; - -} // namespace soci - -#endif // SOCI_ERROR_H_INCLUDED diff --git a/src/core/into-type.cpp b/src/core/into-type.cpp index 15fb1a1dd1..f3124238be 100644 --- a/src/core/into-type.cpp +++ b/src/core/into-type.cpp @@ -6,8 +6,8 @@ // #define SOCI_SOURCE -#include "into-type.h" -#include "statement.h" +#include "soci/into-type.h" +#include "soci/statement.h" using namespace soci; using namespace soci::details; @@ -67,7 +67,6 @@ void vector_into_type::post_fetch(bool gotData, bool /* calledFromFetch */) { if (indVec_ != NULL && indVec_->empty() == false) { - assert(indVec_->empty() == false); backEnd_->post_fetch(gotData, &(*indVec_)[0]); } else diff --git a/src/core/once-temp-type.cpp b/src/core/once-temp-type.cpp index 91dfab5925..2b968e67e1 100644 --- a/src/core/once-temp-type.cpp +++ b/src/core/once-temp-type.cpp @@ -6,9 +6,9 @@ // #define SOCI_SOURCE -#include "once-temp-type.h" -#include "ref-counted-statement.h" -#include "session.h" +#include "soci/once-temp-type.h" +#include "soci/ref-counted-statement.h" +#include "soci/session.h" using namespace soci; using namespace soci::details; @@ -45,9 +45,3 @@ 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; -} diff --git a/src/core/prepare-temp-type.cpp b/src/core/prepare-temp-type.cpp index 4631bb38f8..1018bc3fa1 100644 --- a/src/core/prepare-temp-type.cpp +++ b/src/core/prepare-temp-type.cpp @@ -6,9 +6,9 @@ // #define SOCI_SOURCE -#include "prepare-temp-type.h" -#include "ref-counted-prepare-info.h" -#include "session.h" +#include "soci/prepare-temp-type.h" +#include "soci/ref-counted-prepare-info.h" +#include "soci/session.h" using namespace soci; using namespace soci::details; @@ -45,9 +45,3 @@ prepare_temp_type & prepare_temp_type::operator,(into_type_ptr const & i) rcpi_->exchange(i); return *this; } - -prepare_temp_type & prepare_temp_type::operator,(use_type_ptr const & u) -{ - rcpi_->exchange(u); - return *this; -} diff --git a/src/core/procedure.cpp b/src/core/procedure.cpp index 59f8d7a92d..a1775dc772 100644 --- a/src/core/procedure.cpp +++ b/src/core/procedure.cpp @@ -6,9 +6,9 @@ // #define SOCI_SOURCE -#include "procedure.h" -#include "statement.h" -#include "prepare-temp-type.h" +#include "soci/procedure.h" +#include "soci/statement.h" +#include "soci/prepare-temp-type.h" using namespace soci; using namespace soci::details; diff --git a/src/core/ref-counted-prepare-info.cpp b/src/core/ref-counted-prepare-info.cpp index a655e16ea3..5094217926 100644 --- a/src/core/ref-counted-prepare-info.cpp +++ b/src/core/ref-counted-prepare-info.cpp @@ -6,24 +6,12 @@ // #define SOCI_SOURCE -#include "ref-counted-prepare-info.h" -#include "session.h" +#include "soci/ref-counted-prepare-info.h" +#include "soci/session.h" using namespace soci; using namespace soci::details; -void ref_counted_prepare_info::exchange(into_type_ptr const & i) -{ - intos_.push_back(i.get()); - i.release(); -} - -void ref_counted_prepare_info::exchange(use_type_ptr const & u) -{ - uses_.push_back(u.get()); - u.release(); -} - void ref_counted_prepare_info::final_action() { // deallocate all bind and define objects diff --git a/src/core/ref-counted-statement.cpp b/src/core/ref-counted-statement.cpp index ec7b4ddd49..38a96b2edb 100644 --- a/src/core/ref-counted-statement.cpp +++ b/src/core/ref-counted-statement.cpp @@ -6,8 +6,8 @@ // #define SOCI_SOURCE -#include "ref-counted-statement.h" -#include "session.h" +#include "soci/ref-counted-statement.h" +#include "soci/session.h" using namespace soci; using namespace soci::details; diff --git a/src/core/row.cpp b/src/core/row.cpp index eb2f10bcd1..2612e60095 100644 --- a/src/core/row.cpp +++ b/src/core/row.cpp @@ -6,7 +6,7 @@ // #define SOCI_SOURCE -#include "row.h" +#include "soci/row.h" #include #include @@ -79,8 +79,7 @@ void row::clean_up() indicator row::get_indicator(std::size_t pos) const { - assert(indicators_.size() >= static_cast(pos + 1)); - return *indicators_[pos]; + return *indicators_.at(pos); } indicator row::get_indicator(std::string const &name) const @@ -90,8 +89,7 @@ indicator row::get_indicator(std::string const &name) const column_properties const & row::get_properties(std::size_t pos) const { - assert(columns_.size() >= pos + 1); - return columns_[pos]; + return columns_.at(pos); } column_properties const & row::get_properties(std::string const &name) const diff --git a/src/core/rowid.cpp b/src/core/rowid.cpp index 5f64244a99..eed0e2fa22 100644 --- a/src/core/rowid.cpp +++ b/src/core/rowid.cpp @@ -6,8 +6,8 @@ // #define SOCI_SOURCE -#include "rowid.h" -#include "session.h" +#include "soci/rowid.h" +#include "soci/session.h" using namespace soci; using namespace soci::details; diff --git a/src/core/session.cpp b/src/core/session.cpp index 27568bfee4..920cd7cf13 100644 --- a/src/core/session.cpp +++ b/src/core/session.cpp @@ -6,15 +6,11 @@ // #define SOCI_SOURCE -#include "session.h" -#include "connection-parameters.h" -#include "connection-pool.h" -#include "soci-backend.h" -#include "query_transformation.h" - -#ifdef _MSC_VER -#pragma warning(disable:4355) -#endif +#include "soci/session.h" +#include "soci/connection-parameters.h" +#include "soci/connection-pool.h" +#include "soci/soci-backend.h" +#include "soci/query_transformation.h" using namespace soci; using namespace soci::details; @@ -222,7 +218,7 @@ std::string session::get_query() const else { // preserve logical constness of get_query, - // stream used as read-only here, + // stream used as read-only here, session* pthis = const_cast(this); // sole place where any user-defined query transformation is applied @@ -234,8 +230,14 @@ std::string session::get_query() const } } -void session::set_query_transformation_( - std::auto_ptr qtf) + +#ifdef SOCI_CXX_C11 +void session::set_query_transformation_( std::unique_ptr &qtf) +#else +void session::set_query_transformation_( std::auto_ptr qtf) +#endif + + { if (isFromPool_) { diff --git a/src/core/soci-config.h b/src/core/soci-config.h deleted file mode 100644 index 3cae58c9f0..0000000000 --- a/src/core/soci-config.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (C) 2006-2008 Mateusz Loskot -// 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_CONFIG_H_INCLUDED -#define SOCI_CONFIG_H_INCLUDED - -// -// On Windows platform, define SOCI_DECL depending on -// static or dynamic (SOCI_DLL) linkage. -// -// For details, see -// http://www.boost.org/more/separate_compilation.html -// - -#ifdef _WIN32 -# ifdef SOCI_DLL -# ifdef SOCI_SOURCE -# define SOCI_DECL __declspec(dllexport) -# else -# define SOCI_DECL __declspec(dllimport) -# endif // SOCI_SOURCE -# endif // SOCI_DLL -#endif // _WIN32 -// -// If SOCI_DECL isn't defined yet define it now -#ifndef SOCI_DECL -# define SOCI_DECL -#endif - -#ifdef _MSC_VER -#pragma warning(disable:4251 4275) -#endif // _MSC_VER - -#endif // SOCI_CONFIG_H_INCLUDED diff --git a/src/core/soci-platform.h b/src/core/soci-platform.h deleted file mode 100644 index a6c1bb4f56..0000000000 --- a/src/core/soci-platform.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (C) 2006-2008 Mateusz Loskot -// 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_PLATFORM_H_INCLUDED -#define SOCI_PLATFORM_H_INCLUDED - -#if defined(_MSC_VER) || defined(__MINGW32__) -#define LL_FMT_FLAGS "I64" -#else -#define LL_FMT_FLAGS "ll" -#endif - -// Portability hacks for Microsoft Visual C++ compiler -#ifdef _MSC_VER -#include - -// Define if you have the vsnprintf variants. -#if _MSC_VER < 1500 -# define HAVE_VSNPRINTF 1 -# define vsnprintf _vsnprintf -#endif - -// Define if you have the snprintf variants. -#define HAVE_SNPRINTF 1 -#define snprintf _snprintf - -// Define if you have the strtoll and strtoull variants. -#if _MSC_VER >= 1300 -# define HAVE_STRTOLL 1 -# define HAVE_STRTOULL 1 - -#if _MSC_VER < 1800 -namespace std { - inline long long strtoll(char const* str, char** str_end, int base) - { - return _strtoi64(str, str_end, base); - } - - inline unsigned long long strtoull(char const* str, char** str_end, int base) - { - return _strtoui64(str, str_end, base); - } -} -#endif - -#else -# undef HAVE_STRTOLL -# undef HAVE_STRTOULL -# error "Visual C++ versions prior 1300 don't support _strtoi64 and _strtoui64" -#endif // _MSC_VER >= 1300 -#endif // _MSC_VER - -#endif // SOCI_PLATFORM_H_INCLUDED diff --git a/src/core/soci-simple.cpp b/src/core/soci-simple.cpp index f73a18dd72..99a2de1291 100644 --- a/src/core/soci-simple.cpp +++ b/src/core/soci-simple.cpp @@ -7,8 +7,8 @@ #define SOCI_SOURCE -#include "soci-simple.h" -#include "soci.h" +#include "soci/soci-simple.h" +#include "soci/soci.h" #include #include @@ -134,6 +134,157 @@ SOCI_DECL char const * soci_session_error_message(session_handle s) } +// blob +struct blob_wrapper +{ + blob_wrapper(session &sql) + : blob_(sql), is_ok(true) {} + + blob blob_; + bool is_ok; + std::string error_message; +}; + +blob_wrapper *soci_create_blob_session(soci::session &sql) +{ + blob_wrapper *bw = NULL; + try + { + bw = new blob_wrapper(sql); + } + catch (...) + { + delete bw; + return NULL; + } + + return bw; +} + +SOCI_DECL blob_handle soci_create_blob(session_handle s) +{ + session_wrapper * session = static_cast(s); + if (!session->is_ok) + return NULL; + + return soci_create_blob_session(session->sql); +} + +SOCI_DECL void soci_destroy_blob(blob_handle b) +{ + blob_wrapper *blob = static_cast(b); + try + { + delete blob; + } + catch (...) + {} +} + +SOCI_DECL int soci_blob_get_len(blob_handle b) +{ + blob_wrapper *blob = static_cast(b); + return static_cast(blob->blob_.get_len()); +} + +SOCI_DECL int soci_blob_read(blob_handle b, int offset, char *buf, int toRead) +{ + blob_wrapper *blob = static_cast(b); + try + { + return static_cast(blob->blob_.read(offset, buf, toRead)); + } + catch (std::exception &e) + { + blob->is_ok = false; + blob->error_message = e.what(); + return -1; + } + catch (...) + { + blob->is_ok = false; + blob->error_message = "unknown exception"; + return -1; + } +} + +SOCI_DECL int soci_blob_write(blob_handle b, int offset, char const *buf, int toWrite) +{ + blob_wrapper *blob = static_cast(b); + try + { + return static_cast(blob->blob_.write(offset, buf, toWrite)); + } + catch (std::exception &e) + { + blob->is_ok = false; + blob->error_message = e.what(); + return -1; + } + catch (...) + { + blob->is_ok = false; + blob->error_message = "unknown exception"; + return -1; + } +} + +SOCI_DECL int soci_blob_append(blob_handle b, char const *buf, int toWrite) +{ + blob_wrapper *blob = static_cast(b); + try + { + return static_cast(blob->blob_.append(buf, toWrite)); + } + catch (std::exception &e) + { + blob->is_ok = false; + blob->error_message = e.what(); + return -1; + } + catch (...) + { + blob->is_ok = false; + blob->error_message = "unknown exception"; + return -1; + } +} + +SOCI_DECL int soci_blob_trim(blob_handle b, int newLen) +{ + blob_wrapper *blob = static_cast(b); + try + { + blob->blob_.trim(newLen); + } + catch (std::exception &e) + { + blob->is_ok = false; + blob->error_message = e.what(); + return -1; + } + catch (...) + { + blob->is_ok = false; + blob->error_message = "unknown exception"; + return -1; + } + + return 0; +} + +SOCI_DECL int soci_blob_state(blob_handle b) +{ + blob_wrapper *blob = static_cast(b); + return (blob->is_ok ? 1 : 0); +} + +SOCI_DECL char const * soci_blob_error_message(blob_handle b) +{ + blob_wrapper *blob = static_cast(b); + return blob->error_message.c_str(); +} + // statement @@ -142,10 +293,13 @@ namespace // unnamed struct statement_wrapper { - statement_wrapper(session & sql) - : st(sql), statement_state(clean), into_kind(empty), use_kind(empty), + statement_wrapper(session & _sql) + : sql(_sql), st(sql), statement_state(clean), into_kind(empty), use_kind(empty), next_position(0), is_ok(true) {} + ~statement_wrapper(); + + session &sql; statement st; enum state { clean, defining, executing } statement_state; @@ -160,6 +314,7 @@ struct statement_wrapper std::map into_longlongs; std::map into_doubles; std::map into_dates; + std::map into_blob; std::vector > into_indicators_v; std::map > into_strings_v; @@ -175,6 +330,7 @@ struct statement_wrapper std::map use_longlongs; std::map use_doubles; std::map use_dates; + std::map use_blob; std::map > use_indicators_v; std::map > use_strings_v; @@ -188,8 +344,29 @@ struct statement_wrapper bool is_ok; std::string error_message; + +private: + SOCI_NOT_COPYABLE(statement_wrapper) }; +statement_wrapper::~statement_wrapper() +{ + for (std::map::iterator iter = into_blob.begin(), last = into_blob.end(); + iter != last; ++iter) + { + soci_destroy_blob(iter->second); + } + + for (std::map::iterator iter = use_blob.begin(), last = use_blob.end(); + iter != last; ++iter) + { + soci::indicator &ind = use_indicators[iter->first]; + blob_wrapper *&blob = iter->second; + if (ind == i_null && blob != NULL) + soci_destroy_blob(blob); + } +} + // helper for checking if the attempt was made to add more into/use elements // after the statement was set for execution bool cannot_add_elements(statement_wrapper & wrapper, statement_wrapper::kind k, bool into) @@ -200,7 +377,7 @@ bool cannot_add_elements(statement_wrapper & wrapper, statement_wrapper::kind k, wrapper.error_message = "Cannot add more data items."; return true; } - + if (into) { if (k == statement_wrapper::single && wrapper.into_kind == statement_wrapper::bulk) @@ -377,6 +554,7 @@ bool name_exists_check_failed(statement_wrapper & wrapper, } break; case dt_long_long: + case dt_unsigned_long_long: { typedef std::map::const_iterator iterator; @@ -398,8 +576,13 @@ bool name_exists_check_failed(statement_wrapper & wrapper, name_exists = (it != wrapper.use_dates.end()); } break; - default: - assert(false); + case dt_blob: + { + typedef std::map::const_iterator iterator; + iterator const it = wrapper.use_blob.find(name); + name_exists = (it != wrapper.use_blob.end()); + } + break; } } else @@ -431,6 +614,7 @@ bool name_exists_check_failed(statement_wrapper & wrapper, } break; case dt_long_long: + case dt_unsigned_long_long: { typedef std::map < @@ -457,8 +641,9 @@ bool name_exists_check_failed(statement_wrapper & wrapper, name_exists = (it != wrapper.use_dates_v.end()); } break; - default: - assert(false); + case dt_blob: + // no support for bulk load + break; } } @@ -647,6 +832,24 @@ SOCI_DECL int soci_into_date(statement_handle st) return wrapper->next_position++; } +SOCI_DECL int soci_into_blob(statement_handle st) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, true)) + { + return -1; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->into_kind = statement_wrapper::single; + + wrapper->into_types.push_back(dt_blob); + wrapper->into_indicators.push_back(i_ok); + wrapper->into_blob[wrapper->next_position] = soci_create_blob_session(wrapper->sql); // create new entry + return wrapper->next_position++; +} + SOCI_DECL int soci_into_string_v(statement_handle st) { statement_wrapper * wrapper = static_cast(st); @@ -824,6 +1027,20 @@ SOCI_DECL char const * soci_get_into_date(statement_handle st, int position) return format_date(*wrapper, d); } +SOCI_DECL blob_handle soci_get_into_blob(statement_handle st, int position) +{ + statement_wrapper * wrapper = static_cast(st); + + if (position_check_failed(*wrapper, + statement_wrapper::single, position, dt_blob, "blob") || + not_null_check_failed(*wrapper, position)) + { + return NULL; + } + + return wrapper->into_blob[position]; +} + SOCI_DECL int soci_into_get_size_v(statement_handle st) { statement_wrapper * wrapper = static_cast(st); @@ -869,6 +1086,7 @@ SOCI_DECL void soci_into_resize_v(statement_handle st, int new_size) wrapper->into_ints_v[i].resize(new_size); break; case dt_long_long: + case dt_unsigned_long_long: wrapper->into_longlongs_v[i].resize(new_size); break; case dt_double: @@ -877,8 +1095,9 @@ SOCI_DECL void soci_into_resize_v(statement_handle st, int new_size) case dt_date: wrapper->into_dates_v[i].resize(new_size); break; - default: - assert(false); + case dt_blob: + // no support for bulk blob + break; } } @@ -1090,6 +1309,23 @@ SOCI_DECL void soci_use_date(statement_handle st, char const * name) wrapper->use_dates[name]; // create new entry } +SOCI_DECL void soci_use_blob(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (cannot_add_elements(*wrapper, statement_wrapper::single, false) || + name_unique_check_failed(*wrapper, statement_wrapper::single, name)) + { + return; + } + + wrapper->statement_state = statement_wrapper::defining; + wrapper->use_kind = statement_wrapper::single; + + wrapper->use_indicators[name] = i_null; // create new entry + wrapper->use_blob[name] = soci_create_blob_session(wrapper->sql); // create new entry +} + SOCI_DECL void soci_use_string_v(statement_handle st, char const * name) { statement_wrapper * wrapper = static_cast(st); @@ -1269,6 +1505,25 @@ SOCI_DECL void soci_set_use_date(statement_handle st, char const * name, char co wrapper->use_dates[name] = dt; } +SOCI_DECL void soci_set_use_blob(statement_handle st, char const * name, blob_handle b) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_blob, statement_wrapper::single, "blob")) + { + return; + } + + soci::indicator &ind = wrapper->use_indicators[name]; + blob_wrapper *&blob = wrapper->use_blob[name]; + if (ind == i_null && blob != NULL) + soci_destroy_blob(blob); + + ind = i_ok; + blob = static_cast(b); +} + SOCI_DECL int soci_use_get_size_v(statement_handle st) { statement_wrapper * wrapper = static_cast(st); @@ -1280,12 +1535,14 @@ SOCI_DECL int soci_use_get_size_v(statement_handle st) return -1; } - typedef std::map >::const_iterator iterator; - iterator const any_element = wrapper->use_indicators_v.begin(); - assert(any_element != wrapper->use_indicators_v.end()); + if (wrapper->use_indicators_v.empty()) + { + wrapper->is_ok = false; + wrapper->error_message = "Empty indicators vector."; + return -1; + } - return static_cast(any_element->second.size()); + return static_cast(wrapper->use_indicators_v.begin()->second.size()); } SOCI_DECL void soci_use_resize_v(statement_handle st, int new_size) @@ -1539,6 +1796,19 @@ SOCI_DECL char const * soci_get_use_date(statement_handle st, char const * name) return wrapper->date_formatted; } +SOCI_DECL blob_handle soci_get_use_blob(statement_handle st, char const * name) +{ + statement_wrapper * wrapper = static_cast(st); + + if (name_exists_check_failed(*wrapper, + name, dt_blob, statement_wrapper::bulk, "blob")) + { + return NULL; + } + + return wrapper->use_blob[name]; +} + SOCI_DECL void soci_prepare(statement_handle st, char const * query) { statement_wrapper * wrapper = static_cast(st); @@ -1565,6 +1835,7 @@ SOCI_DECL void soci_prepare(statement_handle st, char const * query) into(wrapper->into_ints[i], wrapper->into_indicators[i])); break; case dt_long_long: + case dt_unsigned_long_long: wrapper->st.exchange( into(wrapper->into_longlongs[i], wrapper->into_indicators[i])); break; @@ -1576,8 +1847,10 @@ SOCI_DECL void soci_prepare(statement_handle st, char const * query) wrapper->st.exchange( into(wrapper->into_dates[i], wrapper->into_indicators[i])); break; - default: - assert(false); + case dt_blob: + wrapper->st.exchange( + into(wrapper->into_blob[i]->blob_, wrapper->into_indicators[i])); + break; } } } @@ -1597,6 +1870,7 @@ SOCI_DECL void soci_prepare(statement_handle st, char const * query) into(wrapper->into_ints_v[i], wrapper->into_indicators_v[i])); break; case dt_long_long: + case dt_unsigned_long_long: wrapper->st.exchange( into(wrapper->into_longlongs_v[i], wrapper->into_indicators_v[i])); break; @@ -1608,8 +1882,9 @@ SOCI_DECL void soci_prepare(statement_handle st, char const * query) wrapper->st.exchange( into(wrapper->into_dates_v[i], wrapper->into_indicators_v[i])); break; - default: - assert(false); + case dt_blob: + // no support for bulk blob + break; } } } @@ -1680,6 +1955,19 @@ SOCI_DECL void soci_prepare(statement_handle st, char const * query) wrapper->st.exchange(use(use_date, use_ind, use_name)); } } + { + // blobs + typedef std::map::iterator iterator; + iterator uit = wrapper->use_blob.begin(); + iterator uend = wrapper->use_blob.end(); + for ( ; uit != uend; ++uit) + { + std::string const & use_name = uit->first; + blob &use_blob = uit->second->blob_; + indicator & use_ind = wrapper->use_indicators[use_name]; + wrapper->st.exchange(use(use_blob, use_ind, use_name)); + } + } // bind all use vecctor elements { diff --git a/src/core/soci.h b/src/core/soci.h deleted file mode 100644 index 423282edca..0000000000 --- a/src/core/soci.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (C) 2004-2008 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) -// - -#ifndef SOCI_H_INCLUDED -#define SOCI_H_INCLUDED - -#ifdef _MSC_VER -#pragma warning(disable:4251 4512 4511) -#endif - -// namespace soci -#include "backend-loader.h" -#include "blob.h" -#include "blob-exchange.h" -#include "connection-pool.h" -#include "error.h" -#include "exchange-traits.h" -#include "into.h" -#include "into-type.h" -#include "once-temp-type.h" -#include "prepare-temp-type.h" -#include "procedure.h" -#include "ref-counted-prepare-info.h" -#include "ref-counted-statement.h" -#include "row.h" -#include "row-exchange.h" -#include "rowid.h" -#include "rowid-exchange.h" -#include "rowset.h" -#include "session.h" -#include "soci-backend.h" -#include "soci-config.h" -#include "soci-platform.h" -#include "statement.h" -#include "transaction.h" -#include "type-conversion.h" -#include "type-conversion-traits.h" -#include "type-holder.h" -#include "type-ptr.h" -#include "unsigned-types.h" -#include "use.h" -#include "use-type.h" -#include "values.h" -#include "values-exchange.h" - -// namespace boost -#ifdef SOCI_USE_BOOST -#include -#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 -#include "boost-fusion.h" -#endif // BOOST_VERSION -#include "boost-optional.h" -#include "boost-tuple.h" -#include "boost-gregorian-date.h" -#endif // SOCI_USE_BOOST - -#endif // SOCI_H_INCLUDED diff --git a/src/core/statement.cpp b/src/core/statement.cpp index 57ff40a7bf..596bce1f99 100644 --- a/src/core/statement.cpp +++ b/src/core/statement.cpp @@ -6,30 +6,18 @@ // #define SOCI_SOURCE -#include "statement.h" -#include "session.h" -#include "into-type.h" -#include "use-type.h" -#include "values.h" +#include "soci/statement.h" +#include "soci/session.h" +#include "soci/into-type.h" +#include "soci/use-type.h" +#include "soci/values.h" +#include "soci-compiler.h" #include #include -#ifdef _MSC_VER -#pragma warning(disable:4355) -#endif - using namespace soci; using namespace soci::details; -void statement::exchange(into_type_ptr const & i) -{ - impl_->exchange(i); -} - -void statement::exchange(use_type_ptr const & u) -{ - impl_->exchange(u); -} statement_impl::statement_impl(session & s) : session_(s), refCount_(1), row_(0), @@ -114,10 +102,10 @@ void statement_impl::bind(values & values) // make sure we do not go out of range on the string const char nextChar = (pos + placeholder.size()) < query_.size() ? query_[pos + placeholder.size()] : '\0'; - + if (std::isalnum(nextChar)) { - // We got a partial match only, + // We got a partial match only, // keep looking for the placeholder pos = query_.find(placeholder, pos + placeholder.size()); } @@ -147,45 +135,12 @@ void statement_impl::bind(values & values) { values.add_unused(values.uses_[i], values.indicators_[i]); } - throw; + + rethrow_current_exception_with_context("binding parameters of"); } } -void statement_impl::exchange(into_type_ptr const & i) -{ - intos_.push_back(i.get()); - i.release(); -} - -void statement_impl::exchange_for_row(into_type_ptr const & i) -{ - intosForRow_.push_back(i.get()); - i.release(); -} - -void statement_impl::exchange_for_rowset(into_type_ptr const & i) -{ - if (intos_.empty() == false) - { - throw soci_error("Explicit into elements not allowed with rowset."); - } - - into_type_base* p = i.get(); - intos_.push_back(p); - i.release(); - - int definePosition = 1; - p->define(*this, definePosition); - definePositionForRow_ = definePosition; -} - -void statement_impl::exchange(use_type_ptr const & u) -{ - uses_.push_back(u.get()); - u.release(); -} - -void statement_impl::clean_up() +void statement_impl::bind_clean_up() { // deallocate all bind and define objects std::size_t const isize = intos_.size(); @@ -219,6 +174,13 @@ void statement_impl::clean_up() indicators_[i] = NULL; } + row_ = NULL; + alreadyDescribed_ = false; +} + +void statement_impl::clean_up() +{ + bind_clean_up(); if (backEnd_ != NULL) { backEnd_->clean_up(); @@ -230,10 +192,17 @@ void statement_impl::clean_up() void statement_impl::prepare(std::string const & query, statement_type eType) { - query_ = query; - session_.log_query(query); + try + { + query_ = query; + session_.log_query(query); - backEnd_->prepare(query, eType); + backEnd_->prepare(query, eType); + } + catch (...) + { + rethrow_current_exception_with_context("preparing"); + } } void statement_impl::define_and_bind() @@ -291,163 +260,184 @@ void statement_impl::undefine_and_bind() bool statement_impl::execute(bool withDataExchange) { - initialFetchSize_ = intos_size(); - - if (intos_.empty() == false && initialFetchSize_ == 0) + try { - // this can happen only with into-vectors elements - // and is not allowed when calling execute - throw soci_error("Vectors of size 0 are not allowed."); - } + initialFetchSize_ = intos_size(); - fetchSize_ = initialFetchSize_; - - // pre-use should be executed before inspecting the sizes of use - // elements, as they can be resized in type conversion routines - - pre_use(); - - std::size_t const bindSize = uses_size(); - - if (bindSize > 1 && fetchSize_ > 1) - { - throw soci_error( - "Bulk insert/update and bulk select not allowed in same query"); - } - - // looks like a hack and it is - row description should happen - // *after* the use elements were completely prepared - // and *before* the into elements are touched, so that the row - // description process can inject more into elements for - // implicit data exchange - if (row_ != NULL && alreadyDescribed_ == false) - { - describe(); - define_for_row(); - } - - int num = 0; - if (withDataExchange) - { - num = 1; - - pre_fetch(); - - if (static_cast(fetchSize_) > num) + if (intos_.empty() == false && initialFetchSize_ == 0) { - num = static_cast(fetchSize_); + // this can happen only with into-vectors elements + // and is not allowed when calling execute + throw soci_error("Vectors of size 0 are not allowed."); } - if (static_cast(bindSize) > num) + + fetchSize_ = initialFetchSize_; + + // pre-use should be executed before inspecting the sizes of use + // elements, as they can be resized in type conversion routines + + pre_use(); + + std::size_t const bindSize = uses_size(); + + if (bindSize > 1 && fetchSize_ > 1) { - num = static_cast(bindSize); + throw soci_error( + "Bulk insert/update and bulk select not allowed in same query"); } - } - statement_backend::exec_fetch_result res = backEnd_->execute(num); + // looks like a hack and it is - row description should happen + // *after* the use elements were completely prepared + // and *before* the into elements are touched, so that the row + // description process can inject more into elements for + // implicit data exchange + if (row_ != NULL && alreadyDescribed_ == false) + { + describe(); + define_for_row(); + } - bool gotData = false; + int num = 0; + if (withDataExchange) + { + num = 1; - if (res == statement_backend::ef_success) - { - // the "success" means that the statement executed correctly - // and for select statement this also means that some rows were read + pre_fetch(); + + if (static_cast(fetchSize_) > num) + { + num = static_cast(fetchSize_); + } + if (static_cast(bindSize) > num) + { + num = static_cast(bindSize); + } + } + + statement_backend::exec_fetch_result res = backEnd_->execute(num); + + bool gotData = false; + + if (res == statement_backend::ef_success) + { + // the "success" means that the statement executed correctly + // and for select statement this also means that some rows were read + + if (num > 0) + { + gotData = true; + + // ensure into vectors have correct size + resize_intos(static_cast(num)); + } + } + else // res == ef_no_data + { + // the "no data" means that the end-of-rowset condition was hit + // but still some rows might have been read (the last bunch of rows) + // it can also mean that the statement did not produce any results + + gotData = fetchSize_ > 1 ? resize_intos() : false; + } if (num > 0) { - gotData = true; - - // ensure into vectors have correct size - resize_intos(static_cast(num)); + post_fetch(gotData, false); } + + post_use(gotData); + + session_.set_got_data(gotData); + return gotData; } - else // res == ef_no_data + catch (...) { - // the "no data" means that the end-of-rowset condition was hit - // but still some rows might have been read (the last bunch of rows) - // it can also mean that the statement did not produce any results - - gotData = fetchSize_ > 1 ? resize_intos() : false; + rethrow_current_exception_with_context("executing"); } - - if (num > 0) - { - post_fetch(gotData, false); - } - - post_use(gotData); - - session_.set_got_data(gotData); - return gotData; } long long statement_impl::get_affected_rows() { - return backEnd_->get_affected_rows(); + try + { + return backEnd_->get_affected_rows(); + } + catch (...) + { + rethrow_current_exception_with_context("getting the number of rows affected by"); + } } bool statement_impl::fetch() { - if (fetchSize_ == 0) + try { - truncate_intos(); - session_.set_got_data(false); - return false; - } - - bool gotData = false; - - // vectors might have been resized between fetches - std::size_t const newFetchSize = intos_size(); - if (newFetchSize > initialFetchSize_) - { - // this is not allowed, because most likely caused reallocation - // of the vector - this would require complete re-bind - - throw soci_error( - "Increasing the size of the output vector is not supported."); - } - else if (newFetchSize == 0) - { - session_.set_got_data(false); - return false; - } - else - { - // the output vector was downsized or remains the same as before - fetchSize_ = newFetchSize; - } - - statement_backend::exec_fetch_result const res = backEnd_->fetch(static_cast(fetchSize_)); - if (res == statement_backend::ef_success) - { - // the "success" means that some number of rows was read - // and that it is not yet the end-of-rowset (there are more rows) - - gotData = true; - - // ensure into vectors have correct size - resize_intos(fetchSize_); - } - else // res == ef_no_data - { - // end-of-rowset condition - - if (fetchSize_ > 1) + if (fetchSize_ == 0) { - // but still the last bunch of rows might have been read - gotData = resize_intos(); - fetchSize_ = 0; + truncate_intos(); + session_.set_got_data(false); + return false; + } + + bool gotData = false; + + // vectors might have been resized between fetches + std::size_t const newFetchSize = intos_size(); + if (newFetchSize > initialFetchSize_) + { + // this is not allowed, because most likely caused reallocation + // of the vector - this would require complete re-bind + + throw soci_error( + "Increasing the size of the output vector is not supported."); + } + else if (newFetchSize == 0) + { + session_.set_got_data(false); + return false; } else { - truncate_intos(); - gotData = false; + // the output vector was downsized or remains the same as before + fetchSize_ = newFetchSize; } - } - post_fetch(gotData, true); - session_.set_got_data(gotData); - return gotData; + statement_backend::exec_fetch_result const res = backEnd_->fetch(static_cast(fetchSize_)); + if (res == statement_backend::ef_success) + { + // the "success" means that some number of rows was read + // and that it is not yet the end-of-rowset (there are more rows) + + gotData = true; + + // ensure into vectors have correct size + resize_intos(fetchSize_); + } + else // res == ef_no_data + { + // end-of-rowset condition + + if (fetchSize_ > 1) + { + // but still the last bunch of rows might have been read + gotData = resize_intos(); + fetchSize_ = 0; + } + else + { + truncate_intos(); + gotData = false; + } + } + + post_fetch(gotData, true); + session_.set_got_data(gotData); + return gotData; + } + catch (...) + { + rethrow_current_exception_with_context("fetching data from"); + } } std::size_t statement_impl::intos_size() @@ -740,3 +730,54 @@ statement_impl::make_vector_use_type_backend() { return backEnd_->make_vector_use_type_backend(); } + +SOCI_NORETURN +statement_impl::rethrow_current_exception_with_context(char const* operation) +{ + try + { + throw; + } + catch (soci_error& e) + { + if (!query_.empty()) + { + std::ostringstream oss; + oss << "while " << operation << " \"" << query_ << "\""; + + if (!uses_.empty()) + { + oss << " with "; + + std::size_t const usize = uses_.size(); + for (std::size_t i = 0; i != usize; ++i) + { + if (i != 0) + oss << ", "; + + details::use_type_base const& u = *uses_[i]; + + // Use the name specified in the "use()" call if any, + // otherwise get the name of the matching parameter from + // the query itself, as parsed by the backend. + std::string name = u.get_name(); + if (name.empty()) + name = backEnd_->get_parameter_name(static_cast(i)); + + oss << ":"; + if (!name.empty()) + oss << name; + else + oss << (i + 1); + oss << "="; + + u.dump_value(oss); + } + } + + e.add_context(oss.str()); + } + + throw; + } +} diff --git a/src/core/test/common-tests.h b/src/core/test/common-tests.h deleted file mode 100644 index 37fc4fbe0d..0000000000 --- a/src/core/test/common-tests.h +++ /dev/null @@ -1,3980 +0,0 @@ -// -// Copyright (C) 2004-2008 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) -// - -#ifndef SOCI_COMMON_TESTS_H_INCLUDED -#define SOCI_COMMON_TESTS_H_INCLUDED - -#include "soci.h" -#include "soci-config.h" - -#ifdef HAVE_BOOST -// explicitly pull conversions for Boost's optional, tuple and fusion: -#include -#include -#include -#include -#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 -#include -#endif // BOOST_VERSION -#endif // HAVE_BOOST - -#include -#include -#include -#include -#include -#include - -// Objects used later in tests 14,15 -struct PhonebookEntry -{ - std::string name; - std::string phone; -}; - -struct PhonebookEntry2 : public PhonebookEntry -{ -}; - -class PhonebookEntry3 -{ -public: - void setName(std::string const & n) { name_ = n; } - std::string getName() const { return name_; } - - void setPhone(std::string const & p) { phone_ = p; } - std::string getPhone() const { return phone_; } - -public: - std::string name_; - std::string phone_; -}; - -// user-defined object for test26 and test28 -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 -{ - -// basic type conversion for user-defined type with single base value -template<> struct type_conversion -{ - typedef int base_type; - - static void from_base(int i, indicator ind, MyInt &mi) - { - if (ind == i_ok) - { - mi.set(i); - } - } - - static void to_base(MyInt const &mi, int &i, indicator &ind) - { - i = mi.get(); - ind = i_ok; - } -}; - -// basic type conversion on many values (ORM) -template<> struct type_conversion -{ - typedef soci::values base_type; - - static void from_base(values const &v, indicator /* ind */, PhonebookEntry &pe) - { - // here we ignore the possibility the the whole object might be NULL - pe.name = v.get("NAME"); - pe.phone = v.get("PHONE", ""); - } - - static void to_base(PhonebookEntry const &pe, values &v, indicator &ind) - { - v.set("NAME", pe.name); - v.set("PHONE", pe.phone, pe.phone.empty() ? i_null : i_ok); - ind = i_ok; - } -}; - -// type conversion which directly calls values::get_indicator() -template<> struct type_conversion -{ - typedef soci::values base_type; - - static void from_base(values const &v, indicator /* ind */, PhonebookEntry2 &pe) - { - // here we ignore the possibility the the whole object might be NULL - - pe.name = v.get("NAME"); - indicator ind = v.get_indicator("PHONE"); //another way to test for null - pe.phone = ind == i_null ? "" : v.get("PHONE"); - } - - static void to_base(PhonebookEntry2 const &pe, values &v, indicator &ind) - { - v.set("NAME", pe.name); - v.set("PHONE", pe.phone, pe.phone.empty() ? i_null : i_ok); - ind = i_ok; - } -}; - -template<> struct type_conversion -{ - typedef soci::values base_type; - - static void from_base(values const &v, indicator /* ind */, PhonebookEntry3 &pe) - { - // here we ignore the possibility the the whole object might be NULL - - pe.setName(v.get("NAME")); - pe.setPhone(v.get("PHONE", "")); - } - - static void to_base(PhonebookEntry3 const &pe, values &v, indicator &ind) - { - v.set("NAME", pe.getName()); - v.set("PHONE", pe.getPhone(), pe.getPhone().empty() ? i_null : i_ok); - ind = i_ok; - } -}; - -} // namespace soci - -namespace soci -{ -namespace tests -{ - -// ensure connection is checked, no crash occurs - -#define SOCI_TEST_ENSURE_CONNECTED(sql, method) { \ - std::string msg; \ - try { \ - (sql.method)(); \ - assert(!"exception expected"); \ - } catch (soci_error const &e) { msg = e.what(); } \ - assert(msg.empty() == false); } (void)sql - -#define SOCI_TEST_ENSURE_CONNECTED2(sql, method) { \ - std::string msg; \ - try { std::string seq; long v(0); \ - (sql.method)(seq, v); \ - assert(!"exception expected"); \ - } catch (soci_error const &e) { msg = e.what(); } \ - assert(msg.empty() == false); } (void)sql - -inline bool equal_approx(double const a, double const b) -{ - // The formula taken from CATCH test framework - // https://github.com/philsquared/Catch/ - // Thanks to Richard Harris for his help refining this formula - double const epsilon(std::numeric_limits::epsilon() * 100); - double const scale(1.0); - return std::fabs(a - b) < epsilon * (scale + (std::max)(std::fabs(a), std::fabs(b))); -} - -// TODO: improve cleanup capabilities by subtypes, soci_test name may be omitted --mloskot -// i.e. optional ctor param accepting custom table name -class table_creator_base -{ -public: - table_creator_base(session& sql) - : msession(sql) { drop(); } - - virtual ~table_creator_base() { drop();} -private: - void drop() - { - try - { - msession << "drop table soci_test"; - } - catch (soci_error const& e) - { - //std::cerr << e.what() << std::endl; - e.what(); - } - } - session& msession; -}; - -class procedure_creator_base -{ -public: - procedure_creator_base(session& sql) - : msession(sql) { drop(); } - - virtual ~procedure_creator_base() { drop();} -private: - void drop() - { - try { msession << "drop procedure soci_test"; } catch (soci_error&) {} - } - session& msession; -}; - -class function_creator_base -{ -public: - function_creator_base(session& sql) - : msession(sql) { drop(); } - - virtual ~function_creator_base() { drop();} - -protected: - virtual std::string dropstatement() - { - return "drop function soci_test"; - } - -private: - void drop() - { - try { msession << dropstatement(); } catch (soci_error&) {} - } - session& msession; -}; - -class test_context_base -{ -public: - test_context_base(backend_factory const &backEnd, - std::string const &connectString) - : backEndFactory_(backEnd), - connectString_(connectString) {} - - backend_factory const & get_backend_factory() const - { - return backEndFactory_; - } - - std::string get_connect_string() const - { - return connectString_; - } - - virtual std::string to_date_time(std::string const &dateTime) const = 0; - - virtual table_creator_base* table_creator_1(session&) const = 0; - virtual table_creator_base* table_creator_2(session&) const = 0; - virtual table_creator_base* table_creator_3(session&) const = 0; - virtual table_creator_base* table_creator_4(session&) const = 0; - - virtual ~test_context_base() {} // quiet the compiler - -private: - backend_factory const &backEndFactory_; - std::string const connectString_; -}; - -class common_tests -{ -public: - common_tests(test_context_base const &tc) - : tc_(tc), - backEndFactory_(tc.get_backend_factory()), - connectString_(tc.get_connect_string()) - {} - - void run(bool dbSupportsTransactions = true) - { - std::cout<<"\nSOCI Common Tests:\n\n"; - - test0(); - test1(); - test2(); - test3(); - test4(); - test5(); - test6(); - test7(); - test8(); - test9(); - - if (dbSupportsTransactions) - { - test10(); - } - else - { - std::cout<<"skipping test 10 (database doesn't support transactions)\n"; - } - - test11(); - test12(); - test13(); - test14(); - test15(); - test16(); - test17(); - test18(); - test19(); - test20(); - test21(); - test22(); - test23(); - test24(); - test25(); - test26(); - test27(); - test28(); - test29(); - test30(); - test31(); - test_get_affected_rows(); - test_query_transformation(); - test_query_transformation_with_connection_pool(); - test_pull5(); - test_issue67(); - test_prepared_insert_with_orm_type(); - test_issue154(); - test_placeholder_partial_matching_with_orm_type(); - } - -private: - test_context_base const & tc_; - backend_factory const &backEndFactory_; - std::string const connectString_; - -typedef std::auto_ptr auto_table_creator; - -void test0() -{ - { - soci::session sql; // no connection - SOCI_TEST_ENSURE_CONNECTED(sql, begin); - SOCI_TEST_ENSURE_CONNECTED(sql, commit); - SOCI_TEST_ENSURE_CONNECTED(sql, rollback); - SOCI_TEST_ENSURE_CONNECTED(sql, get_backend_name); - SOCI_TEST_ENSURE_CONNECTED(sql, make_statement_backend); - SOCI_TEST_ENSURE_CONNECTED(sql, make_rowid_backend); - SOCI_TEST_ENSURE_CONNECTED(sql, make_blob_backend); - SOCI_TEST_ENSURE_CONNECTED2(sql, get_next_sequence_value); - SOCI_TEST_ENSURE_CONNECTED2(sql, get_last_insert_id); - } - std::cout << "test 0 passed\n"; -} -void test1() -{ - session sql(backEndFactory_, connectString_); - - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::string msg; - try - { - // expected error - sql << "drop table soci_test_nosuchtable"; - assert(false); - } - catch (soci_error const &e) - { - msg = e.what(); - } - assert(msg.empty() == false); - - sql << "insert into soci_test (id) values (" << 123 << ")"; - int id; - sql << "select id from soci_test", into(id); - assert(id == 123); - - std::cout << "test 1 passed\n"; -} - -// "into" tests, type conversions, etc. -void test2() -{ - { - session sql(backEndFactory_, connectString_); - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - char c('a'); - sql << "insert into soci_test(c) values(:c)", use(c); - sql << "select c from soci_test", into(c); - assert(c == 'a'); - } - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::string helloSOCI("Hello, SOCI!"); - sql << "insert into soci_test(str) values(:s)", use(helloSOCI); - std::string str; - sql << "select str from soci_test", into(str); - assert(str == "Hello, SOCI!"); - } - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - short three(3); - sql << "insert into soci_test(sh) values(:id)", use(three); - short sh(0); - sql << "select sh from soci_test", into(sh); - assert(sh == 3); - } - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - int five(5); - sql << "insert into soci_test(id) values(:id)", use(five); - int i(0); - sql << "select id from soci_test", into(i); - assert(i == 5); - } - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - unsigned long seven(7); - sql << "insert into soci_test(ul) values(:ul)", use(seven); - unsigned long ul(0); - sql << "select ul from soci_test", into(ul); - assert(ul == 7); - } - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - double pi(3.14159265); - sql << "insert into soci_test(d) values(:d)", use(pi); - double d(0.0); - sql << "select d from soci_test", into(d); - assert(equal_approx(d, 3.14159265)); - } - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::tm nov15; - nov15.tm_year = 105; - nov15.tm_mon = 10; - nov15.tm_mday = 15; - nov15.tm_hour = 0; - nov15.tm_min = 0; - nov15.tm_sec = 0; - - sql << "insert into soci_test(tm) values(:tm)", use(nov15); - - std::tm t; - sql << "select tm from soci_test", into(t); - assert(t.tm_year == 105); - assert(t.tm_mon == 10); - assert(t.tm_mday == 15); - assert(t.tm_hour == 0); - assert(t.tm_min == 0); - assert(t.tm_sec == 0); - } - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::tm nov15; - nov15.tm_year = 105; - nov15.tm_mon = 10; - nov15.tm_mday = 15; - nov15.tm_hour = 22; - nov15.tm_min = 14; - nov15.tm_sec = 17; - - sql << "insert into soci_test(tm) values(:tm)", use(nov15); - - std::tm t; - sql << "select tm from soci_test", into(t); - assert(t.tm_year == 105); - assert(t.tm_mon == 10); - assert(t.tm_mday == 15); - assert(t.tm_hour == 22); - assert(t.tm_min == 14); - assert(t.tm_sec == 17); - } - - // test indicators - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - int id(1); - std::string str("Hello"); - sql << "insert into soci_test(id, str) values(:id, :str)", - use(id), use(str); - - int i; - indicator ind; - sql << "select id from soci_test", into(i, ind); - assert(ind == i_ok); - } - - // more indicator tests, NULL values - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - sql << "insert into soci_test(id,tm) values(NULL,NULL)"; - int i; - indicator ind; - sql << "select id from soci_test", into(i, ind); - assert(ind == i_null); - - // additional test for NULL with std::tm - std::tm t; - sql << "select tm from soci_test", into(t, ind); - assert(ind == i_null); - - try - { - // expect error - sql << "select id from soci_test", into(i); - assert(false); - } - catch (soci_error const &e) - { - std::string error = e.what(); - assert(error == - "Null value fetched and no indicator defined."); - } - - sql << "select id from soci_test where id = 1000", into(i, ind); - assert(sql.got_data() == false); - - // no data expected - sql << "select id from soci_test where id = 1000", into(i); - assert(sql.got_data() == false); - - // no data expected, test correct behaviour with use - int id = 1000; - sql << "select id from soci_test where id = :id", use(id), into(i); - assert(sql.got_data() == false); - } - } - - std::cout << "test 2 passed" << std::endl; -} - -// repeated fetch and bulk fetch -void test3() -{ - { - session sql(backEndFactory_, connectString_); - - // repeated fetch and bulk fetch of char - { - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - char c; - for (c = 'a'; c <= 'z'; ++c) - { - sql << "insert into soci_test(c) values(\'" << c << "\')"; - } - - int count; - sql << "select count(*) from soci_test", into(count); - assert(count == 'z' - 'a' + 1); - - { - char c2 = 'a'; - - statement st = (sql.prepare << - "select c from soci_test order by c", into(c)); - - st.execute(); - while (st.fetch()) - { - assert(c == c2); - ++c2; - } - assert(c2 == 'a' + count); - } - { - char c2 = 'a'; - - std::vector vec(10); - statement st = (sql.prepare << - "select c from soci_test order by c", into(vec)); - st.execute(); - while (st.fetch()) - { - for (std::size_t i = 0; i != vec.size(); ++i) - { - assert(c2 == vec[i]); - ++c2; - } - - vec.resize(10); - } - assert(c2 == 'a' + count); - } - - { - // verify an exception is thrown when empty vector is used - std::vector vec; - try - { - sql << "select c from soci_test", into(vec); - assert(false); - } - catch (soci_error const &e) - { - std::string msg = e.what(); - assert(msg == "Vectors of size 0 are not allowed."); - } - } - - } - - // repeated fetch and bulk fetch of std::string - { - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - int const rowsToTest = 10; - for (int i = 0; i != rowsToTest; ++i) - { - std::ostringstream ss; - ss << "Hello_" << i; - - sql << "insert into soci_test(str) values(\'" - << ss.str() << "\')"; - } - - int count; - sql << "select count(*) from soci_test", into(count); - assert(count == rowsToTest); - - { - int i = 0; - std::string s; - statement st = (sql.prepare << - "select str from soci_test order by str", into(s)); - - st.execute(); - while (st.fetch()) - { - std::ostringstream ss; - ss << "Hello_" << i; - assert(s == ss.str()); - ++i; - } - assert(i == rowsToTest); - } - { - int i = 0; - - std::vector vec(4); - statement st = (sql.prepare << - "select str from soci_test order by str", into(vec)); - st.execute(); - while (st.fetch()) - { - for (std::size_t j = 0; j != vec.size(); ++j) - { - std::ostringstream ss; - ss << "Hello_" << i; - assert(ss.str() == vec[j]); - ++i; - } - - vec.resize(4); - } - assert(i == rowsToTest); - } - } - - // repeated fetch and bulk fetch of short - { - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - short const rowsToTest = 100; - short sh; - for (sh = 0; sh != rowsToTest; ++sh) - { - sql << "insert into soci_test(sh) values(" << sh << ")"; - } - - int count; - sql << "select count(*) from soci_test", into(count); - assert(count == rowsToTest); - - { - short sh2 = 0; - - statement st = (sql.prepare << - "select sh from soci_test order by sh", into(sh)); - - st.execute(); - while (st.fetch()) - { - assert(sh == sh2); - ++sh2; - } - assert(sh2 == rowsToTest); - } - { - short sh2 = 0; - - std::vector vec(8); - statement st = (sql.prepare << - "select sh from soci_test order by sh", into(vec)); - st.execute(); - while (st.fetch()) - { - for (std::size_t i = 0; i != vec.size(); ++i) - { - assert(sh2 == vec[i]); - ++sh2; - } - - vec.resize(8); - } - assert(sh2 == rowsToTest); - } - } - - // repeated fetch and bulk fetch of int (4-bytes) - { - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - int const rowsToTest = 100; - int i; - for (i = 0; i != rowsToTest; ++i) - { - sql << "insert into soci_test(id) values(" << i << ")"; - } - - int count; - sql << "select count(*) from soci_test", into(count); - assert(count == rowsToTest); - - { - int i2 = 0; - - statement st = (sql.prepare << - "select id from soci_test order by id", into(i)); - - st.execute(); - while (st.fetch()) - { - assert(i == i2); - ++i2; - } - assert(i2 == rowsToTest); - } - { - // additional test with the use element - - int i2 = 0; - int cond = 0; // this condition is always true - - statement st = (sql.prepare << - "select id from soci_test where id >= :cond order by id", - use(cond), into(i)); - - st.execute(); - while (st.fetch()) - { - assert(i == i2); - ++i2; - } - assert(i2 == rowsToTest); - } - { - int i2 = 0; - - std::vector vec(8); - statement st = (sql.prepare << - "select id from soci_test order by id", into(vec)); - st.execute(); - while (st.fetch()) - { - for (std::size_t i = 0; i != vec.size(); ++i) - { - assert(i2 == vec[i]); - ++i2; - } - - vec.resize(8); - } - assert(i2 == rowsToTest); - } - } - - // repeated fetch and bulk fetch of unsigned int (4-bytes) - { - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - unsigned int const rowsToTest = 100; - unsigned int ul; - for (ul = 0; ul != rowsToTest; ++ul) - { - sql << "insert into soci_test(ul) values(" << ul << ")"; - } - - int count; - sql << "select count(*) from soci_test", into(count); - assert(count == static_cast(rowsToTest)); - - { - unsigned int ul2 = 0; - - statement st = (sql.prepare << - "select ul from soci_test order by ul", into(ul)); - - st.execute(); - while (st.fetch()) - { - assert(ul == ul2); - ++ul2; - } - assert(ul2 == rowsToTest); - } - { - unsigned int ul2 = 0; - - std::vector vec(8); - statement st = (sql.prepare << - "select ul from soci_test order by ul", into(vec)); - st.execute(); - while (st.fetch()) - { - for (std::size_t i = 0; i != vec.size(); ++i) - { - assert(ul2 == vec[i]); - ++ul2; - } - - vec.resize(8); - } - assert(ul2 == rowsToTest); - } - } - - // repeated fetch and bulk fetch of double - { - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - int const rowsToTest = 100; - double d = 0.0; - for (int i = 0; i != rowsToTest; ++i) - { - sql << "insert into soci_test(d) values(" << d << ")"; - d += 0.6; - } - - int count; - sql << "select count(*) from soci_test", into(count); - assert(count == rowsToTest); - - { - double d2 = 0.0; - int i = 0; - - statement st = (sql.prepare << - "select d from soci_test order by d", into(d)); - - st.execute(); - while (st.fetch()) - { - assert(equal_approx(d, d2)); - d2 += 0.6; - ++i; - } - assert(i == rowsToTest); - } - { - double d2 = 0.0; - int i = 0; - - std::vector vec(8); - statement st = (sql.prepare << - "select d from soci_test order by d", into(vec)); - st.execute(); - while (st.fetch()) - { - for (std::size_t j = 0; j != vec.size(); ++j) - { - assert(equal_approx(d2, vec[j])); - d2 += 0.6; - ++i; - } - - vec.resize(8); - } - assert(i == rowsToTest); - } - } - - // repeated fetch and bulk fetch of std::tm - { - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - int const rowsToTest = 8; - for (int i = 0; i != rowsToTest; ++i) - { - std::ostringstream ss; - ss << 2000 + i << "-0" << 1 + i << '-' << 20 - i << ' ' - << 15 + i << ':' << 50 - i << ':' << 40 + i; - - sql << "insert into soci_test(id, tm) values(" << i - << ", " << tc_.to_date_time(ss.str()) << ")"; - } - - int count; - sql << "select count(*) from soci_test", into(count); - assert(count == rowsToTest); - - { - std::tm t; - int i = 0; - - statement st = (sql.prepare << - "select tm from soci_test order by id", into(t)); - - st.execute(); - while (st.fetch()) - { - assert(t.tm_year + 1900 == 2000 + i); - assert(t.tm_mon + 1 == 1 + i); - assert(t.tm_mday == 20 - i); - assert(t.tm_hour == 15 + i); - assert(t.tm_min == 50 - i); - assert(t.tm_sec == 40 + i); - - ++i; - } - assert(i == rowsToTest); - } - { - int i = 0; - - std::vector vec(3); - statement st = (sql.prepare << - "select tm from soci_test order by id", into(vec)); - st.execute(); - while (st.fetch()) - { - for (std::size_t j = 0; j != vec.size(); ++j) - { - assert(vec[j].tm_year + 1900 == 2000 + i); - assert(vec[j].tm_mon + 1 == 1 + i); - assert(vec[j].tm_mday == 20 - i); - assert(vec[j].tm_hour == 15 + i); - assert(vec[j].tm_min == 50 - i); - assert(vec[j].tm_sec == 40 + i); - - ++i; - } - - vec.resize(3); - } - assert(i == rowsToTest); - } - } - } - - std::cout << "test 3 passed" << std::endl; -} - -// test for indicators (repeated fetch and bulk) -void test4() -{ - session sql(backEndFactory_, connectString_); - - // 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)"; - - { - int val; - indicator ind; - - statement st = (sql.prepare << - "select val from soci_test order by id", into(val, ind)); - - st.execute(); - bool gotData = st.fetch(); - assert(gotData); - assert(ind == i_ok); - assert(val == 10); - gotData = st.fetch(); - assert(gotData); - assert(ind == i_ok); - assert(val == 11); - gotData = st.fetch(); - assert(gotData); - assert(ind == i_null); - gotData = st.fetch(); - assert(gotData); - assert(ind == i_null); - gotData = st.fetch(); - assert(gotData); - assert(ind == i_ok); - assert(val == 12); - gotData = st.fetch(); - assert(gotData == false); - } - { - std::vector vals(3); - std::vector inds(3); - - statement st = (sql.prepare << - "select val from soci_test order by id", into(vals, inds)); - - st.execute(); - bool gotData = st.fetch(); - assert(gotData); - assert(vals.size() == 3); - assert(inds.size() == 3); - assert(inds[0] == i_ok); - assert(vals[0] == 10); - assert(inds[1] == i_ok); - assert(vals[1] == 11); - assert(inds[2] == i_null); - gotData = st.fetch(); - assert(gotData); - assert(vals.size() == 2); - assert(inds[0] == i_null); - assert(inds[1] == i_ok); - assert(vals[1] == 12); - gotData = st.fetch(); - assert(gotData == false); - } - - // additional test for "no data" condition - { - std::vector vals(3); - std::vector inds(3); - - statement st = (sql.prepare << - "select val from soci_test where 0 = 1", into(vals, inds)); - - bool gotData = st.execute(true); - assert(gotData == false); - - // for convenience, vectors should be truncated - assert(vals.empty()); - assert(inds.empty()); - - // for even more convenience, fetch should not fail - // but just report end of rowset - // (and vectors should be truncated) - - vals.resize(1); - inds.resize(1); - - gotData = st.fetch(); - assert(gotData == false); - assert(vals.empty()); - assert(inds.empty()); - } - - // additional test for "no data" without prepared statement - { - std::vector vals(3); - std::vector inds(3); - - sql << "select val from soci_test where 0 = 1", - into(vals, inds); - - // vectors should be truncated - assert(vals.empty()); - assert(inds.empty()); - } - } - - std::cout << "test 4 passed" << std::endl; -} - -// test for different sizes of data vector and indicators vector -// (library should force ind. vector to have same size as data vector) -void test5() -{ - session sql(backEndFactory_, connectString_); - - // 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)"; - - { - std::vector vals(4); - std::vector inds; - - statement st = (sql.prepare << - "select val from soci_test order by id", into(vals, inds)); - - st.execute(); - st.fetch(); - assert(vals.size() == 4); - assert(inds.size() == 4); - vals.resize(3); - st.fetch(); - assert(vals.size() == 1); - assert(inds.size() == 1); - } - } - - std::cout << "test 5 passed" << std::endl; -} - -// "use" tests, type conversions, etc. -void test6() -{ -// Note: this functionality is not available with older PostgreSQL -#ifndef SOCI_POSTGRESQL_NOPARAMS - { - session sql(backEndFactory_, connectString_); - - // test for char - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - char c('a'); - sql << "insert into soci_test(c) values(:c)", use(c); - - c = 'b'; - sql << "select c from soci_test", into(c); - assert(c == 'a'); - - } - - // test for std::string - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - std::string s = "Hello SOCI!"; - sql << "insert into soci_test(str) values(:s)", use(s); - - std::string str; - sql << "select str from soci_test", into(str); - - assert(str == "Hello SOCI!"); - } - - // test for short - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - short s = 123; - sql << "insert into soci_test(id) values(:id)", use(s); - - short s2 = 0; - sql << "select id from soci_test", into(s2); - - assert(s2 == 123); - } - - // test for int - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - int i = -12345678; - sql << "insert into soci_test(id) values(:i)", use(i); - - int i2 = 0; - sql << "select id from soci_test", into(i2); - - assert(i2 == -12345678); - } - - // test for unsigned long - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - unsigned long ul = 4000000000ul; - sql << "insert into soci_test(ul) values(:num)", use(ul); - - unsigned long ul2 = 0; - sql << "select ul from soci_test", into(ul2); - - assert(ul2 == 4000000000ul); - } - - // test for double - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - double d = 3.14159265; - sql << "insert into soci_test(d) values(:d)", use(d); - - double d2 = 0; - sql << "select d from soci_test", into(d2); - - assert(equal_approx(d2, d)); - } - - // test for std::tm - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - std::tm t; - t.tm_year = 105; - t.tm_mon = 10; - t.tm_mday = 19; - t.tm_hour = 21; - t.tm_min = 39; - t.tm_sec = 57; - sql << "insert into soci_test(tm) values(:t)", use(t); - - std::tm t2; - t2.tm_year = 0; - t2.tm_mon = 0; - t2.tm_mday = 0; - t2.tm_hour = 0; - t2.tm_min = 0; - t2.tm_sec = 0; - - sql << "select tm from soci_test", into(t2); - - assert(t.tm_year == 105); - assert(t.tm_mon == 10); - assert(t.tm_mday == 19); - assert(t.tm_hour == 21); - assert(t.tm_min == 39); - assert(t.tm_sec == 57); - } - - // test for repeated use - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - int i; - statement st = (sql.prepare - << "insert into soci_test(id) values(:id)", use(i)); - - i = 5; - st.execute(true); - i = 6; - st.execute(true); - i = 7; - st.execute(true); - - std::vector v(5); - sql << "select id from soci_test order by id", into(v); - - assert(v.size() == 3); - assert(v[0] == 5); - assert(v[1] == 6); - assert(v[2] == 7); - } - - // tests for use of const objects - - // test for char - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - char const c('a'); - sql << "insert into soci_test(c) values(:c)", use(c); - - char c2 = 'b'; - sql << "select c from soci_test", into(c2); - assert(c2 == 'a'); - - } - - // test for std::string - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - std::string const s = "Hello const SOCI!"; - sql << "insert into soci_test(str) values(:s)", use(s); - - std::string str; - sql << "select str from soci_test", into(str); - - assert(str == "Hello const SOCI!"); - } - - // test for short - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - short const s = 123; - sql << "insert into soci_test(id) values(:id)", use(s); - - short s2 = 0; - sql << "select id from soci_test", into(s2); - - assert(s2 == 123); - } - - // test for int - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - int const i = -12345678; - sql << "insert into soci_test(id) values(:i)", use(i); - - int i2 = 0; - sql << "select id from soci_test", into(i2); - - assert(i2 == -12345678); - } - - // test for unsigned long - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - unsigned long const ul = 4000000000ul; - sql << "insert into soci_test(ul) values(:num)", use(ul); - - unsigned long ul2 = 0; - sql << "select ul from soci_test", into(ul2); - - assert(ul2 == 4000000000ul); - } - - // test for double - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - double const d = 3.14159265; - sql << "insert into soci_test(d) values(:d)", use(d); - - double d2 = 0; - sql << "select d from soci_test", into(d2); - - assert(equal_approx(d2, d)); - } - - // test for std::tm - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - std::tm t; - t.tm_year = 105; - t.tm_mon = 10; - t.tm_mday = 19; - t.tm_hour = 21; - t.tm_min = 39; - t.tm_sec = 57; - std::tm const & ct = t; - sql << "insert into soci_test(tm) values(:t)", use(ct); - - std::tm t2; - t2.tm_year = 0; - t2.tm_mon = 0; - t2.tm_mday = 0; - t2.tm_hour = 0; - t2.tm_min = 0; - t2.tm_sec = 0; - - sql << "select tm from soci_test", into(t2); - - assert(t.tm_year == 105); - assert(t.tm_mon == 10); - assert(t.tm_mday == 19); - assert(t.tm_hour == 21); - assert(t.tm_min == 39); - assert(t.tm_sec == 57); - } - } - - std::cout << "test 6 passed" << std::endl; -#endif // SOCI_POSTGRESQL_NOPARAMS -} - -// test for multiple use (and into) elements -void test7() -{ - { - session sql(backEndFactory_, connectString_); - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - { - int i1 = 5; - int i2 = 6; - int i3 = 7; - -#ifndef SOCI_POSTGRESQL_NOPARAMS - - sql << "insert into soci_test(i1, i2, i3) values(:i1, :i2, :i3)", - use(i1), use(i2), use(i3); - -#else - // Older PostgreSQL does not support use elements. - - sql << "insert into soci_test(i1, i2, i3) values(5, 6, 7)"; - -#endif // SOCI_POSTGRESQL_NOPARAMS - - i1 = 0; - i2 = 0; - i3 = 0; - sql << "select i1, i2, i3 from soci_test", - into(i1), into(i2), into(i3); - - assert(i1 == 5); - assert(i2 == 6); - assert(i3 == 7); - - // same for vectors - sql << "delete from soci_test"; - - i1 = 0; - i2 = 0; - i3 = 0; - -#ifndef SOCI_POSTGRESQL_NOPARAMS - - statement st = (sql.prepare - << "insert into soci_test(i1, i2, i3) values(:i1, :i2, :i3)", - use(i1), use(i2), use(i3)); - - i1 = 1; - i2 = 2; - i3 = 3; - st.execute(true); - i1 = 4; - i2 = 5; - i3 = 6; - st.execute(true); - i1 = 7; - i2 = 8; - i3 = 9; - st.execute(true); - -#else - // Older PostgreSQL does not support use elements. - - sql << "insert into soci_test(i1, i2, i3) values(1, 2, 3)"; - sql << "insert into soci_test(i1, i2, i3) values(4, 5, 6)"; - sql << "insert into soci_test(i1, i2, i3) values(7, 8, 9)"; - -#endif // SOCI_POSTGRESQL_NOPARAMS - - std::vector v1(5); - std::vector v2(5); - std::vector v3(5); - - sql << "select i1, i2, i3 from soci_test order by i1", - into(v1), into(v2), into(v3); - - assert(v1.size() == 3); - assert(v2.size() == 3); - assert(v3.size() == 3); - assert(v1[0] == 1); - assert(v1[1] == 4); - assert(v1[2] == 7); - assert(v2[0] == 2); - assert(v2[1] == 5); - assert(v2[2] == 8); - assert(v3[0] == 3); - assert(v3[1] == 6); - assert(v3[2] == 9); - } - } - - std::cout << "test 7 passed" << std::endl; -} - -// use vector elements -void test8() -{ -// Not supported with older PostgreSQL -#ifndef SOCI_POSTGRESQL_NOPARAMS - - { - session sql(backEndFactory_, connectString_); - - // test for char - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::vector v; - v.push_back('a'); - v.push_back('b'); - v.push_back('c'); - v.push_back('d'); - - sql << "insert into soci_test(c) values(:c)", use(v); - - std::vector v2(4); - - sql << "select c from soci_test order by c", into(v2); - assert(v2.size() == 4); - assert(v2[0] == 'a'); - assert(v2[1] == 'b'); - assert(v2[2] == 'c'); - assert(v2[3] == 'd'); - } - - // test for std::string - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::vector v; - v.push_back("ala"); - v.push_back("ma"); - v.push_back("kota"); - - sql << "insert into soci_test(str) values(:s)", use(v); - - std::vector v2(4); - - sql << "select str from soci_test order by str", into(v2); - assert(v2.size() == 3); - assert(v2[0] == "ala"); - assert(v2[1] == "kota"); - assert(v2[2] == "ma"); - } - - // test for short - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::vector v; - v.push_back(-5); - v.push_back(6); - v.push_back(7); - v.push_back(123); - - sql << "insert into soci_test(sh) values(:sh)", use(v); - - std::vector v2(4); - - sql << "select sh from soci_test order by sh", into(v2); - assert(v2.size() == 4); - assert(v2[0] == -5); - assert(v2[1] == 6); - assert(v2[2] == 7); - assert(v2[3] == 123); - } - - // test for int - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::vector v; - v.push_back(-2000000000); - v.push_back(0); - v.push_back(1); - v.push_back(2000000000); - - sql << "insert into soci_test(id) values(:i)", use(v); - - std::vector v2(4); - - sql << "select id from soci_test order by id", into(v2); - assert(v2.size() == 4); - assert(v2[0] == -2000000000); - assert(v2[1] == 0); - assert(v2[2] == 1); - assert(v2[3] == 2000000000); - } - - // test for unsigned int - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::vector v; - v.push_back(0); - v.push_back(1); - v.push_back(123); - v.push_back(1000); - - sql << "insert into soci_test(ul) values(:ul)", use(v); - - std::vector v2(4); - - sql << "select ul from soci_test order by ul", into(v2); - assert(v2.size() == 4); - assert(v2[0] == 0); - assert(v2[1] == 1); - assert(v2[2] == 123); - assert(v2[3] == 1000); - } - - // test for double - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::vector v; - v.push_back(0); - v.push_back(-0.0001); - v.push_back(0.0001); - v.push_back(3.1415926); - - sql << "insert into soci_test(d) values(:d)", use(v); - - std::vector v2(4); - - sql << "select d from soci_test order by d", into(v2); - assert(v2.size() == 4); - assert(equal_approx(v2[0],-0.0001)); - assert(equal_approx(v2[1], 0)); - assert(equal_approx(v2[2], 0.0001)); - assert(equal_approx(v2[3], 3.1415926)); - } - - // test for std::tm - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::vector v; - std::tm t; - t.tm_year = 105; - t.tm_mon = 10; - t.tm_mday = 26; - t.tm_hour = 22; - t.tm_min = 45; - t.tm_sec = 17; - - v.push_back(t); - - t.tm_sec = 37; - v.push_back(t); - - t.tm_mday = 25; - v.push_back(t); - - sql << "insert into soci_test(tm) values(:t)", use(v); - - std::vector v2(4); - - sql << "select tm from soci_test order by tm", into(v2); - assert(v2.size() == 3); - assert(v2[0].tm_year == 105); - assert(v2[0].tm_mon == 10); - assert(v2[0].tm_mday == 25); - assert(v2[0].tm_hour == 22); - assert(v2[0].tm_min == 45); - assert(v2[0].tm_sec == 37); - assert(v2[1].tm_year == 105); - assert(v2[1].tm_mon == 10); - assert(v2[1].tm_mday == 26); - assert(v2[1].tm_hour == 22); - assert(v2[1].tm_min == 45); - assert(v2[1].tm_sec == 17); - assert(v2[2].tm_year == 105); - assert(v2[2].tm_mon == 10); - assert(v2[2].tm_mday == 26); - assert(v2[2].tm_hour == 22); - assert(v2[2].tm_min == 45); - assert(v2[2].tm_sec == 37); - } - - // additional test for int (use const vector) - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::vector v; - v.push_back(-2000000000); - v.push_back(0); - v.push_back(1); - v.push_back(2000000000); - - std::vector const & cv = v; - - sql << "insert into soci_test(id) values(:i)", use(cv); - - std::vector v2(4); - - sql << "select id from soci_test order by id", into(v2); - assert(v2.size() == 4); - assert(v2[0] == -2000000000); - assert(v2[1] == 0); - assert(v2[2] == 1); - assert(v2[3] == 2000000000); - } - } - - std::cout << "test 8 passed" << std::endl; - -#endif // SOCI_POSTGRESQL_NOPARAMS - -} - -// test for named binding -void test9() -{ -// Not supported with older PostgreSQL -#ifndef SOCI_POSTGRESQL_NOPARAMS - - { - session sql(backEndFactory_, connectString_); - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - int i1 = 7; - int i2 = 8; - - // verify the exception is thrown if both by position - // and by name use elements are specified - try - { - sql << "insert into soci_test(i1, i2) values(:i1, :i2)", - use(i1, "i1"), use(i2); - - assert(false); - } - catch (soci_error const& e) - { - std::string what(e.what()); - assert(what == - "Binding for use elements must be either by position " - "or by name."); - } - - // normal test - sql << "insert into soci_test(i1, i2) values(:i1, :i2)", - use(i1, "i1"), use(i2, "i2"); - - i1 = 0; - i2 = 0; - sql << "select i1, i2 from soci_test", into(i1), into(i2); - assert(i1 == 7); - assert(i2 == 8); - - i2 = 0; - sql << "select i2 from soci_test where i1 = :i1", into(i2), use(i1); - assert(i2 == 8); - - sql << "delete from soci_test"; - - // test vectors - - std::vector v1; - v1.push_back(1); - v1.push_back(2); - v1.push_back(3); - - std::vector v2; - v2.push_back(4); - v2.push_back(5); - v2.push_back(6); - - sql << "insert into soci_test(i1, i2) values(:i1, :i2)", - use(v1, "i1"), use(v2, "i2"); - - sql << "select i2, i1 from soci_test order by i1 desc", - into(v1), into(v2); - assert(v1.size() == 3); - assert(v2.size() == 3); - assert(v1[0] == 6); - assert(v1[1] == 5); - assert(v1[2] == 4); - assert(v2[0] == 3); - assert(v2[1] == 2); - assert(v2[2] == 1); - } - } - - std::cout << "test 9 passed" << std::endl; - -#endif // SOCI_POSTGRESQL_NOPARAMS - -} - -// transaction test -void test10() -{ - { - session sql(backEndFactory_, connectString_); - - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - int count; - sql << "select count(*) from soci_test", into(count); - assert(count == 0); - - { - transaction tr(sql); - - sql << "insert into soci_test (id, name) values(1, 'John')"; - sql << "insert into soci_test (id, name) values(2, 'Anna')"; - sql << "insert into soci_test (id, name) values(3, 'Mike')"; - - tr.commit(); - } - { - transaction tr(sql); - - sql << "select count(*) from soci_test", into(count); - assert(count == 3); - - sql << "insert into soci_test (id, name) values(4, 'Stan')"; - - sql << "select count(*) from soci_test", into(count); - assert(count == 4); - - tr.rollback(); - - sql << "select count(*) from soci_test", into(count); - assert(count == 3); - } - { - transaction tr(sql); - - sql << "delete from soci_test"; - - sql << "select count(*) from soci_test", into(count); - assert(count == 0); - - tr.rollback(); - - sql << "select count(*) from soci_test", into(count); - assert(count == 3); - } - { - // additional test for detection of double commit - transaction tr(sql); - tr.commit(); - try - { - tr.commit(); - assert(false); - } - catch (soci_error const &e) - { - std::string msg = e.what(); - assert(msg == - "The transaction object cannot be handled twice."); - } - } - } - - std::cout << "test 10 passed" << std::endl; -} - -// test of use elements with indicators -void test11() -{ -#ifndef SOCI_POSTGRESQL_NOPARAMS - { - session sql(backEndFactory_, connectString_); - - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - indicator ind1 = i_ok; - indicator ind2 = i_ok; - - int id = 1; - int val = 10; - - sql << "insert into soci_test(id, val) values(:id, :val)", - use(id, ind1), use(val, ind2); - - id = 2; - val = 11; - ind2 = i_null; - sql << "insert into soci_test(id, val) values(:id, :val)", - use(id, ind1), use(val, ind2); - - sql << "select val from soci_test where id = 1", into(val, ind2); - assert(ind2 == i_ok); - assert(val == 10); - sql << "select val from soci_test where id = 2", into(val, ind2); - assert(ind2 == i_null); - - std::vector ids; - ids.push_back(3); - ids.push_back(4); - ids.push_back(5); - std::vector vals; - vals.push_back(12); - vals.push_back(13); - vals.push_back(14); - std::vector inds; - inds.push_back(i_ok); - inds.push_back(i_null); - inds.push_back(i_ok); - - sql << "insert into soci_test(id, val) values(:id, :val)", - use(ids), use(vals, inds); - - ids.resize(5); - vals.resize(5); - sql << "select id, val from soci_test order by id desc", - into(ids), into(vals, inds); - - assert(ids.size() == 5); - assert(ids[0] == 5); - assert(ids[1] == 4); - assert(ids[2] == 3); - assert(ids[3] == 2); - assert(ids[4] == 1); - assert(inds.size() == 5); - assert(inds[0] == i_ok); - assert(inds[1] == i_null); - assert(inds[2] == i_ok); - assert(inds[3] == i_null); - assert(inds[4] == i_ok); - assert(vals.size() == 5); - assert(vals[0] == 14); - assert(vals[2] == 12); - assert(vals[4] == 10); - } - - std::cout << "test 11 passed" << std::endl; - -#endif // SOCI_POSTGRESQL_NOPARAMS -} - -// Dynamic binding to Row objects -void test12() -{ - { - session sql(backEndFactory_, connectString_); - - sql.uppercase_column_names(true); - - auto_table_creator tableCreator(tc_.table_creator_2(sql)); - - row r; - sql << "select * from soci_test", into(r); - assert(sql.got_data() == false); - - sql << "insert into soci_test" - " values(3.14, 123, \'Johny\'," - << tc_.to_date_time("2005-12-19 22:14:17") - << ", 'a')"; - - // select into a row - { - row r; - statement st = (sql.prepare << - "select * from soci_test", into(r)); - st.execute(true); - assert(r.size() == 5); - - assert(r.get_properties(0).get_data_type() == dt_double); - assert(r.get_properties(1).get_data_type() == dt_integer); - assert(r.get_properties(2).get_data_type() == dt_string); - assert(r.get_properties(3).get_data_type() == dt_date); - - // type char is visible as string - // - to comply with the implementation for Oracle - assert(r.get_properties(4).get_data_type() == dt_string); - - assert(r.get_properties("NUM_INT").get_data_type() == dt_integer); - - assert(r.get_properties(0).get_name() == "NUM_FLOAT"); - assert(r.get_properties(1).get_name() == "NUM_INT"); - assert(r.get_properties(2).get_name() == "NAME"); - assert(r.get_properties(3).get_name() == "SOMETIME"); - assert(r.get_properties(4).get_name() == "CHR"); - - assert(equal_approx(r.get(0), 3.14)); - assert(r.get(1) == 123); - assert(r.get(2) == "Johny"); - std::tm t = { 0 }; - t = r.get(3); - assert(t.tm_year == 105); - - // again, type char is visible as string - assert(r.get(4) == "a"); - - assert(equal_approx(r.get("NUM_FLOAT"), 3.14)); - assert(r.get("NUM_INT") == 123); - assert(r.get("NAME") == "Johny"); - assert(r.get("CHR") == "a"); - - assert(r.get_indicator(0) == i_ok); - - // verify exception thrown on invalid get<> - bool caught = false; - try - { - r.get(0); - } - catch (std::bad_cast const &) - { - caught = true; - } - assert(caught); - - // additional test for stream-like extraction - { - double d; - int i; - std::string s; - std::tm t; - std::string c; - - r >> d >> i >> s >> t >> c; - - assert(equal_approx(d, 3.14)); - assert(i == 123); - assert(s == "Johny"); - assert(t.tm_year == 105); - assert(t.tm_mon == 11); - assert(t.tm_mday == 19); - assert(t.tm_hour == 22); - assert(t.tm_min == 14); - assert(t.tm_sec == 17); - assert(c == "a"); - } - } - - // additional test to check if the row object can be - // reused between queries - { - row r; - sql << "select * from soci_test", into(r); - - assert(r.size() == 5); - - assert(r.get_properties(0).get_data_type() == dt_double); - assert(r.get_properties(1).get_data_type() == dt_integer); - assert(r.get_properties(2).get_data_type() == dt_string); - assert(r.get_properties(3).get_data_type() == dt_date); - - sql << "select name, num_int from soci_test", into(r); - - assert(r.size() == 2); - - assert(r.get_properties(0).get_data_type() == dt_string); - assert(r.get_properties(1).get_data_type() == dt_integer); - } - } - - std::cout << "test 12 passed" << std::endl; -} - -// more dynamic bindings -void test13() -{ - session sql(backEndFactory_, connectString_); - - 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, 20)"; - sql << "insert into soci_test(id, val) values(3, 30)"; - -#ifndef SOCI_POSTGRESQL_NOPARAMS - { - int id = 2; - row r; - sql << "select val from soci_test where id = :id", use(id), into(r); - - assert(r.size() == 1); - assert(r.get_properties(0).get_data_type() == dt_integer); - assert(r.get(0) == 20); - } - { - int id; - row r; - statement st = (sql.prepare << - "select val from soci_test where id = :id", use(id), into(r)); - - id = 2; - st.execute(true); - assert(r.size() == 1); - assert(r.get_properties(0).get_data_type() == dt_integer); - assert(r.get(0) == 20); - - id = 3; - st.execute(true); - assert(r.size() == 1); - assert(r.get_properties(0).get_data_type() == dt_integer); - assert(r.get(0) == 30); - - id = 1; - st.execute(true); - assert(r.size() == 1); - assert(r.get_properties(0).get_data_type() == dt_integer); - assert(r.get(0) == 10); - } -#else - { - row r; - sql << "select val from soci_test where id = 2", into(r); - - assert(r.size() == 1); - assert(r.get_properties(0).get_data_type() == dt_integer); - assert(r.get(0) == 20); - } -#endif // SOCI_POSTGRESQL_NOPARAMS - - std::cout << "test 13 passed" << std::endl; -} - -// More Dynamic binding to row objects -void test14() -{ - { - session sql(backEndFactory_, connectString_); - - sql.uppercase_column_names(true); - - auto_table_creator tableCreator(tc_.table_creator_3(sql)); - - row r1; - sql << "select * from soci_test", into(r1); - assert(sql.got_data() == false); - - sql << "insert into soci_test values('david', '(404)123-4567')"; - sql << "insert into soci_test values('john', '(404)123-4567')"; - sql << "insert into soci_test values('doe', '(404)123-4567')"; - - row r2; - statement st = (sql.prepare << "select * from soci_test", into(r2)); - st.execute(); - - assert(r2.size() == 2); - - int count = 0; - while (st.fetch()) - { - ++count; - assert(r2.get("PHONE") == "(404)123-4567"); - } - assert(count == 3); - } - std::cout << "test 14 passed" << std::endl; -} - -// test15 is like test14 but with a type_conversion instead of a row -void test15() -{ - session sql(backEndFactory_, connectString_); - - sql.uppercase_column_names(true); - - // simple conversion (between single basic type and user type) - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - MyInt mi; - mi.set(123); - sql << "insert into soci_test(id) values(:id)", use(mi); - - int i; - sql << "select id from soci_test", into(i); - assert(i == 123); - - sql << "update soci_test set id = id + 1"; - - sql << "select id from soci_test", into(mi); - assert(mi.get() == 124); - } - - // simple conversion with use const - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - MyInt mi; - mi.set(123); - - MyInt const & cmi = mi; - sql << "insert into soci_test(id) values(:id)", use(cmi); - - int i; - sql << "select id from soci_test", into(i); - assert(i == 123); - } - - // conversions based on values (many fields involved -> ORM) - - { - auto_table_creator tableCreator(tc_.table_creator_3(sql)); - - PhonebookEntry p1; - sql << "select * from soci_test", into(p1); - assert(p1.name == ""); - assert(p1.phone == ""); - - p1.name = "david"; - - // Note: uppercase column names are used here (and later on) - // for consistency with how they can be read from database - // (which means forced to uppercase on Oracle) and how they are - // set/get in the type conversion routines for PhonebookEntry. - // In short, IF the database is Oracle, - // then all column names for binding should be uppercase. - sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); - sql << "insert into soci_test values('john', '(404)123-4567')"; - sql << "insert into soci_test values('doe', '(404)123-4567')"; - - PhonebookEntry p2; - statement st = (sql.prepare << "select * from soci_test", into(p2)); - st.execute(); - - int count = 0; - while (st.fetch()) - { - ++count; - if (p2.name == "david") - { - // see type_conversion - assert(p2.phone ==""); - } - else - { - assert(p2.phone == "(404)123-4567"); - } - } - assert(count == 3); - } - - // conversions based on values with use const - - { - auto_table_creator tableCreator(tc_.table_creator_3(sql)); - - PhonebookEntry p1; - p1.name = "Joe Coder"; - p1.phone = "123-456"; - - PhonebookEntry const & cp1 = p1; - - sql << "insert into soci_test values(:NAME, :PHONE)", use(cp1); - - PhonebookEntry p2; - sql << "select * from soci_test", into(p2); - assert(sql.got_data()); - - assert(p2.name == "Joe Coder"); - assert(p2.phone == "123-456"); - } - - // conversions based on accessor functions (as opposed to direct variable bindings) - - { - auto_table_creator tableCreator(tc_.table_creator_3(sql)); - - PhonebookEntry3 p1; - p1.setName("Joe Hacker"); - p1.setPhone("10010110"); - - sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); - - PhonebookEntry3 p2; - sql << "select * from soci_test", into(p2); - assert(sql.got_data()); - - assert(p2.getName() == "Joe Hacker"); - assert(p2.getPhone() == "10010110"); - } - - { - // Use the PhonebookEntry2 type conversion, to test - // calls to values::get_indicator() - auto_table_creator tableCreator(tc_.table_creator_3(sql)); - - PhonebookEntry2 p1; - sql << "select * from soci_test", into(p1); - assert(p1.name == ""); - assert(p1.phone == ""); - p1.name = "david"; - - sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); - sql << "insert into soci_test values('john', '(404)123-4567')"; - sql << "insert into soci_test values('doe', '(404)123-4567')"; - - PhonebookEntry2 p2; - statement st = (sql.prepare << "select * from soci_test", into(p2)); - st.execute(); - - int count = 0; - while (st.fetch()) - { - ++count; - if (p2.name == "david") - { - // see type_conversion - assert(p2.phone ==""); - } - else - { - assert(p2.phone == "(404)123-4567"); - } - } - assert(count == 3); - } - - std::cout << "test 15 passed" << std::endl; -} - -void test_prepared_insert_with_orm_type() -{ - { - session sql(backEndFactory_, connectString_); - - sql.uppercase_column_names(true); - auto_table_creator tableCreator(tc_.table_creator_3(sql)); - - PhonebookEntry temp; - PhonebookEntry e1 = { "name1", "phone1" }; - PhonebookEntry e2 = { "name2", "phone2" }; - - //sql << "insert into soci_test values (:NAME, :PHONE)", use(temp); - statement insertStatement = (sql.prepare << "insert into soci_test values (:NAME, :PHONE)", use(temp)); - - temp = e1; - insertStatement.execute(true); - temp = e2; - insertStatement.execute(true); - - int count = 0; - - sql << "select count(*) from soci_test where NAME in ('name1', 'name2')", into(count); - - assert(count == 2); - } - - std::cout << "test test_prepared_insert_with_orm_type passed" << std::endl; -} - -void test_placeholder_partial_matching_with_orm_type() -{ - { - session sql(backEndFactory_, connectString_); - sql.uppercase_column_names(true); - auto_table_creator tableCreator(tc_.table_creator_3(sql)); - - PhonebookEntry in = { "name1", "phone1" }; - std::string name = "nameA"; - sql << "insert into soci_test values (:NAMED, :PHONE)", use(in), use(name, "NAMED"); - - PhonebookEntry out; - sql << "select * from soci_test where PHONE = 'phone1'", into(out); - assert(out.name == "nameA"); - assert(out.phone == "phone1"); - } - - std::cout << "test test_placeholder_partial_matching_with_orm_type passed" << std::endl; -} - -// test for bulk fetch with single use -void test16() -{ -#ifndef SOCI_POSTGRESQL_NOPARAMS - { - session sql(backEndFactory_, connectString_); - - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - sql << "insert into soci_test(name, id) values('john', 1)"; - sql << "insert into soci_test(name, id) values('george', 2)"; - sql << "insert into soci_test(name, id) values('anthony', 1)"; - sql << "insert into soci_test(name, id) values('marc', 3)"; - sql << "insert into soci_test(name, id) values('julian', 1)"; - - int code = 1; - std::vector names(10); - sql << "select name from soci_test where id = :id order by name", - into(names), use(code); - - assert(names.size() == 3); - assert(names[0] == "anthony"); - assert(names[1] == "john"); - assert(names[2] == "julian"); - } -#endif // SOCI_POSTGRESQL_NOPARAMS - - std::cout << "test 16 passed" << std::endl; -} - -// test for basic logging support -void test17() -{ - session sql(backEndFactory_, connectString_); - - std::ostringstream log; - sql.set_log_stream(&log); - - try - { - sql << "drop table soci_test1"; - } - catch (...) {} - - assert(sql.get_last_query() == "drop table soci_test1"); - - sql.set_log_stream(NULL); - - try - { - sql << "drop table soci_test2"; - } - catch (...) {} - - assert(sql.get_last_query() == "drop table soci_test2"); - - sql.set_log_stream(&log); - - try - { - sql << "drop table soci_test3"; - } - catch (...) {} - - assert(sql.get_last_query() == "drop table soci_test3"); - assert(log.str() == - "drop table soci_test1\n" - "drop table soci_test3\n"); - - std::cout << "test 17 passed\n"; -} - -// test for rowset creation and copying -void test18() -{ - session sql(backEndFactory_, connectString_); - - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - { - // Open empty rowset - rowset rs1 = (sql.prepare << "select * from soci_test"); - assert(rs1.begin() == rs1.end()); - } - - { - // Copy construction - rowset rs1 = (sql.prepare << "select * from soci_test"); - rowset rs2(rs1); - rowset rs3(rs1); - rowset rs4(rs3); - - assert(rs1.begin() == rs2.begin()); - assert(rs1.begin() == rs3.begin()); - assert(rs1.end() == rs2.end()); - assert(rs1.end() == rs3.end()); - } - - { - // Assignment - rowset rs1 = (sql.prepare << "select * from soci_test"); - rowset rs2 = (sql.prepare << "select * from soci_test"); - rowset rs3 = (sql.prepare << "select * from soci_test"); - rs1 = rs2; - rs3 = rs2; - - assert(rs1.begin() == rs2.begin()); - assert(rs1.begin() == rs3.begin()); - assert(rs1.end() == rs2.end()); - assert(rs1.end() == rs3.end()); - } - std::cout << "test 18 passed" << std::endl; -} - -// test for simple iterating using rowset iterator (without reading data) -void test19() -{ - session sql(backEndFactory_, connectString_); - - // 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)"; - { - rowset rs = (sql.prepare << "select * from soci_test"); - - assert(5 == std::distance(rs.begin(), rs.end())); - } - } - - std::cout << "test 19 passed" << std::endl; -} - -// test for reading rowset using iterator -void test20() -{ - session sql(backEndFactory_, connectString_); - - sql.uppercase_column_names(true); - - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_2(sql)); - { - { - // Empty rowset - rowset rs = (sql.prepare << "select * from soci_test"); - assert(0 == std::distance(rs.begin(), rs.end())); - } - - { - // Non-empty rowset - sql << "insert into soci_test values(3.14, 123, \'Johny\'," - << tc_.to_date_time("2005-12-19 22:14:17") - << ", 'a')"; - sql << "insert into soci_test values(6.28, 246, \'Robert\'," - << tc_.to_date_time("2004-10-01 18:44:10") - << ", 'b')"; - - rowset rs = (sql.prepare << "select * from soci_test"); - - rowset::const_iterator it = rs.begin(); - assert(it != rs.end()); - - // - // First row - // - row const & r1 = (*it); - - // Properties - assert(r1.size() == 5); - assert(r1.get_properties(0).get_data_type() == dt_double); - assert(r1.get_properties(1).get_data_type() == dt_integer); - assert(r1.get_properties(2).get_data_type() == dt_string); - assert(r1.get_properties(3).get_data_type() == dt_date); - assert(r1.get_properties(4).get_data_type() == dt_string); - assert(r1.get_properties("NUM_INT").get_data_type() == dt_integer); - - // Data - - // Since we didn't specify order by in the above query, - // the 2 rows may be returned in either order - // (If we specify order by, we can't do it in a cross db - // compatible way, because the Oracle table for this has been - // created with lower case column names) - - std::string name = r1.get(2); - - assert(name == "Johny" || name == "Robert"); - if (name == "Johny") - { - assert(equal_approx(r1.get(0), 3.14)); - assert(r1.get(1) == 123); - assert(r1.get(2) == "Johny"); - std::tm t1 = { 0 }; - t1 = r1.get(3); - assert(t1.tm_year == 105); - assert(r1.get(4) == "a"); - assert(equal_approx(r1.get("NUM_FLOAT"), 3.14)); - assert(r1.get("NUM_INT") == 123); - assert(r1.get("NAME") == "Johny"); - assert(r1.get("CHR") == "a"); - } - else - { - assert(equal_approx(r1.get(0), 6.28)); - assert(r1.get(1) == 246); - assert(r1.get(2) == "Robert"); - std::tm t1 = r1.get(3); - assert(t1.tm_year == 104); - assert(r1.get(4) == "b"); - assert(equal_approx(r1.get("NUM_FLOAT"), 6.28)); - assert(r1.get("NUM_INT") == 246); - assert(r1.get("NAME") == "Robert"); - assert(r1.get("CHR") == "b"); - } - - // - // Iterate to second row - // - ++it; - assert(it != rs.end()); - - // - // Second row - // - row const & r2 = (*it); - - // Properties - assert(r2.size() == 5); - assert(r2.get_properties(0).get_data_type() == dt_double); - assert(r2.get_properties(1).get_data_type() == dt_integer); - assert(r2.get_properties(2).get_data_type() == dt_string); - assert(r2.get_properties(3).get_data_type() == dt_date); - assert(r2.get_properties(4).get_data_type() == dt_string); - assert(r2.get_properties("NUM_INT").get_data_type() == dt_integer); - - std::string newName = r2.get(2); - assert(name != newName); - assert(newName == "Johny" || newName == "Robert"); - - if (newName == "Johny") - { - assert(equal_approx(r2.get(0), 3.14)); - assert(r2.get(1) == 123); - assert(r2.get(2) == "Johny"); - std::tm t2 = r2.get(3); - assert(t2.tm_year == 105); - assert(r2.get(4) == "a"); - assert(equal_approx(r2.get("NUM_FLOAT"), 3.14)); - assert(r2.get("NUM_INT") == 123); - assert(r2.get("NAME") == "Johny"); - assert(r2.get("CHR") == "a"); - } - else - { - assert(equal_approx(r2.get(0), 6.28)); - assert(r2.get(1) == 246); - assert(r2.get(2) == "Robert"); - std::tm t2 = r2.get(3); - assert(t2.tm_year == 104); - assert(r2.get(4) == "b"); - assert(equal_approx(r2.get("NUM_FLOAT"), 6.28)); - assert(r2.get("NUM_INT") == 246); - assert(r2.get("NAME") == "Robert"); - assert(r2.get("CHR") == "b"); - } - } - - { - // Non-empty rowset with NULL values - sql << "insert into soci_test " - << "(num_int, num_float , name, sometime, chr) " - << "values (0, NULL, NULL, NULL, NULL)"; - - rowset rs = (sql.prepare - << "select num_int, num_float, name, sometime, chr " - << "from soci_test where num_int = 0"); - - rowset::const_iterator it = rs.begin(); - assert(it != rs.end()); - - // - // First row - // - row const& r1 = (*it); - - // Properties - assert(r1.size() == 5); - assert(r1.get_properties(0).get_data_type() == dt_integer); - assert(r1.get_properties(1).get_data_type() == dt_double); - assert(r1.get_properties(2).get_data_type() == dt_string); - assert(r1.get_properties(3).get_data_type() == dt_date); - assert(r1.get_properties(4).get_data_type() == dt_string); - - // Data - assert(r1.get_indicator(0) == soci::i_ok); - assert(r1.get(0) == 0); - assert(r1.get_indicator(1) == soci::i_null); - assert(r1.get_indicator(2) == soci::i_null); - assert(r1.get_indicator(3) == soci::i_null); - assert(r1.get_indicator(4) == soci::i_null); - } - } - - std::cout << "test 20 passed" << std::endl; -} - -// test for reading rowset using iterator -void test21() -{ - session sql(backEndFactory_, connectString_); - - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - { - sql << "insert into soci_test(id) values(1)"; - sql << "insert into soci_test(id) values(2)"; - sql << "insert into soci_test(id) values(3)"; - sql << "insert into soci_test(id) values(4)"; - sql << "insert into soci_test(id) values(5)"; - { - rowset rs = (sql.prepare << "select id from soci_test order by id asc"); - - // 1st row - rowset::const_iterator pos = rs.begin(); - assert(1 == (*pos)); - - // 3rd row - std::advance(pos, 2); - assert(3 == (*pos)); - - // 5th row - std::advance(pos, 2); - assert(5 == (*pos)); - - // The End - ++pos; - assert(pos == rs.end()); - } - } - - std::cout << "test 21 passed" << std::endl; -} - -// test for handling 'use' and reading rowset using iterator -void test22() -{ - session sql(backEndFactory_, connectString_); - - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - { - sql << "insert into soci_test(str) values('abc')"; - sql << "insert into soci_test(str) values('def')"; - sql << "insert into soci_test(str) values('ghi')"; - sql << "insert into soci_test(str) values('jkl')"; - { - // Expected result in numbers - std::string idle("def"); - rowset rs1 = (sql.prepare - << "select str from soci_test where str = :idle", - use(idle)); - - assert(1 == std::distance(rs1.begin(), rs1.end())); - - // Expected result in value - idle = "jkl"; - rowset rs2 = (sql.prepare - << "select str from soci_test where str = :idle", - use(idle)); - - assert(idle == *(rs2.begin())); - } - } - - std::cout << "test 22 passed" << std::endl; -} - -// test for handling troublemaker -void test23() -{ - session sql(backEndFactory_, connectString_); - - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - { - sql << "insert into soci_test(str) values('abc')"; - { - // verify exception thrown - bool caught = false; - try - { - std::string troublemaker; - rowset rs1 = (sql.prepare << "select str from soci_test", - into(troublemaker)); - } - catch (soci_error const&) - { - caught = true; - } - assert(caught); - } - std::cout << "test 23 passed" << std::endl; - } - -} - -// test for handling NULL values with expected exception: -// "Null value fetched and no indicator defined." -void test24() -{ - session sql(backEndFactory_, connectString_); - - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - { - sql << "insert into soci_test(val) values(1)"; - sql << "insert into soci_test(val) values(2)"; - sql << "insert into soci_test(val) values(NULL)"; - sql << "insert into soci_test(val) values(3)"; - { - // verify exception thrown - bool caught = false; - try - { - rowset rs = (sql.prepare << "select val from soci_test order by val asc"); - - int tester = 0; - for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) - { - tester = *it; - } - (void)tester; - - // Never should get here - assert(false); - } - catch (soci_error const&) - { - caught = true; - } - assert(caught); - } - std::cout << "test 24 passed" << std::endl; - } -} - -// test25 is like test15 but with rowset and iterators use -void test25() -{ - session sql(backEndFactory_, connectString_); - - sql.uppercase_column_names(true); - - { - auto_table_creator tableCreator(tc_.table_creator_3(sql)); - - PhonebookEntry p1; - sql << "select * from soci_test", into(p1); - assert(p1.name == ""); - assert(p1.phone == ""); - - p1.name = "david"; - - sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); - sql << "insert into soci_test values('john', '(404)123-4567')"; - sql << "insert into soci_test values('doe', '(404)123-4567')"; - - rowset rs = (sql.prepare << "select * from soci_test"); - - int count = 0; - for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) - { - ++count; - PhonebookEntry const& p2 = (*it); - if (p2.name == "david") - { - // see type_conversion - assert(p2.phone ==""); - } - else - { - assert(p2.phone == "(404)123-4567"); - } - } - - assert(3 == count); - } - std::cout << "test 25 passed" << std::endl; -} - -// test for handling NULL values with boost::optional -// (both into and use) -void test26() -{ -#ifdef HAVE_BOOST - - session sql(backEndFactory_, connectString_); - - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - { - sql << "insert into soci_test(val) values(7)"; - - { - // verify non-null value is fetched correctly - boost::optional opt; - sql << "select val from soci_test", into(opt); - assert(opt.is_initialized()); - assert(opt.get() == 7); - - // indicators can be used with optional - // (although that's just a consequence of implementation, - // not an intended feature - but let's test it anyway) - indicator ind; - opt.reset(); - sql << "select val from soci_test", into(opt, ind); - assert(opt.is_initialized()); - assert(opt.get() == 7); - assert(ind == i_ok); - - // verify null value is fetched correctly - sql << "select i1 from soci_test", into(opt); - assert(opt.is_initialized() == false); - - // and with indicator - opt = 5; - sql << "select i1 from soci_test", into(opt, ind); - assert(opt.is_initialized() == false); - assert(ind == i_null); - - // verify non-null is inserted correctly - opt = 3; - sql << "update soci_test set val = :v", use(opt); - int j = 0; - sql << "select val from soci_test", into(j); - assert(j == 3); - - // verify null is inserted correctly - opt.reset(); - sql << "update soci_test set val = :v", use(opt); - ind = i_ok; - sql << "select val from soci_test", into(j, ind); - assert(ind == i_null); - } - - // vector tests (select) - - { - sql << "delete from soci_test"; - - // simple readout of non-null data - - sql << "insert into soci_test(id, val, str) values(1, 5, \'abc\')"; - sql << "insert into soci_test(id, val, str) values(2, 6, \'def\')"; - sql << "insert into soci_test(id, val, str) values(3, 7, \'ghi\')"; - sql << "insert into soci_test(id, val, str) values(4, 8, null)"; - sql << "insert into soci_test(id, val, str) values(5, 9, \'mno\')"; - - std::vector > v(10); - sql << "select val from soci_test order by val", into(v); - - assert(v.size() == 5); - assert(v[0].is_initialized()); - assert(v[0].get() == 5); - assert(v[1].is_initialized()); - assert(v[1].get() == 6); - assert(v[2].is_initialized()); - assert(v[2].get() == 7); - assert(v[3].is_initialized()); - assert(v[3].get() == 8); - assert(v[4].is_initialized()); - assert(v[4].get() == 9); - - // readout of nulls - - sql << "update soci_test set val = null where id = 2 or id = 4"; - - std::vector ids(5); - sql << "select id, val from soci_test order by id", into(ids), into(v); - - assert(v.size() == 5); - assert(ids.size() == 5); - assert(v[0].is_initialized()); - assert(v[0].get() == 5); - assert(v[1].is_initialized() == false); - assert(v[2].is_initialized()); - assert(v[2].get() == 7); - assert(v[3].is_initialized() == false); - assert(v[4].is_initialized()); - assert(v[4].get() == 9); - - // readout with statement preparation - - int id = 1; - - ids.resize(3); - v.resize(3); - statement st = (sql.prepare << - "select id, val from soci_test order by id", into(ids), into(v)); - st.execute(); - while (st.fetch()) - { - for (std::size_t i = 0; i != v.size(); ++i) - { - assert(id == ids[i]); - - if (id == 2 || id == 4) - { - assert(v[i].is_initialized() == false); - } - else - { - assert(v[i].is_initialized() && v[i].get() == id + 4); - } - - ++id; - } - - ids.resize(3); - v.resize(3); - } - assert(id == 6); - } - - // and why not stress iterators and the dynamic binding, too! - - { - rowset rs = (sql.prepare << "select id, val, str from soci_test order by id"); - - rowset::const_iterator it = rs.begin(); - assert(it != rs.end()); - - row const& r1 = (*it); - - assert(r1.size() == 3); - - // Note: for the reason of differences between number(x,y) type and - // binary representation of integers, the following commented assertions - // do not work for Oracle. - // The problem is that for this single table the data type used in Oracle - // table creator for the id column is number(10,0), - // which allows to insert all int values. - // On the other hand, the column description scheme used in the Oracle - // backend figures out that the natural type for such a column - // is eUnsignedInt - this makes the following assertions fail. - // Other database backends (like PostgreSQL) use other types like int - // and this not only allows to insert all int values (obviously), - // but is also recognized as int (obviously). - // There is a similar problem with stream-like extraction, - // where internally get is called and the type mismatch is detected - // for the id column - that's why the code below skips this column - // and tests the remaining column only. - - //assert(r1.get_properties(0).get_data_type() == dt_integer); - assert(r1.get_properties(1).get_data_type() == dt_integer); - assert(r1.get_properties(2).get_data_type() == dt_string); - //assert(r1.get(0) == 1); - assert(r1.get(1) == 5); - assert(r1.get(2) == "abc"); - assert(r1.get >(1).is_initialized()); - assert(r1.get >(1).get() == 5); - assert(r1.get >(2).is_initialized()); - assert(r1.get >(2).get() == "abc"); - - ++it; - - row const& r2 = (*it); - - assert(r2.size() == 3); - - // assert(r2.get_properties(0).get_data_type() == dt_integer); - assert(r2.get_properties(1).get_data_type() == dt_integer); - assert(r2.get_properties(2).get_data_type() == dt_string); - //assert(r2.get(0) == 2); - try - { - // expect exception here, this is NULL value - (void)r1.get(1); - assert(false); - } - catch (soci_error const &) {} - - // but we can read it as optional - assert(r2.get >(1).is_initialized() == false); - - // stream-like data extraction - - ++it; - row const &r3 = (*it); - - boost::optional io; - boost::optional so; - - r3.skip(); // move to val and str columns - r3 >> io >> so; - - assert(io.is_initialized() && io.get() == 7); - assert(so.is_initialized() && so.get() == "ghi"); - - ++it; - row const &r4 = (*it); - - r3.skip(); // move to val and str columns - r4 >> io >> so; - - assert(io.is_initialized() == false); - assert(so.is_initialized() == false); - } - - // bulk inserts of non-null data - - { - sql << "delete from soci_test"; - - std::vector ids; - std::vector > v; - - ids.push_back(10); v.push_back(20); - ids.push_back(11); v.push_back(21); - ids.push_back(12); v.push_back(22); - ids.push_back(13); v.push_back(23); - - sql << "insert into soci_test(id, val) values(:id, :val)", - use(ids, "id"), use(v, "val"); - - int sum; - sql << "select sum(val) from soci_test", into(sum); - assert(sum == 86); - - // bulk inserts of some-null data - - sql << "delete from soci_test"; - - v[2].reset(); - v[3].reset(); - - sql << "insert into soci_test(id, val) values(:id, :val)", - use(ids, "id"), use(v, "val"); - - sql << "select sum(val) from soci_test", into(sum); - assert(sum == 41); - } - - // composability with user conversions - - { - sql << "delete from soci_test"; - - boost::optional omi1; - boost::optional omi2; - - omi1 = MyInt(125); - omi2.reset(); - - sql << "insert into soci_test(id, val) values(:id, :val)", - use(omi1), use(omi2); - - sql << "select id, val from soci_test", into(omi2), into(omi1); - - assert(omi1.is_initialized() == false); - assert(omi2.is_initialized() && omi2.get().get() == 125); - } - - // use with const optional and user conversions - - { - sql << "delete from soci_test"; - - boost::optional omi1; - boost::optional omi2; - - omi1 = MyInt(125); - omi2.reset(); - - boost::optional const & comi1 = omi1; - boost::optional const & comi2 = omi2; - - sql << "insert into soci_test(id, val) values(:id, :val)", - use(comi1), use(comi2); - - sql << "select id, val from soci_test", into(omi2), into(omi1); - - assert(omi1.is_initialized() == false); - assert(omi2.is_initialized() && omi2.get().get() == 125); - } - - // use with rowset and table containing null values - - { - 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, 13)"; - - rowset > rs = (sql.prepare << - "select val from soci_test order by id asc"); - - // 1st row - rowset >::const_iterator pos = rs.begin(); - assert((*pos).is_initialized()); - assert(10 == (*pos).get()); - - // 2nd row - ++pos; - assert((*pos).is_initialized()); - assert(11 == (*pos).get()); - - // 3rd row - ++pos; - assert((*pos).is_initialized() == false); - - // 4th row - ++pos; - assert((*pos).is_initialized()); - assert(13 == (*pos).get()); - } - } - - std::cout << "test 26 passed" << std::endl; -#else - std::cout << "test 26 skipped (no Boost)" << std::endl; -#endif // HAVE_BOOST -} - -// connection and reconnection tests -void test27() -{ - { - // empty session - session sql; - - // idempotent: - sql.close(); - - try - { - sql.reconnect(); - assert(false); - } - catch (soci_error const &e) - { - assert(e.what() == std::string( - "Cannot reconnect without previous connection.")); - } - - // open from empty session - sql.open(backEndFactory_, connectString_); - sql.close(); - - // reconnecting from closed session - sql.reconnect(); - - // opening already connected session - try - { - sql.open(backEndFactory_, connectString_); - assert(false); - } - catch (soci_error const &e) - { - assert(e.what() == std::string( - "Cannot open already connected session.")); - } - - sql.close(); - - // open from closed - sql.open(backEndFactory_, connectString_); - - // reconnect from already connected session - sql.reconnect(); - } - - { - session sql; - - try - { - sql << "this statement cannot execute"; - assert(false); - } - catch (soci_error const &e) - { - assert(e.what() == std::string("Session is not connected.")); - } - } - - std::cout << "test 27 passed" << std::endl; -} - -void test28() -{ -#ifdef HAVE_BOOST - session sql(backEndFactory_, connectString_); - - auto_table_creator tableCreator(tc_.table_creator_2(sql)); - { - boost::tuple t1(3.5, 7, "Joe Hacker"); - assert(equal_approx(t1.get<0>(), 3.5)); - assert(t1.get<1>() == 7); - assert(t1.get<2>() == "Joe Hacker"); - - sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); - - // basic query - - boost::tuple t2; - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(t2.get<0>(), 3.5)); - assert(t2.get<1>() == 7); - assert(t2.get<2>() == "Joe Hacker"); - - sql << "delete from soci_test"; - } - - { - // composability with boost::optional - - // use: - boost::tuple, std::string> t1( - 3.5, boost::optional(7), "Joe Hacker"); - assert(equal_approx(t1.get<0>(), 3.5)); - assert(t1.get<1>().is_initialized()); - assert(t1.get<1>().get() == 7); - assert(t1.get<2>() == "Joe Hacker"); - - sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); - - // into: - boost::tuple, std::string> t2; - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(t2.get<0>(), 3.5)); - assert(t2.get<1>().is_initialized()); - assert(t2.get<1>().get() == 7); - assert(t2.get<2>() == "Joe Hacker"); - - sql << "delete from soci_test"; - } - - { - // composability with user-provided conversions - - // use: - boost::tuple t1(3.5, 7, "Joe Hacker"); - assert(equal_approx(t1.get<0>(), 3.5)); - assert(t1.get<1>().get() == 7); - assert(t1.get<2>() == "Joe Hacker"); - - sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); - - // into: - boost::tuple t2; - - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(t2.get<0>(), 3.5)); - assert(t2.get<1>().get() == 7); - assert(t2.get<2>() == "Joe Hacker"); - - sql << "delete from soci_test"; - } - - { - // let's have fun - composition of tuple, optional and user-defined type - - // use: - boost::tuple, std::string> t1( - 3.5, boost::optional(7), "Joe Hacker"); - assert(equal_approx(t1.get<0>(), 3.5)); - assert(t1.get<1>().is_initialized()); - assert(t1.get<1>().get().get() == 7); - assert(t1.get<2>() == "Joe Hacker"); - - sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); - - // into: - boost::tuple, std::string> t2; - - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(t2.get<0>(), 3.5)); - assert(t2.get<1>().is_initialized()); - assert(t2.get<1>().get().get() == 7); - assert(t2.get<2>() == "Joe Hacker"); - - sql << "update soci_test set num_int = NULL"; - - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(t2.get<0>(), 3.5)); - assert(t2.get<1>().is_initialized() == false); - assert(t2.get<2>() == "Joe Hacker"); - } - - { - // rowset - - sql << "insert into soci_test(num_float, num_int, name) values(4.0, 8, 'Tony Coder')"; - sql << "insert into soci_test(num_float, num_int, name) values(4.5, NULL, 'Cecile Sharp')"; - sql << "insert into soci_test(num_float, num_int, name) values(5.0, 10, 'Djhava Ravaa')"; - - typedef boost::tuple, std::string> T; - - rowset rs = (sql.prepare - << "select num_float, num_int, name from soci_test order by num_float asc"); - - rowset::const_iterator pos = rs.begin(); - - assert(equal_approx(pos->get<0>(), 3.5)); - assert(pos->get<1>().is_initialized() == false); - assert(pos->get<2>() == "Joe Hacker"); - - ++pos; - assert(equal_approx(pos->get<0>(), 4.0)); - assert(pos->get<1>().is_initialized()); - assert(pos->get<1>().get() == 8); - assert(pos->get<2>() == "Tony Coder"); - - ++pos; - assert(equal_approx(pos->get<0>(), 4.5)); - assert(pos->get<1>().is_initialized() == false); - assert(pos->get<2>() == "Cecile Sharp"); - - ++pos; - assert(equal_approx(pos->get<0>(), 5.0)); - assert(pos->get<1>().is_initialized()); - assert(pos->get<1>().get() == 10); - assert(pos->get<2>() == "Djhava Ravaa"); - - ++pos; - assert(pos == rs.end()); - } - - std::cout << "test 28 passed" << std::endl; -#else - std::cout << "test 28 skipped (no Boost)" << std::endl; -#endif // HAVE_BOOST -} - -void test29() -{ -#ifdef HAVE_BOOST -#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 - - session sql(backEndFactory_, connectString_); - - auto_table_creator tableCreator(tc_.table_creator_2(sql)); - { - boost::fusion::vector t1(3.5, 7, "Joe Hacker"); - assert(equal_approx(boost::fusion::at_c<0>(t1), 3.5)); - assert(boost::fusion::at_c<1>(t1) == 7); - assert(boost::fusion::at_c<2>(t1) == "Joe Hacker"); - - sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); - - // basic query - - boost::fusion::vector t2; - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); - assert(boost::fusion::at_c<1>(t2) == 7); - assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); - - sql << "delete from soci_test"; - } - - { - // composability with boost::optional - - // use: - boost::fusion::vector, std::string> t1( - 3.5, boost::optional(7), "Joe Hacker"); - assert(equal_approx(boost::fusion::at_c<0>(t1), 3.5)); - assert(boost::fusion::at_c<1>(t1).is_initialized()); - assert(boost::fusion::at_c<1>(t1).get() == 7); - assert(boost::fusion::at_c<2>(t1) == "Joe Hacker"); - - sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); - - // into: - boost::fusion::vector, std::string> t2; - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); - assert(boost::fusion::at_c<1>(t2).is_initialized()); - assert(boost::fusion::at_c<1>(t2) == 7); - assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); - - sql << "delete from soci_test"; - } - - { - // composability with user-provided conversions - - // use: - boost::fusion::vector t1(3.5, 7, "Joe Hacker"); - assert(equal_approx(boost::fusion::at_c<0>(t1), 3.5)); - assert(boost::fusion::at_c<1>(t1).get() == 7); - assert(boost::fusion::at_c<2>(t1) == "Joe Hacker"); - - sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); - - // into: - boost::fusion::vector t2; - - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); - assert(boost::fusion::at_c<1>(t2).get() == 7); - assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); - - sql << "delete from soci_test"; - } - - { - // let's have fun - composition of tuple, optional and user-defined type - - // use: - boost::fusion::vector, std::string> t1( - 3.5, boost::optional(7), "Joe Hacker"); - assert(equal_approx(boost::fusion::at_c<0>(t1), 3.5)); - assert(boost::fusion::at_c<1>(t1).is_initialized()); - assert(boost::fusion::at_c<1>(t1).get().get() == 7); - assert(boost::fusion::at_c<2>(t1) == "Joe Hacker"); - - sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); - - // into: - boost::fusion::vector, std::string> t2; - - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); - assert(boost::fusion::at_c<1>(t2).is_initialized()); - assert(boost::fusion::at_c<1>(t2).get().get() == 7); - assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); - - sql << "update soci_test set num_int = NULL"; - - sql << "select num_float, num_int, name from soci_test", into(t2); - - assert(equal_approx(boost::fusion::at_c<0>(t2), 3.5)); - assert(boost::fusion::at_c<1>(t2).is_initialized() == false); - assert(boost::fusion::at_c<2>(t2) == "Joe Hacker"); - } - - { - // rowset - - sql << "insert into soci_test(num_float, num_int, name) values(4.0, 8, 'Tony Coder')"; - sql << "insert into soci_test(num_float, num_int, name) values(4.5, NULL, 'Cecile Sharp')"; - sql << "insert into soci_test(num_float, num_int, name) values(5.0, 10, 'Djhava Ravaa')"; - - typedef boost::fusion::vector, std::string> T; - - rowset rs = (sql.prepare - << "select num_float, num_int, name from soci_test order by num_float asc"); - - rowset::const_iterator pos = rs.begin(); - - assert(equal_approx(boost::fusion::at_c<0>(*pos), 3.5)); - assert(boost::fusion::at_c<1>(*pos).is_initialized() == false); - assert(boost::fusion::at_c<2>(*pos) == "Joe Hacker"); - - ++pos; - assert(equal_approx(boost::fusion::at_c<0>(*pos), 4.0)); - assert(boost::fusion::at_c<1>(*pos).is_initialized()); - assert(boost::fusion::at_c<1>(*pos).get() == 8); - assert(boost::fusion::at_c<2>(*pos) == "Tony Coder"); - - ++pos; - assert(equal_approx(boost::fusion::at_c<0>(*pos), 4.5)); - assert(boost::fusion::at_c<1>(*pos).is_initialized() == false); - assert(boost::fusion::at_c<2>(*pos) == "Cecile Sharp"); - - ++pos; - assert(equal_approx(boost::fusion::at_c<0>(*pos), 5.0)); - assert(boost::fusion::at_c<1>(*pos).is_initialized()); - assert(boost::fusion::at_c<1>(*pos).get() == 10); - assert(boost::fusion::at_c<2>(*pos) == "Djhava Ravaa"); - - ++pos; - assert(pos == rs.end()); - } - - std::cout << "test 29 passed" << std::endl; - -#else - std::cout << "test 29 skipped (no boost::fusion)" << std::endl; -#endif // BOOST_VERSION - -#else - std::cout << "test 29 skipped (no Boost)" << std::endl; -#endif // HAVE_BOOST -} - -// test for boost::gregorian::date -void test30() -{ -#ifdef HAVE_BOOST - - session sql(backEndFactory_, connectString_); - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - std::tm nov15; - nov15.tm_year = 105; - nov15.tm_mon = 10; - nov15.tm_mday = 15; - nov15.tm_hour = 0; - nov15.tm_min = 0; - nov15.tm_sec = 0; - - sql << "insert into soci_test(tm) values(:tm)", use(nov15); - - boost::gregorian::date bgd; - sql << "select tm from soci_test", into(bgd); - - assert(bgd.year() == 2005); - assert(bgd.month() == 11); - assert(bgd.day() == 15); - - sql << "update soci_test set tm = NULL"; - try - { - sql << "select tm from soci_test", into(bgd); - assert(false); - } - catch (soci_error const & e) - { - assert(e.what() == std::string("Null value not allowed for this type")); - } - } - - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - boost::gregorian::date bgd(2008, boost::gregorian::May, 5); - - sql << "insert into soci_test(tm) values(:tm)", use(bgd); - - std::tm t; - sql << "select tm from soci_test", into(t); - - assert(t.tm_year == 108); - assert(t.tm_mon == 4); - assert(t.tm_mday == 5); - } - - std::cout << "test 30 passed" << std::endl; -#else - std::cout << "test 30 skipped (no Boost)" << std::endl; -#endif // HAVE_BOOST -} - -// connection pool - simple sequential test, no multiple threads -void test31() -{ - { - // phase 1: preparation - const size_t pool_size = 10; - connection_pool pool(pool_size); - - for (std::size_t i = 0; i != pool_size; ++i) - { - session & sql = pool.at(i); - sql.open(backEndFactory_, connectString_); - } - - // phase 2: usage - for (std::size_t i = 0; i != pool_size; ++i) - { - // poor man way to lease more than one connection - session sql_unused1(pool); - session sql(pool); - session sql_unused2(pool); - { - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - char c('a'); - sql << "insert into soci_test(c) values(:c)", use(c); - sql << "select c from soci_test", into(c); - assert(c == 'a'); - } - } - } - std::cout << "test 31 passed\n"; -} - -// Issue 66 - test query transformation callback feature -static std::string no_op_transform(std::string query) -{ - return query; -} - -static std::string lower_than_g(std::string query) -{ - return query + " WHERE c < 'g'"; -} - -struct where_condition : std::unary_function -{ - where_condition(std::string const& where) - : where_(where) - {} - - result_type operator()(argument_type query) const - { - return query + " WHERE " + where_; - } - - std::string where_; -}; - - -void run_query_transformation_test(session& sql) -{ - // create and populate the test table - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - - for (char c = 'a'; c <= 'z'; ++c) - { - sql << "insert into soci_test(c) values(\'" << c << "\')"; - } - - char const* query = "select count(*) from soci_test"; - - // free function, no-op - { - sql.set_query_transformation(no_op_transform); - int count; - sql << query, into(count); - assert(count == 'z' - 'a' + 1); - } - - // free function - { - sql.set_query_transformation(lower_than_g); - int count; - sql << query, into(count); - assert(count == 'g' - 'a'); - } - - // function object with state - { - sql.set_query_transformation(where_condition("c > 'g' AND c < 'j'")); - int count = 0; - sql << query, into(count); - assert(count == 'j' - 'h'); - count = 0; - sql.set_query_transformation(where_condition("c > 's' AND c <= 'z'")); - sql << query, into(count); - assert(count == 'z' - 's'); - } - -// Bug in Visual Studio __cplusplus still means C++03 -// https://connect.microsoft.com/VisualStudio/feedback/details/763051/ -#if defined _MSC_VER && _MSC_VER>=1600 -#define SOCI_HAVE_CPP11 1 -#elif __cplusplus >= 201103L -#define SOCI_HAVE_CPP11 1 -#else -#undef SOCI_HAVE_CPP11 -#endif - -#ifdef SOCI_HAVE_CPP11 - // lambda - { - sql.set_query_transformation( - [](std::string const& query) { - return query + " WHERE c > 'g' AND c < 'j'"; - }); - - int count = 0; - sql << query, into(count); - assert(count == 'j' - 'h'); - } -#endif -#undef SOCI_HAVE_CPP11 - - // prepared statements - - // constant effect (pre-prepare set transformation) - { - // set transformation after statement is prepared - sql.set_query_transformation(lower_than_g); - // prepare statement - int count; - statement st = (sql.prepare << query, into(count)); - // observe transformation effect - st.execute(true); - assert(count == 'g' - 'a'); - // reset transformation - sql.set_query_transformation(no_op_transform); - // observe the same transformation, no-op set above has no effect - count = 0; - st.execute(true); - assert(count == 'g' - 'a'); - } - - // no effect (post-prepare set transformation) - { - // reset - sql.set_query_transformation(no_op_transform); - - // prepare statement - int count; - statement st = (sql.prepare << query, into(count)); - // set transformation after statement is prepared - sql.set_query_transformation(lower_than_g); - // observe no effect of WHERE clause injection - st.execute(true); - assert(count == 'z' - 'a' + 1); - } -} - -void test_query_transformation() -{ - { - session sql(backEndFactory_, connectString_); - run_query_transformation_test(sql); - } - std::cout << "test query_transformation passed" << std::endl; -} -void test_query_transformation_with_connection_pool() -{ - { - // phase 1: preparation - const size_t pool_size = 10; - connection_pool pool(pool_size); - - for (std::size_t i = 0; i != pool_size; ++i) - { - session & sql = pool.at(i); - sql.open(backEndFactory_, connectString_); - } - - session sql(pool); - run_query_transformation_test(sql); - } - std::cout << "test query_transformation with connection pool passed" << std::endl; -} - -// Originally, submitted to SQLite3 backend and later moved to common test. -// Test commit b394d039530f124802d06c3b1a969c3117683152 -// Author: Mika Fischer -// Date: Thu Nov 17 13:28:07 2011 +0100 -// Implement get_affected_rows for SQLite3 backend -void test_get_affected_rows() -{ - { - session sql(backEndFactory_, connectString_); - auto_table_creator tableCreator(tc_.table_creator_4(sql)); - if (!tableCreator.get()) - { - std::cout << "test get_affected_rows skipped (function not implemented)" << std::endl; - return; - } - - for (int i = 0; i != 10; i++) - { - sql << "insert into soci_test(val) values(:val)", use(i); - } - - statement st1 = (sql.prepare << - "update soci_test set val = val + 1"); - st1.execute(true); - - assert(st1.get_affected_rows() == 10); - - statement st2 = (sql.prepare << - "delete from soci_test where val <= 5"); - st2.execute(true); - - assert(st2.get_affected_rows() == 5); - - statement st3 = (sql.prepare << - "update soci_test set val = val + 1"); - st3.execute(true); - - assert(st3.get_affected_rows() == 5); - - std::vector v(5, 0); - for (std::size_t i = 0; i < v.size(); ++i) - { - v[i] = (7 + i); - } - - // test affected rows for bulk operations. - statement st4 = (sql.prepare << - "delete from soci_test where val = :v", use(v)); - st4.execute(true); - - assert(st4.get_affected_rows() == 5); - - std::vector w(2, "1"); - w[1] = "a"; // this invalid value may cause an exception. - statement st5 = (sql.prepare << - "insert into soci_test(val) values(:val)", use(w)); - try { st5.execute(true); } - catch(...) {} - - // confirm the partial insertion. - int val = 0; - sql << "select count(val) from soci_test", into(val); - if(val != 0) - { - // test the preserved 'number of rows - // affected' after a potential failure. - assert(st5.get_affected_rows() != 0); - } - } - - std::cout << "test get_affected_rows passed" << std::endl; -} - -// test fix for: Backend is not set properly with connection pool (pull #5) -void test_pull5() -{ - { - const size_t pool_size = 1; - connection_pool pool(pool_size); - - for (std::size_t i = 0; i != pool_size; ++i) - { - session & sql = pool.at(i); - sql.open(backEndFactory_, connectString_); - } - - soci::session sql(pool); - sql.reconnect(); - sql.begin(); // no crash expected - } - - std::cout << "test pull-5 passed\n"; -} - -// issue 67 - Allocated statement backend memory leaks on exception -// If the test runs under memory debugger and it passes, then -// soci::details::statement_impl::backEnd_ must not leak -void test_issue67() -{ - session sql(backEndFactory_, connectString_); - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - { - try - { - rowset rs1 = (sql.prepare << "select * from soci_testX"); - - // TODO: On Linux, no exception thrown; neither from prepare, nor from execute? - // soci_odbc_test_postgresql: - // /home/travis/build/SOCI/soci/src/core/test/common-tests.h:3505: - // void soci::tests::common_tests::test_issue67(): Assertion `!"exception expected"' failed. - //assert(!"exception expected"); // relax temporarily - } - catch (soci_error const &e) - { - (void)e; - assert("expected exception caught"); - std::cout << "test issue-67 passed - check memory debugger output for leaks" << std::endl; - } - } - -} - -// issue 154 - Calling undefine_and_bind and then define_and_bind causes a leak. -// If the test runs under memory debugger and it passes, then -// soci::details::standard_use_type_backend and vector_use_type_backend must not leak -void test_issue154() -{ - session sql(backEndFactory_, connectString_); - auto_table_creator tableCreator(tc_.table_creator_1(sql)); - sql << "insert into soci_test(id) values (1)"; - { - int id = 1; - int val = 0; - statement st(sql); - st.exchange(use(id)); - st.alloc(); - st.prepare("select id from soci_test where id = :1"); - st.define_and_bind(); - st.undefine_and_bind(); - st.exchange(soci::into(val)); - st.define_and_bind(); - st.execute(true); - assert(val == 1); - } - // vector variation - { - std::vector ids(1, 2); - std::vector vals(1, 1); - int val = 0; - statement st(sql); - st.exchange(use(ids)); - st.alloc(); - st.prepare("insert into soci_test(id, val) values (:1, :2)"); - st.define_and_bind(); - st.undefine_and_bind(); - st.exchange(use(vals)); - st.define_and_bind(); - st.execute(true); - sql << "select val from soci_test where id = 2", into(val); - assert(val == 1); - } - std::cout << "test issue-154 passed - check memory debugger output for leaks" << std::endl; -} - -}; // class common_tests - -} // namespace tests - -} // namespace soci - -#endif // SOCI_COMMON_TESTS_H_INCLUDED diff --git a/src/core/transaction.cpp b/src/core/transaction.cpp index 57b38a9e45..bb53d77925 100644 --- a/src/core/transaction.cpp +++ b/src/core/transaction.cpp @@ -6,8 +6,8 @@ // #define SOCI_SOURCE -#include "transaction.h" -#include "error.h" +#include "soci/transaction.h" +#include "soci/error.h" using namespace soci; diff --git a/src/core/use-type.cpp b/src/core/use-type.cpp index 24185eda22..2841563425 100644 --- a/src/core/use-type.cpp +++ b/src/core/use-type.cpp @@ -6,8 +6,12 @@ // #define SOCI_SOURCE -#include "use-type.h" -#include "statement.h" +#include "soci/soci-platform.h" +#include "soci/use-type.h" +#include "soci/statement.h" +#include "soci-exchange-cast.h" + +#include using namespace soci; using namespace soci::details; @@ -33,7 +37,77 @@ void standard_use_type::bind(statement_impl & st, int & position) } } -void standard_use_type::pre_use() +void standard_use_type::dump_value(std::ostream& os) const +{ + if (ind_ && *ind_ == i_null) + { + os << "NULL"; + return; + } + + switch (type_) + { + case x_char: + os << "'" << exchange_type_cast(data_) << "'"; + return; + + case x_stdstring: + // TODO: Escape quotes? + os << "\"" << exchange_type_cast(data_) << "\""; + return; + + case x_short: + os << exchange_type_cast(data_); + return; + + case x_integer: + os << exchange_type_cast(data_); + return; + + case x_long_long: + os << exchange_type_cast(data_); + return; + + case x_unsigned_long_long: + os << exchange_type_cast(data_); + return; + + case x_double: + os << exchange_type_cast(data_); + return; + + case x_stdtm: + { + std::tm const& t = exchange_type_cast(data_); + + char buf[32]; + snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d", + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, + t.tm_hour, t.tm_min, t.tm_sec); + + os << buf; + } + return; + + case x_statement: + os << ""; + return; + + case x_rowid: + os << ""; + return; + + case x_blob: + os << ""; + return; + } + + // This is normally unreachable, but avoid throwing from here as we're + // typically called from an exception handler. + os << ""; +} + +void standard_use_type::pre_use() { // Handle IN direction of parameters of SQL statements and procedures convert_to_base(); @@ -47,8 +121,8 @@ void standard_use_type::post_use(bool gotData) convert_from_base(); // IMPORTANT: - // This treatment of input ("use") parameter as output data sink may be - // confusing, but it is necessary to store OUT data back in the same + // This treatment of input ("use") parameter as output data sink may be + // confusing, but it is necessary to store OUT data back in the same // object as IN, of IN/OUT parameter. // As there is no symmetry for IN/OUT in SQL and there are no OUT/IN // we do not perform convert_to_base() for output ("into") parameter. @@ -84,6 +158,12 @@ void vector_use_type::bind(statement_impl & st, int & position) } } +void vector_use_type::dump_value(std::ostream& os) const +{ + // TODO: Provide more information. + os << ""; +} + void vector_use_type::pre_use() { convert_to_base(); diff --git a/src/core/use.h b/src/core/use.h deleted file mode 100644 index 24c518146e..0000000000 --- a/src/core/use.h +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright (C) 2004-2008 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) -// - -#ifndef SOCI_USE_H_INCLUDED -#define SOCI_USE_H_INCLUDED - -#include "use-type.h" -#include "exchange-traits.h" -#include "type-conversion.h" - -namespace soci -{ - -// the use function is a helper for defining input variables -// these helpers work with both basic and user-defined types thanks to -// the tag-dispatching, as defined in exchange_traits template - -template -details::use_type_ptr use(T & t, std::string const & name = std::string()) -{ - return details::do_use(t, name, - typename details::exchange_traits::type_family()); -} - -template -details::use_type_ptr use(T const & t, - std::string const & name = std::string()) -{ - return details::do_use(t, name, - typename details::exchange_traits::type_family()); -} - -template -details::use_type_ptr use(T & t, indicator & ind, - std::string const &name = std::string()) -{ - return details::do_use(t, ind, name, - typename details::exchange_traits::type_family()); -} - -template -details::use_type_ptr use(T const & t, indicator & ind, - std::string const &name = std::string()) -{ - return details::do_use(t, ind, name, - typename details::exchange_traits::type_family()); -} - -template -details::use_type_ptr use(T & t, std::vector & ind, - std::string const & name = std::string()) -{ - return details::do_use(t, ind, name, - typename details::exchange_traits::type_family()); -} - -template -details::use_type_ptr use(T const & t, std::vector & ind, - std::string const & name = std::string()) -{ - return details::do_use(t, ind, name, - typename details::exchange_traits::type_family()); -} - -// for char buffer with run-time size information -template -details::use_type_ptr use(T & t, std::size_t bufSize, - std::string const & name = std::string()) -{ - return details::use_type_ptr(new details::use_type(t, bufSize)); -} - -// for char buffer with run-time size information -template -details::use_type_ptr use(T const & t, std::size_t bufSize, - std::string const & name = std::string()) -{ - return details::use_type_ptr(new details::use_type(t, bufSize)); -} - -} // namespace soci - -#endif // SOCI_USE_H_INCLUDED diff --git a/src/core/values.cpp b/src/core/values.cpp index 3883415c26..7fa9d1a7d1 100644 --- a/src/core/values.cpp +++ b/src/core/values.cpp @@ -6,8 +6,8 @@ // #define SOCI_SOURCE -#include "values.h" -#include "row.h" +#include "soci/values.h" +#include "soci/row.h" #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000..28ee64af58 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,27 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2013 Mateusz Loskot +# 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) +# +############################################################################### + +colormsg(_HIBLUE_ "Configuring SOCI tests:") + +include_directories( + ${SOCI_SOURCE_DIR}/include/private + ${CMAKE_CURRENT_SOURCE_DIR}) + +file(GLOB SOCI_TESTS_COMMON common-tests.h) + +add_subdirectory(empty) +add_subdirectory(db2) +add_subdirectory(firebird) +add_subdirectory(mysql) +add_subdirectory(odbc) +add_subdirectory(oracle) +add_subdirectory(postgresql) +add_subdirectory(sqlite3) diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..4cce8a3bd1 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,10 @@ +# soci/tests + +SOCI tests using [CATCH](http://catch-lib.net/) testing framework live here. + +Currently one test is built for each backend, i.e. there are +`soci_oracle_test`, `soci_postgresql_test`, `soci_sqlite3_test` and so on and +for ODBC backend there are multiple versions of the test depending on the ODBC +driver used. Each of these tests can be run with a single parameter describing +the database to use for testing in the backend-specific way as well as any of +the standard [CATCH command line options](https://github.com/philsquared/Catch/blob/master/docs/command-line.md). diff --git a/tests/catch.hpp b/tests/catch.hpp new file mode 100644 index 0000000000..6b8dfb5ebd --- /dev/null +++ b/tests/catch.hpp @@ -0,0 +1,8997 @@ +/* + * CATCH v1.0 build 53 (master branch) + * Generated: 2014-08-20 08:08:19.533804 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * 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 TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +// #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" +#elif defined __GNUC__ +#pragma GCC diagnostic ignored "-Wvariadic-macros" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpadded" +#endif + +#ifdef CATCH_CONFIG_MAIN +# define CATCH_CONFIG_RUNNER +#endif + +#ifdef CATCH_CONFIG_RUNNER +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #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) + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_CONFIG_CPP11_NOEXCEPT +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// 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_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 + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1310 ) // (VC++ 7.0+) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#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 +#endif + +#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 +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +namespace Catch { + + class NonCopyable { + NonCopyable( NonCopyable const& ); + void operator = ( NonCopyable const& ); + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool endsWith( std::string const& s, std::string const& 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 ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); + SourceLineInfo( SourceLineInfo const& other ); +# ifdef CATCH_CPP11_OR_GREATER + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + + std::string file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#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 + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + 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& 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 ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + 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; + + }; +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +struct AutoReg { + + 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 ); + } + + void registerTestCase( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #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____ )() + + /////////////////////////////////////////////////////////////////////////////// + #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 ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : 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 ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#else + /////////////////////////////////////////////////////////////////////////////// + #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____ )() + + /////////////////////////////////////////////////////////////////////////////// + #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 ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : 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 ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2 + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x00, + + ContinueOnFailure = 0x01, // Failures fail test, but execution continues + FalseTest = 0x02, // Prefix expression with ! + SuppressFail = 0x04 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ); + + std::string macroName; + SourceLineInfo lineInfo; + std::string capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + + struct AssertionResultData + { + AssertionResultData() : resultType( ResultWas::Unknown ) {} + + std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CPP11_OR_GREATER + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end 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 << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ); + + template + ExpressionLhs operator->* ( T const& operand ); + ExpressionLhs operator->* ( bool value ); + + template + ResultBuilder& operator << ( T const& 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(); + + std::string reconstructExpression() const; + AssertionResult build() const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + struct ExprComponents { + ExprComponents() : testFalse( false ) {} + bool testFalse; + std::string lhs, rhs, op; + } m_exprComponents; + CopyableStream m_stream; + + bool m_shouldDebugBreak; + bool m_shouldThrow; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + inline T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return opCast( lhs ) == opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) != opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) < opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) > opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) >= opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) <= opCast( rhs ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#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 ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, NULL ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #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 +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +namespace Catch { +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 + + struct BorgType { + template BorgType( T const& ); + }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; + +#endif + + template + struct StringMakerBase { + template + static std::string convert( T const& ) { return "{?}"; } + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +std::string toString( T const& value ); + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + 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() ); + } +}; + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +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 << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +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 + +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 + + template + ResultBuilder& operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + bool value = m_lhs ? true : false; + m_rb + .setLhs( Catch::toString( value ) ) + .setResultType( value ) + .endExpression(); + } + + // 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& ); + +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() ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; +}; + +} // end namespace Catch + + +namespace Catch { + + template + inline ExpressionLhs ResultBuilder::operator->* ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator->* ( bool value ) { + return ExpressionLhs( *this, value ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + 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 pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_IPHONE +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CATCH_PLATFORM_WINDOWS +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // 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 + #endif + +#elif defined(_MSC_VER) + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } +#endif + +#ifndef CATCH_BREAK_INTO_DEBUGGER +#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + ( __catchResult->*expr ).endExpression(); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ + } \ + 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 + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( log, macroName ) \ + 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 ) \ + do { \ + 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(); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #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 + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef CATCH_PLATFORM_WINDOWS +typedef unsigned long long uint64_t; +#else +#include +#endif + +namespace Catch { + + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedNanoseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + uint64_t m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + 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; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate() const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate() const { + try { + throw; + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#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 ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + 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) ) ); + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_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 + "\""; + } + + std::string m_str; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr ) : m_substr( substr ){} + Contains( Contains const& other ) : m_substr( other.m_substr ){} + + 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; + }; + + 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 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 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str ) { + return Impl::StdString::Equals( str ); + } + 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 ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != NULL; } + bool none() const { return nullableValue == NULL; } + + bool operator !() const { return nullableValue == NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T* nullableValue; + char storage[sizeof(T)]; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + template + struct StringHolder : MatcherImpl{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string toString() const { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string toString() const { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string toString() const { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string toString() const { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_CONFIG_RUNNER +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: catch_runner.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + 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 ); + } + } + 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 + } + private: + std::string m_name; + WildcardPosition m_wildcard; + }; + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + 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 ) + if( !(*it)->matches( testCase ) ) + return false; + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + } + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + class Stream { + public: + Stream(); + Stream( std::streambuf* _streamBuf, bool _isOwned ); + void release(); + + std::streambuf* streamBuf; + + private: + bool isOwned; + }; +} + +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + abortAfter( -1 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + + int abortAfter; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + + std::string reporterName; + std::string outputFilename; + std::string name; + std::string processName; + + std::vector testsOrTags; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + : m_os( std::cout.rdbuf() ) + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_os( std::cout.rdbuf() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() { + m_os.rdbuf( std::cout.rdbuf() ); + m_stream.release(); + } + + void setFilename( std::string const& filename ) { + m_data.outputFilename = filename; + } + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + + std::string getProcessName() const { return m_data.processName; } + + bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + + 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; } + + 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; } + + private: + ConfigData m_data; + + Stream m_stream; + mutable std::ostream m_os; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + 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 { + public: + 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; + + 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 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + 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 ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + 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" ) + _dest = false; + 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 + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +# 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; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + 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; + } + private: + IArgFunction* functionObj; + }; + + 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 ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + 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; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + 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 ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + 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)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + 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& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + 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 ); + }; + + } // namespace Detail + + struct Parser { + Parser() : separators( " \t=:" ) {} + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { + const std::string doubleDash = "--"; + for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) + parseIntoTokens( argv[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 ); + } + } + std::string separators; + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + // 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 + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg = ArgAutoPtr( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg = ArgAutoPtr( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( int argc, char const * const * argv ) const { + ConfigT config; + parseInto( argc, argv, config ); + return config; + } + + std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { + std::string processName = argv[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 ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.setFlag( config ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + + 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 + "'" ); + + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, "#" ) ) + addTestOrTags( config, "\"" + line + "\"," ); + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &ConfigData::reporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes/no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + // 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-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + 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 { + public: + 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; + + 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 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + 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 ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + namespace Detail { + struct IColourImpl; + } + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + static Detail::IColourImpl* impl(); + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + 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; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CPP11_OR_GREATER + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CPP11_OR_GREATER + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CPP11_OR_GREATER + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CPP11_OR_GREATER + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CPP11_OR_GREATER + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + 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; + }; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map FactoryMap; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + }; + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + std::cout << "Matching test cases:\n"; + else { + std::cout << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + std::cout << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( !testCaseInfo.tags.empty() ) + std::cout << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + std::cout << pluralise( matchedTests, "test case" ) << "\n" << std::endl; + else + std::cout << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + 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; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + std::cout << "Tags for matching test cases:\n"; + else { + std::cout << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + std::cout << oss.str() << wrapper << "\n"; + } + std::cout << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + std::cout << "Available reports:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + std::cout << " " + << it->first + << ":" + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << "\n"; + } + std::cout << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_runner_impl.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 + +namespace Catch { +namespace SectionTracking { + + class TrackedSection { + + typedef std::map TrackedSections; + + public: + enum RunState { + NotStarted, + Executing, + ExecutingChildren, + Completed + }; + + TrackedSection( std::string const& name, TrackedSection* parent ) + : m_name( name ), m_runState( NotStarted ), m_parent( parent ) + {} + + RunState runState() const { return m_runState; } + + TrackedSection* findChild( std::string const& childName ) { + TrackedSections::iterator it = m_children.find( childName ); + return it != m_children.end() + ? &it->second + : NULL; + } + 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 enter() { + if( m_runState == NotStarted ) + m_runState = Executing; + } + 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; + } + TrackedSection* getParent() { + return m_parent; + } + bool hasChildren() const { + return !m_children.empty(); + } + + private: + std::string m_name; + RunState m_runState; + TrackedSections m_children; + TrackedSection* m_parent; + + }; + + class TestCaseTracker { + public: + TestCaseTracker( std::string const& testCaseName ) + : m_testCase( testCaseName, NULL ), + m_currentSection( &m_testCase ), + m_completedASectionThisRun( false ) + {} + + 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(); + } + ~Guard() { + m_tracker.leaveTestCase(); + } + 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(); + } + + TrackedSection m_testCase; + TrackedSection* m_currentSection; + bool m_completedASectionThisRun; + }; + +} // namespace SectionTracking + +using SectionTracking::TestCaseTracker; + +} // namespace Catch + +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& config, Ptr const& reporter ) + : m_runInfo( config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( NULL ), + m_config( config ), + m_reporter( reporter ), + m_prevRunner( m_context.getRunner() ), + m_prevResultCapture( m_context.getResultCapture() ), + m_prevConfig( m_context.getConfig() ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + 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 ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + m_testCaseTracker = TestCaseTracker( testInfo.name ); + + do { + do { + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isCompleted() && !aborting() ); + } + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = NULL; + m_testCaseTracker.reset(); + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) + m_messages.clear(); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + std::ostringstream oss; + oss << sectionInfo.name << "@" << sectionInfo.lineInfo; + + if( !m_testCaseTracker->enterSection( oss.str() ) ) + return false; + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 || + !m_config->warnAboutMissingAssertions() || + m_testCaseTracker->currentSectionHasChildren() ) + 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; + bool missingAssertions = testForMissingAssertions( assertions ); + + m_testCaseTracker->leaveSection(); + + m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : ""; + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + TestCaseTracker::Guard guard( *m_testCaseTracker ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( std::cout, redirectedCout ); + StreamRedirect cerrRedir( std::cerr, redirectedCerr ); + m_activeTestCase->invoke(); + } + else { + m_activeTestCase->invoke(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // 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(); + } + // 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_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + private: + struct UnfinishedSections { + UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) + : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo info; + Counts prevAssertions; + double durationInSeconds; + }; + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + Option m_testCaseTracker; + 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; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + 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 const majorVersion; + unsigned int const minorVersion; + unsigned int const buildNumber; + char const* const branchName; + + private: + void operator=( Version const& ); + }; + + extern Version libraryVersion; +} + +#include +#include +#include + +namespace Catch { + + class Runner { + + public: + Runner( Ptr const& config ) + : m_config( config ) + { + openStream(); + makeReporter(); + } + + Totals runTests() { + + RunContext context( m_config.get(), m_reporter ); + + Totals totals; + + context.testGroupStarting( "", 1, 1 ); // deprecated? + + 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; + } + + 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 { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + std::cerr << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + 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"; + + m_cli.usage( std::cout, processName ); + std::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 ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + std::cerr << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( std::cout, m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char* const argv[] ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + Runner runner( m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runner.runTests().assertions.failed ); + } + catch( std::exception& ex ) { + std::cerr << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class TestRegistry : public ITestCaseRegistry { + public: + TestRegistry() : m_unnamedCount( 0 ) {} + virtual ~TestRegistry(); + + virtual void registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name == "" ) { + 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); + } + } + + virtual std::vector const& getAllTests() const { + return m_functionsInOrder; + } + + 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 ); + } + } + + private: + + std::set m_functions; + std::vector m_functionsInOrder; + std::vector m_nonHiddenFunctions; + size_t m_unnamedCount; + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, "&" ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg( 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 ) ); + } + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() { + deleteAllValues( m_factories ); + } + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + + FactoryMap const& getFactories() const { + return m_factories; + } + + private: + FactoryMap m_factories; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + throw; + } + @catch (NSException *exception) { + return toString( [exception description] ); + } +#else + throw; +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return tryTranslators( m_translators.begin() ); + } + } + + 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 ); + } + } + + private: + std::vector m_translators; + }; +} + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + return m_exceptionTranslatorRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerTest( TestCase const& testInfo ) { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #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 + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + Stream::Stream() + : streamBuf( NULL ), isOwned( false ) + {} + + Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) + : streamBuf( _streamBuf ), isOwned( _isOwned ) + {} + + void Stream::release() { + if( isOwned ) { + delete streamBuf; + streamBuf = NULL; + isOwned = false; + } + } +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + 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; + } +} + +// #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; + }; +}} + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +namespace Catch { +namespace { + + class Win32ColourImpl : public Detail::IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalAttributes = csbiInfo.wAttributes; + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalAttributes ); + 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 ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + } + HANDLE stdoutHandle; + WORD originalAttributes; + }; + + inline bool shouldUseColourForPlatform() { + return true; + } + + static Detail::IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + return &s_instance; + } + +} // end anon namespace +} // end namespace Catch + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public Detail::IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + 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::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + private: + void setColour( const char* _escapeCode ) { + std::cout << '\033' << _escapeCode; + } + }; + + inline bool shouldUseColourForPlatform() { + return isatty(STDOUT_FILENO); + } + + static Detail::IColourImpl* platformColourInstance() { + static PosixColourImpl s_instance; + return &s_instance; + } + +} // end anon namespace +} // end namespace Catch + +#endif // not Windows + +namespace Catch { + + namespace { + struct NoColourImpl : Detail::IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + static bool shouldUseColour() { + return shouldUseColourForPlatform() && !isDebuggerActive(); + } + } + + 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(); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!" + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructedExpression; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( tag == "." || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !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); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + enforceNotReservedTag( tag, _lineInfo ); + + inTag = false; + if( tag == "hide" || tag == "." ) + isHidden = true; + else + tags.insert( tag ); + tag.clear(); + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : 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(); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + // These numbers are maintained by a script + Version libraryVersion( 1, 0, 53, "master" ); +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + getResultCapture().popScopedMessage( m_info ); + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#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; + if (!hz) { + QueryPerformanceFrequency((LARGE_INTEGER*)&hz); + QueryPerformanceCounter((LARGE_INTEGER*)&hzo); + } + uint64_t t; + QueryPerformanceCounter((LARGE_INTEGER*)&t); + return ((t-hzo)*1000000)/hz; + } +#else + uint64_t getCurrentTicks() { + timeval t; + gettimeofday(&t,NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedNanoseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast((getCurrentTicks() - m_ticks)/1000); + } + double Timer::getElapsedSeconds() const { + return (getCurrentTicks() - m_ticks)/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == 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; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + 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 ) : ""; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << " " << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << "s"; + return os; + } + + SourceLineInfo::SourceLineInfo() : 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(); + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && file == other.file; + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << "(" << info.line << ")"; +#else + 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 << "'"; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) + getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#include + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // 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; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#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() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + std::cout << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + namespace { + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return "\"" + s + "\""; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + oss << value; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + if( value > 8192 ) + oss << "0x" << std::hex << value; + else + oss << value; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + "f"; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + return value < ' ' + ? toString( static_cast( value ) ) + : Detail::makeString( value ); +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ) + {} + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + 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::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream.oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || m_assertionInfo.resultDisposition == ResultDisposition::Normal ) + m_shouldThrow = true; + } + } + void ResultBuilder::react() { + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() 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; + } + + 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 + ")"; + } + 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}"; + } + +} // end 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() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + 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; + 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(); } + + 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); + } + } + +} // 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 + +namespace Catch { + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + + virtual ~StreamingReporterBase(); + + virtual void noMatchingTestCases( std::string const& ) {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + currentTestCaseInfo.reset(); + assert( m_sectionStack.empty() ); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( 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& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + ~CumulativeReporterBase(); + + virtual void testRunStarting( TestRunInfo const& ) {} + virtual void testGroupStarting( GroupInfo const& ) {} + + virtual void testCaseStarting( TestCaseInfo const& ) {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + + }; + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; +} + +#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 ); } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &std::cout ) + {} + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &os ) + {} + + ~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_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + stream() << "/>\n"; + m_tagIsOpen = false; + } + else { + stream() << m_indent << "\n"; + } + 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() << "\""; + } + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + if( !name.empty() ) + stream() << " " << name << "=\"" << attribute << "\""; + return *this; + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + stream() << m_indent; + writeEncodedText( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + stream() << m_indent << ""; + m_needsNewline = true; + return *this; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + stream() << "\n"; + return *this; + } + + void setStream( std::ostream& os ) { + m_os = &os; + } + + 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 newlineIfNecessary() { + if( m_needsNewline ) { + stream() << "\n"; + 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; + }; + +} +namespace Catch { + class XmlReporter : public SharedImpl { + public: + XmlReporter( ReporterConfig const& config ) : m_config( config ), m_sectionDepth( 0 ) {} + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + virtual ~XmlReporter(); + + private: // IReporter + + virtual bool shouldRedirectStdout() const { + return true; + } + + virtual void StartTesting() { + m_xml.setStream( m_config.stream() ); + m_xml.startElement( "Catch" ); + if( !m_config.fullConfig()->name().empty() ) + m_xml.writeAttribute( "name", m_config.fullConfig()->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 ) { + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupName ); + } + + 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 StartSection( const std::string& sectionName, const std::string& description ) { + 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(); + } + } + + virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) { + m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); + m_currentTestSuccess = true; + } + + virtual void Result( const Catch::AssertionResult& assertionResult ) { + if( !m_config.fullConfig()->includeSuccessfulResults() && assertionResult.getResultType() == ResultWas::Ok ) + return; + + if( assertionResult.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", assertionResult.succeeded() ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ); + + m_xml.scopedElement( "Original" ) + .writeText( assertionResult.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( assertionResult.getExpandedExpression() ); + m_currentTestSuccess &= assertionResult.succeeded(); + } + + switch( assertionResult.getResultType() ) { + case ResultWas::ThrewException: + m_xml.scopedElement( "Exception" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + m_currentTestSuccess = false; + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Warning: + m_xml.scopedElement( "Warning" ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::ExplicitFailure: + m_xml.scopedElement( "Failure" ) + .writeText( assertionResult.getMessage() ); + m_currentTestSuccess = false; + break; + case ResultWas::Unknown: + case ResultWas::Ok: + case ResultWas::FailureBit: + case ResultWas::ExpressionFailed: + case ResultWas::Exception: + case ResultWas::DidntThrowException: + break; + } + if( assertionResult.hasExpression() ) + m_xml.endElement(); + } + + virtual void Aborted() { + // !TBD + } + + virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals&, const std::string&, const std::string& ) { + m_xml.scopedElement( "OverallResult" ).writeAttribute( "success", m_currentTestSuccess ); + m_xml.endElement(); + } + + private: + ReporterConfig m_config; + bool m_currentTestSuccess; + XmlWriter m_xml; + int m_sectionDepth; + }; + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + {} + + ~JunitReporter(); + + 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 ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", "tbd" ); // !TBD + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + "/" + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + 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 << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +#include + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter(); + 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 ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // 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; + } + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + 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; + } + else { + if( m_config->showDurations() == ShowDurations::Always ) + stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << "\n" << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with message"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << "\n"; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << "\n"; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << "\n"; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + 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"; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + 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" + << "Run with -? for options\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << "\n"; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << "\n"; + } + stream << getLineOfChars<'.'>() << "\n" << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << "\n"; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << "\n"; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << "\n"; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = " " + *it; + while( it->size() > row.size() ) + row = " " + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ")" + << "\n"; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << " " << it->label; + } + } + stream << "\n"; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + 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; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // 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; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << " " << passOrFail; + } + stream << ":"; + } + } + + void printIssue( std::string issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + 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 << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - 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 " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : ""; + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "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" ) << "."; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + 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() {} + + void Config::dummy() {} + + INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter ) +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +// Standard C/C++ main entry point +int main (int argc, char * const argv[]) { + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// 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" ) + +#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_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_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 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_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" ) + +#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_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__ ) +#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_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 ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#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, "" ) + +// 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" ) + +#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" ) + +#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 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_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 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" ) + +#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__ ) +#else + #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 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 ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#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, "" ) + +using Catch::Detail::Approx; + +// #included from: internal/catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/tests/common-tests.h b/tests/common-tests.h new file mode 100644 index 0000000000..dd3b26f20e --- /dev/null +++ b/tests/common-tests.h @@ -0,0 +1,4137 @@ +// +// Copyright (C) 2004-2008 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) +// + +#ifndef SOCI_COMMON_TESTS_H_INCLUDED +#define SOCI_COMMON_TESTS_H_INCLUDED + +#include "soci/soci.h" + +#ifdef HAVE_BOOST +// explicitly pull conversions for Boost's optional, tuple and fusion: +#include +#include "soci/boost-optional.h" +#include "soci/boost-tuple.h" +#include "soci/boost-gregorian-date.h" +#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 +#include "soci/boost-fusion.h" +#endif // BOOST_VERSION +#endif // HAVE_BOOST + +#include "soci-compiler.h" + +#define CATCH_CONFIG_RUNNER +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Although SQL standard mandates right padding CHAR(N) values to their length +// with spaces, some backends don't confirm to it: +// +// - Firebird does pad the string but to the byte-size (not character size) of +// the column (i.e. CHAR(10) NONE is padded to 10 bytes but CHAR(10) UTF8 -- +// to 40). +// - For MySql PAD_CHAR_TO_FULL_LENGTH option must be set, otherwise the value +// is trimmed. +// - SQLite never behaves correctly at all. +// +// This method will check result string from column defined as fixed char It +// will check only bytes up to the original string size. If padded string is +// bigger than expected string then all remaining chars must be spaces so if +// any non-space character is found it will fail. +void +checkEqualPadded(const std::string& padded_str, const std::string& expected_str) +{ + size_t const len = expected_str.length(); + std::string const start_str(padded_str, 0, len); + + if (start_str != expected_str) + { + throw soci::soci_error( + "Expected string \"" + expected_str + "\" " + "is different from the padded string \"" + padded_str + "\"" + ); + } + + if (padded_str.length() > len) + { + std::string const end_str(padded_str, len); + if (end_str != std::string(padded_str.length() - len, ' ')) + { + throw soci::soci_error( + "\"" + padded_str + "\" starts with \"" + padded_str + + "\" but non-space characater(s) are found aftewards" + ); + } + } +} + +#define CHECK_EQUAL_PADDED(padded_str, expected_str) \ + CHECK_NOTHROW(checkEqualPadded(padded_str, expected_str)); + +// Objects used later in tests 14,15 +struct PhonebookEntry +{ + std::string name; + std::string phone; +}; + +struct PhonebookEntry2 : public PhonebookEntry +{ +}; + +class PhonebookEntry3 +{ +public: + void setName(std::string const & n) { name_ = n; } + std::string getName() const { return name_; } + + void setPhone(std::string const & p) { phone_ = p; } + std::string getPhone() const { return phone_; } + +public: + std::string name_; + std::string phone_; +}; + +// user-defined object for test26 and test28 +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 +{ + +// basic type conversion for user-defined type with single base value +template<> struct type_conversion +{ + typedef int base_type; + + static void from_base(int i, indicator ind, MyInt &mi) + { + if (ind == i_ok) + { + mi.set(i); + } + } + + static void to_base(MyInt const &mi, int &i, indicator &ind) + { + i = mi.get(); + ind = i_ok; + } +}; + +// basic type conversion on many values (ORM) +template<> struct type_conversion +{ + typedef soci::values base_type; + + static void from_base(values const &v, indicator /* ind */, PhonebookEntry &pe) + { + // here we ignore the possibility the the whole object might be NULL + pe.name = v.get("NAME"); + pe.phone = v.get("PHONE", ""); + } + + static void to_base(PhonebookEntry const &pe, values &v, indicator &ind) + { + v.set("NAME", pe.name); + v.set("PHONE", pe.phone, pe.phone.empty() ? i_null : i_ok); + ind = i_ok; + } +}; + +// type conversion which directly calls values::get_indicator() +template<> struct type_conversion +{ + typedef soci::values base_type; + + static void from_base(values const &v, indicator /* ind */, PhonebookEntry2 &pe) + { + // here we ignore the possibility the the whole object might be NULL + + pe.name = v.get("NAME"); + indicator ind = v.get_indicator("PHONE"); //another way to test for null + pe.phone = ind == i_null ? "" : v.get("PHONE"); + } + + static void to_base(PhonebookEntry2 const &pe, values &v, indicator &ind) + { + v.set("NAME", pe.name); + v.set("PHONE", pe.phone, pe.phone.empty() ? i_null : i_ok); + ind = i_ok; + } +}; + +template<> struct type_conversion +{ + typedef soci::values base_type; + + static void from_base(values const &v, indicator /* ind */, PhonebookEntry3 &pe) + { + // here we ignore the possibility the the whole object might be NULL + + pe.setName(v.get("NAME")); + pe.setPhone(v.get("PHONE", "")); + } + + static void to_base(PhonebookEntry3 const &pe, values &v, indicator &ind) + { + v.set("NAME", pe.getName()); + v.set("PHONE", pe.getPhone(), pe.getPhone().empty() ? i_null : i_ok); + ind = i_ok; + } +}; + +} // namespace soci + +namespace soci +{ +namespace tests +{ + +// TODO: improve cleanup capabilities by subtypes, soci_test name may be omitted --mloskot +// i.e. optional ctor param accepting custom table name +class table_creator_base +{ +public: + table_creator_base(session& sql) + : msession(sql) { drop(); } + + virtual ~table_creator_base() { drop();} +private: + void drop() + { + try + { + msession << "drop table soci_test"; + } + catch (soci_error const& e) + { + //std::cerr << e.what() << std::endl; + e.what(); + } + } + session& msession; + + SOCI_NOT_COPYABLE(table_creator_base) +}; + +class procedure_creator_base +{ +public: + procedure_creator_base(session& sql) + : msession(sql) { drop(); } + + virtual ~procedure_creator_base() { drop();} +private: + void drop() + { + try { msession << "drop procedure soci_test"; } catch (soci_error&) {} + } + session& msession; + + SOCI_NOT_COPYABLE(procedure_creator_base) +}; + +class function_creator_base +{ +public: + function_creator_base(session& sql) + : msession(sql) { drop(); } + + virtual ~function_creator_base() { drop();} + +protected: + virtual std::string dropstatement() + { + return "drop function soci_test"; + } + +private: + void drop() + { + try { msession << dropstatement(); } catch (soci_error&) {} + } + session& msession; + + SOCI_NOT_COPYABLE(function_creator_base) +}; + +// This is a singleton class, at any given time there is at most one test +// context alive and common_tests fixture class uses it. +class test_context_base +{ +public: + test_context_base(backend_factory const &backEnd, + std::string const &connectString) + : backEndFactory_(backEnd), + connectString_(connectString) + { + // This can't be a CHECK() because the test context is constructed + // outside of any test. + assert(!the_test_context_); + + the_test_context_ = this; + + // To allow running tests in non-default ("C") locale, the following + // environment variable can be set and then the current default locale + // (which can itself be changed by setting LC_ALL environment variable) + // will then be used. + if (std::getenv("SOCI_TEST_USE_LC_ALL")) + std::setlocale(LC_ALL, ""); + } + + static test_context_base const& get_instance() + { + REQUIRE(the_test_context_); + + return *the_test_context_; + } + + backend_factory const & get_backend_factory() const + { + return backEndFactory_; + } + + std::string get_connect_string() const + { + return connectString_; + } + + virtual std::string to_date_time(std::string const &dateTime) const = 0; + + virtual table_creator_base* table_creator_1(session&) const = 0; + virtual table_creator_base* table_creator_2(session&) const = 0; + virtual table_creator_base* table_creator_3(session&) const = 0; + virtual table_creator_base* table_creator_4(session&) const = 0; + + // 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. + virtual bool has_fp_bug() const { return false; } + + // Override this if the backend doesn't handle multiple active select + // statements at the same time, i.e. a result set must be entirely consumed + // before creating a new one (this is the case of MS SQL without MARS). + virtual bool has_multiple_select_bug() const { return false; } + + // Override this if the backend may not have transactions support. + virtual bool has_transactions_support(session&) const { return true; } + + // Override this if the backend silently truncates string values too long + // to fit by default. + virtual bool has_silent_truncate_bug(session&) const { return false; } + + // Override this to call commit() if it's necessary for the DDL statements + // to be taken into account (currently this is only the case for Firebird). + virtual void on_after_ddl(session&) const { } + + // Put the database in SQL-complient mode for CHAR(N) values, return false + // if it's impossible, i.e. if the database doesn't behave correctly + // whatever we do. + virtual bool enable_std_char_padding(session&) const { return true; } + + virtual ~test_context_base() + { + the_test_context_ = NULL; + } + +private: + backend_factory const &backEndFactory_; + std::string const connectString_; + + static test_context_base* the_test_context_; + + SOCI_NOT_COPYABLE(test_context_base) +}; + +// Currently all tests consist of just a single source file, so we can define +// this member here because this header is included exactly once. +tests::test_context_base* tests::test_context_base::the_test_context_ = NULL; + + +// Compare doubles for approximate equality. This has to be used everywhere +// where we write "3.14" (or "6.28") to the database as a string and then +// compare the value read back with the literal 3.14 floating point constant +// because they are not the same. +// +// It is also used for the backends which currently don't handle doubles +// correctly. +// +// Notice that this function is normally not used directly but rather from the +// macro below. +inline bool are_doubles_approx_equal(double const a, double const b) +{ + // The formula taken from CATCH test framework + // https://github.com/philsquared/Catch/ + // Thanks to Richard Harris for his help refining this formula + double const epsilon(std::numeric_limits::epsilon() * 100); + double const scale(1.0); + return std::fabs(a - b) < epsilon * (scale + (std::max)(std::fabs(a), std::fabs(b))); +} + +// This is a macro to ensure we use the correct line numbers. The weird +// do/while construction is used to make this a statement and the even weirder +// condition in while ensures that the loop is executed exactly once without +// triggering warnings from MSVC about the condition being always false. +#define ASSERT_EQUAL_APPROX(a, b) \ + do { \ + if (!are_doubles_approx_equal((a), (b))) { \ + FAIL( "Approximate equality check failed: " \ + << std::fixed \ + << std::setprecision(std::numeric_limits::digits10 + 1) \ + << (a) << " != " << (b) ); \ + } \ + } while ( (void)0, 0 ) + + +// Exact double comparison function. We need one, instead of writing "a == b", +// only in order to have some place to put the pragmas disabling gcc warnings. +inline bool +are_doubles_exactly_equal(double a, double b) +{ + // Avoid g++ warnings: we do really want the exact equality here. + GCC_WARNING_SUPPRESS(float-equal) + + return a == b; + + GCC_WARNING_RESTORE(float-equal) +} + +#define ASSERT_EQUAL_EXACT(a, b) \ + do { \ + if (!are_doubles_exactly_equal((a), (b))) { \ + FAIL( "Exact equality check failed: " \ + << std::fixed \ + << std::setprecision(std::numeric_limits::digits10 + 1) \ + << (a) << " != " << (b) ); \ + } \ + } while ( (void)0, 0 ) + + +// Compare two floating point numbers either exactly or approximately depending +// on test_context::has_fp_bug() return value. +inline bool +are_doubles_equal(test_context_base const& tc, double a, double b) +{ + return tc.has_fp_bug() + ? are_doubles_approx_equal(a, b) + : are_doubles_exactly_equal(a, b); +} + +// This macro should be used when where we don't have any problems with string +// literals vs floating point literals mismatches described above and would +// ideally compare the numbers exactly but, unfortunately, currently can't do +// this unconditionally because at least some backends are currently buggy and +// don't handle the floating point values correctly. +// +// This can be only used from inside the common_tests class as it relies on +// having an accessible "tc_" variable to determine whether exact or +// approximate comparison should be used. +#define ASSERT_EQUAL(a, b) \ + do { \ + if (!are_doubles_equal(tc_, (a), (b))) { \ + FAIL( "Equality check failed: " \ + << std::fixed \ + << std::setprecision(std::numeric_limits::digits10 + 1) \ + << (a) << " != " << (b) ); \ + } \ + } while ( (void)0, 0 ) + + +class common_tests +{ +public: + common_tests() + : tc_(test_context_base::get_instance()), + backEndFactory_(tc_.get_backend_factory()), + connectString_(tc_.get_connect_string()) + {} + +protected: + test_context_base const & tc_; + backend_factory const &backEndFactory_; + std::string const connectString_; + + SOCI_NOT_COPYABLE(common_tests) +}; + +typedef std::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 +// building the name of the "anonymous" function by the TEST_CASE macro, we +// could have a conflict between a test defined here and in some backend if +// they happened to start on the same line. +namespace test_cases +{ + +TEST_CASE_METHOD(common_tests, "Exception on not connected", "[core][exception]") +{ + soci::session sql; // no connection + + // ensure connection is checked, no crash occurs + CHECK_THROWS_AS(sql.begin(), soci_error); + CHECK_THROWS_AS(sql.commit(), soci_error); + CHECK_THROWS_AS(sql.rollback(), soci_error); + CHECK_THROWS_AS(sql.get_backend_name(), soci_error); + CHECK_THROWS_AS(sql.make_statement_backend(), soci_error); + CHECK_THROWS_AS(sql.make_rowid_backend(), soci_error); + CHECK_THROWS_AS(sql.make_blob_backend(), soci_error); + + std::string s; + long l; + CHECK_THROWS_AS(sql.get_next_sequence_value(s, l), soci_error); + CHECK_THROWS_AS(sql.get_last_insert_id(s, l), soci_error); +} + +TEST_CASE_METHOD(common_tests, "Basic functionality") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + CHECK_THROWS_AS(sql << "drop table soci_test_nosuchtable", soci_error); + + sql << "insert into soci_test (id) values (" << 123 << ")"; + int id; + sql << "select id from soci_test", into(id); + CHECK(id == 123); +} + +// "into" tests, type conversions, etc. +TEST_CASE_METHOD(common_tests, "Use and into", "[core][into]") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + SECTION("Round trip works for char") + { + char c('a'); + sql << "insert into soci_test(c) values(:c)", use(c); + sql << "select c from soci_test", into(c); + CHECK(c == 'a'); + } + + SECTION("Round trip works for string") + { + std::string helloSOCI("Hello, SOCI!"); + sql << "insert into soci_test(str) values(:s)", use(helloSOCI); + std::string str; + sql << "select str from soci_test", into(str); + CHECK(str == "Hello, SOCI!"); + } + + SECTION("Round trip works for short") + { + short three(3); + sql << "insert into soci_test(sh) values(:id)", use(three); + short sh(0); + sql << "select sh from soci_test", into(sh); + CHECK(sh == 3); + } + + SECTION("Round trip works for int") + { + int five(5); + sql << "insert into soci_test(id) values(:id)", use(five); + int i(0); + sql << "select id from soci_test", into(i); + CHECK(i == 5); + } + + SECTION("Round trip works for unsigned long") + { + unsigned long seven(7); + sql << "insert into soci_test(ul) values(:ul)", use(seven); + unsigned long ul(0); + sql << "select ul from soci_test", into(ul); + CHECK(ul == 7); + } + + SECTION("Round trip works for double") + { + double pi(3.14159265); + sql << "insert into soci_test(d) values(:d)", use(pi); + double d(0.0); + sql << "select d from soci_test", into(d); + ASSERT_EQUAL(d, pi); + } + + SECTION("Round trip works for date without time") + { + std::tm nov15; + nov15.tm_year = 105; + nov15.tm_mon = 10; + nov15.tm_mday = 15; + nov15.tm_hour = 0; + nov15.tm_min = 0; + nov15.tm_sec = 0; + + sql << "insert into soci_test(tm) values(:tm)", use(nov15); + + std::tm t; + sql << "select tm from soci_test", into(t); + CHECK(t.tm_year == 105); + CHECK(t.tm_mon == 10); + CHECK(t.tm_mday == 15); + CHECK(t.tm_hour == 0); + CHECK(t.tm_min == 0); + CHECK(t.tm_sec == 0); + } + + SECTION("Round trip works for date with time") + { + std::tm nov15; + nov15.tm_year = 105; + nov15.tm_mon = 10; + nov15.tm_mday = 15; + nov15.tm_hour = 22; + nov15.tm_min = 14; + nov15.tm_sec = 17; + + sql << "insert into soci_test(tm) values(:tm)", use(nov15); + + std::tm t; + sql << "select tm from soci_test", into(t); + CHECK(t.tm_year == 105); + CHECK(t.tm_mon == 10); + CHECK(t.tm_mday == 15); + CHECK(t.tm_hour == 22); + CHECK(t.tm_min == 14); + CHECK(t.tm_sec == 17); + } + + SECTION("Indicator is filled correctly in the simplest case") + { + int id(1); + std::string str("Hello"); + sql << "insert into soci_test(id, str) values(:id, :str)", + use(id), use(str); + + int i; + indicator ind; + sql << "select id from soci_test", into(i, ind); + CHECK(ind == i_ok); + } + + SECTION("Indicators work correctly more generally") + { + sql << "insert into soci_test(id,tm) values(NULL,NULL)"; + int i; + indicator ind; + sql << "select id from soci_test", into(i, ind); + CHECK(ind == i_null); + + // additional test for NULL with std::tm + std::tm t; + sql << "select tm from soci_test", into(t, ind); + CHECK(ind == i_null); + + try + { + // expect error + sql << "select id from soci_test", into(i); + FAIL("expected exception not thrown"); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Null value fetched and no indicator defined."); + } + + sql << "select id from soci_test where id = 1000", into(i, ind); + CHECK(sql.got_data() == false); + + // no data expected + sql << "select id from soci_test where id = 1000", into(i); + CHECK(sql.got_data() == false); + + // no data expected, test correct behaviour with use + int id = 1000; + sql << "select id from soci_test where id = :id", use(id), into(i); + CHECK(sql.got_data() == false); + } +} + +// repeated fetch and bulk fetch +TEST_CASE_METHOD(common_tests, "Repeated and bulk fetch", "[core][bulk]") +{ + soci::session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + SECTION("char") + { + char c; + for (c = 'a'; c <= 'z'; ++c) + { + sql << "insert into soci_test(c) values(\'" << c << "\')"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + CHECK(count == 'z' - 'a' + 1); + + { + char c2 = 'a'; + + statement st = (sql.prepare << + "select c from soci_test order by c", into(c)); + + st.execute(); + while (st.fetch()) + { + CHECK(c == c2); + ++c2; + } + CHECK(c2 == 'a' + count); + } + { + char c2 = 'a'; + + std::vector vec(10); + statement st = (sql.prepare << + "select c from soci_test order by c", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != vec.size(); ++i) + { + CHECK(c2 == vec[i]); + ++c2; + } + + vec.resize(10); + } + CHECK(c2 == 'a' + count); + } + + { + // verify an exception is thrown when empty vector is used + std::vector vec; + try + { + sql << "select c from soci_test", into(vec); + FAIL("expected exception not thrown"); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Vectors of size 0 are not allowed."); + } + } + + } + + // repeated fetch and bulk fetch of std::string + SECTION("std::string") + { + int const rowsToTest = 10; + for (int i = 0; i != rowsToTest; ++i) + { + std::ostringstream ss; + ss << "Hello_" << i; + + sql << "insert into soci_test(str) values(\'" + << ss.str() << "\')"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + CHECK(count == rowsToTest); + + { + int i = 0; + std::string s; + statement st = (sql.prepare << + "select str from soci_test order by str", into(s)); + + st.execute(); + while (st.fetch()) + { + std::ostringstream ss; + ss << "Hello_" << i; + CHECK(s == ss.str()); + ++i; + } + CHECK(i == rowsToTest); + } + { + int i = 0; + + std::vector vec(4); + statement st = (sql.prepare << + "select str from soci_test order by str", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t j = 0; j != vec.size(); ++j) + { + std::ostringstream ss; + ss << "Hello_" << i; + CHECK(ss.str() == vec[j]); + ++i; + } + + vec.resize(4); + } + CHECK(i == rowsToTest); + } + } + + SECTION("short") + { + short const rowsToTest = 100; + short sh; + for (sh = 0; sh != rowsToTest; ++sh) + { + sql << "insert into soci_test(sh) values(" << sh << ")"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + CHECK(count == rowsToTest); + + { + short sh2 = 0; + + statement st = (sql.prepare << + "select sh from soci_test order by sh", into(sh)); + + st.execute(); + while (st.fetch()) + { + CHECK(sh == sh2); + ++sh2; + } + CHECK(sh2 == rowsToTest); + } + { + short sh2 = 0; + + std::vector vec(8); + statement st = (sql.prepare << + "select sh from soci_test order by sh", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != vec.size(); ++i) + { + CHECK(sh2 == vec[i]); + ++sh2; + } + + vec.resize(8); + } + CHECK(sh2 == rowsToTest); + } + } + + SECTION("int") + { + int const rowsToTest = 100; + int i; + for (i = 0; i != rowsToTest; ++i) + { + sql << "insert into soci_test(id) values(" << i << ")"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + CHECK(count == rowsToTest); + + { + int i2 = 0; + + statement st = (sql.prepare << + "select id from soci_test order by id", into(i)); + + st.execute(); + while (st.fetch()) + { + CHECK(i == i2); + ++i2; + } + CHECK(i2 == rowsToTest); + } + { + // additional test with the use element + + int i2 = 0; + int cond = 0; // this condition is always true + + statement st = (sql.prepare << + "select id from soci_test where id >= :cond order by id", + use(cond), into(i)); + + st.execute(); + while (st.fetch()) + { + CHECK(i == i2); + ++i2; + } + CHECK(i2 == rowsToTest); + } + { + int i2 = 0; + + std::vector vec(8); + statement st = (sql.prepare << + "select id from soci_test order by id", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != vec.size(); ++i) + { + CHECK(i2 == vec[i]); + ++i2; + } + + vec.resize(8); + } + CHECK(i2 == rowsToTest); + } + } + + SECTION("unsigned int") + { + unsigned int const rowsToTest = 100; + unsigned int ul; + for (ul = 0; ul != rowsToTest; ++ul) + { + sql << "insert into soci_test(ul) values(" << ul << ")"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + CHECK(count == static_cast(rowsToTest)); + + { + unsigned int ul2 = 0; + + statement st = (sql.prepare << + "select ul from soci_test order by ul", into(ul)); + + st.execute(); + while (st.fetch()) + { + CHECK(ul == ul2); + ++ul2; + } + CHECK(ul2 == rowsToTest); + } + { + unsigned int ul2 = 0; + + std::vector vec(8); + statement st = (sql.prepare << + "select ul from soci_test order by ul", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != vec.size(); ++i) + { + CHECK(ul2 == vec[i]); + ++ul2; + } + + vec.resize(8); + } + CHECK(ul2 == rowsToTest); + } + } + + SECTION("double") + { + int const rowsToTest = 100; + double d = 0.0; + + statement st = (sql.prepare << + "insert into soci_test(d) values(:d)", use(d)); + for (int i = 0; i != rowsToTest; ++i) + { + st.execute(true); + d += 0.6; + } + + int count; + sql << "select count(*) from soci_test", into(count); + CHECK(count == rowsToTest); + + { + double d2 = 0.0; + int i = 0; + + statement st = (sql.prepare << + "select d from soci_test order by d", into(d)); + + st.execute(); + while (st.fetch()) + { + ASSERT_EQUAL(d, d2); + d2 += 0.6; + ++i; + } + CHECK(i == rowsToTest); + } + { + double d2 = 0.0; + int i = 0; + + std::vector vec(8); + statement st = (sql.prepare << + "select d from soci_test order by d", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t j = 0; j != vec.size(); ++j) + { + ASSERT_EQUAL(d2, vec[j]); + d2 += 0.6; + ++i; + } + + vec.resize(8); + } + CHECK(i == rowsToTest); + } + } + + SECTION("std::tm") + { + int const rowsToTest = 8; + for (int i = 0; i != rowsToTest; ++i) + { + std::ostringstream ss; + ss << 2000 + i << "-0" << 1 + i << '-' << 20 - i << ' ' + << 15 + i << ':' << 50 - i << ':' << 40 + i; + + sql << "insert into soci_test(id, tm) values(" << i + << ", " << tc_.to_date_time(ss.str()) << ")"; + } + + int count; + sql << "select count(*) from soci_test", into(count); + CHECK(count == rowsToTest); + + { + std::tm t; + int i = 0; + + statement st = (sql.prepare << + "select tm from soci_test order by id", into(t)); + + st.execute(); + while (st.fetch()) + { + CHECK(t.tm_year == 2000 - 1900 + i); + CHECK(t.tm_mon == i); + CHECK(t.tm_mday == 20 - i); + CHECK(t.tm_hour == 15 + i); + CHECK(t.tm_min == 50 - i); + CHECK(t.tm_sec == 40 + i); + + ++i; + } + CHECK(i == rowsToTest); + } + { + int i = 0; + + std::vector vec(3); + statement st = (sql.prepare << + "select tm from soci_test order by id", into(vec)); + st.execute(); + while (st.fetch()) + { + for (std::size_t j = 0; j != vec.size(); ++j) + { + CHECK(vec[j].tm_year == 2000 - 1900 + i); + CHECK(vec[j].tm_mon == i); + CHECK(vec[j].tm_mday == 20 - i); + CHECK(vec[j].tm_hour == 15 + i); + CHECK(vec[j].tm_min == 50 - i); + CHECK(vec[j].tm_sec == 40 + i); + + ++i; + } + + vec.resize(3); + } + CHECK(i == rowsToTest); + } + } +} + +// test for indicators (repeated fetch and bulk) +TEST_CASE_METHOD(common_tests, "Indicators", "[core][indicator]") +{ + soci::session sql(backEndFactory_, connectString_); + + // 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)"; + + { + int val; + indicator ind; + + statement st = (sql.prepare << + "select val from soci_test order by id", into(val, ind)); + + st.execute(); + bool gotData = st.fetch(); + CHECK(gotData); + CHECK(ind == i_ok); + CHECK(val == 10); + gotData = st.fetch(); + CHECK(gotData); + CHECK(ind == i_ok); + CHECK(val == 11); + gotData = st.fetch(); + CHECK(gotData); + CHECK(ind == i_null); + gotData = st.fetch(); + CHECK(gotData); + CHECK(ind == i_null); + gotData = st.fetch(); + CHECK(gotData); + CHECK(ind == i_ok); + CHECK(val == 12); + gotData = st.fetch(); + CHECK(gotData == false); + } + { + std::vector vals(3); + std::vector inds(3); + + statement st = (sql.prepare << + "select val from soci_test order by id", into(vals, inds)); + + st.execute(); + bool gotData = st.fetch(); + CHECK(gotData); + CHECK(vals.size() == 3); + CHECK(inds.size() == 3); + CHECK(inds[0] == i_ok); + CHECK(vals[0] == 10); + CHECK(inds[1] == i_ok); + CHECK(vals[1] == 11); + CHECK(inds[2] == i_null); + gotData = st.fetch(); + CHECK(gotData); + CHECK(vals.size() == 2); + CHECK(inds[0] == i_null); + CHECK(inds[1] == i_ok); + CHECK(vals[1] == 12); + gotData = st.fetch(); + CHECK(gotData == false); + } + + // additional test for "no data" condition + { + std::vector vals(3); + std::vector inds(3); + + statement st = (sql.prepare << + "select val from soci_test where 0 = 1", into(vals, inds)); + + bool gotData = st.execute(true); + CHECK(gotData == false); + + // for convenience, vectors should be truncated + CHECK(vals.empty()); + CHECK(inds.empty()); + + // for even more convenience, fetch should not fail + // but just report end of rowset + // (and vectors should be truncated) + + vals.resize(1); + inds.resize(1); + + gotData = st.fetch(); + CHECK(gotData == false); + CHECK(vals.empty()); + CHECK(inds.empty()); + } + + // additional test for "no data" without prepared statement + { + std::vector vals(3); + std::vector inds(3); + + sql << "select val from soci_test where 0 = 1", + into(vals, inds); + + // vectors should be truncated + CHECK(vals.empty()); + CHECK(inds.empty()); + } + } + +} + +// test for different sizes of data vector and indicators vector +// (library should force ind. vector to have same size as data vector) +TEST_CASE_METHOD(common_tests, "Indicators vector", "[core][indicator][vector]") +{ + soci::session sql(backEndFactory_, connectString_); + + // 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)"; + + { + std::vector vals(4); + std::vector inds; + + statement st = (sql.prepare << + "select val from soci_test order by id", into(vals, inds)); + + st.execute(); + st.fetch(); + CHECK(vals.size() == 4); + CHECK(inds.size() == 4); + vals.resize(3); + st.fetch(); + CHECK(vals.size() == 1); + CHECK(inds.size() == 1); + } + } + +} + +// Note: this functionality is not available with older PostgreSQL +#ifndef SOCI_POSTGRESQL_NOPARAMS + +// "use" tests, type conversions, etc. +TEST_CASE_METHOD(common_tests, "Use type conversion", "[core][use]") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + SECTION("char") + { + char c('a'); + sql << "insert into soci_test(c) values(:c)", use(c); + + c = 'b'; + sql << "select c from soci_test", into(c); + CHECK(c == 'a'); + + } + + SECTION("std::string") + { + std::string s = "Hello SOCI!"; + sql << "insert into soci_test(str) values(:s)", use(s); + + std::string str; + sql << "select str from soci_test", into(str); + + CHECK(str == "Hello SOCI!"); + } + + SECTION("short") + { + short s = 123; + sql << "insert into soci_test(id) values(:id)", use(s); + + short s2 = 0; + sql << "select id from soci_test", into(s2); + + CHECK(s2 == 123); + } + + SECTION("int") + { + int i = -12345678; + sql << "insert into soci_test(id) values(:i)", use(i); + + int i2 = 0; + sql << "select id from soci_test", into(i2); + + CHECK(i2 == -12345678); + } + + SECTION("unsigned long") + { + unsigned long ul = 4000000000ul; + sql << "insert into soci_test(ul) values(:num)", use(ul); + + unsigned long ul2 = 0; + sql << "select ul from soci_test", into(ul2); + + CHECK(ul2 == 4000000000ul); + } + + SECTION("double") + { + double d = 3.14159265; + sql << "insert into soci_test(d) values(:d)", use(d); + + double d2 = 0; + sql << "select d from soci_test", into(d2); + + ASSERT_EQUAL(d2, d); + } + + SECTION("std::tm") + { + std::tm t; + t.tm_year = 105; + t.tm_mon = 10; + t.tm_mday = 19; + t.tm_hour = 21; + t.tm_min = 39; + t.tm_sec = 57; + sql << "insert into soci_test(tm) values(:t)", use(t); + + std::tm t2; + t2.tm_year = 0; + t2.tm_mon = 0; + t2.tm_mday = 0; + t2.tm_hour = 0; + t2.tm_min = 0; + t2.tm_sec = 0; + + sql << "select tm from soci_test", into(t2); + + CHECK(t.tm_year == 105); + CHECK(t.tm_mon == 10); + CHECK(t.tm_mday == 19); + CHECK(t.tm_hour == 21); + CHECK(t.tm_min == 39); + CHECK(t.tm_sec == 57); + } + + SECTION("repeated use") + { + int i; + statement st = (sql.prepare + << "insert into soci_test(id) values(:id)", use(i)); + + i = 5; + st.execute(true); + i = 6; + st.execute(true); + i = 7; + st.execute(true); + + std::vector v(5); + sql << "select id from soci_test order by id", into(v); + + CHECK(v.size() == 3); + CHECK(v[0] == 5); + CHECK(v[1] == 6); + CHECK(v[2] == 7); + } + + // tests for use of const objects + + SECTION("const char") + { + char const c('a'); + sql << "insert into soci_test(c) values(:c)", use(c); + + char c2 = 'b'; + sql << "select c from soci_test", into(c2); + CHECK(c2 == 'a'); + + } + + SECTION("const std::string") + { + std::string const s = "Hello const SOCI!"; + sql << "insert into soci_test(str) values(:s)", use(s); + + std::string str; + sql << "select str from soci_test", into(str); + + CHECK(str == "Hello const SOCI!"); + } + + SECTION("const short") + { + short const s = 123; + sql << "insert into soci_test(id) values(:id)", use(s); + + short s2 = 0; + sql << "select id from soci_test", into(s2); + + CHECK(s2 == 123); + } + + SECTION("const int") + { + int const i = -12345678; + sql << "insert into soci_test(id) values(:i)", use(i); + + int i2 = 0; + sql << "select id from soci_test", into(i2); + + CHECK(i2 == -12345678); + } + + SECTION("const unsigned long") + { + unsigned long const ul = 4000000000ul; + sql << "insert into soci_test(ul) values(:num)", use(ul); + + unsigned long ul2 = 0; + sql << "select ul from soci_test", into(ul2); + + CHECK(ul2 == 4000000000ul); + } + + SECTION("const double") + { + double const d = 3.14159265; + sql << "insert into soci_test(d) values(:d)", use(d); + + double d2 = 0; + sql << "select d from soci_test", into(d2); + + ASSERT_EQUAL(d2, d); + } + + SECTION("const std::tm") + { + std::tm t; + t.tm_year = 105; + t.tm_mon = 10; + t.tm_mday = 19; + t.tm_hour = 21; + t.tm_min = 39; + t.tm_sec = 57; + std::tm const & ct = t; + sql << "insert into soci_test(tm) values(:t)", use(ct); + + std::tm t2; + t2.tm_year = 0; + t2.tm_mon = 0; + t2.tm_mday = 0; + t2.tm_hour = 0; + t2.tm_min = 0; + t2.tm_sec = 0; + + sql << "select tm from soci_test", into(t2); + + CHECK(t.tm_year == 105); + CHECK(t.tm_mon == 10); + CHECK(t.tm_mday == 19); + CHECK(t.tm_hour == 21); + CHECK(t.tm_min == 39); + CHECK(t.tm_sec == 57); + } +} + +#endif // SOCI_POSTGRESQL_NOPARAMS + +// test for multiple use (and into) elements +TEST_CASE_METHOD(common_tests, "Multiple use and into", "[core][use][into]") +{ + soci::session sql(backEndFactory_, connectString_); + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + { + int i1 = 5; + int i2 = 6; + int i3 = 7; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + sql << "insert into soci_test(i1, i2, i3) values(:i1, :i2, :i3)", + use(i1), use(i2), use(i3); + +#else + // Older PostgreSQL does not support use elements. + + sql << "insert into soci_test(i1, i2, i3) values(5, 6, 7)"; + +#endif // SOCI_POSTGRESQL_NOPARAMS + + i1 = 0; + i2 = 0; + i3 = 0; + sql << "select i1, i2, i3 from soci_test", + into(i1), into(i2), into(i3); + + CHECK(i1 == 5); + CHECK(i2 == 6); + CHECK(i3 == 7); + + // same for vectors + sql << "delete from soci_test"; + + i1 = 0; + i2 = 0; + i3 = 0; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + statement st = (sql.prepare + << "insert into soci_test(i1, i2, i3) values(:i1, :i2, :i3)", + use(i1), use(i2), use(i3)); + + i1 = 1; + i2 = 2; + i3 = 3; + st.execute(true); + i1 = 4; + i2 = 5; + i3 = 6; + st.execute(true); + i1 = 7; + i2 = 8; + i3 = 9; + st.execute(true); + +#else + // Older PostgreSQL does not support use elements. + + sql << "insert into soci_test(i1, i2, i3) values(1, 2, 3)"; + sql << "insert into soci_test(i1, i2, i3) values(4, 5, 6)"; + sql << "insert into soci_test(i1, i2, i3) values(7, 8, 9)"; + +#endif // SOCI_POSTGRESQL_NOPARAMS + + std::vector v1(5); + std::vector v2(5); + std::vector v3(5); + + sql << "select i1, i2, i3 from soci_test order by i1", + into(v1), into(v2), into(v3); + + CHECK(v1.size() == 3); + CHECK(v2.size() == 3); + CHECK(v3.size() == 3); + CHECK(v1[0] == 1); + CHECK(v1[1] == 4); + CHECK(v1[2] == 7); + CHECK(v2[0] == 2); + CHECK(v2[1] == 5); + CHECK(v2[2] == 8); + CHECK(v3[0] == 3); + CHECK(v3[1] == 6); + CHECK(v3[2] == 9); + } +} + +// Not supported with older PostgreSQL +#ifndef SOCI_POSTGRESQL_NOPARAMS + +// use vector elements +TEST_CASE_METHOD(common_tests, "Use vector", "[core][use][vector]") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + SECTION("char") + { + std::vector v; + v.push_back('a'); + v.push_back('b'); + v.push_back('c'); + v.push_back('d'); + + sql << "insert into soci_test(c) values(:c)", use(v); + + std::vector v2(4); + + sql << "select c from soci_test order by c", into(v2); + CHECK(v2.size() == 4); + CHECK(v2[0] == 'a'); + CHECK(v2[1] == 'b'); + CHECK(v2[2] == 'c'); + CHECK(v2[3] == 'd'); + } + + SECTION("std::string") + { + std::vector v; + v.push_back("ala"); + v.push_back("ma"); + v.push_back("kota"); + + sql << "insert into soci_test(str) values(:s)", use(v); + + std::vector v2(4); + + sql << "select str from soci_test order by str", into(v2); + CHECK(v2.size() == 3); + CHECK(v2[0] == "ala"); + CHECK(v2[1] == "kota"); + CHECK(v2[2] == "ma"); + } + + SECTION("short") + { + std::vector v; + v.push_back(-5); + v.push_back(6); + v.push_back(7); + v.push_back(123); + + sql << "insert into soci_test(sh) values(:sh)", use(v); + + std::vector v2(4); + + sql << "select sh from soci_test order by sh", into(v2); + CHECK(v2.size() == 4); + CHECK(v2[0] == -5); + CHECK(v2[1] == 6); + CHECK(v2[2] == 7); + CHECK(v2[3] == 123); + } + + SECTION("int") + { + std::vector v; + v.push_back(-2000000000); + v.push_back(0); + v.push_back(1); + v.push_back(2000000000); + + sql << "insert into soci_test(id) values(:i)", use(v); + + std::vector v2(4); + + sql << "select id from soci_test order by id", into(v2); + CHECK(v2.size() == 4); + CHECK(v2[0] == -2000000000); + CHECK(v2[1] == 0); + CHECK(v2[2] == 1); + CHECK(v2[3] == 2000000000); + } + + SECTION("unsigned int") + { + std::vector v; + v.push_back(0); + v.push_back(1); + v.push_back(123); + v.push_back(1000); + + sql << "insert into soci_test(ul) values(:ul)", use(v); + + std::vector v2(4); + + sql << "select ul from soci_test order by ul", into(v2); + CHECK(v2.size() == 4); + CHECK(v2[0] == 0); + CHECK(v2[1] == 1); + CHECK(v2[2] == 123); + CHECK(v2[3] == 1000); + } + + SECTION("double") + { + std::vector v; + v.push_back(0); + v.push_back(-0.0001); + v.push_back(0.0001); + v.push_back(3.1415926); + + sql << "insert into soci_test(d) values(:d)", use(v); + + std::vector v2(4); + + sql << "select d from soci_test order by d", into(v2); + CHECK(v2.size() == 4); + ASSERT_EQUAL(v2[0],-0.0001); + ASSERT_EQUAL(v2[1], 0); + ASSERT_EQUAL(v2[2], 0.0001); + ASSERT_EQUAL(v2[3], 3.1415926); + } + + SECTION("std::tm") + { + std::vector v; + std::tm t; + t.tm_year = 105; + t.tm_mon = 10; + t.tm_mday = 26; + t.tm_hour = 22; + t.tm_min = 45; + t.tm_sec = 17; + + v.push_back(t); + + t.tm_sec = 37; + v.push_back(t); + + t.tm_mday = 25; + v.push_back(t); + + sql << "insert into soci_test(tm) values(:t)", use(v); + + std::vector v2(4); + + sql << "select tm from soci_test order by tm", into(v2); + CHECK(v2.size() == 3); + CHECK(v2[0].tm_year == 105); + CHECK(v2[0].tm_mon == 10); + CHECK(v2[0].tm_mday == 25); + CHECK(v2[0].tm_hour == 22); + CHECK(v2[0].tm_min == 45); + CHECK(v2[0].tm_sec == 37); + CHECK(v2[1].tm_year == 105); + CHECK(v2[1].tm_mon == 10); + CHECK(v2[1].tm_mday == 26); + CHECK(v2[1].tm_hour == 22); + CHECK(v2[1].tm_min == 45); + CHECK(v2[1].tm_sec == 17); + CHECK(v2[2].tm_year == 105); + CHECK(v2[2].tm_mon == 10); + CHECK(v2[2].tm_mday == 26); + CHECK(v2[2].tm_hour == 22); + CHECK(v2[2].tm_min == 45); + CHECK(v2[2].tm_sec == 37); + } + + SECTION("const int") + { + std::vector v; + v.push_back(-2000000000); + v.push_back(0); + v.push_back(1); + v.push_back(2000000000); + + std::vector const & cv = v; + + sql << "insert into soci_test(id) values(:i)", use(cv); + + std::vector v2(4); + + sql << "select id from soci_test order by id", into(v2); + CHECK(v2.size() == 4); + CHECK(v2[0] == -2000000000); + CHECK(v2[1] == 0); + CHECK(v2[2] == 1); + CHECK(v2[3] == 2000000000); + } +} + +// test for named binding +TEST_CASE_METHOD(common_tests, "Named parameters", "[core][use][named-params]") +{ + soci::session sql(backEndFactory_, connectString_); + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int i1 = 7; + int i2 = 8; + + // verify the exception is thrown if both by position + // and by name use elements are specified + try + { + sql << "insert into soci_test(i1, i2) values(:i1, :i2)", + use(i1, "i1"), use(i2); + + FAIL("expected exception not thrown"); + } + catch (soci_error const& e) + { + CHECK(e.get_error_message() == + "Binding for use elements must be either by position " + "or by name."); + } + + // normal test + sql << "insert into soci_test(i1, i2) values(:i1, :i2)", + use(i1, "i1"), use(i2, "i2"); + + i1 = 0; + i2 = 0; + sql << "select i1, i2 from soci_test", into(i1), into(i2); + CHECK(i1 == 7); + CHECK(i2 == 8); + + i2 = 0; + sql << "select i2 from soci_test where i1 = :i1", into(i2), use(i1); + CHECK(i2 == 8); + + sql << "delete from soci_test"; + + // test vectors + + std::vector v1; + v1.push_back(1); + v1.push_back(2); + v1.push_back(3); + + std::vector v2; + v2.push_back(4); + v2.push_back(5); + v2.push_back(6); + + sql << "insert into soci_test(i1, i2) values(:i1, :i2)", + use(v1, "i1"), use(v2, "i2"); + + sql << "select i2, i1 from soci_test order by i1 desc", + into(v1), into(v2); + CHECK(v1.size() == 3); + CHECK(v2.size() == 3); + CHECK(v1[0] == 6); + CHECK(v1[1] == 5); + CHECK(v1[2] == 4); + CHECK(v2[0] == 3); + CHECK(v2[1] == 2); + CHECK(v2[2] == 1); + } +} + +#endif // SOCI_POSTGRESQL_NOPARAMS + +// transaction test +TEST_CASE_METHOD(common_tests, "Transactions", "[core][transaction]") +{ + soci::session sql(backEndFactory_, connectString_); + + if (!tc_.has_transactions_support(sql)) + { + WARN("Transactions not supported by the database, skipping the test."); + return; + } + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + int count; + sql << "select count(*) from soci_test", into(count); + CHECK(count == 0); + + { + transaction tr(sql); + + sql << "insert into soci_test (id, name) values(1, 'John')"; + sql << "insert into soci_test (id, name) values(2, 'Anna')"; + sql << "insert into soci_test (id, name) values(3, 'Mike')"; + + tr.commit(); + } + { + transaction tr(sql); + + sql << "select count(*) from soci_test", into(count); + CHECK(count == 3); + + sql << "insert into soci_test (id, name) values(4, 'Stan')"; + + sql << "select count(*) from soci_test", into(count); + CHECK(count == 4); + + tr.rollback(); + + sql << "select count(*) from soci_test", into(count); + CHECK(count == 3); + } + { + transaction tr(sql); + + sql << "delete from soci_test"; + + sql << "select count(*) from soci_test", into(count); + CHECK(count == 0); + + tr.rollback(); + + sql << "select count(*) from soci_test", into(count); + CHECK(count == 3); + } + { + // additional test for detection of double commit + transaction tr(sql); + tr.commit(); + try + { + tr.commit(); + FAIL("expected exception not thrown"); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "The transaction object cannot be handled twice."); + } + } +} + +#ifndef SOCI_POSTGRESQL_NOPARAMS + +// test of use elements with indicators +TEST_CASE_METHOD(common_tests, "Use with indicators", "[core][use][indicator]") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + indicator ind1 = i_ok; + indicator ind2 = i_ok; + + int id = 1; + int val = 10; + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(id, ind1), use(val, ind2); + + id = 2; + val = 11; + ind2 = i_null; + sql << "insert into soci_test(id, val) values(:id, :val)", + use(id, ind1), use(val, ind2); + + 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); + CHECK(ind2 == i_null); + + std::vector ids; + ids.push_back(3); + ids.push_back(4); + ids.push_back(5); + std::vector vals; + vals.push_back(12); + vals.push_back(13); + vals.push_back(14); + std::vector inds; + inds.push_back(i_ok); + inds.push_back(i_null); + inds.push_back(i_ok); + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(ids), use(vals, inds); + + ids.resize(5); + vals.resize(5); + sql << "select id, val from soci_test order by id desc", + into(ids), into(vals, inds); + + CHECK(ids.size() == 5); + CHECK(ids[0] == 5); + CHECK(ids[1] == 4); + CHECK(ids[2] == 3); + CHECK(ids[3] == 2); + CHECK(ids[4] == 1); + CHECK(inds.size() == 5); + CHECK(inds[0] == i_ok); + CHECK(inds[1] == i_null); + CHECK(inds[2] == i_ok); + CHECK(inds[3] == i_null); + CHECK(inds[4] == i_ok); + CHECK(vals.size() == 5); + CHECK(vals[0] == 14); + CHECK(vals[2] == 12); + CHECK(vals[4] == 10); +} + +#endif // SOCI_POSTGRESQL_NOPARAMS + +// Dynamic binding to Row objects +TEST_CASE_METHOD(common_tests, "Dynamic row binding", "[core][dynamic]") +{ + soci::session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + auto_table_creator tableCreator(tc_.table_creator_2(sql)); + + row r; + sql << "select * from soci_test", into(r); + CHECK(sql.got_data() == false); + + sql << "insert into soci_test" + " values(3.14, 123, \'Johny\'," + << tc_.to_date_time("2005-12-19 22:14:17") + << ", 'a')"; + + // select into a row + { + row r; + statement st = (sql.prepare << + "select * from soci_test", into(r)); + st.execute(true); + CHECK(r.size() == 5); + + CHECK(r.get_properties(0).get_data_type() == dt_double); + CHECK(r.get_properties(1).get_data_type() == dt_integer); + CHECK(r.get_properties(2).get_data_type() == dt_string); + CHECK(r.get_properties(3).get_data_type() == dt_date); + + // type char is visible as string + // - to comply with the implementation for Oracle + CHECK(r.get_properties(4).get_data_type() == dt_string); + + CHECK(r.get_properties("NUM_INT").get_data_type() == dt_integer); + + CHECK(r.get_properties(0).get_name() == "NUM_FLOAT"); + CHECK(r.get_properties(1).get_name() == "NUM_INT"); + CHECK(r.get_properties(2).get_name() == "NAME"); + CHECK(r.get_properties(3).get_name() == "SOMETIME"); + CHECK(r.get_properties(4).get_name() == "CHR"); + + 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); + + // again, type char is visible as string + CHECK_EQUAL_PADDED(r.get(4), "a"); + + ASSERT_EQUAL_APPROX(r.get("NUM_FLOAT"), 3.14); + CHECK(r.get("NUM_INT") == 123); + CHECK(r.get("NAME") == "Johny"); + CHECK_EQUAL_PADDED(r.get("CHR"), "a"); + + CHECK(r.get_indicator(0) == i_ok); + + // verify exception thrown on invalid get<> + bool caught = false; + try + { + r.get(0); + } + catch (std::bad_cast const &) + { + caught = true; + } + CHECK(caught); + + // additional test for stream-like extraction + { + double d; + int i; + std::string s; + std::tm t; + std::string c; + + r >> d >> i >> s >> t >> c; + + ASSERT_EQUAL_APPROX(d, 3.14); + CHECK(i == 123); + CHECK(s == "Johny"); + CHECK(t.tm_year == 105); + CHECK(t.tm_mon == 11); + CHECK(t.tm_mday == 19); + CHECK(t.tm_hour == 22); + CHECK(t.tm_min == 14); + CHECK(t.tm_sec == 17); + CHECK_EQUAL_PADDED(c, "a"); + } + } + + // 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); + + CHECK(r.get_properties(0).get_data_type() == dt_double); + CHECK(r.get_properties(1).get_data_type() == dt_integer); + CHECK(r.get_properties(2).get_data_type() == dt_string); + CHECK(r.get_properties(3).get_data_type() == dt_date); + + sql << "select name, num_int from soci_test", into(r); + + CHECK(r.size() == 2); + + CHECK(r.get_properties(0).get_data_type() == dt_string); + CHECK(r.get_properties(1).get_data_type() == dt_integer); + } +} + +// more dynamic bindings +TEST_CASE_METHOD(common_tests, "Dynamic row binding 2", "[core][dynamic]") +{ + soci::session sql(backEndFactory_, connectString_); + + 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, 20)"; + sql << "insert into soci_test(id, val) values(3, 30)"; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + { + int id = 2; + row r; + sql << "select val from soci_test where id = :id", use(id), into(r); + + CHECK(r.size() == 1); + CHECK(r.get_properties(0).get_data_type() == dt_integer); + CHECK(r.get(0) == 20); + } + { + int id; + row r; + statement st = (sql.prepare << + "select val from soci_test where id = :id", use(id), into(r)); + + id = 2; + st.execute(true); + CHECK(r.size() == 1); + CHECK(r.get_properties(0).get_data_type() == dt_integer); + CHECK(r.get(0) == 20); + + id = 3; + st.execute(true); + CHECK(r.size() == 1); + CHECK(r.get_properties(0).get_data_type() == dt_integer); + CHECK(r.get(0) == 30); + + id = 1; + st.execute(true); + CHECK(r.size() == 1); + CHECK(r.get_properties(0).get_data_type() == dt_integer); + CHECK(r.get(0) == 10); + } +#else + { + row r; + sql << "select val from soci_test where id = 2", into(r); + + CHECK(r.size() == 1); + CHECK(r.get_properties(0).get_data_type() == dt_integer); + CHECK(r.get(0) == 20); + } +#endif // SOCI_POSTGRESQL_NOPARAMS +} + +// More Dynamic binding to row objects +TEST_CASE_METHOD(common_tests, "Dynamic row binding 3", "[core][dynamic]") +{ + soci::session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + row r1; + sql << "select * from soci_test", into(r1); + CHECK(sql.got_data() == false); + + sql << "insert into soci_test values('david', '(404)123-4567')"; + sql << "insert into soci_test values('john', '(404)123-4567')"; + sql << "insert into soci_test values('doe', '(404)123-4567')"; + + row r2; + statement st = (sql.prepare << "select * from soci_test", into(r2)); + st.execute(); + + CHECK(r2.size() == 2); + + int count = 0; + while (st.fetch()) + { + ++count; + CHECK(r2.get("PHONE") == "(404)123-4567"); + } + CHECK(count == 3); +} + +// This is like the previous test but with a type_conversion instead of a row +TEST_CASE_METHOD(common_tests, "Dynamic binding with type conversions", "[core][dynamic][type_conversion]") +{ + soci::session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + SECTION("simple conversions") + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + SECTION("between single basic type and user type") + { + MyInt mi; + mi.set(123); + sql << "insert into soci_test(id) values(:id)", use(mi); + + int i; + sql << "select id from soci_test", into(i); + CHECK(i == 123); + + sql << "update soci_test set id = id + 1"; + + sql << "select id from soci_test", into(mi); + CHECK(mi.get() == 124); + } + + SECTION("with use const") + { + MyInt mi; + mi.set(123); + + MyInt const & cmi = mi; + sql << "insert into soci_test(id) values(:id)", use(cmi); + + int i; + sql << "select id from soci_test", into(i); + CHECK(i == 123); + } + } + + SECTION("ORM conversions") + { + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + SECTION("conversions based on values") + { + PhonebookEntry p1; + sql << "select * from soci_test", into(p1); + CHECK(p1.name == ""); + CHECK(p1.phone == ""); + + p1.name = "david"; + + // Note: uppercase column names are used here (and later on) + // for consistency with how they can be read from database + // (which means forced to uppercase on Oracle) and how they are + // set/get in the type conversion routines for PhonebookEntry. + // In short, IF the database is Oracle, + // then all column names for binding should be uppercase. + sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); + sql << "insert into soci_test values('john', '(404)123-4567')"; + sql << "insert into soci_test values('doe', '(404)123-4567')"; + + PhonebookEntry p2; + statement st = (sql.prepare << "select * from soci_test", into(p2)); + st.execute(); + + int count = 0; + while (st.fetch()) + { + ++count; + if (p2.name == "david") + { + // see type_conversion + CHECK(p2.phone ==""); + } + else + { + CHECK(p2.phone == "(404)123-4567"); + } + } + CHECK(count == 3); + } + + SECTION("conversions based on values with use const") + { + PhonebookEntry p1; + p1.name = "Joe Coder"; + p1.phone = "123-456"; + + PhonebookEntry const & cp1 = p1; + + sql << "insert into soci_test values(:NAME, :PHONE)", use(cp1); + + PhonebookEntry p2; + sql << "select * from soci_test", into(p2); + CHECK(sql.got_data()); + + CHECK(p2.name == "Joe Coder"); + CHECK(p2.phone == "123-456"); + } + + SECTION("conversions based on accessor functions (as opposed to direct variable bindings)") + { + PhonebookEntry3 p1; + p1.setName("Joe Hacker"); + p1.setPhone("10010110"); + + sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); + + PhonebookEntry3 p2; + sql << "select * from soci_test", into(p2); + CHECK(sql.got_data()); + + CHECK(p2.getName() == "Joe Hacker"); + CHECK(p2.getPhone() == "10010110"); + } + + SECTION("PhonebookEntry2 type conversion to test calls to values::get_indicator()") + { + PhonebookEntry2 p1; + sql << "select * from soci_test", into(p1); + CHECK(p1.name == ""); + CHECK(p1.phone == ""); + p1.name = "david"; + + sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); + sql << "insert into soci_test values('john', '(404)123-4567')"; + sql << "insert into soci_test values('doe', '(404)123-4567')"; + + PhonebookEntry2 p2; + statement st = (sql.prepare << "select * from soci_test", into(p2)); + st.execute(); + + int count = 0; + while (st.fetch()) + { + ++count; + if (p2.name == "david") + { + // see type_conversion + CHECK(p2.phone ==""); + } + else + { + CHECK(p2.phone == "(404)123-4567"); + } + } + CHECK(count == 3); + } + } +} + +TEST_CASE_METHOD(common_tests, "Prepared insert with ORM", "[core][orm]") +{ + soci::session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry temp; + PhonebookEntry e1 = { "name1", "phone1" }; + PhonebookEntry e2 = { "name2", "phone2" }; + + //sql << "insert into soci_test values (:NAME, :PHONE)", use(temp); + statement insertStatement = (sql.prepare << "insert into soci_test values (:NAME, :PHONE)", use(temp)); + + temp = e1; + insertStatement.execute(true); + temp = e2; + insertStatement.execute(true); + + int count = 0; + + sql << "select count(*) from soci_test where NAME in ('name1', 'name2')", into(count); + + CHECK(count == 2); +} + +TEST_CASE_METHOD(common_tests, "Partial match with ORM", "[core][orm]") +{ + soci::session sql(backEndFactory_, connectString_); + sql.uppercase_column_names(true); + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry in = { "name1", "phone1" }; + std::string name = "nameA"; + sql << "insert into soci_test values (:NAMED, :PHONE)", use(in), use(name, "NAMED"); + + PhonebookEntry out; + sql << "select * from soci_test where PHONE = 'phone1'", into(out); + CHECK(out.name == "nameA"); + CHECK(out.phone == "phone1"); +} + +TEST_CASE_METHOD(common_tests, "Numeric round trip", "[core][float]") +{ + soci::session sql(backEndFactory_, connectString_); + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + double d1 = 0.003958, + d2; + + sql << "insert into soci_test(num76) values (:d1)", use(d1); + sql << "select num76 from soci_test", into(d2); + + // The numeric value should make the round trip unchanged, we really want + // to use exact comparisons here. + ASSERT_EQUAL_EXACT(d1, d2); + + // test negative doubles too + sql << "delete from soci_test"; + d1 = -d1; + + sql << "insert into soci_test(num76) values (:d1)", use(d1); + sql << "select num76 from soci_test", into(d2); + + ASSERT_EQUAL_EXACT(d1, d2); +} + +#ifndef SOCI_POSTGRESQL_NOPARAMS + +// test for bulk fetch with single use +TEST_CASE_METHOD(common_tests, "Bulk fetch with single use", "[core][bulk]") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + sql << "insert into soci_test(name, id) values('john', 1)"; + sql << "insert into soci_test(name, id) values('george', 2)"; + sql << "insert into soci_test(name, id) values('anthony', 1)"; + sql << "insert into soci_test(name, id) values('marc', 3)"; + sql << "insert into soci_test(name, id) values('julian', 1)"; + + int code = 1; + std::vector names(10); + sql << "select name from soci_test where id = :id order by name", + into(names), use(code); + + CHECK(names.size() == 3); + CHECK(names[0] == "anthony"); + CHECK(names[1] == "john"); + CHECK(names[2] == "julian"); +} + +#endif // SOCI_POSTGRESQL_NOPARAMS + +// test for basic logging support +TEST_CASE_METHOD(common_tests, "Basic logging support", "[core][logging]") +{ + soci::session sql(backEndFactory_, connectString_); + + std::ostringstream log; + sql.set_log_stream(&log); + + try + { + sql << "drop table soci_test1"; + } + catch (...) {} + + CHECK(sql.get_last_query() == "drop table soci_test1"); + + sql.set_log_stream(NULL); + + try + { + sql << "drop table soci_test2"; + } + catch (...) {} + + CHECK(sql.get_last_query() == "drop table soci_test2"); + + sql.set_log_stream(&log); + + try + { + sql << "drop table soci_test3"; + } + catch (...) {} + + CHECK(sql.get_last_query() == "drop table soci_test3"); + CHECK(log.str() == + "drop table soci_test1\n" + "drop table soci_test3\n"); + +} + +// test for rowset creation and copying +TEST_CASE_METHOD(common_tests, "Rowset creation and copying", "[core][rowset]") +{ + soci::session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + // Open empty rowset + rowset rs1 = (sql.prepare << "select * from soci_test"); + CHECK(rs1.begin() == rs1.end()); + } + + { + // Copy construction + rowset rs1 = (sql.prepare << "select * from soci_test"); + rowset rs2(rs1); + rowset rs3(rs1); + rowset rs4(rs3); + + CHECK(rs1.begin() == rs2.begin()); + CHECK(rs1.begin() == rs3.begin()); + CHECK(rs1.end() == rs2.end()); + CHECK(rs1.end() == rs3.end()); + } + + if (!tc_.has_multiple_select_bug()) + { + // Assignment + rowset rs1 = (sql.prepare << "select * from soci_test"); + rowset rs2 = (sql.prepare << "select * from soci_test"); + rowset rs3 = (sql.prepare << "select * from soci_test"); + rs1 = rs2; + rs3 = rs2; + + CHECK(rs1.begin() == rs2.begin()); + CHECK(rs1.begin() == rs3.begin()); + CHECK(rs1.end() == rs2.end()); + CHECK(rs1.end() == rs3.end()); + } +} + +// test for simple iterating using rowset iterator (without reading data) +TEST_CASE_METHOD(common_tests, "Rowset iteration", "[core][rowset]") +{ + soci::session sql(backEndFactory_, connectString_); + + // 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)"; + { + rowset rs = (sql.prepare << "select * from soci_test"); + + CHECK(5 == std::distance(rs.begin(), rs.end())); + } + } + +} + +// test for reading rowset using iterator +TEST_CASE_METHOD(common_tests, "Reading rows from rowset", "[core][row][rowset]") +{ + soci::session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_2(sql)); + { + { + // Empty rowset + rowset rs = (sql.prepare << "select * from soci_test"); + CHECK(0 == std::distance(rs.begin(), rs.end())); + } + + { + // Non-empty rowset + sql << "insert into soci_test values(3.14, 123, \'Johny\'," + << tc_.to_date_time("2005-12-19 22:14:17") + << ", 'a')"; + sql << "insert into soci_test values(6.28, 246, \'Robert\'," + << tc_.to_date_time("2004-10-01 18:44:10") + << ", 'b')"; + + rowset rs = (sql.prepare << "select * from soci_test"); + + rowset::const_iterator it = rs.begin(); + CHECK(it != rs.end()); + + // + // First row + // + row const & r1 = (*it); + + // Properties + CHECK(r1.size() == 5); + CHECK(r1.get_properties(0).get_data_type() == dt_double); + CHECK(r1.get_properties(1).get_data_type() == dt_integer); + CHECK(r1.get_properties(2).get_data_type() == dt_string); + CHECK(r1.get_properties(3).get_data_type() == dt_date); + CHECK(r1.get_properties(4).get_data_type() == dt_string); + CHECK(r1.get_properties("NUM_INT").get_data_type() == dt_integer); + + // Data + + // Since we didn't specify order by in the above query, + // the 2 rows may be returned in either order + // (If we specify order by, we can't do it in a cross db + // compatible way, because the Oracle table for this has been + // created with lower case column names) + + std::string name = r1.get(2); + + if (name == "Johny") + { + ASSERT_EQUAL_APPROX(r1.get(0), 3.14); + CHECK(r1.get(1) == 123); + CHECK(r1.get(2) == "Johny"); + std::tm t1 = { 0 }; + t1 = r1.get(3); + CHECK(t1.tm_year == 105); + CHECK_EQUAL_PADDED(r1.get(4), "a"); + ASSERT_EQUAL_APPROX(r1.get("NUM_FLOAT"), 3.14); + CHECK(r1.get("NUM_INT") == 123); + CHECK(r1.get("NAME") == "Johny"); + CHECK_EQUAL_PADDED(r1.get("CHR"), "a"); + } + else if (name == "Robert") + { + ASSERT_EQUAL(r1.get(0), 6.28); + CHECK(r1.get(1) == 246); + CHECK(r1.get(2) == "Robert"); + std::tm t1 = r1.get(3); + CHECK(t1.tm_year == 104); + CHECK(r1.get(4) == "b"); + ASSERT_EQUAL(r1.get("NUM_FLOAT"), 6.28); + CHECK(r1.get("NUM_INT") == 246); + CHECK(r1.get("NAME") == "Robert"); + CHECK_EQUAL_PADDED(r1.get("CHR"), "b"); + } + else + { + CAPTURE(name); + FAIL("expected \"Johny\" or \"Robert\""); + } + + // + // Iterate to second row + // + ++it; + CHECK(it != rs.end()); + + // + // Second row + // + row const & r2 = (*it); + + // Properties + CHECK(r2.size() == 5); + CHECK(r2.get_properties(0).get_data_type() == dt_double); + CHECK(r2.get_properties(1).get_data_type() == dt_integer); + CHECK(r2.get_properties(2).get_data_type() == dt_string); + CHECK(r2.get_properties(3).get_data_type() == dt_date); + CHECK(r2.get_properties(4).get_data_type() == dt_string); + CHECK(r2.get_properties("NUM_INT").get_data_type() == dt_integer); + + std::string newName = r2.get(2); + CHECK(name != newName); + + if (newName == "Johny") + { + ASSERT_EQUAL_APPROX(r2.get(0), 3.14); + CHECK(r2.get(1) == 123); + CHECK(r2.get(2) == "Johny"); + std::tm t2 = r2.get(3); + CHECK(t2.tm_year == 105); + CHECK(r2.get(4) == "a"); + ASSERT_EQUAL_APPROX(r2.get("NUM_FLOAT"), 3.14); + CHECK(r2.get("NUM_INT") == 123); + CHECK(r2.get("NAME") == "Johny"); + CHECK(r2.get("CHR") == "a"); + } + else if (newName == "Robert") + { + ASSERT_EQUAL_APPROX(r2.get(0), 6.28); + CHECK(r2.get(1) == 246); + CHECK(r2.get(2) == "Robert"); + std::tm t2 = r2.get(3); + CHECK(t2.tm_year == 104); + CHECK_EQUAL_PADDED(r2.get(4), "b"); + ASSERT_EQUAL_APPROX(r2.get("NUM_FLOAT"), 6.28); + CHECK(r2.get("NUM_INT") == 246); + CHECK(r2.get("NAME") == "Robert"); + CHECK_EQUAL_PADDED(r2.get("CHR"), "b"); + } + else + { + CAPTURE(newName); + FAIL("expected \"Johny\" or \"Robert\""); + } + } + + { + // Non-empty rowset with NULL values + sql << "insert into soci_test " + << "(num_int, num_float , name, sometime, chr) " + << "values (0, NULL, NULL, NULL, NULL)"; + + rowset rs = (sql.prepare + << "select num_int, num_float, name, sometime, chr " + << "from soci_test where num_int = 0"); + + rowset::const_iterator it = rs.begin(); + CHECK(it != rs.end()); + + // + // First row + // + row const& r1 = (*it); + + // Properties + CHECK(r1.size() == 5); + CHECK(r1.get_properties(0).get_data_type() == dt_integer); + CHECK(r1.get_properties(1).get_data_type() == dt_double); + CHECK(r1.get_properties(2).get_data_type() == dt_string); + CHECK(r1.get_properties(3).get_data_type() == dt_date); + CHECK(r1.get_properties(4).get_data_type() == dt_string); + + // Data + CHECK(r1.get_indicator(0) == soci::i_ok); + CHECK(r1.get(0) == 0); + CHECK(r1.get_indicator(1) == soci::i_null); + CHECK(r1.get_indicator(2) == soci::i_null); + CHECK(r1.get_indicator(3) == soci::i_null); + CHECK(r1.get_indicator(4) == soci::i_null); + } + } +} + +// test for reading rowset using iterator +TEST_CASE_METHOD(common_tests, "Reading ints from rowset", "[core][rowset]") +{ + soci::session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(id) values(1)"; + sql << "insert into soci_test(id) values(2)"; + sql << "insert into soci_test(id) values(3)"; + sql << "insert into soci_test(id) values(4)"; + sql << "insert into soci_test(id) values(5)"; + { + rowset rs = (sql.prepare << "select id from soci_test order by id asc"); + + // 1st row + rowset::const_iterator pos = rs.begin(); + CHECK(1 == (*pos)); + + // 3rd row + std::advance(pos, 2); + CHECK(3 == (*pos)); + + // 5th row + std::advance(pos, 2); + CHECK(5 == (*pos)); + + // The End + ++pos; + CHECK(pos == rs.end()); + } + } + +} + +// test for handling 'use' and reading rowset using iterator +TEST_CASE_METHOD(common_tests, "Reading strings from rowset", "[core][rowset]") +{ + soci::session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(str) values('abc')"; + sql << "insert into soci_test(str) values('def')"; + sql << "insert into soci_test(str) values('ghi')"; + sql << "insert into soci_test(str) values('jkl')"; + { + // Expected result in numbers + std::string idle("def"); + rowset rs1 = (sql.prepare + << "select str from soci_test where str = :idle", + use(idle)); + + CHECK(1 == std::distance(rs1.begin(), rs1.end())); + + // Expected result in value + idle = "jkl"; + rowset rs2 = (sql.prepare + << "select str from soci_test where str = :idle", + use(idle)); + + CHECK(idle == *(rs2.begin())); + } + } + +} + +// test for handling troublemaker +TEST_CASE_METHOD(common_tests, "Rowset expected exception", "[core][exception][rowset]") +{ + soci::session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + sql << "insert into soci_test(str) values('abc')"; + + CHECK_THROWS_AS( + std::string troublemaker; + rowset rs1 = (sql.prepare << "select str from soci_test", + into(troublemaker)), + soci_error + ); +} + +// 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]") +{ + soci::session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + sql << "insert into soci_test(val) values(1)"; + sql << "insert into soci_test(val) values(2)"; + sql << "insert into soci_test(val) values(NULL)"; + 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; +} + +// This is like the first dynamic binding test but with rowset and iterators use +TEST_CASE_METHOD(common_tests, "Dynamic binding with rowset", "[core][dynamic][type_conversion]") +{ + soci::session sql(backEndFactory_, connectString_); + + sql.uppercase_column_names(true); + + { + auto_table_creator tableCreator(tc_.table_creator_3(sql)); + + PhonebookEntry p1; + sql << "select * from soci_test", into(p1); + CHECK(p1.name == ""); + CHECK(p1.phone == ""); + + p1.name = "david"; + + sql << "insert into soci_test values(:NAME, :PHONE)", use(p1); + sql << "insert into soci_test values('john', '(404)123-4567')"; + sql << "insert into soci_test values('doe', '(404)123-4567')"; + + rowset rs = (sql.prepare << "select * from soci_test"); + + int count = 0; + for (rowset::const_iterator it = rs.begin(); it != rs.end(); ++it) + { + ++count; + PhonebookEntry const& p2 = (*it); + if (p2.name == "david") + { + // see type_conversion + CHECK(p2.phone ==""); + } + else + { + CHECK(p2.phone == "(404)123-4567"); + } + } + + CHECK(3 == count); + } +} + +#ifdef HAVE_BOOST + +// test for handling NULL values with boost::optional +// (both into and use) +TEST_CASE_METHOD(common_tests, "NULL with optional", "[core][boost][null]") +{ + + soci::session sql(backEndFactory_, connectString_); + + // create and populate the test table + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + { + sql << "insert into soci_test(val) values(7)"; + + { + // verify non-null value is fetched correctly + boost::optional opt; + sql << "select val from soci_test", into(opt); + CHECK(opt.is_initialized()); + CHECK(opt.get() == 7); + + // indicators can be used with optional + // (although that's just a consequence of implementation, + // not an intended feature - but let's test it anyway) + indicator ind; + opt.reset(); + sql << "select val from soci_test", into(opt, ind); + CHECK(opt.is_initialized()); + CHECK(opt.get() == 7); + CHECK(ind == i_ok); + + // verify null value is fetched correctly + sql << "select i1 from soci_test", into(opt); + CHECK(opt.is_initialized() == false); + + // and with indicator + opt = 5; + sql << "select i1 from soci_test", into(opt, ind); + CHECK(opt.is_initialized() == false); + CHECK(ind == i_null); + + // verify non-null is inserted correctly + opt = 3; + sql << "update soci_test set val = :v", use(opt); + int j = 0; + sql << "select val from soci_test", into(j); + CHECK(j == 3); + + // verify null is inserted correctly + opt.reset(); + sql << "update soci_test set val = :v", use(opt); + ind = i_ok; + sql << "select val from soci_test", into(j, ind); + CHECK(ind == i_null); + } + + // vector tests (select) + + { + sql << "delete from soci_test"; + + // simple readout of non-null data + + sql << "insert into soci_test(id, val, str) values(1, 5, \'abc\')"; + sql << "insert into soci_test(id, val, str) values(2, 6, \'def\')"; + sql << "insert into soci_test(id, val, str) values(3, 7, \'ghi\')"; + sql << "insert into soci_test(id, val, str) values(4, 8, null)"; + sql << "insert into soci_test(id, val, str) values(5, 9, \'mno\')"; + + std::vector > v(10); + sql << "select val from soci_test order by val", into(v); + + CHECK(v.size() == 5); + CHECK(v[0].is_initialized()); + CHECK(v[0].get() == 5); + CHECK(v[1].is_initialized()); + CHECK(v[1].get() == 6); + CHECK(v[2].is_initialized()); + CHECK(v[2].get() == 7); + CHECK(v[3].is_initialized()); + CHECK(v[3].get() == 8); + CHECK(v[4].is_initialized()); + CHECK(v[4].get() == 9); + + // readout of nulls + + sql << "update soci_test set val = null where id = 2 or id = 4"; + + std::vector ids(5); + sql << "select id, val from soci_test order by id", into(ids), into(v); + + CHECK(v.size() == 5); + CHECK(ids.size() == 5); + CHECK(v[0].is_initialized()); + CHECK(v[0].get() == 5); + CHECK(v[1].is_initialized() == false); + CHECK(v[2].is_initialized()); + CHECK(v[2].get() == 7); + CHECK(v[3].is_initialized() == false); + CHECK(v[4].is_initialized()); + CHECK(v[4].get() == 9); + + // readout with statement preparation + + int id = 1; + + ids.resize(3); + v.resize(3); + statement st = (sql.prepare << + "select id, val from soci_test order by id", into(ids), into(v)); + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != v.size(); ++i) + { + CHECK(id == ids[i]); + + if (id == 2 || id == 4) + { + CHECK(v[i].is_initialized() == false); + } + else + { + CHECK(v[i].is_initialized()); + CHECK(v[i].get() == id + 4); + } + + ++id; + } + + ids.resize(3); + v.resize(3); + } + CHECK(id == 6); + } + + // and why not stress iterators and the dynamic binding, too! + + { + rowset rs = (sql.prepare << "select id, val, str from soci_test order by id"); + + rowset::const_iterator it = rs.begin(); + CHECK(it != rs.end()); + + row const& r1 = (*it); + + CHECK(r1.size() == 3); + + // Note: for the reason of differences between number(x,y) type and + // binary representation of integers, the following commented assertions + // do not work for Oracle. + // The problem is that for this single table the data type used in Oracle + // table creator for the id column is number(10,0), + // which allows to insert all int values. + // On the other hand, the column description scheme used in the Oracle + // backend figures out that the natural type for such a column + // is eUnsignedInt - this makes the following assertions fail. + // Other database backends (like PostgreSQL) use other types like int + // and this not only allows to insert all int values (obviously), + // but is also recognized as int (obviously). + // There is a similar problem with stream-like extraction, + // where internally get is called and the type mismatch is detected + // for the id column - that's why the code below skips this column + // and tests the remaining column only. + + //CHECK(r1.get_properties(0).get_data_type() == dt_integer); + CHECK(r1.get_properties(1).get_data_type() == dt_integer); + CHECK(r1.get_properties(2).get_data_type() == dt_string); + //CHECK(r1.get(0) == 1); + CHECK(r1.get(1) == 5); + CHECK(r1.get(2) == "abc"); + CHECK(r1.get >(1).is_initialized()); + CHECK(r1.get >(1).get() == 5); + CHECK(r1.get >(2).is_initialized()); + CHECK(r1.get >(2).get() == "abc"); + + ++it; + + row const& r2 = (*it); + + CHECK(r2.size() == 3); + + // CHECK(r2.get_properties(0).get_data_type() == dt_integer); + CHECK(r2.get_properties(1).get_data_type() == dt_integer); + CHECK(r2.get_properties(2).get_data_type() == dt_string); + //CHECK(r2.get(0) == 2); + try + { + // expect exception here, this is NULL value + (void)r1.get(1); + FAIL("expected exception not thrown"); + } + catch (soci_error const &) {} + + // but we can read it as optional + CHECK(r2.get >(1).is_initialized() == false); + + // stream-like data extraction + + ++it; + row const &r3 = (*it); + + boost::optional io; + boost::optional so; + + r3.skip(); // move to val and str columns + r3 >> io >> so; + + CHECK(io.is_initialized()); + CHECK(io.get() == 7); + CHECK(so.is_initialized()); + CHECK(so.get() == "ghi"); + + ++it; + row const &r4 = (*it); + + r3.skip(); // move to val and str columns + r4 >> io >> so; + + CHECK(io.is_initialized() == false); + CHECK(so.is_initialized() == false); + } + + // bulk inserts of non-null data + + { + sql << "delete from soci_test"; + + std::vector ids; + std::vector > v; + + ids.push_back(10); v.push_back(20); + ids.push_back(11); v.push_back(21); + ids.push_back(12); v.push_back(22); + ids.push_back(13); v.push_back(23); + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(ids, "id"), use(v, "val"); + + int sum; + sql << "select sum(val) from soci_test", into(sum); + CHECK(sum == 86); + + // bulk inserts of some-null data + + sql << "delete from soci_test"; + + v[2].reset(); + v[3].reset(); + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(ids, "id"), use(v, "val"); + + sql << "select sum(val) from soci_test", into(sum); + CHECK(sum == 41); + } + + // composability with user conversions + + { + sql << "delete from soci_test"; + + boost::optional omi1; + boost::optional omi2; + + omi1 = MyInt(125); + omi2.reset(); + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(omi1), use(omi2); + + sql << "select id, val from soci_test", into(omi2), into(omi1); + + CHECK(omi1.is_initialized() == false); + CHECK(omi2.is_initialized()); + CHECK(omi2.get().get() == 125); + } + + // use with const optional and user conversions + + { + sql << "delete from soci_test"; + + boost::optional omi1; + boost::optional omi2; + + omi1 = MyInt(125); + omi2.reset(); + + boost::optional const & comi1 = omi1; + boost::optional const & comi2 = omi2; + + sql << "insert into soci_test(id, val) values(:id, :val)", + use(comi1), use(comi2); + + sql << "select id, val from soci_test", into(omi2), into(omi1); + + CHECK(omi1.is_initialized() == false); + CHECK(omi2.is_initialized()); + CHECK(omi2.get().get() == 125); + } + + // use with rowset and table containing null values + + { + 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, 13)"; + + rowset > rs = (sql.prepare << + "select val from soci_test order by id asc"); + + // 1st row + rowset >::const_iterator pos = rs.begin(); + CHECK((*pos).is_initialized()); + CHECK(10 == (*pos).get()); + + // 2nd row + ++pos; + CHECK((*pos).is_initialized()); + CHECK(11 == (*pos).get()); + + // 3rd row + ++pos; + CHECK((*pos).is_initialized() == false); + + // 4th row + ++pos; + CHECK((*pos).is_initialized()); + CHECK(13 == (*pos).get()); + } + } +} + +#endif // HAVE_BOOST + +// connection and reconnection tests +TEST_CASE_METHOD(common_tests, "Connection and reconnection", "[core][connect]") +{ + { + // empty session + soci::session sql; + + // idempotent: + sql.close(); + + try + { + sql.reconnect(); + FAIL("expected exception not thrown"); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Cannot reconnect without previous connection."); + } + + // open from empty session + sql.open(backEndFactory_, connectString_); + sql.close(); + + // reconnecting from closed session + sql.reconnect(); + + // opening already connected session + try + { + sql.open(backEndFactory_, connectString_); + FAIL("expected exception not thrown"); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Cannot open already connected session."); + } + + sql.close(); + + // open from closed + sql.open(backEndFactory_, connectString_); + + // reconnect from already connected session + sql.reconnect(); + } + + { + soci::session sql; + + try + { + sql << "this statement cannot execute"; + FAIL("expected exception not thrown"); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Session is not connected."); + } + } + +} + +#ifdef HAVE_BOOST + +TEST_CASE_METHOD(common_tests, "Boost tuple", "[core][boost][tuple]") +{ + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_2(sql)); + { + boost::tuple t1(3.5, 7, "Joe Hacker"); + ASSERT_EQUAL(t1.get<0>(), 3.5); + CHECK(t1.get<1>() == 7); + CHECK(t1.get<2>() == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // basic query + + boost::tuple t2; + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(t2.get<0>(), 3.5); + CHECK(t2.get<1>() == 7); + CHECK(t2.get<2>() == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // composability with boost::optional + + // use: + boost::tuple, std::string> t1( + 3.5, boost::optional(7), "Joe Hacker"); + ASSERT_EQUAL(t1.get<0>(), 3.5); + CHECK(t1.get<1>().is_initialized()); + CHECK(t1.get<1>().get() == 7); + CHECK(t1.get<2>() == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::tuple, std::string> t2; + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(t2.get<0>(), 3.5); + CHECK(t2.get<1>().is_initialized()); + CHECK(t2.get<1>().get() == 7); + CHECK(t2.get<2>() == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // composability with user-provided conversions + + // use: + boost::tuple t1(3.5, 7, "Joe Hacker"); + ASSERT_EQUAL(t1.get<0>(), 3.5); + CHECK(t1.get<1>().get() == 7); + CHECK(t1.get<2>() == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::tuple t2; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(t2.get<0>(), 3.5); + CHECK(t2.get<1>().get() == 7); + CHECK(t2.get<2>() == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // let's have fun - composition of tuple, optional and user-defined type + + // use: + boost::tuple, std::string> t1( + 3.5, boost::optional(7), "Joe Hacker"); + ASSERT_EQUAL(t1.get<0>(), 3.5); + CHECK(t1.get<1>().is_initialized()); + CHECK(t1.get<1>().get().get() == 7); + CHECK(t1.get<2>() == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::tuple, std::string> t2; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(t2.get<0>(), 3.5); + CHECK(t2.get<1>().is_initialized()); + CHECK(t2.get<1>().get().get() == 7); + CHECK(t2.get<2>() == "Joe Hacker"); + + sql << "update soci_test set num_int = NULL"; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(t2.get<0>(), 3.5); + CHECK(t2.get<1>().is_initialized() == false); + CHECK(t2.get<2>() == "Joe Hacker"); + } + + { + // rowset + + sql << "insert into soci_test(num_float, num_int, name) values(4.0, 8, 'Tony Coder')"; + sql << "insert into soci_test(num_float, num_int, name) values(4.5, NULL, 'Cecile Sharp')"; + sql << "insert into soci_test(num_float, num_int, name) values(5.0, 10, 'Djhava Ravaa')"; + + typedef boost::tuple, std::string> T; + + rowset rs = (sql.prepare + << "select num_float, num_int, name from soci_test order by num_float asc"); + + rowset::const_iterator pos = rs.begin(); + + ASSERT_EQUAL(pos->get<0>(), 3.5); + CHECK(pos->get<1>().is_initialized() == false); + CHECK(pos->get<2>() == "Joe Hacker"); + + ++pos; + ASSERT_EQUAL(pos->get<0>(), 4.0); + CHECK(pos->get<1>().is_initialized()); + CHECK(pos->get<1>().get() == 8); + CHECK(pos->get<2>() == "Tony Coder"); + + ++pos; + ASSERT_EQUAL(pos->get<0>(), 4.5); + CHECK(pos->get<1>().is_initialized() == false); + CHECK(pos->get<2>() == "Cecile Sharp"); + + ++pos; + ASSERT_EQUAL(pos->get<0>(), 5.0); + CHECK(pos->get<1>().is_initialized()); + CHECK(pos->get<1>().get() == 10); + CHECK(pos->get<2>() == "Djhava Ravaa"); + + ++pos; + CHECK(pos == rs.end()); + } +} + +#if defined(BOOST_VERSION) && BOOST_VERSION >= 103500 + +TEST_CASE_METHOD(common_tests, "Boost fusion", "[core][boost][fusion]") +{ + + soci::session sql(backEndFactory_, connectString_); + + auto_table_creator tableCreator(tc_.table_creator_2(sql)); + { + boost::fusion::vector t1(3.5, 7, "Joe Hacker"); + ASSERT_EQUAL(boost::fusion::at_c<0>(t1), 3.5); + CHECK(boost::fusion::at_c<1>(t1) == 7); + CHECK(boost::fusion::at_c<2>(t1) == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // basic query + + boost::fusion::vector t2; + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5); + CHECK(boost::fusion::at_c<1>(t2) == 7); + CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // composability with boost::optional + + // use: + boost::fusion::vector, std::string> t1( + 3.5, boost::optional(7), "Joe Hacker"); + ASSERT_EQUAL(boost::fusion::at_c<0>(t1), 3.5); + CHECK(boost::fusion::at_c<1>(t1).is_initialized()); + CHECK(boost::fusion::at_c<1>(t1).get() == 7); + CHECK(boost::fusion::at_c<2>(t1) == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::fusion::vector, std::string> t2; + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5); + CHECK(boost::fusion::at_c<1>(t2).is_initialized()); + CHECK(boost::fusion::at_c<1>(t2) == 7); + CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // composability with user-provided conversions + + // use: + boost::fusion::vector t1(3.5, 7, "Joe Hacker"); + ASSERT_EQUAL(boost::fusion::at_c<0>(t1), 3.5); + CHECK(boost::fusion::at_c<1>(t1).get() == 7); + CHECK(boost::fusion::at_c<2>(t1) == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::fusion::vector t2; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5); + CHECK(boost::fusion::at_c<1>(t2).get() == 7); + CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + + sql << "delete from soci_test"; + } + + { + // let's have fun - composition of tuple, optional and user-defined type + + // use: + boost::fusion::vector, std::string> t1( + 3.5, boost::optional(7), "Joe Hacker"); + ASSERT_EQUAL(boost::fusion::at_c<0>(t1), 3.5); + CHECK(boost::fusion::at_c<1>(t1).is_initialized()); + CHECK(boost::fusion::at_c<1>(t1).get().get() == 7); + CHECK(boost::fusion::at_c<2>(t1) == "Joe Hacker"); + + sql << "insert into soci_test(num_float, num_int, name) values(:d, :i, :s)", use(t1); + + // into: + boost::fusion::vector, std::string> t2; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5); + CHECK(boost::fusion::at_c<1>(t2).is_initialized()); + CHECK(boost::fusion::at_c<1>(t2).get().get() == 7); + CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + + sql << "update soci_test set num_int = NULL"; + + sql << "select num_float, num_int, name from soci_test", into(t2); + + ASSERT_EQUAL(boost::fusion::at_c<0>(t2), 3.5); + CHECK(boost::fusion::at_c<1>(t2).is_initialized() == false); + CHECK(boost::fusion::at_c<2>(t2) == "Joe Hacker"); + } + + { + // rowset + + sql << "insert into soci_test(num_float, num_int, name) values(4.0, 8, 'Tony Coder')"; + sql << "insert into soci_test(num_float, num_int, name) values(4.5, NULL, 'Cecile Sharp')"; + sql << "insert into soci_test(num_float, num_int, name) values(5.0, 10, 'Djhava Ravaa')"; + + typedef boost::fusion::vector, std::string> T; + + rowset rs = (sql.prepare + << "select num_float, num_int, name from soci_test order by num_float asc"); + + rowset::const_iterator pos = rs.begin(); + + ASSERT_EQUAL(boost::fusion::at_c<0>(*pos), 3.5); + CHECK(boost::fusion::at_c<1>(*pos).is_initialized() == false); + CHECK(boost::fusion::at_c<2>(*pos) == "Joe Hacker"); + + ++pos; + ASSERT_EQUAL(boost::fusion::at_c<0>(*pos), 4.0); + CHECK(boost::fusion::at_c<1>(*pos).is_initialized()); + CHECK(boost::fusion::at_c<1>(*pos).get() == 8); + CHECK(boost::fusion::at_c<2>(*pos) == "Tony Coder"); + + ++pos; + ASSERT_EQUAL(boost::fusion::at_c<0>(*pos), 4.5); + CHECK(boost::fusion::at_c<1>(*pos).is_initialized() == false); + CHECK(boost::fusion::at_c<2>(*pos) == "Cecile Sharp"); + + ++pos; + ASSERT_EQUAL(boost::fusion::at_c<0>(*pos), 5.0); + CHECK(boost::fusion::at_c<1>(*pos).is_initialized()); + CHECK(boost::fusion::at_c<1>(*pos).get() == 10); + CHECK(boost::fusion::at_c<2>(*pos) == "Djhava Ravaa"); + + ++pos; + CHECK(pos == rs.end()); + } +} + +#endif // defined(BOOST_VERSION) && BOOST_VERSION >= 103500 + +// test for boost::gregorian::date +TEST_CASE_METHOD(common_tests, "Boost date", "[core][boost][datetime]") +{ + soci::session sql(backEndFactory_, connectString_); + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + std::tm nov15; + nov15.tm_year = 105; + nov15.tm_mon = 10; + nov15.tm_mday = 15; + nov15.tm_hour = 0; + nov15.tm_min = 0; + nov15.tm_sec = 0; + + sql << "insert into soci_test(tm) values(:tm)", use(nov15); + + boost::gregorian::date bgd; + sql << "select tm from soci_test", into(bgd); + + CHECK(bgd.year() == 2005); + CHECK(bgd.month() == 11); + CHECK(bgd.day() == 15); + + sql << "update soci_test set tm = NULL"; + try + { + sql << "select tm from soci_test", into(bgd); + FAIL("expected exception not thrown"); + } + catch (soci_error const & e) + { + CHECK(e.get_error_message() == + "Null value not allowed for this type"); + } + } + + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + boost::gregorian::date bgd(2008, boost::gregorian::May, 5); + + sql << "insert into soci_test(tm) values(:tm)", use(bgd); + + std::tm t; + sql << "select tm from soci_test", into(t); + + CHECK(t.tm_year == 108); + CHECK(t.tm_mon == 4); + CHECK(t.tm_mday == 5); + } + +} + +#endif // HAVE_BOOST + +// connection pool - simple sequential test, no multiple threads +TEST_CASE_METHOD(common_tests, "Connection pool", "[core][connection][pool]") +{ + // phase 1: preparation + const size_t pool_size = 10; + connection_pool pool(pool_size); + + for (std::size_t i = 0; i != pool_size; ++i) + { + session & sql = pool.at(i); + sql.open(backEndFactory_, connectString_); + } + + // phase 2: usage + for (std::size_t i = 0; i != pool_size; ++i) + { + // poor man way to lease more than one connection + soci::session sql_unused1(pool); + soci::session sql(pool); + soci::session sql_unused2(pool); + { + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + char c('a'); + sql << "insert into soci_test(c) values(:c)", use(c); + sql << "select c from soci_test", into(c); + CHECK(c == 'a'); + } + } +} + +// Issue 66 - test query transformation callback feature +static std::string no_op_transform(std::string query) +{ + return query; +} + +static std::string lower_than_g(std::string query) +{ + return query + " WHERE c < 'g'"; +} + +struct where_condition : std::unary_function +{ + where_condition(std::string const& where) + : where_(where) + {} + + result_type operator()(argument_type query) const + { + return query + " WHERE " + where_; + } + + std::string where_; +}; + + +void run_query_transformation_test(test_context_base const& tc, session& sql) +{ + // create and populate the test table + auto_table_creator tableCreator(tc.table_creator_1(sql)); + + for (char c = 'a'; c <= 'z'; ++c) + { + sql << "insert into soci_test(c) values(\'" << c << "\')"; + } + + char const* query = "select count(*) from soci_test"; + + // free function, no-op + { + sql.set_query_transformation(no_op_transform); + int count; + sql << query, into(count); + CHECK(count == 'z' - 'a' + 1); + } + + // free function + { + sql.set_query_transformation(lower_than_g); + int count; + sql << query, into(count); + CHECK(count == 'g' - 'a'); + } + + // function object with state + { + sql.set_query_transformation(where_condition("c > 'g' AND c < 'j'")); + int count = 0; + sql << query, into(count); + CHECK(count == 'j' - 'h'); + count = 0; + sql.set_query_transformation(where_condition("c > 's' AND c <= 'z'")); + sql << query, into(count); + CHECK(count == 'z' - 's'); + } + +#if 0 + // lambda is just presented as an example to curious users + { + sql.set_query_transformation( + [](std::string const& query) { + return query + " WHERE c > 'g' AND c < 'j'"; + }); + + int count = 0; + sql << query, into(count); + CHECK(count == 'j' - 'h'); + } +#endif + + // prepared statements + + // constant effect (pre-prepare set transformation) + { + // set transformation after statement is prepared + sql.set_query_transformation(lower_than_g); + // prepare statement + int count; + statement st = (sql.prepare << query, into(count)); + // observe transformation effect + st.execute(true); + CHECK(count == 'g' - 'a'); + // reset transformation + sql.set_query_transformation(no_op_transform); + // observe the same transformation, no-op set above has no effect + count = 0; + st.execute(true); + CHECK(count == 'g' - 'a'); + } + + // no effect (post-prepare set transformation) + { + // reset + sql.set_query_transformation(no_op_transform); + + // prepare statement + int count; + statement st = (sql.prepare << query, into(count)); + // set transformation after statement is prepared + sql.set_query_transformation(lower_than_g); + // observe no effect of WHERE clause injection + st.execute(true); + CHECK(count == 'z' - 'a' + 1); + } +} + +TEST_CASE_METHOD(common_tests, "Query transformation", "[core][query-transform]") +{ + soci::session sql(backEndFactory_, connectString_); + run_query_transformation_test(tc_, sql); +} + +TEST_CASE_METHOD(common_tests, "Query transformation with connection pool", "[core][query-transform][pool]") +{ + // phase 1: preparation + const size_t pool_size = 10; + connection_pool pool(pool_size); + + for (std::size_t i = 0; i != pool_size; ++i) + { + session & sql = pool.at(i); + sql.open(backEndFactory_, connectString_); + } + + soci::session sql(pool); + run_query_transformation_test(tc_, sql); +} + +// Originally, submitted to SQLite3 backend and later moved to common test. +// Test commit b394d039530f124802d06c3b1a969c3117683152 +// Author: Mika Fischer +// Date: Thu Nov 17 13:28:07 2011 +0100 +// Implement get_affected_rows for SQLite3 backend +TEST_CASE_METHOD(common_tests, "Get affected rows", "[core][affected-rows]") +{ + soci::session sql(backEndFactory_, connectString_); + auto_table_creator tableCreator(tc_.table_creator_4(sql)); + if (!tableCreator.get()) + { + std::cout << "test get_affected_rows skipped (function not implemented)" << std::endl; + return; + } + + for (int i = 0; i != 10; i++) + { + sql << "insert into soci_test(val) values(:val)", use(i); + } + + statement st1 = (sql.prepare << + "update soci_test set val = val + 1"); + st1.execute(true); + + CHECK(st1.get_affected_rows() == 10); + + statement st2 = (sql.prepare << + "delete from soci_test where val <= 5"); + st2.execute(true); + + CHECK(st2.get_affected_rows() == 5); + + statement st3 = (sql.prepare << + "update soci_test set val = val + 1"); + st3.execute(true); + + CHECK(st3.get_affected_rows() == 5); + + std::vector v(5, 0); + for (std::size_t i = 0; i < v.size(); ++i) + { + v[i] = (7 + static_cast(i)); + } + + // test affected rows for bulk operations. + statement st4 = (sql.prepare << + "delete from soci_test where val = :v", use(v)); + st4.execute(true); + + CHECK(st4.get_affected_rows() == 5); + + std::vector w(2, "1"); + w[1] = "a"; // this invalid value may cause an exception. + statement st5 = (sql.prepare << + "insert into soci_test(val) values(:val)", use(w)); + try { st5.execute(true); } + catch(...) {} + + // confirm the partial insertion. + int val = 0; + 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); + } +} + +// test fix for: Backend is not set properly with connection pool (pull #5) +TEST_CASE_METHOD(common_tests, "Backend with connection pool", "[core][pool]") +{ + const size_t pool_size = 1; + connection_pool pool(pool_size); + + for (std::size_t i = 0; i != pool_size; ++i) + { + session & sql = pool.at(i); + sql.open(backEndFactory_, connectString_); + } + + soci::session sql(pool); + sql.reconnect(); + sql.begin(); // no crash expected +} + +// issue 67 - Allocated statement backend memory leaks on exception +// If the test runs under memory debugger and it passes, then +// soci::details::statement_impl::backEnd_ must not leak +TEST_CASE_METHOD(common_tests, "Backend memory leak", "[core][leak]") +{ + soci::session sql(backEndFactory_, connectString_); + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + try + { + rowset rs1 = (sql.prepare << "select * from soci_testX"); + + // TODO: On Linux, no exception thrown; neither from prepare, nor from execute? + // soci_odbc_test_postgresql: + // /home/travis/build/SOCI/soci/src/core/test/common-tests.h:3505: + // void soci::tests::common_tests::test_issue67(): Assertion `!"exception expected"' failed. + //FAIL("exception expected"); // relax temporarily + } + catch (soci_error const &e) + { + (void)e; + } +} + +// issue 154 - Calling undefine_and_bind and then define_and_bind causes a leak. +// If the test runs under memory debugger and it passes, then +// soci::details::standard_use_type_backend and vector_use_type_backend must not leak +TEST_CASE_METHOD(common_tests, "Bind memory leak", "[core][leak]") +{ + soci::session sql(backEndFactory_, connectString_); + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + sql << "insert into soci_test(id) values (1)"; + { + int id = 1; + int val = 0; + statement st(sql); + st.exchange(use(id)); + st.alloc(); + st.prepare("select id from soci_test where id = :1"); + st.define_and_bind(); + st.undefine_and_bind(); + st.exchange(soci::into(val)); + st.define_and_bind(); + st.execute(true); + CHECK(val == 1); + } + // vector variation + { + std::vector ids(1, 2); + std::vector vals(1, 1); + int val = 0; + statement st(sql); + st.exchange(use(ids)); + st.alloc(); + st.prepare("insert into soci_test(id, val) values (:1, :2)"); + st.define_and_bind(); + st.undefine_and_bind(); + st.exchange(use(vals)); + st.define_and_bind(); + st.execute(true); + sql << "select val from soci_test where id = 2", into(val); + CHECK(val == 1); + } +} + +TEST_CASE_METHOD(common_tests, "Insert error", "[core][insert][exception]") +{ + soci::session sql(backEndFactory_, connectString_); + + struct pk_table_creator : table_creator_base + { + explicit pk_table_creator(session& sql) : table_creator_base(sql) + { + // For some backends (at least Firebird), it is important to + // execute the DDL statements in a separate transaction, so start + // one here and commit it before using the new table below. + sql.begin(); + sql << "create table soci_test(" + "name varchar(100) not null primary key, " + "age integer not null" + ")"; + sql.commit(); + } + } table_creator(sql); + + SECTION("literal SQL queries appear in the error message") + { + sql << "insert into soci_test(name, age) values ('John', 74)"; + sql << "insert into soci_test(name, age) values ('Paul', 72)"; + sql << "insert into soci_test(name, age) values ('George', 72)"; + + try + { + // Oops, this should have been 'Ringo' + sql << "insert into soci_test(name, age) values ('John', 74)"; + + FAIL("exception expected on unique constraint violation not thrown"); + } + catch (soci_error const &e) + { + std::string const msg = e.what(); + CAPTURE(msg); + + CHECK(msg.find("John") != std::string::npos); + } + } + + SECTION("SQL queries parameters appear in the error message") + { + char const* const names[] = { "John", "Paul", "George", "John", NULL }; + int const ages[] = { 74, 72, 72, 74, 0 }; + + std::string name; + int age; + + statement st = (sql.prepare << + "insert into soci_test(name, age) values (:name, :age)", + use(name), use(age)); + try + { + int const *a = ages; + for (char const* const* n = names; n; ++n, ++a) + { + name = *n; + age = *a; + st.execute(true); + } + } + catch (soci_error const &e) + { + std::string const msg = e.what(); + CAPTURE(msg); + + CHECK(msg.find("John") != std::string::npos); + } + } +} + +namespace +{ + +// This is just a helper to avoid duplicating the same code in two sections in +// the test below, it's logically part of it. +void check_for_exception_on_truncation(session& sql) +{ + // As the name column has length 20, inserting a longer string into it + // shouldn't work, unless we're dealing with a database that doesn't + // respect column types at all (hello SQLite). + try + { + std::string const long_name("George Raymond Richard Martin"); + sql << "insert into soci_test(name) values(:name)", use(long_name); + + // If insert didn't throw, it should have at least preserved the data + // (only SQLite does this currently). + std::string name; + sql << "select name from soci_test", into(name); + CHECK(name == long_name); + } + catch (soci_error const &) + { + // Unfortunately the contents of the message differ too much between + // the backends (most give an error about value being "too long", + // Oracle says "too large" while SQL Server (via ODBC) just says that + // it "would be truncated"), so we can't really check that we received + // the right error here -- be optimistic and hope that we did. + } +} + +} // anonymous namespace + +TEST_CASE_METHOD(common_tests, "Truncation error", "[core][insert][truncate][exception]") +{ + soci::session sql(backEndFactory_, connectString_); + + if (tc_.has_silent_truncate_bug(sql)) + { + WARN("Database is broken and silently truncates input data."); + return; + } + + SECTION("Error given for char column") + { + struct fixed_name_table_creator : table_creator_base + { + fixed_name_table_creator(session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name char(20))"; + } + } tableCreator(sql); + + tc_.on_after_ddl(sql); + + check_for_exception_on_truncation(sql); + } + + SECTION("Error given for varchar column") + { + // Reuse one of the standard tables which has a varchar(20) column. + auto_table_creator tableCreator(tc_.table_creator_1(sql)); + + check_for_exception_on_truncation(sql); + } +} + +TEST_CASE_METHOD(common_tests, "Blank padding", "[core][insert][exception]") +{ + soci::session sql(backEndFactory_, connectString_); + if (!tc_.enable_std_char_padding(sql)) + { + WARN("This backend doesn't pad CHAR(N) correctly, skipping test."); + return; + } + + struct fixed_name_table_creator : table_creator_base + { + fixed_name_table_creator(session& sql) + : table_creator_base(sql) + { + sql.begin(); + sql << "create table soci_test(sc char, name char(10), name2 varchar(10))"; + sql.commit(); + } + } tableCreator(sql); + + std::string test1 = "abcde "; + std::string singleChar = "a"; + sql << "insert into soci_test(sc, name,name2) values(:sc,:name,:name2)", + use(singleChar), use(test1), use(test1); + + std::string sc, tchar,tvarchar; + sql << "select sc,name,name2 from soci_test", + into(sc), into(tchar), into(tvarchar); + + // Firebird can pad "a" to "a " when using UTF-8 encoding. + CHECK_EQUAL_PADDED(sc, singleChar); + CHECK_EQUAL_PADDED(tchar, test1); + CHECK(tvarchar == test1); + + // Check 10-space string - same as inserting empty string since spaces will + // be padded up to full size of the column. + test1 = " "; + singleChar = " "; + sql << "update soci_test set sc=:sc, name=:name, name2=:name2", + use(singleChar), use(test1), use(test1); + sql << "select sc, name,name2 from soci_test", + into(sc), into(tchar), into(tvarchar); + + CHECK_EQUAL_PADDED(sc, singleChar); + CHECK_EQUAL_PADDED(tchar, test1); + CHECK(tvarchar == test1); +} + +} // namespace test_cases + +} // namespace tests + +} // namespace soci + +#endif // SOCI_COMMON_TESTS_H_INCLUDED diff --git a/src/backends/db2/test/CMakeLists.txt b/tests/db2/CMakeLists.txt similarity index 79% rename from src/backends/db2/test/CMakeLists.txt rename to tests/db2/CMakeLists.txt index cb8427504e..594eacca1f 100644 --- a/src/backends/db2/test/CMakeLists.txt +++ b/tests/db2/CMakeLists.txt @@ -2,13 +2,15 @@ # # This file is part of CMake configuration for SOCI library # -# Copyright (C) 2011 Denis Chapligin +# Copyright (C) 2010-2013 Mateusz Loskot # 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) # ############################################################################### + soci_backend_test( BACKEND DB2 - SOURCE test-db2.cpp + DEPENDS DB2 + SOURCE test-db2.cpp ${SOCI_TESTS_COMMON} CONNSTR "DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off") diff --git a/tests/db2/test-db2.cpp b/tests/db2/test-db2.cpp new file mode 100644 index 0000000000..9d73cc9650 --- /dev/null +++ b/tests/db2/test-db2.cpp @@ -0,0 +1,419 @@ +// +// Copyright (C) 2011-2013 Denis Chapligin +// 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.h" +#include "soci/db2/soci-db2.h" +#include "common-tests.h" +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_db2(); + +// +// Support for soci Common Tests +// + +struct table_creator_one : public table_creator_base +{ + table_creator_one(soci::session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(ID INTEGER, VAL SMALLINT, C CHAR, STR VARCHAR(20), SH SMALLINT, UL NUMERIC(20), D DOUBLE, " + "NUM76 NUMERIC(7,6), " + "TM TIMESTAMP, I1 INTEGER, I2 INTEGER, I3 INTEGER, NAME VARCHAR(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(soci::session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(NUM_FLOAT DOUBLE, NUM_INT INTEGER, NAME VARCHAR(20), SOMETIME TIMESTAMP, CHR CHAR)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(soci::session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(NAME VARCHAR(100) NOT NULL, PHONE VARCHAR(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(soci::session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(VAL INTEGER)"; + } +}; + +class test_context :public test_context_base +{ +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 + { + pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; + return new table_creator_one(pr_s); + } + + table_creator_base* table_creator_2(soci::session & pr_s) const + { + pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; + return new table_creator_two(pr_s); + } + + table_creator_base* table_creator_3(soci::session & pr_s) const + { + pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; + return new table_creator_three(pr_s); + } + + table_creator_base* table_creator_4(soci::session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const & pi_datdt_string) const + { + return "to_date('" + pi_datdt_string + "', 'YYYY-MM-DD HH24:MI:SS')"; + } +}; + + +// +// Additional tests to exercise the DB2 backend +// + +TEST_CASE("DB2 test 1", "[db2]") +{ + soci::session sql(backEnd, connectString); + + sql << "SELECT CURRENT TIMESTAMP FROM SYSIBM.SYSDUMMY1"; + sql << "SELECT " << 123 << " FROM SYSIBM.SYSDUMMY1"; + + std::string query = "CREATE TABLE DB2INST1.SOCI_TEST (ID BIGINT,DATA VARCHAR(8))"; + sql << query; + + { + const int i = 7; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(i,"id"); + int j = 0; + sql << "select id from db2inst1.SOCI_TEST where id=7", into(j); + CHECK(j == i); + } + + { + const long int li = 9; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(li,"id"); + long int lj = 0;; + sql << "select id from db2inst1.SOCI_TEST where id=9", into(lj); + CHECK(lj == li); + } + + { + const long long ll = 11; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(ll,"id"); + long long lj = 0; + sql << "select id from db2inst1.SOCI_TEST where id=11", into(lj); + CHECK(lj == ll); + } + + { + const int i = 13; + indicator i_ind = i_ok; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(i,i_ind,"id"); + int j = 0; + indicator j_ind = i_null; + sql << "select id from db2inst1.SOCI_TEST where id=13", into(j,j_ind); + CHECK(j == i); + CHECK(j_ind == i_ok); + } + + { + std::vector numbers(100); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = i + 1000; + } + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(numbers,"id"); + sql << "select id from db2inst1.SOCI_TEST where id >= 1000 and id < 2000 order by id", into(numbers); + for (int i = 0 ; i < 100 ; i++) + { + CHECK(numbers[i] == i + 1000); + } + } + + { + std::vector numbers(100); + std::vector inds(100); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = i + 2000; + inds[i] = i_ok; + } + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(numbers,inds,"id"); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = 0; + inds[i] = i_null; + } + sql << "select id from db2inst1.SOCI_TEST where id >= 2000 and id < 3000 order by id", into(numbers,inds); + for (int i = 0 ; i < 100 ; i++) + { + CHECK(numbers[i] == i + 2000); + CHECK(inds[i] == i_ok); + } + } + + { + int i = 0; + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST where id < 1000", into(i)); + st.execute(); + st.fetch(); + CHECK (i == 7); + st.fetch(); + CHECK (i == 9); + st.fetch(); + CHECK (i == 11); + st.fetch(); + CHECK (i == 13); + } + + { + int i = 0; + indicator i_ind = i_null; + std::string d; + indicator d_ind = i_ok; + statement st = (sql.prepare << "select id, data from db2inst1.SOCI_TEST where id = 13", into(i, i_ind), into(d, d_ind)); + st.execute(); + st.fetch(); + CHECK (i == 13); + CHECK (i_ind == i_ok); + CHECK (d_ind == i_null); + } + + { + std::vector numbers(100); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = 0; + } + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST where id >= 1000 order by id", into(numbers)); + st.execute(); + st.fetch(); + for (int i = 0 ; i < 100 ; i++) + { + CHECK(numbers[i] == i + 1000); + } + st.fetch(); + for (int i = 0 ; i < 100 ; i++) + { + CHECK(numbers[i] == i + 2000); + } + } + + { + std::vector numbers(100); + std::vector inds(100); + for (int i = 0 ; i < 100 ; i++) + { + numbers[i] = 0; + inds[i] = i_null; + } + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST where id >= 1000 order by id", into(numbers, inds)); + st.execute(); + st.fetch(); + for (int i = 0 ; i < 100 ; i++) + { + CHECK(numbers[i] == i + 1000); + CHECK(inds[i] == i_ok); + } + st.fetch(); + for (int i = 0 ; i < 100 ; i++) + { + CHECK(numbers[i] == i + 2000); + CHECK(inds[i] == i_ok); + } + } + + { + // XXX: what is the purpose of this test?? what is the expected value? + int i = 0; + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(i)); + } + + { + // XXX: what is the purpose of this test?? what is the expected value? + int i = 0; + indicator ind = i_ok; + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(i, ind)); + } + + { + // XXX: what is the purpose of this test?? what is the expected value? + std::vector numbers(100); + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(numbers)); + } + + { + // XXX: what is the purpose of this test?? what is the expected value? + std::vector numbers(100); + std::vector inds(100); + statement st = (sql.prepare << "select id from db2inst1.SOCI_TEST", use(numbers, inds)); + } + + sql<<"DROP TABLE DB2INST1.SOCI_TEST"; + sql.commit(); +} + +TEST_CASE("DB2 test 2", "[db2]") +{ + soci::session sql(backEnd, connectString); + + std::string query = "CREATE TABLE DB2INST1.SOCI_TEST (ID BIGINT,DATA VARCHAR(8),DT TIMESTAMP)"; + sql << query; + + { + int i = 7; + std::string n("test"); + sql << "insert into db2inst1.SOCI_TEST (id,data) values (:id,:name)", use(i,"id"),use(n,"name"); + int j; + std::string m; + sql << "select id,data from db2inst1.SOCI_TEST where id=7", into(j),into(m); + CHECK (j == i); + CHECK (m == n); + } + + { + int i = 8; + sql << "insert into db2inst1.SOCI_TEST (id) values (:id)", use(i,"id"); + int j; + std::string m; + indicator ind = i_ok; + sql << "select id,data from db2inst1.SOCI_TEST where id=8", into(j),into(m,ind); + CHECK(j == i); + CHECK(ind==i_null); + } + + { + std::tm dt; + sql << "select current timestamp from sysibm.sysdummy1",into(dt); + sql << "insert into db2inst1.SOCI_TEST (dt) values (:dt)",use(dt,"dt"); + std::tm dt2; + sql << "select dt from db2inst1.SOCI_TEST where dt is not null", into(dt2); + CHECK(dt2.tm_year == dt.tm_year); + CHECK(dt2.tm_mon == dt.tm_mon); + CHECK(dt2.tm_mday == dt.tm_mday); + CHECK(dt2.tm_hour == dt.tm_hour); + CHECK(dt2.tm_min == dt.tm_min); + CHECK(dt2.tm_sec == dt.tm_sec); + } + + sql<<"DROP TABLE DB2INST1.SOCI_TEST"; + sql.commit(); +} + +TEST_CASE("DB2 test 3", "[db2]") +{ + soci::session sql(backEnd, connectString); + int i; + + std::string query = "CREATE TABLE DB2INST1.SOCI_TEST (ID BIGINT,DATA VARCHAR(8),DT TIMESTAMP)"; + sql << query; + + std::vector ids(100); + std::vector data(100); + std::vector dts(100); + for (int i = 0; i < 100; i++) + { + ids[i] = 1000000000LL + i; + data[i] = "test"; + dts[i].tm_year = 112; + dts[i].tm_mon = 7; + dts[i].tm_mday = 17; + dts[i].tm_hour = 0; + dts[i].tm_min = 0; + dts[i].tm_sec = i % 60; + } + + sql << "insert into db2inst1.SOCI_TEST (id, data, dt) values (:id, :data, :dt)", + use(ids, "id"), use(data,"data"), use(dts, "dt"); + + i = 0; + rowset rs = (sql.prepare<<"SELECT ID, DATA, DT FROM DB2INST1.SOCI_TEST"); + for (rowset::const_iterator it = rs.begin(); it != rs.end(); it++) + { + const row & r = *it; + const long long id = r.get(0); + const std::string data = r.get(1); + const std::tm dt = r.get(2); + + CHECK(id == 1000000000LL + i); + CHECK(data == "test"); + CHECK(dt.tm_year == 112); + CHECK(dt.tm_mon == 7); + CHECK(dt.tm_mday == 17); + CHECK(dt.tm_hour == 0); + CHECK(dt.tm_min == 0); + CHECK(dt.tm_sec == i % 60); + + i += 1; + } + + sql<<"DROP TABLE DB2INST1.SOCI_TEST"; + sql.commit(); +} + + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run assert()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc >= 2) + { + connectString = argv[1]; + + argv[1] = argv[0]; + + argc--; + argv++; + } + else + { + std::cout << "usage: " << argv[0] + << " connectstring [test-arguments...]\n" + << "example: " << argv[0] + << " \'DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off\'\n"; + std::exit(1); + } + + test_context tc(backEnd, connectString); + + return Catch::Session().run(argc, argv); +} diff --git a/src/backends/firebird/test/CMakeLists.txt b/tests/empty/CMakeLists.txt similarity index 77% rename from src/backends/firebird/test/CMakeLists.txt rename to tests/empty/CMakeLists.txt index ecfac884a0..38d95b83ee 100644 --- a/src/backends/firebird/test/CMakeLists.txt +++ b/tests/empty/CMakeLists.txt @@ -2,7 +2,7 @@ # # This file is part of CMake configuration for SOCI library # -# Copyright (C) 2013 Viacheslav Naydenov +# Copyright (C) 2010-2013 Mateusz Loskot # 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,6 @@ ############################################################################### soci_backend_test( - BACKEND Firebird - SOURCE test-firebird.cpp + BACKEND Empty + SOURCE test-empty.cpp ${SOCI_TESTS_COMMON} CONNSTR "dummy") diff --git a/src/backends/empty/test/test-empty.cpp b/tests/empty/test-empty.cpp similarity index 50% rename from src/backends/empty/test/test-empty.cpp rename to tests/empty/test-empty.cpp index 304153657f..7399dd0737 100644 --- a/src/backends/empty/test/test-empty.cpp +++ b/tests/empty/test-empty.cpp @@ -5,11 +5,16 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci.h" -#include "soci-empty.h" +#include "soci/soci.h" +#include "soci/empty/soci-empty.h" + +// Normally the tests would include common-tests.h here, but we can't run any +// of the tests registered there, so instead include CATCH header directly. +#define CATCH_CONFIG_RUNNER +#include + #include #include -#include #include #include @@ -18,7 +23,6 @@ using namespace soci; std::string connectString; backend_factory const &backEnd = *soci::factory_empty(); - // NOTE: // This file is supposed to serve two purposes: // 1. To be a starting point for implementing new tests (for new backends). @@ -52,77 +56,72 @@ namespace soci }; } -void test1() +TEST_CASE("Dummy test", "[empty]") { - { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); - sql << "Do what I want."; - sql << "Do what I want " << 123 << " times."; + sql << "Do what I want."; + sql << "Do what I want " << 123 << " times."; - std::string query = "some query"; - sql << query; + std::string query = "some query"; + sql << query; - int i = 7; - sql << "insert", use(i); - sql << "select", into(i); + int i = 7; + sql << "insert", use(i); + sql << "select", into(i); #if defined (__LP64__) || ( __WORDSIZE == 64 ) - long int li = 9; - sql << "insert", use(li); - sql << "select", into(li); + long int li = 9; + sql << "insert", use(li); + sql << "select", into(li); #endif - long long ll = 11; - sql << "insert", use(ll); - sql << "select", into(ll); + long long ll = 11; + sql << "insert", use(ll); + sql << "select", into(ll); - indicator ind = i_ok; - sql << "insert", use(i, ind); - sql << "select", into(i, ind); + indicator ind = i_ok; + sql << "insert", use(i, ind); + sql << "select", into(i, ind); - std::vector numbers(100); - sql << "insert", use(numbers); - sql << "select", into(numbers); + std::vector numbers(100); + sql << "insert", use(numbers); + sql << "select", into(numbers); - std::vector inds(100); - sql << "insert", use(numbers, inds); - sql << "select", into(numbers, inds); - - { - statement st = (sql.prepare << "select", into(i)); - st.execute(); - st.fetch(); - } - { - statement st = (sql.prepare << "select", into(i, ind)); - } - { - statement st = (sql.prepare << "select", into(numbers)); - } - { - statement st = (sql.prepare << "select", into(numbers, inds)); - } - { - statement st = (sql.prepare << "insert", use(i)); - } - { - statement st = (sql.prepare << "insert", use(i, ind)); - } - { - statement st = (sql.prepare << "insert", use(numbers)); - } - { - statement st = (sql.prepare << "insert", use(numbers, inds)); - } - { - Person p; - sql << "select person", into(p); - } + std::vector inds(100); + sql << "insert", use(numbers, inds); + sql << "select", into(numbers, inds); + { + statement st = (sql.prepare << "select", into(i)); + st.execute(); + st.fetch(); + } + { + statement st = (sql.prepare << "select", into(i, ind)); + } + { + statement st = (sql.prepare << "select", into(numbers)); + } + { + statement st = (sql.prepare << "select", into(numbers, inds)); + } + { + statement st = (sql.prepare << "insert", use(i)); + } + { + statement st = (sql.prepare << "insert", use(i, ind)); + } + { + statement st = (sql.prepare << "insert", use(numbers)); + } + { + statement st = (sql.prepare << "insert", use(numbers, inds)); + } + { + Person p; + sql << "select person", into(p); } - - std::cout << "test 1 passed" << std::endl; } @@ -132,39 +131,31 @@ int main(int argc, char** argv) #ifdef _MSC_VER // Redirect errors, unrecoverable problems, and assert() failures to STDERR, // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. + // This hack is required to run assert()-driven tests by Buildbot. // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); #endif //_MSC_VER - if (argc == 2) + if (argc >= 2) { connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; } else { std::cout << "usage: " << argv[0] - << " connectstring\n" + << " connectstring [test-arguments...]\n" << "example: " << argv[0] << " \'connect_string_for_empty_backend\'\n"; std::exit(1); } - try - { - test1(); - // test2(); - // ... - - std::cout << "\nOK, all tests passed.\n\n"; - - return EXIT_SUCCESS; - } - catch (std::exception const & e) - { - std::cout << e.what() << '\n'; - } - - return EXIT_FAILURE; + return Catch::Session().run(argc, argv); } diff --git a/tests/firebird/CMakeLists.txt b/tests/firebird/CMakeLists.txt new file mode 100644 index 0000000000..4389d259d2 --- /dev/null +++ b/tests/firebird/CMakeLists.txt @@ -0,0 +1,16 @@ +############################################################################### +# +# This file is part of CMake configuration for SOCI library +# +# Copyright (C) 2010-2013 Mateusz Loskot +# 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) +# +############################################################################### + +soci_backend_test( + BACKEND Firebird + DEPENDS Firebird + SOURCE test-firebird.cpp ${SOCI_TESTS_COMMON} + CONNSTR "service=/tmp/test.fdb user=SYSDBA password=masterkey") diff --git a/src/backends/firebird/test/test-firebird.cpp b/tests/firebird/test-firebird.cpp similarity index 67% rename from src/backends/firebird/test/test-firebird.cpp rename to tests/firebird/test-firebird.cpp index 4a331dea77..7347abd01b 100644 --- a/src/backends/firebird/test/test-firebird.cpp +++ b/tests/firebird/test-firebird.cpp @@ -6,14 +6,14 @@ // // -#include "soci.h" -#include "soci-firebird.h" -#include "error-firebird.h" // soci::details::Firebird::throw_iscerror() +#include "soci/soci.h" +#include "soci/firebird/soci-firebird.h" +#include "soci-compiler.h" +#include "firebird/error-firebird.h" // soci::details::Firebird::throw_iscerror() +#include "firebird/common.h" #include "common-tests.h" -#include "common.h" #include #include -#include #include #include #include @@ -24,47 +24,43 @@ std::string connectString; soci::backend_factory const &backEnd = *factory_firebird(); // fundamental tests - transactions in Firebird -void test1() +TEST_CASE("Firebird transactions", "[firebird][transaction]") { + soci::session sql(backEnd, connectString); + + // In Firebird transaction is always required and is started + // automatically when session is opened. There is no need to + // call session::begin(); it will do nothing if there is active + // transaction. + + // sql.begin(); + + try { - session sql(backEnd, connectString); - - // In Firebird transaction is always required and is started - // automatically when session is opened. There is no need to - // call session::begin(); it will do nothing if there is active - // transaction. - - // sql.begin(); - - try - { - sql << "drop table test1"; - } - catch (soci_error const &) - {} // ignore if error - - sql << "create table test1 (id integer)"; - - // After DDL statement transaction must be commited or changes - // won't be visible to active transaction. - sql.commit(); - - // After commit or rollback, transaction must be started manually. - sql.begin(); - - sql << "insert into test1(id) values(5)"; sql << "drop table test1"; - - // Transaction is automatically commited in session's destructor } + catch (soci_error const &) + {} // ignore if error - std::cout << "test 1 passed" << std::endl; + sql << "create table test1 (id integer)"; + + // After DDL statement transaction must be commited or changes + // won't be visible to active transaction. + sql.commit(); + + // After commit or rollback, transaction must be started manually. + sql.begin(); + + sql << "insert into test1(id) values(5)"; + sql << "drop table test1"; + + // Transaction is automatically commited in session's destructor } // character types -void test2() +TEST_CASE("Firebird char types", "[firebird][string]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); try { @@ -73,7 +69,7 @@ void test2() catch (soci_error const &) {} // ignore if error - sql << "create table test2 (p1 char(10), p2 varchar(10))"; + sql << "create table test2 (p1 char(10) character set none, p2 varchar(10) character set none)"; sql.commit(); sql.begin(); @@ -84,7 +80,8 @@ void test2() sql << "insert into test2(p1,p2) values(?,?)", use(a), use(b); sql << "select p1,p2 from test2", into(c1), into(c2); - assert(c1 == 'a' && c2 == 'b'); + CHECK(c1 == 'a'); + CHECK(c2 == 'b'); sql << "delete from test2"; } @@ -100,7 +97,8 @@ void test2() sql << "insert into test2(p1, p2) values (?,?)", use(b1, 100), use(b1, 100); sql << "select p1, p2 from test2", into(b2, 100), into(b3, 100); - assert(!std::strcmp(buf2, buf3) && !std::strcmp(buf2, "Hello, Fir")); + CHECK(!std::strcmp(buf2, buf3)); + CHECK(!std::strcmp(buf2, "Hello, Fir")); sql << "delete from test2"; } @@ -114,19 +112,22 @@ void test2() use(buf1), use(buf1); sql << "select p1, p2 from test2", into(buf2), into(buf3); - assert(!std::strcmp(buf2, buf3) && !std::strcmp(buf2, "Hello, Fir")); + CHECK(!std::strcmp(buf2, buf3)); + CHECK(!std::strcmp(buf2, "Hello, Fir")); sql << "delete from test2"; } #endif { - std::string b1("Hello, Firebird!"), b2, b3; + // The test string is exactly 10 bytes long, i.e. same as column length. + std::string b1("Hello, FB!"), b2, b3; sql << "insert into test2(p1, p2) values (?,?)", use(b1), use(b1); sql << "select p1, p2 from test2", into(b2), into(b3); - assert(b2 == b3 && b2 == "Hello, Fir"); + CHECK(b2 == b3); + CHECK(b2 == "Hello, FB!"); sql << "delete from test2"; } @@ -142,31 +143,20 @@ void test2() sql << "select p1 from test2", into(buf_str); std::strcpy(buf, buf_str.c_str()); - assert(std::strncmp(buf, msg, 5) == 0); - assert(std::strncmp(buf+5, " ", 5) == 0); - - sql << "delete from test2"; - } - - { - std::string str1("Hello, Firebird!"), str2, str3; - sql << "insert into test2(p1, p2) values (?, ?)", - use(str1), use(str1); - - sql << "select p1, p2 from test2", into(str2), into(str3); - assert(str2 == "Hello, Fir" && str3 == "Hello, Fir"); + CHECK(std::strncmp(buf, msg, 5) == 0); + // This test works only for charset none + CHECK(std::strncmp(buf+5, " ", 5) == 0); sql << "delete from test2"; } sql << "drop table test2"; - std::cout << "test 2 passed" << std::endl; } // date and time -void test3() +TEST_CASE("Firebird date and time", "[firebird][datetime]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); try { @@ -183,44 +173,42 @@ void test3() std::tm t1, t2, t3; std::time_t now = std::time(NULL); std::tm t = *std::localtime(&now); - sql << "insert into test3(p1, p2, p3) " << "values (?,?,?)", use(t), use(t), use(t); sql << "select p1, p2, p3 from test3", into(t1), into(t2), into(t3); // timestamp - assert(t1.tm_year == t.tm_year); - assert(t1.tm_mon == t.tm_mon); - assert(t1.tm_mday == t.tm_mday); - assert(t1.tm_hour == t.tm_hour); - assert(t1.tm_min == t.tm_min); - assert(t1.tm_sec == t.tm_sec); + CHECK(t1.tm_year == t.tm_year); + CHECK(t1.tm_mon == t.tm_mon); + CHECK(t1.tm_mday == t.tm_mday); + CHECK(t1.tm_hour == t.tm_hour); + CHECK(t1.tm_min == t.tm_min); + CHECK(t1.tm_sec == t.tm_sec); // date - assert(t2.tm_year == t.tm_year); - assert(t2.tm_mon == t.tm_mon); - assert(t2.tm_mday == t.tm_mday); - assert(t2.tm_hour == 0); - assert(t2.tm_min == 0); - assert(t2.tm_sec == 0); + CHECK(t2.tm_year == t.tm_year); + CHECK(t2.tm_mon == t.tm_mon); + CHECK(t2.tm_mday == t.tm_mday); + CHECK(t2.tm_hour == 0); + CHECK(t2.tm_min == 0); + CHECK(t2.tm_sec == 0); // time - assert(t3.tm_year == 0); - assert(t3.tm_mon == 0); - assert(t3.tm_mday == 0); - assert(t3.tm_hour == t.tm_hour); - assert(t3.tm_min == t.tm_min); - assert(t3.tm_sec == t.tm_sec); + CHECK(t3.tm_year == 0); + CHECK(t3.tm_mon == 0); + CHECK(t3.tm_mday == 0); + CHECK(t3.tm_hour == t.tm_hour); + CHECK(t3.tm_min == t.tm_min); + CHECK(t3.tm_sec == t.tm_sec); sql << "drop table test3"; - std::cout << "test 3 passed" << std::endl; } // floating points -void test4() +TEST_CASE("Firebird floating point", "[firebird][float]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); try { @@ -244,7 +232,11 @@ void test4() sql << "select p1, p2, p3 from test4", into(d4), into(d5), into(d6); - assert(d1 == d4 && d2 == d5 && d3 == d6); + // The doubles should make the round trip unchanged, so use the exact + // comparisons here. + CHECK(tests::are_doubles_exactly_equal(d1, d4)); + CHECK(tests::are_doubles_exactly_equal(d2, d5)); + CHECK(tests::are_doubles_exactly_equal(d3, d6)); // test negative doubles too sql << "delete from test4"; @@ -258,7 +250,9 @@ void test4() sql << "select p1, p2, p3 from test4", into(d4), into(d5), into(d6); - assert(d1 == d4 && d2 == d5 && d3 == d6); + CHECK(tests::are_doubles_exactly_equal(d1, d4)); + CHECK(tests::are_doubles_exactly_equal(d2, d5)); + CHECK(tests::are_doubles_exactly_equal(d3, d6)); // verify an exception is thrown when fetching non-integral value // to integral variable @@ -268,12 +262,11 @@ void test4() sql << "select p1 from test4", into(i); // expecting error - assert(false); + CHECK(false); } catch (soci_error const &e) { - std::string error = e.what(); - assert(error == + CHECK(e.get_error_message() == "Can't convert value with scale 2 to integral type"); } @@ -284,40 +277,38 @@ void test4() sql << "insert into test4(p4) values(?)", use(d1); // expecting error - assert(false); + CHECK(false); } catch (soci_error const &e) { - std::string error = e.what(); - assert(error == + CHECK(e.get_error_message() == "Can't convert non-integral value to integral column type"); } sql << "drop table test4"; - std::cout << "test 4 passed" << std::endl; } // integer types and indicators -void test5() +TEST_CASE("Firebird integers", "[firebird][int]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); { short sh(0); sql << "select 3 from rdb$database", into(sh); - assert(sh == 3); + CHECK(sh == 3); } { int i(0); sql << "select 5 from rdb$database", into(i); - assert(i == 5); + CHECK(i == 5); } { unsigned long ul(0); sql << "select 7 from rdb$database", into(ul); - assert(ul == 7); + CHECK(ul == 7); } { @@ -326,45 +317,42 @@ void test5() int i; sql << "select 2 from rdb$database", into(i, ind); - assert(ind == i_ok); + CHECK(ind == i_ok); sql << "select NULL from rdb$database", into(i, ind); - assert(ind == i_null); + CHECK(ind == i_null); #if 0 // SOCI doesn't support binding into(char *, ...) anymore, use std::string char buf[4]; sql << "select \'Hello\' from rdb$database", into(buf, ind); - assert(ind == i_truncated); + CHECK(ind == i_truncated); #endif sql << "select 5 from rdb$database where 0 = 1", into(i, ind); - assert(sql.got_data() == false); + CHECK(sql.got_data() == false); try { // expect error sql << "select NULL from rdb$database", into(i); - assert(false); + CHECK(false); } catch (soci_error const &e) { - std::string error = e.what(); - assert(error == + CHECK(e.get_error_message() == "Null value fetched and no indicator defined."); } // expect no data sql << "select 5 from rdb$database where 0 = 1", into(i); - assert(!sql.got_data()); + CHECK(!sql.got_data()); } - - std::cout << "test 5 passed" << std::endl; } // repeated fetch and bulk operations for character types -void test6() +TEST_CASE("Firebird bulk operations", "[firebird][bulk]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); try { @@ -373,7 +361,7 @@ void test6() catch (soci_error const &) {} // ignore if error - sql << "create table test6 (p1 char(10), p2 varchar(10))"; + sql << "create table test6 (p1 char(10) character set none, p2 varchar(10) character set none)"; sql.commit(); sql.begin(); @@ -397,10 +385,11 @@ void test6() c='a'; while (st.fetch()) { - assert(c == c1 && c == c2); + CHECK(c == c1); + CHECK(c == c2); ++c; } - assert(c == 'z'+1); + CHECK(c == 'z'+1); } } @@ -417,11 +406,12 @@ void test6() { for (std::size_t i = 0; i != c1.size(); ++i) { - assert(c == c1[i] && c == c2[i]); + CHECK(c == c1[i]); + CHECK(c == c2[i]); ++c; } } - assert(c == 'z' + 1); + CHECK(c == 'z' + 1); } { @@ -430,12 +420,12 @@ void test6() try { sql << "select p1 from test6", into(vec); - assert(false); + CHECK(false); } catch (soci_error const &e) { - std::string msg = e.what(); - assert(msg == "Vectors of size 0 are not allowed."); + CHECK(e.get_error_message() == + "Vectors of size 0 are not allowed."); } } @@ -456,7 +446,7 @@ void test6() int count; sql << "select count(*) from test6", into(count); - assert(count == rowsToTest); + CHECK(count == rowsToTest); { int i = 0; @@ -473,10 +463,11 @@ void test6() // Note: CHAR fields are always padded with whitespaces ss << " "; - assert(s1 == ss.str() && s2 == x); + CHECK(s1 == ss.str()); + CHECK(s2 == x); ++i; } - assert(i == rowsToTest); + CHECK(i == rowsToTest); } { @@ -496,21 +487,21 @@ void test6() // Note: CHAR fields are always padded with whitespaces ss << " "; - assert(ss.str() == s1[j] && x == s2[j]); + CHECK(ss.str() == s1[j]); + CHECK(x == s2[j]); ++i; } } - assert(i == rowsToTest); + CHECK(i == rowsToTest); } sql << "drop table test6"; - std::cout << "test 6 passed" << std::endl; } // blob test -void test7() +TEST_CASE("Firebird blobs", "[firebird][blob]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); try { @@ -531,8 +522,8 @@ void test7() sql << "insert into test7(id, img) values(1,?)", use(b); sql << "select img from test7 where id = 1", into(b, ind); - assert(ind == i_ok); - assert(b.get_len() == 0); + CHECK(ind == i_ok); + CHECK(b.get_len() == 0); sql << "delete from test7"; } @@ -547,7 +538,9 @@ void test7() char str2[20]; std::size_t i = b.read(3, str2, 2); str2[i] = '\0'; - assert(str2[0] == 'l' && str2[1] == 'o' && str2[2] == '\0'); + CHECK(str2[0] == 'l'); + CHECK(str2[1] == 'o'); + CHECK(str2[2] == '\0'); char str3[] = ", Firebird!"; b.append(str3, strlen(str3)); @@ -563,7 +556,7 @@ void test7() std::vector text(b.get_len()); b.read(0, &text[0], b.get_len()); - assert(strncmp(&text[0], "Hello, Firebird!", b.get_len()) == 0); + CHECK(strncmp(&text[0], "Hello, Firebird!", b.get_len()) == 0); char str1[] = "FIREBIRD"; b.write(7, str1, strlen(str1)); @@ -585,7 +578,7 @@ void test7() b.write(0, str1, strlen(str1)); b.read(0, &text[0], b.get_len()); - assert(strncmp(&text[0], "HELLO, FIREBIRD!", b.get_len()) == 0); + CHECK(strncmp(&text[0], "HELLO, FIREBIRD!", b.get_len()) == 0); b.trim(5); sql << "insert into test7(id, img) values(2,?)", use(b); @@ -600,12 +593,12 @@ void test7() st.fetch(); std::vector text(b.get_len()); b.read(0, &text[0], b.get_len()); - assert(strncmp(&text[0], "Hello, FIREBIRD!", b.get_len()) == 0); + CHECK(strncmp(&text[0], "Hello, FIREBIRD!", b.get_len()) == 0); st.fetch(); text.resize(b.get_len()); b.read(0, &text[0], b.get_len()); - assert(strncmp(&text[0], "HELLO", b.get_len()) == 0); + CHECK(strncmp(&text[0], "HELLO", b.get_len()) == 0); } { @@ -615,20 +608,19 @@ void test7() sql << "update test7 set img=? where id = 1", use(b, ind); sql << "select img from test7 where id = 2", into(b, ind); - assert(ind==i_ok); + CHECK(ind==i_ok); sql << "select img from test7 where id = 1", into(b, ind); - assert(ind==i_null); + CHECK(ind==i_null); } sql << "drop table test7"; - std::cout << "test 7 passed" << std::endl; } // named parameters -void test8() +TEST_CASE("Firebird named parameters", "[firebird][named-params]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); try { @@ -646,7 +638,8 @@ void test8() sql << "insert into test8(id1, id2) values(:id1, :id2)", use(k, "id2"), use(j, "id1"); sql << "select id1, id2 from test8", into(i), into(m); - assert(i == j && m == k); + CHECK(i == j); + CHECK(m == k); sql << "delete from test8"; @@ -680,7 +673,8 @@ void test8() std::size_t x(0); while (st.fetch()) { - assert(i = in1[x] && m == in2[x]); + CHECK(i == in1[x]); + CHECK(m == in2[x]); ++x; } } @@ -695,21 +689,21 @@ void test8() sql << "select id1, id2 from test8", into(out1), into(out2); std::size_t s = out1.size(); - assert(s == 3); + CHECK(s == 3); for (std::size_t x = 0; x(0) == 1); - assert(r.get(1) == "Hello"); - assert(r.get(2) == d); + CHECK(r.get(0) == 1); + CHECK(r.get(1) == "Hello"); + CHECK(tests::are_doubles_exactly_equal(r.get(2), d)); // get values by name - assert(r.get("ID") == 1); - assert(r.get("MSG") == "Hello"); - assert(r.get("NTEST") == d); + CHECK(r.get("ID") == 1); + CHECK(r.get("MSG") == "Hello"); + CHECK(tests::are_doubles_exactly_equal(r.get("NTEST"), d)); st.fetch(); - assert(r.get(0) == 2); - assert(r.get("MSG") == "Firebird"); - assert(r.get_indicator(2) == i_null); + CHECK(r.get(0) == 2); + CHECK(r.get("MSG") == "Firebird"); + CHECK(r.get_indicator(2) == i_null); // verify default values - assert(r.get("NTEST", 2) == 2); - bool caught = false; - try - { - double d1 = r.get("NTEST"); - std::cout << d1 << std::endl; // just for compiler - } - catch (soci_error&) - { - caught = true; - } - assert(caught); + CHECK(tests::are_doubles_exactly_equal(r.get("NTEST", 2), 2)); + + CHECK_THROWS_AS(r.get("NTEST"), soci_error); // verify exception thrown on invalid get<> - caught = false; - try - { - r.get(0); - } - catch (std::bad_cast const &) - { - caught = true; - } - assert(caught); + CHECK_THROWS_AS(r.get(0), std::bad_cast); sql << "drop table test9"; - std::cout << "test 9 passed" << std::endl; } // stored procedures -void test10() +TEST_CASE("Firebird stored procedures", "[firebird][procedure]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); try { @@ -873,7 +848,8 @@ void test10() // 'select ... from ...' statement sql << "select * from sp_test10", into(r); - assert(r.get(0) == p1 && r.get(1) == p2); + CHECK(r.get(0) == p1); + CHECK(r.get(1) == p2); sql << "delete from test10"; @@ -891,7 +867,8 @@ void test10() procedure proc = (sql.prepare << "sp_test10", into(rw)); proc.execute(1); - assert(rw.get(0) == p1 && rw.get(1) == p2); + CHECK(rw.get(0) == p1); + CHECK(rw.get(1) == p2); } sql << "delete from test10"; @@ -917,12 +894,15 @@ void test10() procedure proc = (sql.prepare << "sp_test10", into(rw)); proc.execute(1); - assert(rw.get(0) == in1[0] && rw.get(1) == in2[0]); + CHECK(rw.get(0) == in1[0]); + CHECK(rw.get(1) == in2[0]); proc.fetch(); - assert(rw.get(0) == in1[1] && rw.get(1) == in2[1]); + CHECK(rw.get(0) == in1[1]); + CHECK(rw.get(1) == in2[1]); proc.fetch(); - assert(rw.get(0) == in1[2] && rw.get(1) == in2[2]); - assert(proc.fetch() == false); + CHECK(rw.get(0) == in1[2]); + CHECK(rw.get(1) == in2[2]); + CHECK(proc.fetch() == false); } { @@ -931,11 +911,12 @@ void test10() proc.execute(1); std::size_t s = out1.size(); - assert(s == 3); + CHECK(s == 3); for (std::size_t x = 0; x < s; ++x) { - assert(out1[x] == in1[x] && out2[x] == in2[x]); + CHECK(out1[x] == in1[x]); + CHECK(out2[x] == in2[x]); } } @@ -945,8 +926,6 @@ void test10() sql << "drop procedure sp_test10"; sql << "drop procedure sp_test10a"; sql << "drop table test10"; - - std::cout << "test 10 passed" << std::endl; } // direct access to Firebird using handles exposed by @@ -991,7 +970,7 @@ namespace soci char count_type = *ptr++; int m = isc_vax_integer(ptr, 2); ptr += 2; - count = isc_vax_integer(ptr, m); + count = isc_vax_integer(ptr, static_cast(m)); if (count_type == type_) { @@ -1006,9 +985,9 @@ namespace soci } // namespace soci -void test11() +TEST_CASE("Firebird direct API use", "[firebird][native]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); try { @@ -1036,7 +1015,7 @@ void test11() // statement to achieve the effect of inserting vectors of values. // Since getRowCount() returns number of rows affected by the *last* // statement, it will return 1 here. - assert(getRowCount(st, eRowsInserted) == 1); + CHECK(getRowCount(st, eRowsInserted) == 1); } { @@ -1044,10 +1023,10 @@ void test11() statement st = (sql.prepare << "update test11 set id = ? where id<3", use(i)); st.execute(1); - assert(getRowCount(st, eRowsUpdated) == 2); + CHECK(getRowCount(st, eRowsUpdated) == 2); // verify that no rows were deleted - assert(getRowCount(st, eRowsDeleted) == 0); + CHECK(getRowCount(st, eRowsDeleted) == 0); } { @@ -1055,28 +1034,27 @@ void test11() statement st = (sql.prepare << "select id from test11", into(out)); st.execute(1); - assert(getRowCount(st, eRowsSelected) == 3); + CHECK(getRowCount(st, eRowsSelected) == 3); } { statement st = (sql.prepare << "delete from test11 where id=10"); st.execute(1); - assert(getRowCount(st, eRowsDeleted) == 0); + CHECK(getRowCount(st, eRowsDeleted) == 0); } { statement st = (sql.prepare << "delete from test11"); st.execute(1); - assert(getRowCount(st, eRowsDeleted) == 3); + CHECK(getRowCount(st, eRowsDeleted) == 3); } sql << "drop table test11"; - std::cout << "test 11 passed" << std::endl; } -void test12() +TEST_CASE("Firebird string coercions", "[firebird][string]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); try { @@ -1098,7 +1076,7 @@ void test12() "insert into test12(a, b, c, d) values (?, ?, ?, ?)", use(a), use(b), use(c), use(d)); st.execute(1); - assert(getRowCount(st, eRowsInserted) == 1); + CHECK(getRowCount(st, eRowsInserted) == 1); } { @@ -1106,38 +1084,47 @@ void test12() std::tm b, c, d; sql << "select a, b, c, d from test12", into(a), into(b), into(c), into(d); - assert(std::fabs(a - (-3.141)) < 0.000001); - assert(b.tm_year + 1900 == 2013 && b.tm_mon + 1 == 2 && b.tm_mday == 28); - assert(b.tm_hour == 23 && b.tm_min == 36 && b.tm_sec == 1); - assert(c.tm_year + 1900 == 2013 && c.tm_mon + 1 == 2 && c.tm_mday == 28); - assert(c.tm_hour == 0 && c.tm_min == 0 && c.tm_sec == 0); - assert(d.tm_hour == 23 && d.tm_min == 36 && d.tm_sec == 1); + CHECK(std::fabs(a - (-3.141)) < 0.000001); + CHECK(b.tm_year == 2013 - 1900); + CHECK(b.tm_mon == 2 - 1); + CHECK(b.tm_mday == 28); + CHECK(b.tm_hour == 23); + CHECK(b.tm_min == 36); + CHECK(b.tm_sec == 1); + CHECK(c.tm_year == 2013 - 1900); + CHECK(c.tm_mon == 2 - 1); + CHECK(c.tm_mday == 28); + CHECK(c.tm_hour == 0); + CHECK(c.tm_min == 0); + CHECK(c.tm_sec == 0); + CHECK(d.tm_hour == 23); + CHECK(d.tm_min == 36); + CHECK(d.tm_sec == 1); } sql << "drop table test12"; - std::cout << "test 12 passed" << std::endl; } // Dynamic binding to row objects: decimals_as_strings -void test13() +TEST_CASE("Firebird decimals as strings", "[firebird][decimal][string]") { using namespace soci::details::firebird; int a = -12345678; - assert(format_decimal(&a, 1) == "-123456780"); - assert(format_decimal(&a, 0) == "-12345678"); - assert(format_decimal(&a, -3) == "-12345.678"); - assert(format_decimal(&a, -8) == "-0.12345678"); - assert(format_decimal(&a, -9) == "-0.012345678"); + CHECK(format_decimal(&a, 1) == "-123456780"); + CHECK(format_decimal(&a, 0) == "-12345678"); + CHECK(format_decimal(&a, -3) == "-12345.678"); + CHECK(format_decimal(&a, -8) == "-0.12345678"); + CHECK(format_decimal(&a, -9) == "-0.012345678"); a = 12345678; - assert(format_decimal(&a, 1) == "123456780"); - assert(format_decimal(&a, 0) == "12345678"); - assert(format_decimal(&a, -3) == "12345.678"); - assert(format_decimal(&a, -8) == "0.12345678"); - assert(format_decimal(&a, -9) == "0.012345678"); + CHECK(format_decimal(&a, 1) == "123456780"); + CHECK(format_decimal(&a, 0) == "12345678"); + CHECK(format_decimal(&a, -3) == "12345.678"); + CHECK(format_decimal(&a, -8) == "0.12345678"); + CHECK(format_decimal(&a, -9) == "0.012345678"); - session sql(backEnd, connectString + " decimals_as_strings=1"); + soci::session sql(backEnd, connectString + " decimals_as_strings=1"); try { @@ -1155,7 +1142,7 @@ void test13() { row r; sql << "select * from test13", into(r); - assert(sql.got_data() == false); + CHECK(sql.got_data() == false); } std::string d_str0("+03.140"), d_str1("3.14"), @@ -1179,41 +1166,40 @@ void test13() statement st = (sql.prepare << "select * from test13", into(r)); st.execute(1); - assert(r.size() == 3); + CHECK(r.size() == 3); // get properties by position - assert(r.get_properties(0).get_name() == "NTEST1"); - assert(r.get_properties(0).get_data_type() == dt_string); - assert(r.get_properties(1).get_name() == "NTEST2"); - assert(r.get_properties(1).get_data_type() == dt_string); - assert(r.get_properties(2).get_name() == "NTEST3"); - assert(r.get_properties(2).get_data_type() == dt_string); + CHECK(r.get_properties(0).get_name() == "NTEST1"); + CHECK(r.get_properties(0).get_data_type() == dt_string); + CHECK(r.get_properties(1).get_name() == "NTEST2"); + CHECK(r.get_properties(1).get_data_type() == dt_string); + CHECK(r.get_properties(2).get_name() == "NTEST3"); + CHECK(r.get_properties(2).get_data_type() == dt_string); // get properties by name - assert(r.get_properties("NTEST1").get_name() == "NTEST1"); - assert(r.get_properties("NTEST1").get_data_type() == dt_string); - assert(r.get_properties("NTEST2").get_name() == "NTEST2"); - assert(r.get_properties("NTEST2").get_data_type() == dt_string); - assert(r.get_properties("NTEST3").get_name() == "NTEST3"); - assert(r.get_properties("NTEST3").get_data_type() == dt_string); + CHECK(r.get_properties("NTEST1").get_name() == "NTEST1"); + CHECK(r.get_properties("NTEST1").get_data_type() == dt_string); + CHECK(r.get_properties("NTEST2").get_name() == "NTEST2"); + CHECK(r.get_properties("NTEST2").get_data_type() == dt_string); + CHECK(r.get_properties("NTEST3").get_name() == "NTEST3"); + CHECK(r.get_properties("NTEST3").get_data_type() == dt_string); // get values by position - assert(r.get(0) == d_str1); - assert(r.get(1) == d_str2); - assert(r.get(2) == d_str3); + CHECK(r.get(0) == d_str1); + CHECK(r.get(1) == d_str2); + CHECK(r.get(2) == d_str3); // get values by name - assert(r.get("NTEST1") == d_str1); - assert(r.get("NTEST2") == d_str2); - assert(r.get("NTEST3") == d_str3); + CHECK(r.get("NTEST1") == d_str1); + CHECK(r.get("NTEST2") == d_str2); + CHECK(r.get("NTEST3") == d_str3); st.fetch(); - assert(r.get_indicator(0) == i_null); - assert(r.get_indicator(1) == i_ok); - assert(r.get_indicator(2) == i_ok); + CHECK(r.get_indicator(0) == i_null); + CHECK(r.get_indicator(1) == i_ok); + CHECK(r.get_indicator(2) == i_ok); sql << "drop table test13"; - std::cout << "test 13 passed" << std::endl; } // @@ -1222,11 +1208,12 @@ void test13() struct TableCreator1 : public tests::table_creator_base { - TableCreator1(session & sql) + TableCreator1(soci::session & sql) : tests::table_creator_base(sql) { sql << "create table soci_test(id integer, val integer, c char, " "str varchar(20), sh smallint, ul bigint, d double precision, " + "num76 numeric(7,6), " "tm timestamp, i1 integer, i2 integer, i3 integer, name varchar(20))"; sql.commit(); sql.begin(); @@ -1235,7 +1222,7 @@ struct TableCreator1 : public tests::table_creator_base struct TableCreator2 : public tests::table_creator_base { - TableCreator2(session & sql) + TableCreator2(soci::session & sql) : tests::table_creator_base(sql) { sql << "create table soci_test(num_float float, num_int integer, " @@ -1247,7 +1234,7 @@ struct TableCreator2 : public tests::table_creator_base struct TableCreator3 : public tests::table_creator_base { - TableCreator3(session & sql) + TableCreator3(soci::session & sql) : tests::table_creator_base(sql) { sql << "create table soci_test(name varchar(100) not null, " @@ -1259,7 +1246,7 @@ struct TableCreator3 : public tests::table_creator_base struct TableCreator4 : public tests::table_creator_base { - TableCreator4(session & sql) + TableCreator4(soci::session & sql) : tests::table_creator_base(sql) { sql << "create table soci_test(val integer)"; @@ -1276,22 +1263,22 @@ class test_context : public tests::test_context_base : test_context_base(backEnd, connectString) {} - tests::table_creator_base* table_creator_1(session& s) const + tests::table_creator_base* table_creator_1(soci::session& s) const { return new TableCreator1(s); } - tests::table_creator_base* table_creator_2(session& s) const + tests::table_creator_base* table_creator_2(soci::session& s) const { return new TableCreator2(s); } - tests::table_creator_base* table_creator_3(session& s) const + tests::table_creator_base* table_creator_3(soci::session& s) const { return new TableCreator3(s); } - tests::table_creator_base* table_creator_4(session& s) const + tests::table_creator_base* table_creator_4(soci::session& s) const { return new TableCreator4(s); } @@ -1300,6 +1287,11 @@ class test_context : public tests::test_context_base { return "'" + datdt_string + "'"; } + + virtual void on_after_ddl(soci::session& sql) const + { + sql.commit(); + } }; @@ -1309,53 +1301,33 @@ int main(int argc, char** argv) #ifdef _MSC_VER // Redirect errors, unrecoverable problems, and assert() failures to STDERR, // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. + // This hack is required to run assert()-driven tests by Buildbot. // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); #endif //_MSC_VER - if (argc == 2) + if (argc >= 2) { connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; } else { std::cout << "usage: " << argv[0] - << " connectstring\n" + << " connectstring [test-arguments...]\n" << "example: " << argv[0] << " \"service=/usr/local/firebird/db/test.fdb user=SYSDBA password=masterkey\"\n"; return EXIT_FAILURE; } - try - { - test_context tc(backEnd, connectString); - tests::common_tests tests(tc); - tests.run(); + test_context tc(backEnd, connectString); - std::cout << "\nSOCI Firebird Tests:\n\n"; - test1(); - test2(); - test3(); - test4(); - test5(); - test6(); - test7(); - test8(); - test9(); - test10(); - test11(); - test12(); - test13(); - - std::cout << "\nOK, all tests passed.\n\n"; - - return EXIT_SUCCESS; - } - catch (std::exception const & e) - { - std::cout << e.what() << '\n'; - } - return EXIT_FAILURE; + return Catch::Session().run(argc, argv); } diff --git a/src/backends/mysql/test/CMakeLists.txt b/tests/mysql/CMakeLists.txt similarity index 73% rename from src/backends/mysql/test/CMakeLists.txt rename to tests/mysql/CMakeLists.txt index 47dd07493b..533b61f68b 100644 --- a/src/backends/mysql/test/CMakeLists.txt +++ b/tests/mysql/CMakeLists.txt @@ -2,13 +2,15 @@ # # This file is part of CMake configuration for SOCI library # -# Copyright (C) 2010 Mateusz Loskot +# Copyright (C) 2010-2013 Mateusz Loskot # 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) # ############################################################################### + soci_backend_test( BACKEND MySQL - SOURCE test-mysql.cpp - CONNSTR "dummy") \ No newline at end of file + DEPENDS MySQL + SOURCE test-mysql.cpp ${SOCI_TESTS_COMMON} + CONNSTR "db=soci_test") diff --git a/src/backends/mysql/test/test-mysql.cpp b/tests/mysql/test-mysql.cpp similarity index 52% rename from src/backends/mysql/test/test-mysql.cpp rename to tests/mysql/test-mysql.cpp index 6e3e46f957..1f045e4d4a 100644 --- a/src/backends/mysql/test/test-mysql.cpp +++ b/tests/mysql/test-mysql.cpp @@ -6,14 +6,14 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci.h" -#include "soci-mysql.h" -#include "test/common-tests.h" +#include "soci/soci.h" +#include "soci/mysql/soci-mysql.h" +#include "mysql/test-mysql.h" +#include #include #include #include #include -#include #include #include #include @@ -21,86 +21,82 @@ #include #include -using namespace soci; -using namespace soci::tests; - std::string connectString; backend_factory const &backEnd = *soci::factory_mysql(); - // procedure call test -void test1() +TEST_CASE("MySQL stored procedures", "[mysql][stored-procedure]") { + soci::session sql(backEnd, connectString); + + mysql_session_backend *sessionBackEnd + = static_cast(sql.get_backend()); + std::string version = mysql_get_server_info(sessionBackEnd->conn_); + int v; + std::istringstream iss(version); + if ((iss >> v) and v < 5) { - session sql(backEnd, connectString); - - mysql_session_backend *sessionBackEnd - = static_cast(sql.get_backend()); - std::string version = mysql_get_server_info(sessionBackEnd->conn_); - int v; - std::istringstream iss(version); - if ((iss >> v) and v < 5) - { - std::cout << "skipping test 1 (MySQL server version "; - std::cout << version << " does not support stored procedures)\n"; - return; - } - - try { sql << "drop function myecho"; } - catch (soci_error const &) {} - - sql << - "create function myecho(msg text) " - "returns text deterministic " - " return msg; "; - - std::string in("my message"); - std::string out; - - statement st = (sql.prepare << - "select myecho(:input)", - into(out), - use(in, "input")); - - st.execute(1); - assert(out == in); - - // explicit procedure syntax - { - std::string in("my message2"); - std::string out; - - procedure proc = (sql.prepare << - "myecho(:input)", - into(out), use(in, "input")); - - proc.execute(1); - assert(out == in); - } - - sql << "drop function myecho"; + WARN("MySQL server version " << v + << " does not support stored procedures, skipping test."); + return; } - std::cout << "test 1 passed" << std::endl; + try { sql << "drop function myecho"; } + catch (soci_error const &) {} + + sql << + "create function myecho(msg text) " + "returns text deterministic " + " return msg; "; + + std::string in("my message"); + std::string out; + + statement st = (sql.prepare << + "select myecho(:input)", + into(out), + use(in, "input")); + + st.execute(1); + CHECK(out == in); + + // explicit procedure syntax + { + std::string in("my message2"); + std::string out; + + procedure proc = (sql.prepare << + "myecho(:input)", + into(out), use(in, "input")); + + proc.execute(1); + CHECK(out == in); + } + + sql << "drop function myecho"; } // MySQL error reporting test. -void test2() +TEST_CASE("MySQL error reporting", "[mysql][exception]") { { try { - session sql(backEnd, "host=test.soci.invalid"); + soci::session sql(backEnd, "host=test.soci.invalid"); } catch (mysql_soci_error const &e) { - assert(e.err_num_ == CR_UNKNOWN_HOST || - e.err_num_ == CR_CONN_HOST_ERROR); + if (e.err_num_ != CR_UNKNOWN_HOST && + e.err_num_ != CR_CONN_HOST_ERROR) + { + CAPTURE(e.err_num_); + FAIL("Unexpected error trying to connect to invalid host."); + } } } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); sql << "create table soci_test (id integer)"; try { @@ -109,7 +105,7 @@ void test2() } catch (mysql_soci_error const &e) { - assert(e.err_num_ == ER_NO_SUCH_TABLE); + CHECK(e.err_num_ == ER_NO_SUCH_TABLE); } try { @@ -117,7 +113,7 @@ void test2() } catch (mysql_soci_error const &e) { - assert(e.err_num_ == ER_BAD_FIELD_ERROR); + CHECK(e.err_num_ == ER_BAD_FIELD_ERROR); } // A bulk operation. try @@ -127,17 +123,15 @@ void test2() } catch (mysql_soci_error const &e) { - assert(e.err_num_ == ER_NO_SUCH_TABLE); + CHECK(e.err_num_ == ER_NO_SUCH_TABLE); } sql << "drop table soci_test"; } - - std::cout << "test 2 passed" << std::endl; } struct bigint_table_creator : table_creator_base { - bigint_table_creator(session & sql) + bigint_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val bigint)"; @@ -146,35 +140,32 @@ struct bigint_table_creator : table_creator_base struct bigint_unsigned_table_creator : table_creator_base { - bigint_unsigned_table_creator(session & sql) + bigint_unsigned_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val bigint unsigned)"; } }; -// long long test -void test3() +TEST_CASE("MySQL long long", "[mysql][longlong]") { { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); bigint_table_creator tableCreator(sql); long long v1 = 1000000000000LL; - assert(v1 / 1000000 == 1000000); - sql << "insert into soci_test(val) values(:val)", use(v1); long long v2 = 0LL; sql << "select val from soci_test", into(v2); - assert(v2 == v1); + CHECK(v2 == v1); } // vector { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); bigint_table_creator tableCreator(sql); @@ -190,16 +181,16 @@ void test3() std::vector v2(10); sql << "select val from soci_test order by val desc", into(v2); - assert(v2.size() == 5); - assert(v2[0] == 1000000000004LL); - assert(v2[1] == 1000000000003LL); - assert(v2[2] == 1000000000002LL); - assert(v2[3] == 1000000000001LL); - assert(v2[4] == 1000000000000LL); + REQUIRE(v2.size() == 5); + CHECK(v2[0] == 1000000000004LL); + CHECK(v2[1] == 1000000000003LL); + CHECK(v2[2] == 1000000000002LL); + CHECK(v2[3] == 1000000000001LL); + CHECK(v2[4] == 1000000000000LL); } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); bigint_unsigned_table_creator tableCreator(sql); @@ -209,7 +200,7 @@ void test3() } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); bigint_unsigned_table_creator tableCreator(sql); @@ -219,11 +210,11 @@ void test3() sql << "select val from soci_test", into(vv); std::stringstream buf; buf << vv; - assert(buf.str() == source); + CHECK(buf.str() == source); } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); bigint_unsigned_table_creator tableCreator(sql); @@ -233,11 +224,11 @@ void test3() sql << "select val from soci_test", into(v); std::stringstream buf; buf << v.at(0); - assert(buf.str() == source); + CHECK(buf.str() == source); } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); bigint_unsigned_table_creator tableCreator(sql); @@ -245,11 +236,11 @@ void test3() sql << "insert into soci_test(val) values (:n)", use(n); unsigned long long m = 0; sql << "select val from soci_test", into(m); - assert(n == m); + CHECK(n == m); } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); bigint_unsigned_table_creator tableCreator(sql); @@ -262,13 +253,11 @@ void test3() std::vector v2(10); sql << "select val from soci_test order by val", into(v2); - assert(v2.size() == 3); - assert(v2[0] == 18446744073709551613ULL); - assert(v2[1] == 18446744073709551614ULL); - assert(v2[2] == 18446744073709551615ULL); + REQUIRE(v2.size() == 3); + CHECK(v2[0] == 18446744073709551613ULL); + CHECK(v2[1] == 18446744073709551614ULL); + CHECK(v2[2] == 18446744073709551615ULL); } - - std::cout << "test 3 passed" << std::endl; } template @@ -276,7 +265,7 @@ void test_num(const char* s, bool valid, T value) { try { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); T val; sql << "select \'" << s << "\'", into(val); if (valid) @@ -285,32 +274,36 @@ void test_num(const char* s, bool valid, T value) double v2 = static_cast(val); double d = std::fabs(v1 - v2); double epsilon = 0.001; - assert(d < epsilon || - d < epsilon * (std::fabs(v1) + std::fabs(v2))); + if (d >= epsilon && + d >= epsilon * (std::fabs(v1) + std::fabs(v2))) + { + FAIL("Difference between " << value + << " and " << val << " is too big."); + } } else { - std::cout << "string \"" << s << "\" parsed as " << val - << " but should have failed.\n"; - assert(false); + FAIL("string \"" << s << "\" parsed as " << val + << " but should have failed."); } } catch (soci_error const& e) { if (valid) { - std::cout << "couldn't parse number: \"" << s << "\"\n"; - assert(false); + FAIL("couldn't parse number: \"" << s << "\""); } else { - assert(std::string(e.what()) == "Cannot convert data."); + char const * expectedPrefix = "Cannot convert data"; + CAPTURE(e.what()); + CHECK(strncmp(e.what(), expectedPrefix, strlen(expectedPrefix)) == 0); } } } // Number conversion test. -void test4() +TEST_CASE("MySQL number conversion", "[mysql][float][int]") { test_num("", false, 0); test_num("foo", false, 0); @@ -363,29 +356,25 @@ void test4() test_num("123", true, 123); test_num("9223372036854775807", true, 9223372036854775807LL); test_num("9223372036854775808", false, 0); - - std::cout << "test 4 passed" << std::endl; } -void test5() +TEST_CASE("MySQL datetime", "[mysql][datetime]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); std::tm t; sql << "select maketime(19, 54, 52)", into(t); - assert(t.tm_year == 100); - assert(t.tm_mon == 0); - assert(t.tm_mday == 1); - assert(t.tm_hour == 19); - assert(t.tm_min == 54); - assert(t.tm_sec == 52); - - std::cout << "test 5 passed" << std::endl; + CHECK(t.tm_year == 100); + CHECK(t.tm_mon == 0); + CHECK(t.tm_mday == 1); + CHECK(t.tm_hour == 19); + CHECK(t.tm_min == 54); + CHECK(t.tm_sec == 52); } // TEXT and BLOB types support test. -void test6() +TEST_CASE("MySQL text and blob", "[mysql][text][blob]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); std::string a("asdfg\0hjkl", 10); std::string b("lkjhg\0fd\0\0sa\0", 13); std::string c("\\0aa\\0bb\\0cc\\0", 10); @@ -414,133 +403,126 @@ void test6() sql << "select text_value, blob_value, longblob_value " << "from soci_test order by id", into(text_vec), into(blob_vec), into(longblob_vec); - assert(text_vec.size() == 4); - assert(blob_vec.size() == 4); - assert(longblob_vec.size() == 4); - assert(text_vec[0] == "foo"); - assert(blob_vec[0] == "bar"); - assert(longblob_vec[0] == "baz"); - assert(text_vec[1] == std::string("qwerty\0uiop", 11)); - assert(blob_vec[1] == std::string("zxcv\0bnm", 8)); - assert(longblob_vec[1] == std::string("qwerty\0uiop\0zxcvbnm\0", 20)); - assert(text_vec[2] == a); - assert(blob_vec[2] == b); - assert(longblob_vec[2] == c); - assert(text_vec[3] == x); - assert(blob_vec[3] == y); - assert(longblob_vec[3] == z); + REQUIRE(text_vec.size() == 4); + REQUIRE(blob_vec.size() == 4); + REQUIRE(longblob_vec.size() == 4); + CHECK(text_vec[0] == "foo"); + CHECK(blob_vec[0] == "bar"); + CHECK(longblob_vec[0] == "baz"); + CHECK(text_vec[1] == std::string("qwerty\0uiop", 11)); + CHECK(blob_vec[1] == std::string("zxcv\0bnm", 8)); + CHECK(longblob_vec[1] == std::string("qwerty\0uiop\0zxcvbnm\0", 20)); + CHECK(text_vec[2] == a); + CHECK(blob_vec[2] == b); + CHECK(longblob_vec[2] == c); + CHECK(text_vec[3] == x); + CHECK(blob_vec[3] == y); + CHECK(longblob_vec[3] == z); std::string text, blob, longblob; sql << "select text_value, blob_value, longblob_value " << "from soci_test where id = 1", into(text), into(blob), into(longblob); - assert(text == "foo"); - assert(blob == "bar"); - assert(longblob == "baz"); + CHECK(text == "foo"); + CHECK(blob == "bar"); + CHECK(longblob == "baz"); sql << "select text_value, blob_value, longblob_value " << "from soci_test where id = 2", into(text), into(blob), into(longblob); - assert(text == std::string("qwerty\0uiop", 11)); - assert(blob == std::string("zxcv\0bnm", 8)); - assert(longblob == std::string("qwerty\0uiop\0zxcvbnm\0", 20)); + CHECK(text == std::string("qwerty\0uiop", 11)); + CHECK(blob == std::string("zxcv\0bnm", 8)); + CHECK(longblob == std::string("qwerty\0uiop\0zxcvbnm\0", 20)); sql << "select text_value, blob_value, longblob_value " << "from soci_test where id = 3", into(text), into(blob), into(longblob); - assert(text == a); - assert(blob == b); - assert(longblob == c); + CHECK(text == a); + CHECK(blob == b); + CHECK(longblob == c); sql << "select text_value, blob_value, longblob_value " << "from soci_test where id = 4", into(text), into(blob), into(longblob); - assert(text == x); - assert(blob == y); - assert(longblob == z); + CHECK(text == x); + CHECK(blob == y); + CHECK(longblob == z); rowset rs = (sql.prepare << "select text_value, blob_value, longblob_value " "from soci_test order by id"); rowset::const_iterator r = rs.begin(); - assert(r->get_properties(0).get_data_type() == dt_string); - assert(r->get(0) == "foo"); - assert(r->get_properties(1).get_data_type() == dt_string); - assert(r->get(1) == "bar"); - assert(r->get_properties(2).get_data_type() == dt_string); - assert(r->get(2) == "baz"); + CHECK(r->get_properties(0).get_data_type() == dt_string); + CHECK(r->get(0) == "foo"); + CHECK(r->get_properties(1).get_data_type() == dt_string); + CHECK(r->get(1) == "bar"); + CHECK(r->get_properties(2).get_data_type() == dt_string); + CHECK(r->get(2) == "baz"); ++r; - assert(r->get_properties(0).get_data_type() == dt_string); - assert(r->get(0) == std::string("qwerty\0uiop", 11)); - assert(r->get_properties(1).get_data_type() == dt_string); - assert(r->get(1) == std::string("zxcv\0bnm", 8)); - assert(r->get_properties(2).get_data_type() == dt_string); - assert(r->get(2) == + CHECK(r->get_properties(0).get_data_type() == dt_string); + CHECK(r->get(0) == std::string("qwerty\0uiop", 11)); + CHECK(r->get_properties(1).get_data_type() == dt_string); + CHECK(r->get(1) == std::string("zxcv\0bnm", 8)); + CHECK(r->get_properties(2).get_data_type() == dt_string); + CHECK(r->get(2) == std::string("qwerty\0uiop\0zxcvbnm\0", 20)); ++r; - assert(r->get_properties(0).get_data_type() == dt_string); - assert(r->get(0) == a); - assert(r->get_properties(1).get_data_type() == dt_string); - assert(r->get(1) == b); - assert(r->get_properties(2).get_data_type() == dt_string); - assert(r->get(2) == c); + CHECK(r->get_properties(0).get_data_type() == dt_string); + CHECK(r->get(0) == a); + CHECK(r->get_properties(1).get_data_type() == dt_string); + CHECK(r->get(1) == b); + CHECK(r->get_properties(2).get_data_type() == dt_string); + CHECK(r->get(2) == c); ++r; - assert(r->get_properties(0).get_data_type() == dt_string); - assert(r->get(0) == x); - assert(r->get_properties(1).get_data_type() == dt_string); - assert(r->get(1) == y); - assert(r->get_properties(2).get_data_type() == dt_string); - assert(r->get(2) == z); + CHECK(r->get_properties(0).get_data_type() == dt_string); + CHECK(r->get(0) == x); + CHECK(r->get_properties(1).get_data_type() == dt_string); + CHECK(r->get(1) == y); + CHECK(r->get_properties(2).get_data_type() == dt_string); + CHECK(r->get(2) == z); ++r; - assert(r == rs.end()); + CHECK(r == rs.end()); sql << "drop table soci_test"; - - std::cout << "test 6 passed" << std::endl; } // test for number of affected rows struct integer_value_table_creator : table_creator_base { - integer_value_table_creator(session & sql) + integer_value_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val integer)"; } }; -void test7() +TEST_CASE("MySQL get affected rows", "[mysql][affected-rows]") { + soci::session sql(backEnd, connectString); + + integer_value_table_creator tableCreator(sql); + + for (int i = 0; i != 10; i++) { - session sql(backEnd, connectString); - - integer_value_table_creator tableCreator(sql); - - for (int i = 0; i != 10; i++) - { - sql << "insert into soci_test(val) values(:val)", use(i); - } - - statement st1 = (sql.prepare << - "update soci_test set val = val + 1"); - st1.execute(false); - - assert(st1.get_affected_rows() == 10); - - statement st2 = (sql.prepare << - "delete from soci_test where val <= 5"); - st2.execute(false); - - assert(st2.get_affected_rows() == 5); + sql << "insert into soci_test(val) values(:val)", use(i); } - std::cout << "test 7 passed" << std::endl; + statement st1 = (sql.prepare << + "update soci_test set val = val + 1"); + st1.execute(false); + + CHECK(st1.get_affected_rows() == 10); + + statement st2 = (sql.prepare << + "delete from soci_test where val <= 5"); + st2.execute(false); + + CHECK(st2.get_affected_rows() == 5); } // The prepared statements should survive session::reconnect(). -void test8() +TEST_CASE("MySQL statements after reconnect", "[mysql][connect]") { - { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); integer_value_table_creator tableCreator(sql); @@ -563,18 +545,15 @@ void test8() std::vector v(5); sql << "select val from soci_test order by val", into(v); - assert(v.size() == 3); - assert(v[0] == 5); - assert(v[1] == 6); - assert(v[2] == 7); - } - - std::cout << "test 8 passed" << std::endl; + REQUIRE(v.size() == 3); + CHECK(v[0] == 5); + CHECK(v[1] == 6); + CHECK(v[2] == 7); } struct unsigned_value_table_creator : table_creator_base { - unsigned_value_table_creator(session & sql) + unsigned_value_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val int unsigned)"; @@ -582,10 +561,9 @@ struct unsigned_value_table_creator : table_creator_base }; // rowset<> should be able to take INT UNSIGNED. -void test9() +TEST_CASE("MySQL unsigned int", "[mysql][int]") { - { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); unsigned_value_table_creator tableCreator(sql); @@ -598,99 +576,91 @@ void test9() { cnt++; } - assert(cnt == 1); - } - - std::cout << "test 9 passed" << std::endl; + CHECK(cnt == 1); } -void test10() +TEST_CASE("MySQL function call", "[mysql][function]") { - session sql(backEnd, connectString); - + soci::session sql(backEnd, connectString); + row r; - + sql << "set @day = '5'"; sql << "set @mm = 'december'"; - sql << "set @year = '2012'"; + sql << "set @year = '2012'"; sql << "select concat(@day,' ',@mm,' ',@year)", into(r); - - std::cout << "test 10 passed" << std::endl; } struct double_value_table_creator : table_creator_base { - double_value_table_creator(session & sql) + double_value_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val double)"; } }; -void test11() +TEST_CASE("MySQL special floating point values", "[mysql][float]") { + static bool is_iec559 = std::numeric_limits::is_iec559; + if (!is_iec559) + { + WARN("C++ double type is not IEC-559, skipping test."); + return; + } + const std::string expectedError = - "Use element used with infinity or NaN, which are " + "Use element used with infinity or NaN, which are " "not supported by the MySQL server."; { - session sql(backEnd, connectString); - + soci::session sql(backEnd, connectString); + double x = std::numeric_limits::quiet_NaN(); statement st = (sql.prepare << "SELECT :x", use(x, "x")); try { st.execute(true); } catch (soci_error const &e) { - if (e.what() != expectedError) { - throw; - } + CHECK(e.get_error_message() == expectedError); } } { - session sql(backEnd, connectString); - + soci::session sql(backEnd, connectString); + double x = std::numeric_limits::infinity(); statement st = (sql.prepare << "SELECT :x", use(x, "x")); try { st.execute(true); } catch (soci_error const &e) { - if (e.what() != expectedError) { - throw; - } + CHECK(e.get_error_message() == expectedError); } } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); double_value_table_creator tableCreator(sql); - + std::vector v(1, std::numeric_limits::quiet_NaN()); try { sql << "insert into soci_test (val) values (:val)", use(v); } catch (soci_error const &e) { - if (e.what() != expectedError) { - throw; - } + CHECK(e.get_error_message() == expectedError); } } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); double_value_table_creator tableCreator(sql); - + std::vector v(1, std::numeric_limits::infinity()); try { sql << "insert into soci_test (val) values (:val)", use(v); } catch (soci_error const &e) { - if (e.what() != expectedError) { - throw; - } + CHECK(e.get_error_message() == expectedError); } } - - std::cout << "test 11 passed" << std::endl; } struct tinyint_value_table_creator : table_creator_base { - tinyint_value_table_creator(session & sql) + tinyint_value_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val tinyint)"; @@ -699,74 +669,72 @@ struct tinyint_value_table_creator : table_creator_base struct tinyint_unsigned_value_table_creator : table_creator_base { - tinyint_unsigned_value_table_creator(session & sql) + tinyint_unsigned_value_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val tinyint unsigned)"; } }; -void test12() +TEST_CASE("MySQL tinyint", "[mysql][int][tinyint]") { { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); unsigned_value_table_creator tableCreator(sql); unsigned int mask = 0xffffff00; sql << "insert into soci_test set val = " << mask; row r; sql << "select val from soci_test", into(r); - assert(r.size() == 1); - assert(r.get_properties("val").get_data_type() == dt_long_long); - assert(r.get("val") == 0xffffff00); - assert(r.get("val") == 0xffffff00); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_long_long); + CHECK(r.get("val") == 0xffffff00); + CHECK(r.get("val") == 0xffffff00); } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); tinyint_value_table_creator tableCreator(sql); sql << "insert into soci_test set val = -123"; row r; sql << "select val from soci_test", into(r); - assert(r.size() == 1); - assert(r.get_properties("val").get_data_type() == dt_integer); - assert(r.get("val") == -123); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_integer); + CHECK(r.get("val") == -123); } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); tinyint_unsigned_value_table_creator tableCreator(sql); sql << "insert into soci_test set val = 123"; row r; sql << "select val from soci_test", into(r); - assert(r.size() == 1); - assert(r.get_properties("val").get_data_type() == dt_integer); - assert(r.get("val") == 123); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_integer); + CHECK(r.get("val") == 123); } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); bigint_unsigned_table_creator tableCreator(sql); sql << "insert into soci_test set val = 123456789012345"; row r; sql << "select val from soci_test", into(r); - assert(r.size() == 1); - assert(r.get_properties("val").get_data_type() == dt_unsigned_long_long); - assert(r.get("val") == 123456789012345ULL); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_unsigned_long_long); + CHECK(r.get("val") == 123456789012345ULL); } { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); bigint_table_creator tableCreator(sql); sql << "insert into soci_test set val = -123456789012345"; row r; sql << "select val from soci_test", into(r); - assert(r.size() == 1); - assert(r.get_properties("val").get_data_type() == dt_long_long); - assert(r.get("val") == -123456789012345LL); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_long_long); + CHECK(r.get("val") == -123456789012345LL); } - - std::cout << "test 12 passed" << std::endl; } struct strings_table_creator : table_creator_base { - strings_table_creator(session & sql) + strings_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(s1 char(20), s2 varchar(20), " @@ -776,37 +744,55 @@ struct strings_table_creator : table_creator_base } }; -void test13() +TEST_CASE("MySQL strings", "[mysql][string]") { - { - session sql(backEnd, connectString); - strings_table_creator tableCreator(sql); - std::string text = "Ala ma kota."; - std::string binary("Ala\0ma\0kota.........", 20); - sql << "insert into soci_test " - "(s1, s2, s3, s4, s5, s6, b1, b2, b3, b4, b5, b6, e1) values " - "(:s1, :s2, :s3, :s4, :d5, :s6, :b1, :b2, :b3, :b4, :b5, :b6, " - "\'foo\')", - use(text), use(text), use(text), use(text), use(text), use(text), - use(binary), use(binary), use(binary), use(binary), use(binary), - use(binary); - row r; - sql << "select s1, s2, s3, s4, s5, s6, b1, b2, b3, b4, b5, b6, e1 " - "from soci_test", into(r); - assert(r.size() == 13); - for (int i = 0; i < 13; i++) { - assert(r.get_properties(i).get_data_type() == dt_string); - if (i < 6) { - assert(r.get(i) == text); - } else if (i < 12) { - assert(r.get(i) == binary); - } else { - assert(r.get(i) == "foo"); - } + soci::session sql(backEnd, connectString); + strings_table_creator tableCreator(sql); + std::string text = "Ala ma kota."; + std::string binary("Ala\0ma\0kota.........", 20); + sql << "insert into soci_test " + "(s1, s2, s3, s4, s5, s6, b1, b2, b3, b4, b5, b6, e1) values " + "(:s1, :s2, :s3, :s4, :d5, :s6, :b1, :b2, :b3, :b4, :b5, :b6, " + "\'foo\')", + use(text), use(text), use(text), use(text), use(text), use(text), + use(binary), use(binary), use(binary), use(binary), use(binary), + use(binary); + row r; + sql << "select s1, s2, s3, s4, s5, s6, b1, b2, b3, b4, b5, b6, e1 " + "from soci_test", into(r); + REQUIRE(r.size() == 13); + for (int i = 0; i < 13; i++) { + CHECK(r.get_properties(i).get_data_type() == dt_string); + if (i < 6) { + CHECK(r.get(i) == text); + } else if (i < 12) { + CHECK(r.get(i) == binary); + } else { + CHECK(r.get(i) == "foo"); } } - - std::cout << "test 13 passed" << std::endl; +} + +struct table_creator_for_get_last_insert_id : table_creator_base +{ + table_creator_for_get_last_insert_id(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer not null auto_increment, " + "primary key (id))"; + sql << "alter table soci_test auto_increment = 42"; + } +}; + +TEST_CASE("MySQL last insert id", "[mysql][last-insert-id]") +{ + soci::session sql(backEnd, connectString); + table_creator_for_get_last_insert_id tableCreator(sql); + sql << "insert into soci_test () values ()"; + long id; + bool result = sql.get_last_insert_id("soci_test", id); + CHECK(result == true); + CHECK(id == 42); } std::string escape_string(soci::session& sql, const std::string& s) @@ -814,7 +800,7 @@ std::string escape_string(soci::session& sql, const std::string& s) mysql_session_backend* backend = static_cast( sql.get_backend()); char* escaped = new char[2 * s.size() + 1]; - mysql_real_escape_string(backend->conn_, escaped, s.data(), s.size()); + mysql_real_escape_string(backend->conn_, escaped, s.data(), static_cast(s.size())); std::string retv = escaped; delete [] escaped; return retv; @@ -823,7 +809,7 @@ std::string escape_string(soci::session& sql, const std::string& s) void test14() { { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); strings_table_creator tableCreator(sql); std::string s = "word1'word2:word3"; std::string escaped = escape_string(sql, s); @@ -833,7 +819,7 @@ void test14() sql << query; std::string s2; sql << "select s5 from soci_test", into(s2); - assert(s == s2); + CHECK(s == s2); } std::cout << "test 14 passed" << std::endl; @@ -842,161 +828,38 @@ void test14() void test15() { { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); int n; sql << "select @a := 123", into(n); - assert(n == 123); + CHECK(n == 123); } - + std::cout << "test 15 passed" << std::endl; } -// DDL Creation objects for common tests -struct table_creator_one : public table_creator_base -{ - table_creator_one(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(id integer, val integer, c char, " - "str varchar(20), sh int2, ul numeric(20), d float8, " - "tm datetime, i1 integer, i2 integer, i3 integer, " - "name varchar(20)) engine=InnoDB"; - } -}; - -struct table_creator_two : public table_creator_base -{ - table_creator_two(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(num_float float8, num_int integer," - " name varchar(20), sometime datetime, chr char)"; - } -}; - -struct table_creator_three : public table_creator_base -{ - table_creator_three(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(name varchar(100) not null, " - "phone varchar(15))"; - } -}; - -struct table_creator_for_get_affected_rows : table_creator_base -{ - table_creator_for_get_affected_rows(session & sql) - : table_creator_base(sql) - { - sql << "create table soci_test(val integer)"; - } -}; - -// -// Support for SOCI Common Tests -// - -class test_context : public test_context_base -{ -public: - test_context(backend_factory const &backEnd, - std::string const &connectString) - : test_context_base(backEnd, connectString) {} - - table_creator_base* table_creator_1(session& s) const - { - return new table_creator_one(s); - } - - table_creator_base* table_creator_2(session& s) const - { - return new table_creator_two(s); - } - - table_creator_base* table_creator_3(session& s) const - { - return new table_creator_three(s); - } - - table_creator_base* table_creator_4(session& s) const - { - return new table_creator_for_get_affected_rows(s); - } - - std::string to_date_time(std::string const &datdt_string) const - { - return "\'" + datdt_string + "\'"; - } - -}; - -bool are_transactions_supported() -{ - session sql(backEnd, connectString); - sql << "drop table if exists soci_test"; - sql << "create table soci_test (id int) engine=InnoDB"; - row r; - sql << "show table status like \'soci_test\'", into(r); - bool retv = (r.get(1) == "InnoDB"); - sql << "drop table soci_test"; - return retv; -} - int main(int argc, char** argv) { - if (argc == 2) + if (argc >= 2) { connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; } else { std::cout << "usage: " << argv[0] - << " connectstring\n" + << " connectstring [test-arguments...]\n" << "example: " << argv[0] << " \"dbname=test user=root password=\'Ala ma kota\'\"\n"; std::exit(1); } - try - { - test_context tc(backEnd, connectString); - common_tests tests(tc); - bool checkTransactions = are_transactions_supported(); - tests.run(checkTransactions); + test_context tc(backEnd, connectString); - std::cout << "\nSOCI MySQL Tests:\n\n"; - - test1(); - test2(); - test3(); - test4(); - test5(); - test6(); - test7(); - // Test 8 commented out because a bug in soci can make it crash - // https://github.com/SOCI/soci/issues/136 - //test8(); - test9(); - test10(); - if (std::numeric_limits::is_iec559) { - test11(); - } else { - std::cout << "Skipping test11 " - << "(C++ implementation's double type is not IEC-559)\n"; - } - test12(); - test13(); - test14(); - test15(); - - std::cout << "\nOK, all tests passed.\n\n"; - return EXIT_SUCCESS; - } - catch (std::exception const & e) - { - std::cout << e.what() << '\n'; - } - - return EXIT_FAILURE; + return Catch::Session().run(argc, argv); } diff --git a/tests/mysql/test-mysql.h b/tests/mysql/test-mysql.h new file mode 100644 index 0000000000..32037d0b02 --- /dev/null +++ b/tests/mysql/test-mysql.h @@ -0,0 +1,132 @@ +#ifndef SOCI_TESTS_MYSQL_H_INCLUDED +#define SOCI_TESTS_MYSQL_H_INCLUDED + +#include "common-tests.h" + +using namespace soci; +using namespace soci::tests; + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh int2, ul numeric(20), d float8, " + "num76 numeric(7,6), " + "tm datetime, i1 integer, i2 integer, i3 integer, " + "name varchar(20)) engine=InnoDB"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float8, num_int integer," + " name varchar(20), sometime datetime, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base* table_creator_1(soci::session& s) const + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(soci::session& s) const + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(soci::session& s) const + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(soci::session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "\'" + datdt_string + "\'"; + } + + virtual bool has_fp_bug() const + { + // 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 + { + sql << "drop table if exists soci_test"; + sql << "create table soci_test (id int) engine=InnoDB"; + row r; + sql << "show table status like \'soci_test\'", into(r); + bool retv = (r.get(1) == "InnoDB"); + sql << "drop table soci_test"; + return retv; + } + + virtual bool has_silent_truncate_bug(soci::session& sql) const + { + std::string sql_mode; + sql << "select @@session.sql_mode", into(sql_mode); + + // The database must be configured to use STRICT_{ALL,TRANS}_TABLES in + // SQL mode to avoid silent truncation of too long values. + return sql_mode.find("STRICT_") == std::string::npos; + } + + virtual bool enable_std_char_padding(session& sql) const + { + // turn on standard right padding on mysql. This options is supported as of version 5.1.20 + try + { + sql << "SET @@session.sql_mode = 'PAD_CHAR_TO_FULL_LENGTH'"; + return true; + } + catch(const soci_error&) + { + // Padding cannot be enabled - test will not be performed + return false; + } + } +}; + +#endif // SOCI_TESTS_MYSQL_H_INCLUDED diff --git a/src/backends/odbc/test/CMakeLists.txt b/tests/odbc/CMakeLists.txt similarity index 66% rename from src/backends/odbc/test/CMakeLists.txt rename to tests/odbc/CMakeLists.txt index 01f8ae672f..80238763eb 100644 --- a/src/backends/odbc/test/CMakeLists.txt +++ b/tests/odbc/CMakeLists.txt @@ -2,7 +2,7 @@ # # This file is part of CMake configuration for SOCI library # -# Copyright (C) 2010 Mateusz Loskot +# Copyright (C) 2010-2013 Mateusz Loskot # 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,27 +14,32 @@ if (WIN32) soci_backend_test( NAME access BACKEND ODBC - SOURCE test-odbc-access.cpp + DEPENDS ODBC + SOURCE test-odbc-access.cpp ${SOCI_TESTS_COMMON} CONNSTR "test-access.dsn") - - # We have no means to test SQL Server at travis-ci.org - soci_backend_test( - NAME mssql - BACKEND ODBC - SOURCE test-odbc-mssql.cpp - CONNSTR "test-mssql.dsn") +else() + message(STATUS "MS Access test disabled on non-Windows platform") endif() +soci_backend_test( + NAME mssql + BACKEND ODBC + DEPENDS ODBC + SOURCE test-odbc-mssql.cpp ${SOCI_TESTS_COMMON} + CONNSTR "test-mssql.dsn") + soci_backend_test( NAME mysql BACKEND ODBC - SOURCE test-odbc-mysql.cpp + DEPENDS ODBC + SOURCE test-odbc-mysql.cpp ${SOCI_TESTS_COMMON} CONNSTR "test-mysql.dsn") soci_backend_test( NAME postgresql BACKEND ODBC - SOURCE test-odbc-postgresql.cpp + DEPENDS ODBC + SOURCE test-odbc-postgresql.cpp ${SOCI_TESTS_COMMON} CONNSTR "test-postgresql.dsn") # TODO: DB2 backend is tested by Travis CI on dedicated VM, separate from ODBC, diff --git a/src/backends/odbc/test/test-access.dsn b/tests/odbc/test-access.dsn similarity index 100% rename from src/backends/odbc/test/test-access.dsn rename to tests/odbc/test-access.dsn diff --git a/src/backends/odbc/test/test-mssql.dsn b/tests/odbc/test-mssql.dsn similarity index 100% rename from src/backends/odbc/test/test-mssql.dsn rename to tests/odbc/test-mssql.dsn diff --git a/src/backends/odbc/test/test-mysql.dsn b/tests/odbc/test-mysql.dsn similarity index 100% rename from src/backends/odbc/test/test-mysql.dsn rename to tests/odbc/test-mysql.dsn diff --git a/src/backends/odbc/test/test-odbc-access.cpp b/tests/odbc/test-odbc-access.cpp similarity index 66% rename from src/backends/odbc/test/test-odbc-access.cpp rename to tests/odbc/test-odbc-access.cpp index 9b8b41b6cf..fa5b05db8f 100644 --- a/src/backends/odbc/test/test-odbc-access.cpp +++ b/tests/odbc/test-odbc-access.cpp @@ -5,29 +5,46 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci.h" -#include "soci-odbc.h" +#include "soci/soci.h" +#include "soci/odbc/soci-odbc.h" #include "common-tests.h" #include #include -#include #include #include 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(); // DDL Creation objects for common tests struct table_creator_one : public table_creator_base { - table_creator_one(session & sql) + table_creator_one(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(id integer, val integer, c char, " "str varchar(20), sh integer, ul number, d float, " + "num76 numeric(7,6), " "tm timestamp, i1 integer, i2 integer, i3 integer, " "name varchar(20))"; } @@ -35,7 +52,7 @@ struct table_creator_one : public table_creator_base struct table_creator_two : public table_creator_base { - table_creator_two(session & sql) + table_creator_two(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(num_float float, num_int integer," @@ -45,7 +62,7 @@ struct table_creator_two : public table_creator_base struct table_creator_three : public table_creator_base { - table_creator_three(session & sql) + table_creator_three(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(name varchar(100) not null, " @@ -55,7 +72,7 @@ struct table_creator_three : public table_creator_base struct table_creator_for_get_affected_rows : table_creator_base { - table_creator_for_get_affected_rows(session & sql) + table_creator_for_get_affected_rows(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val integer)"; @@ -69,26 +86,26 @@ struct table_creator_for_get_affected_rows : table_creator_base class test_context : public test_context_base { public: - + test_context(backend_factory const &backEnd, std::string const &connectString) : test_context_base(backEnd, connectString) {} - table_creator_base * table_creator_1(session& s) const - { + table_creator_base * table_creator_1(soci::session& s) const + { return new table_creator_one(s); } - table_creator_base * table_creator_2(session& s) const + table_creator_base * table_creator_2(soci::session& s) const { return new table_creator_two(s); } - table_creator_base * table_creator_3(session& s) const + table_creator_base * table_creator_3(soci::session& s) const { return new table_creator_three(s); } - table_creator_base * table_creator_4(session& s) const + table_creator_base * table_creator_4(soci::session& s) const { return new table_creator_for_get_affected_rows(s); } @@ -115,41 +132,29 @@ int main(int argc, char** argv) #ifdef _MSC_VER // Redirect errors, unrecoverable problems, and assert() failures to STDERR, // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. + // This hack is required to run assert()-driven tests by Buildbot. // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); #endif //_MSC_VER - if (argc == 2) + if (argc >= 2 && argv[1][0] != '-') { connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; } else { connectString = "FILEDSN=./test-access.dsn"; } - try - { - std::cout << "\nSOCI ODBC with MS Access Tests:\n\n"; - test_context tc(backEnd, connectString); - common_tests tests(tc); - tests.run(); + test_context tc(backEnd, connectString); - std::cout << "\nOK, all tests passed.\n\n"; - return EXIT_SUCCESS; - } - catch (soci::odbc_soci_error const & e) - { - std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl - << "Native Error Code: " << e.native_error_code() << std::endl - << "SOCI Message: " << e.what() << std::endl - << "ODBC Message: " << e.odbc_error_message() << std::endl; - } - catch (std::exception const & e) - { - std::cout << "STD::EXECEPTION " << e.what() << '\n'; - } - return EXIT_FAILURE; + return Catch::Session().run(argc, argv); } diff --git a/src/backends/odbc/test/test-odbc-db2.cpp b/tests/odbc/test-odbc-db2.cpp similarity index 71% rename from src/backends/odbc/test/test-odbc-db2.cpp rename to tests/odbc/test-odbc-db2.cpp index d5bbc2e5f0..598ff32ec1 100644 --- a/src/backends/odbc/test/test-odbc-db2.cpp +++ b/tests/odbc/test-odbc-db2.cpp @@ -5,12 +5,11 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci.h" -#include "soci-odbc.h" +#include "soci/soci.h" +#include "soci/odbc/soci-odbc.h" #include "common-tests.h" #include #include -#include #include #include @@ -23,17 +22,18 @@ backend_factory const &backEnd = *soci::factory_odbc(); // DDL Creation objects for common tests struct table_creator_one : public table_creator_base { - table_creator_one(session & sql) + table_creator_one(soci::session & sql) : table_creator_base(sql) { sql << "CREATE TABLE SOCI_TEST(ID INTEGER, VAL SMALLINT, C CHAR, STR VARCHAR(20), SH SMALLINT, UL NUMERIC(20), D DOUBLE, " + "NUM76 NUMERIC(7,6), " "TM TIMESTAMP(9), I1 INTEGER, I2 INTEGER, I3 INTEGER, NAME VARCHAR(20))"; } }; struct table_creator_two : public table_creator_base { - table_creator_two(session & sql) + table_creator_two(soci::session & sql) : table_creator_base(sql) { sql << "CREATE TABLE SOCI_TEST(NUM_FLOAT DOUBLE, NUM_INT INTEGER, NAME VARCHAR(20), SOMETIME TIMESTAMP, CHR CHAR)"; @@ -42,7 +42,7 @@ struct table_creator_two : public table_creator_base struct table_creator_three : public table_creator_base { - table_creator_three(session & sql) + table_creator_three(soci::session & sql) : table_creator_base(sql) { sql << "CREATE TABLE SOCI_TEST(NAME VARCHAR(100) NOT NULL, PHONE VARCHAR(15))"; @@ -51,7 +51,7 @@ struct table_creator_three : public table_creator_base struct table_creator_for_get_affected_rows : table_creator_base { - table_creator_for_get_affected_rows(session & sql) + table_creator_for_get_affected_rows(soci::session & sql) : table_creator_base(sql) { sql << "CREATE TABLE SOCI_TEST(VAL INTEGER)"; @@ -69,22 +69,22 @@ public: std::string const &connectString) : test_context_base(backEnd, connectString) {} - table_creator_base * table_creator_1(session& s) const + table_creator_base * table_creator_1(soci::session& s) const { return new table_creator_one(s); } - table_creator_base * table_creator_2(session& s) const + table_creator_base * table_creator_2(soci::session& s) const { return new table_creator_two(s); } - table_creator_base * table_creator_3(session& s) const + table_creator_base * table_creator_3(soci::session& s) const { return new table_creator_three(s); } - table_creator_base * table_creator_4(session& s) const + table_creator_base * table_creator_4(soci::session& s) const { return new table_creator_for_get_affected_rows(s); } @@ -97,17 +97,17 @@ public: struct table_creator_bigint : table_creator_base { - table_creator_bigint(session & sql) + table_creator_bigint(soci::session & sql) : table_creator_base(sql) { sql << "CREATE TABLE SOCI_TEST (VAL BIGINT)"; } }; -void test_odbc_db2_long_long() +TEST_CASE("ODBC/DB2 long long", "[odbc][db2][longlong]") { const int num_recs = 100; - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); table_creator_bigint table(sql); { @@ -128,17 +128,15 @@ void test_odbc_db2_long_long() for (int i = 0; i < num_recs; i++) { st.fetch(); - assert(n2 == 1000000000LL + i); + CHECK(n2 == 1000000000LL + i); } } - - std::cout << "test odbc_db2_long_long passed" << std::endl; } -void test_odbc_db2_unsigned_long_long() +TEST_CASE("ODBC/DB2 unsigned long long", "[odbc][db2][unsigned][longlong]") { const int num_recs = 100; - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); table_creator_bigint table(sql); { @@ -159,17 +157,15 @@ void test_odbc_db2_unsigned_long_long() for (int i = 0; i < num_recs; i++) { st.fetch(); - assert(n2 == 1000000000LL + i); + CHECK(n2 == 1000000000LL + i); } } - - std::cout << "test odbc_db2_unsigned_long_long passed" << std::endl; } -void test_odbc_db2_long_long_vector() +TEST_CASE("ODBC/DB2 vector long long", "[odbc][db2][vector][longlong]") { const std::size_t num_recs = 100; - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); table_creator_bigint table(sql); { @@ -198,21 +194,19 @@ void test_odbc_db2_long_long_vector() const std::size_t vsize = v.size(); for (std::size_t i = 0; i < vsize; i++) { - assert(v[i] == 1000000000LL + + CHECK(v[i] == 1000000000LL + static_cast(recs)); recs++; } } - assert(recs == num_recs); + CHECK(recs == num_recs); } - - std::cout << "test odbc_db2_long_long_vector passed" << std::endl; } -void test_odbc_db2_unsigned_long_long_vector() +TEST_CASE("ODBC/DB2 vector unsigned long long", "[odbc][db2][vector][unsigned][longlong]") { const std::size_t num_recs = 100; - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); table_creator_bigint table(sql); { @@ -241,12 +235,12 @@ void test_odbc_db2_unsigned_long_long_vector() const std::size_t vsize = v.size(); for (std::size_t i = 0; i < vsize; i++) { - assert(v[i] == 1000000000LL + + CHECK(v[i] == 1000000000LL + static_cast(recs)); recs++; } } - assert(recs == num_recs); + CHECK(recs == num_recs); } std::cout << "test odbc_db2_unsigned_long_long_vector passed" << std::endl; @@ -257,15 +251,22 @@ int main(int argc, char** argv) #ifdef _MSC_VER // Redirect errors, unrecoverable problems, and assert() failures to STDERR, // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. + // This hack is required to run assert()-driven tests by Buildbot. // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); #endif //_MSC_VER - if (argc == 2) + if (argc >= 2) { connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; } else { @@ -274,33 +275,8 @@ int main(int argc, char** argv) std::endl << std::endl; return EXIT_FAILURE; } - try - { - std::cout << "\nSOCI ODBC with DB2 Tests:\n\n"; - test_context tc(backEnd, connectString); - common_tests tests(tc); - tests.run(); + test_context tc(backEnd, connectString); - std::cout << "\nSOCI DB2 Specific Tests:\n\n"; - test_odbc_db2_long_long(); - test_odbc_db2_unsigned_long_long(); - test_odbc_db2_long_long_vector(); - test_odbc_db2_unsigned_long_long_vector(); - - std::cout << "\nOK, all tests passed.\n\n"; - return EXIT_SUCCESS; - } - catch (soci::odbc_soci_error const & e) - { - std::cout << "ODBC Error Code: " << e.odbc_error_code() << std::endl - << "Native Error Code: " << e.native_error_code() << std::endl - << "SOCI Message: " << e.what() << std::endl - << "ODBC Message: " << e.odbc_error_message() << std::endl; - } - catch (std::exception const & e) - { - std::cout << "STD::EXECEPTION " << e.what() << '\n'; - } - return EXIT_FAILURE; + return Catch::Session().run(argc, argv); } diff --git a/tests/odbc/test-odbc-mssql.cpp b/tests/odbc/test-odbc-mssql.cpp new file mode 100644 index 0000000000..f4511c9ebe --- /dev/null +++ b/tests/odbc/test-odbc-mssql.cpp @@ -0,0 +1,195 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// 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.h" +#include "soci/odbc/soci-odbc.h" +#include "common-tests.h" +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_odbc(); + +// MS SQL-specific tests +TEST_CASE("MS SQL long string", "[odbc][mssql][long]") +{ + session sql(backEnd, connectString); + + struct long_text_table_creator : public table_creator_base + { + explicit long_text_table_creator(session& sql) + : table_creator_base(sql) + { + // Notice that 4000 is the maximal length of an nvarchar() column, + // at least when using FreeTDS ODBC driver. + sql << "create table soci_test (" + "long_text nvarchar(max) null, " + "fixed_text nvarchar(4000) null" + ")"; + } + } long_text_table_creator(sql); + + // Build a string at least 8000 characters long to test that it survives + // the round trip unscathed. + std::ostringstream os; + for ( int n = 0; n < 1000; ++n ) + { + os << "Line #" << n << "\n"; + } + + std::string const str_in = os.str(); + sql << "insert into soci_test(long_text) values(:str)", use(str_in); + + std::string str_out; + sql << "select long_text from soci_test", into(str_out); + + // Don't just compare the strings because the error message in case they + // differ is completely unreadable due to their size, so give a better + // error in the common failure case. + if (str_out.length() != str_in.length()) + { + FAIL("Read back string of length " << str_out.length() << + " instead of expected " << str_in.length()); + } + else + { + CHECK(str_out == str_in); + } + + // The long string should be truncated when inserting it into a fixed size + // column. + CHECK_THROWS_AS( + (sql << "insert into soci_test(fixed_text) values(:str)", use(str_in)), + soci_error + ); +} + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh smallint, ul numeric(20), d float, " + "num76 numeric(7,6), " + "tm datetime, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float, num_int integer," + " name varchar(20), sometime datetime, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base* table_creator_1(soci::session& s) const + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(soci::session& s) const + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(soci::session& s) const + { + return new table_creator_three(s); + } + + table_creator_base * table_creator_4(soci::session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "convert(datetime, \'" + datdt_string + "\', 120)"; + } + + virtual bool has_multiple_select_bug() const + { + // MS SQL does support MARS (multiple active result sets) since 2005 + // version, but this support needs to be explicitly enabled and is not + // implemented in FreeTDS ODBC driver used under Unix currently, so err + // on the side of caution and suppose that it's not supported. + return true; + } +}; + +int main(int argc, char** argv) +{ +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run assert()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc >= 2 && argv[1][0] != '-') + { + connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; + } + else + { + connectString = "FILEDSN=./test-mssql.dsn"; + } + + test_context tc(backEnd, connectString); + + return Catch::Session().run(argc, argv); +} diff --git a/tests/odbc/test-odbc-mysql.cpp b/tests/odbc/test-odbc-mysql.cpp new file mode 100644 index 0000000000..9ea70adf6e --- /dev/null +++ b/tests/odbc/test-odbc-mysql.cpp @@ -0,0 +1,49 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// 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.h" +#include "soci/odbc/soci-odbc.h" +#include "mysql/test-mysql.h" +#include +#include +#include +#include + +std::string connectString; +backend_factory const &backEnd = *soci::factory_odbc(); + +int main(int argc, char** argv) +{ +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run assert()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc >= 2 && argv[1][0] != '-') + { + connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; + } + else + { + connectString = "FILEDSN=./test-mysql.dsn"; + } + + test_context tc(backEnd, connectString); + + return Catch::Session().run(argc, argv); +} diff --git a/tests/odbc/test-odbc-postgresql.cpp b/tests/odbc/test-odbc-postgresql.cpp new file mode 100644 index 0000000000..6ff33ec45e --- /dev/null +++ b/tests/odbc/test-odbc-postgresql.cpp @@ -0,0 +1,249 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// 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.h" +#include "soci/odbc/soci-odbc.h" +#include "common-tests.h" +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +// A generic version class: we might want to factor it out later if it is +// needed elsewhere (it would probably also need to be renamed to something +// less generic then). +class odbc_version +{ +public: + odbc_version() + { + initialized_ = false; + } + + odbc_version(unsigned major, unsigned minor, unsigned release) + : major_(major), minor_(minor), release_(release) + { + initialized_ = true; + } + + bool init_from_string(char const* s) + { + initialized_ = std::sscanf(s, "%u.%u.%u", + &major_, &minor_, &release_) == 3; + return initialized_; + } + + bool is_initialized() const { return initialized_; } + + std::string as_string() const + { + if (initialized_) + { + char buf[128]; + // This uses the ODBC convention of padding the minor and release + // versions with 0 and might be not appropriate in general. + std::sprintf(buf, "%u.%02u.%04u", major_, minor_, release_); + return buf; + } + else + { + return "(uninitialized)"; + } + } + + // Compare versions using the lexicographical sort order, with + // uninitialized version considered less than any initialized one. + bool operator<(odbc_version const& v) const + { + if (!initialized_) + return v.initialized_; + + return major_ < v.major_ || + (major_ == v.major_ && (minor_ < v.minor_ || + (minor_ == v.minor_ && release_ < v.release_))); + } + +private: + unsigned major_, minor_, release_; + bool initialized_; +}; + +std::ostream& operator<<(std::ostream& os, odbc_version const& v) +{ + os << v.as_string(); + return os; +} + +std::string connectString; +backend_factory const &backEnd = *soci::factory_odbc(); + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh int2, ul numeric(20), d float8, " + "num76 numeric(7,6), " + "tm timestamp, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float8, num_int integer," + " name varchar(20), sometime timestamp, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString), + m_verDriver(get_driver_version()) + { + std::cout << "Using ODBC driver version " << m_verDriver << "\n"; + } + + table_creator_base * table_creator_1(soci::session& s) const + { + return new table_creator_one(s); + } + + table_creator_base * table_creator_2(soci::session& s) const + { + return new table_creator_two(s); + } + + table_creator_base * table_creator_3(soci::session& s) const + { + return new table_creator_three(s); + } + + table_creator_base * table_creator_4(soci::session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "timestamptz(\'" + datdt_string + "\')"; + } + + virtual bool has_fp_bug() const + { + // The bug with using insufficiently many digits for double values was + // only fixed in 9.03.0400 version of the ODBC driver (see commit + // a5fed2338b59ae16a2d3a8d2744b084949684775 in its repository), so we + // need to check for its version here. + // + // Be pessimistic if we failed to retrieve the version at all. + return !m_verDriver.is_initialized() || m_verDriver < odbc_version(9, 3, 400); + } + +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) + { + 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; + } + + odbc_version const m_verDriver; +}; + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run assert()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc >= 2 && argv[1][0] != '-') + { + connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; + } + else + { + connectString = "FILEDSN=./test-postgresql.dsn"; + } + + test_context tc(backEnd, connectString); + + return Catch::Session().run(argc, argv); +} diff --git a/src/backends/odbc/test/test-postgresql.dsn b/tests/odbc/test-postgresql.dsn similarity index 100% rename from src/backends/odbc/test/test-postgresql.dsn rename to tests/odbc/test-postgresql.dsn diff --git a/src/backends/oracle/test/CMakeLists.txt b/tests/oracle/CMakeLists.txt similarity index 69% rename from src/backends/oracle/test/CMakeLists.txt rename to tests/oracle/CMakeLists.txt index 219b8e63a8..da6767ba93 100644 --- a/src/backends/oracle/test/CMakeLists.txt +++ b/tests/oracle/CMakeLists.txt @@ -2,13 +2,15 @@ # # This file is part of CMake configuration for SOCI library # -# Copyright (C) 2010 Mateusz Loskot +# Copyright (C) 2010-2013 Mateusz Loskot # 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) # ############################################################################### + soci_backend_test( BACKEND Oracle - SOURCE test-oracle.cpp - CONNSTR "dummy") \ No newline at end of file + DEPENDS Oracle + SOURCE test-oracle.cpp ${SOCI_TESTS_COMMON} + CONNSTR "service=orcl user=scott password=tiger") diff --git a/tests/oracle/Makefile.basic b/tests/oracle/Makefile.basic new file mode 100644 index 0000000000..11c3e24cd6 --- /dev/null +++ b/tests/oracle/Makefile.basic @@ -0,0 +1,13 @@ +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I../../include -I../../include/private -I.. \ + -I${ORACLE_HOME}/rdbms/public +LIBDIRS = -L../../src/core -L../../src/backends/oracle -L${ORACLE_HOME}/lib +LIBS = -lsoci_core -lsoci_oracle -ldl -lclntsh -locci -lnnz11 + +test-oracle : test-oracle.cpp + ${COMPILER} $? -o $@ ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + + +clean : + rm -f test-oracle diff --git a/src/backends/oracle/test/test-oracle.cpp b/tests/oracle/test-oracle.cpp similarity index 65% rename from src/backends/oracle/test/test-oracle.cpp rename to tests/oracle/test-oracle.cpp index 9aca871ec5..5a3f757c85 100644 --- a/src/backends/oracle/test/test-oracle.cpp +++ b/tests/oracle/test-oracle.cpp @@ -5,13 +5,12 @@ // http://www.boost.org/LICENSE_1_0.txt) // -#include "soci.h" -#include "soci-oracle.h" #include "common-tests.h" +#include "soci/soci.h" +#include "soci/oracle/soci-oracle.h" #include #include #include -#include #include using namespace soci; @@ -21,9 +20,9 @@ std::string connectString; backend_factory const &backEnd = *soci::factory_oracle(); // Extra tests for date/time -void test1() +TEST_CASE("Oracle datetime", "[oracle][datetime]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); { std::time_t now = std::time(NULL); @@ -33,15 +32,15 @@ void test1() sql << "select t from (select :t as t from dual)", into(t1), use(t2); - assert(t1.tm_sec == t2.tm_sec); - assert(t1.tm_min == t2.tm_min); - assert(t1.tm_hour == t2.tm_hour); - assert(t1.tm_mday == t2.tm_mday); - assert(t1.tm_mon == t2.tm_mon); - assert(t1.tm_year == t2.tm_year); - assert(t1.tm_wday == t2.tm_wday); - assert(t1.tm_yday == t2.tm_yday); - assert(t1.tm_isdst == t2.tm_isdst); + CHECK(t1.tm_sec == t2.tm_sec); + CHECK(t1.tm_min == t2.tm_min); + CHECK(t1.tm_hour == t2.tm_hour); + CHECK(t1.tm_mday == t2.tm_mday); + CHECK(t1.tm_mon == t2.tm_mon); + CHECK(t1.tm_year == t2.tm_year); + CHECK(t1.tm_wday == t2.tm_wday); + CHECK(t1.tm_yday == t2.tm_yday); + CHECK(t1.tm_isdst == t2.tm_isdst); // make sure the date is stored properly in Oracle char buf[25]; @@ -52,7 +51,7 @@ void test1() sql << "select to_char(t, :format) from (select :t as t from dual)", into(t_out), use(format), use(t2); - assert(t_out == std::string(buf)); + CHECK(t_out == std::string(buf)); } { @@ -64,15 +63,15 @@ void test1() sql << "select t from (select :t as t from dual)", into(t1), use(t2); - assert(t1.tm_sec == t2.tm_sec); - assert(t1.tm_min == t2.tm_min); - assert(t1.tm_hour == t2.tm_hour); - assert(t1.tm_mday == t2.tm_mday); - assert(t1.tm_mon == t2.tm_mon); - assert(t1.tm_year == t2.tm_year); - assert(t1.tm_wday == t2.tm_wday); - assert(t1.tm_yday == t2.tm_yday); - assert(t1.tm_isdst == t2.tm_isdst); + CHECK(t1.tm_sec == t2.tm_sec); + CHECK(t1.tm_min == t2.tm_min); + CHECK(t1.tm_hour == t2.tm_hour); + CHECK(t1.tm_mday == t2.tm_mday); + CHECK(t1.tm_mon == t2.tm_mon); + CHECK(t1.tm_year == t2.tm_year); + CHECK(t1.tm_wday == t2.tm_wday); + CHECK(t1.tm_yday == t2.tm_yday); + CHECK(t1.tm_isdst == t2.tm_isdst); // make sure the date is stored properly in Oracle char buf[25]; @@ -83,16 +82,14 @@ void test1() sql << "select to_char(t, :format) from (select :t as t from dual)", into(t_out), use(format), use(t2); - assert(t_out == std::string(buf)); + CHECK(t_out == std::string(buf)); } - - std::cout << "test 1 passed" << std::endl; } // explicit calls test -void test2() +TEST_CASE("Oracle explicit calls", "[oracle]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); statement st(sql); st.alloc(); @@ -101,16 +98,14 @@ void test2() st.prepare("select 7 from dual"); st.define_and_bind(); st.execute(1); - assert(i == 7); - - std::cout << "test 2 passed" << std::endl; + CHECK(i == 7); } // DDL + blob test struct blob_table_creator : public table_creator_base { - blob_table_creator(session & sql) + blob_table_creator(soci::session & sql) : table_creator_base(sql) { sql << @@ -121,10 +116,10 @@ struct blob_table_creator : public table_creator_base } }; -void test3() +TEST_CASE("Oracle blob", "[oracle][blob]") { - session sql(backEnd, connectString); - + soci::session sql(backEnd, connectString); + blob_table_creator tableCreator(sql); char buf[] = "abcdefghijklmnopqrstuvwxyz"; @@ -143,31 +138,29 @@ void test3() sessionBackEnd->errhp_, blobBackEnd->lobp_); sql << "select img from soci_test where id = 7", into(b); - assert(b.get_len() == 0); + CHECK(b.get_len() == 0); // note: blob offsets start from 1 b.write(1, buf, sizeof(buf)); - assert(b.get_len() == sizeof(buf)); + CHECK(b.get_len() == sizeof(buf)); b.trim(10); - assert(b.get_len() == 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); + //CHECK(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); - assert(b.get_len() == 10); + //CHECK(b.get_len() == sizeof(buf) + 10); + CHECK(b.get_len() == 10); char buf2[100]; b.read(1, buf2, 10); - assert(strncmp(buf2, "abcdefghij", 10) == 0); + CHECK(strncmp(buf2, "abcdefghij", 10) == 0); } - - std::cout << "test 3 passed" << std::endl; } // nested statement test @@ -175,7 +168,7 @@ void test3() struct basic_table_creator : public table_creator_base { - basic_table_creator(session & sql) + basic_table_creator(soci::session & sql) : table_creator_base(sql) { sql << @@ -187,9 +180,9 @@ struct basic_table_creator : public table_creator_base } }; -void test4() +TEST_CASE("Oracle nested statement", "[oracle][blob]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); basic_table_creator tableCreator(sql); int id; @@ -216,19 +209,17 @@ void test4() std::vector names; while (stInner.fetch()) { names.push_back(name); } - assert(names.size() == 3); - assert(names[0] == "John"); - assert(names[1] == "Anna"); - assert(names[2] == "Mike"); - - std::cout << "test 4 passed" << std::endl; + REQUIRE(names.size() == 3); + CHECK(names[0] == "John"); + CHECK(names[1] == "Anna"); + CHECK(names[2] == "Mike"); } // ROWID test -void test5() +TEST_CASE("Oracle rowid", "[oracle][rowid]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); basic_table_creator tableCreator(sql); sql << "insert into soci_test(id, name) values(7, \'John\')"; @@ -241,16 +232,14 @@ void test5() sql << "select id, name from soci_test where rowid = :rid", into(id), into(name), use(rid); - assert(id == 7); - assert(name == "John"); - - std::cout << "test 5 passed" << std::endl; + CHECK(id == 7); + CHECK(name == "John"); } // Stored procedures struct procedure_creator : procedure_creator_base { - procedure_creator(session & sql) + procedure_creator(soci::session & sql) : procedure_creator_base(sql) { sql << @@ -260,34 +249,30 @@ struct procedure_creator : procedure_creator_base } }; -void test6() +TEST_CASE("Oracle stored procedure", "[oracle][stored-procedure]") { + soci::session sql(backEnd, connectString); + procedure_creator procedure_creator(sql); + + std::string in("my message"); + std::string out; + statement st = (sql.prepare << + "begin soci_test(:output, :input); end;", + use(out, "output"), + use(in, "input")); + st.execute(1); + CHECK(out == in); + + // explicit procedure syntax { - session sql(backEnd, connectString); - procedure_creator procedure_creator(sql); - - std::string in("my message"); + std::string in("my message2"); std::string out; - statement st = (sql.prepare << - "begin soci_test(:output, :input); end;", - use(out, "output"), - use(in, "input")); - st.execute(1); - assert(out == in); - - // explicit procedure syntax - { - std::string in("my message2"); - std::string out; - procedure proc = (sql.prepare << - "soci_test(:output, :input)", - use(out, "output"), use(in, "input")); - proc.execute(1); - assert(out == in); - } + procedure proc = (sql.prepare << + "soci_test(:output, :input)", + use(out, "output"), use(in, "input")); + proc.execute(1); + CHECK(out == in); } - - std::cout << "test 6 passed" << std::endl; } // bind into user-defined objects @@ -323,7 +308,7 @@ namespace soci struct in_out_procedure_creator : public procedure_creator_base { - in_out_procedure_creator(session & sql) + in_out_procedure_creator(soci::session & sql) : procedure_creator_base(sql) { sql << "create or replace procedure soci_test(s in out varchar2)" @@ -333,7 +318,7 @@ struct in_out_procedure_creator : public procedure_creator_base struct returns_null_procedure_creator : public procedure_creator_base { - returns_null_procedure_creator(session & sql) + returns_null_procedure_creator(soci::session & sql) : procedure_creator_base(sql) { sql << "create or replace procedure soci_test(s in out varchar2)" @@ -341,81 +326,70 @@ struct returns_null_procedure_creator : public procedure_creator_base } }; -void test7() +TEST_CASE("Oracle user-defined objects", "[oracle][type_conversion]") { + soci::session sql(backEnd, connectString); { - session sql(backEnd, connectString); - { - basic_table_creator tableCreator(sql); + basic_table_creator tableCreator(sql); - int id(1); - string_holder in("my string"); - sql << "insert into soci_test(id, name) values(:id, :name)", use(id), use(in); + int id(1); + string_holder in("my string"); + sql << "insert into soci_test(id, name) values(:id, :name)", use(id), use(in); - string_holder out; - sql << "select name from soci_test", into(out); - assert(out.get() == "my string"); + string_holder out; + sql << "select name from soci_test", into(out); + CHECK(out.get() == "my string"); - row r; - sql << "select * from soci_test", into(r); - string_holder dynamicOut = r.get(1); - assert(dynamicOut.get() == "my string"); - } + row r; + sql << "select * from soci_test", into(r); + string_holder dynamicOut = r.get(1); + CHECK(dynamicOut.get() == "my string"); } - std::cout << "test 7 passed" << std::endl; } -void test7inout() +TEST_CASE("Oracle user-defined objects in/out", "[oracle][type_conversion]") { + soci::session sql(backEnd, connectString); + + // test procedure with user-defined type as in-out parameter { - session sql(backEnd, connectString); + in_out_procedure_creator procedureCreator(sql); - // test procedure with user-defined type as in-out parameter - { - in_out_procedure_creator procedureCreator(sql); - - std::string sh("test"); - procedure proc = (sql.prepare << "soci_test(:s)", use(sh)); - proc.execute(1); - assert(sh == "testtest"); - } - - // test procedure with user-defined type as in-out parameter - { - in_out_procedure_creator procedureCreator(sql); - - string_holder sh("test"); - procedure proc = (sql.prepare << "soci_test(:s)", use(sh)); - proc.execute(1); - assert(sh.get() == "testtest"); - } + std::string sh("test"); + procedure proc = (sql.prepare << "soci_test(:s)", use(sh)); + proc.execute(1); + CHECK(sh == "testtest"); + } + + // test procedure with user-defined type as in-out parameter + { + in_out_procedure_creator procedureCreator(sql); + + string_holder sh("test"); + procedure proc = (sql.prepare << "soci_test(:s)", use(sh)); + proc.execute(1); + CHECK(sh.get() == "testtest"); } - std::cout << "test 7-inout passed" << std::endl; } -void test7outnull() +TEST_CASE("Oracle null user-defined objects in/out", "[oracle][null][type_conversion]") { - { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); - // test procedure which returns null - { - returns_null_procedure_creator procedureCreator(sql); + // test procedure which returns null + returns_null_procedure_creator procedureCreator(sql); - string_holder sh; - indicator ind = i_ok; - procedure proc = (sql.prepare << "soci_test(:s)", use(sh, ind)); - proc.execute(1); - assert(ind == i_null); - } - } - std::cout << "test 7-outnull passed" << std::endl; + string_holder sh; + indicator ind = i_ok; + procedure proc = (sql.prepare << "soci_test(:s)", use(sh, ind)); + proc.execute(1); + CHECK(ind == i_null); } // test bulk insert features -void test8() +TEST_CASE("Oracle bulk insert", "[oracle][insert][bulk]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); basic_table_creator tableCreator(sql); @@ -426,30 +400,33 @@ void test8() ids.push_back(2); std::vector codes; codes.push_back(1); - std::string error; try { sql << "insert into soci_test(id,code) values(:id,:code)", use(ids), use(codes); + FAIL("expected exception not thrown"); } catch (soci_error const &e) { - error = e.what(); + std::string const error = e.what(); + CAPTURE(error); + CHECK(error.find("Bind variable size mismatch") + != std::string::npos); } - assert(error.find("Bind variable size mismatch") - != std::string::npos); try { sql << "select from soci_test", into(ids), into(codes); + FAIL("expected exception not thrown"); } catch (std::exception const &e) { - error = e.what(); + std::string const error = e.what(); + CAPTURE(error); + CHECK(error.find("Bind variable size mismatch") + != std::string::npos); } - assert(error.find("Bind variable size mismatch") - != std::string::npos); } // verify partial insert occurs when one of the records is bad @@ -458,21 +435,22 @@ void test8() ids.push_back(100); ids.push_back(1000000); // too big for column - std::string error; try { sql << "insert into soci_test (id) values(:id)", use(ids, "id"); + FAIL("expected exception not thrown"); } catch (soci_error const &e) { - error = e.what(); + std::string const error = e.what(); //TODO e could be made to tell which row(s) failed + CAPTURE(error); + CHECK(error.find("ORA-01438") != std::string::npos); } sql.commit(); - assert(error.find("ORA-01438") != std::string::npos); int count(7); sql << "select count(*) from soci_test", into(count); - assert(count == 1); + CHECK(count == 1); sql << "delete from soci_test"; } @@ -489,37 +467,19 @@ void test8() st.execute(1); int count; sql << "select count(*) from soci_test", into(count); - assert(count == 3); + CHECK(count == 3); } //verify an exception is thrown if into vector is zero length { std::vector ids; - bool caught(false); - try - { - sql << "select id from soci_test", into(ids); - } - catch (soci_error const &) - { - caught = true; - } - assert(caught); + CHECK_THROWS_AS((sql << "select id from soci_test", into(ids)), soci_error); } // verify an exception is thrown if use vector is zero length { std::vector ids; - bool caught(false); - try - { - sql << "insert into soci_test(id) values(:id)", use(ids); - } - catch (soci_error const &) - { - caught = true; - } - assert(caught); + CHECK_THROWS_AS((sql << "insert into soci_test(id) values(:id)", use(ids)), soci_error); } // test "no data" condition @@ -530,7 +490,7 @@ void test8() into(ids_out, inds)); // false return value means "no data" - assert(st.execute(1) == false); + CHECK(st.execute(1) == false); // that's it - nothing else is guaranteed // and nothing else is to be tested here @@ -558,10 +518,13 @@ void test8() std::vector codes(3); sql << "select code from soci_test", into(codes, inds_out); - assert(codes.size() == 3 && inds_out.size() == 3); - assert(codes[0] == 10 && codes[2] == 10); - assert(inds_out[0] == i_ok && inds_out[1] == i_null - && inds_out[2] == i_ok); + REQUIRE(codes.size() == 3); + REQUIRE(inds_out.size() == 3); + CHECK(codes[0] == 10); + CHECK(codes[2] == 10); + CHECK(inds_out[0] == i_ok); + CHECK(inds_out[1] == i_null); + CHECK(inds_out[2] == i_ok); } // verify an exception is thrown if null is selected @@ -572,12 +535,13 @@ void test8() try { sql << "select code from soci_test", into(intos); + FAIL("expected exception not thrown"); } catch (soci_error const &e) { - msg = e.what(); + CHECK(e.get_error_message() == + "Null value fetched and no indicator defined." ); } - assert(msg == "Null value fetched and no indicator defined." ); } // test basic select @@ -588,12 +552,14 @@ void test8() statement st = (sql.prepare << "select id from soci_test", into(ids_out, inds)); const bool gotData = st.execute(true); - assert(gotData); - assert(ids_out.size() == sz); - assert(ids_out[0] == 10); - assert(ids_out[2] == 12); - assert(inds.size() == 3 && inds[0] == i_ok - && inds[1] == i_ok && inds[2] == i_ok); + CHECK(gotData); + REQUIRE(ids_out.size() == sz); + CHECK(ids_out[0] == 10); + CHECK(ids_out[2] == 12); + REQUIRE(inds.size() == 3); + CHECK(inds[0] == i_ok); + CHECK(inds[1] == i_ok); + CHECK(inds[2] == i_ok); } // verify execute(0) @@ -603,15 +569,18 @@ void test8() into(ids_out)); st.execute(); - assert(ids_out.size() == 2); + REQUIRE(ids_out.size() == 2); bool gotData = st.fetch(); - assert(gotData); - assert(ids_out.size() == 2 && ids_out[0] == 10 && ids_out[1] == 11); + CHECK(gotData); + REQUIRE(ids_out.size() == 2); + CHECK(ids_out[0] == 10); + CHECK(ids_out[1] == 11); gotData = st.fetch(); - assert(gotData); - assert(ids_out.size() == 1 && ids_out[0] == 12); + CHECK(gotData); + REQUIRE(ids_out.size() == 1); + CHECK(ids_out[0] == 12); gotData = st.fetch(); - assert(gotData == false); + CHECK(gotData == false); } // verify resizing happens if vector is larger @@ -621,10 +590,10 @@ void test8() statement st2 = (sql.prepare << "select id from soci_test", into(ids_out)); bool gotData = st2.execute(true); - assert(gotData); - assert(ids_out.size() == 3); - assert(ids_out[0] == 10); - assert(ids_out[2] == 12); + CHECK(gotData); + REQUIRE(ids_out.size() == 3); + CHECK(ids_out[0] == 10); + CHECK(ids_out[2] == 12); } // verify resizing happens properly during fetch() @@ -637,31 +606,29 @@ void test8() std::vector ids(2); statement st3 = (sql.prepare << "select id from soci_test", into(ids)); bool gotData = st3.execute(true); - assert(gotData); - assert(ids[0] == 10); - assert(ids[1] == 11); + CHECK(gotData); + CHECK(ids[0] == 10); + CHECK(ids[1] == 11); gotData = st3.fetch(); - assert(gotData); - assert(ids[0] == 12); - assert(ids[1] == 13); + CHECK(gotData); + CHECK(ids[0] == 12); + CHECK(ids[1] == 13); gotData = st3.fetch(); - assert(gotData); - assert(ids.size() == 1); - assert(ids[0] == 14); + CHECK(gotData); + REQUIRE(ids.size() == 1); + CHECK(ids[0] == 14); gotData = st3.fetch(); - assert(gotData == false); + CHECK(gotData == false); } - - std::cout << "test 8 passed" << std::endl; } // more tests for bulk fetch -void test9() +TEST_CASE("Oracle bulk fetch", "[oracle][fetch][bulk]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); basic_table_creator tableCreator(sql); @@ -675,7 +642,7 @@ void test9() int count(0); sql << "select count(*) from soci_test", into(count); - assert(count == 10); + CHECK(count == 10); // verify that the exception is thrown when trying to resize // the output vector to the size that is bigger than that @@ -688,20 +655,20 @@ void test9() st.execute(); st.fetch(); - assert(out.size() == 4); - assert(out[0] == 1); - assert(out[1] == 2); - assert(out[2] == 3); - assert(out[3] == 4); + REQUIRE(out.size() == 4); + CHECK(out[0] == 1); + CHECK(out[1] == 2); + CHECK(out[2] == 3); + CHECK(out[3] == 4); out.resize(5); // this should be detected as error try { st.fetch(); - assert(false); // should never reach here + FAIL("expected exception not thrown"); } catch (soci_error const &e) { - assert(std::string(e.what()) == + CHECK(e.get_error_message() == "Increasing the size of the output vector is not supported."); } } @@ -715,28 +682,26 @@ void test9() st.execute(); st.fetch(); - assert(out.size() == 4); - assert(out[0] == 1); - assert(out[1] == 2); - assert(out[2] == 3); - assert(out[3] == 4); + REQUIRE(out.size() == 4); + CHECK(out[0] == 1); + CHECK(out[1] == 2); + CHECK(out[2] == 3); + CHECK(out[3] == 4); out.resize(3); // ok st.fetch(); - assert(out.size() == 3); - assert(out[0] == 5); - assert(out[1] == 6); - assert(out[2] == 7); + REQUIRE(out.size() == 3); + CHECK(out[0] == 5); + CHECK(out[1] == 6); + CHECK(out[2] == 7); out.resize(4); // ok, not bigger than initially st.fetch(); - assert(out.size() == 3); // downsized because of end of data - assert(out[0] == 8); - assert(out[1] == 9); - assert(out[2] == 10); + REQUIRE(out.size() == 3); // downsized because of end of data + CHECK(out[0] == 8); + CHECK(out[1] == 9); + CHECK(out[2] == 10); bool gotData = st.fetch(); - assert(gotData == false); // end of data + CHECK(gotData == false); // end of data } - - std::cout << "test 9 passed" << std::endl; } struct person @@ -781,7 +746,7 @@ namespace soci struct person_table_creator : public table_creator_base { - person_table_creator(session & sql) + person_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(id numeric(5,0) NOT NULL," @@ -792,7 +757,7 @@ struct person_table_creator : public table_creator_base struct times100_procedure_creator : public procedure_creator_base { - times100_procedure_creator(session & sql) + times100_procedure_creator(soci::session & sql) : procedure_creator_base(sql) { sql << "create or replace procedure soci_test(id in out number)" @@ -800,9 +765,9 @@ struct times100_procedure_creator : public procedure_creator_base } }; -void test10() +TEST_CASE("Oracle ORM", "[oracle][orm]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); { person_table_creator tableCreator(sql); @@ -815,31 +780,33 @@ void test10() << "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p); // p should be unchanged - assert(p.id == 1); - assert(p.firstName == "Pat"); - assert(p.lastName.get() == "Smith"); + CHECK(p.id == 1); + CHECK(p.firstName == "Pat"); + CHECK(p.lastName.get() == "Smith"); person p1; sql << "select * from soci_test", into(p1); - assert(p1.id == 1); - assert(p1.firstName + p1.lastName.get() == "PatSmith"); - assert(p1.gender == "unknown"); + CHECK(p1.id == 1); + CHECK(p1.firstName == "Pat"); + CHECK(p1.lastName.get() == "Smith"); + CHECK(p1.gender == "unknown"); p.firstName = "Patricia"; sql << "update soci_test set first_name = :FIRST_NAME " "where id = :ID", use(p); // p should be unchanged - assert(p.id == 1); - assert(p.firstName == "Patricia"); - assert(p.lastName.get() == "Smith"); + CHECK(p.id == 1); + CHECK(p.firstName == "Patricia"); + CHECK(p.lastName.get() == "Smith"); // Note: gender is now "unknown" because of the mapping, not "" - assert(p.gender == "unknown"); + CHECK(p.gender == "unknown"); person p2; sql << "select * from soci_test", into(p2); - assert(p2.id == 1); - assert(p2.firstName + p2.lastName.get() == "PatriciaSmith"); + CHECK(p2.id == 1); + CHECK(p2.firstName == "Patricia"); + CHECK(p2.lastName.get() == "Smith"); // insert a second row so we can test fetching person p3; @@ -855,16 +822,16 @@ void test10() st.execute(); bool gotData = st.fetch(); - assert(gotData); - assert(p4.id == 1); - assert(p4.firstName == "Patricia"); + CHECK(gotData); + CHECK(p4.id == 1); + CHECK(p4.firstName == "Patricia"); gotData = st.fetch(); - assert(gotData); - assert(p4.id == 2); - assert(p4.firstName == "Joe"); + CHECK(gotData); + CHECK(p4.id == 2); + CHECK(p4.firstName == "Joe"); gotData = st.fetch(); - assert(gotData == false); + CHECK(gotData == false); } // test with stored procedure @@ -877,36 +844,35 @@ void test10() p.lastName = "Smith"; procedure proc = (sql.prepare << "soci_test(:ID)", use(p)); proc.execute(1); - assert(p.id == 100); - assert(p.firstName == "Pat"); - assert(p.lastName.get() == "Smith"); + CHECK(p.id == 100); + CHECK(p.firstName == "Pat"); + CHECK(p.lastName.get() == "Smith"); } // test with stored procedure which returns null { returns_null_procedure_creator procedureCreator(sql); - std::string msg; person p; try { procedure proc = (sql.prepare << "soci_test(:FIRST_NAME)", use(p)); proc.execute(1); + FAIL("expected exception not thrown"); } catch (soci_error& e) { - msg = e.what(); + CHECK(e.get_error_message() == + "Null value not allowed for this type"); } - assert(msg == "Null value not allowed for this type"); procedure proc = (sql.prepare << "soci_test(:GENDER)", use(p)); proc.execute(1); - assert(p.gender == "unknown"); + CHECK(p.gender == "unknown"); } - std::cout << "test 10 passed" << std::endl; } // Experimental support for position based O/R Mapping @@ -954,9 +920,9 @@ namespace soci }; } -void test11() +TEST_CASE("Oracle ORM by index", "[oracle][orm]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); person_table_creator tableCreator(sql); @@ -970,20 +936,20 @@ void test11() // test position-based conversion person2 p3; sql << "select id, first_name, last_name, gender from soci_test", into(p3); - assert(p3.id == 1); - assert(p3.firstName + p3.lastName == "PatriciaSmith"); - assert(p3.gender == "whoknows"); + CHECK(p3.id == 1); + CHECK(p3.firstName == "Patricia"); + CHECK(p3.lastName == "Smith"); + CHECK(p3.gender == "whoknows"); sql << "update soci_test set gender = 'F' where id = 1"; // additional test for stream-like conversion person3 p4; sql << "select id, first_name, last_name, gender from soci_test", into(p4); - assert(p4.id == 1); - assert(p4.firstName + p4.lastName == "PatriciaSmith"); - assert(p4.gender == "F"); - - std::cout << "test 11 passed" << std::endl; + CHECK(p4.id == 1); + CHECK(p4.firstName == "Patricia"); + CHECK(p4.lastName == "Smith"); + CHECK(p4.gender == "F"); } // @@ -992,16 +958,16 @@ void test11() /// struct long_table_creator : public table_creator_base { - long_table_creator(session & sql) + long_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(l long)"; } }; -void test12() +TEST_CASE("Oracle large strings as long", "[oracle][compatibility]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); long_table_creator creator(sql); const std::string::size_type max = 32768; @@ -1012,22 +978,20 @@ void test12() std::string out; sql << "select l from soci_test", into(out); - assert(out.size() == max); - assert(in == out); - - std::cout << "test 12 passed" << std::endl; + CHECK(out.size() == max); + CHECK(in == out); } // test for modifiable and const use elements -void test13() +TEST_CASE("Oracle const and modifiable parameters", "[oracle][use]") { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); int i = 7; sql << "begin " "select 2 * :i into :i from dual; " "end;", use(i); - assert(i == 14); + CHECK(i == 14); const int j = 7; try @@ -1036,20 +1000,18 @@ void test13() "select 2 * :i into :i from dual;" " end;", use(j); - assert(false); // should never get here + FAIL("expected exception not thrown"); } catch (soci_error const & e) { - const std::string msg = e.what(); - assert(msg == "Attempted modification of const use element"); + CHECK(e.get_error_message() == + "Attempted modification of const use element"); } - - std::cout << "test 13 passed" << std::endl; } struct longlong_table_creator : table_creator_base { - longlong_table_creator(session & sql) + longlong_table_creator(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val number(20))"; @@ -1057,27 +1019,25 @@ struct longlong_table_creator : table_creator_base }; // long long test -void test14() +TEST_CASE("Oracle long long", "[oracle][longlong]") { { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); longlong_table_creator tableCreator(sql); long long v1 = 1000000000000LL; - assert(v1 / 1000000 == 1000000); - sql << "insert into soci_test(val) values(:val)", use(v1); long long v2 = 0LL; sql << "select val from soci_test", into(v2); - assert(v2 == v1); + CHECK(v2 == v1); } // vector { - session sql(backEnd, connectString); + soci::session sql(backEnd, connectString); longlong_table_creator tableCreator(sql); @@ -1093,15 +1053,13 @@ void test14() std::vector v2(10); sql << "select val from soci_test order by val desc", into(v2); - assert(v2.size() == 5); - assert(v2[0] == 1000000000004LL); - assert(v2[1] == 1000000000003LL); - assert(v2[2] == 1000000000002LL); - assert(v2[3] == 1000000000001LL); - assert(v2[4] == 1000000000000LL); + REQUIRE(v2.size() == 5); + CHECK(v2[0] == 1000000000004LL); + CHECK(v2[1] == 1000000000003LL); + CHECK(v2[2] == 1000000000002LL); + CHECK(v2[3] == 1000000000001LL); + CHECK(v2[4] == 1000000000000LL); } - - std::cout << "test 14 passed" << std::endl; } // @@ -1110,18 +1068,19 @@ void test14() struct table_creator_one : public table_creator_base { - table_creator_one(session & sql) + table_creator_one(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(id number(10,0), val number(4,0), c char, " "str varchar2(20), sh number, ul number, d number, " + "num76 numeric(7,6), " "tm date, i1 number, i2 number, i3 number, name varchar2(20))"; } }; struct table_creator_two : public table_creator_base { - table_creator_two(session & sql) + table_creator_two(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(num_float number, num_int numeric(4,0)," @@ -1131,7 +1090,7 @@ struct table_creator_two : public table_creator_base struct table_creator_three : public table_creator_base { - table_creator_three(session & sql) + table_creator_three(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(name varchar2(100) not null, " @@ -1141,7 +1100,7 @@ struct table_creator_three : public table_creator_base struct table_creator_four : public table_creator_base { - table_creator_four(session & sql) + table_creator_four(soci::session & sql) : table_creator_base(sql) { sql << "create table soci_test(val number)"; @@ -1155,22 +1114,22 @@ public: std::string const &connectString) : test_context_base(backEnd, connectString) {} - table_creator_base* table_creator_1(session& s) const + table_creator_base* table_creator_1(soci::session& s) const { return new table_creator_one(s); } - table_creator_base* table_creator_2(session& s) const + table_creator_base* table_creator_2(soci::session& s) const { return new table_creator_two(s); } - table_creator_base* table_creator_3(session& s) const + table_creator_base* table_creator_3(soci::session& s) const { return new table_creator_three(s); } - table_creator_base* table_creator_4(session& s) const + table_creator_base* table_creator_4(soci::session& s) const { return new table_creator_four(s); } @@ -1186,56 +1145,33 @@ int main(int argc, char** argv) #ifdef _MSC_VER // Redirect errors, unrecoverable problems, and assert() failures to STDERR, // instead of debug message window. - // This hack is required to run asser()-driven tests by Buildbot. + // This hack is required to run assert()-driven tests by Buildbot. // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); #endif //_MSC_VER - if (argc == 2) + if (argc >= 2) { connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; } else { std::cout << "usage: " << argv[0] - << " connectstring\n" + << " connectstring [test-arguments...]\n" << "example: " << argv[0] << " \'service=orcl user=scott password=tiger\'\n"; std::exit(1); } - try - { - test_context tc(backEnd, connectString); - common_tests tests(tc); - tests.run(); + test_context tc(backEnd, connectString); - std::cout << "\nsoci Oracle tests:\n\n"; - test1(); - test2(); - test3(); - test4(); - test5(); - test6(); - test7(); - test7inout(); - test7outnull(); - test8(); - test9(); - test10(); - test11(); - test12(); - test13(); - test14(); - - std::cout << "\nOK, all tests passed.\n\n"; - - return EXIT_SUCCESS; - } - catch (std::exception const & e) - { - std::cout << e.what() << '\n'; - } - return EXIT_FAILURE; + return Catch::Session().run(argc, argv); } diff --git a/src/backends/postgresql/test/CMakeLists.txt b/tests/postgresql/CMakeLists.txt similarity index 72% rename from src/backends/postgresql/test/CMakeLists.txt rename to tests/postgresql/CMakeLists.txt index b16c15af36..0fac5737b9 100644 --- a/src/backends/postgresql/test/CMakeLists.txt +++ b/tests/postgresql/CMakeLists.txt @@ -2,7 +2,7 @@ # # This file is part of CMake configuration for SOCI library # -# Copyright (C) 2010 Mateusz Loskot +# Copyright (C) 2010-2013 Mateusz Loskot # 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) @@ -11,5 +11,6 @@ soci_backend_test( BACKEND PostgreSQL - SOURCE test-postgresql.cpp - CONNSTR "dummy") \ No newline at end of file + DEPENDS PostgreSQL + SOURCE test-postgresql.cpp ${SOCI_TESTS_COMMON} + CONNSTR "dbname=soci_test") diff --git a/tests/postgresql/Makefile.basic b/tests/postgresql/Makefile.basic new file mode 100644 index 0000000000..145347c358 --- /dev/null +++ b/tests/postgresql/Makefile.basic @@ -0,0 +1,12 @@ +COMPILER = g++ +CXXFLAGS = -Wall -pedantic -Wno-long-long +INCLUDEDIRS = -I../../include -I../../include/private -I.. -I/usr/include/postgresql +LIBDIRS = -L../../src/core -L../../src/backends/postgresql -L/usr/lib/x86_64-linux-gnu +LIBS = -lsoci_postgresql -lsoci_core -ldl -lpq + +test-postgresql : test-postgresql.cpp + ${COMPILER} $? -o $@ ${INCLUDEDIRS} ${LIBDIRS} ${LIBS} + + +clean : + rm -f test-postgresql diff --git a/tests/postgresql/test-postgresql.cpp b/tests/postgresql/test-postgresql.cpp new file mode 100644 index 0000000000..b8fd1b27d8 --- /dev/null +++ b/tests/postgresql/test-postgresql.cpp @@ -0,0 +1,786 @@ +// +// Copyright (C) 2004-2008 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.h" +#include "soci/postgresql/soci-postgresql.h" +#include "common-tests.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_postgresql(); + +// Postgres-specific tests + +struct oid_table_creator : public table_creator_base +{ + oid_table_creator(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test (" + " id integer," + " name varchar(100)" + ") with oids"; + } +}; + +// ROWID test +// Note: in PostgreSQL, there is no ROWID, there is OID. +// It is still provided as a separate type for "portability", +// whatever that means. +TEST_CASE("PostgreSQL ROWID", "[postgresql][rowid][oid]") +{ + soci::session sql(backEnd, connectString); + + oid_table_creator tableCreator(sql); + + sql << "insert into soci_test(id, name) values(7, \'John\')"; + + rowid rid(sql); + sql << "select oid from soci_test where id = 7", into(rid); + + int id; + std::string name; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + sql << "select id, name from soci_test where oid = :rid", + into(id), into(name), use(rid); + +#else + // Older PostgreSQL does not support use elements. + + postgresql_rowid_backend *rbe + = static_cast(rid.get_backend()); + + unsigned long oid = rbe->value_; + + sql << "select id, name from soci_test where oid = " << oid, + into(id), into(name); + +#endif // SOCI_POSTGRESQL_NOPARAMS + + CHECK(id == 7); + CHECK(name == "John"); +} + +TEST_CASE("PostgreSQL prepare error", "[postgresql][exception]") +{ + soci::session sql(backEnd, connectString); + + // Must not cause the application to crash. + statement st(sql); + st.prepare(""); // Throws an exception in some versions. +} + +// function call test +class function_creator : function_creator_base +{ +public: + + function_creator(soci::session & sql) + : function_creator_base(sql) + { + // before a language can be used it must be defined + // if it has already been defined then an error will occur + try { sql << "create language plpgsql"; } + catch (soci_error const &) {} // ignore if error + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + sql << + "create or replace function soci_test(msg varchar) " + "returns varchar as $$ " + "declare x int := 1;" + "begin " + " return msg; " + "end $$ language plpgsql"; +#else + + sql << + "create or replace function soci_test(varchar) " + "returns varchar as \' " + "declare x int := 1;" + "begin " + " return $1; " + "end \' language plpgsql"; +#endif + } + +protected: + + std::string drop_statement() + { + return "drop function soci_test(varchar)"; + } +}; + +TEST_CASE("PostgreSQL function call", "[postgresql][function]") +{ + soci::session sql(backEnd, connectString); + + function_creator functionCreator(sql); + + std::string in("my message"); + std::string out; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + statement st = (sql.prepare << + "select soci_test(:input)", + into(out), + use(in, "input")); + +#else + // Older PostgreSQL does not support use elements. + + statement st = (sql.prepare << + "select soci_test(\'" << in << "\')", + into(out)); + +#endif // SOCI_POSTGRESQL_NOPARAMS + + st.execute(true); + CHECK(out == in); + + // explicit procedure syntax + { + std::string in("my message2"); + std::string out; + +#ifndef SOCI_POSTGRESQL_NOPARAMS + + procedure proc = (sql.prepare << + "soci_test(:input)", + into(out), use(in, "input")); + +#else + // Older PostgreSQL does not support use elements. + + procedure proc = (sql.prepare << + "soci_test(\'" << in << "\')", into(out)); + +#endif // SOCI_POSTGRESQL_NOPARAMS + + proc.execute(true); + CHECK(out == in); + } +} + +// BLOB test +struct blob_table_creator : public table_creator_base +{ + blob_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << + "create table soci_test (" + " id integer," + " img oid" + ")"; + } +}; + +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); + + 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 << ")"; +} + +struct longlong_table_creator : table_creator_base +{ + longlong_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val int8)"; + } +}; + +// long long test +TEST_CASE("PostgreSQL long long", "[postgresql][longlong]") +{ + soci::session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + long long v1 = 1000000000000LL; + sql << "insert into soci_test(val) values(:val)", use(v1); + + long long v2 = 0LL; + sql << "select val from soci_test", into(v2); + + CHECK(v2 == v1); +} + +// vector +TEST_CASE("PostgreSQL vector long long", "[postgresql][vector][longlong]") +{ + soci::session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + std::vector v1; + v1.push_back(1000000000000LL); + v1.push_back(1000000000001LL); + v1.push_back(1000000000002LL); + v1.push_back(1000000000003LL); + v1.push_back(1000000000004LL); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::vector v2(10); + sql << "select val from soci_test order by val desc", into(v2); + + REQUIRE(v2.size() == 5); + CHECK(v2[0] == 1000000000004LL); + CHECK(v2[1] == 1000000000003LL); + CHECK(v2[2] == 1000000000002LL); + CHECK(v2[3] == 1000000000001LL); + CHECK(v2[4] == 1000000000000LL); +} + +// unsigned long long test +TEST_CASE("PostgreSQL unsigned long long", "[postgresql][unsigned][longlong]") +{ + soci::session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + unsigned long long v1 = 1000000000000ULL; + sql << "insert into soci_test(val) values(:val)", use(v1); + + unsigned long long v2 = 0ULL; + sql << "select val from soci_test", into(v2); + + CHECK(v2 == v1); +} + +struct boolean_table_creator : table_creator_base +{ + boolean_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val boolean)"; + } +}; + +TEST_CASE("PostgreSQL boolean", "[postgresql][boolean]") +{ + soci::session sql(backEnd, connectString); + + boolean_table_creator tableCreator(sql); + + int i1 = 0; + + sql << "insert into soci_test(val) values(:val)", use(i1); + + int i2 = 7; + sql << "select val from soci_test", into(i2); + + CHECK(i2 == i1); + + sql << "update soci_test set val = true"; + sql << "select val from soci_test", into(i2); + CHECK(i2 == 1); +} + +// dynamic backend test -- currently skipped by default +TEST_CASE("PostgreSQL dynamic backend", "[postgresql][backend][.]") +{ + try + { + soci::session sql("nosuchbackend://" + connectString); + FAIL("expected exception not thrown"); + } + catch (soci_error const & e) + { + CHECK(e.get_error_message() == + "Failed to open: libsoci_nosuchbackend.so"); + } + + { + dynamic_backends::register_backend("pgsql", backEnd); + + std::vector backends = dynamic_backends::list_all(); + REQUIRE(backends.size() == 1); + CHECK(backends[0] == "pgsql"); + + { + soci::session sql("pgsql://" + connectString); + } + + dynamic_backends::unload("pgsql"); + + backends = dynamic_backends::list_all(); + CHECK(backends.empty()); + } + + { + soci::session sql("postgresql://" + connectString); + } +} + +TEST_CASE("PostgreSQL literals", "[postgresql][into]") +{ + soci::session sql(backEnd, connectString); + + int i; + sql << "select 123", into(i); + CHECK(i == 123); + + try + { + sql << "select 'ABC'", into (i); + FAIL("expected exception not thrown"); + } + catch (soci_error const & e) + { + char const * expectedPrefix = "Cannot convert data"; + CAPTURE(e.what()); + CHECK(strncmp(e.what(), expectedPrefix, strlen(expectedPrefix)) == 0); + } +} + +TEST_CASE("PostgreSQL backend name", "[postgresql][backend]") +{ + soci::session sql(backEnd, connectString); + + CHECK(sql.get_backend_name() == "postgresql"); +} + +// test for double-colon cast in SQL expressions +TEST_CASE("PostgreSQL double colon cast", "[postgresql][cast]") +{ + soci::session sql(backEnd, connectString); + + int a = 123; + int b = 0; + sql << "select :a::integer", use(a), into(b); + CHECK(b == a); +} + +// test for date, time and timestamp parsing +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; + + sql << "select :sd::date, :sd::time, :sd::timestamp", + use(someDate, "sd"), into(t1), into(t2), into(t3); + + // t1 should contain only the date part + CHECK(t1.tm_year == 2009 - 1900); + CHECK(t1.tm_mon == 6 - 1); + CHECK(t1.tm_mday == 17); + CHECK(t1.tm_hour == 0); + CHECK(t1.tm_min == 0); + CHECK(t1.tm_sec == 0); + + // t2 should contain only the time of day part + CHECK(t2.tm_year == 0); + CHECK(t2.tm_mon == 0); + CHECK(t2.tm_mday == 1); + CHECK(t2.tm_hour == 22); + CHECK(t2.tm_min == 51); + CHECK(t2.tm_sec == 3); + + // t3 should contain all information + CHECK(t3.tm_year == 2009 - 1900); + CHECK(t3.tm_mon == 6 - 1); + CHECK(t3.tm_mday == 17); + CHECK(t3.tm_hour == 22); + CHECK(t3.tm_min == 51); + CHECK(t3.tm_sec == 3); +} + +// test for number of affected rows + +struct table_creator_for_test11 : table_creator_base +{ + table_creator_for_test11(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +TEST_CASE("PostgreSQL get affected rows", "[postgresql][affected-rows]") +{ + soci::session sql(backEnd, connectString); + + table_creator_for_test11 tableCreator(sql); + + for (int i = 0; i != 10; i++) + { + sql << "insert into soci_test(val) values(:val)", use(i); + } + + statement st1 = (sql.prepare << + "update soci_test set val = val + 1"); + st1.execute(false); + + CHECK(st1.get_affected_rows() == 10); + + statement st2 = (sql.prepare << + "delete from soci_test where val <= 5"); + st2.execute(false); + + CHECK(st2.get_affected_rows() == 5); +} + +// test INSERT INTO ... RETURNING syntax + +struct table_creator_for_test12 : table_creator_base +{ + table_creator_for_test12(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(sid serial, txt text)"; + } +}; + +TEST_CASE("PostgreSQL insert into ... returning", "[postgresql]") +{ + soci::session sql(backEnd, connectString); + + table_creator_for_test12 tableCreator(sql); + + std::vector ids(10); + for (std::size_t i = 0; i != ids.size(); i++) + { + long sid(0); + std::string txt("abc"); + sql << "insert into soci_test(txt) values(:txt) returning sid", use(txt, "txt"), into(sid); + ids[i] = sid; + } + + std::vector ids2(ids.size()); + sql << "select sid from soci_test order by sid", into(ids2); + CHECK(std::equal(ids.begin(), ids.end(), ids2.begin())); +} + +struct bytea_table_creator : public table_creator_base +{ + bytea_table_creator(soci::session& sql) + : table_creator_base(sql) + { + sql << "drop table if exists soci_test;"; + sql << "create table soci_test ( val bytea null )"; + } +}; + +TEST_CASE("PostgreSQL bytea", "[postgresql][bytea]") +{ + soci::session sql(backEnd, connectString); + + // PostgreSQL supports two different output formats for bytea values: + // historical "escape" format, which is the only one supported until + // PostgreSQL 9.0, and "hex" format used by default since 9.0, we need + // to determine which one is actually in use. + std::string bytea_output_format; + sql << "select setting from pg_settings where name='bytea_output'", + into(bytea_output_format); + char const* expectedBytea; + if (bytea_output_format.empty() || bytea_output_format == "escape") + expectedBytea = "\\015\\014\\013\\012"; + else if (bytea_output_format == "hex") + expectedBytea = "\\x0d0c0b0a"; + else + throw std::runtime_error("Unknown PostgreSQL bytea_output \"" + + bytea_output_format + "\""); + + bytea_table_creator tableCreator(sql); + + int v = 0x0A0B0C0D; + unsigned char* b = reinterpret_cast(&v); + std::string data; + std::copy(b, b + sizeof(v), std::back_inserter(data)); + { + + sql << "insert into soci_test(val) values(:val)", use(data); + + // 1) into string, no Oid mapping + std::string bin1; + sql << "select val from soci_test", into(bin1); + CHECK(bin1 == expectedBytea); + + // 2) Oid-to-dt_string mapped + row r; + sql << "select * from soci_test", into(r); + + REQUIRE(r.size() == 1); + column_properties const& props = r.get_properties(0); + CHECK(props.get_data_type() == soci::dt_string); + std::string bin2 = r.get(0); + CHECK(bin2 == expectedBytea); + } +} + +// json +struct table_creator_json : public table_creator_base +{ + table_creator_json(soci::session& sql) + : table_creator_base(sql) + { + sql << "drop table if exists soci_json_test;"; + sql << "create table soci_json_test(data json)"; + } +}; + +// Return 9,2 for 9.2.3 +typedef std::pair server_version; + +server_version get_postgresql_version(soci::session& sql) +{ + std::string version; + std::pair result; + sql << "select version()",into(version); + if (sscanf(version.c_str(),"PostgreSQL %i.%i", &result.first, &result.second) < 2) + { + throw std::runtime_error("Failed to retrieve PostgreSQL version number"); + } + return result; +} + +// Test JSON. Only valid for PostgreSQL Server 9.2++ +TEST_CASE("PostgreSQL JSON", "[postgresql][json]") +{ + soci::session sql(backEnd, connectString); + server_version version = get_postgresql_version(sql); + if ( version >= server_version(9,2)) + { + std::string result; + std::string valid_input = "{\"tool\":\"soci\",\"result\":42}"; + std::string invalid_input = "{\"tool\":\"other\",\"result\":invalid}"; + + table_creator_json tableCreator(sql); + + sql << "insert into soci_json_test (data) values(:data)",use(valid_input); + sql << "select data from soci_json_test",into(result); + CHECK(result == valid_input); + + CHECK_THROWS_AS(( + sql << "insert into soci_json_test (data) values(:data)",use(invalid_input)), + soci_error + ); + } + else + { + WARN("JSON test skipped (PostgreSQL >= 9.2 required, found " << version.first << "." << version.second << ")"); + } +} + +struct table_creator_text : public table_creator_base +{ + table_creator_text(soci::session& sql) : table_creator_base(sql) + { + sql << "drop table if exists soci_test;"; + sql << "create table soci_test(name varchar(20))"; + } +}; + +// Test deallocate_prepared_statement called for non-existing statement +// which creation failed due to invalid SQL syntax. +// https://github.com/SOCI/soci/issues/116 +TEST_CASE("PostgreSQL statement prepare failure", "[postgresql][prepare]") +{ + soci::session sql(backEnd, connectString); + table_creator_text tableCreator(sql); + + try + { + // types mismatch should lead to PQprepare failure + statement get_trades = + (sql.prepare + << "select * from soci_test where name=9999"); + FAIL("expected exception not thrown"); + } + catch(soci_error const& e) + { + std::string const msg(e.what()); + CAPTURE(msg); + + // poor-man heuristics + CHECK(msg.find("prepared statement") == std::string::npos); + CHECK(msg.find("operator does not exist") != std::string::npos); + } +} + +// Test the support of PostgreSQL-style casts with ORM +TEST_CASE("PostgreSQL ORM cast", "[postgresql][orm]") +{ + soci::session sql(backEnd, connectString); + values v; + v.set("a", 1); + sql << "select :a::int", use(v); // Must not throw an exception! +} + +// +// Support for soci Common Tests +// + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh int2, ul numeric(20), d float8, " + "num76 numeric(7,6), " + "tm timestamp, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float8, num_int integer," + " name varchar(20), sometime timestamp, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// Common tests context +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, std::string const &connectString) + : test_context_base(backEnd, connectString) + {} + + table_creator_base* table_creator_1(soci::session& s) const + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(soci::session& s) const + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(soci::session& s) const + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(soci::session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "timestamptz(\'" + datdt_string + "\')"; + } + + virtual bool has_fp_bug() const + { + return false; + } +}; + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run assert()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc >= 2) + { + connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; + } + else + { + std::cout << "usage: " << argv[0] + << " connectstring [test-arguments...]\n" + << "example: " << argv[0] + << " \'connect_string_for_PostgreSQL\'\n"; + return EXIT_FAILURE; + } + + test_context tc(backEnd, connectString); + + return Catch::Session().run(argc, argv); +} diff --git a/src/backends/sqlite3/test/CMakeLists.txt b/tests/sqlite3/CMakeLists.txt similarity index 73% rename from src/backends/sqlite3/test/CMakeLists.txt rename to tests/sqlite3/CMakeLists.txt index 4e21f54b02..0fe8e77383 100644 --- a/src/backends/sqlite3/test/CMakeLists.txt +++ b/tests/sqlite3/CMakeLists.txt @@ -2,7 +2,7 @@ # # This file is part of CMake configuration for SOCI library # -# Copyright (C) 2010 Mateusz Loskot +# Copyright (C) 2010-2013 Mateusz Loskot # 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) @@ -11,5 +11,6 @@ soci_backend_test( BACKEND SQLite3 - SOURCE test-sqlite3.cpp - CONNSTR "soci_test.db") \ No newline at end of file + DEPENDS SQLite3 + SOURCE test-sqlite3.cpp ${SOCI_TESTS_COMMON} + CONNSTR ":memory:") diff --git a/tests/sqlite3/test-sqlite3.cpp b/tests/sqlite3/test-sqlite3.cpp new file mode 100644 index 0000000000..5b27981c1a --- /dev/null +++ b/tests/sqlite3/test-sqlite3.cpp @@ -0,0 +1,403 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, David Courtney +// 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 +#include +#include "common-tests.h" +#include +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_sqlite3(); + +// ROWID test +// In sqlite3 the row id can be called ROWID, _ROWID_ or oid +TEST_CASE("SQLite rowid", "[sqlite][rowid][oid]") +{ + soci::session sql(backEnd, connectString); + + try { sql << "drop table test1"; } + catch (soci_error const &) {} // ignore if error + + sql << + "create table test1 (" + " id integer," + " name varchar(100)" + ")"; + + sql << "insert into test1(id, name) values(7, \'John\')"; + + rowid rid(sql); + sql << "select oid from test1 where id = 7", into(rid); + + int id; + std::string name; + + sql << "select id, name from test1 where oid = :rid", + into(id), into(name), use(rid); + + CHECK(id == 7); + CHECK(name == "John"); + + sql << "drop table test1"; +} + +// BLOB test +struct blob_table_creator : public table_creator_base +{ + blob_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << + "create table soci_test (" + " id integer," + " img blob" + ")"; + } +}; + +TEST_CASE("SQLite blob", "[sqlite][blob]") +{ + soci::session sql(backEnd, connectString); + + blob_table_creator tableCreator(sql); + + char buf[] = "abcdefghijklmnopqrstuvwxyz"; + + sql << "insert into soci_test(id, img) values(7, '')"; + + { + 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)); + sql << "update soci_test set img=? where id = 7", use(b); + + b.append(buf, sizeof(buf)); + CHECK(b.get_len() == 2 * sizeof(buf)); + sql << "insert into soci_test(id, img) values(8, ?)", use(b); + } + { + blob b(sql); + sql << "select img from soci_test where id = 8", 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 << "select img from soci_test where id = 7", into(b); + CHECK(b.get_len() == sizeof(buf)); + + } +} + +// This test was put in to fix a problem that occurs when there are both +// into and use elements in the same query and one of them (into) binds +// to a vector object. + +struct test3_table_creator : table_creator_base +{ + test3_table_creator(soci::session & sql) : table_creator_base(sql) + { + sql << "create table soci_test( id integer, name varchar, subname varchar);"; + } +}; + +TEST_CASE("SQLite use and vector into", "[sqlite][use][into][vector]") +{ + soci::session sql(backEnd, connectString); + + test3_table_creator tableCreator(sql); + + sql << "insert into soci_test(id,name,subname) values( 1,'john','smith')"; + sql << "insert into soci_test(id,name,subname) values( 2,'george','vals')"; + sql << "insert into soci_test(id,name,subname) values( 3,'ann','smith')"; + sql << "insert into soci_test(id,name,subname) values( 4,'john','grey')"; + sql << "insert into soci_test(id,name,subname) values( 5,'anthony','wall')"; + + { + std::vector v(10); + + statement s(sql.prepare << "Select id from soci_test where name = :name"); + + std::string name = "john"; + + s.exchange(use(name, "name")); + s.exchange(into(v)); + + s.define_and_bind(); + s.execute(true); + + CHECK(v.size() == 2); + } +} + + +// Test case from Amnon David 11/1/2007 +// I've noticed that table schemas in SQLite3 can sometimes have typeless +// columns. One (and only?) example is the sqlite_sequence that sqlite +// creates for autoincrement . Attempting to traverse this table caused +// SOCI to crash. I've made the following code change in statement.cpp to +// create a workaround: + +struct test4_table_creator : table_creator_base +{ + test4_table_creator(soci::session & sql) : table_creator_base(sql) + { + sql << "create table soci_test (col INTEGER PRIMARY KEY AUTOINCREMENT, name char)"; + } +}; + +TEST_CASE("SQLite select from sequence", "[sqlite][sequence]") +{ + // we need to have an table that uses autoincrement to test this. + soci::session sql(backEnd, connectString); + + test4_table_creator tableCreator(sql); + + sql << "insert into soci_test(name) values('john')"; + sql << "insert into soci_test(name) values('james')"; + + { + int key; + std::string name; + sql << "select * from soci_test", into(key), into(name); + CHECK(name == "john"); + + rowset rs = (sql.prepare << "select * from sqlite_sequence"); + rowset::const_iterator it = rs.begin(); + row const& r1 = (*it); + CHECK(r1.get(0) == "soci_test"); + CHECK(r1.get(1) == "2"); + } +} + +struct longlong_table_creator : table_creator_base +{ + longlong_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val number(20))"; + } +}; + +// long long test +TEST_CASE("SQLite long long", "[sqlite][longlong]") +{ + soci::session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + long long v1 = 1000000000000LL; + sql << "insert into soci_test(val) values(:val)", use(v1); + + long long v2 = 0LL; + sql << "select val from soci_test", into(v2); + + CHECK(v2 == v1); +} + +TEST_CASE("SQLite vector long long", "[sqlite][vector][longlong]") +{ + soci::session sql(backEnd, connectString); + + longlong_table_creator tableCreator(sql); + + std::vector v1; + v1.push_back(1000000000000LL); + v1.push_back(1000000000001LL); + v1.push_back(1000000000002LL); + v1.push_back(1000000000003LL); + v1.push_back(1000000000004LL); + + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::vector v2(10); + sql << "select val from soci_test order by val desc", into(v2); + + REQUIRE(v2.size() == 5); + CHECK(v2[0] == 1000000000004LL); + CHECK(v2[1] == 1000000000003LL); + CHECK(v2[2] == 1000000000002LL); + CHECK(v2[3] == 1000000000001LL); + CHECK(v2[4] == 1000000000000LL); +} + +struct table_creator_for_get_last_insert_id : table_creator_base +{ + table_creator_for_get_last_insert_id(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer primary key autoincrement)"; + sql << "insert into soci_test (id) values (41)"; + sql << "delete from soci_test where id = 41"; + } +}; + +TEST_CASE("SQLite last insert id", "[sqlite][last-insert-id]") +{ + soci::session sql(backEnd, connectString); + table_creator_for_get_last_insert_id tableCreator(sql); + sql << "insert into soci_test default values"; + long id; + bool result = sql.get_last_insert_id("soci_test", id); + CHECK(result == true); + CHECK(id == 42); +} + +// DDL Creation objects for common tests +struct table_creator_one : public table_creator_base +{ + table_creator_one(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh smallint, ul numeric(20), d float, " + "num76 numeric(7,6), " + "tm datetime, i1 integer, i2 integer, i3 integer, " + "name varchar(20))"; + } +}; + +struct table_creator_two : public table_creator_base +{ + table_creator_two(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float float, num_int integer," + " name varchar(20), sometime datetime, chr char)"; + } +}; + +struct table_creator_three : public table_creator_base +{ + table_creator_three(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + } +}; + +// Originally, submitted to SQLite3 backend and later moved to common test. +// Test commit b394d039530f124802d06c3b1a969c3117683152 +// Author: Mika Fischer +// Date: Thu Nov 17 13:28:07 2011 +0100 +// Implement get_affected_rows for SQLite3 backend +struct table_creator_for_get_affected_rows : table_creator_base +{ + table_creator_for_get_affected_rows(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) {} + + table_creator_base* table_creator_1(soci::session& s) const + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(soci::session& s) const + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(soci::session& s) const + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(soci::session& s) const + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "datetime(\'" + datdt_string + "\')"; + } + + virtual bool has_fp_bug() const + { + /* + SQLite seems to be buggy when using text conversion, e.g.: + + % echo 'create table t(f real); \ + insert into t(f) values(1.79999999999999982); \ + select * from t;' | sqlite3 + 1.8 + + And there doesn't seem to be any way to avoid this rounding, so we + have no hope of getting back exactly what we write into it unless, + perhaps, we start using sqlite3_bind_double() in the backend code. + */ + + return true; + } + + virtual bool enable_std_char_padding(soci::session& s) const + { + // SQLite does not support right padded char type. + return false; + } +}; + +int main(int argc, char** argv) +{ + +#ifdef _MSC_VER + // Redirect errors, unrecoverable problems, and assert() failures to STDERR, + // instead of debug message window. + // This hack is required to run assert()-driven tests by Buildbot. + // NOTE: Comment this 2 lines for debugging with Visual C++ debugger to catch assertions inside. + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif //_MSC_VER + + if (argc >= 2 && argv[1][0] != '-') + { + connectString = argv[1]; + + // Replace the connect string with the process name to ensure that + // CATCH uses the correct name in its messages. + argv[1] = argv[0]; + + argc--; + argv++; + } + else + { + // If no file name is specfied then work in-memory + connectString = ":memory:"; + } + + test_context tc(backEnd, connectString); + + return Catch::Session().run(argc, argv); +} diff --git a/www/articles.html b/www/articles.html index 78860a9c6f..21b6307182 100644 --- a/www/articles.html +++ b/www/articles.html @@ -30,8 +30,13 @@

The following articles were published about SOCI:

+ +

as well as presentations:

+ diff --git a/www/doc/index.html b/www/doc/index.html index fd07c376b7..141815c3a2 100644 --- a/www/doc/index.html +++ b/www/doc/index.html @@ -19,9 +19,9 @@

Home
Download
- Documentation
+ Documentation
Articles
- People
+ People
Events
Links

diff --git a/www/events.html b/www/events.html index 0946043732..bb41701447 100644 --- a/www/events.html +++ b/www/events.html @@ -30,6 +30,10 @@

Events:

+2015-04-09: +
3.2.3 version is released. +
+ 2013-09-11:
3.2.2 version is released.
diff --git a/www/index.html b/www/index.html index 1d4879c8ad..cb836da6c9 100644 --- a/www/index.html +++ b/www/index.html @@ -37,7 +37,7 @@ C++ code, staying entirely within the Standard C++.

databases in the most natural and intuitive way. If you find existing libraries too difficult for your needs or just distracting, SOCI can be a good alternative.

- +

The simplest motivating code example for the SQL query that is supposed to retrieve a single row is:

@@ -69,12 +69,12 @@ copy(rs.begin(), rs.end(), ostream_iterator<string>(cout, "\n"));
 
 

SOCI offers also extensive integration with Boost datatypes (optional, tuple and fusion) and flexible support for user-defined datatypes.

- +

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.

- +

Starting from its 2.0.0 release, SOCI uses the plug-in architecture for backends - this allows to target various database servers. -Currently (3.2.2), the following database systems are supported:

+Currently (3.2.3), the following database systems are supported:

  • DB2
  • Firebird
  • @@ -97,11 +97,11 @@ If you are interested in participating, please contact the .

    All SOCI downloads are hosted on SourceForge.net servers. The current -stable release (3.2.2) can be downloaded here, -and all previous releases are available +stable release (3.2.3) can be downloaded here, +and all previous releases are available here.

    -

    The development of SOCI happens on GitHub. All repositories live +

    The development of SOCI happens on GitHub. All repositories live under the SOCI organization where all Git repositories are available.

    @@ -110,20 +110,20 @@ organization where all Git repositories are available.

    $ git clone git://github.com/SOCI/soci.git
-

The Issues tracker is open +

The Issues tracker is open for bug reports and patches submission.

-

The best way to contribute to SOCI is to follow the typical GitHub workflow: +

The best way to contribute to SOCI is to follow the typical GitHub workflow: fork SOCI, apply your edits and submit Pull Request.
Feel free to join SOCI development!

-

To meet other users, please consider subscribing to the -SOCI-users mailing list. -There is also SOCI-devel mailing list +

To meet other users, please consider subscribing to the +SOCI-users mailing list. +There is also SOCI-devel mailing list available dedicated to development discussions only.

-

There is also community-driven Wiki -and FAQ hosted at GitHub, where everybody +

There is also community-driven Wiki +and FAQ hosted at GitHub, where everybody is welcome to contribute.

diff --git a/www/style.css b/www/style.css index 399ff792f9..8836c3ba74 100644 --- a/www/style.css +++ b/www/style.css @@ -1,5 +1,5 @@ body -{ +{ background-color: white; color: black; margin-left: 100px;