首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言通用數據庫接口設計:基於 SQLite 的類 ORM 映射實戰

     編輯:關於C語言

C 語言缺乏反射與泛型,但通過「結構體↔表結構映射」+「宏包裝 SQL」+「回調函數綁定」,可構建輕量級 ORM 風格接口。本文展示一套基於 SQLite 的完整實現:插入/刪除/更新統一入口、查詢自動映射到結構體鏈表、表名與綁定函數註冊機制。

核心設計思想

  1. 結構體即 Schema:每張表對應一個結構體,字段名與列名一致。
  2. 宏封裝 SQL 模板:INSERT_INTO_(table, cols, vals) 等宏生成參數化 SQL。
  3. 統一執行入口:sql_exec 處理增刪改,select_table 處理查詢映射。
  4. 回調綁定:每表註冊 void* (*row_mapper)(sqlite3_stmt*) 將行轉結構體。
  5. 關鍵數據結構

    // 業務實體
    typedef struct User {
        int id;
        char *name;
        char *password;
    } User;
    
    // 查詢結果鏈表節點
    typedef struct SelectNode {
        void *value;              // 指向具體結構體 (User*)
        struct SelectNode *next;
    } SelectNode;
    
    // 釋放回調
    typedef void (*ValueFreeFunc)(void *value);

    通用執行器:增刪改

    int sql_exec(const char *fmt, ...) {
        char sql[1024];
        va_list ap; va_start(ap, fmt);
        vsnprintf(sql, sizeof(sql), fmt, ap); va_end(ap);
        
        char *err = NULL;
        int rc = sqlite3_exec(g_db, sql, NULL, NULL, &err);
        if (rc != SQLITE_OK) {
            fprintf(stderr, "SQL錯誤: %s\nSQL: %s\n", err, sql);
            sqlite3_free(err);
            return SQLITE_ERROR;
        }
        return SQLITE_OK;
    }
    
    // 宏定義 (## 為連接符)
    #define SELECT_(cols, table, where)  "SELECT " #cols " FROM " #table " WHERE " #where
    #define INSERT_INTO_(table, cols, vals) "INSERT INTO " #table " (" #cols ") VALUES (" #vals ")"
    #define UPDATE_(table, set, where) "UPDATE " #table " SET " #set " WHERE " #where
    #define DELETE_FROM_(table, where) "DELETE FROM " #table " WHERE " #where
    
    // 使用示例
    User u = {0, "alice", "secret"};
    sql_exec(INSERT_INTO_(User, id,name,password, "%d,'%s','%s'"), u.id, u.name, u.password);

    查詢映射核心:select_table

    // 註冊表名→映射函數 (簡化版用 strcmp,生產建議哈希表)
    void *map_user_row(sqlite3_stmt *stmt) {
        User *u = calloc(1, sizeof(User));
        int cols = sqlite3_column_count(stmt);
        for (int i = 0; i < cols; ++i) {
            const char *col = sqlite3_column_name(stmt, i);
            if (strcmp(col, "id") == 0) u->id = sqlite3_column_int(stmt, i);
            else if (strcmp(col, "name") == 0) u->name = strdup((char*)sqlite3_column_text(stmt, i));
            else if (strcmp(col, "password") == 0) u->password = strdup((char*)sqlite3_column_text(stmt, i));
        }
        return u;
    }
    
    int select_table(void **out_head, const char *fmt, ...) {
        char sql[1024];
        va_list ap; va_start(ap, fmt);
        vsnprintf(sql, sizeof(sql), fmt, ap); va_end(ap);
        
        sqlite3_stmt *stmt;
        if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
        
        SelectNode *head = NULL, *tail = NULL;
        int count = 0;
        
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            // 獲取表名決定映射函數
            const char *tbl = sqlite3_column_table_name(stmt, 0);
            void *obj = NULL;
            if (tbl && strcmp(tbl, "User") == 0) obj = map_user_row(stmt);
            // else if ... 其他表
            
            if (obj) {
                SelectNode *node = malloc(sizeof(SelectNode));
                node->value = obj; node->next = NULL;
                if (!head) head = tail = node;
                else { tail->next = node; tail = node; }
                count++;
            }
        }
        sqlite3_finalize(stmt);
        *out_head = head;
        return count;
    }
    
    // 使用
    SelectNode *list = NULL;
    select_table((void**)&list, SELECT_(id, User, "name='alice' AND password='secret'"));
    for (SelectNode *n = list; n; n = n->next) {
        User *u = n->value;
        printf("%d %s %s\n", u->id, u->name, u->password);
    }
    // 釋放
    for (SelectNode *n = list, *next; n; n = next) {
        next = n->next;
        free(n->value->name); free(n->value->password); free(n->value); free(n);
    }

    聚合查詢特殊處理

    // COUNT(*) 等無表名列,直接取整數
    int total = 0;
    select_table((void**)&total, SELECT_(count(*), User, "1=1"));
    // select_table 內部識別無表名時,按整數列處理並返回給 int*

    擴展:哈希表註冊映射器 (生產級)

    typedef void* (*RowMapper)(sqlite3_stmt*);
    
    typedef struct { char *table; RowMapper mapper; } MapperEntry;
    MapperEntry g_mappers[] = {
        {"User", map_user_row},
        {"Order", map_order_row},
        {NULL, NULL}
    };
    
    RowMapper get_mapper(const char *table) {
        for (int i = 0; g_mappers[i].table; ++i)
            if (strcmp(g_mappers[i].table, table) == 0) return g_mappers[i].mapper;
        return NULL;
    }

    優缺點與演進方向

    優點局限演進
    零依賴、極輕量、適合嵌入式無遷移工具、Schema 變更需手動同步結構體引入代碼生成器 (Python 解析 .h 生成映射函數)
    SQL 完全可控、性能可預測複雜 JOIN/子查詢映射困難支援 View 結構體、嵌套映射回調
    編譯期類型檢查 (宏參數)宏參數易誤用、調試困難C11 _Generic 封裝類型安全包裝器

    這套方案證明了:C 語言雖無反射,但通過「約定優於配置」+「宏元編程」+「函數指針多態」,完全能構建工程可用的數據訪問層。關鍵在於建立並堅守「結構體字段 ≡ 表列」的映射契約。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved