iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
佛心分享-SideProject30

Mongory:打造跨語言、高效能的萬用查詢引擎系列 第 12

Day 11:Value wrapper 與 function pointer dispatch

  • 分享至 

  • xImage
  •  

在 C 裡面動態帶進來一個 void*,根本不知道他是什麼型別
要嘛一路 cast,要嘛寫一堆 if/else 判斷,邏輯又醜又慢
筆者直接把「值的世界」統一起來:用一個 mongory_value 包住所有型別,配上型別標籤(type tag)與一組 function pointer(比較、字串化),matcher 就能用一致語意在 C 裡輕鬆說話

設計目標

  • 一個結構裝下所有可能的值(null/bool/int/double/string/array/table/regex/pointer/unsupported)
  • 每個值都帶型別標籤(mongory_type
  • 透過函式指標進行比較與字串化(compto_str),避免外部灑滿 switch(type)
  • 與 memory pool 整合:建立、字串緩衝、巢狀容器都走 pool->alloc
  • 可橋接:origin 指向原始世界(Ruby/Go)的 VALUE/handle,便於 bridge 做淺包裝

資料結構(摘要)

typedef enum mongory_type {
  MG_T_NULL,
  MG_T_BOOL,
  MG_T_INT,
  MG_T_DOUBLE,
  MG_T_STRING,
  MG_T_ARRAY,
  MG_T_TABLE,
  MG_T_REGEX,
  MG_T_PTR,
  MG_T_UNSUPPORTED
} mongory_type;

typedef struct mongory_value mongory_value;

typedef int  (*mongory_value_compare_func)(mongory_value *self, mongory_value *other);
typedef char *(*mongory_value_to_str_func)(mongory_value *v, mongory_memory_pool *temp_pool); // 字串化使用的暫時性 memory pool

struct mongory_value {
  mongory_memory_pool *pool;     // 生命週期
  mongory_type type;             // 型別標籤
  mongory_value_compare_func comp;   // 比較函式
  mongory_value_to_str_func to_str;  // 字串化
  union {
    bool b;
    int64_t i;
    double d;
    char *s;
    struct mongory_array *a;
    struct mongory_table *t;
    void *regex;
    void *ptr;
    void *u; // unsupported
  } data;
  void *origin; // 原始世界的指標/handle(bridge 用)
};

這個 mongory_value 的關鍵是:外界不需要知道他哪種型別,只要呼叫 v->comp(v, other)v->to_str(v, pool),就能獲得一致行為

包裝 API(wrap functions)

int/double/string 為例,包裝時就把函式指標與資料欄位設好:

mongory_value *mongory_value_wrap_i(mongory_memory_pool *pool, int64_t i) {
  mongory_value *v = pool->alloc(pool, sizeof(mongory_value));
  v->pool = pool;
  v->type = MG_T_INT;
  v->data.i = i;
  v->comp = mongory_value_int_compare;   // 型別專屬 compare
  v->to_str = mongory_value_int_to_str;  // 型別專屬 to_str
  return v;
}

mongory_value *mongory_value_wrap_d(mongory_memory_pool *pool, double d);
mongory_value *mongory_value_wrap_s(mongory_memory_pool *pool, const char *s);
mongory_value *mongory_value_wrap_a(mongory_memory_pool *pool, struct mongory_array *a);
mongory_value *mongory_value_wrap_t(mongory_memory_pool *pool, struct mongory_table *t);

包裝發生在「邊界」:

  • 從 JSON/cJSON 轉進來(測試/benchmark 用)
  • 從 Ruby/Go 進來(bridge 用,會用 shallow/深包裝)
  • 內部建立臨時值(如比較/運算過程)

比較策略(comp

各型別之間的比較在 wrap 階段就被決定
例如:

static inline int mongory_value_int_compare(mongory_value *a, mongory_value *b) {
  if (b->type == MG_T_INT)   return (a->data.i > b->data.i) - (a->data.i < b->data.i);
  if (b->type == MG_T_DOUBLE){
    double ai = (double)a->data.i, bd = b->data.d;
    return (ai > bd) - (ai < bd);
    // 統一 api,大於就回傳 1,小於就回傳 -1,相等則回傳 0
  }
  // 其他型別:視需求決定(通常當作不相等)
  return mongory_value_compare_fail // 另立一個常數代表比對失敗,交給外層處理
}

string/array/table 也都有各自的 compare 策略:

  • string:字典序,必要時先比較長度再逐字
  • array/table:逐元素(或逐鍵)比較,對 matcher 來說更常用的是成員運算(包含/交集)

字串化(to_str

為了方便 debug 與 explain,筆者實作了一個簡單的 string_buffer,專責動態串接字串並與 pool 整合
to_str 就能把任何 mongory_value 變成 char*,輸出給 trace/explain:

typedef struct mongory_string_buffer {
  mongory_memory_pool *pool;
  char *buffer;
  size_t size;
  size_t capacity;
} mongory_string_buffer;

// 省略 grow 與 append 細節

static char *mongory_value_to_str(mongory_value *v, mongory_memory_pool *pool) {
  mongory_string_buffer *sb = mongory_string_buffer_new(pool);
  switch (v->type) {
    case MG_T_INT:    mongory_string_buffer_appendf(sb, "%lld", (long long)v->data.i); break;
    case MG_T_DOUBLE: mongory_string_buffer_appendf(sb, "%g", v->data.d); break;
    case MG_T_STRING: mongory_string_buffer_appendf(sb, "\"%s\"", v->data.s); break;
    // ... 其他型別
  }
  return sb->buffer; // buffer 記憶體受 pool 管理
}

與 matcher 的關係

matcher 只關心「值能比較」與「可以呈現字串」
例如 $gt 只需要 a->comp(a, b) == 1,完全不在乎 a/b 的內部結構
這讓 matcher 的邏輯非常簡潔,也讓新增型別時不用去動到 matcher

與 memory pool 的配合

  • 所有 mongory_value 與內部字串、緩衝都走 pool->alloc
  • to_str 使用的緩衝在另一個 temp pool;一個 explain/trace 階段可以放在一個臨時 pool,最後 reset 一鍵回收
  • 巢狀容器(array/table)也都持有 pool 指標,深層結構不需要手動 free

小結

有了 value wrapper,matcher 就能專注在語意,dispatch 交給函式指標
寫 C 的手感變得像在寫高階語言,幾乎不必煩惱型別錯配,舒服

寫 C 的每一步,要思考的一直都是:如何讓你的下一步更舒服。

Day 12 會把容器打開:Array 與 Table 的設計,為什麼 API 要長得像 Ruby,還有排序與遍歷的細節

專案(C Core)


上一篇
Day 10:Memory pool 設計:chunk 倍增與 O(log n) 擴容
下一篇
Day 12:動態 Array 設計:Ruby 風格 API 與介面設計
系列文
Mongory:打造跨語言、高效能的萬用查詢引擎26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言