在 Go 版的 Mongory,最棘手的不是 matcher 邏輯,而是「資料如何過橋」。筆者最終採用 shallow/deep/recover 的分工,盡量把反射的成本壓到必要處,讓讀者既能保有 Go 的型別自由,又能以 MongoDB 風格的條件語法快速上手
map[string]any
為主,直覺、對齊 JSON 心智模型map[string]any
、[]any
、或自訂型別(由 shallow 包裝延遲取值)這套邊界讓「建立 matcher」與「批次匹配」兩個階段各自最優:建構時花該花的,匹配時保 O(1) 取值與惰性展開
x.(T)
難以覆蓋大量實務類型map[string]any
與 []any
的樹型結構更貼近條件描述,易於轉換條件 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 會在取值時處理Go 端最難的是「型別的自由度」與「跨語言一致性的成本」。筆者以 shallow/deep/recover 排好責任邊界,讓反射只在必要處出現,並維持輸出可讀。這套邏輯已在 Ruby 端驗證有效,遷移到 Go 上同樣站得住腳
Day 25:Go ↔ C:unsafe.Pointer/cgo.Handle 的正確姿勢——指標安全、生命週期、以及如何不踩 runtime 的雷