非侵入C++ ORM

最近工作中需要将一些信息保存到数据库,由于使用的是C++,没有反射机制,所以对应的ORM库非常少,手写sql又非常麻烦,这个时候非常羡慕java,C#等语言可以在编译的时候就将字段信息等编译到可执行文件中,因此可以非常轻松的实现反射,也有非常多好用成熟的ORM库。

本文主要是利用C++的宏和模板来实现非侵入式简单的C++ORM的功能(主要是针对sqlite数据库)

(1)要利用C++实现反射,最主要的是可以获取C++结构体各个字段的字段名,字段类型。我在网上找到一段如下的宏可以很好的解决这个问题。

#define DEFINE_STRUCT_SCHEMA(Struct, ...)          \
template <>                                        \
inline auto StructSchema<Struct>()                 \
{                                                  \
   using _Struct = Struct;                         \
   return std::make_tuple(__VA_ARGS__);            \
}                                                  \
template<>                                         \
inline std::string GetTableName<Struct>()          \
{                                                  \
   return #Struct;                                 \
}

#define DEFINE_STRUCT_FIELD(StructField)           \
std::make_tuple(                                   \
(int)(&(((_Struct*)0 )->StructField)),             \
#StructField,                                      \
&_Struct::StructField)

这个宏示例如下:

struct SimpleStruct 
{
   int m_id;
   int m_iAge;
   double m_dHeight;
   std::string m_strName;
};

DEFINE_STRUCT_SCHEMA
(
SimpleStruct,
DEFINE_STRUCT_FIELD(m_id),
DEFINE_STRUCT_FIELD(m_iAge),
DEFINE_STRUCT_FIELD(m_dHeight),
DEFINE_STRUCT_FIELD(m_strName)
);

我们可以发现这个是是将各个字段的信息(各个成员变量的的地址偏移,字段名,成员指针)保存在tuple之中,因为结构体的每个成员变量的地址都是不同的数据类型,用tuple去保存是一个合理的选择。

(2)由于信息都是保存在tuple之中,我们还需要提供遍历各个字段信息的方式。代码如下:

namespace detail 
{
   template <typename Fn, typename Tuple, std::size_t... I>
   inline void ForEachTuple(Tuple&& tuple,Fn&& fn,std::index_sequence<I...>) 
   {
      using Expander = int[];
      (void)Expander {0, ((void)fn(I,std::get<I>(std::forward<Tuple>(tuple))), 0)...};
   }

