add options --data-source-name and --data-source-url for custom datasource; drop options --file and --file-keep
--- 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 <http://www.gnu.org/licenses/>.
+_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
--- 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<string_t>& 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";
}
}
--- 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<Parameter> 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<Statement> statements;
--- 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);
}
--- 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::DataSource> 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<relpipe::reader::string_t, relpipe::reader::string_t>& parameters) {
- throw SqlException(L"not yet implemented: buildDSN()"); // FIXME: implement buildDSN()
-}
-
-
}
}
}
--- 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<std::codecvt_utf8<wchar_t>> convertor; // TODO: support also other encodings
public:
@@ -54,8 +54,7 @@
virtual ~DriverManager();
std::vector<DataSource> 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<relpipe::reader::string_t, relpipe::reader::string_t>& parameters);
+ Connection* getConnectionByURL(relpipe::reader::string_t connectionString);
};
}
--- 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) {
--- 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);