Add date/time support, minor security improvements

This commit is contained in:
gzip4 2025-02-22 12:25:25 +05:00
parent 7082aff6ac
commit cf9e75feef

View File

@ -1,8 +1,6 @@
#pragma once #pragma once
#include <firebird/Interface.h> #include <cassert>
#include <firebird/Message.h>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@ -24,11 +22,11 @@ namespace fbsqlxx2 {
#ifdef FBSQLXX_DEBUG #ifdef FBSQLXX_DEBUG
template <typename ...Args> template <typename ...Args>
void debug(Args&& ...args) inline void debug(Args&& ...args)
{ {
(std::cout << ... << std::forward<Args>(args)) << std::endl; (std::cout << ... << std::forward<Args>(args)) << std::endl;
} }
void debug(const char* s) inline void debug(const char* s)
{ {
std::cout << s << std::endl; std::cout << s << std::endl;
} }
@ -64,67 +62,24 @@ inline void print_metadata(Firebird::IMessageMetadata* md, Firebird::ThrowStatus
#endif // FBSQLXX_DEBUG #endif // FBSQLXX_DEBUG
class connection; // static functions
class transaction; inline Firebird::IMaster* master()
class statement;
class result_set;
static inline Firebird::IMaster* master()
{ {
static Firebird::IMaster* _master = Firebird::fb_get_master_interface(); static Firebird::IMaster* _master = Firebird::fb_get_master_interface();
return _master; return _master;
} }
static inline Firebird::IUtil* util() inline Firebird::IUtil* util()
{ {
static Firebird::IUtil* _util = master()->getUtilInterface(); static Firebird::IUtil* _util = master()->getUtilInterface();
return _util; 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); 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) inline std::string type_name(unsigned int type)
{ {
switch (type & ~1u) switch (type & ~1u)
@ -191,14 +146,149 @@ inline std::string type_name(unsigned int type)
case SQL_VARYING: case SQL_VARYING:
return "VARCHAR"; return "VARCHAR";
default:
break;
} }
return "UNKNOWN"; 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 { namespace _details {
@ -345,12 +435,36 @@ public:
cast<double>(offset) = param.double_value; cast<double>(offset) = param.double_value;
break; 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: case SQL_TEXT:
memcpy(((void*)offset), param.str_value.data(), param.str_value.size()); memcpy(((void*)offset), param.str_value.data(), param.str_value.size());
break; break;
case SQL_VARYING: case SQL_VARYING:
cast<short>(offset) = static_cast<short>(param.str_value.size()); cast<int16_t>(offset) = static_cast<int16_t>(param.str_value.size());
memcpy(offset + 2, param.str_value.data(), param.str_value.size()); memcpy(offset + 2, param.str_value.data(), param.str_value.size());
break; break;
@ -359,10 +473,12 @@ public:
break; break;
default: default:
{
std::string msg = "Not implemented parameter type: "; std::string msg = "Not implemented parameter type: ";
msg += type_name(param.type); msg += type_name(param.type);
throw logic_error(msg.data()); throw logic_error(msg.data());
} }
} // switch (param.type)
} }
print_metadata(md, m_status); print_metadata(md, m_status);
@ -372,7 +488,7 @@ public:
void setNull(unsigned int i) void setNull(unsigned int i)
{ {
m_parameters[i] = {}; m_parameters.at(i) = {};
} }
void set(unsigned int i, int16_t value, int scale) void set(unsigned int i, int16_t value, int scale)
@ -381,7 +497,7 @@ public:
p.type = SQL_SHORT; p.type = SQL_SHORT;
p.scale = -scale; p.scale = -scale;
p.int16_value = value; p.int16_value = value;
m_parameters[i] = p; m_parameters.at(i) = p;
} }
void set(unsigned int i, int32_t value, int scale) void set(unsigned int i, int32_t value, int scale)
@ -390,7 +506,7 @@ public:
p.type = SQL_LONG; p.type = SQL_LONG;
p.scale = -scale; p.scale = -scale;
p.int32_value = value; p.int32_value = value;
m_parameters[i] = p; m_parameters.at(i) = p;
} }
void set(unsigned int i, int64_t value, int scale) void set(unsigned int i, int64_t value, int scale)
@ -399,7 +515,7 @@ public:
p.type = SQL_INT64; p.type = SQL_INT64;
p.scale = -scale; p.scale = -scale;
p.int64_value = value; p.int64_value = value;
m_parameters[i] = p; m_parameters.at(i) = p;
} }
void set(unsigned int i, double value) void set(unsigned int i, double value)
@ -407,7 +523,51 @@ public:
parameter p{}; parameter p{};
p.type = SQL_DOUBLE; p.type = SQL_DOUBLE;
p.double_value = value; 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) void set(unsigned int i, const char* value)
@ -415,7 +575,7 @@ public:
parameter p{}; parameter p{};
p.type = SQL_TEXT; p.type = SQL_TEXT;
p.str_value = value; 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<uint8_t>& value) void set(unsigned int i, const std::vector<uint8_t>& value)
@ -423,7 +583,7 @@ public:
parameter p{}; parameter p{};
p.type = MY_SQL_OCTETS; p.type = MY_SQL_OCTETS;
p.octets_value = value; p.octets_value = value;
m_parameters[i] = std::move(p); m_parameters.at(i) = std::move(p);
} }
private: private:
@ -431,31 +591,40 @@ private:
{ {
const auto count = static_cast<unsigned>(m_parameters.size()); const auto count = static_cast<unsigned>(m_parameters.size());
auto builder = make_autodestroy(master()->getMetadataBuilder(m_status, count)); auto builder = make_autodestroy(master()->getMetadataBuilder(m_status, count));
for (unsigned i = 0; i < count; ++i) for (unsigned i = 0; i < count; ++i)
{ {
auto const& param = m_parameters[i]; auto const& param = m_parameters[i];
if (param.type) // not null 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<unsigned>(param.str_value.size()); auto length = static_cast<unsigned>(param.str_value.size());
builder->setLength(m_status, i, length); 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); builder->setType(m_status, i, SQL_TEXT + 1);
auto length = static_cast<unsigned>(param.octets_value.size()); auto length = static_cast<unsigned>(param.str_value.size());
builder->setLength(m_status, i, length); builder->setLength(m_status, i, length);
} }
else break;
default:
{ {
builder->setType(m_status, i, param.type + 1); builder->setType(m_status, i, param.type + 1);
builder->setSubType(m_status, i, param.subtype); builder->setSubType(m_status, i, param.subtype);
if (param.scale) if (param.scale)
builder->setScale(m_status, i, param.scale); builder->setScale(m_status, i, param.scale);
} }
break;
} // switch (param.type)
} }
else else
builder->setType(m_status, i, SQL_BOOLEAN + 1); // null, type not important? builder->setType(m_status, i, SQL_BOOLEAN + 1); // null, type not important?
@ -644,8 +813,9 @@ public:
/// <returns>true if column is null</returns> /// <returns>true if column is null</returns>
bool is_null(unsigned index) const bool is_null(unsigned index) const
{ {
assert(index >= 1);
auto const& f = m_fields.at(index - 1); auto const& f = m_fields.at(index - 1);
return 0 != cast<int16_t>(m_buffer.data() + f.null); return 0 != cast<int16_t>(m_buffer.data() + f.null_offset);
} }
/// <summary> /// <summary>
@ -657,14 +827,10 @@ public:
template <typename T> template <typename T>
std::enable_if_t<!std::is_integral_v<T>, T> as(unsigned index) const 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); auto const& f = m_fields.at(index - 1);
check_field_null(f.name, f.null_offset);
if (sizeof(T) > f.length) check_field_size(f.name, f.length, sizeof(T));
{
// XXX: field value size override
throw logic_error("!!!");
}
return cast<T>(m_buffer.data() + f.offset); return cast<T>(m_buffer.data() + f.offset);
} }
@ -677,15 +843,11 @@ public:
template <typename T> template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> as(unsigned index) const 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); auto const& f = m_fields.at(index - 1);
check_field_null(f.name, f.null_offset);
if (sizeof(T) > f.length) check_field_size(f.name, f.length, sizeof(T));
{ return static_cast<T>(portable_integer(m_buffer.data() + f.offset, sizeof(T)));
// XXX: field value size override
throw logic_error("!!!");
}
return portable_integer(m_buffer.data() + f.offset, sizeof(T));
} }
Firebird::IResultSet* operator->() const Firebird::IResultSet* operator->() const
@ -701,10 +863,33 @@ private:
return *((const T*)offset); 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 struct field
{ {
const char* name; const char* name;
unsigned type, subtype, length, offset, null; unsigned type, subtype, length, offset, null_offset;
int scale; int scale;
}; };
@ -719,7 +904,7 @@ private:
f.subtype = m_meta->getSubType(m_status, i); f.subtype = m_meta->getSubType(m_status, i);
f.length = m_meta->getLength(m_status, i); f.length = m_meta->getLength(m_status, i);
f.offset = m_meta->getOffset(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); f.scale = m_meta->getScale(m_status, i);
} }
} }
@ -778,7 +963,9 @@ private:
template <> template <>
inline std::string result_set::as<std::string>(unsigned i) const inline std::string result_set::as<std::string>(unsigned i) const
{ {
assert(i >= 1);
auto const& f = m_fields.at(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; auto start = m_buffer.data() + f.offset;
return std::string(start, start + f.length); return std::string(start, start + f.length);
} }
@ -791,7 +978,9 @@ inline std::string result_set::as<std::string>(unsigned i) const
template <> template <>
inline std::vector<uint8_t> result_set::as<std::vector<uint8_t>>(unsigned i) const 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); auto const& f = m_fields.at(i - 1);
check_field_null(f.name, f.null_offset);
auto start = m_buffer.data() + f.offset; auto start = m_buffer.data() + f.offset;
return std::vector<uint8_t>(start, start + f.length); return std::vector<uint8_t>(start, start + f.length);
} }
@ -939,6 +1128,41 @@ public:
return *this; 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) statement& set(unsigned i, const char* value)
{ {
debug("statement: ", this, ", set #", i, " -> ", 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 } // namespace fbsqlxx2
#undef debug #undef debug
#undef print_metadata
#undef CATCH_SQL #undef CATCH_SQL