   template <typename Fn, typename Tuple>
   inline void ForEachTuple(Tuple&& tuple, Fn&& fn) 
   {
      ForEachTuple(std::forward<Tuple>(tuple), std::forward<Fn>(fn),
         std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
   }

}  // namespace detail

其实本质就是对可变参数模板的展开。然后在展开的过程中对每个字段信息调用Fn函数进行处理。

(3)有了以上两个基础机制,我们现在可以很容易的获取各个字段的信息了。首先我们完成两个函数:

*获取各个字段的字段名,类型。我们将信息保存到一个vector中;

*通过成员变量地址获取指定字段信息

代码如下:

enum class ValueType
{
   UNDEFINE = 0,
   INTEGER = 1,
   BOOL,
   REAL,
   TEXT,
   BLOB
};

struct FieldInfo
{
public:
   FieldInfo(const std::string& strFieldName, ValueType valueType, int offset) :
      m_strFieldName(strFieldName),
      m_valueType(valueType),
      m_iOffset(offset)
   {}

public:
   std::string m_strFieldName;
   ValueType m_valueType;
   int m_iOffset;
};

//绑定浮点型
template <typename Entity, typename Field>
constexpr T_IF_REAL(Field, ValueType) GetFieldType(Field Entity::*)
{
   return ValueType::REAL;
}

//绑定整型
template <typename Entity, typename Field>
constexpr T_IF_INT(Field,ValueType) GetFieldType(Field Entity::*)
{
   return ValueType::INTEGER;
}

//绑定布尔型
template <typename Entity, typename Field>
constexpr T_IF_BOOL(Field, ValueType) GetFieldType(Field Entity::*)
{
   return ValueType::BOOL;
}

//绑定字符串
template <typename Entity, typename Field>
constexpr T_IF_TEXT(Field, ValueType) GetFieldType(Field Entity::*)
{
   return ValueType::TEXT;
}

//绑定浮点型
template <typename Entity, typename Field,typename Entity2,typename Field2>
typename std::enable_if<std::is_same<Field Entity::*, Field2 Entity2::*>::value,bool>::type
CmpFieldAdd(Field Entity::*p1,Field2 Entity2::* p2, ValueType& valueType)
{
   if (p1 == p2)
   {
      valueType = GetFieldType(p2);
      return true;
   }

   return false;
}

template <typename Entity, typename Field, typename Entity2, typename Field2>
constexpr typename std::enable_if<!std::is_same<Field Entity::*, Field2 Entity2::*>::value, bool>::type
CmpFieldAdd(Field Entity::*p1, Field2 Entity2::* p2,ValueType& valueType)
{
   return false;
}

template <typename Entity>
inline std::vector<FieldInfo> GetStructFieldInfos()
{
   auto struct_schema = StructSchema<std::decay_t<Entity>>();

   std::vector<FieldInfo> vecFieldInfo;

   detail::ForEachTuple(struct_schema, [&](size_t index, auto&& field_schema)
      {
         using FieldSchema = std::decay_t<decltype(field_schema)>;
         
         int offset = std::get<0>(std::forward<decltype(field_schema)>(field_schema));
         std::string fieldName = std::get<1>(std::forward<decltype(field_schema)>(field_schema));
         ValueType valueType = GetFieldType(std::get<2>(std::forward<decltype(field_schema)>(field_schema)));
         vecFieldInfo.emplace_back(fieldName, valueType, offset);
      });

   return vecFieldInfo;
}

template<typename Entity,typename Field> 
inline FieldInfo GetStructFieldInfo(Field Entity::* pp)
{
   auto struct_schema = StructSchema<std::decay_t<Entity>>();

   FieldInfo fieldInfo("", ValueType::UNDEFINE,0);

   detail::ForEachTuple(struct_schema, [&](size_t index, auto&& field_schema)
   {
      using FieldSchema = std::decay_t<decltype(field_schema)>;
      auto fieldPtr = std::get<2>(std::forward<decltype(field_schema)>(field_schema));
      ValueType valueType = ValueType::UNDEFINE;
      if (CmpFieldAdd(pp, fieldPtr, valueType))
      {
         fieldInfo.m_iOffset = std::get<0>(std::forward<decltype(field_schema)>(field_schema));
         fieldInfo.m_strFieldName = std::get<1>(std::forward<decltype(field_schema)>(field_schema));
         fieldInfo.m_valueType = valueType;
      }
   });

   return fieldInfo;
}

我将结构体字段分为了几大类,与数据库里字段对应。

以上代码用到了类似T_IF_INT这种宏,定义如下:

#define T_IF_INT(Field,T)                                                        \
typename std::enable_if<                                                         \
   (std::is_enum<std::decay_t<Field>>::value                                     \
   || std::is_integral<std::decay_t<Field>>::value) &&                           \
   !std::is_same<std::decay_t<Field>, long long>::value &&                       \
   !std::is_same<std::decay_t<Field>, unsigned long>::value &&                   \
   !std::is_same<std::decay_t<Field>, bool>::value &&                            \
   !std::is_same<std::decay_t<Field>, char>::value &&                            \
   !std::is_same<std::decay_t<Field>, wchar_t>::value &&                         \
   !std::is_same<std::decay_t<Field>, char16_t>::value &&                        \
   !std::is_same<std::decay_t<Field>, char32_t>::value &&                        \
   !std::is_same<std::decay_t<Field>, unsigned char>::value,                     \
   T>::type

#define T_IF_REAL(Field,T)                                                       \
typename std::enable_if<std::is_floating_point<std::decay_t<Field>>::value, T>::type

#define T_IF_BOOL(Field,T)                                                       \
typename std::enable_if<std::is_same<std::decay_t<Field>,bool>::value, T>::type

#define T_IF_TEXT(Field,T)                                                       \
typename std::enable_if<std::is_same<std::decay_t<Field>, std::string>::value, T>::type

这些宏主要是采用了SFINAE机制,这些宏在下面的绑定参数和设置各个成员的值也有用到,所以定义成了宏。

(4)有了以上函数,我们首先可以完成创建数据库表的功能。

但是创建数据库表还需要对字段有约束。因此定义一个结构体表达字段的约束信息,如下:

#define PK        0x0001
#define NOT_NULL  0x0010
#define UNQUE     0x0100

struct FieldContraint
{
   public:
   template<typename Entity,typename Field>
   FieldContraint(Field Entity::* pp, int contraintType)
   {
      FieldInfo fieldInfo = GetStructFieldInfo(pp);

      m_strFieldName = fieldInfo.m_strFieldName;

      if (contraintType&PK)
      {
         m_strContraint.append(" PRIMARY KEY");
      }

      if (contraintType&NOT_NULL)
      {
         m_strContraint.append(" NOT NULL");
      }

      if (contraintType&UNQUE)
      {
         m_strContraint.append(" UNIQUE");
      }
   }

