diff --git a/CMakeLists.txt b/CMakeLists.txt index edb30357..8a21e6e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,37 +1,52 @@ +# +# ELASTICSEARCH CONFIDENTIAL +# __________________ +# +# [2018] Elasticsearch Incorporated. All Rights Reserved. +# +# NOTICE: All information contained herein is, and remains +# the property of Elasticsearch Incorporated and its suppliers, +# if any. The intellectual and technical concepts contained +# herein are proprietary to Elasticsearch Incorporated +# and its suppliers and may be covered by U.S. and Foreign Patents, +# patents in process, and are protected by trade secret or copyright law. +# Dissemination of this information or reproduction of this material +# is strictly forbidden unless prior written permission is obtained +# from Elasticsearch Incorporated. +# # 2.8.6: to generate the _EXPORTS define -cmake_minimum_required(VERSION 2.8.6 FATAL_ERROR) +# 3.1.0: CMAKE_GENERATOR_PLATFORM +cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) + +set(DRIVER_BASE_NAME elasticodbc CACHE STRING + "The base name to give the driver") # driver's version set(DRV_VER_MAJOR 0) set(DRV_VER_MINOR 1) -set(BASE_NAME elasticodbc) -# build a UNICODE driver? (currently only supported way) +# build a UNICODE driver? (the only supported way currently) set(IS_UNICODE 1) #include(GenerateExportHeader) -# https://cmake.org/cmake/help/latest/variable/CMAKE_GENERATOR_PLATFORM.html : -# "The value of this variable should never be modified by project code." -# , BUT: -# https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2015%202017.html : -# "The CMAKE_GENERATOR_PLATFORM variable may be set to specify a target -# platform name (architecture)." if (${WIN32}) - if ($ENV{VSCMD_ARG_TGT_ARCH} MATCHES x64) - set(CMAKE_GENERATOR_PLATFORM x64) + if (${CMAKE_GENERATOR_PLATFORM} MATCHES x64) + #set(CMAKE_GENERATOR_PLATFORM x64) set(TARCH x64) # target arch set(BARCH ) # bits architecture (64 is the default, silent) - else ($ENV{VSCMD_ARG_TGT_ARCH} MATCHES x64) + else (${CMAKE_GENERATOR_PLATFORM} MATCHES x64) set(TARCH x86) set(BARCH 32) - endif ($ENV{VSCMD_ARG_TGT_ARCH} MATCHES x64) + endif (${CMAKE_GENERATOR_PLATFORM} MATCHES x64) message("Building on Windows, ${TARCH}.") +else (${WIN32}) + message(FATAL_ERROR "No support for current platform yet") endif (${WIN32}) # explicit languages support (Cs are defaults) -project(${BASE_NAME} CXX C) +project(${DRIVER_BASE_NAME} CXX C) if (${IS_UNICODE}) set(ENCODING u) # Unicode @@ -41,14 +56,34 @@ endif (${IS_UNICODE}) # driver name # XXX: ANSI/Unicode/32/64 -set(DRV_NAME "${BASE_NAME}${DRV_VER_MAJOR}${ENCODING}${BARCH}") +set(DRV_NAME "${DRIVER_BASE_NAME}${DRV_VER_MAJOR}${ENCODING}${BARCH}") # Turn on the ability to create folders to organize projects (.vcproj) # It creates "CMakePredefinedTargets" folder by default and adds CMake # defined projects like INSTALL.vcproj and ZERO_CHECK.vcproj set_property(GLOBAL PROPERTY USE_FOLDERS ON) - -# Set compiler flags and options. + + +# which version is the code at? +execute_process(COMMAND + git describe --dirty=+ --broken=X --always --tags + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE CMD_RETURN + OUTPUT_VARIABLE CMD_OUTPUT + ERROR_VARIABLE CMD_OUTERR + ) +# WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/${DRV_SRC_DIR}" +if (${CMD_RETURN}) + message(WARNING "Git command failed:") + message("\tret: ${CMD_RETURN}") + message("\tout: ${CMD_OUTPUT}") + message("\terr: ${CMD_OUTERR}") + set(DRV_SRC_VER "n/a") +else (${CMD_RETURN}) + set(DRV_SRC_VER ${CMD_OUTPUT}) +endif (${CMD_RETURN}) + +# Set compiler flags and options. if (${WIN32}) # set the Visual Studio warning level to 4 #set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4") @@ -61,31 +96,40 @@ if (${WIN32}) if (${IS_UNICODE}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DUNICODE /D_UNICODE") endif (${IS_UNICODE}) - # set the version + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DDRV_NAME=${DRV_NAME}") + + # set the all identifiers set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DDRV_VER_MAJOR=${DRV_VER_MAJOR}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DDRV_VER_MINOR=${DRV_VER_MINOR}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DDRV_SRC_VER=${DRV_SRC_VER}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DDRV_ENCODING=${ENCODING}") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DDRV_NAME=${DRV_NAME}") - - #set(LIBCURL_PATH_LIB ${LIBCURL_PATH_LIB}\\libcurl.dll) + # ...including the build type indicator + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /DDRV_BUILD_TYPE=d") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /DDRV_BUILD_TYPE=r") + set(CMAKE_C_FLAGS_RELWITHDEBINFO + "${CMAKE_C_FLAGS_RELWITHDEBINFO} /DDRV_BUILD_TYPE=i") + set(CMAKE_C_FLAGS_MINSIZEREL + "${CMAKE_C_FLAGS_MINSIZEREL} /DDRV_BUILD_TYPE=s") + + # unless building for stripping Release, export the testing functions + # (this will allow RelWithDebInfo buliding and still be able to test it) + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /DTEST_API=") + set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_RELEASE} /DTEST_API=") else (${WIN32}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") - - #set(LIBCURL_PATH_LIB ${LIBCURL_PATH_LIB}/libcurl.so) endif (${WIN32}) message("C flags: ${CMAKE_C_FLAGS} .") set(DRV_SRC_DIR driver) set(DRV_LIB_DIR lib) -set(DRV_BUILD_DIR builds) message("Directories: source: '${DRV_SRC_DIR}', output: '${DRV_LIB_DIR}'.") aux_source_directory(${DRV_SRC_DIR} DRV_SRC) # generate Module definition file (symbols to export) -execute_process(COMMAND +execute_process(COMMAND ${CMAKE_SOURCE_DIR}/${DRV_SRC_DIR}/build_def.bat ${DRV_NAME} - ${CMAKE_SOURCE_DIR}/${DRV_BUILD_DIR}/${DRV_NAME}.def + ${CMAKE_BINARY_DIR}/${DRV_NAME}.def WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/${DRV_SRC_DIR}" RESULT_VARIABLE CMD_RETURN OUTPUT_VARIABLE CMD_OUTPUT @@ -93,7 +137,7 @@ execute_process(COMMAND ) # WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/${DRV_SRC_DIR}" if (${CMD_RETURN}) - message("Generating module .def file failed:") + message(WARNING "Generating module .def file failed:") message("\tret: ${CMD_RETURN}") message("\tout: ${CMD_OUTPUT}") message("\terr: ${CMD_OUTERR}") @@ -103,106 +147,101 @@ endif (${CMD_RETURN}) # # add ODBC-Specification to the project # -if (IS_DIRECTORY $ENV{ODBC_PATH_SRC}) - set(ODBC_PATH_SRC $ENV{ODBC_PATH_SRC}) -else (IS_DIRECTORY $ENV{ODBC_PATH_SRC}) - if (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/ODBC-Specification) - set(ODBC_PATH_SRC ${CMAKE_SOURCE_DIR}/libs/ODBC-Specification) - else (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/ODBC-Specification) - message(FATAL_ERROR "No ODBC-Specification directory found: set " - "environment var ODBC_PATH_SRC or place an " - "'ODBC-Specification' export into local 'lib' dir.") - endif (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/ODBC-Specification) -endif (IS_DIRECTORY $ENV{ODBC_PATH_SRC}) +# TODO: add this as a git subtree and/or as ExternalProject +set(ODBC_PATH_SRC ${CMAKE_SOURCE_DIR}/libs/ODBC-Specification CACHE PATH + "ODBC-Specification source path") +if (NOT IS_DIRECTORY ${ODBC_PATH_SRC}) + message(FATAL_ERROR "No ODBC-Specification directory found: use " + "ODBC_PATH_SRC cmake option or place an 'ODBC-Specification' " + "clone into local 'lib' dir.") +endif (NOT IS_DIRECTORY ${ODBC_PATH_SRC}) message("ODBC-Specification source path: ${ODBC_PATH_SRC} .") -set(ODBC_INC ${ODBC_PATH_SRC}/Windows/inc ) +set(ODBC_INC ${ODBC_PATH_SRC}/Windows/inc) # # add ujson4c to the project # -if (IS_DIRECTORY $ENV{UJSON4C_PATH_SRC}) - set(UJSON4C_PATH_SRC $ENV{UJSON4C_PATH_SRC}) -else (IS_DIRECTORY $ENV{UJSON4C_PATH_SRC}) - if (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/ujson4c) - set(UJSON4C_PATH_SRC ${CMAKE_SOURCE_DIR}/libs/ujson4c) - else (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/ujson4c) - message(FATAL_ERROR "No ujson4c directory found: set environment var " - "UJSON4C_PATH_SRC or place an 'ujson4c' export into local " - "'libs' dir.") - endif (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/ujson4c) -endif (IS_DIRECTORY $ENV{UJSON4C_PATH_SRC}) +# TODO: add this as a git subtree and/or as ExternalProject (with patching) +set(UJSON4C_PATH_SRC ${CMAKE_SOURCE_DIR}/libs/ujson4c CACHE PATH + "Lib ujson4c source path") +if (NOT IS_DIRECTORY ${UJSON4C_PATH_SRC}) + message(FATAL_ERROR "No ujson4c directory found: use UJSON4C_PATH_SRC " + "cmake option or place an 'ujson4c' clone into local 'libs' dir.") +endif (NOT IS_DIRECTORY ${UJSON4C_PATH_SRC}) message("Lib ujson4c source path: ${UJSON4C_PATH_SRC} .") aux_source_directory(${UJSON4C_PATH_SRC}/src DRV_SRC) aux_source_directory(${UJSON4C_PATH_SRC}/3rdparty DRV_SRC) set(UJSON4C_INC ${UJSON4C_PATH_SRC}/src ${UJSON4C_PATH_SRC}/3rdparty ) +# +# add c-timestamp to the project +# +# TODO: add this as a git subtree and/or as ExternalProject +set(CTIMESTAMP_PATH_SRC ${CMAKE_SOURCE_DIR}/libs/c-timestamp CACHE PATH + "Lib c-timestamp source path") +if (NOT IS_DIRECTORY ${CTIMESTAMP_PATH_SRC}) + message(FATAL_ERROR "No c-timestamp directory found: use " + "CTIMESTAMP_PATH_SRC cmake option or place a 'c-timestamp' clone " + "in local 'lib' dir.") +endif (NOT IS_DIRECTORY ${CTIMESTAMP_PATH_SRC}) +message("Lib c-timestamp source path: ${CTIMESTAMP_PATH_SRC} .") +aux_source_directory(${CTIMESTAMP_PATH_SRC}/ DRV_SRC) + # # add libcurl to the project # -# read libcurl's build paths from the environment (assume local dir otherwise) -if (IS_DIRECTORY $ENV{LIBCURL_PATH_BUILD}) - set(LIBCURL_PATH_BUILD $ENV{LIBCURL_PATH_BUILD}) -else (IS_DIRECTORY $ENV{LIBCURL_PATH_BUILD}) - if (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/libcurl) - set(LIBCURL_PATH_BUILD ${CMAKE_SOURCE_DIR}/libs/libcurl) - else (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/libcurl) - message(FATAL_ERROR "No libcurl directory found: set environment var " - "LIBCURL_PATH_BUILD or place a 'libcurl' dir in local source" - "tree, with 'bin' and 'include' subdirs.") - endif (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/libcurl) -endif (IS_DIRECTORY $ENV{LIBCURL_PATH_BUILD}) -message("Lib libcurl build path: ${LIBCURL_PATH_BUILD} .") +# TODO: add this as a git subtree and/or as ExternalProject +set(LIBCURL_LD_PATH + # curl "installs" the .dll and .lib in different directories -> use the + # build dir to find both files in same directory instead of installing + ${CMAKE_SOURCE_DIR}/libs/curl/builds/libcurl-vc-${TARCH}-release-dll-ipv6-sspi-winssl-obj-lib/ + CACHE PATH "Lib curl load library path") +set(LIBCURL_INC_PATH ${CMAKE_SOURCE_DIR}/libs/curl/include CACHE PATH + "Lib curl include path") +if (NOT IS_DIRECTORY ${LIBCURL_LD_PATH} + OR NOT IS_DIRECTORY ${LIBCURL_INC_PATH}) + message(FATAL_ERROR "Missing libcurl lib and/or inc directories: use " + "LIBCURL_LD_PATH and LIBCURL_INC_PATH cmake options or place a built " + "'curl' clone in local 'libs' dir.") +endif() +message("Curl paths load lib: ${LIBCURL_LD_PATH}, inc: ${LIBCURL_INC_PATH} .") # add libcurl as dependency add_library(libcurl SHARED IMPORTED) -set(LIBCURL_INC ${LIBCURL_PATH_BUILD}/include) if (${WIN32}) - set_property(TARGET libcurl PROPERTY IMPORTED_LOCATION - ${LIBCURL_PATH_BUILD}/bin/libcurl.dll) + set_property(TARGET libcurl PROPERTY IMPORTED_LOCATION + ${LIBCURL_LD_PATH}/libcurl${CMAKE_SHARED_LIBRARY_SUFFIX}) set_property(TARGET libcurl PROPERTY IMPORTED_IMPLIB - ${LIBCURL_PATH_BUILD}/lib/libcurl.lib) + ${LIBCURL_LD_PATH}/libcurl${CMAKE_STATIC_LIBRARY_SUFFIX}) else (${WIN32}) - set_property(TARGET libcurl PROPERTY IMPORTED_LOCATION - ${LIBCURL_PATH_BUILD}/lib/libcurl.so) + set_property(TARGET libcurl PROPERTY IMPORTED_LOCATION + ${LIBCURL_LD_PATH}/libcurl${CMAKE_SHARED_LIBRARY_SUFFIX}) endif (${WIN32}) -# -# add c-timestamp to the project -# -if (IS_DIRECTORY $ENV{CTIMESTAMP_PATH_SRC}) - set(CTIMESTAMP_PATH_SRC $ENV{CTIMESTAMP_PATH_SRC}) -else (IS_DIRECTORY $ENV{CTIMESTAMP_PATH_SRC}) - if (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/c-timestamp) - set(CTIMESTAMP_PATH_SRC ${CMAKE_SOURCE_DIR}/libs/c-timestamp) - else (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/c-timestamp) - message(FATAL_ERROR "No c-timestamp directory found: set environment" - " var CTIMESTAMP_PATH_SRC or place an export into " - "'c-timestamp' dir in local source tree.") - endif (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/libs/c-timestamp) -endif (IS_DIRECTORY $ENV{CTIMESTAMP_PATH_SRC}) -message("Lib c-timestamp source path: ${CTIMESTAMP_PATH_SRC} .") -aux_source_directory(${CTIMESTAMP_PATH_SRC}/ DRV_SRC) -set(CTIMESTAMP_INC ${CTIMESTAMP_PATH_SRC}/ ) - message("Driver source files: ${DRV_SRC} .") -message("Driver include paths: " ${ODBC_INC} ${DRV_SRC_DIR} ${LIBCURL_INC} - ${UJSON4C_INC} ${CTIMESTAMP_INC}) +message("Driver include paths: " ${ODBC_INC} ${DRV_SRC_DIR} + ${LIBCURL_INC_PATH} ${UJSON4C_INC} ${CTIMESTAMP_PATH_SRC}) # # finally, set destination library # -add_library(${DRV_NAME} SHARED ${DRV_SRC} ${DRV_BUILD_DIR}/${DRV_NAME}.def) +add_library(${DRV_NAME} SHARED ${DRV_SRC} ${CMAKE_BINARY_DIR}/${DRV_NAME}.def) #generate_export_header(${DRV_NAME}) +target_compile_definitions(${DRV_NAME} PRIVATE "DRIVER_BUILD") -#include_directories(${DRV_SRC_DIR} ${DRV_BUILD_DIR}) -include_directories(${ODBC_INC} ${DRV_SRC_DIR} ${LIBCURL_INC} ${UJSON4C_INC} - ${CTIMESTAMP_INC}) - +include_directories(${ODBC_INC} ${DRV_SRC_DIR} ${LIBCURL_INC_PATH} + ${UJSON4C_INC} ${CTIMESTAMP_PATH_SRC}) target_link_libraries(${DRV_NAME} libcurl) +# add testing project/target +enable_testing() +# ... and testing directory to build +add_subdirectory(test) + +# add instalation project/target +install(TARGETS ${DRV_NAME} DESTINATION ${DRV_LIB_DIR}) + -install(TARGETS ${DRV_NAME} - DESTINATION ${DRV_LIB_DIR} -) +# vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : diff --git a/build.bat b/build.bat index 81a20bf6..a784a50a 100644 --- a/build.bat +++ b/build.bat @@ -1,72 +1,76 @@ -REM This is just a helper script for building the ODBC driver in development. @echo off +REM +REM ELASTICSEARCH CONFIDENTIAL +REM __________________ +REM +REM [2018] Elasticsearch Incorporated. All Rights Reserved. +REM +REM NOTICE: All information contained herein is, and remains +REM the property of Elasticsearch Incorporated and its suppliers, +REM if any. The intellectual and technical concepts contained +REM herein are proprietary to Elasticsearch Incorporated +REM and its suppliers and may be covered by U.S. and Foreign Patents, +REM patents in process, and are protected by trade secret or copyright law. +REM Dissemination of this information or reproduction of this material +REM is strictly forbidden unless prior written permission is obtained +REM from Elasticsearch Incorporated. +REM +REM This is just a helper script for building the ODBC driver in development. setlocal EnableExtensions EnableDelayedExpansion -REM cls +cls -SET ARG="%*" -SET SRC_PATH=%~dp0 -call:SET_TEMP +set DRIVER_BASE_NAME=elasticodbc -REM 32/64 bit argument needs to stay on top, before setting LIBCURL path. -REM presence of '32' or '64': set the Bits/Target ARCHhitecture -if not _%ARG:32=% == _%ARG% ( - set TARCH=x86 - set BARCH=32 -) else ( - set TARCH=x64 - set BARCH=64 -) +set ARG="%*" +set SRC_PATH=%~dp0 +REM "funny" fact: removing 'REM X' from above label definition makes 'cmd' +REM no longer find the label -- why? (check with "> build nobuild") +call:SET_ARCH +call:SET_TEMP +call:SET_CMAKE REM REM List of variables that can be customized REM -if "%BUILD_DIR%" == "" ( +if [%BUILD_DIR%] == [] ( SET BUILD_DIR=%SRC_PATH%\builds ) -if not "%ESODBC_LOG_DIR%" == "" ( +if not [%ESODBC_LOG_DIR%] == [] ( + REM Strip the log level, if any present (format: path?level) for /f "tokens=1 delims=?" %%a in ("%ESODBC_LOG_DIR%") do set LOGGING_DIR=%%a ) -if "%INSTALL_DIR%" == "" ( +if [%INSTALL_DIR%] == [] ( set INSTALL_DIR=%TEMP% ) -if "%LIBCURL_PATH_BUILD%" == "" ( - set LIBCURL_PATH_BUILD=%SRC_PATH%\libs\curl\builds\libcurl-vc-!TARCH!-release-dll-ipv6-sspi-winssl -) - -if "%CMAKE_BIN_PATH%" == "" ( - set CMAKE_BIN_PATH="C:\Program Files\CMake\bin" -) - - REM REM Perform the building steps REM REM presence of 'help'/'?': invoke USAGE "function" and exit -if /i not _%ARG:help=% == _%ARG% ( +if /i not [%ARG:help=%] == [%ARG%] ( call:USAGE %0 goto end -) else if not _%ARG:?=% == _%ARG% ( +) else if not [%ARG:?=%] == [%ARG%] ( call:USAGE %0 goto end ) REM presence of 'proper' or 'clean': invoke respective "functions" -if /i not _%ARG:proper=% == _%ARG% ( +if /i not [%ARG:proper=%] == [%ARG%] ( call:PROPER goto end -) else if /i not _%ARG:clean=% == _%ARG% ( +) else if /i not [%ARG:clean=%] == [%ARG%] ( call:CLEAN ) REM presence of 'setup': invoke SETUP "function" -if /i not _%ARG:setup=% == _%ARG% ( +if /i not [%ARG:setup=%] == [%ARG%] ( call:SETUP ) else ( REM Invoked without 'setup': setting up build vars skipped. @@ -74,51 +78,68 @@ if /i not _%ARG:setup=% == _%ARG% ( where cl.exe >nul 2>&1 if ERRORLEVEL 1 ( echo. - echo Note: building environment not set. Run with /? to see options. + echo ERROR: building environment not set. Run with /? to see options. echo. + goto end ) ) -REM REM presence of 'fetch': invoke FETCH "function" -if /i not _%ARG:fetch=% == _%ARG% ( +if /i not [%ARG:fetch=%] == [%ARG%] ( call:FETCH ) cd %BUILD_DIR% -REM absence of nobuild: invoke BUILD "function" -if /i _%ARG:nobuild=% == _%ARG% ( +REM Presence of 'clearlogs': invoke CLEARLOGS "function" +REM This needs to stay before building happens (else test logs will be +REM whipped) +if /i not [%ARG:clearlogs=%] == [%ARG%] ( + call:CLEARLOGS +) else ( + REM Invoked without 'clearlogs', logs not touched. +) + +REM presence of type: invoke BUILDTYPE "function" +if /i not [%ARG:type=%] == [%ARG%] ( + call:BUILDTYPE +) else ( + echo Invoked without 'type', default applied (see usage^). +) + +REM absence of nobuild: invoke BUILD "function"; +REM 'all' and 'test' arguments presence checked inside the "function". +if /i [%ARG:nobuild=%] == [%ARG%] ( call:BUILD ) else ( echo Invoked with 'nobuild', building skipped. ) -REM presence of 'copy': invoke COPY "function" -if /i not _%ARG:copy=% == _%ARG% ( - call:COPY +REM presence of 'test': invoke TEST "function" +if /i not [%ARG:test=%] == [%ARG%] ( + call:TEST ) else ( - REM Invoked without 'copy': DLLs test installation skipped. + REM Invoked without 'test': tests running skipped. ) -REM presence of 'clearlogs': invoke CLEARLOGS "function" -if /i not _%ARG:clearlogs=% == _%ARG% ( - call:CLEARLOGS +REM presence of 'copy': invoke COPY "function" +if /i not [%ARG:copy=%] == [%ARG%] ( + call:COPY ) else ( - REM Invoked without 'clearlogs', logs not touched. + REM Invoked without 'copy': DLLs test installation skipped. ) REM presence of 'regadd': call REGADD "function" -if /i not _%ARG:regadd=% == _%ARG% ( +if /i not [%ARG:regadd=%] == [%ARG%] ( call:REGADD ) else ( REM Invoked without 'regadd': registry adding skipped. ) REM presence of 'regdel': invoke REGDEL "function" -if /i not _%ARG:regdel=% == _%ARG% ( +if /i not [%ARG:regdel=%] == [%ARG%] ( call:REGDEL ) else ( REM Invoked without 'regadd': registry adding skipped. @@ -133,6 +154,40 @@ REM REM "Functions" REM +REM SET_ARCH function: set/detect the build architecture: +REM Bits and Target ARCHhitecture +:SET_ARCH + REM presence of '32' or '64' arguments? + if not [%ARG:32=%] == [%ARG%] ( + set TARCH=x86 + set BARCH=32 + ) else if not [%ARG:64=%] == [%ARG%] ( + set TARCH=x64 + set BARCH=64 + + REM is the MSVC environment already set up? + ) else if /i [%VSCMD_ARG_TGT_ARCH%] == [x86] ( + set TARCH=x86 + set BARCH=32 + ) else if /i [%VSCMD_ARG_TGT_ARCH%] == [x64] ( + set TARCH=x64 + set BARCH=64 + + + REM neither arguments, nor MSVC environment: read OS architecture + ) else ( + reg query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && ( + set TARCH=x86 + set BARCH=32 + ) || ( + set TARCH=x64 + set BARCH=64 + ) + ) + + goto:eof + + REM if TEMP var not set, set it. :SET_TEMP if exist %TEMP% goto:eof @@ -149,44 +204,75 @@ REM if TEMP var not set, set it. goto:eof +REM function to check and set cmake binary (if installed) +:SET_CMAKE + where cmake.exe >nul 2>&1 + if ERRORLEVEL 1 ( + if exist C:\Progra~1\CMake\bin\cmake.exe ( + REM set CMAKE="C:\Program Files\CMake\bin\cmake.exe" + REM I don't know how to make the for-loop in :COPY work with the + REM long format... + set CMAKE=C:\Progra~1\CMake\bin\cmake.exe + ) else if exist "%CMAKE%" ( + REM Using already set environment path + ) else ( + echo. + echo ERROR: needed cmake executable not found: when installed, + echo either set it in path or in environment variable CMAKE + echo. + goto end + ) + ) else ( + set CMAKE=cmake.exe + ) + echo Using CMAKE binary: %CMAKE% + + goto:eof REM USAGE function: output a usage message :USAGE echo Usage: %1 [argument(s^)] echo. echo The following arguments are supported: - echo help^|*?* : output this message and exit. - echo 32^|64 : setup the architecture to 32- or 64-bit (default^); - echo useful with 'setup' and 'reg*' arguments only; - echo x86 and x64 platforms supported only. - echo setup : invoke MSVC's build environment setup script before - echo building (requires 2017 version or later^). - echo clean : clean the build dir files. - echo proper : clean both the build and libs dir and exit. - echo fetch : fetch, patch and build the dependency libs. - echo nobuild : skip project building (the default is to build^). - echo copy : copy the DLL into the test dir (%INSTALL_DIR%^). - echo regadd : register the driver into the registry; - echo (needs Administrator privileges^). - echo regdel : deregister the driver from the registry; - echo (needs Administrator privileges^). - echo clearlogs : clear the logs (in: %LOGGING_DIR%^). + echo help^|*?* : output this message and exit. + echo 32^|64 : set the architecture to x86 or x64, respectively; + echo if none is specified, autodetection is attempted. + echo setup : invoke MSVC's build environment setup script before + echo building (requires 2017 version or later^). + echo clean : remove all the files in the build dir. + echo proper : clean both the build and libs dir and exit. + echo fetch : fetch, patch and build the dependency libs. + echo nobuild : skip project building (the default is to build^). + echo type=T : selects the build type; T can be one of Debug/Release/ + echo RelWithDebInfo/MinSizeRel^); defaults to Debug. + echo exports : dump the exported symbols in the DLL after - and + echo only if - building the driver. + echo all : build all artifacts (driver and tests^). + echo test : run all the defined tests: invoke the 'all' build, + echo then the 'tests' build. + echo suites : compile and run each test individually, stopping at the + echo first failure; the 'all' or 'test' targets must be + echo built beforehand. + echo copy : copy the driver into the test dir (%INSTALL_DIR%^). + echo regadd : register the driver into the registry; + echo (needs Administrator privileges^). + echo regdel : deregister the driver from the registry; + echo (needs Administrator privileges^). + echo clearlogs : clear the logs (in: %LOGGING_DIR%^). echo. echo Multiple arguments can be used concurrently. echo Invoked with no arguments, the script will only initiate a build. echo Example:^> %1 setup 32 fetch echo. - echo List of settable environment variables: - echo BUILD_DIR : path to folder to hold the build files; - echo now set to: `%BUILD_DIR%`. - echo ESODBC_LOG_DIR : path to folder holding the logging files; - echo now set to: `%ESODBC_LOG_DIR%`. - echo INSTALL_DIR : path to folder to hold the built driver; - echo now set to: `%INSTALL_DIR%`. - echo LIBCURL_PATH_BUILD : path to libcurl library; - echo now set to: `%LIBCURL_PATH_BUILD%`. - echo CMAKE_BIN_PATH : path to cmake executable; - echo now set to: `%CMAKE_BIN_PATH%`. + echo List of read environment variables: + echo BUILD_DIR : folder path to build the driver into; + echo currently set to: `%BUILD_DIR%`. + echo ESODBC_LOG_DIR : folder path to write logging files into; + echo currently set to: `%ESODBC_LOG_DIR%`. + echo INSTALL_DIR : folder path to install the driver into; + echo currently set to: `%INSTALL_DIR%`. + echo CMAKE : cmake executable to use (if not in path^); + echo currently set to: `%CMAKE%`. echo. goto:eof @@ -227,7 +313,7 @@ REM SETUP function: set-up the build environment break ) ) - if "%EDITION%" == "" ( + if [%EDITION%] == [] ( echo. echo WARNING: no MSVC edition found, environment not set. echo. @@ -267,6 +353,11 @@ REM FETCH function: fetch, patch, build the external libs ) else ( echo curl dir present, skipping cloning repo. ) + if not exist googletest ( + git clone "https://github.com/google/googletest.git" + ) else ( + echo googletest dir present, skipping cloning repo. + ) REM build libcurl cd curl @@ -279,35 +370,94 @@ REM FETCH function: fetch, patch, build the external libs goto:eof +REM BUILDTYPE function: set the build config to feed MSBuild +:BUILDTYPE + REM cycle through the args, look for 'type' token and use the follow-up 1 + set prev= + for %%a in (%ARG:"=%) do ( + if /i [!prev!] == [type] ( + set MSBUILD_ARGS=/p:Configuration=%%a + set CFG_INTDIR=%%a + echo Setting the build type to: !MSBUILD_ARGS! + goto:eof + ) else ( + set prev=%%a + ) + ) + set MSBUILD_ARGS= + + goto:eof -REM BUILD function: compile, build the driver +REM BUILD function: build various targets :BUILD - echo Building the driver: if not exist ALL_BUILD.vcxproj ( echo Generating the project files. - %CMAKE_BIN_PATH%\%cmake .. + set CMAKE_ARGS=-DDRIVER_BASE_NAME=%DRIVER_BASE_NAME% + REM no explicit x86 generator and is the default (MSVC2017 only?). + set CMAKE_ARGS=%CMAKE_ARGS% -DCMAKE_GENERATOR_PLATFORM=%TARCH:x86=% + %CMAKE% !CMAKE_ARGS! .. + ) + + if /i not [%ARG:test=%] == [%ARG%] ( + echo Building all the project (including tests^). + MSBuild ALL_BUILD.vcxproj %MSBUILD_ARGS% + ) else if /i not [%ARG:all=%] == [%ARG%] ( + echo Building all the project. + MSBuild ALL_BUILD.vcxproj %MSBUILD_ARGS% + ) else if /i not [%ARG:suites=%] == [%ARG%] ( + echo Building the test projects only. + for %%i in (test\test_*.vcxproj) do ( + MSBuild %%~fi %MSBUILD_ARGS% + if not ERRORLEVEL 1 ( + echo Running test\%CFG_INTDIR%\%%~ni.exe : + test\%CFG_INTDIR%\%%~ni.exe + if ERRORLEVEL 1 ( + goto:eof + ) + ) else ( + echo Building %%~fi failed. + goto:eof + ) + ) + ) else ( + echo Building the driver only. + REM file name expansion, cmd style... + for /f %%i in ("%DRIVER_BASE_NAME%*.vcxproj") do MSBuild %%~nxi %MSBUILD_ARGS% + + if not ERRORLEVEL 1 if /i not [%ARG:symbols=%] == [%ARG%] ( + dumpbin /exports %CFG_INTDIR%\%DRIVER_BASE_NAME%*.dll + ) ) - echo Building the project. - REM MSBuild ALL_BUILD.vcxproj /t:rebuild - MSBuild ALL_BUILD.vcxproj goto:eof +REM TEST function: run the compiled tests +:TEST + REM if called with nobuild, but test, this will trigger the build + if not exist RUN_TESTS.vcxproj ( + call:BUILD + ) + MSBuild RUN_TESTS.vcxproj !MSBUILD_ARGS! + + goto:eof REM COPY function: copy DLLs (libcurl, odbc) to the test "install" dir :COPY echo Copying into test install folder %INSTALL_DIR%. - rem dumpbin /exports Debug\elasticodbc*.dll + copy %CFG_INTDIR%\%DRIVER_BASE_NAME%*.dll %INSTALL_DIR% - copy Debug\elasticodbc*.dll %INSTALL_DIR%\ - copy %LIBCURL_PATH_BUILD%\bin\libcurl.dll %INSTALL_DIR%\ + REM Read LIBCURL_LD_PATH value from cmake's cache + for /f "tokens=2 delims==" %%i in ('%CMAKE% -L %BUILD_DIR% 2^>NUL ^| find "LIBCURL_LD_PATH"') do set LIBCURL_LD_PATH=%%i + REM change the slashes' direction + set LIBCURL_LD_PATH=%LIBCURL_LD_PATH:/=\% + copy %LIBCURL_LD_PATH%\libcurl.dll %INSTALL_DIR% goto:eof REM CLEARLOGS function: empty logs files :CLEARLOGS - if not "%LOGGING_DIR%" == "" ( + if not [%LOGGING_DIR%] == [] ( echo Clearing logs in %LOGGING_DIR%. del %LOGGING_DIR%\esodbc_*.log >nul 2>&1 if exist %LOGGING_DIR%\SQL32.LOG ( @@ -327,11 +477,11 @@ REM REGADD function: add driver into the registry echo Adding driver into the registry. REM check if driver exists, otherwise the filename is unknown - if not exist %BUILD_DIR%\Debug\elasticodbc*.dll ( + if not exist %BUILD_DIR%\%CFG_INTDIR%\%DRIVER_BASE_NAME%*.dll ( echo Error: Driver can only be added into the registry once built. goto end ) - for /f %%i in ("%BUILD_DIR%\Debug\elasticodbc*.dll") do set DRVNAME=%%~nxi + for /f %%i in ("%BUILD_DIR%\%CFG_INTDIR%\%DRIVER_BASE_NAME%*.dll") do set DRVNAME=%%~nxi echo Adding ESODBC driver %INSTALL_DIR%\!DRVNAME! to the registry. diff --git a/driver/build_def.bat b/driver/build_def.bat index becdd8d7..a8cd6d11 100644 --- a/driver/build_def.bat +++ b/driver/build_def.bat @@ -16,3 +16,6 @@ echo.>> %OUTFILE% echo EXPORTS>> %OUTFILE% echo.>> %OUTFILE% powershell -NoLogo -ExecutionPolicy Bypass -Command %FILTER%>> %OUTFILE% +echo.>> %OUTFILE% +REM minimize the exposure of this unwanted export +echo JSON_DecodeObject @1 NONAME PRIVATE>> %OUTFILE% diff --git a/driver/catalogue.c b/driver/catalogue.c index 7c750f42..f26665fa 100644 --- a/driver/catalogue.c +++ b/driver/catalogue.c @@ -488,7 +488,7 @@ SQLRETURN EsSQLForeignKeysW( return SQL_SUCCESS; } -SQLRETURN SQL_API EsSQLPrimaryKeysW( +SQLRETURN EsSQLPrimaryKeysW( SQLHSTMT hstmt, _In_reads_opt_(cchCatalogName) SQLWCHAR* szCatalogName, SQLSMALLINT cchCatalogName, diff --git a/driver/catalogue.h b/driver/catalogue.h index 7d721223..7bad948b 100644 --- a/driver/catalogue.h +++ b/driver/catalogue.h @@ -79,7 +79,7 @@ SQLRETURN EsSQLForeignKeysW( _In_reads_opt_(cchFkTableName) SQLWCHAR* szFkTableName, SQLSMALLINT cchFkTableName); -SQLRETURN SQL_API EsSQLPrimaryKeysW( +SQLRETURN EsSQLPrimaryKeysW( SQLHSTMT hstmt, _In_reads_opt_(cchCatalogName) SQLWCHAR* szCatalogName, SQLSMALLINT cchCatalogName, diff --git a/driver/connect.c b/driver/connect.c index 949fff91..026b0f0a 100644 --- a/driver/connect.c +++ b/driver/connect.c @@ -1176,8 +1176,9 @@ static SQLRETURN do_connect(esodbc_dbc_st *dbc, config_attrs_st *attrs) } -/* Maps ES/SQL type to C SQL. */ -SQLSMALLINT type_elastic2csql(wstr_st *type_name) +/* Maps ES/SQL type name to C SQL and SQL id values. */ +static BOOL elastic_name2types(wstr_st *type_name, + SQLSMALLINT *c_sql, SQLSMALLINT *sql) { switch (type_name->cnt) { /* 4: BYTE, LONG, TEXT, DATE, NULL */ @@ -1186,31 +1187,41 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case (SQLWCHAR)'b': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_BYTE), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_BYTE; + *c_sql = ESODBC_ES_TO_CSQL_BYTE; + *sql = ESODBC_ES_TO_SQL_BYTE; + return TRUE; } break; case (SQLWCHAR)'l': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_LONG), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_LONG; + *c_sql = ESODBC_ES_TO_CSQL_LONG; + *sql = ESODBC_ES_TO_SQL_LONG; + return TRUE; } break; case (SQLWCHAR)'t': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_TEXT), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_TEXT; + *c_sql = ESODBC_ES_TO_CSQL_TEXT; + *sql = ESODBC_ES_TO_SQL_TEXT; + return TRUE; } break; case (SQLWCHAR)'d': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_DATE), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_DATE; + *c_sql = ESODBC_ES_TO_CSQL_DATE; + *sql = ESODBC_ES_TO_SQL_DATE; + return TRUE; } break; case (SQLWCHAR)'n': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_NULL), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_NULL; + *c_sql = ESODBC_ES_TO_CSQL_NULL; + *sql = ESODBC_ES_TO_SQL_NULL; + return TRUE; } break; } @@ -1222,13 +1233,17 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case (SQLWCHAR)'s': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_SHORT), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_SHORT; + *c_sql = ESODBC_ES_TO_CSQL_SHORT; + *sql = ESODBC_ES_TO_SQL_SHORT; + return TRUE; } break; case (SQLWCHAR)'f': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_FLOAT), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_FLOAT; + *c_sql = ESODBC_ES_TO_CSQL_FLOAT; + *sql = ESODBC_ES_TO_SQL_FLOAT; + return TRUE; } break; } @@ -1240,25 +1255,33 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case (SQLWCHAR)'d': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_DOUBLE), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_DOUBLE; + *c_sql = ESODBC_ES_TO_CSQL_DOUBLE; + *sql = ESODBC_ES_TO_SQL_DOUBLE; + return TRUE; } break; case (SQLWCHAR)'b': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_BINARY), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_BINARY; + *c_sql = ESODBC_ES_TO_CSQL_BINARY; + *sql = ESODBC_ES_TO_SQL_BINARY; + return TRUE; } break; case (SQLWCHAR)'o': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_OBJECT), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_OBJECT; + *c_sql = ESODBC_ES_TO_CSQL_OBJECT; + *sql = ESODBC_ES_TO_SQL_OBJECT; + return TRUE; } break; case (SQLWCHAR)'n': if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_NESTED), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_NESTED; + *c_sql = ESODBC_ES_TO_CSQL_NESTED; + *sql = ESODBC_ES_TO_SQL_NESTED; + return TRUE; } break; } @@ -1270,17 +1293,23 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case (SQLWCHAR)'i': /* integer */ if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_INTEGER), type_name->cnt) == 0) - return ESODBC_ES_TO_CSQL_INTEGER; + *c_sql = ESODBC_ES_TO_CSQL_INTEGER; + *sql = ESODBC_ES_TO_SQL_INTEGER; + return TRUE; break; case (SQLWCHAR)'b': /* boolean */ if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_BOOLEAN), type_name->cnt) == 0) - return ESODBC_ES_TO_CSQL_BOOLEAN; + *c_sql = ESODBC_ES_TO_CSQL_BOOLEAN; + *sql = ESODBC_ES_TO_SQL_BOOLEAN; + return TRUE; break; case (SQLWCHAR)'k': /* keyword */ if (wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_KEYWORD), type_name->cnt) == 0) - return ESODBC_ES_TO_CSQL_KEYWORD; + *c_sql = ESODBC_ES_TO_CSQL_KEYWORD; + *sql = ESODBC_ES_TO_SQL_KEYWORD; + return TRUE; break; } break; @@ -1289,7 +1318,9 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case sizeof(JSON_COL_HALF_FLOAT) - 1: if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_HALF_FLOAT), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_HALF_FLOAT; + *c_sql = ESODBC_ES_TO_CSQL_HALF_FLOAT; + *sql = ESODBC_ES_TO_SQL_HALF_FLOAT; + return TRUE; } break; @@ -1297,7 +1328,9 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case sizeof(JSON_COL_UNSUPPORTED) - 1: if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_UNSUPPORTED), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_UNSUPPORTED; + *c_sql = ESODBC_ES_TO_CSQL_UNSUPPORTED; + *sql = ESODBC_ES_TO_SQL_UNSUPPORTED; + return TRUE; } break; @@ -1305,14 +1338,16 @@ SQLSMALLINT type_elastic2csql(wstr_st *type_name) case sizeof(JSON_COL_SCALED_FLOAT) - 1: if (! wmemncasecmp(type_name->str, MK_WPTR(JSON_COL_SCALED_FLOAT), type_name->cnt)) { - return ESODBC_ES_TO_CSQL_SCALED_FLOAT; + *c_sql = ESODBC_ES_TO_CSQL_SCALED_FLOAT; + *sql = ESODBC_ES_TO_SQL_SCALED_FLOAT; + return TRUE; } break; } ERR("unrecognized Elastic type `" LWPDL "` (%zd).", LWSTR(type_name), type_name->cnt); - return SQL_UNKNOWN_TYPE; + return FALSE; } /* @@ -1457,6 +1492,7 @@ static void* copy_types_rows(estype_row_st *type_row, SQLULEN rows_fetched, { SQLWCHAR *pos; int i; + SQLSMALLINT sql_type; /* start pointer where the strings will be copied in */ pos = (SQLWCHAR *)&types[rows_fetched]; @@ -1521,13 +1557,21 @@ static void* copy_types_rows(estype_row_st *type_row, SQLULEN rows_fetched, } /* resolve ES type to SQL C type */ - types[i].c_concise_type = type_elastic2csql(&types[i].type_name); - if (types[i].c_concise_type == SQL_UNKNOWN_TYPE) { + if (! elastic_name2types(&types[i].type_name, &types[i].c_concise_type, + &sql_type)) { /* ES version newer than driver's? */ ERR("failed to convert type name `" LWPDL "` to SQL C type.", LWSTR(&types[i].type_name)); return NULL; } + /* .data_type is used in data conversions -> make sure the SQL type + * derived from type's name is the same with type reported value */ + if (sql_type != types[i].data_type) { + ERR("type `" LWPDL "` derived (%d) and reported (%d) SQL type " + "identifiers differ.", LWSTR(&types[i].type_name), + sql_type, types[i].data_type); + return NULL; + } /* set meta type */ types[i].meta_type = concise_to_meta(types[i].c_concise_type, /*C type -> AxD*/DESC_TYPE_ARD); @@ -1574,9 +1618,26 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) } assert(stmt); - if (! SQL_SUCCEEDED(EsSQLGetTypeInfoW(stmt, SQL_ALL_TYPES))) { - ERRH(stmt, "failed to query Elasticsearch."); - goto end; +#ifdef TESTING + /* for testing cases with no ES server available, the connection needs to + * be init'ed with ES types: the test suite passes a JSON answer of ES + * types through the (otherwise) unused window handler pointer */ + if (dbc->hwin) { + cstr_st *types_answer = (cstr_st *)dbc->hwin; + dbc->hwin = NULL; + if (! SQL_SUCCEEDED(attach_answer(stmt, types_answer->str, + types_answer->cnt))) { + ERRH(stmt, "failed to attach dummmy ES types answer"); + goto end; + } + } else { +#else /* TESTING */ + if (TRUE) { +#endif /* TESTING */ + if (! SQL_SUCCEEDED(EsSQLGetTypeInfoW(stmt, SQL_ALL_TYPES))) { + ERRH(stmt, "failed to query Elasticsearch."); + goto end; + } } /* check that we have as many columns as members in target row struct */ @@ -1640,6 +1701,10 @@ static BOOL load_es_types(esodbc_dbc_st *dbc) goto end; } + /* don't check for data compatiblity (since the check needs data fetched + * by this function) */ + stmt->sql2c_conversion = CONVERSION_SKIPPED; + /* fetch the results into the type_row array */ if (! SQL_SUCCEEDED(EsSQLFetch(stmt))) { ERRH(stmt, "failed to fetch results."); @@ -1956,6 +2021,15 @@ SQLRETURN EsSQLDriverConnectW } while (! SQL_SUCCEEDED(do_connect(dbc, &attrs))); break; +#ifdef TESTING + case ESODBC_SQL_DRIVER_TEST: + /* abusing the window handler to pass type data for non-network + * tests (see load_es_types()). */ + assert(! dbc->hwin); + dbc->hwin = hwnd; + break; +#endif /* TESTING */ + default: ERRH(dbc, "unknown driver completion mode: %d", fDriverCompletion); RET_HDIAGS(dbc, SQL_STATE_HY110); diff --git a/driver/connect.h b/driver/connect.h index dc13ec08..4afbfdea 100644 --- a/driver/connect.h +++ b/driver/connect.h @@ -26,7 +26,6 @@ void connect_cleanup(); SQLRETURN post_statement(esodbc_stmt_st *stmt); void cleanup_dbc(esodbc_dbc_st *dbc); -SQLSMALLINT type_elastic2csql(wstr_st *type_name); SQLRETURN EsSQLDriverConnectW ( diff --git a/driver/defs.h b/driver/defs.h index c45fda9d..cfc9db79 100644 --- a/driver/defs.h +++ b/driver/defs.h @@ -39,9 +39,14 @@ /* values for SQL_ATTR_MAX_LENGTH statement attribute */ #define ESODBC_UP_MAX_LENGTH 0 // USHORT_MAX #define ESODBC_LO_MAX_LENGTH 0 -/* prepare a STMT for a new SQL operation. - * To be used with catalog functions, that can be all called with same stmt */ +/* Prepare a STMT for a new SQL operation. + * To be used with catalog functions, that can be all called with same stmt. + * After SQL_CLOSE, an applicatoin can re-open a cursor for same query. */ #define ESODBC_SQL_CLOSE ((SQLUSMALLINT)-1) +#ifdef TESTING +/* connecting mode for testing */ +#define ESODBC_SQL_DRIVER_TEST ((SQLUSMALLINT)-1) +#endif /* TESTING */ #define ESODBC_ALL_TABLES "%" #define ESODBC_ALL_COLUMNS "%" @@ -82,9 +87,9 @@ /* * Versions */ -/* driver version ex. 1.2(u) */ -#define ESODBC_DRIVER_VER \ - STR(DRV_VER_MAJOR) "." STR(DRV_VER_MINOR) "(" STR(DRV_ENCODING) ")" +/* driver version ex. 1.2(b0a34b4,u,d) */ +#define ESODBC_DRIVER_VER STR(DRV_VER_MAJOR) "." STR(DRV_VER_MINOR) \ + "(" STR(DRV_SRC_VER) "," STR(DRV_ENCODING) "," STR(DRV_BUILD_TYPE) ")" /* TODO: learn it from ES */ #define ESODBC_ELASTICSEARCH_VER "7.x" #define ESODBC_ELASTICSEARCH_NAME "Elasticsearch" @@ -295,7 +300,10 @@ /* * ISO8601 template ('yyyy-mm-ddThh:mm:ss.sss+hh:mm') */ -#define ESODBC_ISO8601_TEMPLATE "yyyy-mm-ddThh:mm:ss.sssZ" +#define ESODBC_ISO8601_TEMPLATE "yyyy-mm-ddThh:mm:ss.sssssssZ" +/* https://docs.microsoft.com/en-us/sql/relational-databases/native-client-odbc-date-time/data-type-support-for-odbc-date-and-time-improvements */ +#define ESODBC_DATE_TEMPLATE "yyyy-mm-ddT" +#define ESODBC_TIME_TEMPLATE "hh:mm:ss.9999999" /* * ES-to-C-SQL mappings @@ -304,37 +312,54 @@ */ /* -6: SQL_TINYINT -> SQL_C_TINYINT */ #define ESODBC_ES_TO_CSQL_BYTE SQL_C_TINYINT +#define ESODBC_ES_TO_SQL_BYTE SQL_TINYINT /* 5: SQL_SMALLINT -> SQL_C_SHORT */ #define ESODBC_ES_TO_CSQL_SHORT SQL_C_SSHORT +#define ESODBC_ES_TO_SQL_SHORT SQL_SMALLINT /* 4: SQL_INTEGER -> SQL_C_LONG */ #define ESODBC_ES_TO_CSQL_INTEGER SQL_C_SLONG +#define ESODBC_ES_TO_SQL_INTEGER SQL_INTEGER /* -5: SQL_BIGINT -> SQL_C_SBIGINT */ #define ESODBC_ES_TO_CSQL_LONG SQL_C_SBIGINT +#define ESODBC_ES_TO_SQL_LONG SQL_BIGINT /* 6: SQL_FLOAT -> SQL_C_DOUBLE */ #define ESODBC_ES_TO_CSQL_HALF_FLOAT SQL_C_DOUBLE +#define ESODBC_ES_TO_SQL_HALF_FLOAT SQL_FLOAT /* 6: SQL_FLOAT -> SQL_C_DOUBLE */ #define ESODBC_ES_TO_CSQL_SCALED_FLOAT SQL_C_DOUBLE +#define ESODBC_ES_TO_SQL_SCALED_FLOAT SQL_FLOAT /* 7: SQL_REAL -> SQL_C_DOUBLE */ #define ESODBC_ES_TO_CSQL_FLOAT SQL_C_FLOAT +#define ESODBC_ES_TO_SQL_FLOAT SQL_REAL /* 8: SQL_DOUBLE -> SQL_C_FLOAT */ #define ESODBC_ES_TO_CSQL_DOUBLE SQL_C_DOUBLE +#define ESODBC_ES_TO_SQL_DOUBLE SQL_DOUBLE /* 16: ??? -> SQL_C_TINYINT */ #define ESODBC_ES_TO_CSQL_BOOLEAN SQL_C_STINYINT +#define ESODBC_ES_TO_SQL_BOOLEAN ESODBC_SQL_BOOLEAN /* 12: SQL_VARCHAR -> SQL_C_WCHAR */ -#define ESODBC_ES_TO_CSQL_KEYWORD SQL_C_WCHAR +#define ESODBC_ES_TO_CSQL_KEYWORD SQL_C_WCHAR /* XXX: CBOR needs _CHAR */ +#define ESODBC_ES_TO_SQL_KEYWORD SQL_VARCHAR /* 12: SQL_VARCHAR -> SQL_C_WCHAR */ -#define ESODBC_ES_TO_CSQL_TEXT SQL_C_WCHAR +#define ESODBC_ES_TO_CSQL_TEXT SQL_C_WCHAR /* XXX: CBOR needs _CHAR */ +#define ESODBC_ES_TO_SQL_TEXT SQL_VARCHAR /* 93: SQL_TYPE_TIMESTAMP -> SQL_C_TYPE_TIMESTAMP */ #define ESODBC_ES_TO_CSQL_DATE SQL_C_TYPE_TIMESTAMP +#define ESODBC_ES_TO_SQL_DATE SQL_TYPE_TIMESTAMP /* -3: SQL_VARBINARY -> SQL_C_BINARY */ #define ESODBC_ES_TO_CSQL_BINARY SQL_C_BINARY +#define ESODBC_ES_TO_SQL_BINARY SQL_VARBINARY /* 0: SQL_TYPE_NULL -> SQL_C_TINYINT */ #define ESODBC_ES_TO_CSQL_NULL SQL_C_STINYINT +#define ESODBC_ES_TO_SQL_NULL SQL_TYPE_NULL /* 1111: ??? -> SQL_C_BINARY */ #define ESODBC_ES_TO_CSQL_UNSUPPORTED SQL_C_BINARY +#define ESODBC_ES_TO_SQL_UNSUPPORTED ESODBC_SQL_UNSUPPORTED /* 2002: ??? -> SQL_C_BINARY */ #define ESODBC_ES_TO_CSQL_OBJECT SQL_C_BINARY +#define ESODBC_ES_TO_SQL_OBJECT ESODBC_SQL_OBJECT /* 2002: ??? -> SQL_C_BINARY */ #define ESODBC_ES_TO_CSQL_NESTED SQL_C_BINARY +#define ESODBC_ES_TO_SQL_NESTED ESODBC_SQL_NESTED #endif /* __DEFS_H__ */ diff --git a/driver/handles.c b/driver/handles.c index 97e007e4..de5e5a99 100644 --- a/driver/handles.c +++ b/driver/handles.c @@ -273,6 +273,7 @@ SQLRETURN EsSQLAllocHandle(SQLSMALLINT HandleType, * set at connection level. */ stmt->metadata_id = dbc->metadata_id; stmt->async_enable = dbc->async_enable; + stmt->sql2c_conversion = CONVERSION_UNCHECKED; DBG("new Statement handle allocated @0x%p.", *OutputHandle); break; @@ -514,8 +515,7 @@ SQLRETURN EsSQLSetEnvAttr(SQLHENV EnvironmentHandle, return SQL_SUCCESS; } -SQLRETURN SQL_API EsSQLGetEnvAttr(SQLHENV EnvironmentHandle, - SQLINTEGER Attribute, +SQLRETURN EsSQLGetEnvAttr(SQLHENV EnvironmentHandle, SQLINTEGER Attribute, _Out_writes_(_Inexpressible_(BufferLength)) SQLPOINTER Value, SQLINTEGER BufferLength, _Out_opt_ SQLINTEGER *StringLength) { diff --git a/driver/handles.h b/driver/handles.h index f1a18d50..7bdc49e2 100644 --- a/driver/handles.h +++ b/driver/handles.h @@ -20,8 +20,6 @@ #include -#include "ujdecode.h" - #include "error.h" #include "defs.h" @@ -322,6 +320,14 @@ typedef struct struct_stmt { /* result set */ resultset_st rset; + /* SQL data types conversion to SQL C compatibility (IRD.SQL -> ARD.C) */ + enum { + CONVERSION_VIOLATION = -2, /* specs disallowed */ + CONVERSION_UNSUPPORTED, /* ES/driver not supported */ + CONVERSION_UNCHECKED, /* 0 */ + CONVERSION_SUPPORTED, + CONVERSION_SKIPPED, /* used with driver's meta queries */ + } sql2c_conversion; } esodbc_stmt_st; @@ -345,8 +351,7 @@ SQLRETURN EsSQLSetEnvAttr(SQLHENV EnvironmentHandle, SQLINTEGER Attribute, _In_reads_bytes_opt_(StringLength) SQLPOINTER Value, SQLINTEGER StringLength); -SQLRETURN SQL_API EsSQLGetEnvAttr(SQLHENV EnvironmentHandle, - SQLINTEGER Attribute, +SQLRETURN EsSQLGetEnvAttr(SQLHENV EnvironmentHandle, SQLINTEGER Attribute, _Out_writes_(_Inexpressible_(BufferLength)) SQLPOINTER Value, SQLINTEGER BufferLength, _Out_opt_ SQLINTEGER *StringLength); @@ -453,21 +458,6 @@ SQLRETURN EsSQLSetDescRec( (rec)->octet_length_ptr != NULL) -/* TODO: this is inefficient: add directly into ujson4c lib (as .size of - * ArrayItem struct, inc'd in arrayAddItem()) or local utils file. Only added - * here to be accessible with the statement resultset member. */ -static inline size_t UJArraySize(UJObject obj) -{ - UJObject _u; /* unused */ - size_t size = 0; - void *iter = UJBeginArray(obj); - if (iter) { - while (UJIterArray(&iter, &_u)) - size ++; - } - return size; -} - #endif /* __HANDLES_H__ */ /* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */ diff --git a/driver/info.c b/driver/info.c index 4c12082b..ec785b74 100644 --- a/driver/info.c +++ b/driver/info.c @@ -556,6 +556,32 @@ SQLRETURN EsSQLGetInfoW(SQLHDBC ConnectionHandle, *(SQLUSMALLINT *)InfoValue = ESODBC_TXN_CAPABLE; break; + case SQL_CONVERT_BIGINT: + case SQL_CONVERT_BINARY: + case SQL_CONVERT_BIT: + case SQL_CONVERT_CHAR: + case SQL_CONVERT_DATE: + case SQL_CONVERT_DECIMAL: + case SQL_CONVERT_DOUBLE: + case SQL_CONVERT_FLOAT: + case SQL_CONVERT_INTEGER: + case SQL_CONVERT_INTERVAL_YEAR_MONTH: + case SQL_CONVERT_INTERVAL_DAY_TIME: + case SQL_CONVERT_LONGVARBINARY: + case SQL_CONVERT_LONGVARCHAR: + case SQL_CONVERT_NUMERIC: + case SQL_CONVERT_REAL: + case SQL_CONVERT_SMALLINT: + case SQL_CONVERT_TIME: + case SQL_CONVERT_TIMESTAMP: + case SQL_CONVERT_TINYINT: + case SQL_CONVERT_VARBINARY: + case SQL_CONVERT_VARCHAR: + DBGH(dbc, "requested: convert data-type support (0)."); + INFOH(dbc, "no CONVERT scalar function support."); + *(SQLUINTEGER *)InfoValue = 0; + break; + default: ERRH(dbc, "unknown InfoType: %u.", InfoType); RET_HDIAGS(dbc, SQL_STATE_HYC00/*096?*/); diff --git a/driver/queries.c b/driver/queries.c index c1ca316a..a935cebf 100644 --- a/driver/queries.c +++ b/driver/queries.c @@ -18,6 +18,7 @@ #include #include /* WideCharToMultiByte() */ +#include "ujdecode.h" #include "timestamp.h" #include "queries.h" @@ -50,6 +51,21 @@ } while (0); +/* TODO: this is inefficient: add directly into ujson4c lib (as .size of + * ArrayItem struct, inc'd in arrayAddItem()) or local utils file. */ +static size_t UJArraySize(UJObject obj) +{ + UJObject _u; + size_t size = 0; + void *iter = UJBeginArray(obj); + if (iter) { + while (UJIterArray(&iter, &_u)) + size ++; + } + return size; +} + + void clear_resultset(esodbc_stmt_st *stmt) { DBGH(stmt, "clearing result set; vrows=%zd, nrows=%zd, frows=%zd.", @@ -109,7 +125,7 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) UJObject col_o, name_o, type_o; wstr_st col_name, col_type; size_t ncols, i; - wchar_t *keys[] = { + const wchar_t *keys[] = { MK_WPTR(JSON_ANSWER_COL_NAME), MK_WPTR(JSON_ANSWER_COL_TYPE) }; @@ -200,6 +216,9 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) recno ++; } + /* new columsn attached, need to check compatiblity */ + stmt->sql2c_conversion = CONVERSION_UNCHECKED; + return SQL_SUCCESS; } @@ -210,13 +229,13 @@ static SQLRETURN attach_columns(esodbc_stmt_st *stmt, UJObject columns) * even if the call fails. * - parses it, preparing iterators for SQLFetch()'ing. */ -SQLRETURN attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen) +SQLRETURN TEST_API attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen) { int unpacked; UJObject obj, columns, rows, cursor; const wchar_t *wcurs; size_t eccnt; - wchar_t *keys[] = { + const wchar_t *keys[] = { MK_WPTR(JSON_ANSWER_COLUMNS), MK_WPTR(JSON_ANSWER_ROWS), MK_WPTR(JSON_ANSWER_CURSOR) @@ -319,7 +338,7 @@ SQLRETURN attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen) /* * Parse an error and push it as statement diagnostic. */ -SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) +SQLRETURN TEST_API attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) { UJObject obj, o_status, o_error, o_type, o_reason; const wchar_t *wtype, *wreason; @@ -329,11 +348,11 @@ SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen) size_t wbuflen = sizeof(wbuf)/sizeof(*wbuf); int n; void *state = NULL; - wchar_t *outer_keys[] = { + const wchar_t *outer_keys[] = { MK_WPTR(JSON_ANSWER_ERROR), MK_WPTR(JSON_ANSWER_STATUS) }; - wchar_t *err_keys[] = { + const wchar_t *err_keys[] = { MK_WPTR(JSON_ANSWER_ERR_TYPE), MK_WPTR(JSON_ANSWER_ERR_REASON) }; @@ -578,6 +597,9 @@ SQLRETURN EsSQLBindCol( if (ret != SQL_SUCCESS) goto copy_ret; + /* every binding resets conversion flag */ + stmt->sql2c_conversion = CONVERSION_UNCHECKED; + return SQL_SUCCESS; copy_ret: @@ -1015,7 +1037,7 @@ static SQLRETURN wstr_to_wstr(esodbc_rec_st *arec, esodbc_rec_st *irec, return SQL_SUCCESS; } - +/* function expects chars not to count the 0-term */ static BOOL wstr_to_timestamp_struct(const wchar_t *wstr, size_t chars, TIMESTAMP_STRUCT *tss) { @@ -1026,11 +1048,15 @@ static BOOL wstr_to_timestamp_struct(const wchar_t *wstr, size_t chars, DBG("converting ISO 8601 `" LWPDL "` to timestamp.", chars, wstr); - len = (int)(chars < sizeof(buff) - 1 ? chars : sizeof(buff) - 1); - len = ansi_w2c(wstr, buff, len); + if (sizeof(buff) - 1 < chars) { + ERR("`" LWPDL "` not a TIMESTAMP.", chars, wstr); + return FALSE; + } + len = ansi_w2c(wstr, buff, chars); + /* len counts the 0-term */ if (len <= 0 || timestamp_parse(buff, len - 1, &tsp) || (! timestamp_to_tm_local(&tsp, &tmp))) { - ERR("data `" LWPDL "` not an ANSI ISO 8601 format.", chars,wstr); + ERR("data `" LWPDL "` not an ANSI ISO 8601 format.", chars, wstr); return FALSE; } TM_TO_TIMESTAMP_STRUCT(&tmp, tss); @@ -1047,21 +1073,98 @@ static BOOL wstr_to_timestamp_struct(const wchar_t *wstr, size_t chars, /* * -> SQL_C_TYPE_TIMESTAMP + * + * Conversts an ES/SQL 'date' or a text representation of a + * timestamp/date/time value into a TIMESTAMP_STRUCT (indicates the detected + * input format into the "format" parameter). */ static SQLRETURN wstr_to_timestamp(esodbc_rec_st *arec, esodbc_rec_st *irec, void *data_ptr, SQLLEN *octet_len_ptr, - const wchar_t *wstr, size_t chars) + const wchar_t *wstr, size_t chars_0, SQLSMALLINT *format) { + size_t cnt = chars_0 - 1; esodbc_stmt_st *stmt = arec->desc->hdr.stmt; TIMESTAMP_STRUCT *tss = (TIMESTAMP_STRUCT *)data_ptr; + SQLWCHAR buff[] = MK_WPTR("1000-10-10T10:10:10.1001001Z"); if (octet_len_ptr) { *octet_len_ptr = sizeof(*tss); } if (data_ptr) { - if (! wstr_to_timestamp_struct(wstr, chars, tss)) { - RET_HDIAGS(stmt, SQL_STATE_07006); + /* right & left trim the data before attempting conversion */ + wstr = trim_ws(wstr, &cnt); + + switch (irec->concise_type) { + case SQL_TYPE_TIMESTAMP: + if (! wstr_to_timestamp_struct(wstr, cnt, tss)) { + RET_HDIAGS(stmt, SQL_STATE_22018); + } + if (format) { + *format = SQL_TYPE_TIMESTAMP; + } + break; + case SQL_VARCHAR: + if (sizeof(ESODBC_TIME_TEMPLATE) - 1 < cnt) { + /* longer than a date-value -> try a timestamp */ + if (! wstr_to_timestamp_struct(wstr, cnt, tss)) { + RET_HDIAGS(stmt, SQL_STATE_22018); + } + if (format) { + *format = SQL_TYPE_TIMESTAMP; + } + } else if (/*hh:mm:ss*/8 <= cnt && wstr[2] == L':' && + wstr[5] == L':') { /* could this be a time-val? */ + /* copy active value in template and parse it as TS */ + /* copy is safe: cnt <= [time template] < [buff] */ + wcsncpy(buff + sizeof(ESODBC_DATE_TEMPLATE) - 1, wstr, + cnt); + /* there could be a varying number of fractional digits */ + buff[sizeof(ESODBC_DATE_TEMPLATE) - 1 + cnt] = L'Z'; + if (! wstr_to_timestamp_struct(buff, + sizeof(ESODBC_DATE_TEMPLATE) + cnt, tss)) { + ERRH(stmt, "`" LWPDL "` not a TIME.", cnt, wstr); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + tss->year = tss->month = tss->day = 0; + if (format) { + *format = SQL_TYPE_TIME; + } + } + } else if (/*yyyy-mm-dd*/10 <= cnt && wstr[4] == L'-' && + wstr[7] == L'-') { /* could this be a date-val? */ + /* copy active value in template and parse it as TS */ + /* copy is safe: cnt <= [time template] < [buff] */ + wcsncpy(buff, wstr, cnt); + if (! wstr_to_timestamp_struct(buff, + sizeof(buff)/sizeof(buff[0]) - 1, tss)) { + ERRH(stmt, "`" LWPDL "` not a DATE.", cnt, wstr); + RET_HDIAGS(stmt, SQL_STATE_22018); + } else { + tss->hour = tss->minute = tss->second = 0; + tss->fraction = 0; + if (format) { + *format = SQL_TYPE_DATE; + } + } + } else { + ERRH(stmt, "`" LWPDL "` not a DATE/TIME/Timestamp.", cnt, + wstr); + RET_HDIAGS(stmt, SQL_STATE_22018); + } + break; + + case SQL_CHAR: + case SQL_LONGVARCHAR: + case SQL_WCHAR: + case SQL_WLONGVARCHAR: + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + BUGH(stmt, "unexpected (but permitted) SQL type."); + RET_HDIAGS(stmt, SQL_STATE_HY004); + default: + BUGH(stmt, "uncought invalid conversion."); + RET_HDIAGS(stmt, SQL_STATE_07006); } } else { DBGH(stmt, "REC@0x%p, NULL data_ptr", arec); @@ -1075,22 +1178,76 @@ static SQLRETURN wstr_to_timestamp(esodbc_rec_st *arec, esodbc_rec_st *irec, */ static SQLRETURN wstr_to_date(esodbc_rec_st *arec, esodbc_rec_st *irec, void *data_ptr, SQLLEN *octet_len_ptr, - const wchar_t *wstr, size_t chars) + const wchar_t *wstr, size_t chars_0) { esodbc_stmt_st *stmt = arec->desc->hdr.stmt; DATE_STRUCT *ds = (DATE_STRUCT *)data_ptr; TIMESTAMP_STRUCT tss; + SQLRETURN ret; + SQLSMALLINT fmt; if (octet_len_ptr) { *octet_len_ptr = sizeof(*ds); } if (data_ptr) { - if (! wstr_to_timestamp_struct(wstr, chars, &tss)) - RET_HDIAGS(stmt, SQL_STATE_07006); + ret = wstr_to_timestamp(arec, irec, &tss, NULL, wstr, chars_0, &fmt); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + if (fmt == SQL_TYPE_TIME) { + /* it's a time-value */ + RET_HDIAGS(stmt, SQL_STATE_22018); + } ds->year = tss.year; ds->month = tss.month; ds->day = tss.day; + if (tss.hour || tss.minute || tss.second || tss.fraction) { + /* value's truncated */ + RET_HDIAGS(stmt, SQL_STATE_01S07); + } + } else { + DBGH(stmt, "REC@0x%p, NULL data_ptr", arec); + } + + return SQL_SUCCESS; +} + +/* + * -> SQL_C_TYPE_TIME + */ +static SQLRETURN wstr_to_time(esodbc_rec_st *arec, esodbc_rec_st *irec, + void *data_ptr, SQLLEN *octet_len_ptr, + const wchar_t *wstr, size_t chars_0) +{ + esodbc_stmt_st *stmt = arec->desc->hdr.stmt; + TIME_STRUCT *ts = (TIME_STRUCT *)data_ptr; + TIMESTAMP_STRUCT tss; + SQLRETURN ret; + SQLSMALLINT fmt; + + if (octet_len_ptr) { + *octet_len_ptr = sizeof(*ts); + } + + if (data_ptr) { + ret = wstr_to_timestamp(arec, irec, &tss, NULL, wstr, chars_0, &fmt); + if (! SQL_SUCCEEDED(ret)) { + return ret; + } + /* need to differentiate between: + * - 1234-12-34T00:00:00Z : valid and + * - 1234-12-34 : invalid */ + if (fmt == SQL_TYPE_DATE) { + RET_HDIAGS(stmt, SQL_STATE_22018); + } + ts->hour = tss.hour; + ts->minute = tss.minute; + ts->second = tss.second; + if (tss.fraction) { + /* value's truncated */ + RET_HDIAGS(stmt, SQL_STATE_01S07); + } } else { DBGH(stmt, "REC@0x%p, NULL data_ptr", arec); } @@ -1130,12 +1287,16 @@ static SQLRETURN copy_string(esodbc_rec_st *arec, esodbc_rec_st *irec, case SQL_C_WCHAR: return wstr_to_wstr(arec, irec, data_ptr, octet_len_ptr, wstr, chars_0); + case SQL_C_TYPE_TIMESTAMP: return wstr_to_timestamp(arec, irec, data_ptr, octet_len_ptr, - wstr, chars_0); + wstr, chars_0, NULL); case SQL_C_TYPE_DATE: return wstr_to_date(arec, irec, data_ptr, octet_len_ptr, wstr, chars_0); + case SQL_C_TYPE_TIME: + return wstr_to_time(arec, irec, data_ptr, octet_len_ptr, + wstr, chars_0); default: // FIXME: convert data @@ -1201,16 +1362,14 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) with_info = FALSE; /* iterate over the contents of one table row */ for (i = 0; i < ard->count && UJIterArray(&iter_row, &obj); i ++) { + arec = &ard->recs[i]; /* access safe if 'i < ard->count' */ /* if record not bound skip it */ - if (! REC_IS_BOUND(&ard->recs[i])) { + if (! REC_IS_BOUND(arec)) { DBGH(stmt, "column #%d not bound, skipping it.", i + 1); continue; } - arec = get_record(ard, i + 1, FALSE); - irec = get_record(ird, i + 1, FALSE); - assert(arec); - assert(irec); + irec = &ird->recs[i]; /* access checked by UJIterArray() condition */ switch (UJGetType(obj)) { default: @@ -1221,14 +1380,8 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) case UJT_Null: DBGH(stmt, "value [%zd, %d] is NULL.", rowno, i + 1); -#if 0 - // FIXME: needed? - if (! arec->nullable) { - ERRH(stmt, "received a NULL for a not nullable val."); - RET_ROW_DIAG(SQL_STATE_HY003, "NULL value received for non" - " nullable data type", i + 1); - } -#endif //0 + /* Note: if ever causing an issue, check + * arec->es_type->nullable before returning NULL to app */ ind_len = deferred_address(SQL_DESC_INDICATOR_PTR, pos, arec); if (! ind_len) { ERRH(stmt, "no buffer to signal NULL value."); @@ -1280,7 +1433,7 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) SET_ROW_DIAG(rowno, i + 1); case SQL_SUCCESS: break; - default: + default: /* error */ SET_ROW_DIAG(rowno, i + 1); return ret; } @@ -1299,6 +1452,202 @@ static SQLRETURN copy_one_row(esodbc_stmt_st *stmt, SQLULEN pos, UJObject row) #undef SET_ROW_DIAG } +static BOOL conv_supported(SQLSMALLINT sqltype, SQLSMALLINT ctype) +{ + switch (ctype) { + case SQL_C_GUID: + + case SQL_C_INTERVAL_DAY: + case SQL_C_INTERVAL_HOUR: + case SQL_C_INTERVAL_MINUTE: + case SQL_C_INTERVAL_SECOND: + case SQL_C_INTERVAL_DAY_TO_HOUR: + case SQL_C_INTERVAL_DAY_TO_MINUTE: + case SQL_C_INTERVAL_DAY_TO_SECOND: + case SQL_C_INTERVAL_HOUR_TO_MINUTE: + case SQL_C_INTERVAL_HOUR_TO_SECOND: + case SQL_C_INTERVAL_MINUTE_TO_SECOND: + case SQL_C_INTERVAL_MONTH: + case SQL_C_INTERVAL_YEAR: + case SQL_C_INTERVAL_YEAR_TO_MONTH: + + // case SQL_C_TYPE_TIMESTAMP_WITH_TIMEZONE: + // case SQL_C_TYPE_TIME_WITH_TIMEZONE: + return FALSE; + } + return TRUE; +} + +/* implements the rotation of the matrix in: + * https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/converting-data-from-c-to-sql-data-types */ +static BOOL conv_allowed(SQLSMALLINT sqltype, SQLSMALLINT ctype) +{ + switch (ctype) { + /* application will use implementation's type (irec's) */ + case SQL_C_DEFAULT: + /* anything's convertible to [w]char & binary */ + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_BINARY: + return TRUE; + + /* GUID (outlier) is only convertible to same time in both sets */ + case SQL_C_GUID: + return sqltype == SQL_GUID; + } + + switch (sqltype) { + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + break; /* it's not SQL_C_GUID, checked for above */ + + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_TINYINT: + case SQL_BIGINT: + switch (ctype) { + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_GUID: + return FALSE; + } + break; + + case SQL_BIT: + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + switch (ctype) { + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + case SQL_C_GUID: + + case SQL_C_INTERVAL_MONTH: + case SQL_C_INTERVAL_YEAR: + case SQL_C_INTERVAL_YEAR_TO_MONTH: + case SQL_C_INTERVAL_DAY: + case SQL_C_INTERVAL_HOUR: + case SQL_C_INTERVAL_MINUTE: + case SQL_C_INTERVAL_SECOND: + case SQL_C_INTERVAL_DAY_TO_HOUR: + case SQL_C_INTERVAL_DAY_TO_MINUTE: + case SQL_C_INTERVAL_DAY_TO_SECOND: + case SQL_C_INTERVAL_HOUR_TO_MINUTE: + case SQL_C_INTERVAL_HOUR_TO_SECOND: + case SQL_C_INTERVAL_MINUTE_TO_SECOND: + return FALSE; + } + break; + + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + return FALSE; /* it's not SQL_C_BINARY, checked for above */ + + + case SQL_TYPE_DATE: + switch (ctype) { + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIMESTAMP: + return TRUE; + } + return FALSE; + case SQL_TYPE_TIME: + switch (ctype) { + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + return TRUE; + } + return FALSE; + case SQL_TYPE_TIMESTAMP: + switch (ctype) { + case SQL_C_TYPE_DATE: + case SQL_C_TYPE_TIME: + case SQL_C_TYPE_TIMESTAMP: + return TRUE; + } + return FALSE; + + case SQL_INTERVAL_MONTH: + case SQL_INTERVAL_YEAR: + case SQL_INTERVAL_YEAR_TO_MONTH: + case SQL_INTERVAL_DAY: + case SQL_INTERVAL_HOUR: + case SQL_INTERVAL_MINUTE: + case SQL_INTERVAL_SECOND: + case SQL_INTERVAL_DAY_TO_HOUR: + case SQL_INTERVAL_DAY_TO_MINUTE: + case SQL_INTERVAL_DAY_TO_SECOND: + case SQL_INTERVAL_HOUR_TO_MINUTE: + case SQL_INTERVAL_HOUR_TO_SECOND: + case SQL_INTERVAL_MINUTE_TO_SECOND: + switch (ctype) { + case SQL_C_BIT: + case SQL_C_TINYINT: + case SQL_C_STINYINT: + case SQL_C_UTINYINT: + case SQL_C_SBIGINT: + case SQL_C_UBIGINT: + case SQL_C_SHORT: + case SQL_C_SSHORT: + case SQL_C_USHORT: + case SQL_C_LONG: + case SQL_C_SLONG: + case SQL_C_ULONG: + return TRUE; + } + return FALSE; + + } + return TRUE; +} + +/* check if data types in returned columns are compabile with buffer types + * bound for those columns */ +static int sql2c_convertible(esodbc_stmt_st *stmt) +{ + SQLSMALLINT i, min; + esodbc_desc_st *ard, *ird; + esodbc_rec_st* arec, *irec; + + assert(stmt->hdr.dbc->es_types); + assert(STMT_HAS_RESULTSET(stmt)); + + ard = stmt->ard; + ird = stmt->ird; + + min = ard->count < ird->count ? ard->count : ird->count; + for (i = 0; i < min; i ++) { + arec = &ard->recs[i]; + if (! REC_IS_BOUND(arec)) { + /* skip not bound columns */ + continue; + } + irec = &ird->recs[i]; + + if (! conv_allowed(irec->concise_type, arec->type)) { + ERRH(stmt, "conversion not allowed on column %d types: IRD: %d, " + "ARD: %d.", i, irec->es_type->c_concise_type, arec->type); + return CONVERSION_VIOLATION; + } + if (! conv_supported(irec->concise_type, arec->type)) { + ERRH(stmt, "conversion not supported on column %d types: IRD: %d, " + "ARD: %d.", i, irec->es_type->c_concise_type, arec->type); + return CONVERSION_UNSUPPORTED; + } + } + + return CONVERSION_SUPPORTED; +} + /* * "SQLFetch and SQLFetchScroll use the rowset size at the time of the call to * determine how many rows to fetch." @@ -1377,6 +1726,44 @@ SQLRETURN EsSQLFetch(SQLHSTMT StatementHandle) RET_HDIAGS(stmt, SQL_STATE_HY010); } + /* Check if the data [type] stored in DB is compatiblie with the buffer + * [type] the application provides. This test can only be done at + * fetch-time, since the application can unbind/rebind columns at any time + * (i.e. also in-between consecutive fetches). */ + switch (stmt->sql2c_conversion) { + case CONVERSION_VIOLATION: + ERRH(stmt, "types compibility check had failed already " + "(violation)."); + RET_HDIAGS(stmt, SQL_STATE_07006); + /* RET_ returns */ + + case CONVERSION_UNSUPPORTED: + ERRH(stmt, "types compibility check had failed already " + "(unsupported)."); + RET_HDIAG(stmt, SQL_STATE_HYC00, "Conversion target type not" + " supported", 0); + /* RET_ returns */ + + case CONVERSION_SKIPPED: + DBGH(stmt, "types compatibility skipped."); + /* check unnecessary (SYS TYPES, possiblity other metas) */ + break; + + case CONVERSION_UNCHECKED: + stmt->sql2c_conversion = sql2c_convertible(stmt); + if (stmt->sql2c_conversion < 0) { + ERRH(stmt, "convertibility check: failed!"); + RET_HDIAGS(stmt, + stmt->sql2c_conversion == CONVERSION_VIOLATION ? + SQL_STATE_07006 : SQL_STATE_HYC00); + } + DBGH(stmt, "convertibility check: OK."); + /* no break; */ + + default: + DBGH(stmt, "ES/app data/buffer types found compatible."); + } + DBGH(stmt, "(`%.*s`); cursor @ %zd / %zd.", stmt->sqllen, stmt->u8sql, stmt->rset.vrows, stmt->rset.nrows); @@ -1872,8 +2259,8 @@ SQLRETURN EsSQLColAttributeW( *(SQLWCHAR **)pCharAttr = MK_WPTR(""); *pcbCharAttr = 0; } else { - return write_wptr(&stmt->hdr.diag, pcbCharAttr, wptr, - cbDescMax, pcbCharAttr); + return write_wptr(&stmt->hdr.diag, pCharAttr, wptr, cbDescMax, + pcbCharAttr); } break; diff --git a/driver/queries.h b/driver/queries.h index ffc36f6f..2b099b27 100644 --- a/driver/queries.h +++ b/driver/queries.h @@ -21,9 +21,11 @@ #include "handles.h" void clear_resultset(esodbc_stmt_st *stmt); -SQLRETURN attach_answer(esodbc_stmt_st *stmt, char *buff, size_t blen); -SQLRETURN attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen); -SQLRETURN attach_sql(esodbc_stmt_st *stmt, const SQLWCHAR *sql, size_t tlen); +SQLRETURN TEST_API attach_answer(esodbc_stmt_st *stmt, char *buff, + size_t blen); +SQLRETURN TEST_API attach_error(esodbc_stmt_st *stmt, char *buff, size_t blen); +SQLRETURN TEST_API attach_sql(esodbc_stmt_st *stmt, const SQLWCHAR *sql, + size_t tlen); void detach_sql(esodbc_stmt_st *stmt); diff --git a/driver/util.c b/driver/util.c index 07a107da..0ab11de4 100644 --- a/driver/util.c +++ b/driver/util.c @@ -41,7 +41,7 @@ BOOL wstr2long(wstr_st *val, long *out) return FALSE; switch (val->str[0]) { - case '-': + case L'-': negative = TRUE; i ++; break; @@ -55,9 +55,9 @@ BOOL wstr2long(wstr_st *val, long *out) for ( ; i < val->cnt; i ++) { /* is it a number? */ - if (val->str[i] < '0' || '9' < val->str[i]) + if (val->str[i] < L'0' || L'9' < val->str[i]) return FALSE; - digit = val->str[i] - '0'; + digit = val->str[i] - L'0'; /* would it overflow?*/ if (LONG_MAX - res < digit) return FALSE; @@ -68,10 +68,32 @@ BOOL wstr2long(wstr_st *val, long *out) return TRUE; } +/* + * Trims leading and trailing WS of a wide string of 'chars' lenght. + * 0-terminator should not be counted (as it's a non-WS). + */ +const SQLWCHAR* trim_ws(const SQLWCHAR *wstr, size_t *chars) +{ + const SQLWCHAR *wend; + size_t cnt = *chars; + + /* right trim */ + for (wend = wstr + cnt; wstr < wend && iswspace(*wstr); wstr ++) { + cnt --; + } + + while ((0 < cnt) && iswspace(wstr[cnt - 1])) { + cnt --; + } + + *chars = cnt; + return wstr; +} + /* * Converts a wchar_t string to a C string for ANSI characters. - * 'dst' should be as character-long as 'src', if 'src' is 0-terminated, - * OR one character longer otherwise (for the 0-term). + * 'dst' should be at least as character-long as 'src', if 'src' is + * 0-terminated, OR one character longer otherwise (for the 0-term). * 'dst' will always be 0-term'd. * Returns negative if conversion fails, OR number of converted wchars, * including the 0-term. @@ -80,7 +102,11 @@ BOOL wstr2long(wstr_st *val, long *out) int ansi_w2c(const SQLWCHAR *src, char *dst, size_t chars) { int i = 0; - + + if (chars < 1) { + return -1; + } + do { if (CHAR_MAX < src[i]) return -(i + 1); @@ -89,7 +115,7 @@ int ansi_w2c(const SQLWCHAR *src, char *dst, size_t chars) if (chars <= i) { /* equiv to: (src[i] != 0) */ /* loop stopped b/c of length -> src is not 0-term'd */ - dst[i] = 0; + dst[i] = 0; /* chars + 1 <= [dst] */ } return i + 1; } diff --git a/driver/util.h b/driver/util.h index 5fa0870e..5019f543 100644 --- a/driver/util.h +++ b/driver/util.h @@ -34,6 +34,16 @@ #include "sql.h" #include "sqlext.h" +/* export attribute for internal functions used for testing */ +#ifndef TEST_API /* Release builds define this to an empty macro */ +#ifdef DRIVER_BUILD +#define TEST_API __declspec(dllexport) +#else /* _EXPORTS */ +#define TEST_API __declspec(dllimport) +#endif /* _EXPORTS */ +#define TESTING /* compiles in the testing code */ +#endif /* TEST_API */ + /* * Assert two integral types have same storage and sign. */ @@ -72,6 +82,11 @@ typedef struct cstr { size_t cnt; } cstr_st; +/* + * Trims leading and trailing WS of a wide string of 'chars' lenght. + * 0-terminator should not be counted (as it's a non-WS). + */ +const SQLWCHAR* trim_ws(const SQLWCHAR *wstr, size_t *chars); /* * Copy converted strings from SQLWCHAR to char, for ANSI strings. */ @@ -104,10 +119,14 @@ typedef struct wstr { } wstr_st; /* - * Turn a static C string t a wstr_st. + * Turns a static C string into a wstr_st. */ -#define MK_WSTR(_s) \ +#ifndef __cplusplus /* no MSVC support for compound literals with /TP */ +#define MK_WSTR(_s) \ ((wstr_st){.str = MK_WPTR(_s), .cnt = sizeof(_s) - 1}) +#else /* !__cplusplus */ +#define WSTR_INIT(_s) {MK_WPTR(_s), sizeof(_s) - 1} +#endif /* !__cplusplus */ /* * Test equality of two wstr_st objects. */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..5e87a105 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,121 @@ +# +# ELASTICSEARCH CONFIDENTIAL +# __________________ +# +# [2018] Elasticsearch Incorporated. All Rights Reserved. +# +# NOTICE: All information contained herein is, and remains +# the property of Elasticsearch Incorporated and its suppliers, +# if any. The intellectual and technical concepts contained +# herein are proprietary to Elasticsearch Incorporated +# and its suppliers and may be covered by U.S. and Foreign Patents, +# patents in process, and are protected by trade secret or copyright law. +# Dissemination of this information or reproduction of this material +# is strictly forbidden unless prior written permission is obtained +# from Elasticsearch Incorporated. +# + +# allow the user set the paths, if library already installed on the system +# (not recommended, though) +set(GTEST_LD_PATH "" CACHE PATH "Lib googletest load library path") +set(GTEST_INC_PATH "" CACHE PATH "Lib googletest include path") + +if (NOT IS_DIRECTORY ${GTEST_LD_PATH}) + set(GTEST_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/googletest/install) + include(ExternalProject) + # build googletest along, tunning it to driver's useage + ExternalProject_Add( + googletest + EXCLUDE_FROM_ALL 1 + #GIT_REPOSITORY "https://github.com/google/googletest.git" + #GIT_SHALLOW ON + #GIT_TAG release-1.8.0 + UPDATE_DISCONNECTED ON + DOWNLOAD_COMMAND git clone --depth 1 https://github.com/google/googletest.git + UPDATE_COMMAND "" + CMAKE_ARGS -DBUILD_GMOCK=OFF -DBUILD_GTEST=ON + -Dgtest_force_shared_crt=ON -DINSTALL_GTEST=ON + -DCMAKE_INSTALL_PREFIX=${GTEST_INSTALL_PREFIX} + ) + + set(GTEST_LD_PATH ${GTEST_INSTALL_PREFIX}/lib) + set(GTEST_INC_PATH ${GTEST_INSTALL_PREFIX}/include) + set(GTEST_LIB ${GTEST_LD_PATH}/gtest${CMAKE_STATIC_LIBRARY_SUFFIX}) + set(GTEST_MAIN_LIB + ${GTEST_LD_PATH}/gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}) +else (NOT IS_DIRECTORY ${GTEST_LD_PATH}) +# GoogleTest adds a trailing 'd' to Debug build: find whatever's installed +# (https://github.com/google/googletest/issues/1268) +find_library(GTEST_LIB NAMES gtest gtestd PATHS ${GTEST_LD_PATH}) +find_library(GTEST_MAIN_LIB NAMES gtest_main gtest_maind + PATHS ${GTEST_LD_PATH}) +if (${GTEST_LIB_NOTFOUND} or ${GTEST_MAIN_LIB_NOTFOUND}) + message(FATAL_ERROR "Google test libraries not found") +endif (${GTEST_LIB_NOTFOUND}) +endif (NOT IS_DIRECTORY ${GTEST_LD_PATH}) +message("Googletest paths: load lib: ${GTEST_LD_PATH}, " + "include: ${GTEST_INC_PATH}.") + + +include_directories(${CMAKE_SOURCE_DIR}/test ${GTEST_INC_PATH}) + +# place the build files in test/ dir +# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/test) + +# find all test case source files +file(GLOB TEST_CASES LIST_DIRECTORIES false + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} test_*.cc) +message("Test cases: ${TEST_CASES}") + +set(EXTRA_SRC connected_dbc.cc) + +# copy DLLs linked (later) against, so that test exes can load them +file(TO_NATIVE_PATH ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${DRV_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX} + SRC_PATH_ESODBC_DLL) +file(TO_NATIVE_PATH ${LIBCURL_LD_PATH}/libcurl${CMAKE_SHARED_LIBRARY_SUFFIX} + SRC_PATH_CURL_DLL) +file(TO_NATIVE_PATH ${CMAKE_BINARY_DIR}/test/${CMAKE_CFG_INTDIR}/ + DST_PATH_DLL) + +# gtestd->gtest hack: copy debug libraries to non-debug file names. +# The trouble is, there's no way to configure this behavior (prevening the 'd' +# postfixing) and the libs are built later in the process, not when this code +# is evaluated to generate the project files; which means, no find_library() +# can be triggered here and no way of knowing which build config is going to +# be used either. +file(TO_NATIVE_PATH ${GTEST_INSTALL_PREFIX} GTEST_NATIVE_PREFIX) +# - existing Release googletest libs built along the project (vs. OS provided) +file(TO_NATIVE_PATH ${GTEST_LD_PATH}/gtestd${CMAKE_STATIC_LIBRARY_SUFFIX} + GTEST_REL_LIB) +file(TO_NATIVE_PATH ${GTEST_LD_PATH}/gtest_maind${CMAKE_STATIC_LIBRARY_SUFFIX} + GTEST_REL_MAIN_LIB) +# - target googletest lib, no matter the build config +file(TO_NATIVE_PATH ${GTEST_LIB} GTEST_NATIVE_LIB) +file(TO_NATIVE_PATH ${GTEST_MAIN_LIB} GTEST_NATIVE_MAIN_LIB) + +add_custom_target(install_shared + # dir's otherwise created only later on test target execution + COMMAND if not exist ${DST_PATH_DLL} mkdir ${DST_PATH_DLL} + COMMAND copy ${SRC_PATH_ESODBC_DLL} ${DST_PATH_DLL} + COMMAND copy ${SRC_PATH_CURL_DLL} ${DST_PATH_DLL} + # gtest->gtestd hack + COMMAND if exist ${GTEST_NATIVE_PREFIX} if exist ${GTEST_REL_LIB} + copy ${GTEST_REL_LIB} ${GTEST_NATIVE_LIB} + COMMAND if exist ${GTEST_NATIVE_PREFIX} if exist ${GTEST_REL_MAIN_LIB} + copy ${GTEST_REL_MAIN_LIB} ${GTEST_NATIVE_MAIN_LIB} + ) + +foreach (TSRC ${TEST_CASES}) + string(REPLACE ".cxx" "" TBIN ${TSRC}) + add_executable(${TBIN} ${TSRC} ${EXTRA_SRC}) + set_target_properties(${TBIN} PROPERTIES COMPILE_FLAGS ${CMAKE_C_FLAGS}) + target_link_libraries(${TBIN} ${DRV_NAME} ${GTEST_LIB} ${GTEST_MAIN_LIB}) + add_dependencies(${TBIN} install_shared) + if (GTEST_INSTALL_PREFIX) + # no pre-existing library on the system -> build gtest(d) + add_dependencies(${TBIN} googletest) + endif () + add_test(${TBIN} ${TBIN}) +endforeach (TSRC) + +# vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : diff --git a/test/connected_dbc.cc b/test/connected_dbc.cc new file mode 100644 index 00000000..eb6ab666 --- /dev/null +++ b/test/connected_dbc.cc @@ -0,0 +1,156 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * [2014] Elasticsearch Incorporated. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Elasticsearch Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Elasticsearch Incorporated. + */ + +#include +#include + +extern "C" { +#include "util.h" +#include "defs.h" +} + +#include "connected_dbc.h" + +/* + * Answer ES/SQL sends to SYS TYPES + * TODO: (Changes: MINIMUM_SCALE changed from 0 to MAXIMUM_SCALE for: + * HALF_FLOAT, SCALED_FLOAT, FLOAT, DOUBLE.) + */ +static const char systypes_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"TYPE_NAME\", \"type\": \"keyword\"},\ + {\"name\": \"DATA_TYPE\", \"type\": \"integer\"},\ + {\"name\": \"PRECISION\", \"type\": \"integer\"},\ + {\"name\": \"LITERAL_PREFIX\", \"type\": \"keyword\"},\ + {\"name\": \"LITERAL_SUFFIX\", \"type\": \"keyword\"},\ + {\"name\": \"CREATE_PARAMS\", \"type\": \"keyword\"},\ + {\"name\": \"NULLABLE\", \"type\": \"short\"},\ + {\"name\": \"CASE_SENSITIVE\", \"type\": \"boolean\"},\ + {\"name\": \"SEARCHABLE\", \"type\": \"short\"},\ + {\"name\": \"UNSIGNED_ATTRIBUTE\", \"type\": \"boolean\"},\ + {\"name\": \"FIXED_PREC_SCALE\", \"type\": \"boolean\"},\ + {\"name\": \"AUTO_INCREMENT\", \"type\": \"boolean\"},\ + {\"name\": \"LOCAL_TYPE_NAME\", \"type\": \"keyword\"},\ + {\"name\": \"MINIMUM_SCALE\", \"type\": \"short\"},\ + {\"name\": \"MAXIMUM_SCALE\", \"type\": \"short\"},\ + {\"name\": \"SQL_DATA_TYPE\", \"type\": \"integer\"},\ + {\"name\": \"SQL_DATETIME_SUB\", \"type\": \"integer\"},\ + {\"name\": \"NUM_PREC_RADIX\", \"type\": \"integer\"},\ + {\"name\": \"INTERVAL_PRECISION\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [\"BYTE\", -6, 3, \"'\", \"'\", null, 2, false, 3, false, false, false,\ + null, 0, 0, -6, 0, 10, null],\ + [\"LONG\", -5, 19, \"'\", \"'\", null, 2, false, 3, false, false, false,\ + null, 0, 0, -5, 0, 10, null],\ + [\"BINARY\", -3, 2147483647, \"'\", \"'\", null, 2, false, 3, true, false,\ + false, null, null, null, -3, 0, null, null],\ + [\"NULL\", 0, 0, \"'\", \"'\", null, 2, false, 3, true, false, false,\ + null, null, null, 0, 0, null, null],\ + [\"INTEGER\", 4, 10, \"'\", \"'\", null, 2, false, 3, false, false, false,\ + null, 0, 0, 4, 0, 10, null],\ + [\"SHORT\", 5, 5, \"'\", \"'\", null, 2, false, 3, false, false, false,\ + null, 0, 0, 5, 0, 10, null],\ + [\"HALF_FLOAT\", 6, 16, \"'\", \"'\", null, 2, false, 3, false, false,\ + false, null, 0, 16, 6, 0, 2, null],\ + [\"SCALED_FLOAT\", 6, 19, \"'\", \"'\", null, 2, false, 3, false, false,\ + false, null, 0, 19, 6, 0, 2, null],\ + [\"FLOAT\", 7, 7, \"'\", \"'\", null, 2, false, 3, false, false, false,\ + null, 0, 7, 7, 0, 2, null],\ + [\"DOUBLE\", 8, 15, \"'\", \"'\", null, 2, false, 3, false, false, false,\ + null, 0, 15, 8, 0, 2, null],\ + [\"KEYWORD\", 12, 256, \"'\", \"'\", null, 2, true, 3, true, false, false,\ + null, null, null, 12, 0, null, null],\ + [\"TEXT\", 12, 2147483647, \"'\", \"'\", null, 2, true, 3, true, false,\ + false, null, null, null, 12, 0, null, null],\ + [\"BOOLEAN\", 16, 1, \"'\", \"'\", null, 2, false, 3, true, false, false,\ + null, null, null, 16, 0, null, null],\ + [\"DATE\", 93, 24, \"'\", \"'\", null, 2, false, 3, true, false, false,\ + null, 3, 3, 9, 3, null, null],\ + [\"UNSUPPORTED\", 1111, 0, \"'\", \"'\", null, 2, false, 3, true, false,\ + false, null, null, null, 1111, 0, null, null],\ + [\"OBJECT\", 2002, 0, \"'\", \"'\", null, 2, false, 3, true, false, false,\ + null, null, null, 2002, 0, null, null],\ + [\"NESTED\", 2002, 0, \"'\", \"'\", null, 2, false, 3, true, false, false,\ + null, null, null, 2002, 0, null, null]\ + ]\ +}\ +"; + +/* minimal, valid connection string */ +static const SQLWCHAR connect_string[] = L"Driver=ElasticODBC"; + + +/* + * Class will provide a "connected" DBC: the ES types are loaded. + */ +ConnectedDBC::ConnectedDBC() { + SQLRETURN ret; + cstr_st types; + + ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); + assert(SQL_SUCCEEDED(ret)); + + ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, + (SQLPOINTER)SQL_OV_ODBC3_80, NULL); + assert(SQL_SUCCEEDED(ret)); + + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc); + assert(SQL_SUCCEEDED(ret)); + + + types.cnt = sizeof(systypes_answer) - 1; + types.str = (SQLCHAR *)malloc(types.cnt); + assert(types.str != NULL); + memcpy(types.str, systypes_answer, types.cnt); + + ret = SQLDriverConnect(dbc, (SQLHWND)&types, (SQLWCHAR *)connect_string, + sizeof(connect_string) / sizeof(connect_string[0]) - 1, NULL, 0, NULL, + ESODBC_SQL_DRIVER_TEST); + assert(SQL_SUCCEEDED(ret)); + + ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt); + assert(SQL_SUCCEEDED(ret)); + assert(stmt != NULL); +} + +ConnectedDBC::~ConnectedDBC() { + SQLRETURN ret; + + ret = SQLFreeHandle(SQL_HANDLE_STMT, stmt); + assert(SQL_SUCCEEDED(ret)); + + ret = SQLFreeHandle(SQL_HANDLE_DBC, dbc); + assert(SQL_SUCCEEDED(ret)); + + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + assert(SQL_SUCCEEDED(ret)); +} + +void ConnectedDBC::assertState(const SQLWCHAR *state) { + SQLRETURN ret; + SQLWCHAR buff[SQL_SQLSTATE_SIZE+1] = {L'\0'}; + SQLSMALLINT len; + + ret = SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_SQLSTATE, buff, + (SQL_SQLSTATE_SIZE + 1) * sizeof(buff[0]), &len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ASSERT_EQ(len, SQL_SQLSTATE_SIZE * sizeof(buff[0])); + ASSERT_STREQ(buff, state); + +} diff --git a/test/connected_dbc.h b/test/connected_dbc.h new file mode 100644 index 00000000..0ca3d399 --- /dev/null +++ b/test/connected_dbc.h @@ -0,0 +1,53 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * [2014] Elasticsearch Incorporated. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Elasticsearch Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Elasticsearch Incorporated. + */ + +#ifndef __CONNECTED_DBC_H__ +#define __CONNECTED_DBC_H__ + +extern "C" { +#if defined(_WIN32) || defined (WIN32) +#include +#include +#endif /* _WIN32/WIN32 */ + +#include +#include + +#include "queries.h" +} // extern C + +#if defined(_WIN32) || defined (WIN32) +#define STRDUP _strdup +#else /* _WIN32/WIN32 */ +#define STRDUP strdup +#endif /* _WIN32/WIN32 */ + +/* convenience casting macros */ +#define ATTACH_ANSWER(_h, _s, _l) attach_answer((esodbc_stmt_st *)_h, _s, _l) +#define ATTACH_SQL(_h, _s, _l) attach_sql((esodbc_stmt_st *)_h, _s, _l) + +class ConnectedDBC { + protected: + SQLHANDLE env, dbc, stmt; + + ConnectedDBC(); + virtual ~ConnectedDBC(); + + void assertState(const SQLWCHAR *state); +}; + +#endif /* __CONNECTED_DBC_H__ */ diff --git a/test/test_conversion_sql2c_date.cc b/test/test_conversion_sql2c_date.cc new file mode 100644 index 00000000..d882e725 --- /dev/null +++ b/test/test_conversion_sql2c_date.cc @@ -0,0 +1,202 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * [2014] Elasticsearch Incorporated. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Elasticsearch Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Elasticsearch Incorporated. + */ + +#include +#include "connected_dbc.h" + +#include + +/* placeholders; will be undef'd and redef'd */ +#define SQL_VAL +#define SQL /* attached for troubleshooting purposes */ + +namespace test { + +class ConvertSQL2C_Date : public ::testing::Test, public ConnectedDBC { + + protected: + SQLRETURN ret; + DATE_STRUCT ds; + SQLLEN ind_len = SQL_NULL_DATA; + + ConvertSQL2C_Date() { + } + + virtual ~ConvertSQL2C_Date() { + } + + virtual void SetUp() { + } + + virtual void TearDown() { + } + + void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { + char *answer = STRDUP(json_answer); + ASSERT_TRUE(answer != NULL); + ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = ATTACH_SQL(stmt, sql, wcslen(sql)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_TYPE_DATE, &ds, sizeof(ds), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + } +}; + + +/* ES/SQL 'date' is actually 'timestamp' */ +TEST_F(ConvertSQL2C_Date, Timestamp2Date) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "2345-01-23T00:00:00Z" +#define SQL "CAST(" SQL_VAL "AS DATE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"date\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ds)); + EXPECT_EQ(ds.year, 2345); + EXPECT_EQ(ds.month, 1); + EXPECT_EQ(ds.day, 23); +} + +TEST_F(ConvertSQL2C_Date, Timestamp2Date_truncate) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL " 2345-01-23T12:34:56.789Z " +#define SQL "CAST(" SQL_VAL "AS DATE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"CAST(" SQL ")\", \"type\": \"date\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + assertState(L"01S07"); + + EXPECT_EQ(ind_len, sizeof(ds)); + EXPECT_EQ(ds.year, 2345); + EXPECT_EQ(ds.month, 1); + EXPECT_EQ(ds.day, 23); +} + + +TEST_F(ConvertSQL2C_Date, Date2Date) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "2345-01-23" +#define SQL "CAST(" SQL_VAL "AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ds)); + EXPECT_EQ(ds.year, 2345); + EXPECT_EQ(ds.month, 1); + EXPECT_EQ(ds.day, 23); +} + + +TEST_F(ConvertSQL2C_Date, Time2Date_22018) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "10:10:10.1010" +#define SQL "CAST(" SQL_VAL "AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22018"); +} + + +TEST_F(ConvertSQL2C_Date, Integer2Date_violation_07006) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "1" +#define SQL "select " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"select " SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL_VAL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"07006"); +} + + +} // test namespace + diff --git a/test/test_conversion_sql2c_interval.cc b/test/test_conversion_sql2c_interval.cc new file mode 100644 index 00000000..d5a6848f --- /dev/null +++ b/test/test_conversion_sql2c_interval.cc @@ -0,0 +1,115 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * [2014] Elasticsearch Incorporated. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Elasticsearch Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Elasticsearch Incorporated. + */ + +#include +#include "connected_dbc.h" + +#include + +/* placeholders; will be undef'd and redef'd */ +#define SQL_VAL +#define SQL /* attached for troubleshooting purposes */ + +namespace test { + +class ConvertSQL2C_Interval : public ::testing::Test, public ConnectedDBC { + + protected: + SQLRETURN ret; + SQL_INTERVAL_STRUCT is; + SQLLEN ind_len = SQL_NULL_DATA; + + ConvertSQL2C_Interval() { + } + + virtual ~ConvertSQL2C_Interval() { + } + + virtual void SetUp() { + } + + virtual void TearDown() { + } + + void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { + char *answer = STRDUP(json_answer); + ASSERT_TRUE(answer != NULL); + ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = ATTACH_SQL(stmt, sql, wcslen(sql)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_INTERVAL_HOUR, &is, sizeof(is), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + } +}; + + + +TEST_F(ConvertSQL2C_Interval, Integer2Interval_unsupported_HYC00) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "1" +#define SQL "select " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"select " SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"HYC00"); +} + + +TEST_F(ConvertSQL2C_Interval, Integer2Interval_violation_07006) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "1.1" +#define SQL "select " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"select " SQL "\", \"type\": \"double\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"07006"); +} + + +} // test namespace + diff --git a/test/test_conversion_sql2c_time.cc b/test/test_conversion_sql2c_time.cc new file mode 100644 index 00000000..bc091870 --- /dev/null +++ b/test/test_conversion_sql2c_time.cc @@ -0,0 +1,233 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * [2014] Elasticsearch Incorporated. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Elasticsearch Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Elasticsearch Incorporated. + */ + +#include +#include "connected_dbc.h" + +#include + +/* placeholders; will be undef'd and redef'd */ +#define SQL_VAL +#define SQL /* attached for troubleshooting purposes */ + +namespace test { + +class ConvertSQL2C_Time : public ::testing::Test, public ConnectedDBC { + + protected: + SQLRETURN ret; + TIME_STRUCT ts; + SQLLEN ind_len = SQL_NULL_DATA; + + ConvertSQL2C_Time() { + } + + virtual ~ConvertSQL2C_Time() { + } + + virtual void SetUp() { + } + + virtual void TearDown() { + } + + void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { + char *answer = STRDUP(json_answer); + ASSERT_TRUE(answer != NULL); + ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = ATTACH_SQL(stmt, sql, wcslen(sql)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_TYPE_TIME, &ts, sizeof(ts), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + } +}; + + +/* ES/SQL 'date' is actually 'timestamp' */ +TEST_F(ConvertSQL2C_Time, Timestamp2Time) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "2345-01-23T12:34:56.000Z" +#define SQL "CAST(" SQL_VAL "AS DATE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"date\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ts)); + EXPECT_EQ(ts.hour, 12); + EXPECT_EQ(ts.minute, 34); + EXPECT_EQ(ts.second, 56); +} + +TEST_F(ConvertSQL2C_Time, Timestamp2Time_truncate) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL " 2345-01-23T12:34:56.789Z " +#define SQL "CAST(" SQL_VAL "AS DATE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"date\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + assertState(L"01S07"); + + EXPECT_EQ(ind_len, sizeof(ts)); + EXPECT_EQ(ts.hour, 12); + EXPECT_EQ(ts.minute, 34); + EXPECT_EQ(ts.second, 56); +} + + +TEST_F(ConvertSQL2C_Time, Time2Time) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "12:34:56.0" +#define SQL "CAST(" SQL_VAL "AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL_VAL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ts)); + EXPECT_EQ(ts.hour, 12); + EXPECT_EQ(ts.minute, 34); + EXPECT_EQ(ts.second, 56); +} + + +TEST_F(ConvertSQL2C_Time, Time2Time_truncate) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "12:34:56.7777777" +#define SQL "CAST(" SQL_VAL "AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + assertState(L"01S07"); + + EXPECT_EQ(ind_len, sizeof(ts)); + EXPECT_EQ(ts.hour, 12); + EXPECT_EQ(ts.minute, 34); + EXPECT_EQ(ts.second, 56); +} + + +TEST_F(ConvertSQL2C_Time, Date2Time_22018) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "2345-01-23" +#define SQL "CAST(" SQL_VAL "AS TEXT)" + + const SQLWCHAR *sql = MK_WPTR(SQL_VAL); + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL_VAL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL_VAL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22018"); +} + + +TEST_F(ConvertSQL2C_Time, Integer2Date_violation_07006) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "1" +#define SQL "select " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"select " SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"07006"); +} + + +} // test namespace + diff --git a/test/test_conversion_sql2c_timestamp.cc b/test/test_conversion_sql2c_timestamp.cc new file mode 100644 index 00000000..c00a9867 --- /dev/null +++ b/test/test_conversion_sql2c_timestamp.cc @@ -0,0 +1,279 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * [2014] Elasticsearch Incorporated. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Elasticsearch Incorporated + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Elasticsearch Incorporated. + */ + +#include +#include "connected_dbc.h" + +#include + +/* placeholders; will be undef'd and redef'd */ +#define SQL_VAL +#define SQL /* attached for troubleshooting purposes */ + +namespace test { + +class ConvertSQL2C_Timestamp : public ::testing::Test, public ConnectedDBC { + + protected: + SQLRETURN ret; + TIMESTAMP_STRUCT ts; + SQLLEN ind_len = SQL_NULL_DATA; + + ConvertSQL2C_Timestamp() { + } + + virtual ~ConvertSQL2C_Timestamp() { + } + + virtual void SetUp() { + } + + virtual void TearDown() { + } + + void prepareStatement(const SQLWCHAR *sql, const char *json_answer) { + char *answer = STRDUP(json_answer); + ASSERT_TRUE(answer != NULL); + ret = ATTACH_ANSWER(stmt, answer, strlen(answer)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + ret = ATTACH_SQL(stmt, sql, wcslen(sql)); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + ret = SQLBindCol(stmt, /*col#*/1, SQL_C_TYPE_TIMESTAMP, &ts, sizeof(ts), + &ind_len); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + } +}; + + +/* ES/SQL 'date' is actually 'timestamp' */ +TEST_F(ConvertSQL2C_Timestamp, Timestamp2Timestamp_noTruncate) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "2345-01-23T12:34:56.789Z" +#define SQL "CAST(" SQL_VAL " AS DATE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"date\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ts)); + EXPECT_EQ(ts.year, 2345); + EXPECT_EQ(ts.month, 1); + EXPECT_EQ(ts.day, 23); + EXPECT_EQ(ts.hour, 12); + EXPECT_EQ(ts.minute, 34); + EXPECT_EQ(ts.second, 56); + EXPECT_EQ(ts.fraction, 789); +} +/* No second fraction truncation done by the driver -> no test for 01S07 */ + +TEST_F(ConvertSQL2C_Timestamp, Timestamp2Timestamp_trimming) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL " 2345-01-23T12:34:56.789Z " +#define SQL "CAST(" SQL_VAL " AS DATE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"date\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ts)); + EXPECT_EQ(ts.year, 2345); + EXPECT_EQ(ts.month, 1); + EXPECT_EQ(ts.day, 23); + EXPECT_EQ(ts.hour, 12); + EXPECT_EQ(ts.minute, 34); + EXPECT_EQ(ts.second, 56); + EXPECT_EQ(ts.fraction, 789); +} + + +TEST_F(ConvertSQL2C_Timestamp, Date2Timestamp) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "2345-01-23T" +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ts)); + EXPECT_EQ(ts.year, 2345); + EXPECT_EQ(ts.month, 1); + EXPECT_EQ(ts.day, 23); + EXPECT_EQ(ts.hour, 0); + EXPECT_EQ(ts.minute, 0); + EXPECT_EQ(ts.second, 0); + EXPECT_EQ(ts.fraction, 0); +} + + +TEST_F(ConvertSQL2C_Timestamp, Time2Timestamp) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "10:10:10.1010" +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ts)); + EXPECT_EQ(ts.year, 0); + EXPECT_EQ(ts.month, 0); + EXPECT_EQ(ts.day, 0); + EXPECT_EQ(ts.hour, 10); + EXPECT_EQ(ts.minute, 10); + EXPECT_EQ(ts.second, 10); + EXPECT_EQ(ts.fraction, 101); +} + + +TEST_F(ConvertSQL2C_Timestamp, Time2Timestamp_trimming) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL " 10:10:10.1010 " +#define SQL "CAST(" SQL_VAL " AS TEXT)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"text\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_TRUE(SQL_SUCCEEDED(ret)); + + EXPECT_EQ(ind_len, sizeof(ts)); + EXPECT_EQ(ts.year, 0); + EXPECT_EQ(ts.month, 0); + EXPECT_EQ(ts.day, 0); + EXPECT_EQ(ts.hour, 10); + EXPECT_EQ(ts.minute, 10); + EXPECT_EQ(ts.second, 10); + EXPECT_EQ(ts.fraction, 101); +} + + +TEST_F(ConvertSQL2C_Timestamp, String2Timestamp_invalidFormat_22018) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "invalid 2345-01-23T12:34:56.789Z" +#define SQL "CAST(" SQL_VAL " AS DATE)" + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"date\"}\ + ],\ + \"rows\": [\ + [\"" SQL_VAL "\"]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"22018"); +} + +TEST_F(ConvertSQL2C_Timestamp, Integer2Timestamp_violation_07006) { + +#undef SQL_VAL +#undef SQL +#define SQL_VAL "1" +#define SQL "select " SQL_VAL + + const char json_answer[] = "\ +{\ + \"columns\": [\ + {\"name\": \"" SQL "\", \"type\": \"integer\"}\ + ],\ + \"rows\": [\ + [" SQL_VAL "]\ + ]\ +}\ +"; + prepareStatement(MK_WPTR(SQL), json_answer); + + ret = SQLFetch(stmt); + ASSERT_FALSE(SQL_SUCCEEDED(ret)); + assertState(L"07006"); +} + + +} // test namespace +