iT邦幫忙

2025 iThome 鐵人賽

DAY 24
0
佛心分享-SideProject30

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

Day 24:Go 型別反射與 data converter 困境:設計邊界

  • 分享至 

  • xImage
  •  

在 Go 版的 Mongory,最棘手的不是 matcher 邏輯,而是「資料如何過橋」。筆者最終採用 shallow/deep/recover 的分工,盡量把反射的成本壓到必要處,讓讀者既能保有 Go 的型別自由,又能以 MongoDB 風格的條件語法快速上手


設計邊界與折衝

  • 讀者面向:
    • 條件以 map[string]any 為主,直覺、對齊 JSON 心智模型
    • 待匹配資料可以是 map[string]any[]any、或自訂型別(由 shallow 包裝延遲取值)
  • 框架面向:
    • deep 只用於「條件」與必要常值,確保 matcher 建構時具體、穩定
    • shallow 用於「資料」取值路徑,減少一次性展開與複製
    • recover 將 C Core 的結果還原為 Go 型別或可觀測字串

這套邊界讓「建立 matcher」與「批次匹配」兩個階段各自最優:建構時花該花的,匹配時保 O(1) 取值與惰性展開


反射成本與約束

  • 為什麼不用型別斷言撐到底?
    • Go 的自訂型別氾濫,靠一連串 x.(T) 難以覆蓋大量實務類型
    • map[string]any[]any 的樹型結構更貼近條件描述,易於轉換
  • 反射的代價:
    • deep 轉換需要遞迴走訪陣列/雜湊,成本與資料大小成正比
    • shallow 只在取值時計算,顯著降低熱路徑上的反射開銷
  • 邊界準則:
    • 條件(condition)走 deep,以換取建構期的確定性
    • 資料(value)走 shallow,以換取匹配期的效率

現況實作:deep for condition,shallow for value

條件 deep 轉換(節錄):

func (m *MemoryPool) ConditionConvert(value any) *Value {
    rv := reflect.ValueOf(value)
    if !rv.IsValid() {
        return NewValueUnsupported(m, value)
    }
    switch rv.Kind() {
    case reflect.Array, reflect.Slice:
        array := NewArray(m)
        for i := 0; i < rv.Len(); i++ {
            array.Push(m.ConditionConvert(rv.Index(i).Interface()))
        }
        return NewValueArray(m, array)
    case reflect.Map:
        table := NewTable(m)
        iter := rv.MapRange()
        for iter.Next() {
            key := iter.Key().String()
            table.Set(key, m.ConditionConvert(iter.Value().Interface()))
        }
        return NewValueTable(m, table)
    case reflect.Ptr:
        return m.ConditionConvert(rv.Elem().Interface())
    default:
        return m.primitiveConvert(value)
    }
}

資料 shallow 轉換(節錄):

func (m *MemoryPool) ValueConvert(value any) *Value {
    rv := reflect.ValueOf(value)
    if !rv.IsValid() {
        return NewValueUnsupported(m, value)
    }
    switch rv.Kind() {
    case reflect.Array, reflect.Slice:
        return NewValueShallowArray(m, NewShallowArray(m, value))
    case reflect.Map:
        return NewValueShallowTable(m, NewShallowTable(m, value))
    case reflect.Ptr:
        return m.ValueConvert(rv.Elem().Interface())
    default:
        return m.primitiveConvert(value)
    }
}

觀測輔助(to_string)的 shallow 覆寫(節錄):

// Forward declarations for shallow to_string
extern char *go_shallow_array_to_string(void *go_array);
extern char *go_shallow_table_to_string(void *go_table);

static char *cgo_shallow_array_to_string(mongory_value *v, mongory_memory_pool *pool) {
    return go_shallow_array_to_string(v->data.a);
}

static char *cgo_shallow_table_to_string(mongory_value *v, mongory_memory_pool *pool) {
    return go_shallow_table_to_string(v->data.t);
}

static void mongory_value_set_array_to_string(mongory_value *v) {
    v->to_str = cgo_shallow_array_to_string;
}

static void mongory_value_set_table_to_string(mongory_value *v) {
    v->to_str = cgo_shallow_table_to_string;
}

這讓 explain/trace 與除錯輸出不必強行 deep 展開,就能得到可讀的內容


讀者該如何使用

  • 條件:以 map[string]any 撰寫即可,對齊既有的 MongoDB 風格語法
  • 資料:直接傳入 map[string]any[]any 或結構體/自訂型別,shallow 會在取值時處理
  • 可觀測性:遇到疑義,使用 explain/trace 觀測實際匹配流程(將在後續 Go 章節補齊)

小結

Go 端最難的是「型別的自由度」與「跨語言一致性的成本」。筆者以 shallow/deep/recover 排好責任邊界,讓反射只在必要處出現,並維持輸出可讀。這套邏輯已在 Ruby 端驗證有效,遷移到 Go 上同樣站得住腳


下一篇預告

Day 25:Go ↔ C:unsafe.Pointer/cgo.Handle 的正確姿勢——指標安全、生命週期、以及如何不踩 runtime 的雷

Go 版 Mongory 專案


上一篇
Day 23:cgo 與 submodule:Makefile/腳本同步與開發體驗
系列文
Mongory:打造跨語言、高效能的萬用查詢引擎25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言