   std::string m_strFieldName;
   std::string m_strContraint;
};

目前只做了主键,唯一约束,非空约束。

有了以上信息就可以生成完整的create sql语句了。

(2)插入,更新sql语句我们采用预编译来完成,其中有两部分:一个是预编译语句,这个用以上涉及的函数就可以完成,还有一个是参数绑定。对于查询语句,主要是将数据库各个列的值赋值给结构体,因此成为字段的绑定与获取,主要代码如下:

template <typename Entity, typename Fn>
inline void TravFieldValue(sqlite3* pDb, sqlite3_stmt* m_statement, Entity&& entity, Fn&& fn)
{
   auto struct_schema = StructSchema<std::decay_t<Entity>>();

   detail::ForEachTuple(struct_schema, [&](size_t index, auto&& field_schema)
   {
      using FieldSchema = std::decay_t<decltype(field_schema)>;
      fn(pDb,m_statement, entity.*std::get<2>(std::forward<decltype(field_schema)>(field_schema)),
         (int)index);
   });
}

struct SetFieldValueFn
{
   void CheckColumnDataType(sqlite3_stmt* m_statement, int index, int idataType)
   {
      int coltype = sqlite3_column_type(m_statement, index);
      if(coltype != idataType)
         throw std::runtime_error("date type is not match");
   }

   //获取浮点型成员变量
   template <typename Field>
   T_IF_REAL(Field, void)
      SetFieldValue(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      CheckColumnDataType(m_statement,index,SQLITE_FLOAT);
      fieldValue = (std::decay_t<Field>)sqlite3_column_double(m_statement, index);
   }

   //获取整型成员变量
   template <typename Field>
   T_IF_INT(Field, void)
      SetFieldValue(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      CheckColumnDataType(m_statement, index, SQLITE_INTEGER);
      fieldValue = (std::decay_t<Field>)sqlite3_column_int(m_statement, index);
   }

   //获取布尔型成员变量
   template <typename Field>
   T_IF_BOOL(Field, void)
      SetFieldValue(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      CheckColumnDataType(m_statement, index, SQLITE_INTEGER);
      fieldValue = (std::decay_t<Field>)sqlite3_column_int(m_statement, index);
   }

   //获取字符串成员变量
   template <typename Field>
   T_IF_TEXT(Field, void)
      SetFieldValue(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      CheckColumnDataType(m_statement, index, SQLITE_TEXT);
      const char* valuePtr = (const char*)sqlite3_column_text(m_statement, index);
      ConvertUtf8StringToString(valuePtr,fieldValue);
   }

