1625 lines
41 KiB
C++
1625 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 fbsqlxx {
|
|
|
|
std::string type_name(unsigned int type);
|
|
|
|
#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;
|
|
}
|
|
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
|
|
inline void debug(...)
|
|
{
|
|
}
|
|
inline void 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)
|
|
{
|
|
// 0-bit means nullability, clear before switch
|
|
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(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;
|
|
octets 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 octets& 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 octets result_set::as<octets>(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 octets(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 octets& 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 fbsqlxx
|
|
|
|
#undef CATCH_SQL
|