From 7082aff6ac22f0874e6f68cae0c43989408592c121557d70462b196fb9dd243f Mon Sep 17 00:00:00 2001 From: gzip4 Date: Sat, 22 Feb 2025 08:56:48 +0500 Subject: [PATCH] Initial commit --- include/fbsqlxx.hpp | 1396 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1396 insertions(+) create mode 100644 include/fbsqlxx.hpp diff --git a/include/fbsqlxx.hpp b/include/fbsqlxx.hpp new file mode 100644 index 0000000..27c5389 --- /dev/null +++ b/include/fbsqlxx.hpp @@ -0,0 +1,1396 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef FBSQLXX_DEBUG +#include +#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 +void debug(Args&& ...args) +{ + (std::cout << ... << std::forward(args)) << std::endl; +} +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 + + +class connection; +class transaction; +class statement; +class result_set; + +static inline Firebird::IMaster* master() +{ + static Firebird::IMaster* _master = Firebird::fb_get_master_interface(); + return _master; +} + +static inline Firebird::IUtil* util() +{ + static Firebird::IUtil* _util = master()->getUtilInterface(); + return _util; +} + +static inline int64_t portable_integer(const uint8_t* p, short length) +{ + return isc_portable_integer(p, length); +} + +// 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 }; } + + +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"; + + default: + break; + } + + return "UNKNOWN"; +} + + +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, T>* = nullptr> +inline void destroy(T* _value) +{ + _value->free(); +} + +template < + typename T, + std::enable_if_t, T>* = nullptr> +inline void destroy(T* _value) +{ + _value->dispose(); +} + +template < + typename T, + std::enable_if_t, T>* = nullptr> +inline void destroy(T* _value) +{ + _value->release(); +} + +template +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 +inline autodestroy make_autodestroy(T* value) +{ + return autodestroy{ value }; +} + +struct parameter +{ + int type; + int subtype; + int scale; + std::string str_value; + std::vector 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& buffer) + { + auto md = build_metadata(); + buffer.resize(md->getMessageLength(m_status)); + + const auto count = static_cast(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(offset) = param.int64_value; + break; + + case SQL_LONG: + cast(offset) = param.int32_value; + break; + + case SQL_SHORT: + cast(offset) = param.int16_value; + break; + + case SQL_DOUBLE: + cast(offset) = param.double_value; + break; + + case SQL_TEXT: + memcpy(((void*)offset), param.str_value.data(), param.str_value.size()); + break; + + case SQL_VARYING: + cast(offset) = static_cast(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()); + } + } + + print_metadata(md, m_status); + + return md; + } + + void setNull(unsigned int i) + { + m_parameters[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[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[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[i] = p; + } + + void set(unsigned int i, double value) + { + parameter p{}; + p.type = SQL_DOUBLE; + p.double_value = value; + m_parameters[i] = p; + } + + void set(unsigned int i, const char* value) + { + parameter p{}; + p.type = SQL_TEXT; + p.str_value = value; + m_parameters[i] = std::move(p); + } + + void set(unsigned int i, const std::vector& value) + { + parameter p{}; + p.type = MY_SQL_OCTETS; + p.octets_value = value; + m_parameters[i] = std::move(p); + } + +private: + Firebird::IMessageMetadata* build_metadata() + { + const auto count = static_cast(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 + { + if (param.type == SQL_TEXT) + { + auto length = static_cast(param.str_value.size()); + builder->setLength(m_status, i, length); + } + + if (param.type == MY_SQL_OCTETS) + { + builder->setType(m_status, i, SQL_TEXT + 1); + auto length = static_cast(param.octets_value.size()); + builder->setLength(m_status, i, length); + } + else + { + 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); + } + } + else + builder->setType(m_status, i, SQL_BOOLEAN + 1); // null, type not important? + + } + + return builder->getMetadata(m_status); + } + + template + T& cast(void* offset) const + { + return *((T*)offset); + } + +private: + Firebird::ThrowStatusWrapper* m_status; + std::vector m_parameters; +}; + +} // namespace fbsqlxx2::_details + + + +///////////////////////////////////////////////////////////////////////// + + +/// +/// Database result set (cursor) RAII wrapper class +/// +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"); + } + + /// + /// + /// + void close() + { + try + { + m_rs->close(m_status); + m_rs = nullptr; + debug("result_set closed"); + } + CATCH_SQL + } + + /// + /// + /// + /// + unsigned int field_count() const + { + return m_count; + } + + /// + /// + /// + /// + bool is_bof() const + { + try + { + return m_rs->isBof(m_status); + } + CATCH_SQL + } + + /// + /// + /// + /// + bool is_eof() const + { + try + { + return m_rs->isEof(m_status); + } + CATCH_SQL + } + + /// + /// + /// + /// + bool next() + { + try + { + return m_rs->fetchNext(m_status, m_buffer.data()) == Firebird::IStatus::RESULT_OK; + } + CATCH_SQL + } + + /// + /// + /// + /// + bool prev() + { + try + { + return m_rs->fetchPrior(m_status, m_buffer.data()) == Firebird::IStatus::RESULT_OK; + } + CATCH_SQL + } + + /// + /// + /// + /// + bool first() + { + try + { + return m_rs->fetchFirst(m_status, m_buffer.data()) == Firebird::IStatus::RESULT_OK; + } + CATCH_SQL + } + + /// + /// + /// + /// + bool last() + { + try + { + return m_rs->fetchLast(m_status, m_buffer.data()) == Firebird::IStatus::RESULT_OK; + } + CATCH_SQL + } + + /// + /// + /// + /// + /// + bool absolute(int position) + { + try + { + return m_rs->fetchAbsolute(m_status, position, m_buffer.data()) == Firebird::IStatus::RESULT_OK; + } + CATCH_SQL + } + + /// + /// + /// + /// + /// + bool relative(int offset) + { + try + { + return m_rs->fetchRelative(m_status, offset, m_buffer.data()) == Firebird::IStatus::RESULT_OK; + } + CATCH_SQL + } + + /// + /// Check column is null and has no value + /// + /// - Column index (1-based) + /// true if column is null + bool is_null(unsigned index) const + { + auto const& f = m_fields.at(index - 1); + return 0 != cast(m_buffer.data() + f.null); + } + + /// + /// Get value of column by index + /// + /// Requested column type + /// - Column index (1-based) + /// Column value + template + std::enable_if_t, T> as(unsigned index) const + { + auto const& f = m_fields.at(index - 1); + + if (sizeof(T) > f.length) + { + // XXX: field value size override + throw logic_error("!!!"); + } + + return cast(m_buffer.data() + f.offset); + } + + /// + /// + /// + /// + /// + /// + template + std::enable_if_t, T> as(unsigned index) const + { + auto const& f = m_fields.at(index - 1); + + if (sizeof(T) > f.length) + { + // XXX: field value size override + throw logic_error("!!!"); + } + + return portable_integer(m_buffer.data() + f.offset, sizeof(T)); + } + + Firebird::IResultSet* operator->() const + { + return m_rs; + } + +private: + // buffer low level accessor + template + T const& cast(const void* offset) const + { + return *((const T*)offset); + } + + struct field + { + const char* name; + unsigned type, subtype, length, offset, null; + 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 = m_meta->getNullOffset(m_status, i); + f.scale = m_meta->getScale(m_status, i); + } + } + + /// + /// Normal result set constructor + /// + /// + /// + 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"); + } + + /// + /// Special constructor, for pseudo result set + /// + /// + /// + /// + result_set(Firebird::IMessageMetadata* meta, Firebird::ThrowStatusWrapper* status, std::vector&& 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 m_buffer; + std::vector m_fields; + unsigned int m_count; +}; + +/// +/// +/// +/// +/// +template <> +inline std::string result_set::as(unsigned i) const +{ + auto const& f = m_fields.at(i - 1); + auto start = m_buffer.data() + f.offset; + return std::string(start, start + f.length); +} + +/// +/// +/// +/// +/// +template <> +inline std::vector result_set::as>(unsigned i) const +{ + auto const& f = m_fields.at(i - 1); + auto start = m_buffer.data() + f.offset; + return std::vector(start, start + f.length); +} + + +/// +/// Database prepared statement RAII wrapper class +/// +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); + } + + /// + /// Close statement and free resources. Do not call any methods on closed statement instance. + /// + void close() + { + try + { + m_stmt->free(m_status); + m_stmt = nullptr; + + debug("statement closed: ", this); + } + CATCH_SQL + } + + /// + /// Execute an SQL query and return pseudo single-row result set (as for "insert ... returning" statement, or stored procedure call) + /// + /// Pseudo result set (may be empty), having single row and positioned on it already. Data access methods allowed only + result_set execute() + { + using namespace _details; + using namespace Firebird; + + try + { + auto omd = m_stmt->getOutputMetadata(m_status); + std::vector out_buffer(omd->getMessageLength(m_status)); + + print_metadata(m_stmt->getOutputMetadata(m_status), m_status); + + if (m_input_count) + { + std::vector 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 + } + + /// + /// Execute an SQL query and return result set (cursor) + /// + /// Result set of executed query + result_set cursor() + { + using namespace _details; + using namespace Firebird; + + try + { + IResultSet* rs{}; + + if (m_input_count) + { + std::vector 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 + } + + /// + /// + /// + /// field index + /// field value + /// self-reference + 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, 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& 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{}; +}; + + +/// +/// Database transaction RAII wrapper class +/// +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"); + } + + /// + /// Commit transaction. Makes current instance unusable + /// + void commit() + { + try + { + m_tx->commit(m_status); + m_tx = nullptr; + + debug("transaction committed"); + } + CATCH_SQL + } + + /// + /// Rollback transaction. Makes current instance unusable + /// + void rollback() + { + try + { + m_tx->rollback(m_status); + m_tx = nullptr; + + debug("transaction rolled back"); + } + CATCH_SQL + } + + /// + /// Commit transaction and start a new one in-place + /// + /// self-reference + transaction& commitRetaining() + { + try + { + m_tx->commitRetaining(m_status); + + debug("transaction committed"); + } + CATCH_SQL; + + return *this; + } + + /// + /// Rollback transaction and start a new one in-place + /// + /// self-reference + transaction& rollbackRetaining() + { + try + { + m_tx->rollbackRetaining(m_status); + + debug("transaction rolled back"); + } + CATCH_SQL; + + return *this; + } + + /// + /// Execute an SQL query as is, no inputs, no outputs + /// + /// - SQL query to execute + /// self-reference + 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; + } + + /// + /// Prepare SQL statement + /// + /// - SQL query to prepare + /// prepared statement + 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; +}; + +/// +/// Transaction parameters +/// +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; + } +}; + +/// +/// Database connection parameters +/// +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 = {}); + + +/// +/// Database connection RAII wrapper class +/// +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"); + } + + /// + /// Close database connection + /// + void close() + { + try + { + if (m_att) m_att->detach(&m_status); + m_att = nullptr; + + debug("connection closed: ", this); + } + CATCH_SQL + } + + /// + /// Ping server + /// + void ping() + { + try + { + m_att->ping(&m_status); + } + CATCH_SQL + } + + /// + /// Start a transaction with server default parameters + /// + /// Transaction instance + transaction begin() + { + auto tx = m_att->startTransaction(&m_status, 0, NULL); + return transaction(tx, m_att, &m_status); + } + + /// + /// Start a transaction, requesting parameters needed + /// + /// Transaction instance + 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); + } + + /// + /// Execute an SQL statement immediately + /// + /// - SQL statement to execute + /// self-reference + 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; +}; + +/// +/// Create database conection +/// +/// - URN, inet://host:port/path/to/db.fdb +/// - login +/// - password +/// - optional connection parameters +/// Connection instance +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 CATCH_SQL