   template<typename Field> void
      operator()(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      SetFieldValue(pDb, m_statement, fieldValue, index);
   }
};

template<typename Entity>
void SetFieldVal(sqlite3* pDb, sqlite3_stmt* m_statement, Entity&& entity)
{
   SetFieldValueFn setFieldValueFn;
   TravFieldValue(pDb, m_statement, entity, setFieldValueFn);
}

struct BindFieldValueFn
{
   //绑定浮点型
   template <typename Field>
   T_IF_REAL(Field, void)
      BindParam(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      if (sqlite3_bind_double(m_statement, index + 1, (double)fieldValue) != SQLITE_OK)
         throw std::runtime_error(std::string("SQL error:bind param failed!'") + sqlite3_errmsg(pDb));
   }

   //绑定整型
   template <typename Field>
   T_IF_INT(Field, void)
      BindParam(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      if (sqlite3_bind_int(m_statement, index + 1, (int)fieldValue) != SQLITE_OK)
         throw std::runtime_error(std::string("SQL error:bind param failed!'") + sqlite3_errmsg(pDb));
   }

   //绑定布尔型
   template <typename Field>
   T_IF_BOOL(Field, void)
      BindParam(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      if (sqlite3_bind_int(m_statement, index + 1, fieldValue ? 1 : 0) != SQLITE_OK)
         throw std::runtime_error(std::string("SQL error:bind param failed!'") + sqlite3_errmsg(pDb));
   }

   //绑定字符串
   template <typename Field>
   T_IF_TEXT(Field, void)
      BindParam(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      std::string strUtf8Str;
      ConvertStringToUTF8String(fieldValue,strUtf8Str);
      if (sqlite3_bind_text(m_statement, index + 1, strUtf8Str.c_str(), -1, SQLITE_TRANSIENT))
         throw std::runtime_error(std::string("SQL error:bind param failed!'") + sqlite3_errmsg(pDb));
   }

   template<typename Field> void
      operator()(sqlite3* pDb, sqlite3_stmt* m_statement, Field&& fieldValue, int index)
   {
      BindParam(pDb,m_statement,fieldValue,index);
   }
};

template<typename Entity>
void BindFieldVal(sqlite3* pDb, sqlite3_stmt* m_statement, Entity&& entity)
{
   BindFieldValueFn bindFieldFn;
   TravFieldValue(pDb, m_statement, entity, bindFieldFn);
}

(5)对于查询信息还需要有查询条件,我们也用一个结构体进行表达,如下:

enum class CondtionType
{
   GREATER,
   LESS,
   EQ,
   LIKE,
   NOT_IN,
   IN,
};

struct Condition
{
   public:
      template<class Entity,class Field>
      Condition(Field Entity::* fieldPtr, CondtionType conType, const std::string& value)
      {
         static FieldInfo fieldInfo = GetStructFieldInfo(fieldPtr);

         if (fieldInfo.m_valueType == ValueType::TEXT)
         {
            strCondition = fieldInfo.m_strFieldName + GetStrByConType(conType) +"'"
            + value +"'";
         }
         else
         {
            strCondition = fieldInfo.m_strFieldName + GetStrByConType(conType) + value;
         }
      }

      Condition operator &&(const Condition& con)
      {
         return Condition(strCondition + " AND " + con.strCondition);
      }

      Condition operator||(const Condition& con)
      {
         return Condition(strCondition + " OR " + con.strCondition);
      }

   std::string strCondition;

   private:
      Condition(const std::string& str) :strCondition(str) {}
      std::string GetStrByConType(CondtionType conType)
      {
         switch (conType)
         {
         case CondtionType::GREATER:
            return " > ";
         case CondtionType::LESS:
            return " < ";
         case CondtionType::EQ:
            return " = ";
         case CondtionType::LIKE:
            return " like ";
         case CondtionType::NOT_IN:
            return " NOT IN ";
         case CondtionType::IN:
            return " IN ";
         default:
            return " ";
         }

         return " ";
      }
};

目前仅仅支持了大于,小于,等于,like等。

(5)基础函数都完成了,接下来完成SqliteDb类,如下:

class SqliteDB
{
public:
   SqliteDB()
   {
      
   }

