# HG changeset patch # User František Kučera # Date 1590428166 -7200 # Node ID 24c05e69d68fe52e20bfb2e23ce29ff6f01b1b4b # Parent 86ceb97db7de840b7522450dfbcbc4f3199d4ca6 add ODBC library and --list-data-sources mode (DSN) diff -r 86ceb97db7de -r 24c05e69d68f bash-completion.sh --- a/bash-completion.sh Sat May 23 23:58:50 2020 +0200 +++ b/bash-completion.sh Mon May 25 19:36:06 2020 +0200 @@ -46,6 +46,7 @@ "--copy-renamed" "--file" "--file-keep" + "--list-data-sources" ) COMPREPLY=($(compgen -W "${OPTIONS[*]}" -- "$w0")) fi diff -r 86ceb97db7de -r 24c05e69d68f nbproject/configurations.xml --- a/nbproject/configurations.xml Sat May 23 23:58:50 2020 +0200 +++ b/nbproject/configurations.xml Mon May 25 19:36:06 2020 +0200 @@ -43,6 +43,9 @@ Connection.cpp + DriverManager.cpp + DriverManager.h + OdbcCommon.h PreparedStatement.cpp SqlException.h relpipe-tr-sql.cpp @@ -99,6 +102,12 @@ + + + + + + @@ -143,6 +152,12 @@ true + + + + + + diff -r 86ceb97db7de -r 24c05e69d68f src/CLIParser.h --- a/src/CLIParser.h Sat May 23 23:58:50 2020 +0200 +++ b/src/CLIParser.h Mon May 25 19:36:06 2020 +0200 @@ -54,6 +54,7 @@ static const string_t OPTION_COPY_RENAMED; static const string_t OPTION_FILE; static const string_t OPTION_FILE_KEEP; + static const string_t OPTION_LIST_DATA_SOURCES; Configuration parse(const std::vector& arguments) { Configuration c; @@ -87,6 +88,8 @@ else if (value == L"true") c.keepFile = KeepFile::Always; else if (value == L"false") c.keepFile = KeepFile::Never; else throw relpipe::cli::RelpipeCLIException(L"Unsupported keep-file value: " + value + L" Expecting: true, false, auto", relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS); + } else if (option == OPTION_LIST_DATA_SOURCES) { + c.listDataSources = true; } else throw relpipe::cli::RelpipeCLIException(L"Unsupported CLI option: " + option, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS); } addQuery(c, currentQuery); // last relation @@ -109,6 +112,7 @@ const string_t CLIParser::OPTION_COPY_RENAMED = L"--copy-renamed"; const string_t CLIParser::OPTION_FILE = L"--file"; const string_t CLIParser::OPTION_FILE_KEEP = L"--file-keep"; +const string_t CLIParser::OPTION_LIST_DATA_SOURCES = L"--list-data-sources"; } } diff -r 86ceb97db7de -r 24c05e69d68f src/CMakeLists.txt --- a/src/CMakeLists.txt Sat May 23 23:58:50 2020 +0200 +++ b/src/CMakeLists.txt Mon May 25 19:36:06 2020 +0200 @@ -16,10 +16,13 @@ set(EXECUTABLE_FILE "relpipe-tr-sql") set(EXECUTABLE_FILE_IN "relpipe-in-sql") +# ODBC libraries: +include("FindODBC.cmake") + # Relpipe libraries: INCLUDE(FindPkgConfig) pkg_check_modules (RELPIPE_LIBS relpipe-lib-reader.cpp relpipe-lib-writer.cpp relpipe-lib-cli.cpp sqlite3) -include_directories(${RELPIPE_LIBS_INCLUDE_DIRS}) +include_directories(${RELPIPE_LIBS_INCLUDE_DIRS} ${ODBC_INCLUDE_DIRS}) link_directories(${RELPIPE_LIBS_LIBRARY_DIRS}) # Add ASan AddressSanitizer @@ -32,13 +35,14 @@ ${EXECUTABLE_FILE} PreparedStatement.cpp Connection.cpp + DriverManager.cpp relpipe-tr-sql.cpp ) ADD_CUSTOM_TARGET(in_mode_symlink ALL COMMAND ${CMAKE_COMMAND} -E create_symlink ${EXECUTABLE_FILE} ${EXECUTABLE_FILE_IN}) # Link libraries: -target_link_libraries(${EXECUTABLE_FILE} ${RELPIPE_LIBS_LIBRARIES}) +target_link_libraries(${EXECUTABLE_FILE} ${RELPIPE_LIBS_LIBRARIES} ${ODBC_LIBRARIES}) set_property(TARGET ${EXECUTABLE_FILE} PROPERTY INSTALL_RPATH_USE_LINK_PATH TRUE) install(TARGETS ${EXECUTABLE_FILE} DESTINATION bin) diff -r 86ceb97db7de -r 24c05e69d68f src/Configuration.h --- a/src/Configuration.h Sat May 23 23:58:50 2020 +0200 +++ b/src/Configuration.h Mon May 25 19:36:06 2020 +0200 @@ -121,6 +121,8 @@ std::wistream* sqlAfterRelational = nullptr; std::vector copyRelations; + + relpipe::writer::boolean_t listDataSources = false; virtual ~Configuration() { } diff -r 86ceb97db7de -r 24c05e69d68f src/DriverManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/DriverManager.cpp Mon May 25 19:36:06 2020 +0200 @@ -0,0 +1,62 @@ +/** + * Relational pipes + * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include +#include + +#include "SqlException.h" +#include "DriverManager.h" +#include "OdbcCommon.h" + +namespace relpipe { +namespace tr { +namespace sql { + +DriverManager::DriverManager() { + env = OdbcCommon::allocateHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE); + SQLRETURN result = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*) SQL_OV_ODBC3, 0); + if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to set ODBC version"); // TODO:, result, SQL_HANDLE_ENV, environment); +} + +DriverManager::~DriverManager() { + OdbcCommon::freeHandle(SQL_HANDLE_ENV, env); +} + +std::vector DriverManager::getDataSources() { + std::vector list; + SQLCHAR name[SQL_MAX_DSN_LENGTH + 1]; + SQLCHAR description[255]; + memset(name, 0, sizeof (name)); + memset(description, 0, sizeof (description)); + SQLSMALLINT nameLength; + SQLSMALLINT descriptionLength; + while (true) { + SQLRETURN result = SQLDataSources(env, SQL_FETCH_NEXT, name, sizeof (name), &nameLength, description, sizeof (description), &descriptionLength); + // TODO: check nameLength and descriptionLength whether values were truncated? + if (OdbcCommon::isSuccessful(result)) list.push_back({convertor.from_bytes((char*) name), convertor.from_bytes((char*) description)}); + else if (result == SQL_NO_DATA_FOUND) break; + else throw SqlException(L"Unable to list data sources: " + std::to_wstring(result)); + } + return list; +} + + +} +} +} \ No newline at end of file diff -r 86ceb97db7de -r 24c05e69d68f src/DriverManager.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/DriverManager.h Mon May 25 19:36:06 2020 +0200 @@ -0,0 +1,51 @@ +/** + * Relational pipes + * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include +#include + +#include + +#include "SqlException.h" + +namespace relpipe { +namespace tr { +namespace sql { + +class DriverManager { +private: + void* env; + std::wstring_convert> convertor; // TODO: support also other encodings + +public: + + class DataSource { + public: + relpipe::reader::string_t name; + relpipe::reader::string_t description; + }; + + DriverManager(); + virtual ~DriverManager(); + std::vector getDataSources(); +}; + +} +} +} \ No newline at end of file diff -r 86ceb97db7de -r 24c05e69d68f src/FindODBC.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/FindODBC.cmake Mon May 25 19:36:06 2020 +0200 @@ -0,0 +1,231 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindODBC +-------- + +Find an Open Database Connectivity (ODBC) include directory and library. + +On Windows, when building with Visual Studio, this module assumes the ODBC +library is provided by the available Windows SDK. + +On Unix, this module allows to search for ODBC library provided by +unixODBC or iODBC implementations of ODBC API. +This module reads hint about location of the config program: + +.. variable:: ODBC_CONFIG + + Location of odbc_config or iodbc-config program + +Otherwise, this module tries to find the config program, +first from unixODBC, then from iODBC. +If no config program found, this module searches for ODBC header +and library in list of known locations. + +Imported targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets: + +.. variable:: ODBC::ODBC + + Imported target for using the ODBC library, if found. + +Result variables +^^^^^^^^^^^^^^^^ + +.. variable:: ODBC_FOUND + + Set to true if ODBC library found, otherwise false or undefined. + +.. variable:: ODBC_INCLUDE_DIRS + + Paths to include directories listed in one variable for use by ODBC client. + May be empty on Windows, where the include directory corresponding to the + expected Windows SDK is already available in the compilation environment. + +.. variable:: ODBC_LIBRARIES + + Paths to libraries to linked against to use ODBC. + May just a library name on Windows, where the library directory corresponding + to the expected Windows SDK is already available in the compilation environment. + +.. variable:: ODBC_CONFIG + + Path to unixODBC or iODBC config program, if found or specified. + +Cache variables +^^^^^^^^^^^^^^^ + +For users who wish to edit and control the module behavior, this module +reads hints about search locations from the following variables: + +.. variable:: ODBC_INCLUDE_DIR + + Path to ODBC include directory with ``sql.h`` header. + +.. variable:: ODBC_LIBRARY + + Path to ODBC library to be linked. + +These variables should not be used directly by project code. + +Limitations +^^^^^^^^^^^ + +On Windows, this module does not search for iODBC. +On Unix, there is no way to prefer unixODBC over iODBC, or vice versa, +other than providing the config program location using the ``ODBC_CONFIG``. +This module does not allow to search for a specific ODBC driver. + +#]=======================================================================] + +# Define lists used internally +set(_odbc_include_paths) +set(_odbc_lib_paths) +set(_odbc_lib_names) +set(_odbc_required_libs_names) + +### Try Windows Kits ########################################################## +if(WIN32) + # List names of ODBC libraries on Windows + if(NOT MINGW) + set(ODBC_LIBRARY odbc32.lib) + else() + set(ODBC_LIBRARY libodbc32.a) + endif() + set(_odbc_lib_names odbc32;) + + # List additional libraries required to use ODBC library + if(MSVC OR CMAKE_CXX_COMPILER_ID MATCHES "Intel") + set(_odbc_required_libs_names odbccp32;ws2_32) + elseif(MINGW) + set(_odbc_required_libs_names odbccp32) + endif() +endif() + +### Try unixODBC or iODBC config program ###################################### +if (UNIX) + find_program(ODBC_CONFIG + NAMES odbc_config iodbc-config + DOC "Path to unixODBC or iODBC config program") + mark_as_advanced(ODBC_CONFIG) +endif() + +if (UNIX AND ODBC_CONFIG) + # unixODBC and iODBC accept unified command line options + execute_process(COMMAND ${ODBC_CONFIG} --cflags + OUTPUT_VARIABLE _cflags OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${ODBC_CONFIG} --libs + OUTPUT_VARIABLE _libs OUTPUT_STRIP_TRAILING_WHITESPACE) + + # Collect paths of include directories from CFLAGS + separate_arguments(_cflags NATIVE_COMMAND "${_cflags}") + foreach(arg IN LISTS _cflags) + if("${arg}" MATCHES "^-I(.*)$") + list(APPEND _odbc_include_paths "${CMAKE_MATCH_1}") + endif() + endforeach() + unset(_cflags) + + # Collect paths of library names and directories from LIBS + separate_arguments(_libs NATIVE_COMMAND "${_libs}") + foreach(arg IN LISTS _libs) + if("${arg}" MATCHES "^-L(.*)$") + list(APPEND _odbc_lib_paths "${CMAKE_MATCH_1}") + elseif("${arg}" MATCHES "^-l(.*)$") + set(_lib_name ${CMAKE_MATCH_1}) + string(REGEX MATCH "odbc" _is_odbc ${_lib_name}) + if(_is_odbc) + list(APPEND _odbc_lib_names ${_lib_name}) + else() + list(APPEND _odbc_required_libs_names ${_lib_name}) + endif() + unset(_lib_name) + endif() + endforeach() + unset(_libs) +endif() + +### Try unixODBC or iODBC in include/lib filesystems ########################## +if (UNIX AND NOT ODBC_CONFIG) + # List names of both ODBC libraries, unixODBC and iODBC + set(_odbc_lib_names odbc;iodbc;unixodbc;) +endif() + +### Find include directories ################################################## +find_path(ODBC_INCLUDE_DIR + NAMES sql.h + PATHS ${_odbc_include_paths}) + +if(NOT ODBC_INCLUDE_DIR AND WIN32) + set(ODBC_INCLUDE_DIR "") +endif() + +### Find libraries ############################################################ +if(NOT ODBC_LIBRARY) + find_library(ODBC_LIBRARY + NAMES ${_odbc_lib_names} + PATHS ${_odbc_lib_paths} + PATH_SUFFIXES odbc) + + foreach(_lib IN LISTS _odbc_required_libs_names) + find_library(_lib_path + NAMES ${_lib} + PATHS ${_odbc_lib_paths} # system parths or collected from ODBC_CONFIG + PATH_SUFFIXES odbc) + if(_lib_path) + list(APPEND _odbc_required_libs_paths ${_lib_path}) + endif() + unset(_lib_path CACHE) + endforeach() +endif() + +# Unset internal lists as no longer used +unset(_odbc_include_paths) +unset(_odbc_lib_paths) +unset(_odbc_lib_names) +unset(_odbc_required_libs_names) + +### Set result variables ###################################################### +set(_odbc_required_vars ODBC_LIBRARY) +if(NOT WIN32) + list(APPEND _odbc_required_vars ODBC_INCLUDE_DIR) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(ODBC DEFAULT_MSG ${_odbc_required_vars}) + +unset(_odbc_required_vars) + +mark_as_advanced(ODBC_LIBRARY ODBC_INCLUDE_DIR) + +set(ODBC_INCLUDE_DIRS ${ODBC_INCLUDE_DIR}) +list(APPEND ODBC_LIBRARIES ${ODBC_LIBRARY}) +list(APPEND ODBC_LIBRARIES ${_odbc_required_libs_paths}) + +### Import targets ############################################################ +if(ODBC_FOUND) + if(NOT TARGET ODBC::ODBC) + if(IS_ABSOLUTE "${ODBC_LIBRARY}") + add_library(ODBC::ODBC UNKNOWN IMPORTED) + set_target_properties(ODBC::ODBC PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${ODBC_LIBRARY}") + else() + add_library(ODBC::ODBC INTERFACE IMPORTED) + set_target_properties(ODBC::ODBC PROPERTIES + IMPORTED_LIBNAME "${ODBC_LIBRARY}") + endif() + set_target_properties(ODBC::ODBC PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${ODBC_INCLUDE_DIR}") + + if(_odbc_required_libs_paths) + set_property(TARGET ODBC::ODBC APPEND PROPERTY + INTERFACE_LINK_LIBRARIES "${_odbc_required_libs_paths}") + endif() + endif() +endif() + +unset(_odbc_required_libs_paths) diff -r 86ceb97db7de -r 24c05e69d68f src/OdbcCommon.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/OdbcCommon.h Mon May 25 19:36:06 2020 +0200 @@ -0,0 +1,53 @@ +/** + * Relational pipes + * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +#include "SqlException.h" + +namespace relpipe { +namespace tr { +namespace sql { + +class OdbcCommon { +public: + + static bool isSuccessful(SQLRETURN result) { + return result == SQL_SUCCESS || result == SQL_SUCCESS_WITH_INFO; + } + + static bool isNotSuccessful(SQLRETURN result) { + return !isSuccessful(result); + } + + static SQLHANDLE allocateHandle(SQLSMALLINT type, SQLHANDLE input) { + SQLHANDLE output; + SQLRETURN result = SQLAllocHandle(type, input, &output); + if (isNotSuccessful(result)) throw SqlException(L"Unable to allocate handle " + std::to_wstring(type)); + return output; + } + + static void freeHandle(SQLSMALLINT type, SQLHANDLE handle) { + SQLRETURN result = SQLFreeHandle(type, handle); + if (isNotSuccessful(result)) throw SqlException(L"Unable to free handle of type: " + std::to_wstring(type)); + } +}; + +} +} +} \ No newline at end of file diff -r 86ceb97db7de -r 24c05e69d68f src/SqlHandler.h --- a/src/SqlHandler.h Sat May 23 23:58:50 2020 +0200 +++ b/src/SqlHandler.h Mon May 25 19:36:06 2020 +0200 @@ -39,6 +39,7 @@ #include "SqlInputScanner.h" #include "PreparedStatement.h" #include "Connection.h" +#include "DriverManager.h" namespace relpipe { namespace tr { @@ -54,6 +55,7 @@ Configuration configuration; boolean_t fileAlreadyExisted = false; writer::RelationalWriter* relationalWriter; + DriverManager* driverManager; std::wstring_convert> convertor; // TODO: support also other encodings vector currentReaderMetadata; integer_t currentAttributeIndex = 0; @@ -156,7 +158,7 @@ public: - SqlHandler(writer::RelationalWriter* relationalWriter, Configuration& configuration) : relationalWriter(relationalWriter), configuration(configuration) { + SqlHandler(writer::RelationalWriter* relationalWriter, DriverManager* driverManager, Configuration& configuration) : relationalWriter(relationalWriter), driverManager(driverManager), configuration(configuration) { std::string file; if (configuration.file.size()) { file = convertor.to_bytes(configuration.file); @@ -270,6 +272,18 @@ } // else: we had no file, everything was in memory } + static void listDataSources(writer::RelationalWriter* relationalWriter, DriverManager* driverManager) { + relationalWriter->startRelation(L"data_source",{ + {L"name", writer::TypeId::STRING}, + {L"description", writer::TypeId::STRING} + }, true); + + for (DriverManager::DataSource ds : driverManager->getDataSources()) { + relationalWriter->writeAttribute(ds.name); + relationalWriter->writeAttribute(ds.description); + } + } + }; } diff -r 86ceb97db7de -r 24c05e69d68f src/relpipe-tr-sql.cpp --- a/src/relpipe-tr-sql.cpp Sat May 23 23:58:50 2020 +0200 +++ b/src/relpipe-tr-sql.cpp Mon May 25 19:36:06 2020 +0200 @@ -36,6 +36,7 @@ #include "SqlHandler.h" #include "CLIParser.h" #include "Configuration.h" +#include "DriverManager.h" using namespace relpipe::cli; using namespace relpipe::reader; @@ -52,19 +53,23 @@ CLIParser cliParser; Configuration configuration = cliParser.parse(cli.arguments()); - if (std::regex_match(cli.programName(), std::wregex(L"^(.*/)?relpipe-in-sql$"))) { + std::shared_ptr driverManager = std::make_shared(); + std::shared_ptr writer(writer::Factory::create(std::cout)); + + if (configuration.listDataSources) { + // --list-data-sources: + SqlHandler::listDataSources(writer.get(), driverManager.get()); + } else if (std::regex_match(cli.programName(), std::wregex(L"^(.*/)?relpipe-in-sql$"))) { // relpipe-in-sql: if (cli.arguments().size() == 0) configuration.copyRelations.push_back({L".*", L"", false}); - std::shared_ptr writer(writer::Factory::create(std::cout)); configuration.sqlBeforeRelational = isatty(fileno(stdin)) ? nullptr : &std::wcin; configuration.sqlAfterRelational = nullptr; - SqlHandler handler(writer.get(), configuration); + SqlHandler handler(writer.get(), driverManager.get(), configuration); handler.endOfPipe(); } else { // relpipe-tr-sql: std::shared_ptr reader(reader::Factory::create(std::cin)); - std::shared_ptr writer(writer::Factory::create(std::cout)); - SqlHandler handler(writer.get(), configuration); + SqlHandler handler(writer.get(), driverManager.get(), configuration); reader->addHandler(&handler); reader->process(); }