fbsqlxx/include/fbsqlxx.hpp

1622 lines
41 KiB
C++

#pragma once
#include <cassert>
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#ifdef FBSQLXX_DEBUG
#include <iostream>
#endif // FBSQLXX_DEBUG
#ifndef FBSQLXX_EXCEPTION_BUFFER_SIZE
#define FBSQLXX_EXCEPTION_BUFFER_SIZE 1024
#endif // !FBSQLXX_EXCEPTION_BUFFER_SIZE
namespace fbsqlxx2 {
#ifdef FBSQLXX_DEBUG
template <typename ...Args>
inline void debug(Args&& ...args)
{
(std::cout << ... << std::forward<Args>(args)) << std::endl;
}
inline void debug(const char* s)
{
std::cout << s << std::endl;
}
std::string type_name(unsigned int type);
inline void print_metadata(Firebird::IMessageMetadata* md, Firebird::ThrowStatusWrapper* status)
{
auto count = md->getCount(status);
std::cout << "count:" << count
<< " mlen:" << md->getMessageLength(status)
<< " alen:" << md->getAlignedLength(status)
<< " align:" << md->getAlignment(status)
<< std::endl;
for (unsigned i = 0; i < count; i++)
{
std::cout << "#" << i << ": " << md->getAlias(status, i)
<< " type:" << type_name(md->getType(status, i) & ~1u)
<< " subtype:" << md->getSubType(status, i)
<< " scale:" << md->getScale(status, i)
<< " len:" << md->getLength(status, i)
<< " off:" << md->getOffset(status, i)
<< " noff:" << md->getNullOffset(status, i)
<< " rel:" << md->getRelation(status, i)
<< " own:" << md->getOwner(status, i)
<< " nullable:" << (md->isNullable(status, i) ? "T" : "F")
<< std::endl;
}
}
#else
#define debug(...)
#define print_metadata(...)
#endif // FBSQLXX_DEBUG
// static functions
inline Firebird::IMaster* master()
{
static Firebird::IMaster* _master = Firebird::fb_get_master_interface();
return _master;
}
inline Firebird::IUtil* util()
{
static Firebird::IUtil* _util = master()->getUtilInterface();
return _util;
}
inline int64_t portable_integer(const uint8_t* p, short length)
{
return isc_portable_integer(p, length);
}
inline std::string type_name(unsigned int type)
{
switch (type & ~1u)
{
case SQL_ARRAY:
return "ARRAY";
case SQL_BLOB:
return "BLOB";
case SQL_BOOLEAN:
return "BOOLEAN";
case SQL_DEC16:
return "DEC16";
case SQL_DEC34:
return "DEC34";
case SQL_DOUBLE:
return "DOUBLE";
case SQL_D_FLOAT:
return "D_FLOAT";
case SQL_FLOAT:
return "FLOAT";
case SQL_INT128:
return "INT128";
case SQL_INT64:
return "BIGINT";
case SQL_LONG:
return "INT";
case SQL_SHORT:
return "SMALLINT";
case SQL_TEXT:
return "CHAR";
case SQL_TIMESTAMP:
return "TIMESTAMP";
case SQL_TIMESTAMP_TZ:
return "TIMESTAMP_TZ";
case SQL_TIMESTAMP_TZ_EX:
return "TIMESTAMP_TZ_EX";
case SQL_TIME_TZ:
return "TIME_TZ";
case SQL_TIME_TZ_EX:
return "TIME_TZ_EX";
case SQL_TYPE_DATE:
return "DATE";
case SQL_TYPE_TIME:
return "TIME";
case SQL_VARYING:
return "VARCHAR";
}
return "UNKNOWN";
}
// types
class connection;
class transaction;
class statement;
class result_set;
using octets = std::vector<uint8_t>;
struct date
{
unsigned year;
unsigned month;
unsigned day;
};
struct time
{
unsigned hours;
unsigned minutes;
unsigned seconds;
unsigned fractions;
};
struct time_tz
{
time utc_time;
ISC_USHORT time_zone;
};
struct time_tz_ex
{
time utc_time;
ISC_USHORT time_zone;
short ext_offset;
};
struct timestamp
{
date date;
time time;
};
struct timestamp_tz
{
timestamp utc_timestamp;
ISC_USHORT time_zone;
};
struct timestamp_tz_ex
{
timestamp utc_timestamp;
ISC_USHORT time_zone;
short ext_offset;
};
// exceptions
class error : public std::runtime_error
{
public:
error(const char* msg)
: std::runtime_error(msg)
{
}
};
class sql_error : public error
{
public:
sql_error(const char* msg, const Firebird::FbException* cause)
: error(msg), m_cause{ cause }
{
}
const Firebird::FbException* cause() const { return m_cause; }
private:
const Firebird::FbException* m_cause;
};
class logic_error : public error
{
public:
logic_error(const char* msg)
: error(msg)
{
}
};
#define CATCH_SQL \
catch (const Firebird::FbException& ex) { \
char buf[FBSQLXX_EXCEPTION_BUFFER_SIZE]; \
util()->formatStatus(buf, sizeof(buf), ex.getStatus()); \
throw sql_error{ buf, &ex }; }
namespace cvt {
inline date to_date(ISC_DATE isc_date)
{
date d{};
util()->decodeDate(isc_date, &d.year, &d.month, &d.day);
return d;
}
inline time to_time(ISC_TIME isc_time)
{
time t{};
util()->decodeTime(isc_time, &t.hours, &t.minutes, &t.seconds, &t.fractions);
return t;
}
inline time_tz to_time_tz(ISC_TIME_TZ isc_time)
{
time t{};
util()->decodeTime(isc_time.utc_time, &t.hours, &t.minutes, &t.seconds, &t.fractions);
return time_tz{ t, isc_time.time_zone };
}
inline timestamp to_timestamp(ISC_TIMESTAMP isc_ts)
{
date d{};
time t{};
util()->decodeDate(isc_ts.timestamp_date, &d.year, &d.month, &d.day);
util()->decodeTime(isc_ts.timestamp_time, &t.hours, &t.minutes, &t.seconds, &t.fractions);
return timestamp{ d, t };
}
inline timestamp_tz to_timestamp(ISC_TIMESTAMP_TZ isc_ts)
{
date d{};
time t{};
util()->decodeDate(isc_ts.utc_timestamp.timestamp_date, &d.year, &d.month, &d.day);
util()->decodeTime(isc_ts.utc_timestamp.timestamp_time, &t.hours, &t.minutes, &t.seconds, &t.fractions);
return timestamp_tz{ {d, t}, isc_ts.time_zone };
}
} // namespace cvt
namespace _details {
class non_copyable
{
protected:
non_copyable() = default;
~non_copyable() = default;
public:
non_copyable(const non_copyable&) = delete;
non_copyable& operator=(const non_copyable&) = delete;
};
template <
typename T,
std::enable_if_t<std::is_member_function_pointer_v<decltype(&T::free)>, T>* = nullptr>
inline void destroy(T* _value)
{
_value->free();
}
template <
typename T,
std::enable_if_t<std::is_member_function_pointer_v<decltype(&T::dispose)>, T>* = nullptr>
inline void destroy(T* _value)
{
_value->dispose();
}
template <
typename T,
std::enable_if_t<std::is_member_function_pointer_v<decltype(&T::release)>, T>* = nullptr>
inline void destroy(T* _value)
{
_value->release();
}
template <typename T>
class autodestroy final : private non_copyable
{
public:
autodestroy(T* value) : _value{ value }
{
}
~autodestroy()
{
if (_value) destroy(_value);
}
T* operator->() const { return _value; }
T* operator&() const { return _value; }
private:
T* _value{};
};
template <typename T>
inline autodestroy<T> make_autodestroy(T* value)
{
return autodestroy{ value };
}
struct parameter
{
int type;
int subtype;
int scale;
std::string str_value;
std::vector<uint8_t> octets_value;
union
{
int8_t bool_value;
int16_t int16_value;
int32_t int32_value;
int64_t int64_value;
float float_value;
double double_value;
ISC_QUAD quad_value;
ISC_DATE date_value;
ISC_TIME time_value;
ISC_TIME_TZ time_tz_value;
ISC_TIME_TZ_EX time_tz_ex_value;
ISC_TIMESTAMP timestamp_value;
ISC_TIMESTAMP_TZ timestamp_tz_value;
ISC_TIMESTAMP_TZ_EX timestamp_tz_ex_value;
FB_DEC16 dec16_value;
FB_DEC34 dec34_value;
FB_I128 i128_value;
};
};
class input_parameters final
{
private:
static constexpr unsigned MY_SQL_OCTETS = 10000001;
public:
input_parameters(Firebird::ThrowStatusWrapper* status, unsigned int field_count)
: m_status{ status }
{
m_parameters.resize(field_count);
}
// buffer - new empty vector
// caller owns IMessageMetadata instance
Firebird::IMessageMetadata* fill_buffer(std::vector<uint8_t>& buffer)
{
auto md = build_metadata();
buffer.resize(md->getMessageLength(m_status));
const auto count = static_cast<unsigned>(m_parameters.size());
for (unsigned i = 0; i < count; ++i)
{
auto const& param = m_parameters[i];
if (!param.type) // null
{
int16_t* p_null = (int16_t*)&buffer[md->getNullOffset(m_status, i)];
*p_null = -1;
continue;
}
uint8_t* offset = &buffer[md->getOffset(m_status, i)];
//auto len = md->getLength(m_status, i);
switch (param.type)
{
case SQL_INT64:
cast<int64_t>(offset) = param.int64_value;
break;
case SQL_LONG:
cast<int32_t>(offset) = param.int32_value;
break;
case SQL_SHORT:
cast<int16_t>(offset) = param.int16_value;
break;
case SQL_DOUBLE:
cast<double>(offset) = param.double_value;
break;
case SQL_FLOAT:
cast<float>(offset) = param.float_value;
break;
case SQL_TYPE_DATE:
cast<ISC_DATE>(offset) = param.date_value;
break;
case SQL_TYPE_TIME:
cast<ISC_TIME>(offset) = param.time_value;
break;
case SQL_TIME_TZ:
cast<ISC_TIME_TZ>(offset) = param.time_tz_value;
break;
case SQL_TIMESTAMP:
cast<ISC_TIMESTAMP>(offset) = param.timestamp_value;
break;
case SQL_TIMESTAMP_TZ:
cast<ISC_TIMESTAMP_TZ>(offset) = param.timestamp_tz_value;
break;
case SQL_TEXT:
memcpy(((void*)offset), param.str_value.data(), param.str_value.size());
break;
case SQL_VARYING:
cast<int16_t>(offset) = static_cast<int16_t>(param.str_value.size());
memcpy(offset + 2, param.str_value.data(), param.str_value.size());
break;
case MY_SQL_OCTETS:
memcpy(((void*)offset), param.octets_value.data(), param.octets_value.size());
break;
default:
{
std::string msg = "Not implemented parameter type: ";
msg += type_name(param.type);
throw logic_error(msg.data());
}
} // switch (param.type)
}
print_metadata(md, m_status);
return md;
}
void setNull(unsigned int i)
{
m_parameters.at(i) = {};
}
void set(unsigned int i, int16_t value, int scale)
{
parameter p{};
p.type = SQL_SHORT;
p.scale = -scale;
p.int16_value = value;
m_parameters.at(i) = p;
}
void set(unsigned int i, int32_t value, int scale)
{
parameter p{};
p.type = SQL_LONG;
p.scale = -scale;
p.int32_value = value;
m_parameters.at(i) = p;
}
void set(unsigned int i, int64_t value, int scale)
{
parameter p{};
p.type = SQL_INT64;
p.scale = -scale;
p.int64_value = value;
m_parameters.at(i) = p;
}
void set(unsigned int i, double value)
{
parameter p{};
p.type = SQL_DOUBLE;
p.double_value = value;
m_parameters.at(i) = p;
}
void set(unsigned int i, date value)
{
parameter p{};
p.type = SQL_TYPE_DATE;
p.date_value = util()->encodeDate(value.year, value.month, value.day);
m_parameters.at(i) = p;
}
void set(unsigned int i, time value)
{
parameter p{};
p.type = SQL_TYPE_TIME;
p.time_value = util()->encodeTime(value.hours, value.minutes, value.seconds, value.fractions);
m_parameters.at(i) = p;
}
void set(unsigned int i, time_tz value)
{
parameter p{};
p.type = SQL_TIME_TZ;
p.time_tz_value.utc_time = util()->encodeTime(value.utc_time.hours, value.utc_time.minutes, value.utc_time.seconds, value.utc_time.fractions);
p.time_tz_value.time_zone = value.time_zone;
m_parameters.at(i) = p;
}
void set(unsigned int i, timestamp value)
{
parameter p{};
p.type = SQL_TIMESTAMP;
p.timestamp_value.timestamp_date = util()->encodeDate(value.date.year, value.date.month, value.date.day);
p.timestamp_value.timestamp_time = util()->encodeTime(value.time.hours, value.time.minutes, value.time.seconds, value.time.fractions);
m_parameters.at(i) = p;
}
void set(unsigned int i, timestamp_tz value)
{
parameter p{};
p.type = SQL_TIMESTAMP_TZ;
p.timestamp_tz_value.utc_timestamp.timestamp_date = util()->encodeDate(value.utc_timestamp.date.year, value.utc_timestamp.date.month, value.utc_timestamp.date.day);
p.timestamp_tz_value.utc_timestamp.timestamp_time = util()->encodeTime(value.utc_timestamp.time.hours, value.utc_timestamp.time.minutes, value.utc_timestamp.time.seconds, value.utc_timestamp.time.fractions);
p.timestamp_tz_value.time_zone = value.time_zone;
m_parameters.at(i) = p;
}
void set(unsigned int i, const char* value)
{
parameter p{};
p.type = SQL_TEXT;
p.str_value = value;
m_parameters.at(i) = std::move(p);
}
void set(unsigned int i, const std::vector<uint8_t>& value)
{
parameter p{};
p.type = MY_SQL_OCTETS;
p.octets_value = value;
m_parameters.at(i) = std::move(p);
}
private:
Firebird::IMessageMetadata* build_metadata()
{
const auto count = static_cast<unsigned>(m_parameters.size());
auto builder = make_autodestroy(master()->getMetadataBuilder(m_status, count));
for (unsigned i = 0; i < count; ++i)
{
auto const& param = m_parameters[i];
if (param.type) // not null
{
switch (param.type)
{
case SQL_TEXT:
{
builder->setType(m_status, i, SQL_TEXT + 1);
auto length = static_cast<unsigned>(param.str_value.size());
builder->setLength(m_status, i, length);
}
break;
case MY_SQL_OCTETS:
{
builder->setType(m_status, i, SQL_TEXT + 1);
auto length = static_cast<unsigned>(param.str_value.size());
builder->setLength(m_status, i, length);
}
break;
default:
{
builder->setType(m_status, i, param.type + 1);
builder->setSubType(m_status, i, param.subtype);
if (param.scale)
builder->setScale(m_status, i, param.scale);
}
break;
} // switch (param.type)
}
else
builder->setType(m_status, i, SQL_BOOLEAN + 1); // null, type not important?
}
return builder->getMetadata(m_status);
}
template <typename T>
T& cast(void* offset) const
{
return *((T*)offset);
}
private:
Firebird::ThrowStatusWrapper* m_status;
std::vector<parameter> m_parameters;
};
} // namespace fbsqlxx2::_details
/////////////////////////////////////////////////////////////////////////
/// <summary>
/// Database result set (cursor) RAII wrapper class
/// </summary>
class result_set final : private _details::non_copyable
{
public:
result_set& operator=(result_set&&) = delete;
result_set(result_set&& rhs) noexcept
: m_rs{ rhs.m_rs }
, m_status{ rhs.m_status }
, m_meta{ rhs.m_meta }
, m_buffer{ std::move(rhs.m_buffer) }
, m_fields{ std::move(rhs.m_fields) }
, m_count{ rhs.m_count }
{
rhs.m_rs = nullptr;
}
~result_set()
{
if (m_rs) m_rs->release();
debug("result_set destroyed");
}
/// <summary>
///
/// </summary>
void close()
{
try
{
m_rs->close(m_status);
m_rs = nullptr;
debug("result_set closed");
}
CATCH_SQL
}
/// <summary>
///
/// </summary>
/// <returns></returns>
unsigned int field_count() const
{
return m_count;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool is_bof() const
{
try
{
return m_rs->isBof(m_status);
}
CATCH_SQL
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool is_eof() const
{
try
{
return m_rs->isEof(m_status);
}
CATCH_SQL
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool next()
{
try
{
return m_rs->fetchNext(m_status, m_buffer.data()) == Firebird::IStatus::RESULT_OK;
}
CATCH_SQL
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool prev()
{
try
{
return m_rs->fetchPrior(m_status, m_buffer.data()) == Firebird::IStatus::RESULT_OK;
}
CATCH_SQL
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool first()
{
try
{
return m_rs->fetchFirst(m_status, m_buffer.data()) == Firebird::IStatus::RESULT_OK;
}
CATCH_SQL
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool last()
{
try
{
return m_rs->fetchLast(m_status, m_buffer.data()) == Firebird::IStatus::RESULT_OK;
}
CATCH_SQL
}
/// <summary>
///
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
bool absolute(int position)
{
try
{
return m_rs->fetchAbsolute(m_status, position, m_buffer.data()) == Firebird::IStatus::RESULT_OK;
}
CATCH_SQL
}
/// <summary>
///
/// </summary>
/// <param name="offset"></param>
/// <returns></returns>
bool relative(int offset)
{
try
{
return m_rs->fetchRelative(m_status, offset, m_buffer.data()) == Firebird::IStatus::RESULT_OK;
}
CATCH_SQL
}
/// <summary>
/// Check column is null and has no value
/// </summary>
/// <param name="index">- Column index (1-based)</param>
/// <returns>true if column is null</returns>
bool is_null(unsigned index) const
{
assert(index >= 1);
auto const& f = m_fields.at(index - 1);
return 0 != cast<int16_t>(m_buffer.data() + f.null_offset);
}
/// <summary>
/// Get value of column by index
/// </summary>
/// <typeparam name="T">Requested column type</typeparam>
/// <param name="index">- Column index (1-based)</param>
/// <returns>Column value</returns>
template <typename T>
std::enable_if_t<!std::is_integral_v<T>, T> as(unsigned index) const
{
assert(index >= 1);
auto const& f = m_fields.at(index - 1);
check_field_null(f.name, f.null_offset);
check_field_size(f.name, f.length, sizeof(T));
return cast<T>(m_buffer.data() + f.offset);
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="index"></param>
/// <returns></returns>
template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> as(unsigned index) const
{
assert(index >= 1);
auto const& f = m_fields.at(index - 1);
check_field_null(f.name, f.null_offset);
check_field_size(f.name, f.length, sizeof(T));
return static_cast<T>(portable_integer(m_buffer.data() + f.offset, sizeof(T)));
}
Firebird::IResultSet* operator->() const
{
return m_rs;
}
private:
// buffer low level accessor
template <typename T>
T const& cast(const void* offset) const
{
return *((const T*)offset);
}
void check_field_null(const char* name, unsigned null_offset) const
{
if (0 != cast<int16_t>(m_buffer.data() + null_offset))
{
auto e = std::string("Field is null: [")
+ (name ? name : "")
+ "]";
throw logic_error(e.c_str());
}
}
void check_field_size(const char* name, size_t actual, size_t requested) const
{
if (requested > actual)
{
auto e = std::string("Field value size override: [")
+ (name ? name : "")
+ "], length=" + std::to_string(actual)
+ ", requested=" + std::to_string(requested);
throw logic_error(e.c_str());
}
}
struct field
{
const char* name;
unsigned type, subtype, length, offset, null_offset;
int scale;
};
void cache_metadata(unsigned count)
{
m_fields.resize(count);
for (unsigned i = 0; i < count; ++i)
{
auto& f = m_fields[i];
f.name = m_meta->getField(m_status, i);
f.type = m_meta->getType(m_status, i) & ~1u;
f.subtype = m_meta->getSubType(m_status, i);
f.length = m_meta->getLength(m_status, i);
f.offset = m_meta->getOffset(m_status, i);
f.null_offset = m_meta->getNullOffset(m_status, i);
f.scale = m_meta->getScale(m_status, i);
}
}
/// <summary>
/// Normal result set constructor
/// </summary>
/// <param name="rs"></param>
/// <param name="status"></param>
result_set(Firebird::IResultSet* rs, Firebird::ThrowStatusWrapper* status)
: m_rs{ rs }
, m_status{ status }
{
m_meta = rs->getMetadata(status);
auto length = m_meta->getMessageLength(status);
m_buffer.resize(length);
m_count = m_meta->getCount(status);
cache_metadata(m_count);
debug("result_set created");
}
/// <summary>
/// Special constructor, for pseudo result set
/// </summary>
/// <param name="meta"></param>
/// <param name="status"></param>
/// <param name="buffer"></param>
result_set(Firebird::IMessageMetadata* meta, Firebird::ThrowStatusWrapper* status, std::vector<uint8_t>&& buffer)
: m_rs{ nullptr }
, m_status{ status }
, m_meta{ meta }
, m_buffer{ std::move(buffer) }
{
m_count = m_meta->getCount(status);
cache_metadata(m_count);
debug("execute result_set created");
}
private:
friend class statement;
Firebird::IResultSet* m_rs;
Firebird::ThrowStatusWrapper* m_status;
Firebird::IMessageMetadata* m_meta;
std::vector<uint8_t> m_buffer;
std::vector<field> m_fields;
unsigned int m_count;
};
/// <summary>
///
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
template <>
inline std::string result_set::as<std::string>(unsigned i) const
{
assert(i >= 1);
auto const& f = m_fields.at(i - 1);
check_field_null(f.name, f.null_offset);
auto start = m_buffer.data() + f.offset;
return std::string(start, start + f.length);
}
/// <summary>
///
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
template <>
inline std::vector<uint8_t> result_set::as<std::vector<uint8_t>>(unsigned i) const
{
assert(i >= 1);
auto const& f = m_fields.at(i - 1);
check_field_null(f.name, f.null_offset);
auto start = m_buffer.data() + f.offset;
return std::vector<uint8_t>(start, start + f.length);
}
/// <summary>
/// Database prepared statement RAII wrapper class
/// </summary>
class statement final : private _details::non_copyable
{
public:
statement& operator=(statement&&) = delete;
statement(statement&& rhs) noexcept
: m_stmt{ rhs.m_stmt }
, m_tx{ rhs.m_tx }
, m_status{ rhs.m_status }
, m_input_parameters{ std::move(rhs.m_input_parameters) }
, m_input_count{ rhs.m_input_count }
{
rhs.m_stmt = nullptr;
}
~statement()
{
if (m_stmt) m_stmt->release();
debug("statement destroyed: ", this);
}
/// <summary>
/// Close statement and free resources. Do not call any methods on closed statement instance.
/// </summary>
void close()
{
try
{
m_stmt->free(m_status);
m_stmt = nullptr;
debug("statement closed: ", this);
}
CATCH_SQL
}
/// <summary>
/// Execute an SQL query and return pseudo single-row result set (as for "insert ... returning" statement, or stored procedure call)
/// </summary>
/// <returns>Pseudo result set (may be empty), having single row and positioned on it already. Data access methods allowed only</returns>
result_set execute()
{
using namespace _details;
using namespace Firebird;
try
{
auto omd = m_stmt->getOutputMetadata(m_status);
std::vector<uint8_t> out_buffer(omd->getMessageLength(m_status));
print_metadata(m_stmt->getOutputMetadata(m_status), m_status);
if (m_input_count)
{
std::vector<uint8_t> buffer;
auto md = make_autodestroy(get_input_parameters()->fill_buffer(buffer));
m_stmt->execute(m_status, m_tx, &md, buffer.data(), omd, out_buffer.data());
}
else
{
m_stmt->execute(m_status, m_tx, NULL, NULL, omd, out_buffer.data());
}
debug("statement executed: ", this);
return result_set{ omd, m_status, std::move(out_buffer) };
}
CATCH_SQL
}
/// <summary>
/// Execute an SQL query and return result set (cursor)
/// </summary>
/// <returns>Result set of executed query</returns>
result_set cursor()
{
using namespace _details;
using namespace Firebird;
try
{
IResultSet* rs{};
if (m_input_count)
{
std::vector<uint8_t> buffer;
auto md = make_autodestroy(get_input_parameters()->fill_buffer(buffer));
rs = m_stmt->openCursor(m_status, m_tx, &md, buffer.data(), NULL, IStatement::CURSOR_TYPE_SCROLLABLE);
}
else
{
rs = m_stmt->openCursor(m_status, m_tx, NULL, NULL, NULL, IStatement::CURSOR_TYPE_SCROLLABLE);
}
debug("statement cursor: ", this);
auto md2 = rs->getMetadata(m_status);
print_metadata(md2, m_status);
return result_set{ rs, m_status };
}
CATCH_SQL
}
/// <summary>
///
/// </summary>
/// <param name="i">field index</param>
/// <param name="value">field value</param>
/// <returns>self-reference</returns>
statement& setNull(unsigned i)
{
debug("statement: ", this, ", set #", i, " -> NULL");
get_input_parameters()->setNull(i - 1);
return *this;
}
statement& set(unsigned i, int32_t value, int scale = 0)
{
debug("statement: ", this, ", set #", i, " -> ", value);
get_input_parameters()->set(i - 1, value, scale);
return *this;
}
statement& set(unsigned i, int64_t value, int scale = 0)
{
debug("statement: ", this, ", set #", i, " -> ", value);
get_input_parameters()->set(i - 1, value, scale);
return *this;
}
statement& set(unsigned i, double value)
{
debug("statement: ", this, ", set #", i, " -> ", value);
get_input_parameters()->set(i - 1, value);
return *this;
}
statement& set(unsigned i, date value)
{
debug("statement: ", this, ", set #", i, " -> date");
get_input_parameters()->set(i - 1, value);
return *this;
}
statement& set(unsigned i, time value)
{
debug("statement: ", this, ", set #", i, " -> time");
get_input_parameters()->set(i - 1, value);
return *this;
}
statement& set(unsigned i, time_tz value)
{
debug("statement: ", this, ", set #", i, " -> time_tz");
get_input_parameters()->set(i - 1, value);
return *this;
}
statement& set(unsigned i, timestamp value)
{
debug("statement: ", this, ", set #", i, " -> timestamp");
get_input_parameters()->set(i - 1, value);
return *this;
}
statement& set(unsigned i, timestamp_tz value)
{
debug("statement: ", this, ", set #", i, " -> timestamp_tz");
get_input_parameters()->set(i - 1, value);
return *this;
}
statement& set(unsigned i, const char* value)
{
debug("statement: ", this, ", set #", i, " -> ", value);
if (value)
get_input_parameters()->set(i - 1, value);
else
get_input_parameters()->setNull(i - 1);
return *this;
}
statement& set(unsigned i, const std::vector<uint8_t>& value)
{
debug("statement: ", this, ", set #", i, " -> octets");
get_input_parameters()->set(i - 1, value);
return *this;
}
Firebird::IStatement* operator->() const
{
return m_stmt;
}
private:
statement(Firebird::IStatement* stmt, Firebird::ITransaction* tx, Firebird::ThrowStatusWrapper* status)
: m_stmt{ stmt }
, m_tx{ tx }
, m_status{ status }
{
auto imd = m_stmt->getInputMetadata(m_status);
m_input_count = imd->getCount(status);
debug("statement created: ", this);
print_metadata(imd, m_status);
}
_details::input_parameters* get_input_parameters()
{
if (!m_input_parameters)
m_input_parameters = std::make_unique<_details::input_parameters>(m_status, m_input_count);
return m_input_parameters.get();
}
private:
friend class transaction;
Firebird::IStatement* m_stmt;
Firebird::ITransaction* m_tx;
Firebird::ThrowStatusWrapper* m_status;
std::unique_ptr<_details::input_parameters> m_input_parameters;
unsigned m_input_count{};
};
/// <summary>
/// Database transaction RAII wrapper class
/// </summary>
class transaction final : private _details::non_copyable
{
public:
transaction& operator=(transaction&&) = delete;
transaction(transaction&& rhs) noexcept
: m_tx{ rhs.m_tx }
, m_att{ rhs.m_att }
, m_status{ rhs.m_status }
{
rhs.m_tx = nullptr;
}
~transaction()
{
if (m_tx) m_tx->release();
debug("transaction destroyed");
}
/// <summary>
/// Commit transaction. Makes current instance unusable
/// </summary>
void commit()
{
try
{
m_tx->commit(m_status);
m_tx = nullptr;
debug("transaction committed");
}
CATCH_SQL
}
/// <summary>
/// Rollback transaction. Makes current instance unusable
/// </summary>
void rollback()
{
try
{
m_tx->rollback(m_status);
m_tx = nullptr;
debug("transaction rolled back");
}
CATCH_SQL
}
/// <summary>
/// Commit transaction and start a new one in-place
/// </summary>
/// <returns>self-reference</returns>
transaction& commitRetaining()
{
try
{
m_tx->commitRetaining(m_status);
debug("transaction committed");
}
CATCH_SQL;
return *this;
}
/// <summary>
/// Rollback transaction and start a new one in-place
/// </summary>
/// <returns>self-reference</returns>
transaction& rollbackRetaining()
{
try
{
m_tx->rollbackRetaining(m_status);
debug("transaction rolled back");
}
CATCH_SQL;
return *this;
}
/// <summary>
/// Execute an SQL query as is, no inputs, no outputs
/// </summary>
/// <param name="sql">- SQL query to execute</param>
/// <returns>self-reference</returns>
transaction& execute(const char* sql)
{
try
{
m_att->execute(m_status, m_tx, 0, sql, SQL_DIALECT_V6, NULL, NULL, NULL, NULL);
debug("transaction executed");
}
CATCH_SQL;
return *this;
}
/// <summary>
/// Prepare SQL statement
/// </summary>
/// <param name="sql">- SQL query to prepare</param>
/// <returns>prepared statement</returns>
statement prepare(const char* sql) const
{
using namespace Firebird;
try
{
IStatement* stmt = m_att->prepare(m_status, m_tx, 0, sql, SQL_DIALECT_V6, IStatement::PREPARE_PREFETCH_METADATA);
return statement{ stmt, m_tx, m_status };
}
CATCH_SQL
}
Firebird::ITransaction* operator->() const
{
return m_tx;
}
private:
transaction(Firebird::ITransaction* tx, Firebird::IAttachment* att, Firebird::ThrowStatusWrapper* status)
: m_tx{ tx }
, m_att{ att }
, m_status{ status }
{
//m_tx = att->startTransaction(status, 0, NULL);
debug("transaction created");
}
private:
friend class connection;
Firebird::ITransaction* m_tx;
Firebird::IAttachment* m_att;
Firebird::ThrowStatusWrapper* m_status;
};
/// <summary>
/// Transaction parameters
/// </summary>
struct transaction_params
{
// defaults
uint8_t m_isolation_level{ isc_tpb_concurrency };
uint8_t m_read_committed_mode{ 0 };
uint8_t m_lock_resolution{ isc_tpb_wait };
uint8_t m_data_access{ isc_tpb_write };
int m_lock_timeout{ -1 };
bool m_auto_commit{ false };
// snapshot
transaction_params& concurrency()
{
m_isolation_level = isc_tpb_concurrency;
return *this;
}
// table stability
transaction_params& consistency()
{
m_isolation_level = isc_tpb_consistency;
return *this;
}
transaction_params& read_committed_no_record_version()
{
m_isolation_level = isc_tpb_read_committed;
m_read_committed_mode = isc_tpb_no_rec_version;
return *this;
}
transaction_params& read_committed_record_version()
{
m_isolation_level = isc_tpb_read_committed;
m_read_committed_mode = isc_tpb_rec_version;
return *this;
}
transaction_params& read_committed_consistency()
{
m_isolation_level = isc_tpb_read_committed;
m_read_committed_mode = isc_tpb_read_consistency;
return *this;
}
transaction_params& lock_wait(int timeout = -1)
{
m_lock_resolution = isc_tpb_wait;
m_lock_timeout = timeout;
return *this;
}
transaction_params& lock_nowait()
{
m_lock_resolution = isc_tpb_nowait;
return *this;
}
transaction_params& read_only()
{
m_data_access = isc_tpb_read;
return *this;
}
transaction_params& auto_commit(bool ac_flag = true)
{
m_auto_commit = ac_flag;
return *this;
}
};
/// <summary>
/// Database connection parameters
/// </summary>
struct connection_params
{
const char* role;
const char* lc_messages; // path to custom firebird.msg file
const char* lc_ctype; // connection charset
const char* session_time_zone;
const char* trusted_role;
int connect_timeout;
int dialect{ SQL_DIALECT_CURRENT };
bool trusted_auth;
};
connection connect(const char* database, const char* user, const char* password, const connection_params& params = {});
/// <summary>
/// Database connection RAII wrapper class
/// </summary>
class connection final : private _details::non_copyable
{
public:
connection& operator=(connection&&) = delete;
connection(connection&& rhs) noexcept
: m_att{ rhs.m_att }
, m_status{ master()->getStatus() } // create new status, old would be disposed
{
rhs.m_att = nullptr;
debug("connection moved: ", this, " from: ", &rhs);
}
~connection()
{
bool real = m_att != nullptr;
if (m_att) m_att->release();
m_status.dispose();
debug("connection destroyed: ", this, " ", real ? "REAL" : "EMPTY");
}
/// <summary>
/// Close database connection
/// </summary>
void close()
{
try
{
if (m_att) m_att->detach(&m_status);
m_att = nullptr;
debug("connection closed: ", this);
}
CATCH_SQL
}
/// <summary>
/// Ping server
/// </summary>
void ping()
{
try
{
m_att->ping(&m_status);
}
CATCH_SQL
}
/// <summary>
/// Start a transaction with server default parameters
/// </summary>
/// <returns>Transaction instance</returns>
transaction begin()
{
auto tx = m_att->startTransaction(&m_status, 0, NULL);
return transaction(tx, m_att, &m_status);
}
/// <summary>
/// Start a transaction, requesting parameters needed
/// </summary>
/// <returns>Transaction instance</returns>
transaction begin(const transaction_params& params)
{
using namespace _details;
using namespace Firebird;
auto tpb = make_autodestroy(util()->getXpbBuilder(&m_status, IXpbBuilder::TPB, nullptr, 0));
tpb->insertTag(&m_status, params.m_isolation_level);
if (params.m_isolation_level == isc_tpb_read_committed)
tpb->insertTag(&m_status, params.m_read_committed_mode);
tpb->insertTag(&m_status, params.m_lock_resolution);
if (params.m_lock_resolution == isc_tpb_wait && params.m_lock_timeout > 0)
tpb->insertInt(&m_status, isc_tpb_lock_timeout, params.m_lock_timeout);
tpb->insertTag(&m_status, params.m_data_access);
if (params.m_auto_commit)
tpb->insertTag(&m_status, isc_tpb_autocommit);
auto tx = m_att->startTransaction(&m_status, tpb->getBufferLength(&m_status), tpb->getBuffer(&m_status));
return transaction(tx, m_att, &m_status);
}
/// <summary>
/// Execute an SQL statement immediately
/// </summary>
/// <param name="sql">- SQL statement to execute</param>
/// <returns>self-reference</returns>
connection& execute(const char* sql)
{
begin().execute(sql).commit();
return *this;
}
Firebird::IAttachment* operator->() const
{
return m_att;
}
private:
friend connection connect(const char* database, const char* user, const char* password, const connection_params& params);
connection(const char* database, const char* user, const char* password, const connection_params& params)
: m_att{}
, m_status{ master()->getStatus() }
{
using namespace Firebird;
using namespace _details;
auto dpb = make_autodestroy(util()->getXpbBuilder(&m_status, IXpbBuilder::DPB, nullptr, 0));
if (user)
dpb->insertString(&m_status, isc_dpb_user_name, user);
if (password)
dpb->insertString(&m_status, isc_dpb_password, password);
if (params.role)
dpb->insertString(&m_status, isc_dpb_sql_role_name, params.role);
if (params.lc_ctype)
dpb->insertString(&m_status, isc_dpb_lc_ctype, params.lc_ctype);
if (params.lc_messages)
dpb->insertString(&m_status, isc_dpb_lc_messages, params.lc_messages);
if (params.session_time_zone)
dpb->insertString(&m_status, isc_dpb_session_time_zone, params.session_time_zone);
if (params.trusted_auth)
dpb->insertTag(&m_status, isc_dpb_trusted_auth);
if (params.trusted_role)
dpb->insertString(&m_status, isc_dpb_trusted_role, params.trusted_role);
if (params.connect_timeout > 0)
dpb->insertInt(&m_status, isc_dpb_connect_timeout, params.connect_timeout);
dpb->insertInt(&m_status, isc_dpb_sql_dialect, params.dialect);
try
{
auto provider = make_autodestroy(master()->getDispatcher());
m_att = provider->attachDatabase(&m_status, database, dpb->getBufferLength(&m_status), dpb->getBuffer(&m_status));
debug("connection: ", database, " ", this);
}
CATCH_SQL
}
private:
Firebird::IAttachment* m_att;
Firebird::ThrowStatusWrapper m_status;
};
/// <summary>
/// Create database conection
/// </summary>
/// <param name="database">- URN, inet://host:port/path/to/db.fdb</param>
/// <param name="user">- login</param>
/// <param name="password">- password</param>
/// <param name="params">- optional connection parameters</param>
/// <returns>Connection instance</returns>
inline connection connect(const char* database, const char* user, const char* password, const connection_params& params)
{
return connection(database, user, password, params);
}
} // namespace fbsqlxx2
#undef debug
#undef print_metadata
#undef CATCH_SQL