   ~SqliteDB()
   {
      close();
   }

   void Open(const std::string& fileName)
   {
      std::string utf8FileName;
      ConvertStringToUTF8String(fileName, utf8FileName);

      if (sqlite3_open(utf8FileName.c_str(), &db) != SQLITE_OK)
      {
         close();
         throw std::runtime_error(std::string("SQL error: Can't open database '") + sqlite3_errmsg(db) + "'");
      }
      m_statement = nullptr;
   }

   void BeginTrans()
   {
      Execute("begin transaction;");
   }

   void Commit()
   {
      Execute("commit transaction;");
   }

   template<class T>
   void CreateTableIfNotExist(const std::vector<FieldContraint>& vecFieldContraint)
   {
      std::string&& strUtf8CreateSql = GenUtf8CreateSql<std::decay_t<T>>(vecFieldContraint);
      Execute(strUtf8CreateSql);
   }

   template<class T>
   void Insert(T&& t)
   {
      static std::string&& strUtf8PreInsertSql = GenUtf8InsertSql<std::decay_t<T>>();

      Prepare(strUtf8PreInsertSql);
      BindFieldVal(db,m_statement,t);
      ExecuteParparseSQL();
   }

   template<typename T, typename Out>
   void Quary(Out& out)
   {
      static std::string&& strUtf8SelectSql = GenUtf8SelectSql<std::decay_t<T>>();

      Prepare(strUtf8SelectSql);

      GetT<T,Out>(out);
   }

   template<typename T,typename Out>
   void Quary(const Condition& condi, Out& out)
   {
      std::string&& strUtf8SelectSql = GenUtf8SelectSql<std::decay_t<T>>(condi);
      Prepare(strUtf8SelectSql);

      GetT<T, Out>(out);
   }

   template<typename T>
   void Delete(const Condition& condi)
   {
      std::string strUtf8DelSql = GenUtf8DeleteSql<T>(condi);
      Execute(strUtf8DelSql);
   }

   template<typename Entity,typename Field>
   void Update(Entity&& t,Field std::decay_t<Entity>::* p)
   {
      static std::string&& strUtf8UpdatePreSql = GenUtf8UpdatePreSql(t,p);
      Prepare(strUtf8UpdatePreSql);
      BindFieldVal(db, m_statement, t);
      ExecuteParparseSQL();
   }

   void Prepare(const std::string& sql)
   {
      m_code = sqlite3_prepare_v2(db, sql.data(), -1, &m_statement, nullptr);

      if (m_code != SQLITE_OK)
      {
         throw std::runtime_error(std::string("SQL error: Prepare failed! '") + sqlite3_errmsg(db) + "'");
      }
   }

   void ExecuteParparseSQL()
   {
      m_code = sqlite3_step(m_statement);
      sqlite3_reset(m_statement);

      if (m_code != SQLITE_DONE)
      {
         throw std::runtime_error(
            std::string("SQL error: execute PrepareSQL failed! '") + sqlite3_errmsg(db) + "'");
      }
   }

   void Execute(const std::string& cmd)
   {
      char* zErrMsg = NULL;
      int rc = SQLITE_OK;

      for (size_t iTry = 0; iTry < MAX_TRIAL; iTry++)
      {
         rc = sqlite3_exec(db, cmd.c_str(), 0, 0, &zErrMsg);

         if (rc != SQLITE_BUSY)
            break;

         std::this_thread::sleep_for(std::chrono::microseconds(20));
      }

      if (rc != SQLITE_OK)
      {
         auto errStr = std::string("SQL error: '") + zErrMsg + "' at '" + cmd + "'";
         sqlite3_free(zErrMsg);
         throw std::runtime_error(errStr);
      }
   }


   int GetColumnIntValue(int iColumnIdx)
   {
      CheckColumnValueType(iColumnIdx, SQLITE_INTEGER);

      return sqlite3_column_int(m_statement,iColumnIdx);
   }

