From cf9e75feef2e120f1ecbff95ee17cf7bd0057734084dba40b0f14c5790dbcd4a Mon Sep 17 00:00:00 2001 From: gzip4 Date: Sat, 22 Feb 2025 12:25:25 +0500 Subject: [PATCH] Add date/time support, minor security improvements --- include/fbsqlxx.hpp | 395 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 310 insertions(+), 85 deletions(-) diff --git a/include/fbsqlxx.hpp b/include/fbsqlxx.hpp index 27c5389..3acdc63 100644 --- a/include/fbsqlxx.hpp +++ b/include/fbsqlxx.hpp @@ -1,8 +1,6 @@ #pragma once -#include -#include - +#include #include #include #include @@ -24,11 +22,11 @@ namespace fbsqlxx2 { #ifdef FBSQLXX_DEBUG template -void debug(Args&& ...args) +inline void debug(Args&& ...args) { (std::cout << ... << std::forward(args)) << std::endl; } -void debug(const char* s) +inline void debug(const char* s) { std::cout << s << std::endl; } @@ -64,67 +62,24 @@ inline void print_metadata(Firebird::IMessageMetadata* md, Firebird::ThrowStatus #endif // FBSQLXX_DEBUG -class connection; -class transaction; -class statement; -class result_set; - -static inline Firebird::IMaster* master() +// static functions +inline Firebird::IMaster* master() { static Firebird::IMaster* _master = Firebird::fb_get_master_interface(); return _master; } -static inline Firebird::IUtil* util() +inline Firebird::IUtil* util() { static Firebird::IUtil* _util = master()->getUtilInterface(); return _util; } -static inline int64_t portable_integer(const uint8_t* p, short length) +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) @@ -191,14 +146,149 @@ inline std::string type_name(unsigned int type) case SQL_VARYING: return "VARCHAR"; - - default: - break; } return "UNKNOWN"; } +// types +class connection; +class transaction; +class statement; +class result_set; + +using octets = std::vector; + +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 { @@ -345,12 +435,36 @@ public: cast(offset) = param.double_value; break; + case SQL_FLOAT: + cast(offset) = param.float_value; + break; + + case SQL_TYPE_DATE: + cast(offset) = param.date_value; + break; + + case SQL_TYPE_TIME: + cast(offset) = param.time_value; + break; + + case SQL_TIME_TZ: + cast(offset) = param.time_tz_value; + break; + + case SQL_TIMESTAMP: + cast(offset) = param.timestamp_value; + break; + + case SQL_TIMESTAMP_TZ: + cast(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(offset) = static_cast(param.str_value.size()); + cast(offset) = static_cast(param.str_value.size()); memcpy(offset + 2, param.str_value.data(), param.str_value.size()); break; @@ -359,10 +473,12 @@ public: 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); @@ -372,7 +488,7 @@ public: void setNull(unsigned int i) { - m_parameters[i] = {}; + m_parameters.at(i) = {}; } void set(unsigned int i, int16_t value, int scale) @@ -381,7 +497,7 @@ public: p.type = SQL_SHORT; p.scale = -scale; p.int16_value = value; - m_parameters[i] = p; + m_parameters.at(i) = p; } void set(unsigned int i, int32_t value, int scale) @@ -390,7 +506,7 @@ public: p.type = SQL_LONG; p.scale = -scale; p.int32_value = value; - m_parameters[i] = p; + m_parameters.at(i) = p; } void set(unsigned int i, int64_t value, int scale) @@ -399,7 +515,7 @@ public: p.type = SQL_INT64; p.scale = -scale; p.int64_value = value; - m_parameters[i] = p; + m_parameters.at(i) = p; } void set(unsigned int i, double value) @@ -407,7 +523,51 @@ public: parameter p{}; p.type = SQL_DOUBLE; p.double_value = value; - m_parameters[i] = p; + 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) @@ -415,7 +575,7 @@ public: parameter p{}; p.type = SQL_TEXT; p.str_value = value; - m_parameters[i] = std::move(p); + m_parameters.at(i) = std::move(p); } void set(unsigned int i, const std::vector& value) @@ -423,7 +583,7 @@ public: parameter p{}; p.type = MY_SQL_OCTETS; p.octets_value = value; - m_parameters[i] = std::move(p); + m_parameters.at(i) = std::move(p); } private: @@ -431,31 +591,40 @@ private: { 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) + switch (param.type) { + case SQL_TEXT: + { + builder->setType(m_status, i, SQL_TEXT + 1); auto length = static_cast(param.str_value.size()); builder->setLength(m_status, i, length); } + break; - if (param.type == MY_SQL_OCTETS) + case MY_SQL_OCTETS: { builder->setType(m_status, i, SQL_TEXT + 1); - auto length = static_cast(param.octets_value.size()); + auto length = static_cast(param.str_value.size()); builder->setLength(m_status, i, length); } - else + 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? @@ -644,8 +813,9 @@ public: /// true if column is null bool is_null(unsigned index) const { + assert(index >= 1); auto const& f = m_fields.at(index - 1); - return 0 != cast(m_buffer.data() + f.null); + return 0 != cast(m_buffer.data() + f.null_offset); } /// @@ -657,14 +827,10 @@ public: template std::enable_if_t, T> as(unsigned index) const { + assert(index >= 1); auto const& f = m_fields.at(index - 1); - - if (sizeof(T) > f.length) - { - // XXX: field value size override - throw logic_error("!!!"); - } - + check_field_null(f.name, f.null_offset); + check_field_size(f.name, f.length, sizeof(T)); return cast(m_buffer.data() + f.offset); } @@ -677,15 +843,11 @@ public: template std::enable_if_t, T> as(unsigned index) const { + assert(index >= 1); 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)); + check_field_null(f.name, f.null_offset); + check_field_size(f.name, f.length, sizeof(T)); + return static_cast(portable_integer(m_buffer.data() + f.offset, sizeof(T))); } Firebird::IResultSet* operator->() const @@ -701,10 +863,33 @@ private: return *((const T*)offset); } + void check_field_null(const char* name, unsigned null_offset) const + { + if (0 != cast(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; + unsigned type, subtype, length, offset, null_offset; int scale; }; @@ -719,7 +904,7 @@ private: 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.null_offset = m_meta->getNullOffset(m_status, i); f.scale = m_meta->getScale(m_status, i); } } @@ -778,7 +963,9 @@ private: template <> inline std::string result_set::as(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); } @@ -791,7 +978,9 @@ inline std::string result_set::as(unsigned i) const template <> inline std::vector result_set::as>(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(start, start + f.length); } @@ -939,6 +1128,41 @@ public: 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); @@ -1393,4 +1617,5 @@ inline connection connect(const char* database, const char* user, const char* pa } // namespace fbsqlxx2 #undef debug +#undef print_metadata #undef CATCH_SQL