add options --data-source-name and --data-source-url for custom datasource; drop options --file and --file-keep v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sun, 31 May 2020 21:20:24 +0200
branchv_0
changeset 37 3de41719d7eb
parent 36 91cb012d779a
child 38 a871779a4e3c
add options --data-source-name and --data-source-url for custom datasource; drop options --file and --file-keep
bash-completion.sh
src/CLIParser.h
src/Configuration.h
src/Connection.cpp
src/DriverManager.cpp
src/DriverManager.h
src/SqlHandler.h
src/relpipe-tr-sql.cpp
--- 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);