   double GetColumnDoubleValue(int iColumnIdx)
   {
      CheckColumnValueType(iColumnIdx, SQLITE_FLOAT);

      return sqlite3_column_double(m_statement, iColumnIdx);
   }

   const char* GetColumnText(int iColumnIdx)
   {
      CheckColumnValueType(iColumnIdx, SQLITE_TEXT);

      return (const char*)sqlite3_column_text(m_statement, iColumnIdx);
   }
private:
   template<typename T>
   std::string GenUtf8CreateSql(const std::vector<FieldContraint>& vecFieldContraint)
   {
      std::stringstream os;
      static std::vector<FieldInfo> vecFileInfo = GetStructFieldInfos<std::decay_t<T>>();
      static std::string strTableName = GetTableName<std::decay_t<T>>();

      os << "CREATE TABLE IF NOT EXISTS "<<strTableName<<"(";

      for (int i = 0; i < (int)vecFileInfo.size(); i++)
      {
         auto& fieldInfo = vecFileInfo.at(i);

         auto iter = std::find_if(vecFieldContraint.begin(),vecFieldContraint.end(),
         [&](const FieldContraint& fieldContraint)
         {
            if(fieldContraint.m_strFieldName == fieldInfo.m_strFieldName)
               return true;
            return false;
         });

         std::string strContraint;
         if (iter != vecFieldContraint.end())
         {
            strContraint.append(iter->m_strContraint);
         }
         strContraint.append(",");

         os << fieldInfo.m_strFieldName;
         if (fieldInfo.m_valueType == ValueType::INTEGER || fieldInfo.m_valueType == ValueType::BOOL)
         {
            os << " INT"<< strContraint;
         }
         else if (fieldInfo.m_valueType == ValueType::REAL)
         {
            os << " REAL" << strContraint;
         }
         else if (fieldInfo.m_valueType == ValueType::TEXT)
         {
            os <<" TEXT" << strContraint;
         }
      }

      os.seekp(os.tellp() - std::streamoff(1));
      os << ")";

      std::string&& strCreateSql = os.str();
      std::string strUtf8CreateSql;
      ConvertStringToUTF8String(strCreateSql, strUtf8CreateSql);
      return strUtf8CreateSql;
   }

   template<typename T>
   std::string GenUtf8InsertSql()
   {
      std::stringstream os;
      std::stringstream osVal;
      static std::vector<FieldInfo> vecFileInfo = GetStructFieldInfos<std::decay_t<T>>();
      static std::string strTableName = GetTableName<std::decay_t<T>>();

      os<<"INSERT INTO " <<strTableName << " (";

      for (int i = 0; i < (int)vecFileInfo.size(); i++)
      {
         os << vecFileInfo.at(i).m_strFieldName<<",";
         osVal<<"?,";
      }

      os.seekp(os.tellp() - std::streamoff(1));
      osVal.seekp(osVal.tellp() - std::streamoff(1));

      osVal << ");";  // To Enable Seekp...
      os << ") VALUES (" << osVal.str();

      std::string&& strInsertSql = os.str();
      std::string strUtf8InsertSql;
      ConvertStringToUTF8String(strInsertSql, strUtf8InsertSql);
      return strUtf8InsertSql;
   }

   template<typename T>
   std::string GenUtf8SelectSql()
   {
      std::stringstream os;
      static std::vector<FieldInfo> vecFileInfo = GetStructFieldInfos<std::decay_t<T>>();
      static std::string strTableName = GetTableName<std::decay_t<T>>();

      os << "SELECT " ;

      for (int i = 0; i < (int)vecFileInfo.size(); i++)
      {
         os << vecFileInfo.at(i).m_strFieldName << ",";
      }

      os.seekp(os.tellp() - std::streamoff(1));

      os << " FROM "<<strTableName;

      std::string&& strSelectSql = os.str();
      std::string strUtf8SelectSql;
      ConvertStringToUTF8String(strSelectSql, strUtf8SelectSql);
      return strUtf8SelectSql;
   }

