从0.25搓一个ORM

发布于:1752168377923 | 修改于:1756439337435 | 分类: 网站的诞生

# 我为什么要ORM 其实手搓sql也很好,完结撒花ヾ(≧▽≦*)o ----- 我在做orm前考虑了很久 首先是要不要做:我查了下知乎,上面说不做会后悔的(那我做qwq 其次是怎么做:我得从头解析sql构建ast再map到不同的方言上... 我查了下知乎,嗷字符串拼接也叫orm啊... > 这部分参考的原代码已经找不到了,但是十分有趣 # 我是怎么搓ORM的 假设我们有几个entity类定义了表 首先是构建几个基础条件:AND OR NOT 等等 把条件链接起来 输出成字符串 给到数据库就好啦! ## 0:反射 工作后我意识到最大的问题就是:花哨的语法只是做题家的玩具,具体的业务才是能力的核心 也就是说我不会去从头构建什么《**世界最强99行动态反射库**》,我会推荐一个我现在喜欢用,并且觉得不错的反射库:[qlibs reflect](https://github.com/qlibs/reflect),语法很简单,功能很强大,最重要的是,单头文件哦 有了这个库,获取类型成员的名字就不是问题啦! ## 1: 条件,语句和sql? 假设有个sql select Cat From Home Where name = feiqi3 那么我们可以把这个语句拆成: 1. 动作: select 2. 目标列:Cat 3. 来源表:Home 4. 条件:name = feiqi3 先来关注条件和目标列: name = feiqi3 可以分为三部分: 字段名称,运算符,参数 定义一下算子:字段 ```cpp template <typename T, typename U> class Field { public: static constexpr Field<T, U> GetField(uint64_t fieldOffset) { T tmp; std::string str; reflect::for_each( [&tmp, fieldOffset, &str](auto i) { bool isMatch = reflect::offset_of<i>(tmp) == fieldOffset; if (isMatch) { str = reflect::member_name<i>(tmp); return; } }, tmp); return Field<T, U>(str, fieldOffset); } constexpr Field(const std::string &view, uint64_t off) : fieldSV(view), offset(off) {} protected: const std::string fieldSV; const uint64_t offset; }; #define FIELD(CLS, MEMBER) \ ::Blog::Field<CLS, decltype(((CLS *)0)->MEMBER)>::GetField( \ offsetof(CLS, MEMBER)) ``` 这里通过offset这个C的内置函数,获取到类成员的偏移,再通过reflect的遍历函数,确定该字段的位置,进而得到名字。 相比直接用宏"#MEMBER"来得到字符串的好处是,用offset函数能享受到编译器的检查哟... 这里的模板参数T储存了当前的实体类(当然也就对应着表) 模板参数U则对应者参数类型 字段可以和一个参数通过比较符号相结合,组合出的结果我叫他为条件 这部分可以通过成员重载运算符达到。 ```cpp //In Field<T, U> constexpr Condition<T> operator==(U &&val) { conditionStaticAssert(); return Condition<T>(fieldSV, " = ", std::forward<U>(val)); } ``` 条件的定义如下 ```cpp template <typename T> class Condition { public: template <StringLike U> constexpr Condition(const std::string &field, const std::string &op, U &&val) { _expr = field + op + safeStr(std::string(val)); } template <NotStringLike U> constexpr Condition(std::string field, const std::string &op, U val) { _expr = field + op + std::to_string(val); } } ``` 第一个参数是字段名称,第二个参数是运算符,也就是 = and or 之类的,第三个则是参数。 对于条件 name = feiqi3 ,在这一步,会被组装为字符串 StringLike和NotStringLike是我自己定义的concept,用来区分字符串和非字符串。 为啥要这么干捏... 防止SQL注入哇!对于字符串类型的参数,我们需要给他做一步转义! 特别特别重要!!!! safeStr里面我调用的是sqlite3的转义函数: ```cpp std::string Blog::safeStr(const std::string &in) { char * str = sqlite3_mprintf("%Q", in.c_str()); std::string ret(str); sqlite3_free(str); return ret; } ``` 如果学我用sqlite3做数据库可以这么玩呀,如果用的其他的...也会有类似的函数滴... 接着关注动作: select 我这里认为一个动作定义了一个sql的性质,所以sql语句和动作应该是强绑定的。 我把这个称为一个查询 query,一个查询负责把 目标列,动作,和条件组合起来 ```cpp template <typename T> class Query { public: constexpr Query() : selectTableName(), whereStr(""), mskip(0), mlimit(0) {} constexpr Query &Select() { op = " SELECT * FROM "; selectTableName = std::string(getTableName()); return *this; } // Use field return type as query type template <class FT> constexpr Query(const Field<FT, T> &queryField) { op = "SELECT " + queryField.toFieldSelect() + " FROM "; } template<class U> constexpr Query &Where(const Condition<U> &whereCondition) { //if is multi-col select condition, no check here if constexpr(is_tuple<T>::value || std::is_integral_v<T> || std::is_floating_point_v<T> || std::is_convertible_v<T, std::string>){ }else{ //if this is a Entity query, do check Entity type. static_assert(std::is_same_v<T, U>,"Field must from the same table as query."); } whereStr = whereCondition.toStr(); return *this; } std::string toSql(){....} private: std::string selectTableName; std::string whereStr; std::string op; } ``` > 这里有个设计失误,我应该写成 select(field){}这样的函数的... 然后写出一个toSql就能组装出一个sql语句啦 其实很简单的对么owo 当然你会发现我这里只支持单表查询..但实际上只要通过扩充condition让他能保存条件的表名,join操作也是能实现的! > 但我没有需求啦... ## 2. 结束咧 好简单欸 就是debug的时候小心点,我写这个东西的时候天天3点睡觉 这就是元编程的魅力! ## 最后 我的博客是如何制作的讲完啦! 完结撒花! <audio controls> <source src="https://pic.feiqi3.cn/media/ImOnABoat.mp3" type="audio/mpeg"> So Hard and it's stupid </audio>