src/ResultSet.cpp
author František Kučera <franta-hg@frantovo.cz>
Mon, 26 Oct 2020 00:00:35 +0100
branchv_0
changeset 53 cc6ffeba0fe5
parent 48 c83119110c7b
child 58 a4907b207f0c
permissions -rw-r--r--
fix: support bigger numbers, SQL_BIGINT there was an overflow and some values resulted into negative ones. TODO: there is still a problem with big negative numbers – this needs to be addressed in relpipe-lib-writer/relpipe-lib-reader

/**
 * 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 <http://www.gnu.org/licenses/>.
 */

#include <sstream>
#include <algorithm>

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

#include "ResultSet.h"
#include "OdbcCommon.h"

namespace relpipe {
namespace tr {
namespace sql {

ResultSet::ResultSet(void* statement) : statement(statement) {

}

ResultSet::~ResultSet() {
	// freeHandle() is called in ~PreparedStatement()
}

bool ResultSet::next() {
	SQLRETURN result = SQLFetch(statement);
	if (OdbcCommon::isSuccessful(result)) return true;
	else if (result == SQL_NO_DATA) return false;
	else throw SqlException(L"Unable to fetch next record", result, SQL_HANDLE_STMT, statement);
}

relpipe::writer::boolean_t ResultSet::getBoolean(unsigned short columnNumber, bool* isNull) {
	relpipe::writer::string_t s = getString(columnNumber, isNull);
	if (isNull && *isNull) return false;
	else if (s == L"1" || s == L"true" || s == L"y" || s == L"yes" || s == L"TRUE" || s == L"Y" || s == L"YES") return true;
	else if (s == L"0" || s == L"false" || s == L"n" || s == L"no" || s == L"FALSE" || s == L"N" || s == L"NO") return false;
	else throw SqlException(L"Unable to parse „" + s + L"“ as boolean");
}

relpipe::writer::integer_t ResultSet::getInteger(unsigned short columnNumber, bool* isNull) {
	// TODO: get integer directly from SQLGetData() without string conversion
	relpipe::writer::string_t s = getString(columnNumber, isNull);
	if (isNull && *isNull) return 0;
	else return std::stoll(s);
}

relpipe::writer::string_t ResultSet::getString(unsigned short columnNumber, bool* isNull) {
	SQLCHAR uselessBuffer; // just to get stringLength – ODBC does not eat nullptr
	SQLLEN stringLength = -1;
	SQLRETURN result = SQLGetData(statement, columnNumber, SQL_C_CHAR, &uselessBuffer, 0, &stringLength);
	if (isNull) *isNull = stringLength == SQL_NULL_DATA;
	if (stringLength == SQL_NULL_DATA) {
		return L"";
	} else if (stringLength >= 0) {
		std::string value;
		value.reserve(stringLength);
		result = SQLGetData(statement, columnNumber, SQL_C_CHAR, (SQLCHAR*) value.c_str(), value.capacity() + 1, &stringLength); // trailing null byte = + 1
		if (OdbcCommon::isSuccessful(result)) return convertor.from_bytes(value.c_str());
	}
	throw SqlException(L"Unable to get string value", result, SQL_HANDLE_STMT, statement);
}

ResultSet::MetaData* ResultSet::getMetaData() {
	SQLRETURN result;
	SQLSMALLINT columnCount;
	std::vector<ResultSet::MetaData::ColumnDescriptor> columnDescriptors;

	result = SQLNumResultCols(statement, &columnCount);
	if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable to get column count", result, SQL_HANDLE_STMT, statement);

	for (SQLSMALLINT columnNumber = 1; columnNumber <= columnCount; columnNumber++) {
		ResultSet::MetaData::ColumnDescriptor columnDescriptor;
		std::string nameBuffer; // TODO: use rather vector<char> instead of string.c_str()?
		nameBuffer.reserve(100); // TODO: max column name length?
		SQLSMALLINT nameLength;
		SQLSMALLINT dataType;
		SQLULEN columnSize;
		SQLSMALLINT decimalDigits;
		SQLSMALLINT nullable;

		result = SQLDescribeCol(statement, columnNumber, (SQLCHAR*) nameBuffer.c_str(), nameBuffer.capacity(), &nameLength, &dataType, &columnSize, &decimalDigits, &nullable);
		if (OdbcCommon::isNotSuccessful(result)) throw SqlException(L"Unable describe column", result, SQL_HANDLE_STMT, statement);

		columnDescriptor.name = convertor.from_bytes(nameBuffer.c_str());

		if (dataType == SQL_INTEGER || dataType == SQL_BIGINT) columnDescriptor.type = relpipe::writer::TypeId::INTEGER;
		else if (dataType == SQL_BIT) columnDescriptor.type = relpipe::writer::TypeId::BOOLEAN;

		columnDescriptors.emplace_back(columnDescriptor);
	}

	return new MetaData(columnCount, columnDescriptors);
}

ResultSet::MetaData::MetaData(unsigned short columnCount, std::vector<ColumnDescriptor> columnDescriptors) : columnCount(columnCount), columnDescriptors(columnDescriptors) {
}

ResultSet::MetaData::~MetaData() {
}

unsigned short ResultSet::MetaData::getColumnCount() {
	return columnCount;
}

ResultSet::MetaData::ColumnDescriptor ResultSet::MetaData::describeColumn(unsigned short columnNumber) {
	if (columnNumber >= 1 && columnNumber <= columnCount) return columnDescriptors[columnNumber - 1];
	else throw SqlException(L"Unable to describe column " + std::to_wstring(columnNumber) + L", out of bounds, column count is " + std::to_wstring(columnCount));
}

SQLUSMALLINT ResultSet::MetaData::getColumnNumber(relpipe::writer::string_t columnName) {
	// TODO: also case insensitive mode
	for (SQLUSMALLINT i = 0; i < columnDescriptors.size(); i++) {
		if (columnDescriptors[i].name == columnName) return i + 1;
	}

	std::wstringstream errorMessage;
	errorMessage << L"Unable to find column with name „" << columnName.c_str() << L"“ Availalable columns are: ";
	for (MetaData::ColumnDescriptor cd : columnDescriptors) errorMessage << L"„" << cd.name.c_str() << L"“ ";
	throw SqlException(errorMessage.str());
}

}
}
}