   template<typename T>
   std::string GenUtf8SelectSql(const Condition& codin)
   {
      std::string strWhere(" Where ");
      strWhere.append(codin.strCondition);

      std::string strUtf8Where;
      ConvertStringToUTF8String(strWhere, strUtf8Where);

      std::string strSelectSql = GenUtf8SelectSql<T>();

      return strSelectSql.append(strUtf8Where);
   }

   template<typename T>
   std::string GenUtf8DeleteSql(const Condition& codin)
   {
      std::string strDelSql = "Delete FROM ";
      strDelSql.append(GetTableName<std::decay_t<T>>());
      strDelSql.append(" WHERE ");
      strDelSql.append(codin.strCondition);

      std::string strUtf8DelSql;
      ConvertStringToUTF8String(strDelSql, strUtf8DelSql);
      return strUtf8DelSql;
   }

   template<typename T,typename Field>
   std::string GenUtf8UpdatePreSql(T&& t, Field std::decay_t<T>::* pField)
   {
      std::stringstream os;
      os << "UPDATE ";
      os << GetTableName <std::decay_t<T>>();
      os << " SET ";

      static std::vector<FieldInfo> vecFileInfo = GetStructFieldInfos<std::decay_t<T>>();
      for (int i = 0; i < (int)vecFileInfo.size(); i++)
      {
         FieldInfo& fieldInfo = vecFileInfo.at(i);
         os << fieldInfo.m_strFieldName;
         os << "=?,";
      }

      os.seekp(os.tellp() - std::streamoff(1));
      os << " WHERE ";

      FieldInfo fieldInfo = GetStructFieldInfo(pField);
      os << fieldInfo.m_strFieldName<<"=";

      auto& fieldValue = t.*pField;

      os << fieldValue <<";";

      std::string strUtf8UpdateSql;
      ConvertStringToUTF8String(os.str(), strUtf8UpdateSql);
      return strUtf8UpdateSql;
   }

   template<typename T,typename Out>
   void GetT(Out& out)
   {
      do
      {
         int r = sqlite3_step(m_statement);
         if (r == SQLITE_DONE)
            break;

         if (r != SQLITE_ROW)
            break;

         T entity;
         out.push_back(entity);
         auto it = out.end();
         it--;
         T& entityR = *it;

         SetFieldVal(db, m_statement, entityR);
      } while (true);
   }
private:
   void CheckColumnValueType(int iColumnIdx, int iDataType)
   {
      int coltype = sqlite3_column_type(m_statement, iColumnIdx);
      if (coltype != iDataType)
         throw std::runtime_error("date type is not match:wstring");
   }

   int CloseDBHandle()
   {
      int code = sqlite3_close(db);
      while (code == SQLITE_BUSY)
      {
         code = SQLITE_OK;
         sqlite3_stmt* stmt = sqlite3_next_stmt(db,NULL);

         if(stmt == nullptr)
            break;

         code = sqlite3_finalize(stmt);

         if(code == SQLITE_OK)
            code = sqlite3_close(db);
      }

      return code;
   }

   bool close()
   {
      if(db == nullptr)
         return true;

      if(m_statement != nullptr)
         sqlite3_finalize(m_statement);

      m_code = CloseDBHandle();

      bool ret = (SQLITE_OK == m_code);
      m_statement = nullptr;
      db = nullptr;
      return ret;
   }

private:
   SqliteDB(const SqliteDB&) = delete;
   SqliteDB& operator=(const SqliteDB&) = delete;

private:
   sqlite3* db = nullptr;
   const static size_t MAX_TRIAL = 16;
   sqlite3_stmt* m_statement = nullptr;
   int m_code;
};

(6)由于sqlite相关函数只接收utf8格式的字符串,因此还需要提供几个转换函数:

void ConvertWstringToUTF8String(const std::wstring& wstr, std::string& str)
{
   int len = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), -1, nullptr, 0, nullptr, nullptr);
   char* strPtr = new char[len + 1];
   memset(strPtr, 0, len + 1);
   WideCharToMultiByte(CP_UTF8, 0, wstr.data(), -1, strPtr, len, nullptr, nullptr);
   str = strPtr;
   delete[] strPtr;
}

