# HG changeset patch # User František Kučera # Date 1590952824 -7200 # Node ID 3de41719d7eb5b11ecc9207a644f4fc47ea0d403 # Parent 91cb012d779a63b955fcc030af236ba31c6c79ea add options --data-source-name and --data-source-url for custom datasource; drop options --file and --file-keep diff -r 91cb012d779a -r 3de41719d7eb bash-completion.sh --- a/bash-completion.sh Sun May 31 16:56:07 2020 +0200 +++ b/bash-completion.sh Sun May 31 21:20:24 2020 +0200 @@ -13,6 +13,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +_relpipe_tr_sql_completion_dsn() { + if type relpipe-out-nullbyte &> /dev/null; then + relpipe-tr-sql --list-data-sources | relpipe-out-nullbyte | while read -r -d '' name; do read -r -d '' description; echo $name; done + fi +} + _relpipe_tr_sql_completion() { local w0 w1 w2 @@ -27,6 +33,12 @@ "boolean" ) + DATA_SOURCE_URL=( + "Driver=SQLite3;Database=file::memory:" + "Driver=SQLite3;Database=file:temp-relpipe.sqlite" + "Driver=SQLite3;Database=file:/tmp/relpipe.sqlite" + ) + if [[ "$w1" == "--relation" && "x$w0" == "x" ]]; then COMPREPLY=("''") elif [[ "$w2" == "--relation" && "x$w0" == "x" ]]; then COMPREPLY=('"SELECT * FROM "') elif [[ "$w1" == "--type-cast" && "x$w0" == "x" ]]; then COMPREPLY=("''") @@ -35,8 +47,8 @@ elif [[ "$w1" == "--copy" && "x$w0" == "x" ]]; then COMPREPLY=("'.+'") elif [[ "$w1" == "--copy-renamed" && "x$w0" == "x" ]]; then COMPREPLY=("'.+'") elif [[ "$w2" == "--copy-renamed" && "x$w0" == "x" ]]; then COMPREPLY=("'copy_of_\$0'") - elif [[ "$w1" == "--file" ]]; then COMPREPLY=($(compgen -f "$w0")) - elif [[ "$w1" == "--file-keep" ]]; then COMPREPLY=($(compgen -W "true false auto" -- "$w0")) + elif [[ "$w1" == "--data-source-name" ]]; then COMPREPLY=($(compgen -W "$(_relpipe_tr_sql_completion_dsn)" -- "$w0")) + elif [[ "$w1" == "--data-source-url" ]]; then COMPREPLY=($(compgen -W "${DATA_SOURCE_URL[*]}" -- "$w0")) else OPTIONS=( "--relation" @@ -44,9 +56,9 @@ "--parameter" "--copy" "--copy-renamed" - "--file" - "--file-keep" "--list-data-sources" + "--data-source-name" + "--data-source-url" ) COMPREPLY=($(compgen -W "${OPTIONS[*]}" -- "$w0")) fi diff -r 91cb012d779a -r 3de41719d7eb src/CLIParser.h --- a/src/CLIParser.h Sun May 31 16:56:07 2020 +0200 +++ b/src/CLIParser.h Sun May 31 21:20:24 2020 +0200 @@ -52,8 +52,8 @@ static const string_t OPTION_PARAMETER; static const string_t OPTION_COPY; 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_DATA_SOURCE_NAME; + static const string_t OPTION_DATA_SOURCE_URL; static const string_t OPTION_LIST_DATA_SOURCES; Configuration parse(const std::vector& arguments) { @@ -80,20 +80,18 @@ c.copyRelations.push_back({readNext(arguments, i), L"", false}); } else if (option == OPTION_COPY_RENAMED) { c.copyRelations.push_back({readNext(arguments, i), readNext(arguments, i), true}); - } else if (option == OPTION_FILE) { - c.file = readNext(arguments, i); - } else if (option == OPTION_FILE_KEEP) { - string_t value = readNext(arguments, i); - if (value == L"auto") c.keepFile = KeepFile::Automatic; - 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_DATA_SOURCE_NAME) { + c.dataSourceName = readNext(arguments, i); + } else if (option == OPTION_DATA_SOURCE_URL) { + c.dataSourceURL = readNext(arguments, i); } 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 + if (c.dataSourceName.size() && c.dataSourceURL.size()) throw relpipe::cli::RelpipeCLIException(L"Specify data source name or data source URL, not both.", relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS); + return c; } @@ -110,9 +108,9 @@ const string_t CLIParser::OPTION_PARAMETER = L"--parameter"; const string_t CLIParser::OPTION_COPY = L"--copy"; 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"; +const string_t CLIParser::OPTION_DATA_SOURCE_NAME = L"--data-source-name"; +const string_t CLIParser::OPTION_DATA_SOURCE_URL = L"--data-source-url"; } } diff -r 91cb012d779a -r 3de41719d7eb src/Configuration.h --- a/src/Configuration.h Sun May 31 16:56:07 2020 +0200 +++ b/src/Configuration.h Sun May 31 21:20:24 2020 +0200 @@ -64,15 +64,6 @@ std::vector parameters; }; -enum class KeepFile { - /** The file will be kept even if it was created during this transformation, so all the input data will stay on the disk. */ - Always, - /** The file will be deleted at the end of this transformation – even if the file already existed. This is potentially dangerous, because we can unintentionally delete a database file. */ - Never, - /** The file will be kept only if it was already present before the transformation. */ - Automatic -}; - /** * Allows copying relations from the input stream to the output stream. * Such relations are specified by a regular expression. @@ -96,17 +87,9 @@ public: - /** - * By default, this transformation runs in-memory. - * If 'file' is not empty, this transformation will operate on an .sqlite file. - * The file will be created if it does not exist. - */ - relpipe::writer::string_t file; - - /** - * see KeepFile documentation - */ - KeepFile keepFile = KeepFile::Automatic; + relpipe::writer::string_t dataSourceName; + + relpipe::writer::string_t dataSourceURL; std::vector statements; diff -r 91cb012d779a -r 3de41719d7eb src/Connection.cpp --- a/src/Connection.cpp Sun May 31 16:56:07 2020 +0200 +++ b/src/Connection.cpp Sun May 31 21:20:24 2020 +0200 @@ -34,7 +34,7 @@ Connection::~Connection() { SQLRETURN result = SQLDisconnect(connection); - // FIXME: nevyhazovat výjimky z destruktorů + // FIXME: do not throw exception from destructor if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to disconnect: " + std::to_wstring(result)); OdbcCommon::freeHandle(SQL_HANDLE_DBC, connection); } diff -r 91cb012d779a -r 3de41719d7eb src/DriverManager.cpp --- a/src/DriverManager.cpp Sun May 31 16:56:07 2020 +0200 +++ b/src/DriverManager.cpp Sun May 31 21:20:24 2020 +0200 @@ -30,13 +30,13 @@ 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", result, SQL_HANDLE_ENV, env); + environment = OdbcCommon::allocateHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE); + SQLRETURN result = SQLSetEnvAttr(environment, SQL_ATTR_ODBC_VERSION, (void*) SQL_OV_ODBC3, 0); + if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to set ODBC version", result, SQL_HANDLE_ENV, environment); } DriverManager::~DriverManager() { - OdbcCommon::freeHandle(SQL_HANDLE_ENV, env); + OdbcCommon::freeHandle(SQL_HANDLE_ENV, environment); } std::vector DriverManager::getDataSources() { @@ -48,33 +48,33 @@ SQLSMALLINT nameLength; SQLSMALLINT descriptionLength; while (true) { - SQLRETURN result = SQLDataSources(env, SQL_FETCH_NEXT, name, sizeof (name), &nameLength, description, sizeof (description), &descriptionLength); + SQLRETURN result = SQLDataSources(environment, 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), result, SQL_HANDLE_ENV, env); + else throw SqlException(L"Unable to list data sources: " + std::to_wstring(result), result, SQL_HANDLE_ENV, environment); } return list; } Connection* DriverManager::getConnectionByDSN(relpipe::reader::string_t dataSourceName, relpipe::reader::string_t userName, relpipe::reader::string_t password) { - SQLHDBC connection = OdbcCommon::allocateHandle(SQL_HANDLE_DBC, env); + SQLHDBC connection = OdbcCommon::allocateHandle(SQL_HANDLE_DBC, environment); std::string dataSourceNameBytes = convertor.to_bytes(dataSourceName); std::string userNameBytes = convertor.to_bytes(userName); std::string passwordBytes = convertor.to_bytes(password); SQLRETURN result = SQLConnect(connection, (SQLCHAR*) dataSourceNameBytes.c_str(), SQL_NTS, (SQLCHAR*) userNameBytes.c_str(), SQL_NTS, - (SQLCHAR*) password.c_str(), SQL_NTS); + (SQLCHAR*) passwordBytes.c_str(), SQL_NTS); if (OdbcCommon::isNotSuccessful(result)) { OdbcCommon::freeHandle(SQL_HANDLE_DBC, connection); - throw SqlException(L"Unable to connect to " + dataSourceName, result, SQL_HANDLE_ENV, env); + throw SqlException(L"Unable to connect to " + dataSourceName, result, SQL_HANDLE_ENV, environment); } return new Connection(connection); } -Connection* DriverManager::getConnectionByString(relpipe::reader::string_t connectionString) { - SQLHDBC connection = OdbcCommon::allocateHandle(SQL_HANDLE_DBC, env); +Connection* DriverManager::getConnectionByURL(relpipe::reader::string_t connectionString) { + SQLHDBC connection = OdbcCommon::allocateHandle(SQL_HANDLE_DBC, environment); char completeConnectionString[SQL_MAX_OPTION_STRING_LENGTH]; memset(completeConnectionString, 0, sizeof (completeConnectionString)); SQLSMALLINT completeConnectionStringLength = -1; @@ -82,16 +82,10 @@ (SQLCHAR*) convertor.to_bytes(connectionString).c_str(), SQL_NTS, (SQLCHAR*) completeConnectionString, sizeof (completeConnectionString), &completeConnectionStringLength, SQL_DRIVER_NOPROMPT); - if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to connect to " + connectionString, result, SQL_HANDLE_ENV, env); - std::wcerr << "ODBC connected to: " << convertor.from_bytes(completeConnectionString) << std::endl; // FIXME: remove + if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to connect to " + connectionString, result, SQL_HANDLE_ENV, environment); return new Connection(connection); } -relpipe::reader::string_t DriverManager::buildDSN(const std::map& parameters) { - throw SqlException(L"not yet implemented: buildDSN()"); // FIXME: implement buildDSN() -} - - } } } diff -r 91cb012d779a -r 3de41719d7eb src/DriverManager.h --- a/src/DriverManager.h Sun May 31 16:56:07 2020 +0200 +++ b/src/DriverManager.h Sun May 31 21:20:24 2020 +0200 @@ -39,7 +39,7 @@ */ class DriverManager { private: - void* env; + void* environment; std::wstring_convert> convertor; // TODO: support also other encodings public: @@ -54,8 +54,7 @@ virtual ~DriverManager(); std::vector getDataSources(); Connection* getConnectionByDSN(relpipe::reader::string_t dataSourceName, relpipe::reader::string_t userName = L"", relpipe::reader::string_t password = L""); - Connection* getConnectionByString(relpipe::reader::string_t connectionString); - relpipe::reader::string_t buildDSN(const std::map& parameters); + Connection* getConnectionByURL(relpipe::reader::string_t connectionString); }; } diff -r 91cb012d779a -r 3de41719d7eb src/SqlHandler.h --- a/src/SqlHandler.h Sun May 31 16:56:07 2020 +0200 +++ b/src/SqlHandler.h Sun May 31 21:20:24 2020 +0200 @@ -160,22 +160,18 @@ output << L'"'; } + Connection* getConnection() { + if (configuration.dataSourceName.size()) return driverManager->getConnectionByDSN(configuration.dataSourceName); + else if (configuration.dataSourceURL.size()) return driverManager->getConnectionByURL(configuration.dataSourceURL); + else return driverManager->getConnectionByURL(L"Driver=SQLite3;Database=:memory:"); + // SQLite is default/fallback oprion + // TODO: use environmental variable to allow setting a different default + } + public: 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); - - // in C++17 we can use: std::filesystem::exists() - struct stat fileStat; - fileAlreadyExisted = (stat(file.c_str(), &fileStat) == 0); - } else { - file = ":memory:"; - } - - connection.reset(driverManager->getConnectionByDSN(L"sqlite-memory")); // FIXME: custom DSN and files - connection.reset(driverManager->getConnectionByDSN(L"relpipe")); // FIXME: custom DSN and files + connection.reset(getConnection()); //connection->setAutoCommit(false); } @@ -266,15 +262,6 @@ for (const CopyRelations& copy : configuration.copyRelations) copyRelations(copy); connection->commit(); - - // delete or keep the file: - if (configuration.file.size()) { - if (configuration.keepFile == KeepFile::Never || (configuration.keepFile == KeepFile::Automatic && !fileAlreadyExisted)) { - std::wcerr << L"will unlink file" << std::endl; - int result = unlink(convertor.to_bytes(configuration.file).c_str()); - if (result) throw SqlException(L"Unable to delete SQLite file."); - } - } // else: we had no file, everything was in memory } static void listDataSources(writer::RelationalWriter* relationalWriter, DriverManager* driverManager) { diff -r 91cb012d779a -r 3de41719d7eb src/relpipe-tr-sql.cpp --- a/src/relpipe-tr-sql.cpp Sun May 31 16:56:07 2020 +0200 +++ b/src/relpipe-tr-sql.cpp Sun May 31 21:20:24 2020 +0200 @@ -61,7 +61,7 @@ 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}); + if (configuration.statements.size() == 0 && configuration.copyRelations.size() == 0) configuration.copyRelations.push_back({L".*", L"", false}); configuration.sqlBeforeRelational = isatty(fileno(stdin)) ? nullptr : &std::wcin; configuration.sqlAfterRelational = nullptr; SqlHandler handler(writer.get(), driverManager.get(), configuration);