获取 SOCI 库的查询结果时,如果转换的字段没有精确匹配,会导致异常。
字段类型可以从 column_properties
中获取。
row r;
const column_properties & props = r.get_properties(i);
props.get_data_type(); // 字段类型
字段类型 soci::data_type
与 C++ 类型对应关系如下:
SOCI Data Type | row::get<T> specialization |
---|---|
dt_double | double |
dt_integer | int |
dt_long_long | long long |
dt_unsigned_long_long | unsigned long long |
dt_string | std::string |
dt_date | std::tm |
参考文档:Data Types
与数据库对应关系:
MySQL Data Type | SOCI Data Type | row::get<T> specializations |
---|---|---|
FLOAT, DOUBLE, DECIMAL and synonyms | dt_double | double |
TINYINT, TINYINT UNSIGNED, SMALLINT, SMALLINT UNSIGNED, INT | dt_integer | int |
INT UNSIGNED | dt_long_long | long long or unsigned |
BIGINT | dt_long_long | long long |
BIGINT UNSIGNED | dt_unsigned_long_long | unsigned long long |
CHAR, VARCHAR, BINARY, VARBINARY, TINYBLOB, MEDIUMBLOB, BLOB,LONGBLOB, TINYTEXT, MEDIUMTEXT, TEXT, LONGTEXT, ENUM | dt_string | std::string |
TIMESTAMP (works only with MySQL >= 5.0), DATE, TIME, DATETIME | dt_date | std::tm |
从 SOCI 的 MySQL 库的查询结果 soci::row
中获取字段的值时,需要使用模板参数指定字段的类型。
template <typename T>
T get(std::size_t pos) const
如果模板参数指定的字段类型与库内部解析的结果不一致,会抛出 bad_cast::bad_cast
异常。
出现问题的表结构如下:
CREATE TABLE `t_enum` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`tag` VARCHAR(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '筛选维度',
`name` VARCHAR(120) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '筛选值名称',
`value` INT(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '筛选值',
PRIMARY KEY (`id`),
KEY `idx_enum` (`tag`,`name`)
) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='枚举字段表'
查询语句:
SELECT `tag`, `name`, `value` FROM stt_stream.t_enum;
解析的代码如下:
std::string tag = cit->get<std::string>(0);
std::string name = cit->get<std::string>(1);
int value = cit->get<int>(2);
在运行过程中会捕获到异常 bad_cast::bad_cast
并打印错误日志。
众所周知,C++ 模板的类型要求精确匹配,所以一开始就怀疑到了字段类型的问题上。
获取 value
字段时,分别尝试了 int64_t
和 uint64_t
后,仍然有类型错误的异常。
查阅 SOCI 库的文档,也并未找到数据库字段类型与 C++ 类型的映射关系。
中途怀疑是其他地方的代码有问题,走了一些弯路。
最终查看了 SOCI 库的源代码,找到了类型转换的一个坑。
SOCI 将不同类型的数据库后端封装成了统一的接口。 对于 MySQL, SOCI 依赖官方的 MySQL C 库。
在 src/backends/mysql/statement.cpp
中的 describe_column
函数,通过 MySQL 的 mysql_fetch_field_direct
获取表字段属性并判断类型。
MYSQL_FIELD *field = mysql_fetch_field_direct(result_, pos);
switch (field->type)
{
case FIELD_TYPE_LONG: //MYSQL_TYPE_LONG:
type = field->flags & UNSIGNED_FLAG ? dt_long_long : dt_integer;
break;
case FIELD_TYPE_LONGLONG: //MYSQL_TYPE_LONGLONG:
type = field->flags & UNSIGNED_FLAG ? dt_unsigned_long_long : dt_long_long;
break;
}
可以看到,对于数据库中原始类型为 MYSQL_TYPE_LONG
并且有 UNSIGNED_FLAG
的字段,SOCI 会当做 dt_long_long
处理,也就是需要转换成 long long
类型。
一般来说,在 64 位系统上,int32_t
是 int
的别名,int64_t
是 long
的别名。long long
长度和 long
一样都是 8 字节,但它们是不同的两个类型。由于模板参数必须精确匹配类型,所以 int64_t
不能代替 long long
。
SOCI 使用基类 holder
保存字段,针对不同类型实现派生类 type_holder
。
使用 row::get<T>(pos)
获取字段值时,将基类 dynamic_cast
到对应类型 T
,如果转换失败,会抛出异常。
类型转换的代码很精妙,摘录如下:
template <typename T>
class type_holder;
class holder
{
public:
holder() {}
virtual ~holder() {}
template<typename T>
T get()
{
type_holder<T>* p = dynamic_cast<type_holder<T> *>(this);
if (p)
{
return p->template value<T>();
}
else
{
throw std::bad_cast();
}
}
private:
template<typename T>
T value();
};
template <typename T>
class type_holder : public holder
{
public:
type_holder(T * t) : t_(t) {}
~type_holder() { delete t_; }
template<typename TypeValue>
TypeValue value() const { return *t_; }
private:
T * t_;
};