//将string转换为wstring
void ConvertUTF8StringToWstring(std::string& str, std::wstring wstr)
{
   //将UTF-8转换为wstring
   int len = MultiByteToWideChar(CP_UTF8, NULL, str.data(), str.size(), NULL, 0);

   wchar_t* wszString = new wchar_t[len + 1];

   MultiByteToWideChar(CP_UTF8, NULL, str.data(), str.size(), wszString, len);
   wszString[len] = L'\0';
   wstr = wszString;
   delete[] wszString;
}

void ConvertUTF8StringToWstring(const char*& strPtr, std::wstring& wstr)
{
   //将UTF-8转换为wstring
   int len = MultiByteToWideChar(CP_UTF8, NULL, strPtr, -1, NULL, 0);

   wchar_t* wszString = new wchar_t[len + 1];

   MultiByteToWideChar(CP_UTF8, NULL, strPtr, -1, wszString, len);
   wszString[len] = L'\0';
   wstr = wszString;
   delete[] wszString;
}

void ConvertStringToUTF8String(const std::string& str, std::string& utf8Str)
{
   int nwLen = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
   wchar_t* pwBuf = new wchar_t[nwLen + 1];//一定要加1,不然会出现尾巴
   ZeroMemory(pwBuf, nwLen * 2 + 2);
   ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), pwBuf, nwLen);
   int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
   char* pBuf = new char[nLen + 1];
   ZeroMemory(pBuf, nLen + 1);
   ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
   utf8Str = pBuf;
   delete[]pwBuf;
   delete[]pBuf;
   pwBuf = NULL;
   pBuf = NULL;
}

void ConvertUtf8StringToString(const std::string& utf8Str, std::string& str)
{
   int nwLen = ::MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, NULL, 0);
   wchar_t* pwBuf = new wchar_t[nwLen + 1];//一定要加1,不然会出现尾巴
   ZeroMemory(pwBuf, nwLen * 2 + 2);
   ::MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), utf8Str.length(), pwBuf, nwLen);
   int nLen = ::WideCharToMultiByte(CP_ACP, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
   char* pBuf = new char[nLen + 1];
   ZeroMemory(pBuf, nLen + 1);
   ::WideCharToMultiByte(CP_ACP, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
   str = pBuf;
   delete[]pwBuf;
   delete[]pBuf;
   pwBuf = NULL;
   pBuf = NULL;
}

(7)至此,一个简单的非侵入的C++ ORM就完成了,看下测试代码:

int main()
{
   std::string strDbPath = "D:\\test.db";
   SqliteDB db;
   db.Open(strDbPath);

   db.BeginTrans();

   FieldContraint fielContraint(&SimpleStruct::m_id,PK|NOT_NULL| UNQUE);
   db.CreateTableIfNotExist<SimpleStruct>({ fielContraint });

   for (int i = 0; i < 100; i++)
   {
      SimpleStruct ss1 = { i,i,(double)i,"哈哈"};
      db.Insert(ss1);
   }

   Condition condit1 = {&SimpleStruct::m_iAge,CondtionType::GREATER,"10"};
   Condition condit2 = { &SimpleStruct::m_strName,CondtionType::LIKE,"%ABC%" };
   
   std::vector<SimpleStruct> vecSimpleStruct;
   db.Quary<SimpleStruct>(condit1,vecSimpleStruct);

   db.Delete<SimpleStruct>({ &SimpleStruct::m_id,CondtionType::GREATER,"10" });

   SimpleStruct ss = { 0,1,(double)1,"哈哈" };
   db.Update(ss,&SimpleStruct::m_id);

   db.Commit();
   

   std::cout<<"成功"<<std::endl;
   system("pause");
   return 0;
}

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值