今天把 Mongory 的 Matchers 架構一次講透:骨架(base/composite/explainable/traversable)、建構 → 匹配 → 遍歷的資料流、priority/unwrap/trace 的掛點,另外用最小範例示意 Regex 與 Custom 的 adapter 設計,最後說清楚為什麼要把這些邏輯外包給宿主語言。
compare
:$eq/$ne/$gt/$gte/$lt/$lte
等比較;面向單值。inclusion
:$in/$nin
;面向集合查找。field
:選擇欄位並把值(或子結構)交給子 matcher。array
:$elemMatch/$every
;針對陣列逐元素套用子 matcher。composite
:$and/$or/Condition
;多子節點合併布林結果。external
:$regex
與自訂 $custom
;由 adapter 接到宿主語言。mongory_matcher
(base):定義共用欄位與行為指標。mongory_composite_matcher
:帶 children
的容器。mongory_matcher_traverse_context
:explain/遍歷的上下文。extern_ctx
:跨語言或外部狀態的掛載點,主要供使用者自定義 custom matcher 調用mongory_matcher
承載必要欄位:
name
、condition
、match
、pool
、extern_ctx
explain
、traverse
、trace_stack
、trace_level
priority
children
(以 mongory_array
承載),例如 $and/$or/$elemMatch/$every
。mongory_matcher_traverse_context
,explain 與 trace 皆以 traversal 為基礎輸出。regex
與 custom
走 adapter,核心只保證生命週期與錯誤註記,邏輯交給宿主語言提供的回呼。補充:matcher_explainable.h
與 matcher_traversable.h
把「如何走樹、如何輸出」從「如何比對」切開,降低耦合
table_cond
讀取條件文件(hash/table),遇到 $
開頭當作運算子,其他視為欄位。children
裡。match(matcher, value)
,composite 依語意合併子結果(AND/OR/elemMatch/every)。trace_stack
,並標記結果。traverse
走訪整棵樹,explain
基於 traversal 直譯節點語意產生可讀輸出,trace
也基於 traversal 替換整棵樹的 match func 以達到追蹤比對過程的效果。更細部的 build 步驟(以 table condition 為例):
// 偽碼示意:將一個條件 table 轉為 matcher 樹
mongory_matcher *build_table_cond(mongory_memory_pool *pool, mongory_value *table) {
mongory_matcher *root = mongory_matcher_composite_new(pool, table, NULL);
// 1) 走訪 table 的每個鍵值
// 2) "$" 開頭 → 運算子 matcher;其他 → field matcher
// 3) children.push(child)
// 4) children 依 priority 排序
// 5) 單子節點 unwrap(若允許)
return root;
}
match 階段的早停(early exit):
$and
:遇到第一個 false
即可返回 false
。$or
:遇到第一個 true
即可返回 true
。$elemMatch
:有任一元素為 true
即可返回 true
。$every
:有任一元素為 false
即可返回 false
。children
時就決定排序(例如先做便宜且可早停的條件),降低平均成本。trace_stack
追蹤呼叫深度與節點結果,mongory_matcher_trace_result_colorful_set(true)
可開彩色輸出(閱讀友善)。Priority 設計心法與實作切點:
$eq
< $in
< $regex
)。Unwrap 的邊界與反例:
$and
僅一個 child 時可解包。field
→ 單一子 matcher,且不影響 trace 與 explain 的語意時。$every
、$elemMatch
此類「語意承載」節點,不應因單子而移除。小範例(explain 前後對照):
Before (unwrap 前)
And
Field("age")
Gt(18)
After (unwrap 後)
Field("age")
Gt(18)
Trace 設計要點:
trace_stack
記錄節點進出與結果(Matched/Dismatch)。level
用於縮排;count/total
可用於進度條式輸出。以下示例展示如何在初始化時註冊 adapter 回呼,讓 C core 在執行 $regex
或自定義運算子時,委派至宿主語言。
#include <mongory-core/foundations/config.h>
#include <mongory-core/foundations/value.h>
// Regex adapter:由宿主語言實作具體比對與 stringify
static bool my_regex_match(mongory_memory_pool *pool, mongory_value *pattern, mongory_value *value) {
(void)pool;
// 由宿主語言綁過來的 regex engine 處理;此處示意直接回 false
return false;
}
static char *my_regex_stringify(mongory_memory_pool *pool, mongory_value *pattern) {
(void)pool; (void)pattern;
return NULL; // 示意:可將 pattern 轉成 "~/.../i" 形式
}
// Custom matcher adapter:lookup/build/match 三段式
static bool my_custom_lookup(char *key) {
return (key && key[0] == '$'); // 示意:所有 $ 開頭皆視為可用
}
// 正確型別:建議回傳 mongory_matcher_custom_context*
static mongory_matcher_custom_context *my_custom_build(char *key, mongory_value *cond, void *extern_ctx) {
(void)cond; (void)extern_ctx;
// 這裡通常會建立一個外部(宿主語言)matcher 並包成 ctx 回傳
return NULL; // 示意用
}
static bool my_custom_match(void *external_matcher, mongory_value *value) {
(void)external_matcher; (void)value;
return false; // 示意:實務上委派宿主語言的邏輯
}
static void register_adapters() {
mongory_regex_func_set(my_regex_match);
mongory_regex_stringify_func_set(my_regex_stringify);
mongory_custom_matcher_lookup_func_set(my_custom_lookup);
mongory_custom_matcher_build_func_set(my_custom_build);
mongory_custom_matcher_match_func_set(my_custom_match);
}
注意:上例僅示意註冊點與責任邊界;實務上會在宿主語言側維護 registry、錯誤轉譯與生命週期(詳見 Day16/Day17/Bonus 3)。
Adapter 實務策略:
Regexp
/Onigmo
,在 Go 走 regexp
標準庫;透過 stringify 讓 explain 顯示人類可讀的樣式(如 /^foo.*/i
)。$key
交由宿主語言接管,避免 C core 誤判。mongory_matcher_custom_context
。pool->error
或 false。跨語言錯誤處理:
pool->error
,避免讓 C core 處於不一致狀態。條件(概念示意):
{
"age": { "$gte": 18 },
"$or": [
{ "status": { "$in": ["active", "pending"] } },
{ "name": { "$regex": "^J" } }
]
}
旅程步驟:
table_cond
識別 age
為欄位、$or
為運算子。Field("age") -> Gte(18)
與 Or(children=[...])
。Or
的第一個 child 是 Field("status") -> In(["active","pending"])
;第二個是 Field("name") -> Regex("^J")
。Gte
與 In
會優先於 Regex
。explain(簡化示意):
And: {"age"=>{"$gte"=>18}, "$or"=>[{"status"=>{"$in"=>...}
├─ Field: "age" to match: {"$gte"=>18}
│ └─ Gte: 18
└─ Or: [{"status"=>{"$in"=>...
├─ Field: "status" to match: {"$in"=>["active","pending"]}
│ └─ In: ["active","pending"]
└─ Field: "name" to match: {"$regex"=>/^J/}
└─ Regex: /^J/
trace(簡化示意):
QueryMatcher Matched, condition: {"age"=>{"$gte"=>18}, "$or"=>[{"status"=>...
AndMatcher Matched, condition: {"age"=>{"$gte"=>18}, "$or"=>[{"status"=>...
FieldMatcher Matched, condition: {"$gte"=>18}, field: "age", record: {"age"=>25}
GteMatcher Matched, condition: 18, record: 25
OrMatcher Matched, condition: [{"status"=>{..}}, {"name"=>{..}], record: {..}
FieldMatcher Matched, condition: {"$in"=>["active","pending"]}, field: "status", record: {"status"=>"active"}
InMatcher Matched, condition: ["active","pending"], record: "active"
$every
誤解包,導致語意改變。
$in/$nin
的集合型別不一致或含 NULL
導致邊界行為不明。
NULL
或轉型),或於 compare 層嚴格型別檢查。pool->error = &MONGORY_ALLOC_ERROR
。false
或上拋至建樹失敗。pool
配置,pool_free
時批次回收。extern_ctx
僅保存指標,不主導釋放;釋放由宿主語言生命週期負責(Day 17 展開)。extern_ctx
與外部物件壽命協同。extconf.rb/build
流程、macOS/Clang 要點、子模組同步與本地發佈。