src/Connection.cpp
author František Kučera <franta-hg@frantovo.cz>
Thu, 04 Jun 2020 00:46:00 +0200
branchv_0
changeset 47 428c278af4be
parent 46 85e6dc1853ee
permissions -rw-r--r--
rename option --data-source-url to --data-source-string In some implementations like JDBC, the connection string is URL, but in ODBC the string is not formally URL, so it is better to use more general term „data source string“ instead of URL. - data source name (DSN) = name of a pre-configured database connection that should be looked-up in configuration and used - data source string (connection string) = arbitrary string containing (in certain encoding which might and might not be URL) all needed parameters (e.g. server name + port + user name + password) Name and string might sometimes be also combined: in ODBC we can e.g. connect to a string: DSN=relpipe;someParameter=foo;someOther=bar which will lookup configuration for the „relpipe“ data source and will combine it with given parameters.

/**
 * Relational pipes
 * Copyright © 2019 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 <http://www.gnu.org/licenses/>.
 */

#include <memory>
#include <iostream>

#include <sql.h>
#include <sqlext.h>

#include "Connection.h"
#include "OdbcCommon.h"

namespace relpipe {
namespace tr {
namespace sql {

Connection::Connection(void* db) : connection(db) {
	setAutoCommit(false);
}

Connection::~Connection() {
	// If an exception was thrown somewhere, there might be a transaction in progress, so we will cancel this transaction.
	// If there was no exception, commit() has already been called and this rollback() will not affect any (desired) transaction.
	// Without this rollback(), SQLDisconnect() will fail and then will fail also freeHandle() which will throw exception.
	// And throwing exception while throwing exception will lead to a catastrophic failure: SIGABRT, core dump.
	rollback();
	SQLRETURN result = SQLDisconnect(connection);
	OdbcCommon::freeHandle(SQL_HANDLE_DBC, connection);
}

PreparedStatement* Connection::prepareStatement(relpipe::reader::string_t sql) {
	SQLHSTMT statement = OdbcCommon::allocateHandle(SQL_HANDLE_STMT, connection);
	SQLRETURN result = SQLPrepare(statement, (SQLCHAR*) convertor.to_bytes(sql).c_str(), SQL_NTS);
	if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to prepare statement", result, SQL_HANDLE_STMT, statement, true);
	return new PreparedStatement(statement);
}

bool Connection::getAutoCommit() {
	SQLULEN autoCommit = SQL_AUTOCOMMIT_DEFAULT;
	SQLRETURN result = SQLGetConnectOption(connection, SQL_AUTOCOMMIT, &autoCommit);
	if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to get auto-commit status", result, SQL_HANDLE_DBC, connection);

	if (autoCommit == SQL_AUTOCOMMIT_ON) return true;
	else if (autoCommit == SQL_AUTOCOMMIT_OFF) return false;
	else throw SqlException(L"Unexpected value of auto-commit status");
}

void Connection::setAutoCommit(bool autoCommit) {
	SQLRETURN result = SQLSetConnectOption(connection, SQL_AUTOCOMMIT, autoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF);
	if (OdbcCommon::isNotSuccessful(result)) throw SqlException(std::wstring(L"Unable to set auto-commit to ") + (autoCommit ? L"enabled" : L"disabled"), result, SQL_HANDLE_DBC, connection);
}

void Connection::commit() {
	SQLRETURN result = SQLEndTran(SQL_HANDLE_DBC, connection, SQL_COMMIT);
	if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to COMMIT: " + std::to_wstring(result));
}

void Connection::rollback() {
	SQLRETURN result = SQLEndTran(SQL_HANDLE_DBC, connection, SQL_ROLLBACK);
	if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to ROLLBACK: " + std::to_wstring(result));
}

std::vector<Connection::TablePrivilege> Connection::getTablePrivileges() {
	std::vector<TablePrivilege> tables;
	SQLHSTMT statementHandle = OdbcCommon::allocateHandle(SQL_HANDLE_STMT, connection);
	SQLRETURN result = SQLTablePrivileges(statementHandle, nullptr, 0, nullptr, 0, nullptr, 0);
	if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable get tables (prepare)", result, SQL_HANDLE_STMT, statementHandle, true);

	ResultSet resultSet(statementHandle);
	std::shared_ptr<ResultSet::MetaData> metaData(resultSet.getMetaData());
	SQLUSMALLINT catalogColumn = metaData->getColumnNumber(L"TABLE_CAT");
	SQLUSMALLINT schemaColumn = metaData->getColumnNumber(L"TABLE_SCHEM");
	SQLUSMALLINT nameColumn = metaData->getColumnNumber(L"TABLE_NAME");
	SQLUSMALLINT grantorColumn = metaData->getColumnNumber(L"GRANTOR");
	SQLUSMALLINT granteeColumn = metaData->getColumnNumber(L"GRANTEE");
	SQLUSMALLINT privilegeColumn = metaData->getColumnNumber(L"PRIVILEGE");
	SQLUSMALLINT isGrantableColumn = metaData->getColumnNumber(L"IS_GRANTABLE");

	while (resultSet.next()) {
		TablePrivilege tp;
		tp.catalog = resultSet.getString(catalogColumn);
		tp.schema = resultSet.getString(schemaColumn);
		tp.name = resultSet.getString(nameColumn);
		tp.grantor = resultSet.getString(grantorColumn);
		tp.grantee = resultSet.getString(granteeColumn);
		tp.privilege = resultSet.getString(privilegeColumn);
		tp.isGrantable = resultSet.getString(isGrantableColumn) == L"YES";
		tables.emplace_back(tp);
	}

	return tables;
}

relpipe::reader::string_t Connection::getUserName() {
	std::vector<char> buffer(100);
	SQLSMALLINT stringLength;
	SQLRETURN result = SQLGetInfo(connection, SQL_USER_NAME, buffer.data(), buffer.size(), &stringLength);
	if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to get user name: " + std::to_wstring(result));
	if (stringLength >= buffer.size()) throw SqlException(L"Unable to get user name: too long" + std::to_wstring(stringLength));
	return convertor.from_bytes(buffer.data(), buffer.data() + stringLength);
}


}
}
}