optional UNION ALL: CLI option '--on-duplicate-relation' with values 'fail' and 'insert' (later more modes)
examples, shortcuts:
relpipe-tr-unionall() { relpipe-tr-sql --copy '.+' --on-duplicate-relation insert; } # does UNION ALL for all tables with same name (including already existing ones – when non-empty database is used)
relpipe-tr-unionall() { relpipe-tr-serialize | relpipe-tr-deserialize; } # does UNION ALL only for tables with same name immediately following each other (interleaved duplicates will stay unaffected)
/**
* 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.resize(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);
}
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 << L"“ Availalable columns are: ";
for (MetaData::ColumnDescriptor cd : columnDescriptors) errorMessage << L"„" << cd.name << L"“ ";
throw SqlException(errorMessage.str());
}
}
}
}