diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7f8708d1..50c4ac6e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,85 +29,61 @@ on: jobs: build: - runs-on: ${{ matrix.runner || 'ubuntu-22.04' }} - container: ${{ matrix.container }} - name: ${{ matrix.name }} strategy: fail-fast: false matrix: + lib_type: [shared, static] + # Can't test DB2 as required db2exc package is no longer available after Ubuntu 14.04 + backend: [SQLite3, PostgreSQL, MySQL, Firebird, Oracle, ODBC, Empty, Valgrind] + runner: [ubuntu-22.04] include: - # Note: the jobs are ordered in the order of decreasing running - # time, as this should minimize the total run-time of all jobs. - - backend: postgresql + - lib_type: shared + backend: Empty runner: macos-14 - name: PostgreSQL macOS - - backend: oracle - name: Oracle 11 no_boost: true - - backend: valgrind - name: Valgrind - - backend: odbc - # There are many leak reports under Ubuntu 22.04, see #1008. - container: ubuntu:18.04 - name: ODBC - # UBSAN gives nonsensical errors on this system, to be reviewed - # after migrating to newer Ubuntu and compiler versions. - no_ubsan: true - - backend: firebird - name: Firebird - - backend: postgresql - name: PostgreSQL Linux - - backend: mysql - name: MySQL - - backend: sqlite3 + - lib_type: shared + backend: PostgreSQL runner: macos-14 - name: SQLite3 macOS - - backend: sqlite3 - name: SQLite3 C++17 - cxxstd: 17 - - backend: sqlite3 - name: SQLite3 - - backend: empty + no_boost: true + - lib_type: shared + backend: SQLite3 runner: macos-14 - name: Empty macOS - - backend: empty - name: Empty + no_boost: true + - lib_type: shared + backend: Oracle + runner: ubuntu-22.04 + no_boost: true + - lib_type: shared + backend: SQLite3 + runner: ubuntu-22.04 + cxxstd: 17 + name: SQLite3 Cxx17 + - lib_type: shared + backend: Empty test_release_package: true - # Unsupported: db2exc package is only available in Ubuntu 14.04 not - # supported by GitHub Actions any longer, we'd need to run it in - # Docker container if we really need it. - # backend: db2 - - backend: empty - name: Examples + name: Release package + - lib_type: shared + backend: Empty build_examples: true + name: Examples + + runs-on: ${{ matrix.runner }} + name: ${{ format('{0} on {1}', (matrix.name != '' && matrix.name || format('{0} ({1})', matrix.backend, matrix.lib_type)), matrix.runner) }} env: SOCI_CI: true SOCI_CI_BACKEND: ${{ matrix.backend }} SOCI_MYSQL_ROOT_PASSWORD: root ASAN_OPTIONS: fast_unwind_on_malloc=0 + UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1' + SOCI_CXXSTD: 14 steps: - name: Checkout - run: | - case "${{matrix.container}}" in - ubuntu:18.04) - export DEBIAN_FRONTEND=noninteractive - - apt-get update -qq - apt-get install -qq git - ;; - esac - - git config --global init.defaultBranch master - git config --global --add safe.directory `pwd` - git config --global advice.detachedHead false - git init . - git remote add origin https://github.com/SOCI/soci.git - git fetch --depth=1 origin $GITHUB_SHA - git checkout FETCH_HEAD + uses: actions/checkout@v4 - name: Set environment variables + shell: bash run: | set_env_var() { echo "Setting environment variable $1=$2" @@ -130,14 +106,6 @@ jobs: ;; esac - case "${{matrix.container}}" in - ubuntu:18.04) - # We need to use this option as GitHub certificate is not recognized by - # wget in this old container otherwise. - set_env_var SOCI_WGET_OPTIONS --no-check-certificate - ;; - esac - if [ -n "${{matrix.cxxstd}}" ]; then set_env_var SOCI_CXXSTD ${{matrix.cxxstd}} fi @@ -150,15 +118,15 @@ jobs: if [ "${{matrix.build_examples}}" = true ]; then set_env_var BUILD_EXAMPLES YES fi - if [ "${{matrix.no_ubsan}}" = true ]; then - set_env_var SOCI_NO_UBSAN 1 - else - set_env_var UBSAN_OPTIONS print_stacktrace=1:halt_on_error=1 + if [ "${{matrix.lib_type}}" = "static" ]; then + set_env_var SOCI_BUILD_STATIC YES fi + # Ensure SOCI_CI_BACKEND is always lowercase + set_env_var SOCI_CI_BACKEND "$( echo "$SOCI_CI_BACKEND" | tr '[:upper:]' '[:lower:]' )" - name: Setup tmate - uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && inputs.enable_ssh }} + uses: mxschmitt/action-tmate@v3 - name: Install dependencies run: ./scripts/ci/install.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 24effe133..a172eb314 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,316 +1,136 @@ -############################################################################### -# -# 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...3.20 FATAL_ERROR) +# Note: we need >= v3.23 in order to make use of file sets for header installation +cmake_minimum_required(VERSION 3.23...3.31 FATAL_ERROR) -project(SOCI) +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/find_modules") -if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 14) - set(CMAKE_CXX_STANDARD 14) -endif() -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Path to additional CMake modules -set(CMAKE_MODULE_PATH ${SOCI_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) -set(CMAKE_MODULE_PATH ${SOCI_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) - -include(SociUtilities) - -############################################################################### -# Build features and variants -############################################################################## - -option(SOCI_SHARED "Enable build of shared libraries" ON) -option(SOCI_STATIC "Enable build of static libraries" ON) -option(SOCI_TESTS "Enable build of collection of SOCI tests" ON) -option(SOCI_ASAN "Enable address sanitizer on GCC v4.8+/Clang v 3.1+" OFF) -option(SOCI_UBSAN "Enable undefined behaviour sanitizer" OFF) -option(SOCI_LTO "Enable link time optimization" OFF) -option(SOCI_VISIBILITY "Enable hiding private symbol using ELF visibility if supported by the platform" ON) - -if (SOCI_LTO) - cmake_minimum_required(VERSION 3.9) +include(soci_parse_version) +soci_parse_version( + ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE SOCI_VERSION +) - # Check and enable lto support - include(CheckIPOSupported) - check_ipo_supported(RESULT supported) +project(SOCI + VERSION ${SOCI_VERSION} + DESCRIPTION "C++ database access library" + HOMEPAGE_URL "https://soci.sourceforge.net/" + LANGUAGES CXX +) - if (NOT supported) - message(STATUS "IPO / LTO not supported") - endif() +include(soci_utils) - if (supported AND NOT SOCI_ASAN) - message(STATUS "IPO / LTO enabled") - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) - - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # Check for lld as clang lto works best with its own linker - soci_use_ld_if_supported(lld) - endif() - elseif(supported) - message(STATUS "IPO / LTO is supported but conflicts with ASAN and not enabled") - endif() +if (NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED OR CMAKE_CXX_STANDARD LESS 14) + set(CMAKE_CXX_STANDARD 14) endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) -if (SOCI_VISIBILITY) - # Test whether visibility is supported - include(CheckCSourceCompiles) - check_c_source_compiles( - " - __attribute__ (( visibility(\"default\") )) int f1() { return 0; } - __attribute__ (( visibility(\"hidden\") )) int f2() { return 1; } +include(CMakeDependentOption) +include(CheckIPOSupported) +include(CheckCXXCompilerFlag) - int main(int argc, char* argv[]) { f1(); f2(); return 0; } - " - SOCI_HAVE_VISIBILITY_SUPPORT - ) +check_ipo_supported(RESULT LTO_AVAILABLE) - if (SOCI_HAVE_VISIBILITY_SUPPORT) - message(STATUS "gcc / clang visibility enabled") - set(CMAKE_CXX_VISIBILITY_PRESET hidden) - cmake_policy(SET CMP0063 NEW) - endif() +if (SOCI_STATIC) + set(SHARED_DEFAULT OFF) else() - set(SOCI_HAVE_VISIBILITY_SUPPORT off) -endif() - -# Allow using alternative linker such as mold, which can be significantly -# faster than GNU ld. -option(SOCI_LD "Use non-default linker, such as 'mold'" "") -if(SOCI_LD) - # -fuse-ld works only with recent gcc (>= 12), but we don't need to support - # this with all gcc versions as this is entirely optional. I.e. if we really - # wanted to, we could use -B option as explained in mold README to make it - # work with any gcc, but for now keep the things simple. - soci_use_ld_if_supported(${SOCI_LD}) + set(SHARED_DEFAULT ON) endif() -############################################################################### -# SOCI configuration -############################################################################### -include(SociConfig) - -colormsg(_HIBLUE_ "Configuring SOCI:") - -############################################################################### -# SOCI version information -############################################################################### -include(SociVersion) - -soci_version() - -############################################################################### -# Build features and variants -############################################################################## - -boost_report_value(SOCI_SHARED) -boost_report_value(SOCI_STATIC) -boost_report_value(SOCI_TESTS) -boost_report_value(SOCI_ASAN) -boost_report_value(SOCI_UBSAN) - -# from SociConfig.cmake -boost_report_value(LIB_SUFFIX) - -# Put the libaries and binaries that get built into directories at the -# top of the build tree rather than in hard-to-find leaf -# 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) +option(SOCI_SHARED "Enable building SOCI as a shared library" ${SHARED_DEFAULT}) +option(SOCI_TESTS "Enable building SOCI test cases" ${PROJECT_IS_TOP_LEVEL}) +option(SOCI_ASAN "Enable building SOCI with enabled address sanitizers" OFF) +option(SOCI_UBSAN "Enable undefined behaviour sanitizer" OFF) +cmake_dependent_option(SOCI_LTO "Enable link time optimizations in release builds" ON "LTO_AVAILABLE" OFF) +option(SOCI_VISIBILITY "Make all functions hidden by default - this exposes only explicitly exported functions" ON) +set(SOCI_LD "" CACHE STRING "Specify a non-default linker") -############################################################################### -# Find SOCI dependencies -############################################################################### -set(SOCI_CORE_TARGET) -set(SOCI_CORE_TARGET_STATIC) -set(SOCI_CORE_DEPS_LIBS) +# Configure LTO for anything but Debug builds (if enabled in the first place) +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${SOCI_LTO}) +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUG OFF) -include(SociDependencies) +if (SOCI_VISIBILITY) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) +endif() -get_property(SOCI_INCLUDE_DIRS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - PROPERTY INCLUDE_DIRECTORIES) +if (NOT APPLE) + # This makes runtime loaders look for library dependencies + # in the same directory as the library is located in. + # For details see Craig Scott's CppCon 2019 talk -if(Threads_FOUND) - list(APPEND SOCI_CORE_DEPS_LIBS ${CMAKE_THREAD_LIBS_INIT}) -else() - message(FATAL_ERROR "No thread library found") + # Note: The variable's content is set to $ORIGIN literally, + # this is NOT a butchered cmake variable expansion + set(CMAKE_INSTALL_RPATH "$ORIGIN") 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) +if (SOCI_LD) + # CMake asks the compiler to do the linking so we have to pass the desired linker to the compiler + set(USE_LD_FLAG "-fuse-ld=${SOCI_LD}") + check_cxx_compiler_flag("${USE_LD_FLAG}" CAN_USE_CUSTOM_LD) + if (NOT CAN_USE_CUSTOM_LD) + message(FATAL_ERROR "Can't use custom linker '${SOCI_LD}' - compiler doesn't accept flag '${USE_LD_FLAG}'") endif() + add_link_options("${USE_LD_FLAG}") endif() -if(Boost_FOUND) - get_property(SOCI_COMPILE_DEFINITIONS - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - PROPERTY COMPILE_DEFINITIONS) - - set(SOCI_HAVE_BOOST ON) - - list(APPEND SOCI_COMPILE_DEFINITIONS "BOOST_ALL_NO_LIB") - - if(Boost_DATE_TIME_FOUND) - list(APPEND SOCI_CORE_DEPS_LIBS ${Boost_DATE_TIME_LIBRARY}) - set(SOCI_HAVE_BOOST_DATE_TIME ON) +if (NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY) + # Ensure that build artifacts are easy to find. And on Windows this + # guarantees that the built DLLs end up next to applications + # linking to them as otherwise they won't be found. + 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) + + if (NOT PROJECT_IS_TOP_LEVEL) + # If the embedding project does not define these variables, this can lead to + # inconsistencies which can cause issues on Windows when e.g. the embedding + # project has an executable that links to a SOCI DLL which will be put into + # a different directory which will lead to the exe not finding the DLL. + message(WARNING "Setting CMAKE_{LIBRARY, ARCHIVE, RUNTIME}_DIRECTORY variables which should have done by the embedding cmake project") endif() +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}") +if (SOCI_SHARED) + set(SOCI_LIB_TYPE "SHARED") else() - set(SOCI_HAVE_BOOST OFF) - set(SOCI_HAVE_BOOST_DATE_TIME OFF) + set(SOCI_LIB_TYPE "STATIC") endif() -set(SOCI_HAVE_BOOST ${SOCI_HAVE_BOOST} CACHE INTERNAL "Boost library") -set(SOCI_HAVE_BOOST_DATE_TIME ${SOCI_HAVE_BOOST_DATE_TIME} CACHE INTERNAL "Boost date_time library") - -list(APPEND SOCI_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}) - -set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - PROPERTY - INCLUDE_DIRECTORIES ${SOCI_INCLUDE_DIRS}) - -############################################################################### -# Installation -############################################################################### -include(GNUInstallDirs) +set(SOCI_LIB_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}soci_" CACHE STRING "Specifies prefix for the lib directory") +set(SOCI_LIB_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}" CACHE STRING "Specifies suffix for the lib directory") +set(SOCI_DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}" CACHE STRING "Specifies suffix for the library file in debug mode") -############################################################################### -# Configuration files -############################################################################### -set(CONFIG_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/include) -install(DIRECTORY ${CONFIG_INCLUDE_DIR}/soci DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -set(CONFIG_FILE_IN "include/soci/soci-config.h.in") -set(CONFIG_FILE_OUT "${CONFIG_INCLUDE_DIR}/soci/soci-config.h") +include(soci_compiler_options) +include(soci_install_dirs) - -############################################################################### -# Build configured components -############################################################################### -include(SociBackend) - -include_directories(${SOCI_SOURCE_DIR}/include ${CONFIG_INCLUDE_DIR}) add_subdirectory(src) -if(SOCI_TESTS) - ############################################################################### - # Enable tests - ############################################################################### - enable_testing() +include(soci_compat) - file(TO_NATIVE_PATH ${PROJECT_SOURCE_DIR} TEST_ACCESS_PATH) - configure_file(${PROJECT_SOURCE_DIR}/cmake/configs/test-access.cmake ${PROJECT_SOURCE_DIR}/tests/odbc/test-access.dsn @ONLY) - set(MYSQL_DRIVER_NAME "MySQL") - if(WIN32) - set(MYSQL_DRIVER_NAME "MySQL ODBC 5.3 ANSI Driver") - endif() - configure_file(${PROJECT_SOURCE_DIR}/cmake/configs/test-mysql.cmake ${PROJECT_SOURCE_DIR}/tests/odbc/test-mysql.dsn @ONLY) - - # Define "make check" as alias for "make test" - add_custom_target(check COMMAND ctest) +if (SOCI_TESTS) + include(CTest) + enable_testing() add_subdirectory(tests) endif() -############################################################################### -# platform-dependent settings -############################################################################### - -include(CheckTypeSize) -check_type_size(long SOCI_SIZEOF_LONG) -set(SOCI_SIZEOF_LONG ${SOCI_SIZEOF_LONG} CACHE INTERNAL "Size of long") - -include(CheckCXXSourceCompiles) - -function(soci_check_types_are_same type1 type2) - string(TOUPPER ${type1} type1_upper) - string(MAKE_C_IDENTIFIER ${type1_upper} type1_id) - string(TOUPPER ${type2} type2_upper) - string(MAKE_C_IDENTIFIER ${type2_upper} type2_id) - - check_cxx_source_compiles(" -#include - -struct Foo { - void foo(${type1} x); - void foo(${type2} x); -}; - -int main() { return 0; } -" - "SOCI_TYPES_${type1_id}_AND_${type2_id}_DIFFER" - ) - - if(SOCI_TYPES_${type1_id}_AND_${type2_id}_DIFFER) - set(soci_types_equal FALSE) - else() - set(soci_types_equal TRUE) - endif() - - set("SOCI_${type1_id}_IS_${type2_id}" ${soci_types_equal} CACHE INTERNAL "${type1} and ${type2} are the same type") -endfunction() - -if(SOCI_SIZEOF_LONG EQUAL 8) - # int64_t can be defined as either long or long long in this case, find out - # which one we have. - soci_check_types_are_same("int64_t" "long") -elseif(SOCI_SIZEOF_LONG EQUAL 4) - # Nothing to do in this case, actually, as it's never going to be the same - # as int32_t which is defined as int and not long in this case. -else() - message(FATAL_ERROR "Unsupported size of long=${SOCI_SIZEOF_LONG}") -endif() - -soci_check_types_are_same("int8_t" "char") - -############################################################################### -# build config file -############################################################################### - -get_cmake_property(ALL_VARIABLES CACHE_VARIABLES) -set(CONFIGURED_VARIABLES) -foreach(v ${ALL_VARIABLES}) - if (v MATCHES "^SOCI_(HAVE|[A-Z0-9_]+_IS)_") - get_property(CACHE_HELPSTRING CACHE ${v} PROPERTY HELPSTRING) - set(CONFIGURED_VARIABLES "${CONFIGURED_VARIABLES}\n// ${CACHE_HELPSTRING}\n") - if (${${v}}) - set(CONFIGURED_VARIABLES "${CONFIGURED_VARIABLES}#define ${v}\n") - else() - set(CONFIGURED_VARIABLES "${CONFIGURED_VARIABLES}/* #undef ${v} */\n") - endif() - endif() - - if (v MATCHES "^SOCI_SIZEOF_") - get_property(CACHE_HELPSTRING CACHE ${v} PROPERTY HELPSTRING) - set(CONFIGURED_VARIABLES "${CONFIGURED_VARIABLES}\n// ${CACHE_HELPSTRING}\n") - set(CONFIGURED_VARIABLES "${CONFIGURED_VARIABLES}#define ${v} ${${v}}\n") - endif() -endforeach() -configure_file("${CONFIG_FILE_IN}" "${CONFIG_FILE_OUT}") -message(STATUS "") +# Packaging +include(CMakePackageConfigHelpers) +configure_package_config_file("soci-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/soci-config.cmake" + INSTALL_DESTINATION "${SOCI_INSTALL_CMAKEDIR}" +) +write_basic_package_version_file(soci-config-version.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/soci-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/soci-config-version.cmake" + DESTINATION "${SOCI_INSTALL_CMAKEDIR}" +) diff --git a/appveyor.yml b/appveyor.yml index 3b4cb4cae..8e4a2fccb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,39 +8,44 @@ configuration: Release environment: matrix: - - G: "Visual Studio 17 2022" - BOOST_ROOT: C:\Libraries\boost_1_77_0 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + G: "Ninja" + BOOST_ROOT: C:\Libraries\boost_1_84_0 MSSQL_VER: 2019 - POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\13 - POSTGRESQL_VER: 13 + POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\15 + POSTGRESQL_VER: 15 MYSQL_VER: 80 MYSQL_DIR: C:\Program Files\MySql\MySQL Server 8.0 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - - G: "Visual Studio 16 2019" - BOOST_ROOT: C:\Libraries\boost_1_73_0 + VCVARS_SCRIPT: "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build" + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + G: "Ninja" + BOOST_ROOT: C:\Libraries\boost_1_77_0 MSSQL_VER: 2017 - POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\12 - POSTGRESQL_VER: 12 + POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\13 + POSTGRESQL_VER: 13 MYSQL_VER: 80 MYSQL_DIR: C:\Program Files\MySql\MySQL Server 8.0 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - - G: "Visual Studio 15 2017 Win64" + VCVARS_SCRIPT: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Auxiliary/Build" + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + G: "Ninja" BOOST_ROOT: C:\Libraries\boost_1_69_0 MSSQL_VER: 2016 - POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\11 - POSTGRESQL_VER: 11 + POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\12 + POSTGRESQL_VER: 12 MYSQL_VER: 57 MYSQL_DIR: C:\Program Files\MySql\MySQL Server 5.7 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - - G: "Visual Studio 14 2015 Win64" + VCVARS_SCRIPT: "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Auxiliary/Build" + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + G: "Ninja" BOOST_ROOT: C:\Libraries\boost_1_60_0 MSSQL_VER: 2014 - POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\10 - POSTGRESQL_VER: 10 + POSTGRESQL_ROOT: C:\Program Files\PostgreSQL\11 + POSTGRESQL_VER: 11 MYSQL_VER: 57 MYSQL_DIR: C:\Program Files\MySql\MySQL Server 5.7 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - - G: "MinGW Makefiles" + VCVARS_SCRIPT: "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC" + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + G: "MinGW Makefiles" MINGW_BIN: C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin MSSQL_VER: 2014 BOOST_ROOT: C:\Libraries\boost_1_60_0 @@ -48,7 +53,7 @@ environment: POSTGRESQL_VER: 9.4 MYSQL_VER: 57 MYSQL_DIR: C:\Program Files\MySql\MySQL Server 5.7 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + VCVARS_SCRIPT: "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC" install: # Start these ones here as we can't specify the service name dynamically above. @@ -59,13 +64,18 @@ install: Import-Module C:\projects\soci\scripts\windows\Get-ODBCList.ps1 $drivers_list = Get-ODBCList Write-Output $drivers_list - if ($drivers_list -clike '*MySQL*') { - $env:SOCI_ODBC_TESTS = $env:SOCI_ODBC_TESTS + '|soci_odbc_test_mysql' + $env:SOCI_ODBC_SKIP_TESTS = "soci_odbc_ms_access_tests" + if (-Not ($drivers_list -contains 'MySQL')) { + $env:SOCI_ODBC_SKIP_TESTS = $env:SOCI_ODBC_SKIP_TESTS + '|soci_odbc_mysql_tests' } - if ($drivers_list -clike '*PostgreSQL*') { - $env:SOCI_ODBC_TESTS = $env:SOCI_ODBC_TESTS + '|soci_odbc_test_postgresql' + if (-Not ($drivers_list -contains 'PostgreSQL')) { + $env:SOCI_ODBC_SKIP_TESTS = $env:SOCI_ODBC_SKIP_TESTS + '|soci_odbc_postgresql_tests' } + Write-Output "To be skipped ODBC tests: $env:SOCI_ODBC_SKIP_TESTS" - git clone https://github.com/snikulov/sqlite.cmake.build.git C:\projects\sqlite\src + # Ensure we have a recent cmake version available + - choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System' + - refreshenv before_build: - set SQLITE_ROOT=C:\projects\sqlite\sqlite @@ -87,7 +97,16 @@ before_build: else { $env:SQLITE3_LIBRARY = $env:SQLITE_ROOT + '/lib/sqlite3-static.lib' - $env:BUILD_TOOL_OPTIONS = '/m' + $env:BUILD_TOOL_OPTIONS = '' + # Set up Visual Studio environment (MSVC compiler etc.) + # Works by calling the respective bat script and then copying over the env vars + cd $env:VCVARS_SCRIPT + cmd /c "vcvarsall.bat x64&set" | + foreach { + if ($_ -match "=") { + $v = $_.split("="); set-item -force -path "ENV:\$($v[0])" -value "$($v[1])" + } + } } - cd C:\projects\sqlite\src - mkdir build @@ -102,18 +121,18 @@ before_build: - set USER=root - mysql -e "create database soci_test;" --user=root - sqlcmd -U sa -P Password12! -S (local)\SQL%MSSQL_VER% -i C:\projects\soci\scripts\windows\mssql_db_create.sql - - cmake .. -G"%G%" -DSQLITE_BUILD_SHARED=OFF -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_INSTALL_PREFIX=%SQLITE_ROOT% + - cmake .. -G"%G%" -DSQLITE_BUILD_SHARED=OFF -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_INSTALL_PREFIX=%SQLITE_ROOT% -DCMAKE_UNITY_BUILD=ON - cmake --build . --config %CONFIGURATION% --target install build_script: - cd C:\projects\soci - mkdir build - cd build - - cmake .. -G"%G%" -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_VERBOSE_MAKEFILE=ON -DSOCI_ENABLE_WERROR=ON -DSQLITE3_INCLUDE_DIR=%SQLITE_ROOT%/include -DSQLITE3_LIBRARY=%SQLITE3_LIBRARY% + - cmake .. -G"%G%" -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_VERBOSE_MAKEFILE=ON -DSOCI_ENABLE_WERROR=ON -DCMAKE_PREFIX_PATH=%SQLITE_ROOT% -DSQLite3_LIBRARY=%SQLITE3_LIBRARY% -DCMAKE_UNITY_BUILD=ON - cmake --build . --config %CONFIGURATION% -- %BUILD_TOOL_OPTIONS% test_script: - - ctest -V --timeout 300 --output-on-failure -R "soci_empty|soci_postgresql|soci_sqlite3|soci_odbc_test_mssql|soci_mysql%SOCI_ODBC_TESTS%" + - ctest -V --timeout 300 --output-on-failure --build-config %CONFIGURATION% --exclude-regex "%SOCI_ODBC_SKIP_TESTS%" # Uncomment this to wait for RDP connection after the build end. #on_finish: diff --git a/cmake/.gitignore b/cmake/.gitignore deleted file mode 100644 index 61caff875..000000000 --- a/cmake/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -*~ -*.kdev[0-9] -*.swp -aclocal.m4 -autom4te.cache -confdefs.h -config.guess -config.log -config.status -config.sub -configure -depcomp -install-sh -libtool -ltmain.sh -m4 -missing -Makefile -Makefile.in -tmp - diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt deleted file mode 100644 index 317236e6a..000000000 --- a/cmake/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -############################################################################### -# -# This file is part of CMake configuration for SOCI library -# -# Copyright (C) 2009 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) -# -############################################################################### - -# install the cmake modules -file(GLOB SOCI_CMAKE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.cmake") diff --git a/cmake/SociBackend.cmake b/cmake/SociBackend.cmake deleted file mode 100644 index b5547f8f4..000000000 --- a/cmake/SociBackend.cmake +++ /dev/null @@ -1,390 +0,0 @@ -################################################################################ -# SociBackend.cmake - part of CMake configuration of 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) -################################################################################ -# Macros in this module: -# -# soci_backend -# - defines project of a database backend for SOCI library -# -# soci_backend_test -# - 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_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) - if( ${DEPU}_INCLUDE_DIR ) - list(APPEND DEPS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIR}) - endif() - if( ${DEPU}_INCLUDE_DIRS ) - list(APPEND DEPS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIRS}) - endif() - list(APPEND DEPS_LIBRARIES ${${DEPU}_LIBRARIES}) - 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(${SUCCESS} True) - endif() - - #message(STATUS "soci_backend_deps_found: ${SUCCESS}=${${SUCCESS}}") -endmacro() - -# Defines project of a database backend for SOCI library -# -# soci_backend(backendname -# DEPENDS dependency1 dependency2 -# DESCRIPTION description -# AUTHORS author1 author2 -# MAINTAINERS maintainer1 maintainer2) -# -macro(soci_backend NAME) - parse_arguments(THIS_BACKEND - "DEPENDS;DESCRIPTION;AUTHORS;MAINTAINERS;" - "" - ${ARGN}) - - colormsg(HIGREEN "${NAME} - ${THIS_BACKEND_DESCRIPTION}") - - # Backend name variants utils - string(TOLOWER "${PROJECT_NAME}" PROJECTNAMEL) - string(TOLOWER "${NAME}" NAMEL) - string(TOUPPER "${NAME}" NAMEU) - - # Backend option available to user - set(THIS_BACKEND_OPTION SOCI_${NAMEU}) - - soci_backend_deps_found(${NAMEU} "${THIS_BACKEND_DEPENDS}" ${NAMEU}_DEPS_FOUND) - if(NOT ${NAMEU}_DEPS_FOUND) - - colormsg(_RED_ "WARNING: Some required dependencies of ${NAME} backend not found:") - - if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 2.8) - foreach(dep ${DEPENDS_NOT_FOUND}) - colormsg(RED " ${dep}") - endforeach() - else() - foreach(dep IN LISTS DEPENDS_NOT_FOUND) - colormsg(RED " ${dep}") - endforeach() - endif() - - # TODO: Abort or warn compilation may fail? --mloskot - colormsg(RED "Skipping") - - set(${THIS_BACKEND_OPTION} OFF) - - else() - - if(${THIS_BACKEND_OPTION}) - - get_directory_property(THIS_INCLUDE_DIRS INCLUDE_DIRECTORIES) - get_directory_property(THIS_COMPILE_DEFS COMPILE_DEFINITIONS) - - # 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}) - - # 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}) - - 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 ${THIS_BACKEND_VAR}_TARGET) - set(${THIS_BACKEND_TARGET_VAR} ${THIS_BACKEND_TARGET}) - - 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} - SHARED - ${THIS_BACKEND_SOURCES} - ${THIS_BACKEND_HEADERS}) - add_library(Soci::${NAMEL} ALIAS ${THIS_BACKEND_TARGET}) - - target_link_libraries(${THIS_BACKEND_TARGET} - ${SOCI_CORE_TARGET} - ${THIS_BACKEND_DEPENDS_LIBRARIES}) - - if(WIN32) - set_target_properties(${THIS_BACKEND_TARGET} - PROPERTIES - OUTPUT_NAME ${THIS_BACKEND_OUTPUT_NAME} - DEFINE_SYMBOL SOCI_DLL) - else() - 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} - PROPERTIES - VERSION ${${PROJECT_NAME}_VERSION} - CLEAN_DIRECT_OUTPUT 1) - endif() - - # Static library target - if(SOCI_STATIC) - set(THIS_BACKEND_TARGET_STATIC ${THIS_BACKEND_TARGET}_static) - - add_library(${THIS_BACKEND_TARGET_STATIC} - STATIC - ${THIS_BACKEND_SOURCES} - ${THIS_BACKEND_HEADERS}) - add_library(Soci::${NAMEL}_static ALIAS ${THIS_BACKEND_TARGET_STATIC}) - - # 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_OUTPUT_NAME} - PREFIX "lib" - CLEAN_DIRECT_OUTPUT 1) - endif() - - # Backend installation - install(FILES ${THIS_BACKEND_HEADERS} - DESTINATION - ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECTNAMEL}/${NAMEL}) - - if (SOCI_SHARED) - install(TARGETS ${THIS_BACKEND_TARGET} - EXPORT SOCI - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - endif() - - if (SOCI_STATIC) - install(TARGETS ${THIS_BACKEND_TARGET_STATIC} - EXPORT SOCI - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - ) - endif() - - else() - colormsg(YELLOW "${NAME} backend explicitly disabled") - endif() - - endif() - - boost_report_value(${THIS_BACKEND_OPTION}) - - if(${THIS_BACKEND_OPTION}) - boost_report_value(${THIS_BACKEND_TARGET_VAR}) - 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 - #message("soci_backend:") - #message("NAME: ${NAME}") - #message("${THIS_BACKEND_OPTION} = ${SOCI_BACKEND_SQLITE3}") - #message("DEPENDS: ${THIS_BACKEND_DEPENDS}") - #message("DESCRIPTION: ${THIS_BACKEND_DESCRIPTION}") - #message("AUTHORS: ${THIS_BACKEND_AUTHORS}") - #message("MAINTAINERS: ${THIS_BACKEND_MAINTAINERS}") - #message("SOURCES: ${THIS_BACKEND_SOURCES}") - #message("DEPENDS_LIBRARIES: ${THIS_BACKEND_DEPENDS_LIBRARIES}") - #message("DEPENDS_INCLUDE_DIRS: ${THIS_BACKEND_DEPENDS_INCLUDE_DIRS}") -endmacro() - -# Generates .vcxproj.user for target of each test. -# -# soci_backend_test_create_vcxproj_user( -# PostgreSQLTest -# "host=localhost dbname=soci_test user=mloskot") -# -function(soci_backend_test_create_vcxproj_user TARGET_NAME TEST_CMD_ARGS) - if(MSVC) - set(SYSTEM_NAME $ENV{USERDOMAIN}) - set(USER_NAME $ENV{USERNAME}) - set(SOCI_TEST_CMD_ARGS ${TEST_CMD_ARGS}) - - if(MSVC_VERSION EQUAL 1600) - configure_file( - ${SOCI_SOURCE_DIR}/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in - ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}.vcxproj.user - @ONLY) - endif() - endif() -endfunction(soci_backend_test_create_vcxproj_user) - -# Defines test project of a database backend for SOCI library -# -# soci_backend_test(BACKEND mybackend SOURCE mytest1.cpp -# NAME mytest1 -# CONNSTR "my test connection" -# DEPENDS library1 library2) -# -macro(soci_backend_test) - parse_arguments(THIS_TEST - "BACKEND;SOURCE;CONNSTR;NAME;DEPENDS;" - "" - ${ARGN}) - - # Test backend name - string(TOUPPER "${THIS_TEST_BACKEND}" BACKENDU) - string(TOLOWER "${THIS_TEST_BACKEND}" BACKENDL) - - if(SOCI_TESTS AND SOCI_${BACKENDU} AND NOT SOCI_${BACKENDU}_DO_NOT_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) - 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: 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_message_value(${TEST_CONNSTR_VAR}) - - if( SOCI_SHARED ) - # Shared libraries test - add_executable(${TEST_TARGET} ${THIS_TEST_SOURCE}) - - target_link_libraries(${TEST_TARGET} - ${SOCI_CORE_DEPS_LIBS} - ${THIS_TEST_DEPENDS_LIBRARIES} - soci_tests_common - soci_core - soci_${BACKENDL}) - - add_test(${TEST_TARGET} - ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TARGET} - ${${TEST_CONNSTR_VAR}} --invisibles) - - 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} ${THIS_TEST_SOURCE}) - - target_link_libraries(${TEST_TARGET_STATIC} - ${SOCI_CORE_DEPS_LIBS} - ${THIS_TEST_DEPENDS_LIBRARIES} - soci_tests_common - soci_${BACKENDL}_static - soci_core_static) - - add_test(${TEST_TARGET_STATIC} - ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TARGET_STATIC} - ${${TEST_CONNSTR_VAR}} --invisibles) - - 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) - - - - # Group source files for IDE source explorers (e.g. Visual Studio) - source_group("Source Files" FILES ${THIS_TEST_SOURCE}) - source_group("CMake Files" FILES CMakeLists.txt) - - endif() -endmacro() diff --git a/cmake/SociConfig.cmake b/cmake/SociConfig.cmake deleted file mode 100644 index 84cd13998..000000000 --- a/cmake/SociConfig.cmake +++ /dev/null @@ -1,101 +0,0 @@ -################################################################################ -# SociConfig.cmake - CMake build configuration of 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) -################################################################################ - -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) - -if(NOT DEFINED LIB_SUFFIX) - if(SOCI_TARGET_ARCH_X64) - set(_lib_suffix "64") - else() - set(_lib_suffix "") - endif() - set(LIB_SUFFIX ${_lib_suffix} CACHE STRING "Specifies suffix for the lib directory") -endif() - -# -# Force compilation flags and set desired warnings level -# - -# This is used to set the -Werror compilation flag only when explicitly -# requested, as e.g. in CI builds. -set(SOCI_WERROR_OPTION "") - -if (MSVC) - add_definitions(-D_CRT_SECURE_NO_DEPRECATE) - add_definitions(-D_CRT_SECURE_NO_WARNINGS) - add_definitions(-D_CRT_NONSTDC_NO_WARNING) - add_definitions(-D_SCL_SECURE_NO_WARNINGS) - - if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") - string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /we4266") - endif() - - if (SOCI_ENABLE_WERROR) - set(SOCI_WERROR_OPTION "/WX") - endif (SOCI_ENABLE_WERROR) -else() - - if (SOCI_ENABLE_WERROR) - set(SOCI_WERROR_OPTION "-Werror") - endif (SOCI_ENABLE_WERROR) - - set(SOCI_GCC_CLANG_COMMON_FLAGS - "-pedantic -Wno-error=parentheses -Wall -Wextra -Wpointer-arith -Wcast-align -Wcast-qual -Wfloat-equal -Woverloaded-virtual -Wredundant-decls -Wno-long-long") - - if(SOCI_UBSAN) - set(SOCI_GCC_CLANG_COMMON_FLAGS "${SOCI_GCC_CLANG_COMMON_FLAGS} -fsanitize=undefined") - endif() - - - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER}" MATCHES "clang") - - if(NOT CMAKE_CXX_COMPILER_VERSION LESS 3.1 AND SOCI_ASAN) - set(SOCI_GCC_CLANG_COMMON_FLAGS "${SOCI_GCC_CLANG_COMMON_FLAGS} -fsanitize=address") - endif() - - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SOCI_GCC_CLANG_COMMON_FLAGS}") - - elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - - if(NOT CMAKE_CXX_COMPILER_VERSION LESS 4.8 AND SOCI_ASAN) - set(SOCI_GCC_CLANG_COMMON_FLAGS "${SOCI_GCC_CLANG_COMMON_FLAGS} -fsanitize=address") - endif() - - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SOCI_GCC_CLANG_COMMON_FLAGS} ") - if (CMAKE_COMPILER_IS_GNUCXX) - if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-variadic-macros") - endif() - endif() - - else() - message(WARNING "Unknown toolset - using default flags to build SOCI") - endif() - -endif() - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SOCI_WERROR_OPTION}") diff --git a/cmake/SociDependencies.cmake b/cmake/SociDependencies.cmake deleted file mode 100644 index 5f174a51f..000000000 --- a/cmake/SociDependencies.cmake +++ /dev/null @@ -1,101 +0,0 @@ -################################################################################ -# SociDependencies.cmake - part of CMake configuration of SOCI library -# -# Based on BoostExternals.cmake from CMake configuration for Boost -################################################################################ -# Copyright (C) 2010 Mateusz Loskot -# Copyright (C) 2009 Troy Straszheim -# -# 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 a database backend for SOCI library -# -################################################################################ - -# -# List of SOCI dependncies -# -set(SOCI_CORE_DEPENDENCIES - Threads - Boost) - -set(SOCI_BACKENDS_DB_DEPENDENCIES - MySQL - ODBC - Oracle - PostgreSQL - SQLite3 - Firebird - DB2) - -set(SOCI_ALL_DEPENDENCIES - ${SOCI_CORE_DEPENDENCIES} - ${SOCI_BACKENDS_DB_DEPENDENCIES}) - -# -# Perform checks -# -colormsg(_HIBLUE_ "Looking for SOCI dependencies:") - -macro(boost_external_report NAME) - - set(VARNAME ${NAME}) - string(TOUPPER ${NAME} VARNAMEU) - - set(VARNAMES ${ARGV}) - list(REMOVE_AT VARNAMES 0) - - # Test both, given original name and uppercase version too - if(NOT ${VARNAME}_FOUND AND NOT ${VARNAMEU}_FOUND) - colormsg(_RED_ "WARNING: ${NAME} libraries not found, some features will be disabled.") - endif() - - foreach(variable ${VARNAMES}) - if(${VARNAMEU}_FOUND) - boost_report_value(${VARNAMEU}_${variable}) - elseif(${VARNAME}_FOUND) - boost_report_value(${VARNAME}_${variable}) - endif() - endforeach() -endmacro() - -# -# Some externals default to OFF -# -option(WITH_VALGRIND "Run tests under valgrind" OFF) - -# -# Detect available dependencies -# -foreach(external ${SOCI_ALL_DEPENDENCIES}) - string(TOUPPER "${external}" EXTERNAL) - - # For historical reasons we use both WITH_xxx and SOCI_xxx options, and - # setting either of them to e.g. OFF should disable the corresponding - # dependency. - option(WITH_${EXTERNAL} "Attempt to find and configure ${external}" ON) - option(SOCI_${EXTERNAL} "Attempt to build ${external} backend" ON) - - if(NOT WITH_${EXTERNAL}) - set(disabled_var "WITH") - elseif(NOT SOCI_${EXTERNAL}) - set(disabled_var "SOCI") - endif() - - if(NOT DEFINED disabled_var) - colormsg(HICYAN "${external}:") - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/dependencies/${external}.cmake) - else() - set(${EXTERNAL}_FOUND FALSE CACHE BOOL "${external} found" FORCE) - colormsg(HICYAN "${external}:" YELLOW "disabled, since ${disabled_var}_${EXTERNAL}=OFF") - unset(disabled_var) - endif() - - if(NOT ${EXTERNAL}_FOUND) - set(SOCI_${EXTERNAL} OFF) - endif() -endforeach() diff --git a/cmake/SociUtilities.cmake b/cmake/SociUtilities.cmake deleted file mode 100644 index 627ecfb1f..000000000 --- a/cmake/SociUtilities.cmake +++ /dev/null @@ -1,436 +0,0 @@ -################################################################################ -# SociUtilities.cmake - part of CMake configuration of SOCI library -# -# Based on BoostUtilities.cmake from CMake configuration for Boost -################################################################################ -# Copyright (C) 2007 Douglas Gregor -# Copyright (C) 2007 Troy Straszheim -# 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: -# -# list_contains: Determine whether a string value is in a list. -# -# car: Return the first element in a list -# -# cdr: Return all but the first element in a list -# -# parse_arguments: Parse keyword arguments for use in other macros. -# -# soci_check_package_found: Test varname-FOUND for case-insensitive varname -# -################################################################################ - -# This utility macro determines whether a particular string value -# 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) - set(${var}) - foreach (value2 ${ARGN}) - if (${value} STREQUAL ${value2}) - set(${var} TRUE) - endif (${value} STREQUAL ${value2}) - endforeach (value2) -endmacro(list_contains) - -# This utility macro extracts the first argument from the list of -# arguments given, and places it into the variable named var. -# -# car(var arg1 arg2 ...) -macro(car var) - set(${var} ${ARGV1}) -endmacro(car) - -# This utility macro extracts all of the arguments given except the -# first, and places them into the variable named var. -# -# car(var arg1 arg2 ...) -macro(cdr var junk) - set(${var} ${ARGN}) -endmacro(cdr) - -# The parse_arguments macro will take the arguments of another macro and -# define several variables. The first argument to parse_arguments is a -# prefix to put on all variables it creates. The second argument is a -# 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...) -# -# 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 -# up to the next arg_name or the end of the arguments. All options are -# removed from these lists. parse_arguments also creates a -# prefix_DEFAULT_ARGS variable containing the list of all arguments up -# to the first arg_name encountered. -macro(parse_arguments prefix arg_names option_names) - set(DEFAULT_ARGS) - foreach(arg_name ${arg_names}) - set(${prefix}_${arg_name}) - endforeach(arg_name) - foreach(option ${option_names}) - set(${prefix}_${option} FALSE) - endforeach(option) - - set(current_arg_name DEFAULT_ARGS) - set(current_arg_list) - foreach(arg ${ARGN}) - list_contains(is_arg_name ${arg} ${arg_names}) - if (is_arg_name) - set(${prefix}_${current_arg_name} ${current_arg_list}) - set(current_arg_name ${arg}) - set(current_arg_list) - else (is_arg_name) - list_contains(is_option ${arg} ${option_names}) - if (is_option) - set(${prefix}_${arg} TRUE) - else (is_option) - set(current_arg_list ${current_arg_list} ${arg}) - endif (is_option) - endif (is_arg_name) - endforeach(arg) - set(${prefix}_${current_arg_name} ${current_arg_list}) -endmacro(parse_arguments) - -# 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 -# sorted in reverse topological order. Each element in the list has a -# set of outgoing edges (for example, those other list elements that -# it depends on). In the resulting reverse topological ordering -# (written back into the variable named LIST), an element will come -# later in the list than any of the elements that can be reached by -# following its outgoing edges and the outgoing edges of any vertices -# they target, recursively. Thus, if the edges represent dependencies -# on build targets, for example, the reverse topological ordering is -# the order in which one would build those targets. -# -# For each element E in this list, the edges for E are contained in -# the variable named ${PREFIX}${E}${SUFFIX}, where E is the -# upper-cased version of the element in the list. If no such variable -# exists, then it is assumed that there are no edges. For example, if -# my_list contains a, b, and c, one could provide a dependency graph -# using the following variables: -# -# MY_A_EDGES b -# MY_B_EDGES -# MY_C_EDGES a b -# -# With the involcation of topological_sort shown above and these -# variables, the resulting reverse topological ordering will be b, a, -# c. -function(topological_sort LIST PREFIX SUFFIX) - # Clear the stack and output variable - set(VERTICES "${${LIST}}") - set(STACK) - set(${LIST}) - - # Loop over all of the vertices, starting the topological sort from - # each one. - foreach(VERTEX ${VERTICES}) - string(TOUPPER ${VERTEX} UPPER_VERTEX) - - # If we haven't already processed this vertex, start a depth-first - # search from where. - if (NOT FOUND_${UPPER_VERTEX}) - # Push this vertex onto the stack with all of its outgoing edges - string(REPLACE ";" " " NEW_ELEMENT - "${VERTEX};${${PREFIX}${UPPER_VERTEX}${SUFFIX}}") - list(APPEND STACK ${NEW_ELEMENT}) - - # We've now seen this vertex - set(FOUND_${UPPER_VERTEX} TRUE) - - # While the depth-first search stack is not empty - list(LENGTH STACK STACK_LENGTH) - while(STACK_LENGTH GREATER 0) - # Remove the vertex and its remaining out-edges from the top - # of the stack - list(GET STACK -1 OUT_EDGES) - list(REMOVE_AT STACK -1) - - # Get the source vertex and the list of out-edges - separate_arguments(OUT_EDGES) - list(GET OUT_EDGES 0 SOURCE) - list(REMOVE_AT OUT_EDGES 0) - - # While there are still out-edges remaining - list(LENGTH OUT_EDGES OUT_DEGREE) - while (OUT_DEGREE GREATER 0) - # Pull off the first outgoing edge - list(GET OUT_EDGES 0 TARGET) - list(REMOVE_AT OUT_EDGES 0) - - string(TOUPPER ${TARGET} UPPER_TARGET) - if (NOT FOUND_${UPPER_TARGET}) - # We have not seen the target before, so we will traverse - # its outgoing edges before coming back to our - # source. This is the key to the depth-first traversal. - - # We've now seen this vertex - set(FOUND_${UPPER_TARGET} TRUE) - - # Push the remaining edges for the current vertex onto the - # stack - 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 - ${${PREFIX}${UPPER_SOURCE}${SUFFIX}}) - endif(NOT FOUND_${UPPER_TARGET}) - - list(LENGTH OUT_EDGES OUT_DEGREE) - endwhile (OUT_DEGREE GREATER 0) - - # We have finished all of the outgoing edges for - # SOURCE; add it to the resulting list. - list(APPEND ${LIST} ${SOURCE}) - - # Check the length of the stack - list(LENGTH STACK STACK_LENGTH) - endwhile(STACK_LENGTH GREATER 0) - endif (NOT FOUND_${UPPER_VERTEX}) - endforeach(VERTEX) - - set(${LIST} ${${LIST}} PARENT_SCOPE) -endfunction(topological_sort) - -# Small little hack that tweaks a component name (as used for CPack) -# to make sure to avoid certain names that cause problems. Sets the -# variable named varname to the "sanitized" name. -# -# FIXME: This is a complete hack. We probably need to fix the CPack -# generators (NSIS in particular) to get rid of the need for this. -macro(fix_cpack_component_name varname name) - if (${name} STREQUAL "foreach") - set(${varname} "boost_foreach") - else() - set(${varname} ${name}) - endif() -endmacro() - - -# -# A big shout out to the cmake gurus @ compiz -# -function (colormsg) - string (ASCII 27 _escape) - set(WHITE "29") - set(GRAY "30") - set(RED "31") - set(GREEN "32") - set(YELLOW "33") - set(BLUE "34") - set(MAG "35") - set(CYAN "36") - - foreach (color WHITE GRAY RED GREEN YELLOW BLUE MAG CYAN) - set(HI${color} "1\;${${color}}") - set(LO${color} "2\;${${color}}") - set(_${color}_ "4\;${${color}}") - set(_HI${color}_ "1\;4\;${${color}}") - set(_LO${color}_ "2\;4\;${${color}}") - endforeach() - - set(str "") - set(coloron FALSE) - foreach(arg ${ARGV}) - if (NOT ${${arg}} STREQUAL "") - if (CMAKE_COLOR_DIAGNOSTICS) - set(str "${str}${_escape}[${${arg}}m") - set(coloron TRUE) - endif() - else() - set(str "${str}${arg}") - if (coloron) - set(str "${str}${_escape}[0m") - set(coloron FALSE) - endif() - set(str "${str} ") - endif() - endforeach() - 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" -# "right?") - -# -# pretty-prints the value of a variable so that the -# equals signs align -# -function(boost_report_value NAME) - string(LENGTH "${NAME}" varlen) - # LOG - #message(STATUS "boost_report_value: NAME=${NAME} (${varlen})") - #message(STATUS "boost_report_value: \${NAME}=${${NAME}}") - math(EXPR padding_len 40-${varlen}) - string(SUBSTRING " " - 0 ${padding_len} varpadding) - colormsg("${NAME}${varpadding} = ${${NAME}}") -endfunction() - -function(boost_message_value NAME) - string(LENGTH "${NAME}" varlen) - math(EXPR padding_len 40-${varlen}) - string(SUBSTRING " " - 0 ${padding_len} varpadding) - message(STATUS "${NAME}${varpadding} = ${${NAME}}") -endfunction() - -function(trace NAME) - if(BOOST_CMAKE_TRACE) - string(LENGTH "${NAME}" varlen) - math(EXPR padding_len 40-${varlen}) - string(SUBSTRING "........................................" - 0 ${padding_len} varpadding) - message("${NAME} ${varpadding} ${${NAME}}") - endif() -endfunction() - -# -# 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 " " - 0 ${padding_len} varpadding) - message(STATUS "${PRETTYNAME}${varpadding} = ${${VARNAME}}") -endfunction() - -# -# assert that ARG is actually a library target -# -macro(dependency_check ARG) - trace(ARG) - if (NOT "${ARG}" STREQUAL "") - get_target_property(deptype ${ARG} TYPE) - if(NOT deptype MATCHES ".*_LIBRARY$") - set(DEPENDENCY_OKAY FALSE) - list(APPEND DEPENDENCY_FAILURES ${ARG}) - endif() - endif() -endmacro() - -# -# Tests package-FOUND for varname in three cases as given, lowercase and -# uppercase. -# -macro(soci_check_package_found NAME SUCCESS) - - set(${SUCCESS} FALSE) - set(VARNAME ${NAME}) - set(VARNAME_SUCCESS ${${VARNAME}_FOUND}) - - # Test both, given original name and uppercase version too - if(VARNAME_SUCCESS) - set(${SUCCESS} TRUE) - else() - string(TOUPPER ${NAME} VARNAME) - set(VARNAME_SUCCESS ${${VARNAME}_FOUND}) - if(VARNAME_SUCCESS) - set(${SUCCESS} TRUE) - endif() - endif() - - #message(STATUS "soci_check_package_found: ${SUCCESS}=${${SUCCESS}}") -endmacro() - -# -# Pretty-print of given property of current directory. -# -function(soci_report_directory_property PROPNAME) - get_directory_property(${PROPNAME} ${PROPNAME}) - boost_report_value(${PROPNAME}) -endfunction() - -# -# Scans the current directory and returns a list of subdirectories. -# Author: Robert Fleming -# Source: http://www.cmake.org/pipermail/cmake/2008-February/020114.html -# -# Third parameter is 1 if you want relative paths returned. -# Usage: list_subdirectories(the_list_is_returned_here /path/to/project TRUE) -# -macro(list_subdirectories retval curdir return_relative) - file(GLOB sub-dir RELATIVE ${curdir} *) - set(list_of_dirs "") - foreach(dir ${sub-dir}) - if(IS_DIRECTORY ${curdir}/${dir}) - if (${return_relative}) - set(list_of_dirs ${list_of_dirs} ${dir}) - else() - set(list_of_dirs ${list_of_dirs} ${curdir}/${dir}) - endif() - endif() - endforeach() - set(${retval} ${list_of_dirs}) -endmacro() - -# -# Generates output name for given target depending on platform and version. -# For instance, on Windows, libraries get ABI version suffix soci_coreXY.{dll|lib}. -# -function(soci_target_output_name TARGET_NAME OUTPUT_NAME) - if(NOT DEFINED TARGET_NAME) - message(SEND_ERROR "Error, the variable TARGET_NAME is not defined!") - endif() - - if(NOT DEFINED ${PROJECT_NAME}_VERSION) - message(SEND_ERROR "Error, the variable ${${PROJECT_NAME}_VERSION} is not defined!") - endif() - - # On Windows, ABI version is specified using binary file name suffix. - # On Unix, suffix is empty and SOVERSION is used instead. - if (WIN32) - string(LENGTH "${${PROJECT_NAME}_ABI_VERSION}" abilen) - if(abilen GREATER 0) - set(SUFFIX "_${${PROJECT_NAME}_ABI_VERSION}") - endif() - endif() - - set(${OUTPUT_NAME} ${TARGET_NAME}${SUFFIX} PARENT_SCOPE) -endfunction() - -# Check if the given linker is supported and use it if it is. -function(soci_use_ld_if_supported ld) - include(CheckCXXCompilerFlag) - set(ld_flag "-fuse-ld=${ld}") - check_cxx_compiler_flag(${ld_flag} can_use_ld) - if (can_use_ld) - add_link_options(${ld_flag}) - endif() -endfunction() diff --git a/cmake/SociVersion.cmake b/cmake/SociVersion.cmake deleted file mode 100644 index 5c8b5d75a..000000000 --- a/cmake/SociVersion.cmake +++ /dev/null @@ -1,62 +0,0 @@ -################################################################################ -# SociVersion.cmake - part of CMake configuration of 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) -################################################################################ -# Macros in this module: -# -# soci_version - defines version information for SOCI library -# -################################################################################ - -# Defines version information for SOCI library -# -# soci_version(MAJOR major_version MINOR minor_version PATCH patch_level) -# -# MAJOR.MINOR version is used to set SOVERSION -# -macro(soci_version) - # get version from soci/version.h - file( - STRINGS - "${PROJECT_SOURCE_DIR}/include/soci/version.h" - _VERSION - REGEX - "#define SOCI_VERSION ([0-9]+)" - ) - string(REGEX MATCH "([0-9]+)" _VERSION "${_VERSION}") - - math(EXPR ${PROJECT_NAME}_VERSION_MAJOR "${_VERSION} / 100000") - math(EXPR ${PROJECT_NAME}_VERSION_MINOR "${_VERSION} / 100 % 1000") - math(EXPR ${PROJECT_NAME}_VERSION_PATCH "${_VERSION} % 100") - - # Set VERSION string - set(${PROJECT_NAME}_VERSION - "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}") - - # Set SOVERSION based on major and minor - set(${PROJECT_NAME}_SOVERSION - "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}") - - # Set ABI version string used to name binary output and, by SOCI loader, to find binaries. - # On Windows, ABI version is specified using binary file name suffix. - # On Unix, suffix ix empty and SOVERSION is used instead. - if (UNIX) - set(${PROJECT_NAME}_ABI_VERSION ${${PROJECT_NAME}_SOVERSION}) - elseif(WIN32) - set(${PROJECT_NAME}_ABI_VERSION - "${${PROJECT_NAME}_VERSION_MAJOR}_${${PROJECT_NAME}_VERSION_MINOR}") - else() - message(FATAL_ERROR "Ambiguous target platform with unknown ABI version scheme. Giving up.") - endif() - - boost_report_value(${PROJECT_NAME}_VERSION) - boost_report_value(${PROJECT_NAME}_ABI_VERSION) - - add_definitions(-DSOCI_ABI_VERSION="${${PROJECT_NAME}_ABI_VERSION}") - -endmacro() diff --git a/cmake/configs/test-access.cmake b/cmake/configs/test-access.cmake deleted file mode 100644 index abc44deb0..000000000 --- a/cmake/configs/test-access.cmake +++ /dev/null @@ -1,13 +0,0 @@ -[ODBC] -DRIVER=Microsoft Access Driver (*.mdb, *.accdb) -UID=admin -UserCommitSync=Yes -Threads=3 -SafeTransactions=0 -PageTimeout=5 -MaxScanRows=8 -MaxBufferSize=2048 -FIL=MS Access -DriverId=25 -DefaultDir=@TEST_ACCESS_PATH@\tests\odbc -DBQ=@TEST_ACCESS_PATH@\tests\odbc\soci_test.mdb diff --git a/cmake/configs/test-mysql.cmake b/cmake/configs/test-mysql.cmake deleted file mode 100644 index 48e54033d..000000000 --- a/cmake/configs/test-mysql.cmake +++ /dev/null @@ -1,4 +0,0 @@ -[ODBC] -DRIVER=@MYSQL_DRIVER_NAME@ -DATABASE=soci_test -OPTION=0 diff --git a/cmake/dependencies/Boost.cmake b/cmake/dependencies/Boost.cmake deleted file mode 100644 index 366d32b74..000000000 --- a/cmake/dependencies/Boost.cmake +++ /dev/null @@ -1,13 +0,0 @@ -set(Boost_FIND_QUIETLY TRUE) - -set(Boost_USE_MULTITHREADED ON) -find_package(Boost 1.33.1 COMPONENTS date_time) - -if (NOT Boost_DATE_TIME_FOUND) - find_package(Boost 1.33.1) -endif() - -set(Boost_RELEASE_VERSION - "${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}") - -boost_external_report(Boost RELEASE_VERSION INCLUDE_DIR LIBRARIES) diff --git a/cmake/dependencies/DB2.cmake b/cmake/dependencies/DB2.cmake deleted file mode 100644 index 063f1a17d..000000000 --- a/cmake/dependencies/DB2.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(DB2_FIND_QUIETLY TRUE) - -find_package(DB2) - -boost_external_report(DB2 INCLUDE_DIR LIBRARIES) diff --git a/cmake/dependencies/Firebird.cmake b/cmake/dependencies/Firebird.cmake deleted file mode 100644 index b072702b0..000000000 --- a/cmake/dependencies/Firebird.cmake +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 5599b0885..000000000 --- a/cmake/dependencies/MySQL.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(MySQL_FIND_QUIETLY TRUE) - -find_package(MySQL) - -boost_external_report(MySQL INCLUDE_DIR LIBRARIES) diff --git a/cmake/dependencies/ODBC.cmake b/cmake/dependencies/ODBC.cmake deleted file mode 100644 index 88b85a1c5..000000000 --- a/cmake/dependencies/ODBC.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(ODBC_FIND_QUIETLY TRUE) - -find_package(ODBC) - -boost_external_report(ODBC INCLUDE_DIR LIBRARIES) diff --git a/cmake/dependencies/Oracle.cmake b/cmake/dependencies/Oracle.cmake deleted file mode 100644 index 32cc649ea..000000000 --- a/cmake/dependencies/Oracle.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(Oracle_FIND_QUIETLY TRUE) - -find_package(Oracle) - -boost_external_report(Oracle INCLUDE_DIR LIBRARIES) diff --git a/cmake/dependencies/PostgreSQL.cmake b/cmake/dependencies/PostgreSQL.cmake deleted file mode 100644 index c6f215425..000000000 --- a/cmake/dependencies/PostgreSQL.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(PostgreSQL_FIND_QUIETLY TRUE) - -find_package(PostgreSQL) - -boost_external_report(PostgreSQL INCLUDE_DIRS LIBRARIES VERSION) diff --git a/cmake/dependencies/SQLite3.cmake b/cmake/dependencies/SQLite3.cmake deleted file mode 100644 index 37f57f614..000000000 --- a/cmake/dependencies/SQLite3.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(SQLite3_FIND_QUIETLY TRUE) - -find_package(SQLite3) - -boost_external_report(SQLite3 INCLUDE_DIR LIBRARIES) diff --git a/cmake/dependencies/Threads.cmake b/cmake/dependencies/Threads.cmake deleted file mode 100644 index 6a5487554..000000000 --- a/cmake/dependencies/Threads.cmake +++ /dev/null @@ -1,4 +0,0 @@ -set(Threads_FIND_QUIETLY TRUE) - -find_package(Threads) -boost_report_value(CMAKE_THREAD_LIBS_INIT) diff --git a/cmake/modules/FindDB2.cmake b/cmake/find_modules/FindDB2.cmake similarity index 82% rename from cmake/modules/FindDB2.cmake rename to cmake/find_modules/FindDB2.cmake index a752f1110..b786d4374 100644 --- a/cmake/modules/FindDB2.cmake +++ b/cmake/find_modules/FindDB2.cmake @@ -3,9 +3,8 @@ # # On success, the macro sets the following variables: # DB2_FOUND = if the library found -# DB2_LIBRARY = full path to the library # DB2_LIBRARIES = full path to the library -# DB2_INCLUDE_DIR = where to find the library headers +# DB2_INCLUDE_DIRS = where to find the library headers # # Copyright (c) 2013 Denis Chapligin # @@ -70,14 +69,15 @@ elseif(WIN32) endif() endif() -find_path(DB2_INCLUDE_DIR sqlcli1.h +find_path(DB2_INCLUDE_DIRS sqlcli1.h $ENV{DB2_INCLUDE_DIR} + $ENV{DB2_INCLUDE_DIRS} $ENV{DB2_DIR}/include $ENV{DB2_HOME} $ENV{IBM_DB_INCLUDE} ${DB2_FIND_INCLUDE_PATHS}) -find_library(DB2_LIBRARY +find_library(DB2_LIBRARIES NAMES db2 db2api PATHS $ENV{DB2LIB} @@ -85,22 +85,16 @@ find_library(DB2_LIBRARY ${DB2_FIND_LIB_PATHS} ${DB2_FIND_LIB_NO_LIB}) -if(DB2_LIBRARY) - get_filename_component(DB2_LIBRARY_DIR ${DB2_LIBRARY} PATH) +if(DB2_LIBRARIES) + get_filename_component(DB2_LIBRARY_DIR ${DB2_LIBRARIES} PATH) endif() -if(DB2_INCLUDE_DIR AND DB2_LIBRARY_DIR) - set(DB2_FOUND TRUE) -endif() - -set(DB2_LIBRARIES ${DB2_LIBRARY}) - -# Handle the QUIETLY and REQUIRED arguments and set DB2_FOUND to TRUE -# if all listed variables are TRUE include(FindPackageHandleStandardArgs) find_package_handle_standard_args(DB2 - DEFAULT_MSG - DB2_INCLUDE_DIR - DB2_LIBRARIES) + REQUIRED_VARS DB2_INCLUDE_DIRS DB2_LIBRARIES +) -mark_as_advanced(DB2_INCLUDE_DIR DB2_LIBRARIES) +add_library(DB2 INTERFACE) +target_link_libraries(DB2 INTERFACE ${DB2_LIBRARIES}) +target_include_directories(DB2 SYSTEM INTERFACE ${DB2_INCLUDE_DIRS}) +add_library(DB2::DB2 ALIAS DB2) diff --git a/cmake/find_modules/FindFirebird.cmake b/cmake/find_modules/FindFirebird.cmake new file mode 100644 index 000000000..9a55a5742 --- /dev/null +++ b/cmake/find_modules/FindFirebird.cmake @@ -0,0 +1,39 @@ +############################################################## +# Copyright (c) 2008 Daniel Pfeifer # +# # +# Distributed under the Boost Software License, Version 1.0. # +############################################################## + +# This module defines +# Firebird_INCLUDE_DIRS - where to find ibase.h +# Firebird_LIBRARIES - the libraries to link against to use Firebird +# Firebird_FOUND - true if Firebird was found + +find_path(Firebird_INCLUDE_DIRS ibase.h + /usr/include + $ENV{ProgramFiles}/Firebird/*/include +) + +if (Firebird_SEARCH_EMBEDDED) + set(Firebird_LIB_NAMES fbembed) +else() + set(Firebird_LIB_NAMES fbclient fbclient_ms) +endif() + +find_library(Firebird_LIBRARIES + NAMES + ${Firebird_LIB_NAMES} + PATHS + /usr/lib + $ENV{ProgramFiles}/Firebird/*/lib +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Firebird + REQUIRED_VARS Firebird_LIBRARIES Firebird_INCLUDE_DIRS +) + +add_library(Firebird INTERFACE) +target_link_libraries(Firebird INTERFACE ${Firebird_LIBRARIES}) +target_include_directories(Firebird SYSTEM INTERFACE ${Firebird_INCLUDE_DIRS}) +add_library(Firebird::Firebird ALIAS Firebird) diff --git a/cmake/find_modules/FindMySQL.cmake b/cmake/find_modules/FindMySQL.cmake new file mode 100644 index 000000000..bb0c53dbc --- /dev/null +++ b/cmake/find_modules/FindMySQL.cmake @@ -0,0 +1,181 @@ +# - Try to find MariaDB / MySQL library +# Find the MySQL includes and client library +# This module defines +# MySQL_FOUND +# An interface target MySQL::MySQL to be used in a target_link_libraries call + + +if (DEFINED VCPKG_TARGET_TRIPLET) + # In vcpg the MySQL packages are called unofficial-libmysql + find_package(unofficial-libmysql) + + set(FOUND_VAR "unofficial-libmysql_FOUND") + set(LIBRARY_TARGET "unofficial::libmysql::libmysql") + + if (NOT ${FOUND_VAR}) + find_package(unofficial-libmariadb QUIET) + set(FOUND_VAR "unofficial-libmariadb_FOUND") + set(LIBRARY_TARGET "unofficial::libmariadb::libmariadb") + endif() + + if (${FOUND_VAR}) + set(MySQL_FOUND TRUE) + message(STATUS "Found MySQL via vcpkg installation") + add_library(MySQL::MySQL ALIAS ${LIBRARY_TARGET}) + return() + endif() +endif() + +find_package(PkgConfig QUIET) + +if (PKG_CONFIG_FOUND) + # Try via PkgConfig + pkg_check_modules(MYSQLCLIENT QUIET mysqlclient) + + if (MYSQLCLIENT_FOUND) + if (NOT BUILD_SHARED_LIBS AND MYSQLCLIENT_STATIC_FOUND) + set(PREFIX MYSQLCLIENT_STATIC) + else() + set(PREFIX MYSQLCLIENT) + endif() + + set(MySQL_LIBRARIES ${${PREFIX}_LINK_LIBRARIES}) + set(MySQL_LDFLAGS ${${PREFIX}_LDFLAGS} ${${PREFIX}_LDFLAGS_OTHER}) + set(MySQL_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${PREFIX}_INCLUDE_DIRS_OTHER}) + set(MySQL_CFLAGS ${${PREFIX}_CFLAGS} ${${PREFIX}_CFLAGS_OTHER}) + set(MySQL_VERSION ${MYSQLCLIENT_VERSION}) + endif() +endif() + +if (NOT MySQL_LIBRARIES) + # Try using config exe + find_program(CONFIG_EXE + NAMES + mysql_config mariadb_config + PATHS + $ENV{MYSQL_DIR} + $ENV{MYSQL_DIRS} + $ENV{ProgramFiles}/MySQL/ + $ENV{ProgramFiles}/MariaDB/ + ) + + if (CONFIG_EXE) + execute_process(COMMAND ${CONFIG_EXE} --include OUTPUT_VARIABLE MySQL_INCLUDE_DIRS OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${CONFIG_EXE} --libs OUTPUT_VARIABLE MySQL_LIBRARIES OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${CONFIG_EXE} --version OUTPUT_VARIABLE MySQL_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() +endif() + +if (NOT MySQL_LIBRARIES) + message(WARNING "Falling back to manual MySQL search -> this might miss dependencies") + set(MySQL_COMPILE_DEFINITIONS "") + + include(CheckCXXSourceCompiles) + + foreach(TOP_LEVEL_DIR IN ITEMS "mysql/" "") + if(WIN32) + find_path(MySQL_INCLUDE_DIRS ${TOP_LEVEL_DIR}mysql.h + PATHS + $ENV{MYSQL_INCLUDE_DIR} + $ENV{MYSQL_INCLUDE_DIRS} + $ENV{MYSQL_DIR}/include + $ENV{MYSQL_DIRS}/include + $ENV{ProgramFiles}/MySQL/*/include + $ENV{SystemDrive}/MySQL/*/include + $ENV{ProgramW6432}/MySQL/*/include + ) + else() + find_path(MySQL_INCLUDE_DIRS ${TOP_LEVEL_DIR}mysql.h + PATHS + $ENV{MYSQL_INCLUDE_DIR} + $ENV{MYSQL_INCLUDE_DIRS} + $ENV{MYSQL_DIR}/include + $ENV{MYSQL_DIRS}/include + PATH_SUFFIXES + mariadb + mysql + ) + endif() + + if (MySQL_INCLUDE_DIRS) + if (TOP_LEVEL_DIR STREQUAL "") + list(APPEND MySQL_COMPILE_DEFINITIONS SOCI_MYSQL_DIRECT_INCLUDE) + set(VERSION_FILE "${MySQL_INCLUDE_DIRS}/mysql_version.h") + else() + set(VERSION_FILE "${MySQL_INCLUDE_DIRS}/mysql/mysql_version.h") + endif() + + # Parse out MySQL version + file(READ "${VERSION_FILE}" VERSION_CONTENT) + string(REGEX MATCH "#define[ \t]+LIBMYSQL_VERSION[ \t]+\"([^\"]+)\"" VERSION_CONTENT "${VERSION_CONTENT}") + set(MySQL_VERSION "${CMAKE_MATCH_1}") + + break() + endif() + endforeach() + + if (MySQL_INCLUDE_DIRS) + endif() + + if(WIN32) + if (${CMAKE_BUILD_TYPE}) + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) + endif() + + # path suffix for debug/release mode + # binary_dist: mysql binary distribution + # build_dist: custom build + if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") + set(binary_dist debug) + set(build_dist Debug) + else() + list(APPEND MySQL_COMPILE_DEFINITIONS DBUG_OFF) + set(binary_dist opt) + set(build_dist Release) + endif() + + 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 libmysql + PATHS + ${MySQL_LIB_PATHS} + ) + else() + set(MySQL_LIB_PATHS + $ENV{MySQL_DIR}/lib + PATH_SUFFIXES + mariadb + mysql + ) + find_library(MySQL_LIBRARIES NAMES mariadbclient mysqlclient + PATHS + ${MySQL_LIB_PATHS} + ) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MySQL + REQUIRED_VARS MySQL_LIBRARIES MySQL_INCLUDE_DIRS + VERSION_VAR MySQL_VERSION +) + +if (MySQL_FOUND) + add_library(MySQL INTERFACE) + target_link_libraries(MySQL INTERFACE ${MySQL_LIBRARIES}) + target_include_directories(MySQL SYSTEM INTERFACE ${MySQL_INCLUDE_DIRS}) + target_compile_options(MySQL INTERFACE ${MySQL_CFLAGS}) + target_link_options(MySQL INTERFACE ${MySQL_LDFLAGS}) + target_compile_definitions(MySQL INTERFACE ${MySQL_COMPILE_DEFINITIONS}) + add_library(MySQL::MySQL ALIAS MySQL) +endif() diff --git a/cmake/find_modules/FindOracle.cmake b/cmake/find_modules/FindOracle.cmake new file mode 100644 index 000000000..64ced2106 --- /dev/null +++ b/cmake/find_modules/FindOracle.cmake @@ -0,0 +1,96 @@ +############################################################################### +# +# 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 AND NOT Oracle_HOME) + if(EXISTS $ENV{ORACLE_HOME}) + set(Oracle_HOME $ENV{ORACLE_HOME}) + endif() + if(EXISTS $ENV{Oracle_HOME}) + set(Oracle_HOME $ENV{Oracle_HOME}) + endif() +endif() +if (ORACLE_HOME) + set(Oracle_HOME "${ORACLE_HOME}") +endif() + +find_path(Oracle_INCLUDE_DIRS + NAMES oci.h + PATHS + ${Oracle_HOME}/rdbms/public + ${Oracle_HOME}/include + ${Oracle_HOME}/sdk/include # Oracle SDK + ${Oracle_HOME}/OCI/include # Oracle XE on Windows + # instant client from rpm + /usr/include/oracle/*/client${LIB_SUFFIX}) + +set(Oracle_VERSIONS 21 20 19 18 12 11 10) +set(Oracle_OCI_NAMES clntsh libclntsh oci) # Dirty trick might help on OSX, see issues/89 +set(Oracle_OCCI_NAMES libocci occi) +set(Oracle_NNZ_NAMES ociw32) + +foreach(loop_var IN LISTS Oracle_VERSIONS) + set(Oracle_OCCI_NAMES ${Oracle_OCCI_NAMES} oraocci${loop_var}) + set(Oracle_NNZ_NAMES ${Oracle_NNZ_NAMES} nnz${loop_var} libnnz${loop_var}) +endforeach(loop_var) + +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 + # Instant client from rpm + /usr/lib/oracle/*/client${LIB_SUFFIX}/lib) + +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}) + +if (Oracle_OCI_LIBRARY AND Oracle_OCCI_LIBRARY AND Oracle_NNZ_LIBRARY) + set(Oracle_LIBRARIES + ${Oracle_OCI_LIBRARY} + ${Oracle_OCCI_LIBRARY} + ${Oracle_NNZ_LIBRARY}) +endif() + +if(NOT WIN32 AND Oracle_CLNTSH_LIBRARY) + list(APPEND Oracle_LIBRARIES ${Oracle_CLNTSH_LIBRARY}) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Oracle + REQUIRED_VARS Oracle_LIBRARIES Oracle_INCLUDE_DIRS) + +if(NOT Oracle_FOUND) + message(STATUS "None of the supported Oracle versions (${Oracle_VERSIONS}) could be found, consider updating Oracle_VERSIONS if the version you use is not among them.") +endif() + +add_library(Oracle INTERFACE) +target_link_libraries(Oracle INTERFACE ${Oracle_LIBRARIES}) +target_include_directories(Oracle SYSTEM INTERFACE ${Oracle_INCLUDE_DIRS}) +add_library(Oracle::Oracle ALIAS Oracle) diff --git a/cmake/modules/FindDL.cmake b/cmake/modules/FindDL.cmake deleted file mode 100644 index 3f7f889f3..000000000 --- a/cmake/modules/FindDL.cmake +++ /dev/null @@ -1,21 +0,0 @@ -if(DL_INCLUDE_DIR) - set(DL_FIND_QUIETLY TRUE) -endif() - -find_path(DL_INCLUDE_DIR dlfcn.h) -find_library(DL_LIBRARY NAMES dl) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(DL DEFAULT_MSG DL_LIBRARY DL_INCLUDE_DIR) - -if(NOT DL_FOUND) - # if dlopen can be found without linking in dl then, - # dlopen is part of libc, so don't need to link extra libs. - include(CheckFunctionExists) - check_function_exists(dlopen DL_FOUND) - set(DL_LIBRARY "") -endif() - -set(DL_LIBRARIES ${DL_LIBRARY}) - -mark_as_advanced(DL_LIBRARY DL_INCLUDE_DIR) diff --git a/cmake/modules/FindFirebird.cmake b/cmake/modules/FindFirebird.cmake deleted file mode 100644 index 0e9b6784b..000000000 --- a/cmake/modules/FindFirebird.cmake +++ /dev/null @@ -1,38 +0,0 @@ -############################################################## -# Copyright (c) 2008 Daniel Pfeifer # -# # -# Distributed under the Boost Software License, Version 1.0. # -############################################################## - -# This module defines -# FIREBIRD_INCLUDE_DIR - where to find ibase.h -# FIREBIRD_LIBRARIES - the libraries to link against to use FIREBIRD -# FIREBIRD_FOUND - true if FIREBIRD was found - -find_path(FIREBIRD_INCLUDE_DIR ibase.h - /usr/include - $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 - ${FIREBIRD_LIB_NAMES} - PATHS - /usr/lib - $ENV{ProgramFiles}/Firebird/*/lib -) - -# fbembed ? - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Firebird - DEFAULT_MSG FIREBIRD_LIBRARIES FIREBIRD_INCLUDE_DIR) - -mark_as_advanced(FIREBIRD_INCLUDE_DIR FIREBIRD_LIBRARIES) - diff --git a/cmake/modules/FindMySQL.cmake b/cmake/modules/FindMySQL.cmake deleted file mode 100644 index 73efa8809..000000000 --- a/cmake/modules/FindMySQL.cmake +++ /dev/null @@ -1,99 +0,0 @@ -# - Try to find MariaDB / MySQL library -# Find the MySQL includes and client library -# This module defines -# MYSQL_INCLUDE_DIR, where to find mysql.h -# MYSQL_LIBRARIES, the libraries needed to use MySQL. -# MYSQL_LIB_DIR, path to the MYSQL_LIBRARIES -# MYSQL_FOUND, If false, do not try to use MySQL. - -# Copyright (c) 2006-2008, Jarosław Staniek -# Copyright (c) 2023 Vadim Zeitline (MariaDB support) -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -include(CheckCXXSourceCompiles) - -if(WIN32) - find_path(MYSQL_INCLUDE_DIR mysql.h - PATHS - $ENV{MYSQL_INCLUDE_DIR} - $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 - PATHS - $ENV{MYSQL_INCLUDE_DIR} - $ENV{MYSQL_DIR}/include - PATH_SUFFIXES - mariadb - mysql - ) -endif(WIN32) - -if(WIN32) - if (${CMAKE_BUILD_TYPE}) - string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) - endif() - - # path suffix for debug/release mode - # binary_dist: mysql binary distribution - # build_dist: custom build - if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") - set(binary_dist debug) - set(build_dist Debug) - else(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") - ADD_DEFINITIONS(-DDBUG_OFF) - set(binary_dist opt) - set(build_dist Release) - endif(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") - -# find_library(MYSQL_LIBRARIES NAMES mysqlclient - 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 libmysql - PATHS - ${MYSQL_LIB_PATHS} - ) -else(WIN32) -# find_library(MYSQL_LIBRARIES NAMES mysqlclient - set(MYSQL_LIB_PATHS - $ENV{MYSQL_DIR}/lib - PATH_SUFFIXES - mariadb - mysql - ) - find_library(MYSQL_LIBRARIES NAMES mariadbclient mysqlclient - PATHS - ${MYSQL_LIB_PATHS} - ) -endif(WIN32) - -if(MYSQL_LIBRARIES) - get_filename_component(MYSQL_LIB_DIR ${MYSQL_LIBRARIES} PATH) -endif(MYSQL_LIBRARIES) - -set( CMAKE_REQUIRED_INCLUDES ${MYSQL_INCLUDE_DIR} ) - -if(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) - set(MYSQL_FOUND TRUE) - message(STATUS "Found MySQL: ${MYSQL_INCLUDE_DIR}, ${MYSQL_LIBRARIES}") -else(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) - set(MYSQL_FOUND FALSE) - message(STATUS "MySQL not found.") -endif(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) - -mark_as_advanced(MYSQL_INCLUDE_DIR MYSQL_LIBRARIES) diff --git a/cmake/modules/FindODBC.cmake b/cmake/modules/FindODBC.cmake deleted file mode 100644 index 67e6237c7..000000000 --- a/cmake/modules/FindODBC.cmake +++ /dev/null @@ -1,242 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#[=======================================================================[.rst: -FindODBC --------- - -Find the ODBC include directory and library. - -Use this module by invoking find_package with the form:: - -.. code-block:: cmake - - find_package(ODBC - [REQUIRED] # Fail with error if ODBC is not found - ) - -On Windows, when building with Visual Studio, this module assumes the ODBC -library is provided by the available Windows SDK. - -On Unix, this module allows to search for ODBC library provided by -unixODBC or iODBC implementations of ODBC API. -This module reads hint about location of the config program: - -.. variable:: ODBC_CONFIG - - Location of odbc_config or iodbc-config program - -Otherwise, this module tries to find the config program, -first from unixODBC, then from iODBC. -If no config program found, this module searches for ODBC header -and library in list of known locations. - -Imported targets -^^^^^^^^^^^^^^^^ - -This module defines the following :prop_tgt:`IMPORTED` targets: - -.. variable:: ODBC::ODBC - - Imported target for using the ODBC library, if found. - -Result variables -^^^^^^^^^^^^^^^^ - -.. variable:: ODBC_FOUND - - Set to true if ODBC library found, otherwise false or undefined. - -.. variable:: ODBC_INCLUDE_DIRS - - Paths to include directories listed in one variable for use by ODBC client. - May be empty on Windows, where the include directory corresponding to the - expected Windows SDK is already available in the compilation environment. - -.. variable:: ODBC_LIBRARIES - - Paths to libraries to linked against to use ODBC. - May just a library name on Windows, where the library directory corresponding - to the expected Windows SDK is already available in the compilation environment. - -.. variable:: ODBC_CONFIG - - Path to unixODBC or iODBC config program, if found or specified. - -Cache variables -^^^^^^^^^^^^^^^ - -For users who wish to edit and control the module behavior, this module -reads hints about search locations from the following variables:: - -.. variable:: ODBC_INCLUDE_DIR - - Path to ODBC include directory with ``sql.h`` header. - -.. variable:: ODBC_LIBRARY - - Path to ODBC library to be linked. - -NOTE: The variables above should not usually be used in CMakeLists.txt files! - -Limitations -^^^^^^^^^^^ - -On Windows, this module does not search for iODBC. -On Unix, there is no way to prefer unixODBC over iODBC, or vice versa, -other than providing the config program location using the ``ODBC_CONFIG``. -This module does not allow to search for a specific ODBC driver. - -#]=======================================================================] - -### Try Windows Kits ########################################################## -if(WIN32) - # List names of ODBC libraries on Windows - set(ODBC_LIBRARY odbc32.lib) - set(_odbc_lib_names odbc32;) - - # List additional libraries required to use ODBC library - if(MSVC OR CMAKE_CXX_COMPILER_ID MATCHES "Intel") - set(_odbc_required_libs_names odbccp32;ws2_32) - elseif(MINGW) - set(_odbc_required_libs_names odbccp32) - endif() -endif() - -### Try unixODBC or iODBC config program ###################################### -if (UNIX AND NOT ODBC_CONFIG) - find_program(ODBC_CONFIG - NAMES odbc_config iodbc-config - DOC "Path to unixODBC or iODBC config program") -endif() - -if (UNIX AND ODBC_CONFIG) - # unixODBC and iODBC accept unified command line options - execute_process(COMMAND ${ODBC_CONFIG} --cflags - OUTPUT_VARIABLE _cflags OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${ODBC_CONFIG} --libs - OUTPUT_VARIABLE _libs OUTPUT_STRIP_TRAILING_WHITESPACE) - - # Collect paths of include directories from CFLAGS - separate_arguments(_cflags NATIVE_COMMAND "${_cflags}") - foreach(arg IN LISTS _cflags) - if("${arg}" MATCHES "^-I(.*)$") - list(APPEND _odbc_include_paths "${CMAKE_MATCH_1}") - endif() - endforeach() - unset(_cflags) - - # Collect paths of library names and directories from LIBS - separate_arguments(_libs NATIVE_COMMAND "${_libs}") - foreach(arg IN LISTS _libs) - if("${arg}" MATCHES "^-L(.*)$") - list(APPEND _odbc_lib_paths "${CMAKE_MATCH_1}") - elseif("${arg}" MATCHES "^-l(.*)$") - set(_lib_name ${CMAKE_MATCH_1}) - string(REGEX MATCH "odbc" _is_odbc ${_lib_name}) - if(_is_odbc) - list(APPEND _odbc_lib_names ${_lib_name}) - else() - list(APPEND _odbc_required_libs_names ${_lib_name}) - endif() - unset(_lib_name) - endif() - endforeach() - unset(_libs) -endif() - -### Try unixODBC or iODBC in include/lib filesystems ########################## -if (UNIX AND NOT ODBC_CONFIG) - # List names of both ODBC libraries, unixODBC and iODBC - set(_odbc_lib_names odbc;iodbc;unixodbc;) - - set(_odbc_include_paths - /usr/local/odbc/include) - - set(_odbc_lib_paths - /usr/local/odbc/lib) -endif() - -# DEBUG -#message("ODBC_CONFIG=${ODBC_CONFIG}") -#message("_odbc_include_hints=${_odbc_include_hints}") -#message("_odbc_include_paths=${_odbc_include_paths}") -#message("_odbc_lib_paths=${_odbc_lib_paths}") -#message("_odbc_lib_names=${_odbc_lib_names}") - -### Find include directories ################################################## -find_path(ODBC_INCLUDE_DIR - NAMES sql.h - HINTS ${_odbc_include_hints} - PATHS ${_odbc_include_paths}) - -if(NOT ODBC_INCLUDE_DIR AND WIN32) - set(ODBC_INCLUDE_DIR "") -endif() - -### Find libraries ############################################################ -if(NOT ODBC_LIBRARY) - find_library(ODBC_LIBRARY - NAMES ${_odbc_lib_names} - PATHS ${_odbc_lib_paths} - PATH_SUFFIXES odbc) - - foreach(_lib IN LISTS _odbc_required_libs_names) - find_library(_lib_path - NAMES ${_lib} - PATHS ${_odbc_lib_paths} # system parths or collected from ODBC_CONFIG - PATH_SUFFIXES odbc) - if (_lib_path) - list(APPEND _odbc_required_libs_paths ${_lib_path}) - endif() - unset(_lib_path CACHE) - endforeach() - - unset(_odbc_lib_names) - unset(_odbc_lib_paths) - unset(_odbc_required_libs_names) -endif() - -### Set result variables ###################################################### -set(REQUIRED_VARS ODBC_LIBRARY) -if(NOT WIN32) - list(APPEND REQUIRED_VARS ODBC_INCLUDE_DIR) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ODBC DEFAULT_MSG ${REQUIRED_VARS}) - -mark_as_advanced(FORCE ODBC_LIBRARY ODBC_INCLUDE_DIR) - -if(ODBC_CONFIG) - mark_as_advanced(FORCE ODBC_CONFIG) -endif() - -set(ODBC_INCLUDE_DIRS ${ODBC_INCLUDE_DIR}) -list(APPEND ODBC_LIBRARIES ${ODBC_LIBRARY}) -list(APPEND ODBC_LIBRARIES ${_odbc_required_libs_paths}) - -### Import targets ############################################################ -if(ODBC_FOUND) - if(NOT TARGET ODBC::ODBC) - if(IS_ABSOLUTE "${ODBC_LIBRARY}") - add_library(ODBC::ODBC UNKNOWN IMPORTED) - set_target_properties(ODBC::ODBC PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${ODBC_LIBRARY}") - else() - add_library(ODBC::ODBC INTERFACE IMPORTED) - set_target_properties(ODBC::ODBC PROPERTIES - IMPORTED_LIBNAME "${ODBC_LIBRARY}") - endif() - set_target_properties(ODBC::ODBC PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${ODBC_INCLUDE_DIR}") - - if(_odbc_required_libs_paths) - set_property(TARGET ODBC::ODBC APPEND PROPERTY - INTERFACE_LINK_LIBRARIES "${_odbc_required_libs_paths}") - endif() - endif() -endif() - -unset(_odbc_required_libs_paths) diff --git a/cmake/modules/FindOracle.cmake b/cmake/modules/FindOracle.cmake deleted file mode 100644 index 37106f761..000000000 --- a/cmake/modules/FindOracle.cmake +++ /dev/null @@ -1,90 +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. -# -############################################################################### - -# 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() -endif() - -message(STATUS "ORACLE_HOME=${ORACLE_HOME}") - -find_path(ORACLE_INCLUDE_DIR - NAMES oci.h - PATHS - ${ORACLE_HOME}/rdbms/public - ${ORACLE_HOME}/include - ${ORACLE_HOME}/sdk/include # Oracle SDK - ${ORACLE_HOME}/OCI/include # Oracle XE on Windows - # instant client from rpm - /usr/include/oracle/*/client${LIB_SUFFIX}) - -set(ORACLE_VERSIONS 21 20 19 18 12 11 10) -set(ORACLE_OCI_NAMES clntsh libclntsh oci) # Dirty trick might help on OSX, see issues/89 -set(ORACLE_OCCI_NAMES libocci occi) -set(ORACLE_NNZ_NAMES ociw32) -foreach(loop_var IN LISTS ORACLE_VERSIONS) - set(ORACLE_OCCI_NAMES ${ORACLE_OCCI_NAMES} oraocci${loop_var}) - set(ORACLE_NNZ_NAMES ${ORACLE_NNZ_NAMES} nnz${loop_var} libnnz${loop_var}) -endforeach(loop_var) - -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 - # Instant client from rpm - /usr/lib/oracle/*/client${LIB_SUFFIX}/lib) - -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) - -if(NOT ORACLE_FOUND) - message(STATUS "None of the supported Oracle versions (${ORACLE_VERSIONS}) could be found, consider updating ORACLE_VERSIONS if the version you use is not among them.") -endif() - -mark_as_advanced(ORACLE_INCLUDE_DIR ORACLE_LIBRARY) diff --git a/cmake/modules/FindPostgreSQL.cmake b/cmake/modules/FindPostgreSQL.cmake deleted file mode 100644 index e795f2e7a..000000000 --- a/cmake/modules/FindPostgreSQL.cmake +++ /dev/null @@ -1,191 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#.rst: -# FindPostgreSQL -# -------------- -# -# Find the PostgreSQL installation. -# -# This module defines -# -# :: -# -# POSTGRESQL_LIBRARIES - the PostgreSQL libraries needed for linking -# POSTGRESQL_INCLUDE_DIRS - the directories of the PostgreSQL headers -# POSTGRESQL_LIBRARY_DIRS - the link directories for PostgreSQL libraries -# POSTGRESQL_VERSION_STRING - the version of PostgreSQL found (since CMake 2.8.8) - -# ---------------------------------------------------------------------------- -# History: -# This module is derived from the module originally found in the VTK source tree. -# -# ---------------------------------------------------------------------------- -# Note: -# POSTGRESQL_ADDITIONAL_VERSIONS is a variable that can be used to set the -# version mumber of the implementation of PostgreSQL. -# In Windows the default installation of PostgreSQL uses that as part of the path. -# E.g C:\Program Files\PostgreSQL\8.4. -# Currently, the following version numbers are known to this module: -# "14" "13" "12" "11" "10" "9.6" "9.5" "9.4" "9.3" "9.2" "9.1" "9.0" "8.4" "8.3" "8.2" "8.1" "8.0" -# -# To use this variable just do something like this: -# set(POSTGRESQL_ADDITIONAL_VERSIONS "9.2" "8.4.4") -# before calling find_package(PostgreSQL) in your CMakeLists.txt file. -# This will mean that the versions you set here will be found first in the order -# specified before the default ones are searched. -# -# ---------------------------------------------------------------------------- -# You may need to manually set: -# POSTGRESQL_INCLUDE_DIR - the path to where the PostgreSQL include files are. -# POSTGRESQL_LIBRARY_DIR - The path to where the PostgreSQL library files are. -# If FindPostgreSQL.cmake cannot find the include files or the library files. -# -# ---------------------------------------------------------------------------- -# The following variables are set if PostgreSQL is found: -# POSTGRESQL_FOUND - Set to true when PostgreSQL is found. -# POSTGRESQL_INCLUDE_DIRS - Include directories for PostgreSQL -# POSTGRESQL_LIBRARY_DIRS - Link directories for PostgreSQL libraries -# POSTGRESQL_LIBRARIES - The PostgreSQL libraries. -# -# ---------------------------------------------------------------------------- -# If you have installed PostgreSQL in a non-standard location. -# (Please note that in the following comments, it is assumed that -# points to the root directory of the include directory of PostgreSQL.) -# Then you have three options. -# 1) After CMake runs, set POSTGRESQL_INCLUDE_DIR to /include and -# POSTGRESQL_LIBRARY_DIR to wherever the library pq (or libpq in windows) is -# 2) Use CMAKE_INCLUDE_PATH to set a path to /PostgreSQL<-version>. This will allow find_path() -# to locate POSTGRESQL_INCLUDE_DIR by utilizing the PATH_SUFFIXES option. e.g. In your CMakeLists.txt file -# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "/include") -# 3) Set an environment variable called ${POSTGRESQL_ROOT} that points to the root of where you have -# installed PostgreSQL, e.g. . -# -# ---------------------------------------------------------------------------- - -set(POSTGRESQL_INCLUDE_PATH_DESCRIPTION "top-level directory containing the PostgreSQL include directories. E.g /usr/local/include/PostgreSQL/8.4 or C:/Program Files/PostgreSQL/8.4/include") -set(POSTGRESQL_INCLUDE_DIR_MESSAGE "Set the POSTGRESQL_INCLUDE_DIR cmake cache entry to the ${POSTGRESQL_INCLUDE_PATH_DESCRIPTION}") -set(POSTGRESQL_LIBRARY_PATH_DESCRIPTION "top-level directory containing the PostgreSQL libraries.") -set(POSTGRESQL_LIBRARY_DIR_MESSAGE "Set the POSTGRESQL_LIBRARY_DIR cmake cache entry to the ${POSTGRESQL_LIBRARY_PATH_DESCRIPTION}") -set(POSTGRESQL_ROOT_DIR_MESSAGE "Set the POSTGRESQL_ROOT system variable to where PostgreSQL is found on the machine E.g C:/Program Files/PostgreSQL/8.4") - - -set(POSTGRESQL_KNOWN_VERSIONS ${POSTGRESQL_ADDITIONAL_VERSIONS} - "14" "13" "12" "11" "10" "9.6" "9.5" "9.4" "9.3" "9.2" "9.1" "9.0" "8.4" "8.3" "8.2" "8.1" "8.0") - -# Define additional search paths for root directories. -set( POSTGRESQL_ROOT_DIRECTORIES - ENV POSTGRESQL_ROOT - ${POSTGRESQL_ROOT} -) -foreach(suffix ${POSTGRESQL_KNOWN_VERSIONS}) - if(WIN32) - list(APPEND POSTGRESQL_LIBRARY_ADDITIONAL_SEARCH_SUFFIXES - "PostgreSQL/${suffix}/lib") - list(APPEND POSTGRESQL_INCLUDE_ADDITIONAL_SEARCH_SUFFIXES - "PostgreSQL/${suffix}/include") - list(APPEND POSTGRESQL_TYPE_ADDITIONAL_SEARCH_SUFFIXES - "PostgreSQL/${suffix}/include/server") - endif() - if(UNIX) - list(APPEND POSTGRESQL_LIBRARY_ADDITIONAL_SEARCH_SUFFIXES - "pgsql-${suffix}/lib") - list(APPEND POSTGRESQL_INCLUDE_ADDITIONAL_SEARCH_SUFFIXES - "pgsql-${suffix}/include") - list(APPEND POSTGRESQL_TYPE_ADDITIONAL_SEARCH_SUFFIXES - "postgresql/${suffix}/server" - "pgsql-${suffix}/include/server") - endif() - if(APPLE) - list(APPEND POSTGRESQL_LIBRARY_ADDITIONAL_SEARCH_SUFFIXES - "postgresql@${suffix}") - list(APPEND POSTGRESQL_INCLUDE_ADDITIONAL_SEARCH_SUFFIXES - "postgresql@${suffix}") - endif() -endforeach() - -# -# Look for an installation. -# -find_path(POSTGRESQL_INCLUDE_DIR - NAMES libpq-fe.h - PATHS - # Look in other places. - ${POSTGRESQL_ROOT_DIRECTORIES} - PATH_SUFFIXES - pgsql - postgresql - include - ${POSTGRESQL_INCLUDE_ADDITIONAL_SEARCH_SUFFIXES} - # Help the user find it if we cannot. - DOC "The ${POSTGRESQL_INCLUDE_DIR_MESSAGE}" -) - -# The PostgreSQL library. -set (POSTGRESQL_LIBRARY_TO_FIND pq) -# Setting some more prefixes for the library -set (POSTGRESQL_LIB_PREFIX "") -if ( WIN32 ) - set (POSTGRESQL_LIB_PREFIX ${POSTGRESQL_LIB_PREFIX} "lib") - set (POSTGRESQL_LIBRARY_TO_FIND ${POSTGRESQL_LIB_PREFIX}${POSTGRESQL_LIBRARY_TO_FIND}) -endif() - -function(__postgresql_find_library _name) - find_library(${_name} - NAMES ${ARGN} - PATHS - ${POSTGRESQL_ROOT_DIRECTORIES} - PATH_SUFFIXES - lib - ${POSTGRESQL_LIBRARY_ADDITIONAL_SEARCH_SUFFIXES} - # Help the user find it if we cannot. - DOC "The ${POSTGRESQL_LIBRARY_DIR_MESSAGE}" - ) -endfunction() - -__postgresql_find_library(POSTGRESQL_LIBRARY ${POSTGRESQL_LIBRARY_TO_FIND}) -__postgresql_find_library(POSTGRESQL_LIBRARY_DEBUG ${POSTGRESQL_LIBRARY_TO_FIND}d) -get_filename_component(POSTGRESQL_LIBRARY_DIR ${POSTGRESQL_LIBRARY} PATH) - -if (POSTGRESQL_INCLUDE_DIR) - # Some platforms include multiple pg_config.hs for multi-lib configurations - # This is a temporary workaround. A better solution would be to compile - # a dummy c file and extract the value of the symbol. - file(GLOB _PG_CONFIG_HEADERS "${POSTGRESQL_INCLUDE_DIR}/pg_config*.h") - foreach(_PG_CONFIG_HEADER ${_PG_CONFIG_HEADERS}) - if(EXISTS "${_PG_CONFIG_HEADER}") - file(STRINGS "${_PG_CONFIG_HEADER}" pgsql_version_str - REGEX "^#define[\t ]+PG_VERSION[\t ]+\".*\"") - if(pgsql_version_str) - string(REGEX REPLACE "^#define[\t ]+PG_VERSION[\t ]+\"([^\"]*)\".*" - "\\1" POSTGRESQL_VERSION_STRING "${pgsql_version_str}") - break() - endif() - endif() - endforeach() - unset(pgsql_version_str) -endif() - -# Did we find anything? -# -#include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) -include(FindPackageHandleStandardArgs) -# -find_package_handle_standard_args(PostgreSQL - REQUIRED_VARS POSTGRESQL_LIBRARY POSTGRESQL_INCLUDE_DIR - VERSION_VAR POSTGRESQL_VERSION_STRING) -set(POSTGRESQL_FOUND ${POSTGRESQL_FOUND}) - -# Now try to get the include and library path. -if(POSTGRESQL_FOUND) - set(POSTGRESQL_INCLUDE_DIRS ${POSTGRESQL_INCLUDE_DIR}) - set(POSTGRESQL_LIBRARY_DIRS ${POSTGRESQL_LIBRARY_DIR}) - if(POSTGRESQL_LIBRARY AND POSTGRESQL_LIBRARY_DEBUG) - set(POSTGRESQL_LIBRARIES optimized ${POSTGRESQL_LIBRARY} debug ${POSTGRESQL_LIBRARY_DEBUG}) - else() - set(POSTGRESQL_LIBRARIES ${POSTGRESQL_LIBRARY}) - endif() - set(POSTGRESQL_VERSION ${POSTGRESQL_VERSION_STRING}) -endif() - -mark_as_advanced(POSTGRESQL_INCLUDE_DIR POSTGRESQL_LIBRARY) diff --git a/cmake/modules/FindSQLite3.cmake b/cmake/modules/FindSQLite3.cmake deleted file mode 100644 index 885a6569c..000000000 --- a/cmake/modules/FindSQLite3.cmake +++ /dev/null @@ -1,66 +0,0 @@ -############################################################################### -# CMake module to search for SQLite 3 library -# -# On success, the macro sets the following variables: -# SQLITE3_FOUND = if the library found -# SQLITE3_LIBRARY = full path to the library -# SQLITE3_LIBRARIES = full path to the library -# SQLITE3_INCLUDE_DIR = where to find the library headers -# -# Copyright (c) 2009 Mateusz Loskot -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# -############################################################################### - -find_path(SQLITE3_INCLUDE_DIR - NAMES sqlite3.h - PATH_PREFIXES sqlite sqlite3 - PATHS - /usr/include - /usr/local/include - $ENV{LIB_DIR}/include - $ENV{LIB_DIR}/include/sqlite - $ENV{LIB_DIR}/include/sqlite3 - $ENV{ProgramFiles}/SQLite/*/include - $ENV{ProgramFiles}/SQLite3/*/include - $ENV{SystemDrive}/SQLite/*/include - $ENV{SystemDrive}/SQLite3/*/include - $ENV{SQLITE_ROOT}/include - ${SQLITE_ROOT_DIR}/include - $ENV{OSGEO4W_ROOT}/include) - -set(SQLITE3_NAMES sqlite3_i sqlite3 sqlite3-static) -find_library(SQLITE3_LIBRARY - NAMES ${SQLITE3_NAMES} - PATHS - /usr/lib - /usr/local/lib - $ENV{LIB_DIR}/lib - $ENV{ProgramFiles}/SQLite/*/lib - $ENV{ProgramFiles}/SQLite3/*/lib - $ENV{SystemDrive}/SQLite/*/lib - $ENV{SystemDrive}/SQLite3/*/lib - $ENV{SQLITE_ROOT}/lib - ${SQLITE_ROOT_DIR}/lib - $ENV{OSGEO4W_ROOT}/lib) - -set(SQLITE3_LIBRARIES - ${SQLITE3_LIBRARIES} - ${SQLITE3_LIBRARY}) - -#message(STATUS ${SQLITE3_LIBRARY}) -# Handle the QUIETLY and REQUIRED arguments and set SQLITE3_FOUND to TRUE -# if all listed variables are TRUE -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SQLite3 - DEFAULT_MSG - SQLITE3_LIBRARIES - SQLITE3_INCLUDE_DIR) - -if(NOT SQLITE3_FOUND) - message(STATUS "SQLite3 not found (SQLITE3_INCLUDE_DIR=${SQLITE3_INCLUDE_DIR}, SQLITE3_LIBRARY=${SQLITE3_LIBRARY}.") -endif() - -mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR SQLITE3_LIBRARIES) diff --git a/cmake/modules/FindSoci.cmake b/cmake/modules/FindSoci.cmake deleted file mode 100644 index ece836e06..000000000 --- a/cmake/modules/FindSoci.cmake +++ /dev/null @@ -1,140 +0,0 @@ -############################################################################### -# CMake module to search for SOCI library -# -# This module defines: -# Soci_INCLUDE_DIRS = include dirs to be used when using the soci library -# Soci_LIBRARY = full path to the soci library -# Soci_VERSION = the soci version found -# Soci_FOUND = true if soci was found -# -# This module respects: -# LIB_SUFFIX = (64|32|"") Specifies the suffix for the lib directory -# -# For each component you specify in find_package(), the following variables are set. -# -# Soci_${COMPONENT}_PLUGIN = full path to the soci plugin (not set for the "core" component) -# Soci_${COMPONENT}_FOUND -# -# This module provides the following imported targets, if found: -# -# Soci::core = target for the core library and include directories -# Soci::${COMPONENT} = target for each plugin -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# -############################################################################### -# -### Global Configuration Section -# -set(_SOCI_ALL_PLUGINS mysql odbc postgresql sqlite3) -set(_SOCI_REQUIRED_VARS Soci_INCLUDE_DIR Soci_LIBRARY) - -# -### FIRST STEP: Find the soci headers. -# -find_path( - Soci_INCLUDE_DIR soci.h - HINTS "/usr/local" - PATH_SUFFIXES "" "soci" - DOC "Soci (http://soci.sourceforge.net) include directory") -mark_as_advanced(Soci_INCLUDE_DIR) - -set(Soci_INCLUDE_DIRS ${Soci_INCLUDE_DIR} CACHE STRING "") - -# -### SECOND STEP: Find the soci core library. Respect LIB_SUFFIX -# -find_library( - Soci_LIBRARY - NAMES soci_core - HINTS ${Soci_INCLUDE_DIR}/.. - PATH_SUFFIXES lib${LIB_SUFFIX}) -mark_as_advanced(Soci_LIBRARY) - -get_filename_component(Soci_LIBRARY_DIR ${Soci_LIBRARY} PATH) -mark_as_advanced(Soci_LIBRARY_DIR) - -# -### THIRD STEP: Find all installed plugins if the library was found -# -if(Soci_INCLUDE_DIR AND Soci_LIBRARY) - set(Soci_core_FOUND TRUE CACHE BOOL "") - - add_library(Soci::core UNKNOWN IMPORTED) - set_target_properties( - Soci::core - PROPERTIES IMPORTED_LOCATION "${Soci_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${Soci_INCLUDE_DIR}" - ) - - # - ### FOURTH STEP: Obtain SOCI version - # - set(Soci_VERSION_FILE "${Soci_INCLUDE_DIR}/version.h") - if(EXISTS "${Soci_VERSION_FILE}") - file(READ "${Soci_VERSION_FILE}" VERSION_CONTENT) - string(REGEX MATCH "#define[ \t]*SOCI_VERSION[ \t]*[0-9]+" VERSION_MATCH "${VERSION_CONTENT}") - string(REGEX REPLACE "#define[ \t]*SOCI_VERSION[ \t]*" "" VERSION_MATCH "${VERSION_MATCH}") - - if(NOT VERSION_MATCH) - message(WARNING "Failed to extract SOCI version") - else() - math(EXPR MAJOR "${VERSION_MATCH} / 100000" OUTPUT_FORMAT DECIMAL) - math(EXPR MINOR "${VERSION_MATCH} / 100 % 1000" OUTPUT_FORMAT DECIMAL) - math(EXPR PATCH "${VERSION_MATCH} % 100" OUTPUT_FORMAT DECIMAL) - - set(Soci_VERSION "${MAJOR}.${MINOR}.${PATCH}" CACHE STRING "") - endif() - else() - message(WARNING "Unable to check SOCI version") - endif() - - foreach(plugin IN LISTS _SOCI_ALL_PLUGINS) - - find_library( - Soci_${plugin}_PLUGIN - NAMES soci_${plugin} - HINTS ${Soci_INCLUDE_DIR}/.. - PATH_SUFFIXES lib${LIB_SUFFIX}) - mark_as_advanced(Soci_${plugin}_PLUGIN) - - if(Soci_${plugin}_PLUGIN) - set(Soci_${plugin}_FOUND TRUE CACHE BOOL "") - add_library(Soci::${plugin} UNKNOWN IMPORTED) - set_target_properties( - Soci::${plugin} - PROPERTIES IMPORTED_LOCATION "${Soci_${plugin}_PLUGIN}" - ) - target_link_libraries(Soci::${plugin} INTERFACE Soci::core) - else() - set(Soci_${plugin}_FOUND FALSE CACHE BOOL "") - endif() - - endforeach() -endif() - -# -### ADHERE TO STANDARDS -# -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Soci - REQUIRED_VARS ${_SOCI_REQUIRED_VARS} - VERSION_VAR Soci_VERSION - HANDLE_COMPONENTS -) - -# For compatibility with previous versions of this script -# DO NOT USE THESE VARIABLES IN NEW PROJECTS! -set(SOCI_FOUND ${Soci_FOUND}) -set(SOCI_INCLUDE_DIRS ${Soci_INCLUDE_DIRS}) -set(SOCI_LIBRARY ${Soci_LIBRARY}) -set(SOCI_VERSION ${Soci_VERSION}) -set(SOCI_mysql_FOUND ${Soci_mysql_FOUND}) -set(SOCI_odbc_FOUND ${Soci_odbc_FOUND}) -set(SOCI_postgresql_FOUND ${Soci_postgresql_PLUGIN}) -set(SOCI_sqlite3_FOUND ${Soci_sqlite3_PLUGIN}) -set(SOCI_mysql_PLUGIN ${Soci_mysql_PLUGIN}) -set(SOCI_odbc_PLUGIN ${Soci_odbc_PLUGIN}) -set(SOCI_postgresql_PLUGIN ${Soci_postgresql_PLUGIN}) -set(SOCI_sqlite3_PLUGIN ${Soci_sqlite3_PLUGIN}) diff --git a/cmake/resources/SOCIConfig.cmake.in b/cmake/resources/SOCIConfig.cmake.in deleted file mode 100644 index 8096a3c25..000000000 --- a/cmake/resources/SOCIConfig.cmake.in +++ /dev/null @@ -1,3 +0,0 @@ -@PACKAGE_INIT@ - -include(${CMAKE_CURRENT_LIST_DIR}/SOCITargets.cmake) diff --git a/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in b/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in deleted file mode 100644 index f41ec9b27..000000000 --- a/cmake/resources/vs2010-test-cmd-args.vcxproj.user.in +++ /dev/null @@ -1,7 +0,0 @@ - - - - @SOCI_TEST_CMD_ARGS@ - WindowsLocalDebugger - - diff --git a/cmake/soci_compat.cmake b/cmake/soci_compat.cmake new file mode 100644 index 000000000..480cb36b4 --- /dev/null +++ b/cmake/soci_compat.cmake @@ -0,0 +1,27 @@ +# Take care of defining ALIAS targets to keep old target names working + +if (SOCI_SHARED) + set(SUFFIX "") +else() + set(SUFFIX "_static") +endif() + +set(OLD_NAMES core db2 empty firebird mysql odbc oracle postgresql sqlite3) +set(ALIAS_NAMES Core DB2 Empty Firebird MySQL ODBC Oracle PostgreSQL SQLite3) + +list(LENGTH ALIAS_NAMES N_ITEMS) +math(EXPR LAST_ITEM "${N_ITEMS} - 1") + +foreach (I RANGE ${LAST_ITEM}) + list(GET OLD_NAMES ${I} CURRENT_NAME) + list(GET ALIAS_NAMES ${I} CURRENT_ALIAS) + + if (NOT TARGET SOCI::${CURRENT_ALIAS}) + continue() + endif() + + get_target_property(UNDERLYING_LIB SOCI::${CURRENT_ALIAS} ALIASED_TARGET) + + add_library(Soci::${CURRENT_NAME}${SUFFIX} ALIAS ${UNDERLYING_LIB}) + add_library(SOCI::${CURRENT_NAME}${SUFFIX} ALIAS ${UNDERLYING_LIB}) +endforeach() diff --git a/cmake/soci_compiler_options.cmake b/cmake/soci_compiler_options.cmake new file mode 100644 index 000000000..465806b93 --- /dev/null +++ b/cmake/soci_compiler_options.cmake @@ -0,0 +1,96 @@ + + +add_library(soci_compiler_interface INTERFACE) + +# +# Force compilation flags and set desired warnings level +# + +option(SOCI_ENABLE_WERROR "Enables turning compiler warnings into errors" OFF) + +if (WIN32) + target_compile_definitions(soci_compiler_interface + INTERFACE + _CRT_SECURE_NO_DEPRECATE + _CRT_SECURE_NO_WARNINGS + _CRT_NONSTDC_NO_WARNING + _SCL_SECURE_NO_WARNINGS + ) + + # Prevent the Windows header files from defining annoying macros + # and also cut down on the definitions in general + target_compile_definitions(soci_compiler_interface + INTERFACE + NOMINMAX + WIN32_LEAN_AND_MEAN + ) +endif() + +if (MSVC) + # Configure warnings + target_compile_options(soci_compiler_interface + INTERFACE + "$<$:/W4>" + "$<$:/we4266>" + ) + + if (SOCI_ENABLE_WERROR) + target_compile_options(soci_compiler_interface INTERFACE "/WX") + endif() + + target_compile_options(soci_compiler_interface INTERFACE "/bigobj" "/utf-8") +else() + + if (SOCI_ENABLE_WERROR) + target_compile_options(soci_compiler_interface INTERFACE "-Werror") + endif() + + if (SOCI_UBSAN) + target_compile_options(soci_compiler_interface INTERFACE "-fsanitize=undefined") + target_link_options(soci_compiler_interface INTERFACE "-fsanitize=undefined") + endif() + + + if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER}" MATCHES "clang") + if(SOCI_ASAN AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 3.1) + # This can also be used to set a linker flag + target_link_libraries(soci_compiler_interface INTERFACE "$<$:-fsanitize=address>") + target_compile_options(soci_compiler_interface INTERFACE "$<$:-fsanitize=address>") + endif() + + set(SOCI_USE_STD_FLAGS ON) + elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + if (SOCI_ASAN AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 4.8) + # This can also be used to set a linker flag + target_link_libraries(soci_compiler_interface INTERFACE "$<$:-fsanitize=address>") + target_compile_options(soci_compiler_interface INTERFACE "$<$:-fsanitize=address>") + endif() + + if (CMAKE_COMPILER_IS_GNUCXX) + if (NOT (CMAKE_SYSTEM_NAME MATCHES "FreeBSD")) + target_compile_options(soci_compiler_interface INTERFACE "$<$:-Wno-variadic-macros>") + endif() + endif() + + set(SOCI_USE_STD_FLAGS ON) + else() + message(WARNING "Unknown toolset - using default flags to build SOCI") + endif() + + if (SOCI_USE_STD_FLAGS) + target_compile_options(soci_compiler_interface + INTERFACE + "$<$:-pedantic>" + "$<$:-Wno-error=parentheses>" + "$<$:-Wall>" + "$<$:-Wextra>" + "$<$:-Wpointer-arith>" + "$<$:-Wcast-align>" + "$<$:-Wcast-qual>" + "$<$:-Wfloat-equal>" + "$<$:-Woverloaded-virtual>" + "$<$:-Wredundant-decls>" + "$<$:-Wno-long-long>" + ) + endif() +endif() diff --git a/cmake/soci_define_backend_target.cmake b/cmake/soci_define_backend_target.cmake new file mode 100644 index 000000000..a981c1b51 --- /dev/null +++ b/cmake/soci_define_backend_target.cmake @@ -0,0 +1,178 @@ +include(soci_utils) + +# Defines a CMake target for a database backend. +# +# This function takes care of orchestrating the boilerplate that is needed in order to set up +# a library target as used for different DB backends. Accepted arguments are +# +# ALIAS_NAME Alias to use for the library. The alias name will be prefixed with "SOCI::" +# BACKEND_NAME Name of the backend +# ENABLED_VARIABLE CMake variable that indicates whether this backend is enabled. Will be set +# to OFF if one of the dependencies are not satisfied. +# MISSING_DEPENDENCY_BEHAVIOR What to do if a dependency is not found. Valid values are "ERROR" and "DISABLE" +# TARGET_NAME Name of the CMake target that shall be created for this backend +# DEPENDENCIES [... ] List of dependency specifications. Each specification has to be a single +# argument (single string) following the syntax +# YIELDS [DEFINES ] +# where will be passed to find_package to find the dependency. Upon +# success, all targets defined in are expected to exist. If provided, +# all defines specified in will be added as public compile definitions +# to the backend library if the dependency has been found. +# For now, all dependencies are expected to be public and required. +# FIND_PACKAGE_FILES [... ] List of files used by find_package to locate one of the dependencies. Specified +# files will be installed alongside SOCI in order to be usable from the install tree. +# HEADER_FILES [... ] List of public header files associated with this backend target. +# PRIVATE_INCLUDE_DIRS [... ] List of private include directories +# REQUIRED_COMPONENTS [... ] List of SOCI components (full target names) to link this backend target to +# SOURCE_FILES [... ] List of source files that shall be part of this backend component +function(soci_define_backend_target) + set(FLAGS "") + set(ONE_VAL_OPTIONS + "ALIAS_NAME" + "BACKEND_NAME" + "ENABLED_VARIABLE" + "MISSING_DEPENDENCY_BEHAVIOR" + "TARGET_NAME" + ) + set(MULTI_VAL_OPTIONS + "DEPENDENCIES" + "FIND_PACKAGE_FILES" + "HEADER_FILES" + "PRIVATE_INCLUDE_DIRS" + "REQUIRED_COMPONENTS" + "SOURCE_FILES" + ) + cmake_parse_arguments(DEFINE_BACKEND "${FLAGS}" "${ONE_VAL_OPTIONS}" "${MULTI_VAL_OPTIONS}" ${ARGV}) + + soci_verify_parsed_arguments( + PREFIX "DEFINE_BACKEND" + FUNCTION_NAME "soci_define_backend_target" + REQUIRED "BACKEND_NAME" "SOURCE_FILES" "ENABLED_VARIABLE" "TARGET_NAME" "ALIAS_NAME" + ) + + if (NOT DEFINE_BACKEND_MISSING_DEPENDENCY_BEHAVIOR) + set(DEFINE_BACKEND_MISSING_DEPENDENCY_BEHAVIOR "ERROR") + else() + string(TOUPPER "${DEFINE_BACKEND_MISSING_DEPENDENCY_BEHAVIOR}" DEFINE_BACKEND_MISSING_DEPENDENCY_BEHAVIOR) + endif() + + + if (DEFINE_BACKEND_MISSING_DEPENDENCY_BEHAVIOR STREQUAL "ERROR") + set(REQUIRE_FLAG "REQUIRED") + set(ERROR_ON_MISSING_DEPENDENCY ON) + elseif(DEFINE_BACKEND_MISSING_DEPENDENCY_BEHAVIOR STREQUAL "DISABLE") + set(DISABLE_ON_MISSING_DEPENDENCY ON) + else() + message(FATAL_ERROR "Invalid value '${DEFINE_BACKEND_MISSING_DEPENDENCY_BEHAVIOR}' for option 'MISSING_DEPENDENCY_BEHAVIOR'") + endif() + + + set(PUBLIC_DEP_CALL_ARGS "") + + foreach(CURRENT_DEP_SPEC IN LISTS DEFINE_BACKEND_DEPENDENCIES) + if (NOT "${CURRENT_DEP_SPEC}" MATCHES "^([a-zA-Z0-9_:-;]+) YIELDS ([a-zA-Z0-9_:-;]+)( DEFINES [a-zA-Z0-9_;])?$") + message(FATAL_ERROR "Invalid format for dependency specification in '${CURRENT_DEP_SPEC}'") + endif() + set(CURRENT_DEP_SEARCH ${CMAKE_MATCH_1}) + set(CURRENT_DEP_TARGETS ${CMAKE_MATCH_2}) + set(CURRENT_DEP_DEFINES ${CMAKE_MATCH_3}) + + list(GET CURRENT_DEP_SEARCH 0 CURRENT_DEP) + + find_package(${CURRENT_DEP_SEARCH} ${REQUIRE_FLAG}) + + if (NOT ${CURRENT_DEP}_FOUND) + if (ERROR_ON_MISSING_DEPENDENCY) + message(FATAL_ERROR "Expected find_package to error due to unmet dependency '${CURRENT_DEP}'") + elseif(DISABLE_ON_MISSING_DEPENDENCY) + message(STATUS "Disabling SOCI backend '${DEFINE_BACKEND_BACKEND_NAME}' due to unsatisfied dependency on '${CURRENT_DEP}'") + + # Set this backend to disabled by overwriting the corresponding cache variable + # (without overwriting its description) + get_property(DESCRIPTION CACHE "${DEFINE_BACKEND_ENABLED_VARIABLE}" PROPERTY HELPSTRING) + set(${DEFINE_BACKEND_ENABLED_VARIABLE} OFF CACHE STRING "${DESCRIPTION}" FORCE) + return() + else() + message(FATAL_ERROR "Unspecified handling of unmet dependency") + endif() + endif() + + foreach (CURRENT IN LISTS CURRENT_DEP_TARGETS) + if (NOT TARGET "${CURRENT}") + message(FATAL_ERROR "Expected successful find_package call with '${CURRENT_DEP_SEARCH}' to define target '${CURRENT}'") + endif() + endforeach() + if (CURRENT_DEP_DEFINES) + set(MACRO_NAMES_ARG "MACRO_NAMES ${CURRENT_DEP_DEFINES}") + endif() + list(APPEND PUBLIC_DEP_CALL_ARGS + "NAME ${CURRENT_DEP} DEP_TARGETS ${CURRENT_DEP_TARGETS} TARGET SOCI::${DEFINE_BACKEND_ALIAS_NAME} ${MACRO_NAMES_ARG} REQUIRED" + ) + endforeach() + + + add_library(${DEFINE_BACKEND_TARGET_NAME} ${SOCI_LIB_TYPE} ${DEFINE_BACKEND_SOURCE_FILES}) + add_library(SOCI::${DEFINE_BACKEND_ALIAS_NAME} ALIAS ${DEFINE_BACKEND_TARGET_NAME}) + + foreach(CURRENT_ARG_SET IN LISTS PUBLIC_DEP_CALL_ARGS) + # Convert space-separated string to list + string(REPLACE " " ";" CURRENT_ARG_SET "${CURRENT_ARG_SET}") + soci_public_dependency(${CURRENT_ARG_SET}) + endforeach() + foreach (CURRENT_DEP IN LISTS DEFINE_BACKEND_REQUIRED_COMPONENTS) + target_link_libraries(${DEFINE_BACKEND_TARGET_NAME} PUBLIC "${CURRENT_DEP}") + endforeach() + + target_include_directories(${DEFINE_BACKEND_TARGET_NAME} PRIVATE ${DEFINE_BACKEND_PRIVATE_INCLUDE_DIRS}) + + set_target_properties(${DEFINE_BACKEND_TARGET_NAME} + PROPERTIES + SOVERSION ${PROJECT_VERSION_MAJOR} + VERSION ${PROJECT_VERSION} + EXPORT_NAME ${DEFINE_BACKEND_ALIAS_NAME} + ) + + if (DEFINE_BACKEND_HEADER_FILES) + target_sources(${DEFINE_BACKEND_TARGET_NAME} + PUBLIC + FILE_SET headers TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/include/" + FILES ${DEFINE_BACKEND_HEADER_FILES} + ) + endif() + + + target_link_libraries(soci_interface INTERFACE SOCI::${DEFINE_BACKEND_ALIAS_NAME}) + + + # Setup installation rules for this backend + install( + TARGETS ${DEFINE_BACKEND_TARGET_NAME} + EXPORT SOCI${DEFINE_BACKEND_BACKEND_NAME}Targets + RUNTIME DESTINATION "${SOCI_INSTALL_BINDIR}" + COMPONENT soci_runtime + LIBRARY DESTINATION "${SOCI_INSTALL_LIBDIR}" + COMPONENT soci_runtime + NAMELINK_COMPONENT soci_development + ARCHIVE DESTINATION "${SOCI_INSTALL_LIBDIR}" + COMPONENT soci_development + FILE_SET headers DESTINATION "${SOCI_INSTALL_INCLUDEDIR}" + COMPONENT soci_development + ) + # Generate and install a targets file + install( + EXPORT SOCI${DEFINE_BACKEND_BACKEND_NAME}Targets + DESTINATION "${SOCI_INSTALL_CMAKEDIR}" + FILE SOCI${DEFINE_BACKEND_BACKEND_NAME}Targets.cmake + NAMESPACE SOCI:: + COMPONENT soci_development + ) + + foreach(FIND_FILE IN LISTS DEFINE_BACKEND_FIND_PACKAGE_FILES) + install( + FILES "${FIND_FILE}" + DESTINATION "${SOCI_INSTALL_CMAKEDIR}/find_package_files/" + COMPONENT soci_development + ) + endforeach() +endfunction() diff --git a/cmake/soci_install_dirs.cmake b/cmake/soci_install_dirs.cmake new file mode 100644 index 000000000..95ba35d8b --- /dev/null +++ b/cmake/soci_install_dirs.cmake @@ -0,0 +1,15 @@ +include(GNUInstallDirs) + +set(SOCI_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/soci-${PROJECT_VERSION}" + CACHE FILEPATH "Directory into which cmake related files (e.g. SOCIConfig.cmake) are installed") + +set(SOCI_INSTALL_BINDIR "${CMAKE_INSTALL_BINDIR}" + CACHE FILEPATH "Directory into which to install any SOCI executables") + +set(SOCI_INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}/soci" + CACHE FILEPATH "Directory into which any SOCI libraries (except DLLs on Windows) are installed") + +# Note that our headers are installed via a file set that already has the headers +# in a dedicated "soci" subdirectory (which will be part of the installation) +set(SOCI_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}" + CACHE FILEPATH "Directory into which the 'soci' directory with all SOCI header files is installed") diff --git a/cmake/soci_parse_version.cmake b/cmake/soci_parse_version.cmake new file mode 100644 index 000000000..d9d01cf54 --- /dev/null +++ b/cmake/soci_parse_version.cmake @@ -0,0 +1,43 @@ +include(soci_utils) + +# This function extracts the current SOCI version from the version header file +# +# Use as +# soci_parse_version( +# ROOT_DIR +# OUTPUT_VARIABLE +# ) +# where +# - is the path to SOCI's source tree root directory +# - is the name of the variable used to make the parsed version available. This variable +# will contain the version in a cmake-compatible format +function(soci_parse_version) + set(FLAGS "") + set(ONE_VAL_OPTIONS "OUTPUT_VARIABLE" "ROOT_DIR") + set(MULTI_VAL_OPTIONS "") + cmake_parse_arguments(PARSE_VERSION "${FLAGS}" "${ONE_VAL_OPTIONS}" "${MULTI_VAL_OPTIONS}" ${ARGV}) + soci_verify_parsed_arguments( + PREFIX "PARSE_VERSION" + FUNCTION_NAME "soci_parse_version" + REQUIRED "OUTPUT_VARIABLE" "ROOT_DIR" + ) + + set(VERSION_REGEX "^#define[ \\t]*SOCI_VERSION[ \\t]*([0-9]+)") + + file(STRINGS "${PARSE_VERSION_ROOT_DIR}/include/soci/version.h" VERSION_LINE + REGEX ${VERSION_REGEX} + LIMIT_COUNT 1 + ) + + if (NOT VERSION_LINE MATCHES "${VERSION_REGEX}") + message(STATUS "${PARSE_VERSION_ROOT_DIR}") + message(FATAL_ERROR "Failed at parsing version from header file") + endif() + + set(RAW_VERSION "${CMAKE_MATCH_1}") + math(EXPR MAJOR_VERSION "${RAW_VERSION} / 100000") + math(EXPR MINOR_VERSION "(${RAW_VERSION} / 100) % 1000") + math(EXPR PATCH_VERSION "${RAW_VERSION} % 100") + + set("${PARSE_VERSION_OUTPUT_VARIABLE}" "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}" PARENT_SCOPE) +endfunction() diff --git a/cmake/soci_utils.cmake b/cmake/soci_utils.cmake new file mode 100644 index 000000000..4d0524e6a --- /dev/null +++ b/cmake/soci_utils.cmake @@ -0,0 +1,226 @@ +# This function is meant to be called by other cmake functions that use the cmake_parse_arguments function. +# It implements the boilerplate that is associated with validating the parsed arguments (e.g. no unknown arguments used; +# ensuring all required options have been set). +# +# Use as +# soci_verify_parsed_arguments( +# PREFIX +# FUNCTION_NAME +# [REQUIRED …] +# ) +# where +# - is the prefix used in the cmake_parse_arguments call that shall be validated +# - is the name of the calling function or macro - this will be part of error mesages +# - are names of options **without** the prefix +function(soci_verify_parsed_arguments) + set(FLAGS "") + set(ONE_VAL_OPTIONS "PREFIX" "FUNCTION_NAME") + set(MULTI_VAL_OPTIONS "REQUIRED") + cmake_parse_arguments(VERIFY_PARSED_ARGS "${FLAGS}" "${ONE_VAL_OPTIONS}" "${MULTI_VAL_OPTIONS}" ${ARGV}) + + # First, verify own arguments + if (DEFINED VERIFY_PARSED_ARGS_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "verify_parsed_arguments - Received unrecognized options: ${VERIFY_PARSED_ARGS_UNPARSED_ARGUMENTS}") + endif() + if (DEFINED VERIFY_PARSED_ARGS_KEYWORDS_MISSING_VALUES) + message(FATAL_ERROR "verify_parsed_arguments - The following options are missing argumens: ${VERIFY_PARSED_ARGS_KEYWORDS_MISSING_VALUES}") + endif() + if (NOT DEFINED VERIFY_PARSED_ARGS_PREFIX) + message(FATAL_ERROR "verify_parsed_arguments - Missing required option 'PREFIX'") + endif() + if (NOT DEFINED VERIFY_PARSED_ARGS_FUNCTION_NAME) + message(FATAL_ERROR "verify_parsed_arguments - Missing required option 'FUNCTION_NAME'") + endif() + + # Now start the function's actual job: Verifying a top-level function's call to cmake_parse_arguments + if (DEFINED ${VERIFY_PARSED_ARGS_PREFIX}_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "${VERIFY_PARSED_ARGS_FUNCTION_NAME} - Received unrecognized options: ${${VERIFY_PARSED_ARGS_PREFIX}_UNPARSED_ARGUMENTS}") + endif() + if (DEFINED ${VERIFY_PARSED_ARGS_PREFIX}_KEYWORDS_MISSING_VALUES) + message(FATAL_ERROR "${VERIFY_PARSED_ARGS_FUNCTION_NAME} - The following options are missing arguments: ${${VERIFY_PARSED_ARGS_PREFIX}_KEYWORDS_MISSING_VALUES}") + endif() + + if (DEFINED VERIFY_PARSED_ARGS_REQUIRED) + foreach(CURRENT_ARG IN LISTS VERIFY_PARSED_ARGS_REQUIRED) + set(CURRENT_ARG "${VERIFY_PARSED_ARGS_PREFIX}_${CURRENT_ARG}") + if (NOT DEFINED ${CURRENT_ARG}) + message(FATAL_ERROR "${VERIFY_PARSED_ARGS_FUNCTION_NAME - Missing required option '${CURRENT_ARG}'") + endif() + endforeach() + endif() +endfunction() + + +# Initialize variables populated by soci_public_dependency +set(SOCI_DEPENDENCY_VARIABLES + "SOCI_DEPENDENCY_SOCI_COMPONENTS" + "SOCI_DEPENDENCY_NAMES" + "SOCI_DEPENDENCY_TARGETS" + "SOCI_DEPENDENCY_REQUIRED" + "SOCI_DEPENDENCY_MACRO_NAMES" + "SOCI_DEPENDENCY_COMPONENTS" +) +if (NOT DEFINED SOCI_UTILS_ALREADY_INCLUDED) + foreach(VAR_NAME IN LISTS SOCI_DEPENDENCY_VARIABLES) + set("${VAR_NAME}" "" CACHE INTERNAL "") + endforeach() +endif() + +# This function declares a public dependency of a given target in such a way that +# the dependency is automatically populated to a generated SOCI config file. +# +# Use as +# soci_public_dependency( +# NAME +# DEP_TARGET +# TARGET +# [MACRO_NAMES ...] +# [REQUIRED] +# [COMPONENTS ...] +# ) +# where +# - is the name of the dependency (used for lookup via find_package) +# - is the name of the target that will be imported upon a +# successful find_package call +# - is the name of the ALIAS target to link the found dependency to +# - are the names of specific components of the dependency +function(soci_public_dependency) + set(FLAGS "REQUIRED") + set(ONE_VAL_OPTIONS "TARGET" "NAME") + set(MULTI_VAL_OPTIONS "COMPONENTS" "MACRO_NAMES" "DEP_TARGETS") + + cmake_parse_arguments(PUBLIC_DEP "${FLAGS}" "${ONE_VAL_OPTIONS}" "${MULTI_VAL_OPTIONS}" ${ARGV}) + soci_verify_parsed_arguments( + PREFIX "PUBLIC_DEP" + FUNCTION_NAME "soci_public_dependency" + REQUIRED "TARGET" "NAME" "DEP_TARGETS" + ) + + # Sanity checking + if (NOT TARGET "${PUBLIC_DEP_TARGET}") + message(FATAL_ERROR "Provided TARGET '${PUBLIC_DEP_TARGET}' does not exist") + endif() + + get_target_property(UNDERLYING_TARGET "${PUBLIC_DEP_TARGET}" ALIASED_TARGET) + if (NOT UNDERLYING_TARGET) + message(FATAL_ERROR "Provided TARGET '${PUBLIC_DEP_TARGET}' is expected to be an ALIAS target") + endif() + + + # Bookkeeping + list(APPEND SOCI_DEPENDENCY_SOCI_COMPONENTS "${PUBLIC_DEP_TARGET}") + list(APPEND SOCI_DEPENDENCY_NAMES "${PUBLIC_DEP_NAME}") + list(JOIN PUBLIC_DEP_DEP_TARGETS "|" STORED_TARGETS) + list(APPEND SOCI_DEPENDENCY_TARGETS "${STORED_TARGETS}") + list(APPEND SOCI_DEPENDENCY_REQUIRED "${PUBLIC_DEP_REQUIRED}") + if (PUBLIC_DEP_MACRO_NAMES) + list(JOIN PUBLIC_DEP_MACRO_NAMES "|" STORED_MACROS) + else() + set(STORED_MACROS "") + endif() + list(APPEND SOCI_DEPENDENCY_MACRO_NAMES "${STORED_MACROS}") + if (PUBLIC_DEP_COMPONENTS) + list(JOIN PUBLIC_DEP_COMPONENTS "|" STORED_COMPONENTS) + else() + set(STORED_COMPONENTS "") + endif() + list(APPEND SOCI_DEPENDENCY_COMPONENTS "${STORED_COMPONENTS}") + + + foreach(VAR_NAME IN LISTS SOCI_DEPENDENCY_VARIABLES) + set("${VAR_NAME}" "${${VAR_NAME}}" CACHE INTERNAL "") + endforeach() + + + # Search for the package now + if (PUBLIC_DEP_REQUIRED) + set(REQUIRED "REQUIRED") + else() + set(REQUIRED "") + endif() + + if (PUBLIC_DEP_COMPONENTS) + set(COMPONENTS COMPONENTS ${PUBLIC_DEP_COMPONENTS}) + else() + set(COMPONENTS "") + endif() + + set(SKIP_SEARCH ON) + foreach (TGT IN LISTS PUBLIC_DEP_DEP_TARGETS) + if (NOT TARGET "${TGT}") + set(SKIP_SEARCH OFF) + endif() + endforeach() + + if (NOT SKIP_SEARCH) + find_package("${PUBLIC_DEP_NAME}" ${COMPONENTS} ${REQUIRED}) + endif() + + set(FOUND_ONE OFF) + foreach (TGT IN LISTS PUBLIC_DEP_DEP_TARGETS) + if (NOT TARGET "${TGT}") + if (FOUND_ONE) + message(DEBUG "The following SOCI dependencies have been found only partially: ${PUBLIC_DEP_DEP_TARGETS}") + endif() + return() + endif() + + set(FOUND_ONE ON) + + target_link_libraries("${UNDERLYING_TARGET}" PUBLIC "$") + endforeach() + + foreach (MACRO_NAME IN LISTS PUBLIC_DEP_MACRO_NAMES) + # Note: We don't want these compile definitions to be exported to the install tree + # -> We put the logic of when they should be defined into the cmake config files + target_compile_definitions("${UNDERLYING_TARGET}" PUBLIC "$") + endforeach() +endfunction() + +# This function can be used to check whether two C++ types actually refer to the same +# type (e.g. if one is aliased to the other). +# +# Use as +# soci_are_types_same( +# TYPES [... ] +# OUTPUT_VARIABLE +# ) +# where +# - , , are the types to test +# - is the name of the variable that will hold the result of the check +function(soci_are_types_same) + set(FLAGS "") + set(ONE_VAL_OPTIONS "OUTPUT_VARIABLE") + set(MULTI_VAL_OPTIONS "TYPES") + + cmake_parse_arguments(TYPES_SAME "${FLAGS}" "${ONE_VAL_OPTIONS}" "${MULTI_VAL_OPTIONS}" ${ARGV}) + soci_verify_parsed_arguments( + PREFIX "TYPES_SAME" + FUNCTION_NAME "soci_are_types_same" + REQUIRED "TYPES" "OUTPUT_VARIABLE" + ) + + set(TEST_CODE "#include \nstruct Foo { ") + set(TEST_NAME "") + foreach(CURRENT_TYPE IN LISTS TYPES_SAME_TYPES) + string(APPEND TEST_CODE "void foo(${CURRENT_TYPE} x); ") + string(TOUPPER "${CURRENT_TYPE}" UPPER_TYPE) + string(APPEND TEST_NAME "${UPPER_TYPE}_") + endforeach() + string(APPEND TEST_CODE "};\nint main() {}") + string(APPEND TEST_NAME "ARE_DISTINGUISHABLE") + + include(CheckCXXSourceCompiles) + + # If some of the provided types are actually the same, compilation + # will fail due to duplication of function declarations. + check_cxx_source_compiles("${TEST_CODE}" ${TEST_NAME}) + + if (${TEST_NAME}) + set("${TYPES_SAME_OUTPUT_VARIABLE}" FALSE PARENT_SCOPE) + else() + set("${TYPES_SAME_OUTPUT_VARIABLE}" TRUE PARENT_SCOPE) + endif() +endfunction() + +set(SOCI_UTILS_ALREADY_INCLUDED TRUE) diff --git a/examples/connect/CMakeLists.txt b/examples/connect/CMakeLists.txt index c315cc91f..ed56df688 100644 --- a/examples/connect/CMakeLists.txt +++ b/examples/connect/CMakeLists.txt @@ -7,20 +7,28 @@ # This is a very simple example of using SOCI in a CMake-based project. -cmake_minimum_required(VERSION 2.8 FATAL_ERROR) +cmake_minimum_required(VERSION 3.23...3.31 FATAL_ERROR) project(SOCIExampleConnect) -find_package(Soci REQUIRED) +# Note: SOCI versions before 4.1 rely on hand-written FindSOCI.cmake files to be +# present in downstream projects as SOCI didn't use to ship a config file. +# This is changed in 4.1 thanks to which the desired database backends can +# be specified as components. Known component names are +# - Empty +# - DB2 +# - Firebird +# - MySQL +# - ODBC +# - Oracle +# - PostgreSQL +# - SQLite3 +# note that some components may still be undefined if the corresponding library is +# not included in the SOCI version installed on your system. +find_package(SOCI 4.1 COMPONENTS Empty REQUIRED) add_executable(connect connect.cpp) -# Note that depending on the SOCI library automatically pulls in the required -# SOCI compilation options too, i.e. there is no need to explicitly use -# target_include_directories(). -# -# Linking with just soci_core is enough when using shared libraries, as the -# required backends will be loaded dynamically during run-time, but when using -# static libraries you would need to link with all the soci_ libraries -# needed too. -target_link_libraries(connect PRIVATE SOCI::soci_core) +# Note that linking to SOCI::soci is enough to inherit +# all of SOCI's dependencies, include directories, etc. +target_link_libraries(connect PRIVATE SOCI::soci) diff --git a/examples/subdir-include/CMakeLists.txt b/examples/subdir-include/CMakeLists.txt index 6861bc560..ebe1f903a 100644 --- a/examples/subdir-include/CMakeLists.txt +++ b/examples/subdir-include/CMakeLists.txt @@ -2,7 +2,7 @@ # when placing SOCI in a subdirectory lib/soci/ of your project. # For this example, the SOCI backend called Empty is used. -cmake_minimum_required(VERSION 2.8 FATAL_ERROR) +cmake_minimum_required(VERSION 3.23 FATAL_ERROR) project(SOCIExampleSubdirectoryInclude) @@ -10,7 +10,16 @@ set(SOCI_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/soci) # All compile options need to be set before the SOCI directory gets included. # The backend you want to use needs to be enabled here. -option(SOCI_EMPTY "Build the sample backend called Empty" ON) +set(SOCI_EMPTY ON CACHE INTERNAL "" FORCE) + +# In order to not run into cross-platform issues, the **top-level** project +# should define these variables to ensure all build artefacts (of a given kind) +# end up in the same place. +if (PROJECT_IS_TOP_LEVEL) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") +endif() # This line needs to be changed to include the directory you placed SOCI in, e.g. # add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/soci ${SOCI_BINARY_DIR}) @@ -18,12 +27,8 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../.. ${SOCI_BINARY_DIR}) add_executable(subdir_include subdir-include.cpp) -# Link the soci_ libraries you want to use here. -# There is no need to explicitly use target_include_directories() for the -# main headers or the generated header (soci/soci-config.h), because both -# are automatically added as part of the Soci::core target. +# Link to SOCI (SOCI::soci will resolve all requested dependencies automatically) target_link_libraries(subdir_include PRIVATE - Soci::core - Soci::empty + SOCI::soci ) diff --git a/include/soci/callbacks.h b/include/soci/callbacks.h index 85825ad1b..e08c24dee 100644 --- a/include/soci/callbacks.h +++ b/include/soci/callbacks.h @@ -8,6 +8,10 @@ #ifndef SOCI_CALLBACKS_H_INCLUDED #define SOCI_CALLBACKS_H_INCLUDED +#include "soci/soci-platform.h" + +#include + namespace soci { @@ -19,26 +23,28 @@ class session; class SOCI_DECL failover_callback { public: + failover_callback() = default; + virtual ~failover_callback(); // Called when the failover operation has started, // after discovering connectivity problems. - virtual void started() {} + virtual void started(); // Called after successful failover and creating a new connection; // the sql parameter denotes the new connection and allows the user // to replay any initial sequence of commands (like session configuration). - virtual void finished(session & /* sql */) {} + virtual void finished(session& /* sql */); // Called when the attempt to reconnect failed, // if the user code sets the retry parameter to true, // then new connection will be attempted; // the newTarget connection string is a hint that can be ignored // by external means. - virtual void failed(bool & /* out */ /* retry */, - std::string & /* out */ /* newTarget */) {} + virtual void failed(bool& /* out */ /* retry */, + std::string& /* out */ /* newTarget */); // Called when there was a failure that prevents further failover attempts. - virtual void aborted() {} + virtual void aborted(); }; } // namespace soci diff --git a/include/soci/mysql/soci-mysql.h b/include/soci/mysql/soci-mysql.h index e9dc17e76..83203c98e 100644 --- a/include/soci/mysql/soci-mysql.h +++ b/include/soci/mysql/soci-mysql.h @@ -19,29 +19,16 @@ #include #include -#ifdef _WIN32 -#include // SOCKET -#endif // _WIN32 - -// Some version of mysql.h contain trailing comma in an enum declaration that -// trigger -Wpedantic, so suppress it as there is nothing to be done about it -// using the macros defined in our private soci-compiler.h header, that we can -// only include when building SOCI itself. -#ifdef SOCI_MYSQL_SOURCE - #include "soci-compiler.h" -#endif - -#ifdef SOCI_GCC_WARNING_SUPPRESS - SOCI_GCC_WARNING_SUPPRESS(pedantic) -#endif +#ifdef SOCI_MYSQL_DIRECT_INCLUDE #include // MySQL Client #include // MySQL Error codes - -#ifdef SOCI_GCC_WARNING_RESTORE - SOCI_GCC_WARNING_RESTORE(pedantic) +#else +#include // MySQL Client +#include // MySQL Error codes #endif + #include diff --git a/include/soci/soci-config.h.in b/include/soci/soci-config.h.in deleted file mode 100644 index 77fff775d..000000000 --- a/include/soci/soci-config.h.in +++ /dev/null @@ -1,16 +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 SOCICONFIG_H_INCLUDED -#define SOCICONFIG_H_INCLUDED - -// -// SOCI has been build with support for: -// -@CONFIGURED_VARIABLES@ - -#endif // SOCICONFIG_H_INCLUDED diff --git a/include/soci/soci-platform.h b/include/soci/soci-platform.h index 3f94cb57b..3172584b4 100644 --- a/include/soci/soci-platform.h +++ b/include/soci/soci-platform.h @@ -21,7 +21,7 @@ #include #include -#include "soci/soci-config.h" // for SOCI_HAVE_CXX11 +#include "soci/soci-config.h" #if defined(_MSC_VER) #define LL_FMT_FLAGS "I64" @@ -169,6 +169,8 @@ private: \ // Here the problem is that MSVC complains about unreachable code in this case // (but only in release builds, where optimizations are enabled), while other // compilers complain about missing return statement without it. +// Note: this macro is no longer used in SOCI's codebase. It is only retained +// in case downstream code is depending on it. #if defined(_MSC_VER) && defined(NDEBUG) #define SOCI_DUMMY_RETURN(x) #else diff --git a/include/soci/version.h b/include/soci/version.h index b13caa3dd..1a3ac7315 100644 --- a/include/soci/version.h +++ b/include/soci/version.h @@ -10,10 +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 +// Caution, this is the only SOCI header that is guaranteed // to change with every SOCI release, including this header // will cause a recompile every time a new SOCI version is // released. diff --git a/scripts/ci/build_empty.sh b/scripts/ci/build_empty.sh index b27a31e08..e61302172 100755 --- a/scripts/ci/build_empty.sh +++ b/scripts/ci/build_empty.sh @@ -13,17 +13,23 @@ run_cmake_for_empty() .. } -run_cmake_for_example() +build_example() { - cmake "../examples/$1" + cmake -S "../examples/$1" -B "$1" + cmake --build "$1" } +run_cmake_for_empty +run_make + if [[ "$BUILD_EXAMPLES" == "YES" ]]; then - run_cmake_for_example subdir-include - run_make -else - run_cmake_for_empty - run_make + # Install previously built SOCI library on the system + sudo make install + + # This example simulates SOCI sources being embedded in the project dir + build_example subdir-include + # This example simulates SOCI being installed on the target system + build_example connect fi # Test release branch packaging and building from the package diff --git a/scripts/ci/build_odbc.sh b/scripts/ci/build_odbc.sh index 0d64965c4..66d1b1412 100755 --- a/scripts/ci/build_odbc.sh +++ b/scripts/ci/build_odbc.sh @@ -11,7 +11,9 @@ if test ! -d ${ODBC_TEST}; then exit 1 fi +# Disable ASAN -> see https://github.com/SOCI/soci/issues/1008 cmake ${SOCI_DEFAULT_CMAKE_OPTIONS} \ + -DSOCI_ASAN=OFF \ -DSOCI_ODBC=ON \ .. diff --git a/scripts/ci/build_oracle.sh b/scripts/ci/build_oracle.sh index a5e235fcc..95d537a3f 100755 --- a/scripts/ci/build_oracle.sh +++ b/scripts/ci/build_oracle.sh @@ -6,7 +6,6 @@ source ${SOCI_SOURCE_DIR}/scripts/ci/common.sh cmake ${SOCI_DEFAULT_CMAKE_OPTIONS} \ - -DWITH_BOOST=OFF \ -DSOCI_ORACLE=ON \ -DSOCI_ORACLE_TEST_CONNSTR:STRING="service=localhost/XE user=travis password=travis" \ .. diff --git a/scripts/ci/common.sh b/scripts/ci/common.sh index 19efd16cf..aa55ef266 100644 --- a/scripts/ci/common.sh +++ b/scripts/ci/common.sh @@ -51,16 +51,16 @@ SOCI_COMMON_CMAKE_OPTIONS=' -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE=ON -DSOCI_ENABLE_WERROR=ON - -DSOCI_STATIC=OFF -DSOCI_TESTS=ON + -DCMAKE_UNITY_BUILD=ON ' if [ -n "${SOCI_CXXSTD}" ]; then SOCI_COMMON_CMAKE_OPTIONS="$SOCI_COMMON_CMAKE_OPTIONS -DCMAKE_CXX_STANDARD=${SOCI_CXXSTD}" fi -if [ -n "${WITH_BOOST}" ]; then - SOCI_COMMON_CMAKE_OPTIONS="$SOCI_COMMON_CMAKE_OPTIONS -DWITH_BOOST=${WITH_BOOST}" +if [ -n "${SOCI_BUILD_STATIC}" ]; then + SOCI_COMMON_CMAKE_OPTIONS="${SOCI_COMMON_CMAKE_OPTIONS} -DSOCI_SHARED=OFF" fi # These options are defaults and used by most builds, but not Valgrind one. diff --git a/scripts/ci/test_odbc.sh b/scripts/ci/test_odbc.sh index ebc011a85..27fa608c1 100755 --- a/scripts/ci/test_odbc.sh +++ b/scripts/ci/test_odbc.sh @@ -8,4 +8,4 @@ source ${SOCI_SOURCE_DIR}/scripts/ci/common.sh # Exclude the tests which can't be run due to the absence of ODBC drivers (MS # SQL and MySQL). -LSAN_OPTIONS=suppressions=${SOCI_SOURCE_DIR}/scripts/suppress_odbc.txt run_test -E 'soci_odbc_test_m.sql' +LSAN_OPTIONS=suppressions=${SOCI_SOURCE_DIR}/scripts/suppress_odbc.txt run_test -E 'soci_odbc_m.sql_test' diff --git a/soci-config.cmake.in b/soci-config.cmake.in new file mode 100644 index 000000000..0aac3e20d --- /dev/null +++ b/soci-config.cmake.in @@ -0,0 +1,161 @@ +@PACKAGE_INIT@ + + +# Auto-generated stub to handle component-wise dependencies +set(__dep_soci_comps "@SOCI_DEPENDENCY_SOCI_COMPONENTS@") +set(__dep_names "@SOCI_DEPENDENCY_NAMES@") +set(__dep_dep_targets "@SOCI_DEPENDENCY_TARGETS@") +set(__dep_required "@SOCI_DEPENDENCY_REQUIRED@") +set(__dep_macro_names "@SOCI_DEPENDENCY_MACRO_NAMES@") +set(__dep_components "@SOCI_DEPENDENCY_COMPONENTS@") + + +set(__prev_module_path "${CMAKE_MODULE_PATH}") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/find_package_files/") + + +if (NOT DEFINED SOCI_FIND_COMPONENTS OR SOCI_FIND_COMPONENTS STREQUAL "") + # Use all available SOCI components + set(SOCI_FIND_COMPONENTS "${__dep_soci_comps}") + list(REMOVE_DUPLICATES SOCI_FIND_COMPONENTS) + list(TRANSFORM SOCI_FIND_COMPONENTS REPLACE "SOCI::" "") +endif() + +# Ensure that the Core target is always included (and as first component) +list(REMOVE_ITEM SOCI_FIND_COMPONENTS Core) +list(INSERT SOCI_FIND_COMPONENTS 0 Core) + + +list(LENGTH __dep_soci_comps __list_size) +foreach (__item IN ITEMS __dep_names __dep_dep_targets __dep_required __dep_macro_names __dep_components) + list(LENGTH ${__item} __current_size) + if (NOT (__list_size EQUAL __current_size)) + message(FATAL_ERROR "SociConfig is invalid -> dependency lists have different sizes") + endif() +endforeach() +unset(__current_size) + +math(EXPR __list_size "${__list_size} - 1") + +foreach(__comp IN LISTS SOCI_FIND_COMPONENTS) + if (NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/SOCI${__comp}Targets.cmake") + set(SOCI_FOUND FALSE) + set(SOCI_NOT_FOUND_MESSAGE "'${__comp}' is not a known SOCI component") + continue() + endif() + + # Handle component-specific dependencies + set(__link_targets) + set(__define_macros) + set(__skip_dependency FALSE) + foreach (__i RANGE ${__list_size}) + list(GET __dep_soci_comps ${__i} __dep_comp) + if (__dep_comp MATCHES "::${__comp}$") + # This entry matches the current component + list(GET __dep_names ${__i} __dep) + list(GET __dep_dep_targets ${__i} __targets) + list(GET __dep_required ${__i} __required) + list(GET __dep_macro_names ${__i} __macros) + list(GET __dep_components ${__i} __components) + + # Split list-valued entries to become actual lists + string(REPLACE "|" ";" __targets "${__targets}") + string(REPLACE "|" ";" __macros "${__macros}") + string(REPLACE "|" ";" __components "${__components}") + + set(__already_found) + foreach (__tgt IN LISTS __targets) + if (TARGET ${__tgt}) + set(__already_found ON) + else() + set(__already_found OFF) + break() + endif() + endforeach() + + if (__already_found) + continue() + endif() + + if (__components) + set(__components COMPONENTS ${__components}) + endif() + + if (${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + set(__quiet "QUIET") + else() + set(__quiet "") + endif() + + find_package( + ${__dep} + ${__components} + ${__quiet} + ) + + if (NOT ${__dep}_FOUND) + if (__required) + set(SOCI_FOUND FALSE) + set(SOCI_NOT_FOUND_MESSAGE "Unmet dependency '${__dep}' for SOCI component '${__comp}'") + set(__skip_dependency TRUE) + endif() + continue() + endif() + + list(APPEND __link_targets ${__targets}) + list(APPEND __define_macros ${__macros}) + endif() + endforeach() + unset(__i) + + if (__skip_dependency) + continue() + endif() + + include("${CMAKE_CURRENT_LIST_DIR}/SOCI${__comp}Targets.cmake") + + set_property( + TARGET SOCI::${__comp} + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES "${__link_targets}" + ) + if (__define_macros) + set_property( + TARGET SOCI::${__comp} + APPEND + PROPERTY INTERFACE_COMPILE_DEFINITIONS "${__define_macros}" + ) + endif() + + set(${CMAKE_FIND_PACKAGE_NAME}_${__comp}_FOUND ON) +endforeach() +unset(__comp) + + +unset(__dep_soci_comps) +unset(__dep_names) +unset(__dep_dep_targets) +unset(__dep_required) +unset(__dep_macro_names) +unset(__dep_components) + + +check_required_components(SOCI) + +if (NOT DEFINED SOCI_FOUND OR SOCI_FOUND) + add_library(soci_interface INTERFACE) + + foreach (__comp IN LISTS SOCI_FIND_COMPONENTS) + target_link_libraries(soci_interface INTERFACE SOCI::${__comp}) + endforeach() + + add_library(SOCI::soci ALIAS soci_interface) + + if (NOT SOCI_FIND_QUIETLY) + list(JOIN SOCI_FIND_COMPONENTS ", " __components) + message(STATUS "Found SOCI: ${CMAKE_CURRENT_LIST_FILE} (found version \"@PROJECT_VERSION@\") found components: ${__components}") + endif() +endif() + +set(CMAKE_MODULE_PATH "${__prev_module_path}") +unset(__prev_module_path) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 472ae864c..4c0c4c865 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,14 +1,110 @@ -############################################################################### -# -# 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) -# -############################################################################### -include_directories(${SOCI_SOURCE_DIR}/include/private) +add_library(soci_interface INTERFACE) +add_library(SOCI::soci ALIAS soci_interface) + +set(SOCI_GENERATED_INCLUDES_DIR "${CMAKE_BINARY_DIR}/include") +file(MAKE_DIRECTORY "${SOCI_GENERATED_INCLUDES_DIR}/soci") add_subdirectory(core) add_subdirectory(backends) + + +############################################################################### +# platform-dependent settings +############################################################################### + +include(CheckTypeSize) +check_type_size(long SOCI_SIZEOF_LONG LANGUAGE CXX) + +soci_are_types_same(TYPES "int64_t" "long" OUTPUT_VARIABLE SOCI_INT64_T_IS_LONG) +soci_are_types_same(TYPES "int8_t" "char" OUTPUT_VARIABLE SOCI_INT8_T_IS_CHAR) + +include(CheckCXXSourceCompiles) +check_cxx_source_compiles( + " + __attribute__ (( visibility(\"default\") )) int f1() { return 0; } + __attribute__ (( visibility(\"hidden\") )) int f2() { return 1; } + + int main(int argc, char* argv[]) { f1(); f2(); return 0; } + " + SOCI_VISIBILITY_ATTRIBUTE_SUPPORTED +) + + +# Generate the soci-config.h file + +set(CONFIG_LINES "#ifndef SOCI_SOCICONFIG_H_INCLUDED" "#define SOCI_SOCICONFIG_H_INCLUDED" "") + +set(CONFIG_VARS + SOCI_EMPTY + SOCI_DB2 + SOCI_FIREBIRD + SOCI_MYSQL + SOCI_ODBC + SOCI_ORACLE + SOCI_POSTGRESQL + SOCI_SQLITE3 + SOCI_VISIBILITY_ATTRIBUTE_SUPPORTED + SOCI_SIZEOF_LONG + SOCI_INT64_T_IS_LONG + SOCI_INT8_T_IS_CHAR +) +set(CONFIG_MACROS + SOCI_HAVE_EMPTY + SOCI_HAVE_DB2 + SOCI_HAVE_FIREBIRD + SOCI_HAVE_MYSQL + SOCI_HAVE_ODBC + SOCI_HAVE_ORACLE + SOCI_HAVE_POSTGRESQL + SOCI_HAVE_SQLITE3 + SOCI_HAVE_VISIBILITY_SUPPORT + SOCI_SIZEOF_LONG + SOCI_INT64_T_IS_LONG + SOCI_INT8_T_IS_CHAR +) + +list(LENGTH CONFIG_MACROS N_CONFIGS) +math(EXPR LAST_CONFIG_IDX "${N_CONFIGS} - 1") + +foreach (I RANGE ${LAST_CONFIG_IDX}) + list(GET CONFIG_VARS ${I} CURRENT_VAR) + list(GET CONFIG_MACROS ${I} CURRENT_MACRO) + + if (${CURRENT_VAR}) + if ("${${CURRENT_VAR}}" MATCHES "[0-9]+") + list(APPEND CONFIG_LINES "#define ${CURRENT_MACRO} ${${CURRENT_VAR}}") + else() + # Assume this is a boolean flag + list(APPEND CONFIG_LINES "#define ${CURRENT_MACRO} 1") + endif() + else() + list(APPEND CONFIG_LINES "/* #define ${CURRENT_MACRO} */") + endif() + list(APPEND CONFIG_LINES "") +endforeach() + +list(APPEND CONFIG_LINES "#endif") + +list(JOIN CONFIG_LINES "\n" CONFIG_CONTENT) + +string(MD5 CONFIG_CONTENT_HASH "${CONFIG_CONTENT}") +if (EXISTS "${SOCI_GENERATED_INCLUDES_DIR}/soci/soci-config.h") + file(MD5 "${SOCI_GENERATED_INCLUDES_DIR}/soci/soci-config.h" CONFIG_FILE_HASH) +else() + set(CONFIG_FILE_HASH 0) +endif() + +# Only overwrite the soci-config.h file if the generated content is different from the +# file's content in order to avoid needless rebuilding +if (NOT (CONFIG_CONTENT_HASH STREQUAL CONFIG_FILE_HASH)) + file(WRITE "${SOCI_GENERATED_INCLUDES_DIR}/soci/soci-config.h" "${CONFIG_CONTENT}") +endif() + +# Append the generated config header to the public headers of the core target +target_sources(soci_core + PUBLIC + FILE_SET headers TYPE HEADERS + BASE_DIRS "${SOCI_GENERATED_INCLUDES_DIR}" + FILES + "${SOCI_GENERATED_INCLUDES_DIR}/soci/soci-config.h" +) diff --git a/src/backends/CMakeLists.txt b/src/backends/CMakeLists.txt index 871e151ef..950d89a01 100644 --- a/src/backends/CMakeLists.txt +++ b/src/backends/CMakeLists.txt @@ -1,50 +1,80 @@ -############################################################################### -# -# 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) -# -############################################################################### -colormsg(_HIBLUE_ "Configuring SOCI backend libraries:") - -# First, we'll investigate what can be found from database engines -foreach(dep ${SOCI_BACKENDS_DB_DEPENDENCIES}) - string(TOUPPER ${dep} depUP) - if (WITH_${depUP}) - find_package(${dep}) - endif() - if(${dep}_FOUND OR ${depUP}_FOUND) - set(${depUP}_FOUND ON) - else() - set(${depUP}_FOUND OFF) - endif() - set(SOCI_HAVE_${depUP} OFF CACHE INTERNAL "${depUP} backend") -endforeach() +set(SOCI_EXISTING_BACKENDS + "DB2" + "Empty" + "Firebird" + "MySQL" + "ODBC" + "Oracle" + "PostgreSQL" + "SQLite3" + CACHE INTERNAL "" FORCE +) +set(ENABLED_BACKENDS "") + +foreach(CURRENT_BACKEND IN LISTS SOCI_EXISTING_BACKENDS) + string(TOUPPER "${CURRENT_BACKEND}" CURRENT_BACKEND_UPPER) + string(TOLOWER "${CURRENT_BACKEND}" CURRENT_BACKEND_LOWER) + + set(CURRENT_ENABLED_VAR "SOCI_${CURRENT_BACKEND_UPPER}") + set( + CURRENT_DESCRIPTION + "Whether to include the '${CURRENT_BACKEND}' backend. Can be a boolean value or 'AUTO' indicating that it shall be included unless its dependencies can't be satisfied." + ) + + # Add option to enable/disable the current backend. Note: if the cache variable already exists + # (e.g. because it has been specified on the command line), this will NOT overwrite the + # explicitly specified value + set(${CURRENT_ENABLED_VAR} "AUTO" CACHE STRING "${CURRENT_DESCRIPTION}") + + + # Backwards compatibility with the old cmake setup that used WITH_* variables + # These are now only defined if the user specified them. + if (DEFINED WITH_${CURRENT_BACKEND_UPPER}) + message(DEPRECATION "Use of the WITH_${CURRENT_BACKEND_UPPER} option is deprecated - use the new ${CURRENT_ENABLED_VAR} instead") + if (WITH_${CURRENT_BACKEND_UPPER} AND ${CURRENT_ENABLED_VAR}) + # Overwrite the new-style enable-option unless that was set to disable the current backend + # (Note that non-empty string - in particular "AUTO" - also convert to a true expression). + # However, the legacy behavior was to only include the backend, if its dependencies are met, + # so we set to AUTO rather than ON. + set(${CURRENT_ENABLED_VAR} "AUTO" CACHE STRING "${CURRENT_DESCRIPTION}" FORCE) + endif() + # Delete the legacy variable from cache + unset(WITH_${CURRENT_BACKEND_UPPER} CACHE) + endif() + + + # Convert the value of the current backend's include flag to uppercase + string(TOUPPER "${${CURRENT_ENABLED_VAR}}" UPPER_VALUE) + set(${CURRENT_ENABLED_VAR} "${UPPER_VALUE}" CACHE STRING "${CURRENT_DESCRIPTION}" FORCE) -# get all files in backends -file(GLOB backend_dirs RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *) - -# empty backend always on by default -option(SOCI_EMPTY "Build empty backend" ON) -if(SOCI_EMPTY) - set(WITH_EMPTY ON) - set(EMPTY_FOUND ON) -endif() - -# enable only found backends -foreach(dir ${backend_dirs}) - if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${dir}) - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/CMakeLists.txt) - string(TOUPPER ${dir} dirUP) - if(${dirUP}_FOUND AND WITH_${dirUP}) - add_subdirectory(${dir}) - set(SOCI_HAVE_${dirUP} ON CACHE INTERNAL "${dirUP} backend") - endif() - endif() - endif() + + # Internal variable indicating whether the current backend's include mode is AUTO + set(CURRENT_AUTO_VAR "SOCI_${CURRENT_BACKEND_UPPER}_AUTO") + set(${CURRENT_AUTO_VAR} OFF) + + + if (${CURRENT_ENABLED_VAR} STREQUAL "AUTO") + set(${CURRENT_AUTO_VAR} ON) + # Convert include status to ON as the AUTO status is kept track of separately + set(${CURRENT_ENABLED_VAR} ON CACHE STRING "${CURRENT_DESCRIPTION}" FORCE) + elseif(${CURRENT_ENABLED_VAR} MATCHES "^(ON|OFF|TRUE|FALSE|YES|NO|[YN01])$") + set(INCLUDE_CURRENT_BACKEND ${${CURRENT_ENABLED_VAR}}) + else() + message(FATAL_ERROR "Invalid value '${${CURRENT_ENABLED_VAR}}' for option '${CURRENT_ENABLED_VAR}'") + endif() + + + if (${CURRENT_ENABLED_VAR}) + add_subdirectory(${CURRENT_BACKEND_LOWER}) + + # Verify that the backend hasn't been disabled (happens if include mode is AUTO and there are + # unmet dependencies. + if (${CURRENT_ENABLED_VAR}) + list(APPEND ENABLED_BACKENDS "${CURRENT_BACKEND}") + endif() + endif() endforeach() -message(STATUS "") +set(SOCI_ENABLED_BACKENDS "${ENABLED_BACKENDS}" CACHE INTERNAL "" FORCE) +message(STATUS "Enabled SOCI backends are: ${ENABLED_BACKENDS}") + diff --git a/src/backends/db2/CMakeLists.txt b/src/backends/db2/CMakeLists.txt index 7913d7920..ee1a07df1 100644 --- a/src/backends/db2/CMakeLists.txt +++ b/src/backends/db2/CMakeLists.txt @@ -1,14 +1,39 @@ -############################################################################### -# -# This file is part of CMake configuration for SOCI library -# -# Copyright (C) 2011 Denis Chapligin -# 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(DB2 - DEPENDS DB2 - DESCRIPTION "SOCI backend for DB2" AUTHORS "Denis Chapligin" - MAINTAINERS "Denis Chapligin") +include(soci_define_backend_target) + +if (SOCI_DB2_AUTO) + set(DEPENDENCY_MODE "DISABLE") +else() + set(DEPENDENCY_MODE "ERROR") +endif() + +soci_define_backend_target( + BACKEND_NAME "DB2" + TARGET_NAME "soci_db2" + ALIAS_NAME "DB2" + DEPENDENCIES + "DB2 YIELDS DB2::DB2" + REQUIRED_COMPONENTS SOCI::Core + SOURCE_FILES + "blob.cpp" + "factory.cpp" + "row-id.cpp" + "session.cpp" + "standard-into-type.cpp" + "standard-use-type.cpp" + "statement.cpp" + "vector-into-type.cpp" + "vector-use-type.cpp" + HEADER_FILES + "${PROJECT_SOURCE_DIR}/include/soci/db2/soci-db2.h" + PRIVATE_INCLUDE_DIRS + "${PROJECT_SOURCE_DIR}/include/private" + FIND_PACKAGE_FILES + "${PROJECT_SOURCE_DIR}/cmake/find_modules/FindDB2.cmake" + MISSING_DEPENDENCY_BEHAVIOR "${DEPENDENCY_MODE}" + ENABLED_VARIABLE "SOCI_DB2" +) + +if (NOT SOCI_DB2) + return() +endif() + diff --git a/src/backends/empty/CMakeLists.txt b/src/backends/empty/CMakeLists.txt index b09185b58..4a141fb8b 100644 --- a/src/backends/empty/CMakeLists.txt +++ b/src/backends/empty/CMakeLists.txt @@ -1,14 +1,30 @@ -############################################################################### -# -# 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(Empty - DESCRIPTION "SOCI backend skeleton for new backends development" - AUTHORS "Maciej Sobczak, Stephen Hutton" - MAINTAINERS "Maciej Sobczak") +include(soci_define_backend_target) + +if (SOCI_EMPTY_AUTO) + set(DEPENDENCY_MODE "DISABLE") +else() + set(DEPENDENCY_MODE "ERROR") +endif() + +soci_define_backend_target( + BACKEND_NAME "Empty" + TARGET_NAME "soci_empty" + ALIAS_NAME "Empty" + REQUIRED_COMPONENTS SOCI::Core + SOURCE_FILES + "blob.cpp" + "factory.cpp" + "row-id.cpp" + "session.cpp" + "standard-into-type.cpp" + "standard-use-type.cpp" + "statement.cpp" + "vector-into-type.cpp" + "vector-use-type.cpp" + MISSING_DEPENDENCY_BEHAVIOR "${DEPENDENCY_MODE}" + ENABLED_VARIABLE "SOCI_EMPTY" +) + +if (NOT SOCI_EMPTY) + return() +endif() diff --git a/src/backends/empty/Makefile.basic b/src/backends/empty/Makefile.basic deleted file mode 100644 index 7fe0f9107..000000000 --- a/src/backends/empty/Makefile.basic +++ /dev/null @@ -1,88 +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. - -EMPTYINCLUDEDIR = - -# 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 ${EMPTYINCLUDEDIR} - - -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 - -OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ - standard-into-type-s.o standard-use-type-s.o statement-s.o \ - vector-into-type-s.o vector-use-type-s.o - - -libsoci_empty.a : ${OBJECTS} - ar rv $@ $? - rm *.o - - -blob.o : blob.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -factory.o : factory.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -row-id.o : row-id.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -session.o : session.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-into-type.o : standard-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-use-type.o : standard-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -statement.o : statement.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-into-type.o : vector-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-use-type.o : vector-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - - -shared : ${OBJECTSSO} - ${COMPILER} -shared -o libsoci_empty.so ${OBJECTSSO} - rm *.o - -blob-s.o : blob.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -factory-s.o : factory.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -row-id-s.o : row-id.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -session-s.o : session.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-into-type-s.o : standard-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-use-type-s.o : standard-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -statement-s.o : statement.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-into-type-s.o : vector-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-use-type-s.o : vector-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - - -clean : - rm -f libsoci_empty.a libsoci_empty.so diff --git a/src/backends/firebird/CMakeLists.txt b/src/backends/firebird/CMakeLists.txt index cbf702813..6076cb643 100644 --- a/src/backends/firebird/CMakeLists.txt +++ b/src/backends/firebird/CMakeLists.txt @@ -1,16 +1,45 @@ -############################################################################### -# -# This file is part of CMake configuration for SOCI library -# -# Copyright (C) 2013 Viacheslav Naydenov -# 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_define_backend_target) + +option(SOCI_FIREBIRD_EMBEDDED "Use the embedded version of Firebird" OFF) +if (SOCI_FIREBIRD_EMBEDDED) + set(Firebird_SEARCH_EMBEDDED ON) +endif() + +if (SOCI_FIREBIRD_AUTO) + set(DEPENDENCY_MODE "DISABLE") +else() + set(DEPENDENCY_MODE "ERROR") +endif() + +soci_define_backend_target( + BACKEND_NAME "Firebird" + TARGET_NAME "soci_firebird" + ALIAS_NAME "Firebird" + DEPENDENCIES + "Firebird YIELDS Firebird::Firebird" + REQUIRED_COMPONENTS SOCI::Core + SOURCE_FILES + "blob.cpp" + "common.cpp" + "error-firebird.cpp" + "factory.cpp" + "session.cpp" + "standard-into-type.cpp" + "standard-use-type.cpp" + "statement.cpp" + "vector-into-type.cpp" + "vector-use-type.cpp" + HEADER_FILES + "${PROJECT_SOURCE_DIR}/include/soci/firebird/soci-firebird.h" + PRIVATE_INCLUDE_DIRS + "${PROJECT_SOURCE_DIR}/include/private" + FIND_PACKAGE_FILES + "${PROJECT_SOURCE_DIR}/cmake/find_modules/FindFirebird.cmake" + MISSING_DEPENDENCY_BEHAVIOR "${DEPENDENCY_MODE}" + ENABLED_VARIABLE "SOCI_FIREBIRD" +) + +if (NOT SOCI_FIREBIRD) + return() +endif() -soci_backend(Firebird - DEPENDS Firebird - DESCRIPTION "SOCI backend for Firebird" - AUTHORS "Rafał Bobrowski" - MAINTAINERS "Viacheslav Naydenov") diff --git a/src/backends/firebird/Makefile.basic b/src/backends/firebird/Makefile.basic deleted file mode 100644 index 5c0ffd15c..000000000 --- a/src/backends/firebird/Makefile.basic +++ /dev/null @@ -1,101 +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 - -# 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 ${FIREBIRDINCLUDEDIR} - -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 \ - error-firebird.o common.o - -OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ - standard-into-type-s.o standard-use-type-s.o statement-s.o \ - vector-into-type-s.o vector-use-type-s.o error-firebird-s.o common-s.o - -FIREBIRDLIBS = -lfbclient -lpthread - -libsoci_firebird.a : ${OBJECTS} - ar rv $@ $? - rm *.o - -soci-firebird.o : soci-firebird.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -blob.o : blob.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -error-firebird.o : error-firebird.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -common.o : common.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -factory.o : factory.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -row-id.o : row-id.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -session.o : session.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-into-type.o : standard-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-use-type.o : standard-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -statement.o : statement.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-into-type.o : vector-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-use-type.o : vector-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -shared : ${OBJECTSSO} - ${COMPILER} -shared -o libsoci_firebird.so ${OBJECTSSO} ${FIREBIRDLIBS} - rm *.o - -blob-s.o : blob.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -error-firebird-s.o : error-firebird.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -common-s.o : common.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -factory-s.o : factory.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -row-id-s.o : row-id.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -session-s.o : session.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-into-type-s.o : standard-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-use-type-s.o : standard-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -statement-s.o : statement.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-into-type-s.o : vector-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-use-type-s.o : vector-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -clean : - rm -f *.o libsoci_firebird.a libsoci_firebird.so diff --git a/src/backends/mysql/CMakeLists.txt b/src/backends/mysql/CMakeLists.txt index b2dcc9ceb..d60280730 100644 --- a/src/backends/mysql/CMakeLists.txt +++ b/src/backends/mysql/CMakeLists.txt @@ -1,15 +1,39 @@ -############################################################################### -# -# 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(MySQL - DEPENDS MySQL - DESCRIPTION "SOCI backend for MySQL" - AUTHORS "Pawel Aleksander Fedorynski" - MAINTAINERS "Pawel Aleksander Fedorynski") +include(soci_define_backend_target) + +if (SOCI_MYSQL_AUTO) + set(DEPENDENCY_MODE "DISABLE") +else() + set(DEPENDENCY_MODE "ERROR") +endif() + +soci_define_backend_target( + BACKEND_NAME "MySQL" + TARGET_NAME "soci_mysql" + ALIAS_NAME "MySQL" + DEPENDENCIES + "MySQL YIELDS MySQL::MySQL" + REQUIRED_COMPONENTS SOCI::Core + SOURCE_FILES + "blob.cpp" + "common.cpp" + "factory.cpp" + "row-id.cpp" + "session.cpp" + "standard-into-type.cpp" + "standard-use-type.cpp" + "statement.cpp" + "vector-into-type.cpp" + "vector-use-type.cpp" + HEADER_FILES + "${PROJECT_SOURCE_DIR}/include/soci/mysql/soci-mysql.h" + PRIVATE_INCLUDE_DIRS + "${PROJECT_SOURCE_DIR}/include/private" + FIND_PACKAGE_FILES + "${PROJECT_SOURCE_DIR}/cmake/find_modules/FindMySQL.cmake" + MISSING_DEPENDENCY_BEHAVIOR "${DEPENDENCY_MODE}" + ENABLED_VARIABLE "SOCI_MYSQL" +) + +if (NOT SOCI_MYSQL) + return() +endif() diff --git a/src/backends/mysql/Makefile.basic b/src/backends/mysql/Makefile.basic deleted file mode 100644 index 0073fadd3..000000000 --- a/src/backends/mysql/Makefile.basic +++ /dev/null @@ -1,97 +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 -CXXFLAGSSO = ${CXXFLAGS} -fPIC -INCLUDEDIRS = -I../../core ${MYSQLINCLUDEDIR} - - -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 \ - common.o - - -OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ - standard-into-type-s.o standard-use-type-s.o statement-s.o \ - vector-into-type-s.o vector-use-type-s.o common-s.o - - -libsoci_mysql.a : ${OBJECTS} - ar rv $@ $? - rm *.o - - -blob.o : blob.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -common.o : common.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -factory.o : factory.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -row-id.o : row-id.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -session.o : session.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-into-type.o : standard-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-use-type.o : standard-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -statement.o : statement.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-into-type.o : vector-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-use-type.o : vector-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -shared : ${OBJECTSSO} - ${COMPILER} -shared -o libsoci_mysql.so ${OBJECTSSO} - rm *.o - -blob-s.o : blob.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -common-s.o : common.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -factory-s.o : factory.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -row-id-s.o : row-id.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -session-s.o : session.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-into-type-s.o : standard-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-use-type-s.o : standard-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -statement-s.o : statement.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-into-type-s.o : vector-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-use-type-s.o : vector-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - - -clean : - rm -f libsoci_mysql.a libsoci_mysql.so diff --git a/src/backends/odbc/CMakeLists.txt b/src/backends/odbc/CMakeLists.txt index ce0195393..273c383f2 100644 --- a/src/backends/odbc/CMakeLists.txt +++ b/src/backends/odbc/CMakeLists.txt @@ -1,15 +1,36 @@ -############################################################################### -# -# 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(ODBC - DEPENDS ODBC - DESCRIPTION "SOCI backend for ODBC" - AUTHORS "Maciej Sobczak, Stephen Hutton, David Courtney" - MAINTAINERS "Vadim Zeitlin, Mateusz Loskot, Maciej Sobczak") +include(soci_define_backend_target) + +if (SOCI_ODBC_AUTO) + set(DEPENDENCY_MODE "DISABLE") +else() + set(DEPENDENCY_MODE "ERROR") +endif() + +soci_define_backend_target( + BACKEND_NAME "ODBC" + TARGET_NAME "soci_odbc" + ALIAS_NAME "ODBC" + DEPENDENCIES + "ODBC YIELDS ODBC::ODBC" + REQUIRED_COMPONENTS SOCI::Core + SOURCE_FILES + "blob.cpp" + "factory.cpp" + "row-id.cpp" + "session.cpp" + "standard-into-type.cpp" + "standard-use-type.cpp" + "statement.cpp" + "vector-into-type.cpp" + "vector-use-type.cpp" + HEADER_FILES + "${PROJECT_SOURCE_DIR}/include/soci/odbc/soci-odbc.h" + PRIVATE_INCLUDE_DIRS + "${PROJECT_SOURCE_DIR}/include/private" + MISSING_DEPENDENCY_BEHAVIOR "${DEPENDENCY_MODE}" + ENABLED_VARIABLE "SOCI_ODBC" +) + +if (NOT SOCI_ODBC) + return() +endif() diff --git a/src/backends/odbc/Makefile.basic b/src/backends/odbc/Makefile.basic deleted file mode 100644 index 5082f914f..000000000 --- a/src/backends/odbc/Makefile.basic +++ /dev/null @@ -1,89 +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 - -# 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 ${ODBCINCLUDEDIR} - - -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 - -OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ - standard-into-type-s.o standard-use-type-s.o statement-s.o \ - vector-into-type-s.o vector-use-type-s.o - - -libsoci_odbc.a : ${OBJECTS} - ar rv $@ $? - ranlib $@ - rm *.o - - -blob.o : blob.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -factory.o : factory.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -row-id.o : row-id.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -session.o : session.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-into-type.o : standard-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-use-type.o : standard-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -statement.o : statement.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-into-type.o : vector-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-use-type.o : vector-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - - -shared : ${OBJECTSSO} - ${COMPILER} -shared -o libsoci_odbc.so ${OBJECTSSO} - rm *.o - -blob-s.o : blob.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -factory-s.o : factory.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -row-id-s.o : row-id.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -session-s.o : session.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-into-type-s.o : standard-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-use-type-s.o : standard-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -statement-s.o : statement.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-into-type-s.o : vector-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-use-type-s.o : vector-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - - -clean : - rm -f libsoci_odbc.a libsoci_odbc.so diff --git a/src/backends/odbc/makefile.msvc b/src/backends/odbc/makefile.msvc deleted file mode 100644 index dcc5a96be..000000000 --- a/src/backends/odbc/makefile.msvc +++ /dev/null @@ -1,48 +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="C:\Program Files\Microsoft Platform SDK\Include" - -# The rest of the Makefile is indepentent of the target environment. - -COMPILER = cl -CXXFLAGS = /nologo /EHsc /D_CRT_SECURE_NO_DEPRECATE -INCLUDEDIRS = /I..\..\core /I$(ODBCINCLUDEDIR) - -OBJECTS = blob.obj factory.obj row-id.obj session.obj standard-into-type.obj \ - standard-use-type.obj statement.obj vector-into-type.obj \ - vector-use-type.obj - -soci-odbc.lib : $(OBJECTS) - lib /NOLOGO /OUT:$@ $? - del *.obj - -blob.obj : blob.cpp - $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) - -factory.obj : factory.cpp - $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) - -row-id.obj : row-id.cpp - $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) - -session.obj : session.cpp - $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) - -standard-into-type.obj : standard-into-type.cpp - $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) - -standard-use-type.obj : standard-use-type.cpp - $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) - -statement.obj : statement.cpp - $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) - -vector-into-type.obj : vector-into-type.cpp - $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) - -vector-use-type.obj : vector-use-type.cpp - $(COMPILER) /c $? $(CXXFLAGS) $(INCLUDEDIRS) - -clean : - del soci-odbc.lib soci-odbc.dll diff --git a/src/backends/oracle/CMakeLists.txt b/src/backends/oracle/CMakeLists.txt index 632ca6179..13d930be7 100644 --- a/src/backends/oracle/CMakeLists.txt +++ b/src/backends/oracle/CMakeLists.txt @@ -1,15 +1,39 @@ -############################################################################### -# -# 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(Oracle - DEPENDS Oracle - DESCRIPTION "SOCI backend for Oracle 10+" - AUTHORS "Maciej Sobczak, Stephen Hutton" - MAINTAINERS "Maciej Sobczak") +include(soci_define_backend_target) + +if (SOCI_ORACLE_AUTO) + set(DEPENDENCY_MODE "DISABLE") +else() + set(DEPENDENCY_MODE "ERROR") +endif() + +soci_define_backend_target( + BACKEND_NAME "Oracle" + TARGET_NAME "soci_oracle" + ALIAS_NAME "Oracle" + DEPENDENCIES + "Oracle YIELDS Oracle::Oracle" + REQUIRED_COMPONENTS SOCI::Core + SOURCE_FILES + "blob.cpp" + "error.cpp" + "factory.cpp" + "row-id.cpp" + "session.cpp" + "standard-into-type.cpp" + "standard-use-type.cpp" + "statement.cpp" + "vector-into-type.cpp" + "vector-use-type.cpp" + HEADER_FILES + "${PROJECT_SOURCE_DIR}/include/soci/oracle/soci-oracle.h" + PRIVATE_INCLUDE_DIRS + "${PROJECT_SOURCE_DIR}/include/private" + FIND_PACKAGE_FILES + "${PROJECT_SOURCE_DIR}/cmake/find_modules/FindOracle.cmake" + MISSING_DEPENDENCY_BEHAVIOR "${DEPENDENCY_MODE}" + ENABLED_VARIABLE "SOCI_ORACLE" +) + +if (NOT SOCI_ORACLE) + return() +endif() diff --git a/src/backends/oracle/Makefile.basic b/src/backends/oracle/Makefile.basic deleted file mode 100644 index 31b861f5e..000000000 --- a/src/backends/oracle/Makefile.basic +++ /dev/null @@ -1,95 +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${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../../../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 \ - error.o - -OBJECTSSO = blob-s.o factory-s.o row-id-s.o session-s.o \ - standard-into-type-s.o standard-use-type-s.o statement-s.o \ - vector-into-type-s.o vector-use-type-s.o error-s.o - -libsoci_oracle.a : ${OBJECTS} - ar rv $@ $? - rm *.o - -soci-oracle.o : soci-oracle.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -blob.o : blob.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -error.o : error.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -factory.o : factory.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -row-id.o : row-id.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -session.o : session.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-into-type.o : standard-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-use-type.o : standard-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -statement.o : statement.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-into-type.o : vector-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-use-type.o : vector-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - - -shared : ${OBJECTSSO} - ${COMPILER} -shared -o libsoci_oracle.so ${OBJECTSSO} - rm *.o - -blob-s.o : blob.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -error-s.o : error.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -factory-s.o : factory.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -row-id-s.o : row-id.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -session-s.o : session.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-into-type-s.o : standard-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -standard-use-type-s.o : standard-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -statement-s.o : statement.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-into-type-s.o : vector-into-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - -vector-use-type-s.o : vector-use-type.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGSSO} ${INCLUDEDIRS} - - -clean : - rm -f *.o libsoci_oracle.a libsoci_oracle.so diff --git a/src/backends/postgresql/CMakeLists.txt b/src/backends/postgresql/CMakeLists.txt index 3debd3aa0..08c2bd7d1 100644 --- a/src/backends/postgresql/CMakeLists.txt +++ b/src/backends/postgresql/CMakeLists.txt @@ -1,32 +1,49 @@ -############################################################################### -# -# 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) -# -############################################################################### +include(soci_define_backend_target) -include(CMakeDependentOption) +if (SOCI_POSTGRESQL_AUTO) + set(DEPENDENCY_MODE "DISABLE") +else() + set(DEPENDENCY_MODE "ERROR") +endif() + +soci_define_backend_target( + BACKEND_NAME "PostgreSQL" + TARGET_NAME "soci_postgresql" + ALIAS_NAME "PostgreSQL" + DEPENDENCIES + "PostgreSQL YIELDS PostgreSQL::PostgreSQL" + REQUIRED_COMPONENTS SOCI::Core + SOURCE_FILES + "blob.cpp" + "error.cpp" + "factory.cpp" + "row-id.cpp" + "session.cpp" + "standard-into-type.cpp" + "standard-use-type.cpp" + "statement.cpp" + "vector-into-type.cpp" + "vector-use-type.cpp" + HEADER_FILES + "${PROJECT_SOURCE_DIR}/include/soci/postgresql/soci-postgresql.h" + PRIVATE_INCLUDE_DIRS + "${PROJECT_SOURCE_DIR}/include/private" + MISSING_DEPENDENCY_BEHAVIOR "${DEPENDENCY_MODE}" + ENABLED_VARIABLE "SOCI_POSTGRESQL" +) + +if (NOT SOCI_POSTGRESQL) + return() +endif() option(SOCI_POSTGRESQL_NO_LO64 "Do not use lo_xxx64() functions for compatibility with PostgreSQL < 9.3" - OFF) - + OFF +) if (POSTGRESQL_VERSION VERSION_LESS "9.3.0") set(SOCI_POSTGRESQL_NO_LO64 ON CACHE BOOL "Avoid using lo_xxx64() functions" FORCE) endif() -if(SOCI_POSTGRESQL_NO_LO64) - add_definitions(-DSOCI_POSTGRESQL_NO_LO64=1) +if (SOCI_POSTGRESQL_NO_LO64) + target_compile_definitions(soci_postgresql INTERFACE SOCI_POSTGRESQL_NO_LO64) endif() - -soci_backend(PostgreSQL - DEPENDS PostgreSQL - DESCRIPTION "SOCI backend for PostgreSQL" - AUTHORS "Maciej Sobczak, Stephen Hutton" - MAINTAINERS "Mateusz Loskot") - -boost_report_value(SOCI_POSTGRESQL_NO_LO64) diff --git a/src/backends/postgresql/Makefile.basic b/src/backends/postgresql/Makefile.basic deleted file mode 100644 index 473abfffd..000000000 --- a/src/backends/postgresql/Makefile.basic +++ /dev/null @@ -1,114 +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/postgresql -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 -SHARED_CXXFLAGS = ${CXXFLAGS} -fPIC -INCLUDEDIRS = -I../../../include -I../../../include/private ${PGSQLINCLUDEDIR} - -SHARED_LIBDIRS = ${PGSQLLIBDIR} -SHARED_LIBS = ${PGSQLLIBS} ../../core/libsoci_core.a - -UNAME = $(shell uname) -ifeq ($(UNAME),Darwin) - SHARED_LINK_FLAGS = -dynamiclib -flat_namespace -undefined suppress -else - SHARED_LINK_FLAGS = -shared -endif - - -OBJECTS = blob.o error.o factory.o row-id.o session.o standard-into-type.o \ - standard-use-type.o statement.o vector-into-type.o vector-use-type.o \ - common.o - -SHARED_OBJECTS = blob-s.o error-s.o factory-s.o row-id-s.o session-s.o \ - standard-into-type-s.o standard-use-type-s.o statement-s.o \ - vector-into-type-s.o vector-use-type-s.o common-s.o - - -libsoci_postgresql.a : ${OBJECTS} - ar rv $@ $? - rm *.o - - -blob.o : blob.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -error.o : error.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -common.o : ../../core/common.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -factory.o : factory.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -row-id.o : row-id.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -session.o : session.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-into-type.o : standard-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-use-type.o : standard-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -statement.o : statement.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-into-type.o : vector-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-use-type.o : vector-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - - -shared : ${SHARED_OBJECTS} - ${COMPILER} ${SHARED_LINK_FLAGS} -o libsoci_postgresql.so \ - ${SHARED_OBJECTS} ${SHARED_LIBDIRS} ${SHARED_LIBS} - rm *.o - -blob-s.o : blob.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -error-s.o : error.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} - -common-s.o : ../../core/common.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -factory-s.o : factory.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -row-id-s.o : row-id.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -session-s.o : session.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -standard-into-type-s.o : standard-into-type.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -standard-use-type-s.o : standard-use-type.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -statement-s.o : statement.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -vector-into-type-s.o : vector-into-type.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -vector-use-type-s.o : vector-use-type.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - - -clean : - rm -f libsoci_postgresql.a libsoci_postgresql.so diff --git a/src/backends/sqlite3/CMakeLists.txt b/src/backends/sqlite3/CMakeLists.txt index cd90b7ae2..1bb28fb2a 100644 --- a/src/backends/sqlite3/CMakeLists.txt +++ b/src/backends/sqlite3/CMakeLists.txt @@ -1,16 +1,37 @@ -############################################################################### -# -# 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) -# -############################################################################### +include(soci_define_backend_target) -soci_backend(SQLite3 - DEPENDS SQLite3 - DESCRIPTION "SOCI backend for SQLite 3" - AUTHORS "Maciej Sobczak, Stephen Hutton, David Courtney" - MAINTAINERS "Maciej Sobczak, Mateusz Loskot") +if (SOCI_SQLITE3_AUTO) + set(DEPENDENCY_MODE "DISABLE") +else() + set(DEPENDENCY_MODE "ERROR") +endif() + +soci_define_backend_target( + BACKEND_NAME "SQLite3" + TARGET_NAME "soci_sqlite3" + ALIAS_NAME "SQLite3" + DEPENDENCIES + "SQLite3 YIELDS SQLite::SQLite3" + REQUIRED_COMPONENTS SOCI::Core + SOURCE_FILES + "blob.cpp" + "error.cpp" + "factory.cpp" + "row-id.cpp" + "session.cpp" + "standard-into-type.cpp" + "standard-use-type.cpp" + "statement.cpp" + "vector-into-type.cpp" + "vector-use-type.cpp" + HEADER_FILES + "${PROJECT_SOURCE_DIR}/include/soci/sqlite3/soci-sqlite3.h" + PRIVATE_INCLUDE_DIRS + "${PROJECT_SOURCE_DIR}/include/private" + MISSING_DEPENDENCY_BEHAVIOR "${DEPENDENCY_MODE}" + ENABLED_VARIABLE "SOCI_SQLITE3" +) + +if (NOT SOCI_SQLITE3) + return() +endif() diff --git a/src/backends/sqlite3/Makefile.basic b/src/backends/sqlite3/Makefile.basic deleted file mode 100644 index 6594dd31e..000000000 --- a/src/backends/sqlite3/Makefile.basic +++ /dev/null @@ -1,114 +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. - -SQLITE3INCLUDEDIR = -I/usr/include -SQLITE3LIBDIR = -L/usr/lib -SQLITE3LIBS = -lsqlite3 - -# The rest of the Makefile is indepentent of the target environment. - -COMPILER = g++ -CXXFLAGS = -Wall -pedantic -Wno-long-long -SHARED_CXXFLAGS = ${CXXFLAGS} -fPIC -INCLUDEDIRS = -I../../../include -I../../../include/private ${SQLITE3INCLUDEDIR} - -SHARED_LIBDIRS = ${SQLITE3LIBDIR} -SHARED_LIBS = ${SQLITE3LIBS} ../../core/libsoci_core.a - -UNAME = $(shell uname) -ifeq ($(UNAME),Darwin) - SHARED_LINK_FLAGS = -dynamiclib -flat_namespace -undefined suppress -else - SHARED_LINK_FLAGS = -shared -endif - - -OBJECTS = blob.o error.o factory.o row-id.o session.o standard-into-type.o \ - standard-use-type.o statement.o vector-into-type.o vector-use-type.o \ - common.o - -SHARED_OBJECTS = blob-s.o factory-s.o row-id-s.o session-s.o \ - standard-into-type-s.o standard-use-type-s.o statement-s.o \ - vector-into-type-s.o vector-use-type-s.o common-s.o - - -libsoci_sqlite3.a : ${OBJECTS} - ar rv $@ $? - rm *.o - - -blob.o : blob.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -error.o : error.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -common.o : common.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -factory.o : factory.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -row-id.o : row-id.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -session.o : session.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-into-type.o : standard-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -standard-use-type.o : standard-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -statement.o : statement.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-into-type.o : vector-into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -vector-use-type.o : vector-use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - - -shared : ${SHARED_OBJECTS} - ${COMPILER} ${SHARED_LINK_FLAGS} -o libsoci_sqlite3.so \ - ${SHARED_OBJECTS} ${SHARED_LIBDIRS} ${SHARED_LIBS} - rm *.o - -blob-s.o : blob.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -error-s.o : error.cpp - ${COMPILER} -c -o $@ $? ${CXXFLAGS} ${INCLUDEDIRS} - -common-s.o : common.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -factory-s.o : factory.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -row-id-s.o : row-id.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -session-s.o : session.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -standard-into-type-s.o : standard-into-type.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -standard-use-type-s.o : standard-use-type.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -statement-s.o : statement.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -vector-into-type-s.o : vector-into-type.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - -vector-use-type-s.o : vector-use-type.cpp - ${COMPILER} -c -o $@ $? ${SHARED_CXXFLAGS} ${INCLUDEDIRS} - - -clean : - rm -f libsoci_sqlite3.a libsoci_sqlite3.so diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0158da227..3fb2baafe 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,151 +1,134 @@ -############################################################################### -# -# 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) -# -############################################################################### -colormsg(_HIBLUE_ "Configuring SOCI core library:") +include(GNUInstallDirs) + +find_package(Threads REQUIRED) + +add_library(soci_core + ${SOCI_LIB_TYPE} + "backend-loader.cpp" + "blob.cpp" + "common.cpp" + "callbacks.cpp" + "connection-parameters.cpp" + "connection-pool.cpp" + "error.cpp" + "into-type.cpp" + "logger.cpp" + "once-temp-type.cpp" + "prepare-temp-type.cpp" + "procedure.cpp" + "ref-counted-prepare-info.cpp" + "ref-counted-statement.cpp" + "row.cpp" + "rowid.cpp" + "session.cpp" + "soci-simple.cpp" + "statement.cpp" + "transaction.cpp" + "unicode.cpp" + "use-type.cpp" + "values.cpp" +) + +add_library(SOCI::Core ALIAS soci_core) + +set_target_properties( + soci_core PROPERTIES + SOVERSION ${PROJECT_VERSION_MAJOR} + VERSION ${PROJECT_VERSION} + EXPORT_NAME Core +) + +# We have to explicitly list all public header files in order for them to get +# installed properly. This will automatically set the BASE_DIR up as an include +# directory for the target wrapped in a BUILD_INTERFACE generator expression. +# Note that we only add the general, public SOCI headers here. Backend-specific +# headers are added by the respective backend target. +file(GLOB SOCI_HEADER_FILES LIST_DIRECTORIES false CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/include/soci/*.h*") +target_sources(soci_core + PUBLIC + FILE_SET headers TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/include/" + FILES "${SOCI_HEADER_FILES}" +) + +target_include_directories(soci_core + PUBLIC + "$" + PRIVATE + "${PROJECT_SOURCE_DIR}/include/soci" + "${PROJECT_SOURCE_DIR}/include/private" +) -include(CMakePackageConfigHelpers) - -# 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}") - -# 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 ${SOCI_SOURCE_DIR}/include/soci/*.h) -file(GLOB SOCI_CORE_SOURCES *.cpp) - -# Group source files for IDE source explorers (e.g. Visual Studio) -source_group("Header Files" FILES ${SOCI_CORE_HEADERS}) -source_group("Source Files" FILES ${SOCI_CORE_SOURCES}) -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) - -# -# Core shared library -# if (SOCI_SHARED) - add_library(${SOCI_CORE_TARGET} SHARED ${SOCI_CORE_HEADERS} ${SOCI_CORE_SOURCES}) - add_library(Soci::core ALIAS ${SOCI_CORE_TARGET}) - - target_link_libraries(${SOCI_CORE_TARGET} ${SOCI_CORE_DEPS_LIBS}) - - if(WIN32) - set_target_properties(${SOCI_CORE_TARGET} - PROPERTIES - DEFINE_SYMBOL SOCI_DLL - OUTPUT_NAME "${SOCI_CORE_TARGET_OUTPUT_NAME}" - VERSION ${SOCI_VERSION} - CLEAN_DIRECT_OUTPUT 1) - else() - set_target_properties(${SOCI_CORE_TARGET} - PROPERTIES - VERSION ${SOCI_VERSION} - SOVERSION ${SOCI_SOVERSION} - INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib - CLEAN_DIRECT_OUTPUT 1) - endif() - - target_include_directories(${SOCI_CORE_TARGET} - PUBLIC - $ - $ - $ - ) - -endif() - -# This adds definitions to all build configurations. SOCI_DEBUG_POSTFIX is passed to soci library -add_definitions(-DSOCI_LIB_PREFIX="${CMAKE_SHARED_LIBRARY_PREFIX}soci_" - -DSOCI_LIB_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}" - -DSOCI_DEBUG_POSTFIX="${CMAKE_DEBUG_POSTFIX}") - -# -# Core static library -# -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_static ALIAS ${SOCI_CORE_TARGET_STATIC}) - - # we still need to link against dl if we have it - target_link_libraries (${SOCI_CORE_TARGET_STATIC} - ${SOCI_CORE_DEPS_LIBS} + target_compile_definitions(soci_core + PUBLIC + # Define the macro SOCI_DLL on Windows + $,SOCI_DLL,> ) - - set_target_properties(${SOCI_CORE_TARGET_STATIC} - PROPERTIES - OUTPUT_NAME ${SOCI_CORE_TARGET_OUTPUT_NAME} - PREFIX "lib" - CLEAN_DIRECT_OUTPUT 1) - - target_include_directories(${SOCI_CORE_TARGET_STATIC} - PUBLIC - $ - $ - $ - ) - endif() - - - -# -# Core installation -# -install(FILES ${SOCI_CORE_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECTNAMEL}) - -if (SOCI_SHARED) - install(TARGETS ${SOCI_CORE_TARGET} - EXPORT SOCI - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +soci_public_dependency( + NAME Boost + DEP_TARGETS Boost::boost Boost::date_time Boost::disable_autolinking + COMPONENTS date_time + MACRO_NAMES SOCI_HAVE_BOOST SOCI_HAVE_BOOST_DATE_TIME + TARGET SOCI::Core +) +soci_public_dependency( + NAME Boost + DEP_TARGETS Boost::boost Boost::disable_autolinking + MACRO_NAMES SOCI_HAVE_BOOST + TARGET SOCI::Core +) + +target_link_libraries(soci_core + PUBLIC + $ + PRIVATE + Threads::Threads + ${CMAKE_DL_LIBS} +) + +if (WIN32) + set(ABI_VERSION "${PROJECT_VERSION_MAJOR}_${PROJECT_VERSION_MINOR}") +elseif(UNIX) + # Use SOVERSION, which is only the major version + set(ABI_VERSION "${PROJECT_VERSION_MAJOR}") endif() -if (SOCI_STATIC) - install(TARGETS ${SOCI_CORE_TARGET_STATIC} - EXPORT SOCI - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +target_compile_definitions(soci_core + PRIVATE + DEFAULT_BACKENDS_PATH="${CMAKE_INSTALL_FULL_LIBDIR}" + SOCI_LIB_PREFIX="${CMAKE_SHARED_LIBRARY_PREFIX}soci_" + SOCI_LIB_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}" + SOCI_DEBUG_POSTFIX="${CMAKE_DEBUG_POSTFIX}" +) +if (DEFINED ABI_VERSION) + target_compile_definitions(soci_core + PRIVATE + SOCI_ABI_VERSION="${ABI_VERSION}" + ) endif() -install(EXPORT SOCI NAMESPACE SOCI:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SOCI FILE SOCITargets.cmake) -configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/resources/SOCIConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/SOCIConfig.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SOCI) -write_basic_package_version_file(SOCIConfigVersion.cmake VERSION ${SOCI_VERSION} COMPATIBILITY SameMajorVersion) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/SOCIConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/SOCIConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SOCI) - -# -# Core configuration summary -# -boost_report_value(SOCI_CORE_TARGET) -boost_report_value(SOCI_CORE_TARGET_OUTPUT_NAME) -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) -message(STATUS "") +install( + TARGETS soci_core + EXPORT SOCICoreTargets + RUNTIME DESTINATION "${SOCI_INSTALL_BINDIR}" + COMPONENT soci_runtime + LIBRARY DESTINATION "${SOCI_INSTALL_LIBDIR}" + COMPONENT soci_runtime + NAMELINK_COMPONENT soci_development + ARCHIVE DESTINATION "${SOCI_INSTALL_LIBDIR}" + COMPONENT soci_development + FILE_SET headers DESTINATION "${SOCI_INSTALL_INCLUDEDIR}" + COMPONENT soci_development +) +# Generate and install a targets file +install( + EXPORT SOCICoreTargets + DESTINATION "${SOCI_INSTALL_CMAKEDIR}" + FILE SOCICoreTargets.cmake + NAMESPACE SOCI:: + COMPONENT soci_development +) diff --git a/src/core/Makefile.basic b/src/core/Makefile.basic deleted file mode 100644 index bd5f78d86..000000000 --- a/src/core/Makefile.basic +++ /dev/null @@ -1,94 +0,0 @@ -COMPILER = g++ -CXXFLAGS = -Wall -pedantic -Wno-long-long -INCLUDEDIRS = -I../../include -I../../include/private - -BACKENDLOADERDEFS = -DSOCI_LIB_PREFIX=\"libsoci_\" -DSOCI_LIB_SUFFIX=\".so\" - -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 connection-parameters.o soci-simple.o - - -libsoci_core.a : generated ${OBJS} - ar rv $@ ${OBJS} - rm *.o - -shared : generated ${OBJS} - ${COMPILER} -fPIC -c ${OBJS} ${CXXFLAGS} ${INCLUDEDIRS} - ${COMPILER} -shared -o libsoci_core.so ${OBJS} - rm *.o - -generated : ../../include/soci/soci-config.h ../../include/private/soci_backends_config.h - -# Note: this file is generated without any configured variables, -# full configuration fill is generated by CMake. -../../include/soci/soci-config.h : ../../include/soci/soci-config.h.in - grep -v CONFIGURED_VARIABLES $? > $@ - -# Note: this file is generated with a basic search path, -# full backends search path is generated by CMake. -../../include/private/soci_backends_config.h : soci_backends_config.h.in - echo '#define DEFAULT_BACKENDS_PATH "."' > $@ - -session.o : session.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -statement.o : statement.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -row.o : row.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -values.o : values.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -into-type.o : into-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -use-type.o : use-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -blob.o : blob.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -error.o : error.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -rowid.o : rowid.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -procedure.o : procedure.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -ref-counted-prepare-info.o : ref-counted-prepare-info.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -ref-counted-statement.o : ref-counted-statement.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -once-temp-type.o : once-temp-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -prepare-temp-type.o : prepare-temp-type.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -transaction.o : transaction.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${INCLUDEDIRS} - -backend-loader.o : backend-loader.cpp - ${COMPILER} -c $? ${CXXFLAGS} ${BACKENDLOADERDEFS} ${INCLUDEDIRS} - -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 ../../include/private/soci_backends_config.h diff --git a/src/core/backend-loader.cpp b/src/core/backend-loader.cpp index 54220685b..79dd18ad7 100644 --- a/src/core/backend-loader.cpp +++ b/src/core/backend-loader.cpp @@ -18,8 +18,6 @@ #include #endif -#include "soci_backends_config.h" - using namespace soci; using namespace soci::dynamic_backends; @@ -77,11 +75,7 @@ std::string get_this_dynlib_path() } // unnamed namespace -#ifdef _UNICODE #define DLOPEN(x) LoadLibraryA(x) -#else -#define DLOPEN(x) LoadLibrary(x) -#endif #define DLCLOSE(x) FreeLibrary(x) #define DLSYM(x, y) GetProcAddress(x, y) diff --git a/src/core/callbacks.cpp b/src/core/callbacks.cpp new file mode 100644 index 000000000..a36a6ca68 --- /dev/null +++ b/src/core/callbacks.cpp @@ -0,0 +1,16 @@ +#define SOCI_SOURCE + +#include "soci/callbacks.h" +#include "soci/soci-platform.h" + +using namespace soci; + +failover_callback::~failover_callback() = default; + +void failover_callback::started() {} + +void failover_callback::finished(session& /* sql */) {} + +void failover_callback::failed(bool& /* out */ /* retry */, std::string& /* out */ /* newTarget */) {} + +void failover_callback::aborted() {} diff --git a/src/core/logger.cpp b/src/core/logger.cpp index 5ff47eb24..696d9e8a6 100644 --- a/src/core/logger.cpp +++ b/src/core/logger.cpp @@ -15,7 +15,7 @@ namespace // anonymous { // Helper to throw from not implemented logger_impl methods. -void throw_not_supported() +[[noreturn]] void throw_not_supported() { throw soci_error("Legacy method not supported by this logger."); } @@ -61,15 +61,11 @@ void logger_impl::set_stream(std::ostream *) std::ostream * logger_impl::get_stream() const { throw_not_supported(); - - SOCI_DUMMY_RETURN(NULL); } std::string logger_impl::get_last_query() const { throw_not_supported(); - - SOCI_DUMMY_RETURN(std::string()); } std::string logger_impl::get_last_query_context() const diff --git a/src/core/session.cpp b/src/core/session.cpp index a303d0c5c..5d617b84b 100644 --- a/src/core/session.cpp +++ b/src/core/session.cpp @@ -26,9 +26,6 @@ namespace soci }; } // soci -namespace // anonymous -{ - void ensureConnected(session_backend * backEnd) { if (backEnd == NULL) @@ -46,7 +43,7 @@ class standard_logger_impl : public logger_impl logStream_ = NULL; } - virtual void start_query(std::string const & query) + virtual void start_query(std::string const & query) override { logger_impl::start_query(query); @@ -58,23 +55,23 @@ class standard_logger_impl : public logger_impl lastQuery_ = query; } - virtual void set_stream(std::ostream * s) + virtual void set_stream(std::ostream * s) override { logStream_ = s; } - virtual std::ostream * get_stream() const + virtual std::ostream * get_stream() const override { return logStream_; } - virtual std::string get_last_query() const + virtual std::string get_last_query() const override { return lastQuery_; } private: - virtual logger_impl* do_clone() const + virtual logger_impl* do_clone() const override { return new standard_logger_impl; } @@ -83,8 +80,6 @@ class standard_logger_impl : public logger_impl std::string lastQuery_; }; -} // namespace anonymous - session::session() : once(this), prepare(this), logger_(new standard_logger_impl) diff --git a/src/core/soci_backends_config.h.in b/src/core/soci_backends_config.h.in deleted file mode 100644 index b6c1d9f29..000000000 --- a/src/core/soci_backends_config.h.in +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (C) 2011 Alex Ott -// 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_BACKENDS_CONFIG_H -#define SOCI_BACKENDS_CONFIG_H - -#define DEFAULT_BACKENDS_PATH "@CMAKE_INSTALL_FULL_LIBDIR@" - -#endif // SOCI_BACKENDS_CONFIG_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aec5e4cb2..e6f507ca4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,48 +1,15 @@ -############################################################################### -# -# 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:") +add_subdirectory(common) -# This works around a problem when building in C++11 mode with clang (see #984). -add_definitions(-DCATCH_CONFIG_CPP11_NO_SHUFFLE) -if(MSVC) - add_compile_options(/bigobj /utf-8) -endif() +foreach (CURRENT_BACKEND IN LISTS SOCI_ENABLED_BACKENDS) + string(TOUPPER "${CURRENT_BACKEND}" CURRENT_BACKEND_UPPER) + string(TOLOWER "${CURRENT_BACKEND}" CURRENT_BACKEND_LOWER) -include_directories( - ${SOCI_SOURCE_DIR}/include/private - ${CMAKE_CURRENT_SOURCE_DIR}) + set(SOCI_${CURRENT_BACKEND_UPPER}_SKIP_TESTS OFF CACHE BOOL "Whether to skip tests for backend '${CURRENT_BACKEND}'") -# This library is used by all test executables. -# It's always static for simplicity, even when using shared SOCI libraries. -add_library(soci_tests_common STATIC - common/test-main.cpp - common/test-boost.cpp - common/test-common.cpp - common/test-connparams.cpp - common/test-custom.cpp - common/test-dynamic.cpp - common/test-lob.cpp - common/test-manual.cpp - common/test-rowset.cpp - test-assert.h - test-context.h - test-myint.h) + if (NOT SOCI_${CURRENT_BACKEND_UPPER}_SKIP_TESTS) + add_subdirectory(${CURRENT_BACKEND_LOWER}) + endif() +endforeach() -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/common/CMakeLists.txt b/tests/common/CMakeLists.txt new file mode 100644 index 000000000..53460fe89 --- /dev/null +++ b/tests/common/CMakeLists.txt @@ -0,0 +1,42 @@ + +add_library(soci_tests_common + STATIC + "test-boost.cpp" + "test-common.cpp" + "test-connparams.cpp" + "test-custom.cpp" + "test-dynamic.cpp" + "test-lob.cpp" + "test-main.cpp" + "test-manual.cpp" + "test-rowset.cpp" + "test-unicode.cpp" +) + +# Catch headers are sensitive when it comes to being included in different +# contexts and this can lead to issues with all functionality to actually +# running the tests i.e. the main function. +# Therefore, we have to make sure that the main file is not included in +# a unity build. +set_source_files_properties("test-main.cpp" + PROPERTIES + SKIP_UNITY_BUILD_INCLUSION TRUE +) + +# Required to work around build issues with C++11 and Clang (see https://github.com/SOCI/soci/issues/984) +target_compile_definitions(soci_tests_common + PUBLIC + CATCH_CONFIG_CPP11_NO_SHUFFLE +) + +target_link_libraries(soci_tests_common + PUBLIC + soci_compiler_interface + SOCI::Core +) + +target_include_directories(soci_tests_common + PUBLIC + "${PROJECT_SOURCE_DIR}/include/private" + "${PROJECT_SOURCE_DIR}/tests" +) diff --git a/tests/db2/CMakeLists.txt b/tests/db2/CMakeLists.txt index 940d9f814..31cff62b0 100644 --- a/tests/db2/CMakeLists.txt +++ b/tests/db2/CMakeLists.txt @@ -1,16 +1,11 @@ -############################################################################### -# -# 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) -# -############################################################################### +add_executable(soci_db2_tests + "test-db2.cpp" +) +target_link_libraries(soci_db2_tests PRIVATE soci_tests_common SOCI::DB2) -soci_backend_test( - BACKEND DB2 - DEPENDS DB2 - SOURCE test-db2.cpp - CONNSTR "DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off") +set(SOCI_DB2_TEST_CONNSTR "DSN=SAMPLE;Uid=db2inst1;Pwd=db2inst1;autocommit=off" CACHE STRING "The connection string to use for DB2 tests") + +add_test( + NAME soci_db2_tests + COMMAND soci_db2_tests "${SOCI_DB2_TEST_CONNSTR}" "--invisibles" +) diff --git a/tests/db2/db2_tests.cpp b/tests/db2/db2_tests.cpp new file mode 100644 index 000000000..b52709e2d --- /dev/null +++ b/tests/db2/db2_tests.cpp @@ -0,0 +1,411 @@ +// +// 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 + +#include "common-tests.h" + +#include "soci/soci.h" +#include "soci/db2/soci-db2.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, LL BIGINT, 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 override + { + pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; + return new table_creator_one(pr_s); + } + + table_creator_base* table_creator_2(soci::session & pr_s) const override + { + pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; + return new table_creator_two(pr_s); + } + + table_creator_base* table_creator_3(soci::session & pr_s) const override + { + pr_s << "SET CURRENT SCHEMA = 'DB2INST1'"; + return new table_creator_three(pr_s); + } + + table_creator_base* table_creator_4(soci::session& s) const override + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const & pi_datdt_string) const override + { + return "to_date('" + pi_datdt_string + "', 'YYYY-MM-DD HH24:MI:SS')"; + } + + std::string sql_length(std::string const& s) const override + { + return "length(" + s + ")"; + } +}; + + +// +// 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(); +} + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/empty/CMakeLists.txt b/tests/empty/CMakeLists.txt index 9d945a5f1..58920627d 100644 --- a/tests/empty/CMakeLists.txt +++ b/tests/empty/CMakeLists.txt @@ -1,18 +1,11 @@ -############################################################################### -# -# 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) -# -############################################################################### +add_executable(soci_empty_tests + "test-empty.cpp" +) +target_link_libraries(soci_empty_tests PRIVATE soci_tests_common SOCI::Empty) -soci_backend_test( - BACKEND Empty - SOURCE test-empty.cpp - # We only run these tests from the empty backend test, as they don't use - # database at all. - ../common/test-unicode.cpp - CONNSTR "dummy") +set(SOCI_EMPTY_TEST_CONNSTR "dummy" CACHE STRING "The connection string to use for Empty tests") + +add_test( + NAME soci_empty_tests + COMMAND soci_empty_tests "${SOCI_EMPTY_TEST_CONNSTR}" "--invisibles" +) diff --git a/tests/empty/empty_tests.cpp b/tests/empty/empty_tests.cpp new file mode 100644 index 000000000..fd28ab66f --- /dev/null +++ b/tests/empty/empty_tests.cpp @@ -0,0 +1,178 @@ +// +// 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) +// + +// 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 "soci/soci.h" +#include "soci/empty/soci-empty.h" + +#include +#include +#include +#include + +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). +// 2. To exercise (at least some of) the syntax and try the SOCI library +// against different compilers, even in those environments where there +// is no database. SOCI uses advanced template techniques which are known +// to cause problems on different versions of popular compilers, and this +// test is handy to verify that the code is accepted by as many compilers +// as possible. +// +// Both of these purposes mean that the actual code here is meaningless +// from the database-development point of view. For new tests, you may wish +// to remove this code and keep only the general structure of this file. + +struct Person +{ + int id; + std::string firstName; + std::string lastName; +}; + +namespace soci +{ + template<> struct type_conversion + { + typedef values base_type; + static void from_base(values & /* r */, indicator /* ind */, + Person & /* p */) + { + } + }; +} + +TEST_CASE("Dummy test", "[empty]") +{ + soci::session sql(backEnd, connectString); + + sql << "Do what I want."; + sql << "Do what I want " << 123 << " times."; + + char const* const query = "some query"; + sql << query; + + { + std::string squery = "some query"; + sql << squery; + } + + int i = 7; + sql << "insert", use(i); + sql << "select", into(i); + sql << query, use(i); + sql << query, into(i); + +#if defined (__LP64__) || ( __WORDSIZE == 64 ) + long int li = 9; + sql << "insert", use(li); + sql << "select", into(li); +#endif + + 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); + sql << query, use(i, ind); + sql << query, use(i, ind); + + 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 << query, into(i)); + st.execute(); + st.fetch(); + } + { + statement st = (sql.prepare << "select", into(i, ind)); + statement sq = (sql.prepare << query, into(i, ind)); + } + { + statement st = (sql.prepare << "select", into(numbers)); + } + { + statement st = (sql.prepare << "select", into(numbers, inds)); + } + { + statement st = (sql.prepare << "insert", use(i)); + statement sq = (sql.prepare << query, use(i)); + } + { + statement st = (sql.prepare << "insert", use(i, ind)); + statement sq = (sql.prepare << query, use(i, ind)); + } + { + statement st = (sql.prepare << "insert", use(numbers)); + } + { + statement st = (sql.prepare << "insert", use(numbers, inds)); + } + { + Person p; + sql << "select person", into(p); + } +} + + +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_empty_backend\'\n"; + std::exit(1); + } + + return Catch::Session().run(argc, argv); +} diff --git a/tests/firebird/CMakeLists.txt b/tests/firebird/CMakeLists.txt index e5989190a..5f51afc4e 100644 --- a/tests/firebird/CMakeLists.txt +++ b/tests/firebird/CMakeLists.txt @@ -1,16 +1,11 @@ -############################################################################### -# -# 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) -# -############################################################################### +add_executable(soci_firebird_tests + "test-firebird.cpp" +) +target_link_libraries(soci_firebird_tests PRIVATE soci_tests_common SOCI::Firebird) -soci_backend_test( - BACKEND Firebird - DEPENDS Firebird - SOURCE test-firebird.cpp - CONNSTR "service=/tmp/soci_test.fdb user=SYSDBA password=masterkey") +set(SOCI_FIREBIRD_TEST_CONNSTR "service=/tmp/soci_test.fdb user=SYSDBA password=masterkey" CACHE STRING "The connection string to use for Firebird tests") + +add_test( + NAME soci_firebird_tests + COMMAND soci_firebird_tests "${SOCI_FIREBIRD_TEST_CONNSTR}" "--invisibles" +) diff --git a/tests/firebird/firebird_tests.cpp b/tests/firebird/firebird_tests.cpp new file mode 100644 index 000000000..aac635d44 --- /dev/null +++ b/tests/firebird/firebird_tests.cpp @@ -0,0 +1,1279 @@ +// +// 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) +// +// + +#include + +#include "common-tests.h" + +#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 +#include +#include +#include +#include + +using namespace soci; + +std::string connectString; +soci::backend_factory const &backEnd = *factory_firebird(); + +// fundamental tests - transactions in Firebird +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 + { + 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 +} + +// character types +TEST_CASE("Firebird char types", "[firebird][string]") +{ + soci::session sql(backEnd, connectString); + + try + { + sql << "drop table test2"; + } + catch (soci_error const &) + {} // ignore if error + + sql << "create table test2 (p1 char(10) character set none, p2 varchar(10) character set none)"; + sql.commit(); + + sql.begin(); + + { + char a('a'), b('b'), c1, c2; + + sql << "insert into test2(p1,p2) values(?,?)", use(a), use(b); + + sql << "select p1,p2 from test2", into(c1), into(c2); + CHECK(c1 == 'a'); + CHECK(c2 == 'b'); + + sql << "delete from test2"; + } + +#if 0 // SOCI doesn't support binding into(char *, ...) anymore, use std::string + { + char msg[] = "Hello, Firebird!"; + char buf1[100], buf2[100], buf3[100]; + char *b1 = buf1, *b2 = buf2, *b3 = buf3; + + strcpy(b1, msg); + + 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); + + CHECK(!std::strcmp(buf2, buf3)); + CHECK(!std::strcmp(buf2, "Hello, Fir")); + + sql << "delete from test2"; + } + + { + char msg[] = "Hello, Firebird!"; + char buf1[100], buf2[100], buf3[100]; + strcpy(buf1, msg); + + sql << "insert into test2(p1, p2) values (?,?)", + use(buf1), use(buf1); + sql << "select p1, p2 from test2", into(buf2), into(buf3); + + CHECK(!std::strcmp(buf2, buf3)); + CHECK(!std::strcmp(buf2, "Hello, Fir")); + + sql << "delete from test2"; + } +#endif + + { + // 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); + + CHECK(b2 == b3); + CHECK(b2 == "Hello, FB!"); + + sql << "delete from test2"; + } + + { + // verify blank padding in CHAR fields + // In Firebird, CHAR fields are always padded with whitespaces. + char msg[] = "Hello"; + sql << "insert into test2(p1) values(\'" << msg << "\')"; + + char buf[20]; + std::string buf_str; + sql << "select p1 from test2", into(buf_str); + std::strcpy(buf, buf_str.c_str()); + + 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"; +} + +// date and time +TEST_CASE("Firebird date and time", "[firebird][datetime]") +{ + soci::session sql(backEnd, connectString); + + try + { + sql << "drop table test3"; + } + catch (soci_error const &) + {} // ignore if error + + sql << "create table test3 (p1 timestamp, p2 date, p3 time)"; + sql.commit(); + + sql.begin(); + + std::tm t1 = std::tm(); + std::tm t2 = std::tm(); + std::tm t3 = std::tm(); + std::time_t now = std::time(NULL); + std::tm t = *std::localtime(&now); + sql << "insert into test3(p1, p2, p3) " + << "values (?,?,?)", use(t), use(t), use(t); + + sql << "select p1, p2, p3 from test3", into(t1), into(t2), into(t3); + + // timestamp + 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 + 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 + 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"; +} + +// floating points +TEST_CASE("Firebird floating point", "[firebird][float]") +{ + soci::session sql(backEnd, connectString); + + try + { + sql << "drop table test4"; + } + catch (soci_error const &) + {} // ignore if error + + sql << "create table test4 (p1 numeric(8,2), " + << "p2 decimal(14,8), p3 double precision, p4 integer)"; + sql.commit(); + + sql.begin(); + + double d1 = 1234.23, d2 = 1e8, d3 = 1.0/1440.0, + d4, d5, d6; + + sql << "insert into test4(p1, p2, p3) values (?,?,?)", + use(d1), use(d2), use(d3); + + sql << "select p1, p2, p3 from test4", + into(d4), into(d5), into(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"; + d1 = -d1; + d2 = -d2; + d3 = -d3; + + sql << "insert into test4(p1, p2, p3) values (?,?,?)", + use(d1), use(d2), use(d3); + + sql << "select p1, p2, p3 from test4", + into(d4), into(d5), into(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 + try + { + int i; + sql << "select p1 from test4", into(i); + + // expecting error + CHECK(false); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Can't convert value with scale 2 to integral type"); + } + + // verify an exception is thrown when inserting non-integral value + // to integral column + try + { + sql << "insert into test4(p4) values(?)", use(d1); + + // expecting error + CHECK(false); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Can't convert non-integral value to integral column type"); + } + + sql << "drop table test4"; +} + +// integer types and indicators +TEST_CASE("Firebird integers", "[firebird][int]") +{ + soci::session sql(backEnd, connectString); + + { + short sh(0); + sql << "select 3 from rdb$database", into(sh); + CHECK(sh == 3); + } + + { + int i(0); + sql << "select 5 from rdb$database", into(i); + CHECK(i == 5); + } + + { + unsigned long ul(0); + sql << "select 7 from rdb$database", into(ul); + CHECK(ul == 7); + } + + { + // test indicators + indicator ind; + int i; + + sql << "select 2 from rdb$database", into(i, ind); + CHECK(ind == i_ok); + + sql << "select NULL from rdb$database", into(i, ind); + 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); + CHECK(ind == i_truncated); +#endif + + sql << "select 5 from rdb$database where 0 = 1", into(i, ind); + CHECK(sql.got_data() == false); + + try + { + // expect error + sql << "select NULL from rdb$database", into(i); + CHECK(false); + } + catch (soci_error const &e) + { + 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); + CHECK(!sql.got_data()); + } +} + +// repeated fetch and bulk operations for character types +TEST_CASE("Firebird bulk operations", "[firebird][bulk]") +{ + soci::session sql(backEnd, connectString); + + try + { + sql << "drop table test6"; + } + catch (soci_error const &) + {} // ignore if error + + sql << "create table test6 (p1 char(10) character set none, p2 varchar(10) character set none)"; + sql.commit(); + + sql.begin(); + + for (char c = 'a'; c <= 'z'; ++c) + { + sql << "insert into test6(p1, p2) values(?,?)", use(c), use(c); + } + + { + char c, c1, c2; + + statement st = (sql.prepare << + "select p1,p2 from test6 order by p1", into(c1), into(c2)); + + // Verify that fetch after re-executing the same statement works. + for (int n = 0; n < 2; ++n) + { + st.execute(); + + c='a'; + while (st.fetch()) + { + CHECK(c == c1); + CHECK(c == c2); + ++c; + } + CHECK(c == 'z'+1); + } + } + + { + char c='a'; + + std::vector c1(10), c2(10); + + statement st = (sql.prepare << + "select p1,p2 from test6 order by p1", into(c1), into(c2)); + + st.execute(); + while (st.fetch()) + { + for (std::size_t i = 0; i != c1.size(); ++i) + { + CHECK(c == c1[i]); + CHECK(c == c2[i]); + ++c; + } + } + CHECK(c == 'z' + 1); + } + + { + // verify an exception is thrown when empty vector is used + std::vector vec; + try + { + sql << "select p1 from test6", into(vec); + CHECK(false); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Vectors of size 0 are not allowed."); + } + } + + sql << "delete from test6"; + + // verifying std::string + int const rowsToTest = 10; + for (int i = 0; i != rowsToTest; ++i) + { + std::ostringstream ss; + ss << "Hello_" << i; + + std::string const &x = ss.str(); + + sql << "insert into test6(p1, p2) values(\'" + << x << "\', \'" << x << "\')"; + } + + int count; + sql << "select count(*) from test6", into(count); + CHECK(count == rowsToTest); + + { + int i = 0; + std::string s1, s2; + statement st = (sql.prepare << + "select p1, p2 from test6 order by p1", into(s1), into(s2)); + + st.execute(); + while (st.fetch()) + { + std::ostringstream ss; + ss << "Hello_" << i; + std::string const &x = ss.str(); + + // Note: CHAR fields are always padded with whitespaces + ss << " "; + CHECK(s1 == ss.str()); + CHECK(s2 == x); + ++i; + } + CHECK(i == rowsToTest); + } + + { + int i = 0; + + std::vector s1(4), s2(4); + statement st = (sql.prepare << + "select p1, p2 from test6 order by p1", into(s1), into(s2)); + st.execute(); + while (st.fetch()) + { + for (std::size_t j = 0; j != s1.size(); ++j) + { + std::ostringstream ss; + ss << "Hello_" << i; + std::string const &x = ss.str(); + + // Note: CHAR fields are always padded with whitespaces + ss << " "; + CHECK(ss.str() == s1[j]); + CHECK(x == s2[j]); + ++i; + } + } + CHECK(i == rowsToTest); + } + + sql << "drop table test6"; +} + +// named parameters +TEST_CASE("Firebird named parameters", "[firebird][named-params]") +{ + soci::session sql(backEnd, connectString); + + try + { + sql << "drop table test8"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test8(id1 integer, id2 integer)"; + sql.commit(); + + sql.begin(); + + int j = 13, k = 4, i, m; + 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); + CHECK(i == j); + CHECK(m == k); + + sql << "delete from test8"; + + std::vector in1(3), in2(3); + in1[0] = 3; + in1[1] = 2; + in1[2] = 1; + in2[0] = 4; + in2[1] = 5; + in2[2] = 6; + + { + statement st = (sql.prepare << + "insert into test8(id1, id2) values(:id1, :id2)", + use(k, "id2"), use(j, "id1")); + + std::size_t s = in1.size(); + for (std::size_t x = 0; x < s; ++x) + { + j = in1[x]; + k = in2[x]; + st.execute(); + } + } + + { + statement st = ( + sql.prepare << "select id1, id2 from test8", into(i), into(m)); + st.execute(); + + std::size_t x(0); + while (st.fetch()) + { + CHECK(i == in1[x]); + CHECK(m == in2[x]); + ++x; + } + } + + sql << "delete from test8"; + + // test vectors + sql << "insert into test8(id1, id2) values(:id1, :id2)", + use(in1, "id1"), use(in2, "id2"); + + std::vector out1(3), out2(3); + + sql << "select id1, id2 from test8", into(out1), into(out2); + std::size_t s = out1.size(); + CHECK(s == 3); + + for (std::size_t x = 0; x(0) == 1); + CHECK(r.get(1) == "Hello"); + CHECK(tests::are_doubles_exactly_equal(r.get(2), d)); + + // get values by name + CHECK(r.get("ID") == 1); + CHECK(r.get("MSG") == "Hello"); + CHECK(tests::are_doubles_exactly_equal(r.get("NTEST"), d)); + + st.fetch(); + CHECK(r.get(0) == 2); + CHECK(r.get("MSG") == "Firebird"); + CHECK(r.get_indicator(2) == i_null); + + // verify default values + 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<> + CHECK_THROWS_AS(r.get(0), std::bad_cast); + + sql << "drop table test9"; +} + +// stored procedures +TEST_CASE("Firebird stored procedures", "[firebird][procedure]") +{ + soci::session sql(backEnd, connectString); + + try + { + sql << "drop procedure sp_test10"; + } + catch (std::runtime_error &) + {} // ignore if error + + try + { + sql << "drop procedure sp_test10a"; + } + catch (std::runtime_error &) + {} // ignore if error + + try + { + sql << "drop table test10"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test10(id integer, id2 integer)"; + + sql << "create procedure sp_test10\n" + << "returns (rid integer, rid2 integer)\n" + << "as begin\n" + << "for select id, id2 from test10 into rid, rid2 do begin\n" + << "suspend;\n" + << "end\n" + << "end;\n"; + + sql << "create procedure sp_test10a (pid integer, pid2 integer)\n" + << "as begin\n" + << "insert into test10(id, id2) values (:pid, :pid2);\n" + << "end;\n"; + + sql.commit(); + + sql.begin(); + + row r; + int p1 = 3, p2 = 4; + + // calling procedures that do not return values requires + // 'execute procedure ...' statement + sql << "execute procedure sp_test10a ?, ?", use(p1), use(p2); + + // calling procedures that return values requires + // 'select ... from ...' statement + sql << "select * from sp_test10", into(r); + + CHECK(r.get(0) == p1); + CHECK(r.get(1) == p2); + + sql << "delete from test10"; + + p1 = 5; + p2 = 6; + { + procedure proc = ( + sql.prepare << "sp_test10a :p1, :p2", + use(p2, "p2"), use(p1, "p1")); + proc.execute(1); + } + + { + row rw; + procedure proc = (sql.prepare << "sp_test10", into(rw)); + proc.execute(1); + + CHECK(rw.get(0) == p1); + CHECK(rw.get(1) == p2); + } + + sql << "delete from test10"; + + // test vectors + std::vector in1(3), in2(3); + in1[0] = 3; + in1[1] = 2; + in1[2] = 1; + in2[0] = 4; + in2[1] = 5; + in2[2] = 6; + + { + procedure proc = ( + sql.prepare << "sp_test10a :p1, :p2", + use(in2, "p2"), use(in1, "p1")); + proc.execute(1); + } + + { + row rw; + procedure proc = (sql.prepare << "sp_test10", into(rw)); + + proc.execute(1); + CHECK(rw.get(0) == in1[0]); + CHECK(rw.get(1) == in2[0]); + proc.fetch(); + CHECK(rw.get(0) == in1[1]); + CHECK(rw.get(1) == in2[1]); + proc.fetch(); + CHECK(rw.get(0) == in1[2]); + CHECK(rw.get(1) == in2[2]); + CHECK(proc.fetch() == false); + } + + { + std::vector out1(3), out2(3); + procedure proc = (sql.prepare << "sp_test10", into(out1), into(out2)); + proc.execute(1); + + std::size_t s = out1.size(); + CHECK(s == 3); + + for (std::size_t x = 0; x < s; ++x) + { + CHECK(out1[x] == in1[x]); + CHECK(out2[x] == in2[x]); + } + } + + sql.rollback(); + + sql.begin(); + sql << "drop procedure sp_test10"; + sql << "drop procedure sp_test10a"; + sql << "drop table test10"; +} + +// direct access to Firebird using handles exposed by +// soci::FirebirdStatmentBackend +namespace soci +{ + enum eRowCountType + { + eRowsSelected = isc_info_req_select_count, + eRowsInserted = isc_info_req_insert_count, + eRowsUpdated = isc_info_req_update_count, + eRowsDeleted = isc_info_req_delete_count + }; + + // Returns number of rows afected by last statement + // or -1 if there is no such counter available. + long getRowCount(soci::statement & statement, eRowCountType type) + { + ISC_STATUS stat[20]; + char cnt_req[2], cnt_info[128]; + + cnt_req[0]=isc_info_sql_records; + cnt_req[1]=isc_info_end; + + firebird_statement_backend* statementBackEnd + = static_cast(statement.get_backend()); + + // Note: This is very poorly documented function. + // It can extract number of rows returned by select statement, + // but it appears that this is only number of rows prefetched by + // client library, not total number of selected rows. + if (isc_dsql_sql_info(stat, &statementBackEnd->stmtp_, sizeof(cnt_req), + cnt_req, sizeof(cnt_info), cnt_info)) + { + soci::details::firebird::throw_iscerror(stat); + } + + long count = -1; + char type_ = static_cast(type); + for (char *ptr = cnt_info + 3; *ptr != isc_info_end;) + { + char count_type = *ptr++; + int m = isc_vax_integer(ptr, 2); + ptr += 2; + count = isc_vax_integer(ptr, static_cast(m)); + + if (count_type == type_) + { + // this is requested number + break; + } + ptr += m; + } + + return count; + } + +} // namespace soci + +TEST_CASE("Firebird direct API use", "[firebird][native]") +{ + soci::session sql(backEnd, connectString); + + try + { + sql << "drop table test11"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test11(id integer)"; + sql.commit(); + + sql.begin(); + + { + std::vector in(3); + in[0] = 3; + in[1] = 2; + in[2] = 1; + + statement st = (sql.prepare << "insert into test11(id) values(?)", + use(in)); + st.execute(1); + + // Note: Firebird backend inserts every row with separate insert + // 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. + CHECK(getRowCount(st, eRowsInserted) == 1); + } + + { + int i = 5; + statement st = (sql.prepare << "update test11 set id = ? where id<3", + use(i)); + st.execute(1); + CHECK(getRowCount(st, eRowsUpdated) == 2); + + // verify that no rows were deleted + CHECK(getRowCount(st, eRowsDeleted) == 0); + } + + { + std::vector out(3); + statement st = (sql.prepare << "select id from test11", into(out)); + st.execute(1); + + CHECK(getRowCount(st, eRowsSelected) == 3); + } + + { + statement st = (sql.prepare << "delete from test11 where id=10"); + st.execute(1); + CHECK(getRowCount(st, eRowsDeleted) == 0); + } + + { + statement st = (sql.prepare << "delete from test11"); + st.execute(1); + CHECK(getRowCount(st, eRowsDeleted) == 3); + } + + sql << "drop table test11"; +} + +TEST_CASE("Firebird string coercions", "[firebird][string]") +{ + soci::session sql(backEnd, connectString); + + try + { + sql << "drop table test12"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test12(a decimal(10,3), b timestamp, c date, d time)"; + sql.commit(); + sql.begin(); + + // Check if passing input parameters as strings works + // for different column types. + { + std::string a = "-3.14150", b = "2013-02-28 23:36:01", + c = "2013-02-28", d = "23:36:01"; + statement st = (sql.prepare << + "insert into test12(a, b, c, d) values (?, ?, ?, ?)", + use(a), use(b), use(c), use(d)); + st.execute(1); + CHECK(getRowCount(st, eRowsInserted) == 1); + } + + { + double a; + std::tm b = std::tm(), c = std::tm(), d = std::tm(); + sql << "select a, b, c, d from test12", + into(a), into(b), into(c), into(d); + CHECK(std::fabs(a - (-3.141)) < 0.000001); + 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"; +} + +// Dynamic binding to row objects: decimals_as_strings +TEST_CASE("Firebird decimals as strings", "[firebird][decimal][string]") +{ + using namespace soci::details::firebird; + + int a = -12345678; + 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; + 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"); + + soci::session sql(backEnd, connectString + " decimals_as_strings=1"); + + try + { + sql << "drop table test13"; + } + catch (std::runtime_error &) + {} // ignore if error + + sql << "create table test13(ntest1 decimal(10,2), " + << "ntest2 decimal(4,4), ntest3 decimal(3,1))"; + sql.commit(); + + sql.begin(); + + { + row r; + sql << "select * from test13", into(r); + CHECK(sql.got_data() == false); + } + + std::string d_str0("+03.140"), d_str1("3.14"), + d_str2("3.1400"), d_str3("3.1"); + indicator ind(i_ok); + + { + statement st((sql.prepare << + "insert into test13(ntest1, ntest2, ntest3) " + "values(:ntest1, :ntest2, :ntest3)", + use(d_str0, ind, "ntest1"), use(d_str0, "ntest2"), + use(d_str0, "ntest3"))); + + st.execute(1); + + ind = i_null; + st.execute(1); + } + + row r; + statement st = (sql.prepare << "select * from test13", into(r)); + st.execute(1); + + CHECK(r.size() == 3); + + // get properties by position + CHECK(r.get_properties(0).get_name() == "NTEST1"); + CHECK(r.get_properties(0).get_data_type() == dt_string); + CHECK(r.get_properties(0).get_db_type() == db_string); + CHECK(r.get_properties(1).get_name() == "NTEST2"); + CHECK(r.get_properties(1).get_data_type() == dt_string); + CHECK(r.get_properties(1).get_db_type() == db_string); + CHECK(r.get_properties(2).get_name() == "NTEST3"); + CHECK(r.get_properties(2).get_data_type() == dt_string); + CHECK(r.get_properties(2).get_db_type() == db_string); + + // get properties by name + CHECK(r.get_properties("NTEST1").get_name() == "NTEST1"); + CHECK(r.get_properties("NTEST1").get_data_type() == dt_string); + CHECK(r.get_properties("NTEST1").get_db_type() == db_string); + CHECK(r.get_properties("NTEST2").get_name() == "NTEST2"); + CHECK(r.get_properties("NTEST2").get_data_type() == dt_string); + CHECK(r.get_properties("NTEST2").get_db_type() == db_string); + CHECK(r.get_properties("NTEST3").get_name() == "NTEST3"); + CHECK(r.get_properties("NTEST3").get_data_type() == dt_string); + CHECK(r.get_properties("NTEST3").get_db_type() == db_string); + + // get values by position + CHECK(r.get(0) == d_str1); + CHECK(r.get(1) == d_str2); + CHECK(r.get(2) == d_str3); + + // get values by name + CHECK(r.get("NTEST1") == d_str1); + CHECK(r.get("NTEST2") == d_str2); + CHECK(r.get("NTEST3") == d_str3); + + st.fetch(); + 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"; +} + +// +// Support for soci Common Tests +// + +struct TableCreator1 : public tests::table_creator_base +{ + 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, ll bigint, ul bigint, " + "d double precision, num76 numeric(7,6), " + "tm timestamp, i1 integer, i2 integer, i3 integer, name varchar(20))"; + sql.commit(); + sql.begin(); + } +}; + +struct TableCreator2 : public tests::table_creator_base +{ + TableCreator2(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(num_float float, num_int integer, " + "name varchar(20), sometime timestamp, chr char)"; + sql.commit(); + sql.begin(); + } +}; + +struct TableCreator3 : public tests::table_creator_base +{ + TableCreator3(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(name varchar(100) not null, " + "phone varchar(15))"; + sql.commit(); + sql.begin(); + } +}; + +struct TableCreator4 : public tests::table_creator_base +{ + TableCreator4(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + sql.commit(); + sql.begin(); + } +}; + +struct TableCreatorCLOB : public tests::table_creator_base +{ + TableCreatorCLOB(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, s blob sub_type text)"; + sql.commit(); + sql.begin(); + } +}; + +struct TableCreatorBLOB : public tests::table_creator_base +{ + TableCreatorBLOB(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, b blob)"; + sql.commit(); + sql.begin(); + } +}; + +struct TableCreatorXML : public tests::table_creator_base +{ + TableCreatorXML(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, x blob sub_type text)"; + sql.commit(); + sql.begin(); + } +}; + +class test_context : public tests::test_context_base +{ + public: + test_context(backend_factory const &backEnd, + std::string const &connectString) + : test_context_base(backEnd, connectString) + {} + + tests::table_creator_base* table_creator_1(soci::session& s) const override + { + return new TableCreator1(s); + } + + tests::table_creator_base* table_creator_2(soci::session& s) const override + { + return new TableCreator2(s); + } + + tests::table_creator_base* table_creator_3(soci::session& s) const override + { + return new TableCreator3(s); + } + + tests::table_creator_base* table_creator_4(soci::session& s) const override + { + return new TableCreator4(s); + } + + tests::table_creator_base* table_creator_clob(soci::session& s) const override + { + return new TableCreatorCLOB(s); + } + + + tests::table_creator_base* table_creator_blob(soci::session& s) const override + { + return new TableCreatorBLOB(s); + } + + tests::table_creator_base* table_creator_xml(soci::session& s) const override + { + return new TableCreatorXML(s); + } + + std::string to_date_time(std::string const &datdt_string) const override + { + return "'" + datdt_string + "'"; + } + + bool has_uint64_storage_bug() const override + { + // Firebird does not support unsigned integer types. + // We're using Firebird 3, which does not yet support a data + // type that can correctly store a UINT64_MAX. The biggest + // numeric data type available is numeric(18,0). + // Firebird 4 introduces the data type int128 and numeric(36,0), + // which will be sufficient for that in the future. + return true; + } + + void on_after_ddl(soci::session& sql) const override + { + sql.commit(); + } + + std::string sql_length(std::string const& s) const override + { + return "char_length(" + s + ")"; + } +}; + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/mysql/CMakeLists.txt b/tests/mysql/CMakeLists.txt index 9bd760e45..9510f6fb1 100644 --- a/tests/mysql/CMakeLists.txt +++ b/tests/mysql/CMakeLists.txt @@ -1,16 +1,11 @@ -############################################################################### -# -# 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) -# -############################################################################### +add_executable(soci_mysql_tests + "test-mysql.cpp" +) +target_link_libraries(soci_mysql_tests PRIVATE soci_tests_common SOCI::MySQL) -soci_backend_test( - BACKEND MySQL - DEPENDS MySQL - SOURCE test-mysql.cpp - CONNSTR "db=soci_test") +set(SOCI_MYSQL_TEST_CONNSTR "db=soci_test" CACHE STRING "The connection string to use for MySQL tests") + +add_test( + NAME soci_mysql_tests + COMMAND soci_mysql_tests "${SOCI_MYSQL_TEST_CONNSTR}" "--invisibles" +) diff --git a/tests/mysql/mysql_tests.cpp b/tests/mysql/mysql_tests.cpp new file mode 100644 index 000000000..a25a32fe2 --- /dev/null +++ b/tests/mysql/mysql_tests.cpp @@ -0,0 +1,823 @@ +// +// Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton +// MySQL backend copyright (C) 2006 Pawel Aleksander Fedorynski +// 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 "mysql_tests.h" + +#include "soci/soci.h" +#include "soci-compiler.h" +#include "soci/mysql/soci-mysql.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SOCI_MYSQL_DIRECT_INCLUDE +#include +#include +#else +#include +#include +#endif + +std::string connectString; +backend_factory const &backEnd = *soci::factory_mysql(); + +// procedure call test +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) && v < 5) + { + WARN("MySQL server version " << v + << " does not support stored procedures, skipping test."); + 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); + CHECK(out == in); + + // explicit procedure syntax + { + 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. +TEST_CASE("MySQL error reporting", "[mysql][exception]") +{ + { + try + { + soci::session sql(backEnd, "host=test.soci.invalid"); + } + catch (mysql_soci_error const &e) + { + 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."); + } + } + } + + { + soci::session sql(backEnd, connectString); + sql << "create table soci_test (id integer)"; + try + { + int n; + sql << "select id from soci_test_nosuchtable", into(n); + } + catch (mysql_soci_error const &e) + { + CHECK(e.err_num_ == ER_NO_SUCH_TABLE); + } + try + { + sql << "insert into soci_test (invalid) values (256)"; + } + catch (mysql_soci_error const &e) + { + CHECK(e.err_num_ == ER_BAD_FIELD_ERROR); + } + // A bulk operation. + try + { + std::vector v(3, 5); + sql << "insert into soci_test_nosuchtable values (:n)", use(v); + } + catch (mysql_soci_error const &e) + { + CHECK(e.err_num_ == ER_NO_SUCH_TABLE); + } + sql << "drop table soci_test"; + } +} + +struct bigint_table_creator : table_creator_base +{ + bigint_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val bigint)"; + } +}; + +struct bigint_unsigned_table_creator : table_creator_base +{ + bigint_unsigned_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val bigint unsigned)"; + } +}; + +TEST_CASE("MySQL long long", "[mysql][longlong]") +{ + { + soci::session sql(backEnd, connectString); + + bigint_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 + { + soci::session sql(backEnd, connectString); + + bigint_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); + } + + { + soci::session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + sql << "insert into soci_test set val = 18446744073709551615"; + row v; + sql << "select * from soci_test", into(v); + } + + { + soci::session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + const char* source = "18446744073709551615"; + sql << "insert into soci_test set val = " << source; + unsigned long long vv = 0; + sql << "select val from soci_test", into(vv); + std::stringstream buf; + buf << vv; + CHECK(buf.str() == source); + } + + { + soci::session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + const char* source = "18446744073709551615"; + sql << "insert into soci_test set val = " << source; + std::vector v(1); + sql << "select val from soci_test", into(v); + std::stringstream buf; + buf << v.at(0); + CHECK(buf.str() == source); + } + + { + soci::session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + unsigned long long n = 18446744073709551615ULL; + sql << "insert into soci_test(val) values (:n)", use(n); + unsigned long long m = 0; + sql << "select val from soci_test", into(m); + CHECK(n == m); + } + + { + soci::session sql(backEnd, connectString); + + bigint_unsigned_table_creator tableCreator(sql); + + std::vector v1; + v1.push_back(18446744073709551615ULL); + v1.push_back(18446744073709551614ULL); + v1.push_back(18446744073709551613ULL); + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::vector v2(10); + sql << "select val from soci_test order by val", into(v2); + + REQUIRE(v2.size() == 3); + CHECK(v2[0] == 18446744073709551613ULL); + CHECK(v2[1] == 18446744073709551614ULL); + CHECK(v2[2] == 18446744073709551615ULL); + } +} + +template +void test_num(const char* s, bool valid, T value) +{ + try + { + soci::session sql(backEnd, connectString); + T val; + sql << "select \'" << s << "\'", into(val); + if (valid) + { + double v1 = static_cast(value); + double v2 = static_cast(val); + double d = std::fabs(v1 - v2); + double epsilon = 0.001; + if (d >= epsilon && + d >= epsilon * (std::fabs(v1) + std::fabs(v2))) + { + FAIL("Difference between " << value + << " and " << val << " is too big."); + } + } + else + { + FAIL("string \"" << s << "\" parsed as " << val + << " but should have failed."); + } + } + catch (soci_error const& e) + { + if (valid) + { + FAIL("couldn't parse number: \"" << s << "\""); + } + else + { + char const * expectedPrefix = "Cannot convert data"; + CAPTURE(e.what()); + CHECK(strncmp(e.what(), expectedPrefix, strlen(expectedPrefix)) == 0); + } + } +} + +// Number conversion test. +TEST_CASE("MySQL number conversion", "[mysql][float][int]") +{ + test_num("", false, 0); + test_num("foo", false, 0); + test_num("1", true, 1); + test_num("12", true, 12); + test_num("123", true, 123); + test_num("12345", true, 12345); + test_num("12341234123412341234123412341234123412341234123412341", + true, 1.23412e+52); + test_num("99999999999999999999999912222222222222222222222222223" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333" + "9999999999999999999999991222222222222222222222222222333333333333", + false, 0); + test_num("1e3", true, 1000); + test_num("1.2", true, 1.2); + test_num("1.2345e2", true, 123.45); + test_num("1 ", false, 0); + test_num(" 123", true, 123); + test_num("1,2", false, 0); + test_num("123abc", false, 0); + test_num("-0", true, 0); + + test_num("123", true, 123); + test_num("100000", false, 0); + + test_num("123", true, 123); + test_num("2147483647", true, 2147483647); + test_num("2147483647a", false, 0); + test_num("2147483648", false, 0); + // -2147483648 causes a warning because it is interpreted as + // 2147483648 (which doesn't fit in an integer) to which a negation + // is applied. + test_num("-2147483648", true, -2147483647 - 1); + test_num("-2147483649", false, 0); + test_num("-0", true, 0); + test_num("1.1", false, 0); + + test_num("123", true, 123); + test_num("9223372036854775807", true, 9223372036854775807LL); + test_num("9223372036854775808", false, 0); +} + +TEST_CASE("MySQL datetime", "[mysql][datetime]") +{ + soci::session sql(backEnd, connectString); + std::tm t = std::tm(); + sql << "select maketime(19, 54, 52)", into(t); + CHECK(t.tm_year == 0); + 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 type support test. +TEST_CASE("MySQL text", "[mysql][text]") +{ + soci::session sql(backEnd, connectString); + std::string a("asdfg\0hjkl", 10); + // The maximum length for TEXT and BLOB is 65536. + std::string x(60000, 'X'); + + sql << "create table soci_test (id int, text_value text)"; + sql << "insert into soci_test values (1, \'foo\')"; + sql << "insert into soci_test " + << "values (2, \'qwerty\\0uiop\')"; + sql << "insert into soci_test values (3, :a)", + use(a); + sql << "insert into soci_test values (4, :x)", + use(x); + + std::vector text_vec(100); + sql << "select text_value from soci_test order by id", into(text_vec); + REQUIRE(text_vec.size() == 4); + CHECK(text_vec[0] == "foo"); + CHECK(text_vec[1] == std::string("qwerty\0uiop", 11)); + CHECK(text_vec[2] == a); + CHECK(text_vec[3] == x); + + std::string text; + sql << "select text_value from soci_test where id = 1", into(text); + CHECK(text == "foo"); + sql << "select text_value from soci_test where id = 2", into(text); + CHECK(text == std::string("qwerty\0uiop", 11)); + sql << "select text_value from soci_test where id = 3", into(text); + CHECK(text == a); + sql << "select text_value from soci_test where id = 4", into(text); + CHECK(text == x); + + rowset rs = + (sql.prepare << "select text_value from soci_test order by id"); + rowset::const_iterator r = rs.begin(); + CHECK(r->get_properties(0).get_data_type() == dt_string); + CHECK(r->get_properties(0).get_db_type() == db_string); + CHECK(r->get(0) == "foo"); + ++r; + CHECK(r->get_properties(0).get_data_type() == dt_string); + CHECK(r->get_properties(0).get_db_type() == db_string); + CHECK(r->get(0) == std::string("qwerty\0uiop", 11)); + ++r; + CHECK(r->get_properties(0).get_data_type() == dt_string); + CHECK(r->get_properties(0).get_db_type() == db_string); + CHECK(r->get(0) == a); + ++r; + CHECK(r->get_properties(0).get_data_type() == dt_string); + CHECK(r->get_properties(0).get_db_type() == db_string); + CHECK(r->get(0) == x); + ++r; + CHECK(r == rs.end()); + + sql << "drop table soci_test"; +} + +// test for number of affected rows + +struct integer_value_table_creator : table_creator_base +{ + integer_value_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val integer)"; + } +}; + +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++) + { + 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); +} + + +// The prepared statements should survive session::reconnect(). +// However currently it doesn't and attempting to use it results in crashes due +// to accessing the already destroyed session backend, so disable this test. +TEST_CASE("MySQL statements after reconnect", "[mysql][connect][.]") +{ + soci::session sql(backEnd, connectString); + + integer_value_table_creator tableCreator(sql); + + int i; + statement st = (sql.prepare + << "insert into soci_test(val) values(:val)", use(i)); + i = 5; + st.execute(true); + + sql.reconnect(); + + i = 6; + st.execute(true); + + sql.close(); + sql.reconnect(); + + i = 7; + st.execute(true); + + std::vector v(5); + sql << "select val from soci_test order by val", into(v); + 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(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val int unsigned)"; + } +}; + +// rowset<> should be able to take INT UNSIGNED. +TEST_CASE("MySQL unsigned int", "[mysql][int]") +{ + soci::session sql(backEnd, connectString); + + unsigned_value_table_creator tableCreator(sql); + + unsigned int mask = 0xffffff00; + sql << "insert into soci_test set val = " << mask; + soci::rowset<> rows(sql.prepare << "select val from soci_test"); + int cnt = 0; + for (soci::rowset<>::iterator it = rows.begin(), end = rows.end(); + it != end; ++it) + { + cnt++; + } + CHECK(cnt == 1); +} + +TEST_CASE("MySQL function call", "[mysql][function]") +{ + soci::session sql(backEnd, connectString); + + row r; + + sql << "set @day = '5'"; + sql << "set @mm = 'december'"; + sql << "set @year = '2012'"; + sql << "select concat(@day,' ',@mm,' ',@year)", into(r); +} + +struct double_value_table_creator : table_creator_base +{ + double_value_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val double)"; + } +}; + +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 " + "not supported by the MySQL server."; + { + 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) { + CHECK(e.get_error_message() == expectedError); + } + } + { + 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) { + CHECK(e.get_error_message() == expectedError); + } + } + { + 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) { + CHECK(e.get_error_message() == expectedError); + } + } + { + 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) { + CHECK(e.get_error_message() == expectedError); + } + } +} + +struct tinyint_value_table_creator : table_creator_base +{ + tinyint_value_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val tinyint)"; + } +}; + +struct tinyint_unsigned_value_table_creator : table_creator_base +{ + tinyint_unsigned_value_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val tinyint unsigned)"; + } +}; + +TEST_CASE("MySQL tinyint", "[mysql][int][tinyint]") +{ + { + 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); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_long_long); + CHECK(r.get_properties("val").get_db_type() == db_uint32); + CHECK(r.get("val") == 0xffffff00); + CHECK(r.get("val") == 0xffffff00); + CHECK(r.get("val") == 0xffffff00); + } + { + 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); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_integer); + CHECK(r.get_properties("val").get_db_type() == db_int8); + CHECK(r.get("val") == -123); + CHECK(r.get("val") == -123); + } + { + 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); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_integer); + CHECK(r.get_properties("val").get_db_type() == db_uint8); + CHECK(r.get("val") == 123); + CHECK(r.get("val") == 123); + } + { + 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); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_unsigned_long_long); + CHECK(r.get_properties("val").get_db_type() == db_uint64); + CHECK(r.get("val") == 123456789012345ULL); + CHECK(r.get("val") == 123456789012345ULL); + } + { + 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); + REQUIRE(r.size() == 1); + CHECK(r.get_properties("val").get_data_type() == dt_long_long); + CHECK(r.get_properties("val").get_db_type() == db_int64); + CHECK(r.get("val") == -123456789012345LL); + CHECK(r.get("val") == -123456789012345LL); + } +} + +struct strings_table_creator : table_creator_base +{ + strings_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(s1 char(20), s2 varchar(20), " + "s3 tinytext, s4 mediumtext, s5 text, s6 longtext, " + "b1 binary(20), b2 varbinary(20), e1 enum ('foo', 'bar', 'baz'))"; + } +}; + +TEST_CASE("MySQL strings", "[mysql][string]") +{ + 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, e1) values " + "(:s1, :s2, :s3, :s4, :d5, :s6, :b1, :b2, 'foo')", + use(text), use(text), use(text), use(text), use(text), use(text), + use(binary), use(binary); + row r; + sql << "select s1, s2, s3, s4, s5, s6, b1, b2, e1 " + "from soci_test", into(r); + REQUIRE(r.size() == 9); + for (int i = 0; i < static_cast(r.size()); i++) { + CHECK(r.get_properties(i).get_data_type() == dt_string); + CHECK(r.get_properties(i).get_db_type() == db_string); + if (i < 6) { + CHECK(r.get(i) == text); + } else if (i < 8) { + CHECK(r.get(i) == binary); + } else { + CHECK(r.get(i) == "foo"); + } + } +} + +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 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) +{ + 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(), static_cast(s.size())); + std::string retv = escaped; + delete [] escaped; + return retv; +} + +void test14() +{ + { + soci::session sql(backEnd, connectString); + strings_table_creator tableCreator(sql); + std::string s = "word1'word2:word3"; + std::string escaped = escape_string(sql, s); + std::string query = "insert into soci_test (s5) values ('"; + query.append(escaped); + query.append("')"); + sql << query; + std::string s2; + sql << "select s5 from soci_test", into(s2); + CHECK(s == s2); + } + + std::cout << "test 14 passed" << std::endl; +} + +void test15() +{ + { + soci::session sql(backEnd, connectString); + int n; + sql << "select @a := 123", into(n); + CHECK(n == 123); + } + + std::cout << "test 15 passed" << std::endl; +} + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/mysql/mysql_tests.h b/tests/mysql/mysql_tests.h new file mode 100644 index 000000000..bcd181f86 --- /dev/null +++ b/tests/mysql/mysql_tests.h @@ -0,0 +1,155 @@ +#ifndef SOCI_TESTS_MYSQL_H_INCLUDED +#define SOCI_TESTS_MYSQL_H_INCLUDED + +#include "test-context.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, ll bigint, ul bigint unsigned, " + "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)"; + } +}; + +struct table_creator_for_blob : public tests::table_creator_base +{ + table_creator_for_blob(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, b blob)"; + } +}; + + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_common +{ +public: + test_context() = default; + + std::string get_example_connection_string() const override + { + return "dbname=test user=root password=\'Ala ma kota\'"; + } + + table_creator_base* table_creator_1(soci::session& s) const override + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(soci::session& s) const override + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(soci::session& s) const override + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(soci::session& s) const override + { + return new table_creator_for_get_affected_rows(s); + } + + std::string to_date_time(std::string const &datdt_string) const override + { + return "\'" + datdt_string + "\'"; + } + + table_creator_base * table_creator_blob(soci::session &s) const override + { + return new table_creator_for_blob(s); + } + + bool has_fp_bug() const override + { + // MySQL fails in the common test3() with "1.8000000000000000 != + // 1.7999999999999998", so don't use exact doubles comparisons for it. + return true; + } + + bool has_transactions_support(soci::session& sql) const override + { + 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; + } + + bool has_silent_truncate_bug(soci::session& sql) const override + { + 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; + } + + bool enable_std_char_padding(soci::session& sql) const override + { + // 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; + } + } + + std::string sql_length(std::string const& s) const override + { + return "char_length(" + s + ")"; + } +}; + +#endif // SOCI_TESTS_MYSQL_H_INCLUDED diff --git a/tests/odbc/CMakeLists.txt b/tests/odbc/CMakeLists.txt index 0791a88f8..4e3579544 100644 --- a/tests/odbc/CMakeLists.txt +++ b/tests/odbc/CMakeLists.txt @@ -1,69 +1,85 @@ -############################################################################### -# -# 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) -# -############################################################################### - -if (WIN32) - # MDBTools driver seems unreliable - soci_backend_test( - NAME access - BACKEND ODBC - DEPENDS ODBC - SOURCE test-odbc-access.cpp - CONNSTR "FILEDSN=${CMAKE_CURRENT_SOURCE_DIR}/test-access.dsn") -else() - message(STATUS "MS Access test disabled on non-Windows platform") -endif() - set(MSSQL_VER $ENV{MSSQL_VER}) if (NOT MSSQL_VER) # Use the same value that was used before by default. set(MSSQL_VER "2014") endif() -configure_file("test-mssql.dsn.in" "test-mssql.dsn" @ONLY) - -soci_backend_test( - NAME mssql - BACKEND ODBC - DEPENDS ODBC - SOURCE test-odbc-mssql.cpp - CONNSTR "FILEDSN=${CMAKE_CURRENT_BINARY_DIR}/test-mssql.dsn") - -soci_backend_test( - NAME mysql - BACKEND ODBC - DEPENDS ODBC - SOURCE test-odbc-mysql.cpp - CONNSTR "FILEDSN=${CMAKE_CURRENT_SOURCE_DIR}/test-mysql.dsn") +configure_file("test-mssql.dsn.in" "${CMAKE_CURRENT_BINARY_DIR}/test-mssql.dsn" @ONLY) + + +if (WIN32) + add_executable(soci_odbc_ms_access_tests + "test-odbc-access.cpp" + ) + target_link_libraries(soci_odbc_ms_access_tests PRIVATE soci_tests_common SOCI::ODBC) + + set(SOCI_ODBC_TEST_ACCESS_CONNSTR "FILEDSN=${CMAKE_CURRENT_SOURCE_DIR}/test-access.dsn" CACHE STRING "Connection string for the ODBC MS Access test") + + add_test( + NAME soci_odbc_ms_access_tests + COMMAND soci_odbc_ms_access_tests "${SOCI_ODBC_TEST_ACCESS_CONNSTR}" "--invisibles" + ) +endif() + + +add_executable(soci_odbc_mssql_tests + "test-odbc-mssql.cpp" +) +target_link_libraries(soci_odbc_mssql_tests PRIVATE soci_tests_common SOCI::ODBC) + +set(SOCI_ODBC_TEST_MSSQL_CONNSTR "FILEDSN=${CMAKE_CURRENT_BINARY_DIR}/test-mssql.dsn" CACHE STRING "Connection string for the ODBC MSSQL test") + +add_test( + NAME soci_odbc_mssql_tests + COMMAND soci_odbc_mssql_tests "${SOCI_ODBC_TEST_MSSQL_CONNSTR}" "--invisibles" +) + + +add_executable(soci_odbc_mysql_tests + "test-odbc-mysql.cpp" +) +target_link_libraries(soci_odbc_mysql_tests PRIVATE soci_tests_common SOCI::ODBC) + +set(SOCI_ODBC_TEST_MYSQL_CONNSTR "FILEDSN=${CMAKE_CURRENT_SOURCE_DIR}/test-mysql.dsn" CACHE STRING "Connection string for the ODBC MySQL test") + +add_test( + NAME soci_odbc_mysql_tests + COMMAND soci_odbc_mysql_tests "${SOCI_ODBC_TEST_MYSQL_CONNSTR}" "--invisibles" +) + if(WIN32) set(TEST_PGSQL_DSN "test-postgresql-win64.dsn") else() set(TEST_PGSQL_DSN "test-postgresql.dsn") endif() -soci_backend_test( - NAME postgresql - BACKEND ODBC - DEPENDS ODBC - SOURCE test-odbc-postgresql.cpp - CONNSTR "FILEDSN=${CMAKE_CURRENT_SOURCE_DIR}/${TEST_PGSQL_DSN}") + +add_executable(soci_odbc_postgresql_tests + "test-odbc-postgresql.cpp" +) +target_link_libraries(soci_odbc_postgresql_tests PRIVATE soci_tests_common SOCI::ODBC) + +set(SOCI_ODBC_TEST_POSTGRESQL_CONNSTR "FILEDSN=${CMAKE_CURRENT_SOURCE_DIR}/${TEST_PGSQL_DSN}" CACHE STRING "Connection string for the ODBC PostgreSQL test") + +add_test( + NAME soci_odbc_postgresql_tests + COMMAND soci_odbc_postgresql_tests "${SOCI_ODBC_TEST_POSTGRESQL_CONNSTR}" "--invisibles" +) + # TODO: DB2 backend is tested by Travis CI on dedicated VM, separate from ODBC, # in order to test DB2 with ODBC, it would be best to install DB2 driver only. # if (NOT $ENV{TRAVIS}) option(WITH_ODBC_TEST_DB2 "Build ODBC DB2 test" OFF) if (WITH_ODBC_TEST_DB2) - soci_backend_test( - NAME db2 - BACKEND ODBC - SOURCE test-odbc-db2.cpp - CONNSTR "FILEDSN=${CMAKE_CURRENT_SOURCE_DIR}/test-db2.dsn") -else() - message(STATUS "ODBC DB2 test disabled.") + add_executable(soci_odbc_db2_tests + "test-odbc-db2.cpp" + ) + target_link_libraries(osoci_dbc_db2_tests PRIVATE soci_tests_common SOCI::ODBC) + + set(SOCI_ODBC_TEST_DB2_CONNSTR "FILEDSN=${CMAKE_CURRENT_SOURCE_DIR}/test-db2.dsn" CACHE STRING "Connection string for the ODBC DB2 test") + + add_test( + NAME soci_odbc_db2_tests + COMMAND soci_odbc_db2_tests "${SOCI_ODBC_TEST_DB2_CONNSTR}" "--invisibles" + ) endif() diff --git a/tests/odbc/odbc_db2_tests.cpp b/tests/odbc/odbc_db2_tests.cpp new file mode 100644 index 000000000..b3a1c4af0 --- /dev/null +++ b/tests/odbc/odbc_db2_tests.cpp @@ -0,0 +1,276 @@ +// +// 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 "common-tests.h" + +#include "soci/soci.h" +#include "soci/odbc/soci-odbc.h" + +#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(soci::session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST(ID INTEGER, VAL SMALLINT, C CHAR, STR VARCHAR(20), SH SMALLINT, LL BIGINT, 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(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)"; + } +}; + +// +// 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 std::string sql_length(std::string const& s) const + { + return "length(" + s + ")"; + } +}; + +struct table_creator_bigint : table_creator_base +{ + table_creator_bigint(soci::session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE SOCI_TEST (VAL BIGINT)"; + } +}; + +TEST_CASE("ODBC/DB2 long long", "[odbc][db2][longlong]") +{ + const int num_recs = 100; + soci::session sql(backEnd, connectString); + table_creator_bigint table(sql); + + { + long long n; + statement st = (sql.prepare << + "INSERT INTO SOCI_TEST (VAL) VALUES (:val)", use(n)); + for (int i = 0; i < num_recs; i++) + { + n = 1000000000LL + i; + st.execute(); + } + } + { + long long n2; + statement st = (sql.prepare << + "SELECT VAL FROM SOCI_TEST ORDER BY VAL", into(n2)); + st.execute(); + for (int i = 0; i < num_recs; i++) + { + st.fetch(); + CHECK(n2 == 1000000000LL + i); + } + } +} + +TEST_CASE("ODBC/DB2 unsigned long long", "[odbc][db2][unsigned][longlong]") +{ + const int num_recs = 100; + soci::session sql(backEnd, connectString); + table_creator_bigint table(sql); + + { + unsigned long long n; + statement st = (sql.prepare << + "INSERT INTO SOCI_TEST (VAL) VALUES (:val)", use(n)); + for (int i = 0; i < num_recs; i++) + { + n = 1000000000LL + i; + st.execute(); + } + } + { + unsigned long long n2; + statement st = (sql.prepare << + "SELECT VAL FROM SOCI_TEST ORDER BY VAL", into(n2)); + st.execute(); + for (int i = 0; i < num_recs; i++) + { + st.fetch(); + CHECK(n2 == 1000000000LL + i); + } + } +} + +TEST_CASE("ODBC/DB2 vector long long", "[odbc][db2][vector][longlong]") +{ + const std::size_t num_recs = 100; + soci::session sql(backEnd, connectString); + table_creator_bigint table(sql); + + { + std::vector v(num_recs); + for (std::size_t i = 0; i < num_recs; i++) + { + v[i] = 1000000000LL + i; + } + + sql << "INSERT INTO SOCI_TEST (VAL) VALUES (:bi)", use(v); + } + { + std::size_t recs = 0; + + std::vector v(num_recs / 2 + 1); + statement st = (sql.prepare << + "SELECT VAL FROM SOCI_TEST ORDER BY VAL", into(v)); + st.execute(); + while (true) + { + if (!st.fetch()) + { + break; + } + + const std::size_t vsize = v.size(); + for (std::size_t i = 0; i < vsize; i++) + { + CHECK(v[i] == 1000000000LL + + static_cast(recs)); + recs++; + } + } + CHECK(recs == num_recs); + } +} + +TEST_CASE("ODBC/DB2 vector unsigned long long", "[odbc][db2][vector][unsigned][longlong]") +{ + const std::size_t num_recs = 100; + soci::session sql(backEnd, connectString); + table_creator_bigint table(sql); + + { + std::vector v(num_recs); + for (std::size_t i = 0; i < num_recs; i++) + { + v[i] = 1000000000LL + i; + } + + sql << "INSERT INTO SOCI_TEST (VAL) VALUES (:bi)", use(v); + } + { + std::size_t recs = 0; + + std::vector v(num_recs / 2 + 1); + statement st = (sql.prepare << + "SELECT VAL FROM SOCI_TEST ORDER BY VAL", into(v)); + st.execute(); + while (true) + { + if (!st.fetch()) + { + break; + } + + const std::size_t vsize = v.size(); + for (std::size_t i = 0; i < vsize; i++) + { + CHECK(v[i] == 1000000000LL + + static_cast(recs)); + recs++; + } + } + CHECK(recs == num_recs); + } + + std::cout << "test odbc_db2_unsigned_long_long_vector passed" << std::endl; +} + + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/odbc/odbc_ms_access_tests.cpp b/tests/odbc/odbc_ms_access_tests.cpp new file mode 100644 index 000000000..43d58bd0b --- /dev/null +++ b/tests/odbc/odbc_ms_access_tests.cpp @@ -0,0 +1,139 @@ +// +// 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 "common-tests.h" + +#include "soci/soci.h" +#include "soci/odbc/soci-odbc.h" + +#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(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, val integer, c char, " + "str varchar(20), sh integer, ll number, ul number, " + "d float, 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 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 &connstr) + : test_context_base(backend, connstr) {} + + 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 fromDual(std::string const &sql) const + { + return sql; + } + + std::string toDate(std::string const &datdt_string) const + { + return "#" + datdt_string + "#"; + } + + std::string to_date_time(std::string const &datdt_string) const + { + return "#" + datdt_string + "#"; + } + + virtual std::string sql_length(std::string const& s) const + { + return "len(" + s + ")"; + } +}; + + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/odbc/odbc_mssql_tests.cpp b/tests/odbc/odbc_mssql_tests.cpp new file mode 100644 index 000000000..0ec3e3e6b --- /dev/null +++ b/tests/odbc/odbc_mssql_tests.cpp @@ -0,0 +1,241 @@ +// +// 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 "common-tests.h" + +#include "soci/soci.h" +#include "soci/odbc/soci-odbc.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]") +{ + soci::session sql(backEnd, connectString); + + struct long_text_table_creator : public table_creator_base + { + explicit long_text_table_creator(soci::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(); + CHECK_NOTHROW(( + 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, ll bigint, 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)"; + } +}; + +struct table_creator_for_clob : table_creator_base +{ + table_creator_for_clob(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, s text)"; + } +}; + +struct table_creator_for_xml : table_creator_base +{ + table_creator_for_xml(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, x xml)"; + } +}; + +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 identity(1, 1), val integer)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backend, + std::string const &connstr) + : test_context_base(backend, connstr) {} + + table_creator_base* table_creator_1(soci::session& s) const override + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(soci::session& s) const override + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(soci::session& s) const override + { + return new table_creator_three(s); + } + + table_creator_base * table_creator_4(soci::session& s) const override + { + return new table_creator_for_get_affected_rows(s); + } + + tests::table_creator_base* table_creator_clob(soci::session& s) const override + { + return new table_creator_for_clob(s); + } + + tests::table_creator_base* table_creator_xml(soci::session& s) const override + { + return new table_creator_for_xml(s); + } + + tests::table_creator_base* table_creator_get_last_insert_id(soci::session& s) const override + { + return new table_creator_for_get_last_insert_id(s); + } + + bool has_real_xml_support() const override + { + return true; + } + + std::string to_date_time(std::string const &datdt_string) const override + { + return "convert(datetime, \'" + datdt_string + "\', 120)"; + } + + bool has_multiple_select_bug() const override + { + // 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; + } + + std::string sql_length(std::string const& s) const override + { + return "len(" + s + ")"; + } +}; + + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/odbc/odbc_mysql_tests.cpp b/tests/odbc/odbc_mysql_tests.cpp new file mode 100644 index 000000000..dfef37180 --- /dev/null +++ b/tests/odbc/odbc_mysql_tests.cpp @@ -0,0 +1,65 @@ +// +// 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 "mysql/mysql_tests.h" + +#include "soci/soci.h" +#include "soci/odbc/soci-odbc.h" + +#include +#include +#include +#include +#include + +std::string connectString; +backend_factory const &backEnd = *soci::factory_odbc(); + +class test_context_odbc : public test_context +{ +public: + using test_context::test_context; + + bool truncates_uint64_to_int64() const override + { + // The ODBC driver of MySQL truncates values bigger then INT64_MAX. + // There are open bugs related to this issue: + // - https://bugs.mysql.com/bug.php?id=95978 + // - https://bugs.mysql.com/bug.php?id=61114 + // Driver version 8.0.31 seems to have fixed this (https://github.com/mysql/mysql-connector-odbc/commit/e78da1344247752f76a082de51cfd36d5d2dd98f), + // but we use an older version in the AppVeyor builds. + return true; + } + + table_creator_base* table_creator_blob(soci::session&) const override + { + // Blobs are not supported + return nullptr; + } +}; + + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/odbc/odbc_postgresql_tests.cpp b/tests/odbc/odbc_postgresql_tests.cpp new file mode 100644 index 000000000..206ef1466 --- /dev/null +++ b/tests/odbc/odbc_postgresql_tests.cpp @@ -0,0 +1,307 @@ +// +// 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 "common-tests.h" + +#include "soci/soci.h" +#include "soci/odbc/soci-odbc.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. + snprintf(buf, sizeof(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, ll bigint, 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)"; + } +}; + +struct table_creator_for_xml : table_creator_base +{ + table_creator_for_xml(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, x xml)"; + } +}; + +struct table_creator_for_clob : table_creator_base +{ + table_creator_for_clob(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, s text)"; + } +}; + +// +// Support for SOCI Common Tests +// + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backend, + std::string const &connstr) + : test_context_base(backend, connstr), + m_verDriver(get_driver_version()) + { + std::cout << "Using ODBC driver version " << m_verDriver << "\n"; + } + + table_creator_base * table_creator_1(soci::session& s) const override + { + return new table_creator_one(s); + } + + table_creator_base * table_creator_2(soci::session& s) const override + { + return new table_creator_two(s); + } + + table_creator_base * table_creator_3(soci::session& s) const override + { + return new table_creator_three(s); + } + + table_creator_base * table_creator_4(soci::session& s) const override + { + return new table_creator_for_get_affected_rows(s); + } + + table_creator_base* table_creator_xml(soci::session& s) const override + { + return new table_creator_for_xml(s); + } + + table_creator_base* table_creator_clob(soci::session& s) const override + { + return new table_creator_for_clob(s); + } + + bool has_real_xml_support() const override + { + return true; + } + + std::string to_date_time(std::string const &datdt_string) const override + { + return "timestamptz(\'" + datdt_string + "\')"; + } + + bool has_fp_bug() const override + { + // The bug with using insufficiently many digits for double values was + // only fixed in 9.03.0400 version of the ODBC driver (see commit + // 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); + } + + std::string fix_crlf_if_necessary(std::string const& s) const override + { + // Version 9.03.0300 (ancient, but still used on AppVeyor CI) is known + // to have a bug which replaces new lines, i.e. LF characters, with CR + // LF when reading CLOBs. Assume it was also fixed in later versions. + if ( m_verDriver.is_initialized() && odbc_version(9, 3, 300) < m_verDriver ) + return s; + + std::string s2; + s2.reserve(s.size()); + for (std::size_t i = 0; i < s.size(); ++i) + { + if (s[i] == '\r') + continue; + + s2 += s[i]; + } + + return s2; + } + + std::string sql_length(std::string const& s) const override + { + return "char_length(" + s + ")"; + } + +private: + odbc_version get_driver_version() const + { + try + { + soci::session sql(get_backend_factory(), get_connect_string()); + odbc_session_backend* const + odbc_session = static_cast(sql.get_backend()); + if (!odbc_session) + { + std::cerr << "Failed to get odbc_session_backend?\n"; + return odbc_version(); + } + + char driver_ver[1024]; + SQLSMALLINT len = sizeof(driver_ver); + SQLRETURN rc = SQLGetInfo(odbc_session->hdbc_, SQL_DRIVER_VER, + driver_ver, len, &len); + if (soci::is_odbc_error(rc)) + { + std::cerr << "Retrieving ODBC driver version failed: " + << rc << "\n"; + return odbc_version(); + } + + odbc_version v; + if (!v.init_from_string(driver_ver)) + { + std::cerr << "Unknown ODBC driver version format: \"" + << driver_ver << "\"\n"; + } + + return v; + } + catch ( ... ) + { + // Failure getting the version is not fatal. + return odbc_version(); + } + } + + odbc_version const m_verDriver; +}; + + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/oracle/CMakeLists.txt b/tests/oracle/CMakeLists.txt index 290111d1c..39819b956 100644 --- a/tests/oracle/CMakeLists.txt +++ b/tests/oracle/CMakeLists.txt @@ -1,16 +1,11 @@ -############################################################################### -# -# 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) -# -############################################################################### +add_executable(soci_oracle_tests + "test-oracle.cpp" +) +target_link_libraries(soci_oracle_tests PRIVATE soci_tests_common SOCI::Oracle) -soci_backend_test( - BACKEND Oracle - DEPENDS Oracle - SOURCE test-oracle.cpp - CONNSTR "service=orcl user=scott password=tiger") +set(SOCI_ORACLE_TEST_CONNSTR "service=orcl user=scott password=tiger" CACHE STRING "The connection string to use for Oracle tests") + +add_test( + NAME soci_oracle_tests + COMMAND soci_oracle_tests "${SOCI_ORACLE_TEST_CONNSTR}" "--invisibles" +) diff --git a/tests/oracle/oracle_tests.cpp b/tests/oracle/oracle_tests.cpp new file mode 100644 index 000000000..6443391db --- /dev/null +++ b/tests/oracle/oracle_tests.cpp @@ -0,0 +1,1560 @@ +// +// // Copyright (C) 2004-2007 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 + +#include "common-tests.h" + +#include "soci/soci.h" +#include "soci/oracle/soci-oracle.h" + +#include +#include +#include +#include +#include +#include + +using namespace soci; +using namespace soci::tests; + +std::string connectString; +backend_factory const &backEnd = *soci::factory_oracle(); + +struct table_creator_for_timestamp : public tests::table_creator_base +{ + table_creator_for_timestamp(soci::session &sql) : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, t timestamp (6))"; + } +}; + +// Extra tests for date/time +TEST_CASE("Oracle datetime", "[oracle][datetime]") +{ + soci::session sql(backEnd, connectString); + + { + std::time_t now = std::time(NULL); + std::tm t1, t2; + t2 = *std::localtime(&now); + + sql << "select t from (select :t as t from dual)", + into(t1), use(t2); + + 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]; + strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &t2); + + std::string t_out; + std::string format("MM-DD-YYYY HH24:MI:SS"); + sql << "select to_char(t, :format) from (select :t as t from dual)", + into(t_out), use(format), use(t2); + + CHECK(t_out == std::string(buf)); + } + + { + // date and time - before year 2000 + std::time_t then = std::time(NULL) - 17*365*24*60*60; + std::tm t1, t2; + t2 = *std::localtime(&then); + + sql << "select t from (select :t as t from dual)", + into(t1), use(t2); + + 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]; + strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &t2); + + std::string t_out; + std::string format("MM-DD-YYYY HH24:MI:SS"); + sql << "select to_char(t, :format) from (select :t as t from dual)", + into(t_out), use(format), use(t2); + + CHECK(t_out == std::string(buf)); + } + + { + // date and time - between years 1- 2201 + soci::session sql(backEnd, connectString); + + table_creator_for_timestamp tableCreator(sql); + + for(int i = 100; i <= 2201; i = i + 50) + { + char t[10]; + snprintf(t, sizeof(t), "%04d", i); + + std::string date = std::string(t) + "-03-28 14:06:13"; + std::tm t1 {}, t2 {}, t4 {}; + + std::istringstream is(date.c_str()); + is.imbue(std::locale(setlocale(LC_ALL, nullptr))); + is >> std::get_time(&t2, "%Y-%m-%d %H:%M:%S"); + REQUIRE(!is.fail()); + + std::tm t3 = t2; + sql << "select t from (select :t as t from dual)", + into(t1), use(t3); + + char buf1[25]; + char buf2[25]; + strftime(buf1, sizeof(buf1), "%Y-%m-%d %H:%M:%S", &t1); + strftime(buf2, sizeof(buf2), "%Y-%m-%d %H:%M:%S", &t2); + std::cout << "buf1 = " << buf1 << ", buf2 = " << buf2 << std::endl; + CHECK(std::string(buf1) == std::string(buf2)); + 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((1900 + t1.tm_year) == i); + + sql << "insert into soci_test(id, t) values(:i, :t)", use(i), use(t3); + sql << "select t from soci_test where id = :i", use(i), into(t4); + CHECK(t4.tm_sec == t2.tm_sec); + CHECK(t4.tm_min == t2.tm_min); + CHECK(t4.tm_hour == t2.tm_hour); + CHECK(t4.tm_mday == t2.tm_mday); + CHECK(t4.tm_mon == t2.tm_mon); + CHECK(t4.tm_year == t2.tm_year); + CHECK((1900 + t4.tm_year) == i); + } + } +} + +// explicit calls test +TEST_CASE("Oracle explicit calls", "[oracle]") +{ + soci::session sql(backEnd, connectString); + + statement st(sql); + st.alloc(); + int i = 0; + st.exchange(into(i)); + st.prepare("select 7 from dual"); + st.define_and_bind(); + st.execute(1); + CHECK(i == 7); +} + +// DDL test + +// nested statement test +// (the same syntax is used for output cursors in PL/SQL) + +struct basic_table_creator : public table_creator_base +{ + basic_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << + "create table soci_test (" + " id number(5) not null," + " name varchar2(100)," + " code number(5)" + ")"; + } +}; + +TEST_CASE("Oracle nested statement", "[oracle][blob]") +{ + soci::session sql(backEnd, connectString); + basic_table_creator tableCreator(sql); + + int id; + std::string name; + { + statement st1 = (sql.prepare << + "insert into soci_test (id, name) values (:id, :name)", + use(id), use(name)); + + id = 1; name = "John"; st1.execute(1); + id = 2; name = "Anna"; st1.execute(1); + id = 3; name = "Mike"; st1.execute(1); + } + + statement stInner(sql); + statement stOuter = (sql.prepare << + "select cursor(select name from soci_test order by id)" + " from soci_test where id = 1", + into(stInner)); + stInner.exchange(into(name)); + stOuter.execute(); + stOuter.fetch(); + + std::vector names; + while (stInner.fetch()) { names.push_back(name); } + + REQUIRE(names.size() == 3); + CHECK(names[0] == "John"); + CHECK(names[1] == "Anna"); + CHECK(names[2] == "Mike"); +} + + +// ROWID test +TEST_CASE("Oracle rowid", "[oracle][rowid]") +{ + soci::session sql(backEnd, connectString); + basic_table_creator tableCreator(sql); + + sql << "insert into soci_test(id, name) values(7, \'John\')"; + + rowid rid(sql); + sql << "select rowid from soci_test where id = 7", into(rid); + + int id; + std::string name; + sql << "select id, name from soci_test where rowid = :rid", + into(id), into(name), use(rid); + + CHECK(id == 7); + CHECK(name == "John"); +} + +// Stored procedures +struct procedure_creator : procedure_creator_base +{ + procedure_creator(soci::session & sql) + : procedure_creator_base(sql) + { + sql << + "create or replace procedure soci_test(output out varchar2," + "input in varchar2) as " + "begin output := input; end;"; + } +}; + +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 + { + 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); + CHECK(out == in); + } +} + +// bind into user-defined objects +struct string_holder +{ + string_holder() {} + string_holder(const char* s) : s_(s) {} + string_holder(std::string s) : s_(s) {} + std::string get() const { return s_; } +private: + std::string s_; +}; + +namespace soci +{ + template <> + struct type_conversion + { + typedef std::string base_type; + static void from_base(const std::string &s, indicator /* ind */, + string_holder &sh) + { + sh = string_holder(s); + } + + static void to_base(const string_holder &sh, std::string &s, indicator &ind) + { + s = sh.get(); + ind = i_ok; + } + }; +} + +struct in_out_procedure_creator : public procedure_creator_base +{ + in_out_procedure_creator(soci::session & sql) + : procedure_creator_base(sql) + { + sql << "create or replace procedure soci_test(s in out varchar2)" + " as begin s := s || s; end;"; + } +}; + +struct returns_null_procedure_creator : public procedure_creator_base +{ + returns_null_procedure_creator(soci::session & sql) + : procedure_creator_base(sql) + { + sql << "create or replace procedure soci_test(s in out varchar2)" + " as begin s := NULL; end;"; + } +}; + +TEST_CASE("Oracle user-defined objects", "[oracle][type_conversion]") +{ + soci::session sql(backEnd, connectString); + { + 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); + + 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); + CHECK(dynamicOut.get() == "my string"); + } +} + +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 + { + in_out_procedure_creator procedureCreator(sql); + + 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"); + } +} + +TEST_CASE("Oracle null user-defined objects in/out", "[oracle][null][type_conversion]") +{ + soci::session sql(backEnd, connectString); + + // 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); + CHECK(ind == i_null); +} + +// test bulk insert features +TEST_CASE("Oracle bulk insert", "[oracle][insert][bulk]") +{ + soci::session sql(backEnd, connectString); + + basic_table_creator tableCreator(sql); + + // verify exception is thrown if vectors of unequal size are passed in + { + std::vector ids; + ids.push_back(1); + ids.push_back(2); + std::vector codes; + codes.push_back(1); + + 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) + { + std::string const error = e.what(); + CAPTURE(error); + CHECK(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) + { + std::string const error = e.what(); + CAPTURE(error); + CHECK(error.find("Bind variable size mismatch") + != std::string::npos); + } + } + + // verify partial insert occurs when one of the records is bad + { + std::vector ids; + ids.push_back(100); + ids.push_back(1000000); // too big for column + + try + { + sql << "insert into soci_test (id) values(:id)", use(ids, "id"); + FAIL("expected exception not thrown"); + } + catch (soci_error const &e) + { + 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(); + int count(7); + sql << "select count(*) from soci_test", into(count); + CHECK(count == 1); + sql << "delete from soci_test"; + } + + // test insert + { + std::vector ids; + for (int i = 0; i != 3; ++i) + { + ids.push_back(i+10); + } + + statement st = (sql.prepare << "insert into soci_test(id) values(:id)", + use(ids)); + st.execute(1); + int count; + sql << "select count(*) from soci_test", into(count); + CHECK(count == 3); + } + + //verify an exception is thrown if into vector is zero length + { + std::vector ids; + 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; + CHECK_THROWS_AS((sql << "insert into soci_test(id) values(:id)", use(ids)), soci_error); + } + + // test "no data" condition + { + std::vector inds(3); + std::vector ids_out(3); + statement st = (sql.prepare << "select id from soci_test where 1=0", + into(ids_out, inds)); + + // false return value means "no data" + CHECK(st.execute(1) == false); + + // that's it - nothing else is guaranteed + // and nothing else is to be tested here + } + + // test NULL indicators + { + std::vector ids(3); + sql << "select id from soci_test", into(ids); + + std::vector inds_in; + inds_in.push_back(i_ok); + inds_in.push_back(i_null); + inds_in.push_back(i_ok); + + std::vector new_codes; + new_codes.push_back(10); + new_codes.push_back(11); + new_codes.push_back(10); + + sql << "update soci_test set code = :code where id = :id", + use(new_codes, inds_in), use(ids); + + std::vector inds_out(3); + std::vector codes(3); + + sql << "select code from soci_test", into(codes, inds_out); + 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 + // and no indicator was provided + { + std::string msg; + std::vector intos(3); + try + { + sql << "select code from soci_test", into(intos); + FAIL("expected exception not thrown"); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Null value fetched and no indicator defined." ); + } + } + + // test basic select + { + const size_t sz = 3; + std::vector inds(sz); + std::vector ids_out(sz); + statement st = (sql.prepare << "select id from soci_test", + into(ids_out, inds)); + const bool gotData = st.execute(true); + 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) + { + std::vector ids_out(2); + statement st = (sql.prepare << "select id from soci_test", + into(ids_out)); + + st.execute(); + REQUIRE(ids_out.size() == 2); + bool gotData = st.fetch(); + CHECK(gotData); + REQUIRE(ids_out.size() == 2); + CHECK(ids_out[0] == 10); + CHECK(ids_out[1] == 11); + gotData = st.fetch(); + CHECK(gotData); + REQUIRE(ids_out.size() == 1); + CHECK(ids_out[0] == 12); + gotData = st.fetch(); + CHECK(gotData == false); + } + + // verify resizing happens if vector is larger + // than number of rows returned + { + std::vector ids_out(4); // one too many + statement st2 = (sql.prepare << "select id from soci_test", + into(ids_out)); + bool gotData = st2.execute(true); + CHECK(gotData); + REQUIRE(ids_out.size() == 3); + CHECK(ids_out[0] == 10); + CHECK(ids_out[2] == 12); + } + + // verify resizing happens properly during fetch() + { + std::vector more; + more.push_back(13); + more.push_back(14); + sql << "insert into soci_test(id) values(:id)", use(more); + + std::vector ids(2); + statement st3 = (sql.prepare << "select id from soci_test", into(ids)); + bool gotData = st3.execute(true); + CHECK(gotData); + CHECK(ids[0] == 10); + CHECK(ids[1] == 11); + + gotData = st3.fetch(); + CHECK(gotData); + CHECK(ids[0] == 12); + CHECK(ids[1] == 13); + + gotData = st3.fetch(); + CHECK(gotData); + REQUIRE(ids.size() == 1); + CHECK(ids[0] == 14); + + gotData = st3.fetch(); + CHECK(gotData == false); + } +} + +// more tests for bulk fetch +TEST_CASE("Oracle bulk fetch", "[oracle][fetch][bulk]") +{ + soci::session sql(backEnd, connectString); + + basic_table_creator tableCreator(sql); + + std::vector in; + for (int i = 1; i <= 10; ++i) + { + in.push_back(i); + } + + sql << "insert into soci_test (id) values(:id)", use(in); + + int count(0); + sql << "select count(*) from soci_test", into(count); + CHECK(count == 10); + + // verify that the exception is thrown when trying to resize + // the output vector to the size that is bigger than that + // at the time of binding + { + std::vector out(4); + statement st = (sql.prepare << + "select id from soci_test", into(out)); + + st.execute(); + + st.fetch(); + 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(); + FAIL("expected exception not thrown"); + } + catch (soci_error const &e) + { + CHECK(e.get_error_message() == + "Increasing the size of the output vector is not supported."); + } + } + + // on the other hand, downsizing is OK + { + std::vector out(4); + statement st = (sql.prepare << + "select id from soci_test", into(out)); + + st.execute(); + + st.fetch(); + 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(); + 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(); + 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(); + CHECK(gotData == false); // end of data + } +} + +struct person +{ + int id; + std::string firstName; + string_holder lastName; //test mapping of type_conversion-based types + std::string gender; +}; + +// Object-Relational Mapping +// Note: Use the values class as shown below in type_conversions +// to achieve object relational mapping. The values class should +// not be used directly in any other fashion. +namespace soci +{ + // name-based conversion + template<> struct type_conversion + { + typedef values base_type; + + static void from_base(values const &v, indicator /* ind */, person &p) + { + // ignoring possibility that the whole object might be NULL + + p.id = v.get("ID"); + p.firstName = v.get("FIRST_NAME"); + p.lastName = v.get("LAST_NAME"); + p.gender = v.get("GENDER", "unknown"); + } + + static void to_base(person const & 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; + } + }; +} + +struct person_table_creator : public table_creator_base +{ + person_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id numeric(5,0) NOT NULL," + << " last_name varchar2(20), first_name varchar2(20), " + " gender varchar2(10))"; + } +}; + +struct times100_procedure_creator : public procedure_creator_base +{ + times100_procedure_creator(soci::session & sql) + : procedure_creator_base(sql) + { + sql << "create or replace procedure soci_test(id in out number)" + " as begin id := id * 100; end;"; + } +}; + +TEST_CASE("Oracle ORM", "[oracle][orm]") +{ + soci::session sql(backEnd, connectString); + + { + person_table_creator tableCreator(sql); + + person p; + p.id = 1; + p.lastName = "Smith"; + p.firstName = "Pat"; + sql << "insert into soci_test(id, first_name, last_name, gender) " + << "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p); + + // p should be unchanged + CHECK(p.id == 1); + CHECK(p.firstName == "Pat"); + CHECK(p.lastName.get() == "Smith"); + + person p1; + sql << "select * from soci_test", into(p1); + 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 + CHECK(p.id == 1); + CHECK(p.firstName == "Patricia"); + CHECK(p.lastName.get() == "Smith"); + // Note: gender is now "unknown" because of the mapping, not "" + CHECK(p.gender == "unknown"); + + person p2; + sql << "select * from soci_test", into(p2); + CHECK(p2.id == 1); + CHECK(p2.firstName == "Patricia"); + CHECK(p2.lastName.get() == "Smith"); + + // insert a second row so we can test fetching + person p3; + p3.id = 2; + p3.firstName = "Joe"; + p3.lastName = "Smith"; + sql << "insert into soci_test(id, first_name, last_name, gender) " + << "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p3); + + person p4; + statement st = (sql.prepare << "select * from soci_test order by id", + into(p4)); + + st.execute(); + bool gotData = st.fetch(); + CHECK(gotData); + CHECK(p4.id == 1); + CHECK(p4.firstName == "Patricia"); + + gotData = st.fetch(); + CHECK(gotData); + CHECK(p4.id == 2); + CHECK(p4.firstName == "Joe"); + gotData = st.fetch(); + CHECK(gotData == false); + } + + // test with stored procedure + { + times100_procedure_creator procedureCreator(sql); + + person p; + p.id = 1; + p.firstName = "Pat"; + p.lastName = "Smith"; + procedure proc = (sql.prepare << "soci_test(:ID)", use(p)); + proc.execute(1); + 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); + + 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) + { + CHECK(e.get_error_message() == + "Null value not allowed for this type"); + } + + procedure proc = (sql.prepare << "soci_test(:GENDER)", + use(p)); + proc.execute(1); + CHECK(p.gender == "unknown"); + + } +} + +// Experimental support for position based O/R Mapping + +// additional type for position-based test +struct person2 +{ + int id; + std::string firstName; + std::string lastName; + std::string gender; +}; + +// additional type for stream-like test +struct person3 : person2 {}; + +namespace soci +{ + // position-based conversion + template<> struct type_conversion + { + typedef values base_type; + + static void from_base(values const &v, indicator /* ind */, person2 &p) + { + p.id = v.get(0); + p.firstName = v.get(1); + p.lastName = v.get(2); + p.gender = v.get(3, "whoknows"); + } + + // What about the "to" part? Does it make any sense to have it? + }; + + // stream-like conversion + template<> struct type_conversion + { + typedef values base_type; + + static void from_base(values const &v, indicator /* ind */, person3 &p) + { + v >> p.id >> p.firstName >> p.lastName >> p.gender; + } + // TODO: The "to" part is certainly needed. + }; +} + +TEST_CASE("Oracle ORM by index", "[oracle][orm]") +{ + soci::session sql(backEnd, connectString); + + person_table_creator tableCreator(sql); + + person p; + p.id = 1; + p.lastName = "Smith"; + p.firstName = "Patricia"; + sql << "insert into soci_test(id, first_name, last_name, gender) " + << "values(:ID, :FIRST_NAME, :LAST_NAME, :GENDER)", use(p); + + // test position-based conversion + person2 p3; + sql << "select id, first_name, last_name, gender from soci_test", into(p3); + 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); + CHECK(p4.id == 1); + CHECK(p4.firstName == "Patricia"); + CHECK(p4.lastName == "Smith"); + CHECK(p4.gender == "F"); +} + +// +// Backwards compatibility - support use of large strings with +// columns of type LONG +/// +struct long_table_creator : public table_creator_base +{ + long_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(l long)"; + } +}; + +TEST_CASE("Oracle large strings as long", "[oracle][compatibility]") +{ + soci::session sql(backEnd, connectString); + long_table_creator creator(sql); + + const std::string::size_type max = 32768; + std::string in(max, 'X'); + + sql << "insert into soci_test values(:l)", use(in); + + std::string out; + sql << "select l from soci_test", into(out); + + CHECK(out.size() == max); + CHECK(in == out); +} + +// test for modifiable and const use elements +TEST_CASE("Oracle const and modifiable parameters", "[oracle][use]") +{ + soci::session sql(backEnd, connectString); + + int i = 7; + sql << "begin " + "select 2 * :i into :i from dual; " + "end;", use(i); + CHECK(i == 14); + + const int j = 7; + try + { + sql << "begin " + "select 2 * :i into :i from dual;" + " end;", use(j); + + FAIL("expected exception not thrown"); + } + catch (soci_error const & e) + { + CHECK(e.get_error_message() == + "Attempted modification of const use element"); + } +} + +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))"; + } +}; + +// test using the result of to_number() which uses the default NUMBER type, +// with precision == scale == 0, with rowset +TEST_CASE("Oracle to_number with rowset", "[oracle][rowset][to_number]") +{ + soci::session sql(backEnd, connectString); + + soci::rowset + rs = (sql.prepare << "select to_number('123456789012345') from dual"); + double d = rs.begin()->get(0); + ASSERT_EQUAL_EXACT(d, 123456789012345); + const double pi = 3.14; + rs = (sql.prepare << "select to_number(:t) from dual", use(pi)); + d = rs.begin()->get(0); + ASSERT_EQUAL_EXACT(d, 3.14); +} + +// long long test +TEST_CASE("Oracle long long", "[oracle][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 + { + 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); + } +} + +// Test the DDL and metadata functionality +TEST_CASE("Oracle DDL with metadata", "[oracle][ddl]") +{ + soci::session sql(backEnd, connectString); + + // note: prepare_column_descriptions expects l-value + std::string ddl_t1 = "DDL_T1"; + std::string ddl_t2 = "DDL_T2"; + std::string ddl_t3 = "DDL_T3"; + + // single-expression variant: + sql.create_table(ddl_t1).column("I", soci::dt_integer).column("J", soci::dt_integer); + + // check whether this table was created: + + bool ddl_t1_found = false; + bool ddl_t2_found = false; + bool ddl_t3_found = false; + std::string table_name; + soci::statement st = (sql.prepare_table_names(), into(table_name)); + st.execute(); + while (st.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); + + // check whether ddl_t1 has the right structure: + + bool i_found = false; + bool j_found = false; + bool other_found = false; + soci::column_info ci; + soci::statement st1 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st1.execute(); + while (st1.fetch()) + { + if (ci.name == "I") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "J") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + j_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(other_found == false); + + // two more tables: + + // separately defined columns: + // (note: statement is executed when ddl object goes out of scope) + { + soci::ddl_type ddl = sql.create_table(ddl_t2); + ddl.column("I", soci::dt_integer); + ddl.column("J", soci::dt_integer); + ddl.column("K", soci::dt_integer)("not null"); + ddl.primary_key("t2_pk", "J"); + } + + sql.add_column(ddl_t1, "K", soci::dt_integer); + sql.add_column(ddl_t1, "BIG", soci::dt_string, 0); // "unlimited" length -> CLOB + sql.drop_column(ddl_t1, "I"); + + // or with constraint as in t2: + sql.add_column(ddl_t2, "M", soci::dt_integer)("not null"); + + // third table with a foreign key to the second one + { + soci::ddl_type ddl = sql.create_table(ddl_t3); + ddl.column("X", soci::dt_integer); + ddl.column("Y", soci::dt_integer); + ddl.foreign_key("t3_fk", "X", ddl_t2, "J"); + } + + // check if all tables were created: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + soci::statement st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found); + CHECK(ddl_t3_found); + + // check if ddl_t1 has the right structure (it was altered): + + i_found = false; + j_found = false; + bool k_found = false; + bool big_found = false; + other_found = false; + soci::statement st3 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st3.execute(); + while (st3.fetch()) + { + if (ci.name == "J") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + j_found = true; + } + else if (ci.name == "K") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + k_found = true; + } + else if (ci.name == "BIG") + { + CHECK(ci.type == soci::dt_string); + CHECK(ci.precision == 0); // "unlimited" for strings + big_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found == false); + CHECK(j_found); + CHECK(k_found); + CHECK(big_found); + CHECK(other_found == false); + + // check if ddl_t2 has the right structure: + + i_found = false; + j_found = false; + k_found = false; + bool m_found = false; + other_found = false; + soci::statement st4 = (sql.prepare_column_descriptions(ddl_t2), into(ci)); + st4.execute(); + while (st4.fetch()) + { + if (ci.name == "I") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "J") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); // primary key + j_found = true; + } + else if (ci.name == "K") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); + k_found = true; + } + else if (ci.name == "M") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); + m_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(k_found); + CHECK(m_found); + CHECK(other_found == false); + + sql.drop_table(ddl_t1); + sql.drop_table(ddl_t3); // note: this must be dropped before ddl_t2 + sql.drop_table(ddl_t2); + + // check if all tables were dropped: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found == false); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); + + int i = -1; + sql << "select length(" + sql.empty_blob() + ") from dual", into(i); + CHECK(i == 0); + sql << "select " + sql.nvl() + "(1, 2) from dual", into(i); + CHECK(i == 1); + sql << "select " + sql.nvl() + "(NULL, 2) from dual", into(i); + CHECK(i == 2); +} + +// Test the bulk iterators functionality +TEST_CASE("Bulk iterators", "[oracle][bulkiters]") +{ + soci::session sql(backEnd, connectString); + + sql << "create table t (i integer)"; + + // test bulk iterators with basic types + { + std::vector v; + v.push_back(10); + v.push_back(20); + v.push_back(30); + v.push_back(40); + v.push_back(50); + + std::size_t begin = 2; + std::size_t end = 5; + sql << "insert into t (i) values (:v)", soci::use(v, begin, end); + + v.clear(); + v.resize(20); + begin = 5; + end = 20; + sql << "select i from t", soci::into(v, begin, end); + + CHECK(end == 8); + for (std::size_t i = 0; i != 5; ++i) + { + CHECK(v[i] == 0); + } + CHECK(v[5] == 30); + CHECK(v[6] == 40); + CHECK(v[7] == 50); + for (std::size_t i = end; i != 20; ++i) + { + CHECK(v[i] == 0); + } + } + + sql << "delete from t"; + + // test bulk iterators with user types + { + std::vector v; + v.push_back(MyInt(10)); + v.push_back(MyInt(20)); + v.push_back(MyInt(30)); + v.push_back(MyInt(40)); + v.push_back(MyInt(50)); + + std::size_t begin = 2; + std::size_t end = 5; + sql << "insert into t (i) values (:v)", soci::use(v, begin, end); + + v.clear(); + for (std::size_t i = 0; i != 20; ++i) + { + v.push_back(MyInt(-1)); + } + + begin = 5; + end = 20; + sql << "select i from t", soci::into(v, begin, end); + + CHECK(end == 8); + for (std::size_t i = 0; i != 5; ++i) + { + CHECK(v[i].get() == -1); + } + CHECK(v[5].get() == 30); + CHECK(v[6].get() == 40); + CHECK(v[7].get() == 50); + for (std::size_t i = end; i != 20; ++i) + { + CHECK(v[i].get() == -1); + } + } + + sql << "drop table t"; +} + +// +// Support for soci Common Tests +// + +struct table_creator_one : public table_creator_base +{ + table_creator_one(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id number(10,0), val number(8,0), c char, " + "str varchar2(20), sh number, ll 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(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(num_float number, num_int numeric(4,0)," + " name varchar2(20), sometime date, 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 varchar2(100) not null, " + "phone varchar2(15))"; + } +}; + +struct table_creator_four : public table_creator_base +{ + table_creator_four(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val number)"; + } +}; + +struct table_creator_for_xml : table_creator_base +{ + table_creator_for_xml(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, x xmltype)"; + } +}; + +struct table_creator_for_clob : table_creator_base +{ + table_creator_for_clob(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, s clob)"; + } +}; + +struct table_creator_for_blob : public tests::table_creator_base +{ + table_creator_for_blob(soci::session &sql) : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, b blob)"; + } +}; + +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 override + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(soci::session& s) const override + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(soci::session& s) const override + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(soci::session& s) const override + { + return new table_creator_four(s); + } + + table_creator_base* table_creator_clob(soci::session& s) const override + { + return new table_creator_for_clob(s); + } + + table_creator_base* table_creator_blob(soci::session& s) const override + { + return new table_creator_for_blob(s); + } + + table_creator_base* table_creator_xml(soci::session& s) const override + { + return new table_creator_for_xml(s); + } + + std::string to_xml(std::string const& x) const override + { + return "xmltype(" + x + ")"; + } + + std::string from_xml(std::string const& x) const override + { + // Notice that using just x.getCLOBVal() doesn't work, only + // table.x.getCLOBVal() or (x).getCLOBVal(), as used here, does. + return "(" + x + ").getCLOBVal()"; + } + + bool has_real_xml_support() const override + { + return true; + } + + bool treats_empty_strings_as_null() const override + { + return true; + } + + std::string to_date_time(std::string const &datdt_string) const override + { + return "to_date('" + datdt_string + "', 'YYYY-MM-DD HH24:MI:SS')"; + } + + std::string sql_length(std::string const& s) const override + { + // Oracle treats empty strings as NULLs, but we want to return the + // length of 0 for them for consistency with the other backends, so use + // nvl() explicitly to achieve this. + return "nvl(length(" + s + "), 0)"; + } +}; + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/postgresql/CMakeLists.txt b/tests/postgresql/CMakeLists.txt index 02b4d535d..7e1b02c68 100644 --- a/tests/postgresql/CMakeLists.txt +++ b/tests/postgresql/CMakeLists.txt @@ -1,16 +1,11 @@ -############################################################################### -# -# 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) -# -############################################################################### +add_executable(soci_postgresql_tests + "test-postgresql.cpp" +) +target_link_libraries(soci_postgresql_tests PRIVATE soci_tests_common SOCI::PostgreSQL) -soci_backend_test( - BACKEND PostgreSQL - DEPENDS PostgreSQL - SOURCE test-postgresql.cpp - CONNSTR "dbname=soci_test") +set(SOCI_POSTGRESQL_TEST_CONNSTR "dbname=soci_test" CACHE STRING "The connection string to use for PostgreSQL tests") + +add_test( + NAME soci_postgresql_tests + COMMAND soci_postgresql_tests "${SOCI_POSTGRESQL_TEST_CONNSTR}" "--invisibles" +) diff --git a/tests/postgresql/postgresql_tests.cpp b/tests/postgresql/postgresql_tests.cpp new file mode 100644 index 000000000..df9d6d024 --- /dev/null +++ b/tests/postgresql/postgresql_tests.cpp @@ -0,0 +1,1425 @@ +// +// 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 + +#include "common-tests.h" + +#include "soci/soci.h" +#include "soci/postgresql/soci-postgresql.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 + +enum TestStringEnum +{ + VALUE_STR_1=0, + VALUE_STR_2, + VALUE_STR_3 +}; + +enum TestIntEnum +{ + VALUE_INT_1=0, + VALUE_INT_2, + VALUE_INT_3 +}; + +namespace soci { +template <> struct type_conversion +{ + typedef std::string base_type; + static void from_base(const std::string & v, indicator & ind, TestStringEnum & p) + { + if ( ind == i_null ) + throw soci_error("Null value not allowed for this type"); + + if ( v.compare("A") == 0 ) + p = TestStringEnum::VALUE_STR_1; + else if ( v.compare("B") == 0 ) + p = TestStringEnum::VALUE_STR_2; + else if ( v.compare("C") == 0 ) + p = TestStringEnum::VALUE_STR_3; + else + throw soci_error("Value not allowed for this type"); + } + static void to_base(TestStringEnum & p, std::string & v, indicator & ind) + { + switch ( p ) + { + case TestStringEnum::VALUE_STR_1: + v = "A"; + ind = i_ok; + return; + case TestStringEnum::VALUE_STR_2: + v = "B"; + ind = i_ok; + return; + case TestStringEnum::VALUE_STR_3: + v = "C"; + ind = i_ok; + return; + default: + throw soci_error("Value not allowed for this type"); + } + } +}; + +template <> struct type_conversion +{ + typedef int base_type; + static void from_base(const int & v, indicator & ind, TestIntEnum & p) + { + if ( ind == i_null ) + throw soci_error("Null value not allowed for this type"); + + switch( v ) + { + case 0: + case 1: + case 2: + p = (TestIntEnum)v; + ind = i_ok; + return; + default: + throw soci_error("Null value not allowed for this type"); + } + } + static void to_base(TestIntEnum & p, int & v, indicator & ind) + { + switch( p ) + { + case TestIntEnum::VALUE_INT_1: + case TestIntEnum::VALUE_INT_2: + case TestIntEnum::VALUE_INT_3: + v = (int)p; + ind = i_ok; + return; + default: + throw soci_error("Value not allowed for this type"); + } + } +}; +} + +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); + + int server_version_num; + sql << "show server_version_num", into(server_version_num); + if ( server_version_num >= 120000 ) + { + WARN("Skipping test because OIDs are no longer supported in PostgreSQL " + << server_version_num); + return; + } + + 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; + + sql << "select id, name from soci_test where oid = :rid", + into(id), into(name), use(rid); + + 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 + + sql << + "create or replace function soci_test(msg varchar) " + "returns varchar as $$ " + "declare x int := 1;" + "begin " + " return msg; " + "end $$ language plpgsql"; + } + +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; + + statement st = (sql.prepare << + "select soci_test(:input)", + into(out), + use(in, "input")); + + st.execute(true); + CHECK(out == in); + + // explicit procedure syntax + { + procedure proc = (sql.prepare << + "soci_test(:input)", + into(out), use(in, "input")); + + proc.execute(true); + CHECK(out == in); + } +} + +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; + row r; + sql << "select val from soci_test", into(i2); + sql << "select val from soci_test", into(r); + + CHECK(i2 == i1); + CHECK(r.get(0) == i1); + + sql << "update soci_test set val = true"; + sql << "select val from soci_test", into(i2); + sql << "select val from soci_test", into(r); + CHECK(i2 == 1); + CHECK(r.get(0) == 1); +} + +struct uuid_table_creator : table_creator_base +{ + uuid_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(val uuid)"; + } +}; + +// uuid test +TEST_CASE("PostgreSQL uuid", "[postgresql][uuid]") +{ + soci::session sql(backEnd, connectString); + + uuid_table_creator tableCreator(sql); + + std::string v1("cd2dcb78-3817-442e-b12a-17c7e42669a0"); + sql << "insert into soci_test(val) values(:val)", use(v1); + + std::string v2; + sql << "select val from soci_test", into(v2); + + CHECK(v2 == v1); +} + +// dynamic backend test -- currently skipped by default +TEST_CASE("PostgreSQL dynamic backend", "[postgresql][backend][.]") +{ + 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 = std::tm(), t2 = std::tm(), t3 = std::tm(); + + sql << "select :sd::date, :sd::time, :sd::timestamp", + use(someDate, "sd"), into(t1), into(t2), into(t3); + + // 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); + CHECK(props.get_db_type() == soci::db_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! +} + +// Test the DDL and metadata functionality +TEST_CASE("PostgreSQL DDL with metadata", "[postgresql][ddl]") +{ + soci::session sql(backEnd, connectString); + + // note: prepare_column_descriptions expects l-value + std::string ddl_t1 = "ddl_t1"; + std::string ddl_t2 = "ddl_t2"; + std::string ddl_t3 = "ddl_t3"; + + // single-expression variant: + sql.create_table(ddl_t1).column("i", soci::dt_integer).column("j", soci::dt_integer); + + // check whether this table was created: + + bool ddl_t1_found = false; + bool ddl_t2_found = false; + bool ddl_t3_found = false; + std::string table_name; + soci::statement st = (sql.prepare_table_names(), into(table_name)); + st.execute(); + while (st.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); + + // check whether ddl_t1 has the right structure: + + bool i_found = false; + bool j_found = false; + bool other_found = false; + soci::column_info ci; + soci::statement st1 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st1.execute(); + while (st1.fetch()) + { + if (ci.name == "i") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.dataType == soci::db_int32); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "j") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.dataType == soci::db_int32); + CHECK(ci.nullable); + j_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(other_found == false); + + // two more tables: + + // separately defined columns: + // (note: statement is executed when ddl object goes out of scope) + { + soci::ddl_type ddl = sql.create_table(ddl_t2); + ddl.column("i", soci::dt_integer); + ddl.column("j", soci::dt_integer); + ddl.column("k", soci::dt_integer)("not null"); + ddl.primary_key("t2_pk", "j"); + } + + sql.add_column(ddl_t1, "k", soci::dt_integer); + sql.add_column(ddl_t1, "big", soci::dt_string, 0); // "unlimited" length -> text + sql.drop_column(ddl_t1, "i"); + + // or with constraint as in t2: + sql.add_column(ddl_t2, "m", soci::dt_integer)("not null"); + + // third table with a foreign key to the second one + { + soci::ddl_type ddl = sql.create_table(ddl_t3); + ddl.column("x", soci::dt_integer); + ddl.column("y", soci::dt_integer); + ddl.foreign_key("t3_fk", "x", ddl_t2, "j"); + } + + // check if all tables were created: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + soci::statement st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found); + CHECK(ddl_t3_found); + + // check if ddl_t1 has the right structure (it was altered): + + i_found = false; + j_found = false; + bool k_found = false; + bool big_found = false; + other_found = false; + soci::statement st3 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st3.execute(); + while (st3.fetch()) + { + if (ci.name == "j") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.dataType == soci::db_int32); + CHECK(ci.nullable); + j_found = true; + } + else if (ci.name == "k") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.dataType == soci::db_int32); + CHECK(ci.nullable); + k_found = true; + } + else if (ci.name == "big") + { + CHECK(ci.type == soci::dt_string); + CHECK(ci.dataType == soci::db_string); + CHECK(ci.precision == 0); // "unlimited" for strings + big_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found == false); + CHECK(j_found); + CHECK(k_found); + CHECK(big_found); + CHECK(other_found == false); + + // check if ddl_t2 has the right structure: + + i_found = false; + j_found = false; + k_found = false; + bool m_found = false; + other_found = false; + soci::statement st4 = (sql.prepare_column_descriptions(ddl_t2), into(ci)); + st4.execute(); + while (st4.fetch()) + { + if (ci.name == "i") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.dataType == soci::db_int32); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "j") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.dataType == soci::db_int32); + CHECK(ci.nullable == false); // primary key + j_found = true; + } + else if (ci.name == "k") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.dataType == soci::db_int32); + CHECK(ci.nullable == false); + k_found = true; + } + else if (ci.name == "m") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.dataType == soci::db_int32); + CHECK(ci.nullable == false); + m_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(k_found); + CHECK(m_found); + CHECK(other_found == false); + + sql.drop_table(ddl_t1); + sql.drop_table(ddl_t3); // note: this must be dropped before ddl_t2 + sql.drop_table(ddl_t2); + + // check if all tables were dropped: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found == false); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); + + int i = -1; + sql << "select lo_unlink(" + sql.empty_blob() + ")", into(i); + CHECK(i == 1); + sql << "select " + sql.nvl() + "(1, 2)", into(i); + CHECK(i == 1); + sql << "select " + sql.nvl() + "(NULL, 2)", into(i); + CHECK(i == 2); +} + +// Test the bulk iterators functionality +TEST_CASE("Bulk iterators", "[postgresql][bulkiters]") +{ + soci::session sql(backEnd, connectString); + + sql << "create table t (i integer)"; + + // test bulk iterators with basic types + { + std::vector v; + v.push_back(10); + v.push_back(20); + v.push_back(30); + v.push_back(40); + v.push_back(50); + + std::size_t begin = 2; + std::size_t end = 5; + sql << "insert into t (i) values (:v)", soci::use(v, begin, end); + + v.clear(); + v.resize(20); + begin = 5; + end = 20; + sql << "select i from t", soci::into(v, begin, end); + + CHECK(end == 8); + for (std::size_t i = 0; i != 5; ++i) + { + CHECK(v[i] == 0); + } + CHECK(v[5] == 30); + CHECK(v[6] == 40); + CHECK(v[7] == 50); + for (std::size_t i = end; i != 20; ++i) + { + CHECK(v[i] == 0); + } + } + + sql << "delete from t"; + + // test bulk iterators with user types + { + std::vector v; + v.push_back(MyInt(10)); + v.push_back(MyInt(20)); + v.push_back(MyInt(30)); + v.push_back(MyInt(40)); + v.push_back(MyInt(50)); + + std::size_t begin = 2; + std::size_t end = 5; + sql << "insert into t (i) values (:v)", soci::use(v, begin, end); + + v.clear(); + for (std::size_t i = 0; i != 20; ++i) + { + v.push_back(MyInt(-1)); + } + + begin = 5; + end = 20; + sql << "select i from t", soci::into(v, begin, end); + + CHECK(end == 8); + for (std::size_t i = 0; i != 5; ++i) + { + CHECK(v[i].get() == -1); + } + CHECK(v[5].get() == 30); + CHECK(v[6].get() == 40); + CHECK(v[7].get() == 50); + for (std::size_t i = end; i != 20; ++i) + { + CHECK(v[i].get() == -1); + } + } + + sql << "drop table t"; +} + + +// false_bind_variable_inside_identifier +struct test_false_bind_variable_inside_identifier_table_creator : table_creator_base +{ + test_false_bind_variable_inside_identifier_table_creator(soci::session & sql) + : table_creator_base(sql) + , msession(sql) + { + + try + { + sql << "CREATE TABLE soci_test( \"column_with:colon\" integer)"; + sql << "CREATE TYPE \"type_with:colon\" AS ENUM ('en_one', 'en_two');"; + sql << "CREATE FUNCTION \"function_with:colon\"() RETURNS integer LANGUAGE 'sql' AS " + "$BODY$" + " SELECT \"column_with:colon\" FROM soci_test LIMIT 1; " + "$BODY$;" + ; + } + catch(...) + { + drop(); + } + + } + ~test_false_bind_variable_inside_identifier_table_creator(){ + drop(); + } +private: + void drop() + { + try + { + msession << "DROP FUNCTION IF EXISTS \"function_with:colon\"();"; + msession << "DROP TYPE IF EXISTS \"type_with:colon\" ;"; + } + catch (soci_error const&){} + } + soci::session& msession; +}; +TEST_CASE("false_bind_variable_inside_identifier", "[postgresql][bind-variables]") +{ + std::string col_name; + int fct_return_value; + std::string type_value; + + { + soci::session sql(backEnd, connectString); + test_false_bind_variable_inside_identifier_table_creator tableCreator(sql); + + sql << "insert into soci_test(\"column_with:colon\") values(2020)"; + sql << "SELECT column_name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'soci_test';", into(col_name); + sql << "SELECT \"function_with:colon\"() ;", into(fct_return_value); + sql << "SELECT unnest(enum_range(NULL::\"type_with:colon\")) ORDER BY 1 LIMIT 1;", into(type_value); + } + + CHECK(col_name.compare("column_with:colon") == 0); + CHECK(fct_return_value == 2020); + CHECK(type_value.compare("en_one")==0); +} + +// test_enum_with_explicit_custom_type_string_rowset +struct test_enum_with_explicit_custom_type_string_rowset : table_creator_base +{ + test_enum_with_explicit_custom_type_string_rowset(soci::session & sql) + : table_creator_base(sql) + , msession(sql) + { + try + { + sql << "CREATE TYPE EnumType AS ENUM ('A','B','C');"; + sql << "CREATE TABLE soci_test (Type EnumType NOT NULL DEFAULT 'A');"; + } + catch (...) + { + drop(); + } + + } + ~test_enum_with_explicit_custom_type_string_rowset() + { + drop(); + } + +private: + void drop() + { + try + { + msession << "drop table if exists soci_test;"; + msession << "DROP TYPE IF EXISTS EnumType ;"; + } + catch (soci_error const& e){ + std::cerr << e.what() << std::endl; + } + } + soci::session& msession; +}; + +TEST_CASE("test_enum_with_explicit_custom_type_string_rowset", "[postgresql][bind-variables]") +{ + TestStringEnum test_value = TestStringEnum::VALUE_STR_2; + TestStringEnum type_value; + + { + soci::session sql(backEnd, connectString); + test_enum_with_explicit_custom_type_string_rowset tableCreator(sql); + + statement s1 = (sql.prepare << "insert into soci_test values(:val);", use(test_value, "val")); + statement s2 = (sql.prepare << "SELECT Type FROM soci_test;"); + + s1.execute(false); + + soci::row result; + s2.define_and_bind(); + s2.exchange_for_rowset(soci::into(result)); + s2.execute(true); + + type_value = result.get("type"); + } + + CHECK(type_value==TestStringEnum::VALUE_STR_2); +} + +TEST_CASE("test_enum_with_explicit_custom_type_string_into", "[postgresql][bind-variables]") +{ + TestStringEnum test_value = TestStringEnum::VALUE_STR_2; + TestStringEnum type_value; + + { + soci::session sql(backEnd, connectString); + test_enum_with_explicit_custom_type_string_rowset tableCreator(sql); + + statement s1 = (sql.prepare << "insert into soci_test values(:val);", use(test_value, "val")); + statement s2 = (sql.prepare << "SELECT Type FROM soci_test;", into(type_value)); + + s1.execute(false); + s2.execute(true); + } + + CHECK(type_value==TestStringEnum::VALUE_STR_2); +} + +// test_enum_with_explicit_custom_type_int_rowset +struct test_enum_with_explicit_custom_type_int_rowset : table_creator_base +{ + test_enum_with_explicit_custom_type_int_rowset(soci::session & sql) + : table_creator_base(sql) + , msession(sql) + { + + try + { + sql << "CREATE TABLE soci_test( Type smallint)"; + ; + } + catch (...) + { + drop(); + } + + } + ~test_enum_with_explicit_custom_type_int_rowset() + { + drop(); + } + +private: + void drop() + { + try + { + msession << "drop table if exists soci_test;"; + } + catch (soci_error const& e){ + std::cerr << e.what() << std::endl; + } + } + soci::session& msession; +}; + +TEST_CASE("test_enum_with_explicit_custom_type_int_rowset", "[postgresql][bind-variables]") +{ + TestIntEnum test_value = TestIntEnum::VALUE_INT_2; + TestIntEnum type_value; + + { + soci::session sql(backEnd, connectString); + test_enum_with_explicit_custom_type_int_rowset tableCreator(sql); + + statement s1 = (sql.prepare << "insert into soci_test(Type) values(:val)", use(test_value, "val")); + statement s2 = (sql.prepare << "SELECT Type FROM soci_test ;"); + + s1.execute(false); + + soci::row result; + s2.define_and_bind(); + s2.exchange_for_rowset(soci::into(result)); + s2.execute(true); + + type_value = result.get("type"); + } + + CHECK(type_value==TestIntEnum::VALUE_INT_2); +} + +TEST_CASE("test_enum_with_explicit_custom_type_int_into", "[postgresql][bind-variables]") +{ + TestIntEnum test_value = TestIntEnum::VALUE_INT_2; + TestIntEnum type_value; + + { + soci::session sql(backEnd, connectString); + test_enum_with_explicit_custom_type_int_rowset tableCreator(sql); + + statement s1 = (sql.prepare << "insert into soci_test(Type) values(:val)", use(test_value, "val")); + statement s2 = (sql.prepare << "SELECT Type FROM soci_test ;", into(type_value)); + + s1.execute(false); + s2.execute(true); + } + + CHECK(type_value==TestIntEnum::VALUE_INT_2); +} + +// false_bind_variable_inside_identifier +struct table_creator_colon_in_double_quotes_in_single_quotes : + table_creator_base +{ + table_creator_colon_in_double_quotes_in_single_quotes(soci::session & sql) + : table_creator_base(sql) + { + sql << "CREATE TABLE soci_test( \"column_with:colon\" text)"; + } + +}; +TEST_CASE("colon_in_double_quotes_in_single_quotes", + "[postgresql][bind-variables]") +{ + std::string return_value; + + { + soci::session sql(backEnd, connectString); + table_creator_colon_in_double_quotes_in_single_quotes + tableCreator(sql); + + sql << "insert into soci_test(\"column_with:colon\") values('hello " + "it is \"10:10\"')"; + sql << "SELECT \"column_with:colon\" from soci_test ;", into + (return_value); + } + + CHECK(return_value == "hello it is \"10:10\""); +} + +// +// 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, ll bigint, 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)"; + } +}; + +struct table_creator_for_xml : table_creator_base +{ + table_creator_for_xml(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, x xml)"; + } +}; + +struct table_creator_for_clob : table_creator_base +{ + table_creator_for_clob(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test(id integer, s text)"; + } +}; + +struct table_creator_for_blob : public tests::table_creator_base +{ + table_creator_for_blob(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, b oid)"; + } +}; + + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backend, std::string const &connstr) + : test_context_base(backend, connstr) + {} + + table_creator_base* table_creator_1(soci::session& s) const override + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(soci::session& s) const override + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(soci::session& s) const override + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(soci::session& s) const override + { + return new table_creator_for_get_affected_rows(s); + } + + table_creator_base* table_creator_xml(soci::session& s) const override + { + return new table_creator_for_xml(s); + } + + table_creator_base* table_creator_clob(soci::session& s) const override + { + return new table_creator_for_clob(s); + } + + table_creator_base* table_creator_blob(soci::session& s) const override + { + return new table_creator_for_blob(s); + } + + bool has_real_xml_support() const override + { + return true; + } + + std::string to_date_time(std::string const &datdt_string) const override + { + return "timestamptz(\'" + datdt_string + "\')"; + } + + bool has_fp_bug() const override + { + return false; + } + + std::string sql_length(std::string const& s) const override + { + return "char_length(" + s + ")"; + } +}; + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +} diff --git a/tests/sqlite3/CMakeLists.txt b/tests/sqlite3/CMakeLists.txt index 6d8bee49b..288bef28c 100644 --- a/tests/sqlite3/CMakeLists.txt +++ b/tests/sqlite3/CMakeLists.txt @@ -1,16 +1,11 @@ -############################################################################### -# -# 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) -# -############################################################################### +add_executable(soci_sqlite3_tests + "test-sqlite3.cpp" +) +target_link_libraries(soci_sqlite3_tests PRIVATE soci_tests_common SOCI::SQLite3) -soci_backend_test( - BACKEND SQLite3 - DEPENDS SQLite3 - SOURCE test-sqlite3.cpp - CONNSTR ":memory:") +set(SOCI_SQLITE3_TEST_CONNSTR ":memory:" CACHE STRING "The connection string to use for SQLite3 tests") + +add_test( + NAME soci_sqlite3_tests + COMMAND soci_sqlite3_tests "${SOCI_SQLITE3_TEST_CONNSTR}" "--invisibles" +) diff --git a/tests/sqlite3/sqlite3_tests.cpp b/tests/sqlite3/sqlite3_tests.cpp new file mode 100644 index 000000000..95fc5dc5c --- /dev/null +++ b/tests/sqlite3/sqlite3_tests.cpp @@ -0,0 +1,913 @@ +// +// 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 "common-tests.h" + +#include +#include + +#include +#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 +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"; +} + +class SetupForeignKeys +{ +public: + SetupForeignKeys(soci::session& sql) + : m_sql(sql) + { + m_sql << + "create table parent (" + " id integer primary key" + ")"; + + m_sql << + "create table child (" + " id integer primary key," + " parent integer," + " foreign key(parent) references parent(id)" + ")"; + + m_sql << "insert into parent(id) values(1)"; + m_sql << "insert into child(id, parent) values(100, 1)"; + } + + ~SetupForeignKeys() + { + m_sql << "drop table child"; + m_sql << "drop table parent"; + } + +private: + SetupForeignKeys(const SetupForeignKeys&); + SetupForeignKeys& operator=(const SetupForeignKeys&); + + soci::session& m_sql; +}; + +TEST_CASE("SQLite foreign keys are disabled by default", "[sqlite][foreignkeys]") +{ + soci::session sql(backEnd, connectString); + + SetupForeignKeys setupForeignKeys(sql); + + sql << "delete from parent where id = 1"; + + int parent = 0; + sql << "select parent from child where id = 100 ", into(parent); + + CHECK(parent == 1); +} + +TEST_CASE("SQLite foreign keys are enabled by foreign_keys option", "[sqlite][foreignkeys]") +{ + soci::session sql(backEnd, "dbname=:memory: foreign_keys=on"); + + SetupForeignKeys setupForeignKeys(sql); + + CHECK_THROWS_WITH(sql << "delete from parent where id = 1", + "sqlite3_statement_backend::loadOne: FOREIGN KEY " + "constraint failed while executing " + "\"delete from parent where id = 1\"."); +} + +class SetupAutoIncrementTable +{ +public: + SetupAutoIncrementTable(soci::session& sql) + : m_sql(sql) + { + m_sql << + "create table t(" + " id integer primary key autoincrement," + " name text" + ")"; + } + + ~SetupAutoIncrementTable() + { + m_sql << "drop table t"; + } + +private: + SetupAutoIncrementTable(const SetupAutoIncrementTable&); + SetupAutoIncrementTable& operator=(const SetupAutoIncrementTable&); + + soci::session& m_sql; +}; + +TEST_CASE("SQLite get_last_insert_id works with AUTOINCREMENT", + "[sqlite][rowid]") +{ + soci::session sql(backEnd, connectString); + SetupAutoIncrementTable createTable(sql); + + sql << "insert into t(name) values('x')"; + sql << "insert into t(name) values('y')"; + + long long val; + sql.get_last_insert_id("t", val); + CHECK(val == 2); +} + +TEST_CASE("SQLite get_last_insert_id with AUTOINCREMENT does not reuse IDs when rows deleted", + "[sqlite][rowid]") +{ + soci::session sql(backEnd, connectString); + SetupAutoIncrementTable createTable(sql); + + sql << "insert into t(name) values('x')"; + sql << "insert into t(name) values('y')"; + + sql << "delete from t where id = 2"; + + long long val; + sql.get_last_insert_id("t", val); + CHECK(val == 2); +} + +class SetupNoAutoIncrementTable +{ +public: + SetupNoAutoIncrementTable(soci::session& sql) + : m_sql(sql) + { + m_sql << + "create table t(" + " id integer primary key," + " name text" + ")"; + } + + ~SetupNoAutoIncrementTable() + { + m_sql << "drop table t"; + } + +private: + SetupNoAutoIncrementTable(const SetupNoAutoIncrementTable&); + SetupNoAutoIncrementTable& operator=(const SetupNoAutoIncrementTable&); + + soci::session& m_sql; +}; + +TEST_CASE("SQLite get_last_insert_id without AUTOINCREMENT reuses IDs when rows deleted", + "[sqlite][rowid]") +{ + soci::session sql(backEnd, connectString); + SetupNoAutoIncrementTable createTable(sql); + + sql << "insert into t(name) values('x')"; + sql << "insert into t(name) values('y')"; + + sql << "delete from t where id = 2"; + + long long val; + sql.get_last_insert_id("t", val); + CHECK(val == 1); +} + +TEST_CASE("SQLite get_last_insert_id throws if table not found", + "[sqlite][rowid]") +{ + soci::session sql(backEnd, connectString); + + long long val; + CHECK_THROWS(sql.get_last_insert_id("notexisting", val)); +} + +class SetupTableWithDoubleQuoteInName +{ +public: + SetupTableWithDoubleQuoteInName(soci::session& sql) + : m_sql(sql) + { + m_sql << + "create table \"t\"\"fff\"(" + " id integer primary key," + " name text" + ")"; + } + + ~SetupTableWithDoubleQuoteInName() + { + m_sql << "drop table \"t\"\"fff\""; + } + +private: + SetupTableWithDoubleQuoteInName(const SetupTableWithDoubleQuoteInName&); + SetupTableWithDoubleQuoteInName& operator=(const SetupTableWithDoubleQuoteInName&); + + soci::session& m_sql; +}; + +TEST_CASE("SQLite get_last_insert_id escapes table name", + "[sqlite][rowid]") +{ + soci::session sql(backEnd, connectString); + SetupTableWithDoubleQuoteInName table(sql); + + long long val; + sql.get_last_insert_id("t\"fff", val); + CHECK(val == 0); +} + +// 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 the DDL and metadata functionality +TEST_CASE("SQLite DDL with metadata", "[sqlite][ddl]") +{ + if (sqlite_api::sqlite3_libversion_number() < 3036000) { + if (sqlite_api::sqlite3_libversion_number() < 3014000) { + WARN("SQLite requires at least version 3.14.0 for column description, detected " << sqlite_api::sqlite3_libversion()); + } + WARN("SQLite requires at least version 3.36.0 for drop column, detected " << sqlite_api::sqlite3_libversion()); + return; + } + soci::session sql(backEnd, connectString); + + // note: prepare_column_descriptions expects l-value + std::string ddl_t1 = "DDL_T1"; + std::string ddl_t2 = "DDL_T2"; + std::string ddl_t3 = "DDL_T3"; + + // single-expression variant: + sql.create_table(ddl_t1).column("I", soci::dt_integer).column("J", soci::dt_integer); + + // check whether this table was created: + + bool ddl_t1_found = false; + bool ddl_t2_found = false; + bool ddl_t3_found = false; + std::string table_name; + soci::statement st = (sql.prepare_table_names(), into(table_name)); + st.execute(); + while (st.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); + + // check whether ddl_t1 has the right structure: + + bool i_found = false; + bool j_found = false; + bool other_found = false; + soci::column_info ci; + soci::statement st1 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st1.execute(); + while (st1.fetch()) + { + if (ci.name == "I") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "J") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + j_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(other_found == false); + + // two more tables: + + // separately defined columns: + // (note: statement is executed when ddl object goes out of scope) + { + soci::ddl_type ddl = sql.create_table(ddl_t2); + ddl.column("I", soci::dt_integer); + ddl.column("J", soci::dt_integer); + ddl.column("K", soci::dt_integer)("not null"); + ddl.primary_key("t2_pk", "J"); + } + + sql.add_column(ddl_t1, "K", soci::dt_integer); + sql.add_column(ddl_t1, "BIG", soci::dt_string, 0); // "unlimited" length -> CLOB + sql.drop_column(ddl_t1, "I"); + + // or with constraint as in t2: + sql.add_column(ddl_t2, "M", soci::dt_integer)("not null"); + + // third table with a foreign key to the second one + { + soci::ddl_type ddl = sql.create_table(ddl_t3); + ddl.column("X", soci::dt_integer); + ddl.column("Y", soci::dt_integer); + ddl.foreign_key("t3_fk", "X", ddl_t2, "J"); + } + + // check if all tables were created: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + soci::statement st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found); + CHECK(ddl_t2_found); + CHECK(ddl_t3_found); + + // check if ddl_t1 has the right structure (it was altered): + + i_found = false; + j_found = false; + bool k_found = false; + bool big_found = false; + other_found = false; + soci::statement st3 = (sql.prepare_column_descriptions(ddl_t1), into(ci)); + st3.execute(); + while (st3.fetch()) + { + if (ci.name == "J") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + j_found = true; + } + else if (ci.name == "K") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + k_found = true; + } + else if (ci.name == "BIG") + { + CHECK(ci.type == soci::dt_string); + CHECK(ci.precision == 0); // "unlimited" for strings + big_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found == false); + CHECK(j_found); + CHECK(k_found); + CHECK(big_found); + CHECK(other_found == false); + + // check if ddl_t2 has the right structure: + + i_found = false; + j_found = false; + k_found = false; + bool m_found = false; + other_found = false; + soci::statement st4 = (sql.prepare_column_descriptions(ddl_t2), into(ci)); + st4.execute(); + while (st4.fetch()) + { + if (ci.name == "I") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable); + i_found = true; + } + else if (ci.name == "J") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == true); // primary key -> SQLite default behavior + j_found = true; + } + else if (ci.name == "K") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); + k_found = true; + } + else if (ci.name == "M") + { + CHECK(ci.type == soci::dt_integer); + CHECK(ci.nullable == false); + m_found = true; + } + else + { + other_found = true; + } + } + + CHECK(i_found); + CHECK(j_found); + CHECK(k_found); + CHECK(m_found); + CHECK(other_found == false); + + sql.drop_table(ddl_t1); + sql.drop_table(ddl_t3); // note: this must be dropped before ddl_t2 + sql.drop_table(ddl_t2); + + // check if all tables were dropped: + + ddl_t1_found = false; + ddl_t2_found = false; + ddl_t3_found = false; + st2 = (sql.prepare_table_names(), into(table_name)); + st2.execute(); + while (st2.fetch()) + { + if (table_name == ddl_t1) { ddl_t1_found = true; } + if (table_name == ddl_t2) { ddl_t2_found = true; } + if (table_name == ddl_t3) { ddl_t3_found = true; } + } + + CHECK(ddl_t1_found == false); + CHECK(ddl_t2_found == false); + CHECK(ddl_t3_found == false); +} + +TEST_CASE("SQLite DDL roundrip", "[sqlite][ddl][roundtrip]") +{ + soci::session sql(backEnd, connectString); + test_roundtrip(sql, soci::db_double, std::numeric_limits::max()); + test_roundtrip(sql, soci::db_int8, std::numeric_limits::max()); + test_roundtrip(sql, soci::db_int16, std::numeric_limits::max()); + test_roundtrip(sql, soci::db_int32, std::numeric_limits::max()); + test_roundtrip(sql, soci::db_int64, std::numeric_limits::max()); + test_roundtrip(sql, soci::db_uint8, std::numeric_limits::max()); + test_roundtrip(sql, soci::db_uint16, std::numeric_limits::max()); + test_roundtrip(sql, soci::db_uint32, std::numeric_limits::max()); + test_roundtrip(sql, soci::db_uint64, std::numeric_limits::max()); +} + +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 type_inference_table_creator : table_creator_base +{ + type_inference_table_creator(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(cvc varchar (10), cdec decimal (20), " + "cll bigint, cull unsigned bigint, clls big int, culls unsigned big int)"; + } +}; + +// test for correct type inference form sqlite column type +TEST_CASE("SQLite type inference", "[sqlite][sequence]") +{ + soci::session sql(backEnd, connectString); + + type_inference_table_creator tableCreator(sql); + + std::string cvc = "john"; + double cdec = 12345.0; // integers can be stored precisely in IEEE 754 + long long cll = 1000000000003LL; + unsigned long long cull = 1000000000004ULL; + + sql << "insert into soci_test(cvc, cdec, cll, cull, clls, culls) values(:cvc, :cdec, :cll, :cull, :clls, :culls)", + use(cvc), use(cdec), use(cll), use(cull), use(cll), use(cull); + + { + rowset rs = (sql.prepare << "select * from soci_test"); + rowset::const_iterator it = rs.begin(); + row const& r1 = (*it); + CHECK(r1.get(0) == cvc); + CHECK(r1.get(1) == Approx(cdec)); + CHECK(r1.get(2) == cll); + CHECK(r1.get(3) == cull); + CHECK(r1.get(4) == cll); + CHECK(r1.get(5) == cull); + } +} + +TEST_CASE("SQLite DDL wrappers", "[sqlite][ddl]") +{ + soci::session sql(backEnd, connectString); + + int i = -1; + sql << "select length(" + sql.empty_blob() + ")", into(i); + CHECK(i == 0); + sql << "select " + sql.nvl() + "(1, 2)", into(i); + CHECK(i == 1); + sql << "select " + sql.nvl() + "(NULL, 2)", into(i); + CHECK(i == 2); +} + +struct table_creator_for_get_last_insert_id : table_creator_base +{ + table_creator_for_get_last_insert_id(soci::session & sql) + : 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 long id; + bool result = sql.get_last_insert_id("soci_test", id); + CHECK(result == true); + CHECK(id == 42); +} + +struct table_creator_for_std_tm_bind : table_creator_base +{ + table_creator_for_std_tm_bind(soci::session & sql) + : table_creator_base(sql) + { + sql << "create table soci_test(date datetime)"; + sql << "insert into soci_test (date) values ('2017-04-04 00:00:00')"; + sql << "insert into soci_test (date) values ('2017-04-04 12:00:00')"; + sql << "insert into soci_test (date) values ('2017-04-05 00:00:00')"; + } +}; + +TEST_CASE("SQLite std::tm bind", "[sqlite][std-tm-bind]") +{ + soci::session sql(backEnd, connectString); + table_creator_for_std_tm_bind tableCreator(sql); + + std::time_t datetimeEpoch = 1491307200; // 2017-04-04 12:00:00 + + std::tm datetime = *std::gmtime(&datetimeEpoch); + soci::rowset rs = (sql.prepare << "select date from soci_test where date=:dt", soci::use(datetime)); + + std::vector result; + std::copy(rs.begin(), rs.end(), std::back_inserter(result)); + REQUIRE(result.size() == 1); + result.front().tm_isdst = 0; + CHECK(std::mktime(&result.front()) == std::mktime(&datetime)); +} + +// 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, ll bigint, ul unsigned bigint, " + "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 +// + +struct table_creator_from_str : table_creator_base +{ + table_creator_from_str(soci::session & sql, std::string const& sqlStr) + : table_creator_base(sql) + { + sql << sqlStr; + } +}; + +struct table_creator_for_blob : public tests::table_creator_base +{ + table_creator_for_blob(soci::session & sql) + : tests::table_creator_base(sql) + { + sql << "create table soci_test(id integer, b blob)"; + } +}; + +class test_context : public test_context_base +{ +public: + test_context(backend_factory const &backend, + std::string const &connstr) + : test_context_base(backend, connstr) {} + + table_creator_base* table_creator_1(soci::session& s) const override + { + return new table_creator_one(s); + } + + table_creator_base* table_creator_2(soci::session& s) const override + { + return new table_creator_two(s); + } + + table_creator_base* table_creator_3(soci::session& s) const override + { + return new table_creator_three(s); + } + + table_creator_base* table_creator_4(soci::session& s) const override + { + return new table_creator_for_get_affected_rows(s); + } + + table_creator_base* table_creator_get_last_insert_id(soci::session& s) const override + { + return new table_creator_from_str(s, + "create table soci_test (id integer primary key, val integer)"); + } + + table_creator_base* table_creator_blob(soci::session& s) const override + { + return new table_creator_for_blob(s); + } + + table_creator_base* table_creator_xml(soci::session& s) const override + { + return new table_creator_from_str(s, + "create table soci_test (id integer, x text)"); + } + + std::string to_date_time(std::string const &datdt_string) const override + { + return "datetime(\'" + datdt_string + "\')"; + } + + bool has_fp_bug() const override + { + /* + 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; + } + + bool has_uint64_storage_bug() const override + { + // SQLite processes integers as 8-byte signed values. Values bigger + // than INT64_MAX therefore overflow and are stored as negative values. + return true; + } + + bool enable_std_char_padding(soci::session&) const override + { + // SQLite does not support right padded char type. + return false; + } + + std::string sql_length(std::string const& s) const override + { + return "length(" + s + ")"; + } +}; + + +namespace soci +{ +namespace tests +{ + +std::unique_ptr instantiate_test_context(const soci::backend_factory &backend, const std::string &connection_string) +{ + connectString = connection_string; + return std::make_unique(backend, connection_string); +} + +const backend_factory &create_backend_factory() +{ + return backEnd; +} + +} +}