Skip to content

Commit e9723d1

Browse files
h3n4lclaude
andauthored
test(mongodb): add Unicode and $function/$accumulator test coverage (#55)
- Add unicode.js with emoji, Hindi, CJK, Arabic, Hebrew, Thai, Cyrillic, Greek, combining characters, and mixed scripts - Add aggregate-js-functions.js with $function and $accumulator operators Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6987049 commit e9723d1

File tree

2 files changed

+309
-0
lines changed

2 files changed

+309
-0
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// $function operator - custom JavaScript functions in aggregation
2+
// The JavaScript code is passed as a string in the "body" field
3+
4+
// Basic $function usage
5+
db.players.aggregate([
6+
{ $addFields: {
7+
isFound: {
8+
$function: {
9+
body: "function(name) { return name.length > 5; }",
10+
args: ["$name"],
11+
lang: "js"
12+
}
13+
}
14+
}}
15+
])
16+
17+
// $function with multiple arguments
18+
db.orders.aggregate([
19+
{ $addFields: {
20+
discount: {
21+
$function: {
22+
body: "function(price, quantity) { return price * quantity * 0.1; }",
23+
args: ["$price", "$quantity"],
24+
lang: "js"
25+
}
26+
}
27+
}}
28+
])
29+
30+
// $function with complex logic
31+
db.events.aggregate([
32+
{ $addFields: {
33+
category: {
34+
$function: {
35+
body: "function(score) { if (score >= 90) return 'A'; else if (score >= 80) return 'B'; else if (score >= 70) return 'C'; else return 'F'; }",
36+
args: ["$score"],
37+
lang: "js"
38+
}
39+
}
40+
}}
41+
])
42+
43+
// $function with no arguments
44+
db.items.aggregate([
45+
{ $addFields: {
46+
timestamp: {
47+
$function: {
48+
body: "function() { return new Date().toISOString(); }",
49+
args: [],
50+
lang: "js"
51+
}
52+
}
53+
}}
54+
])
55+
56+
// $function with array argument
57+
db.data.aggregate([
58+
{ $addFields: {
59+
processed: {
60+
$function: {
61+
body: "function(arr) { return arr.map(x => x * 2); }",
62+
args: ["$values"],
63+
lang: "js"
64+
}
65+
}
66+
}}
67+
])
68+
69+
// $function with multiline string (escaped)
70+
db.documents.aggregate([
71+
{ $addFields: {
72+
result: {
73+
$function: {
74+
body: "function(doc) {\n var sum = 0;\n for (var i = 0; i < doc.length; i++) {\n sum += doc[i];\n }\n return sum;\n}",
75+
args: ["$numbers"],
76+
lang: "js"
77+
}
78+
}
79+
}}
80+
])
81+
82+
// $accumulator operator - custom accumulator with JavaScript
83+
db.sales.aggregate([
84+
{ $group: {
85+
_id: "$category",
86+
customSum: {
87+
$accumulator: {
88+
init: "function() { return { sum: 0, count: 0 }; }",
89+
accumulate: "function(state, value) { return { sum: state.sum + value, count: state.count + 1 }; }",
90+
accumulateArgs: ["$amount"],
91+
merge: "function(state1, state2) { return { sum: state1.sum + state2.sum, count: state1.count + state2.count }; }",
92+
finalize: "function(state) { return state.sum / state.count; }",
93+
lang: "js"
94+
}
95+
}
96+
}}
97+
])
98+
99+
// $accumulator with initArgs
100+
db.transactions.aggregate([
101+
{ $group: {
102+
_id: "$accountId",
103+
balance: {
104+
$accumulator: {
105+
init: "function(initial) { return initial; }",
106+
initArgs: [0],
107+
accumulate: "function(state, amount, type) { return type === 'credit' ? state + amount : state - amount; }",
108+
accumulateArgs: ["$amount", "$type"],
109+
merge: "function(s1, s2) { return s1 + s2; }",
110+
lang: "js"
111+
}
112+
}
113+
}}
114+
])
115+
116+
// $accumulator without finalize
117+
db.metrics.aggregate([
118+
{ $group: {
119+
_id: "$source",
120+
values: {
121+
$accumulator: {
122+
init: "function() { return []; }",
123+
accumulate: "function(state, val) { state.push(val); return state; }",
124+
accumulateArgs: ["$value"],
125+
merge: "function(s1, s2) { return s1.concat(s2); }",
126+
lang: "js"
127+
}
128+
}
129+
}}
130+
])
131+
132+
// Combined $function and standard operators
133+
db.products.aggregate([
134+
{ $match: { inStock: true } },
135+
{ $addFields: {
136+
priceCategory: {
137+
$function: {
138+
body: "function(p) { return p < 10 ? 'cheap' : p < 100 ? 'moderate' : 'expensive'; }",
139+
args: ["$price"],
140+
lang: "js"
141+
}
142+
}
143+
}},
144+
{ $group: { _id: "$priceCategory", count: { $sum: 1 } } },
145+
{ $sort: { count: -1 } }
146+
])
147+
148+
// $function in $project stage
149+
db.users.aggregate([
150+
{ $project: {
151+
name: 1,
152+
maskedEmail: {
153+
$function: {
154+
body: "function(email) { var parts = email.split('@'); return parts[0].substring(0, 2) + '***@' + parts[1]; }",
155+
args: ["$email"],
156+
lang: "js"
157+
}
158+
}
159+
}}
160+
])
161+
162+
// $function with object argument
163+
db.records.aggregate([
164+
{ $addFields: {
165+
serialized: {
166+
$function: {
167+
body: "function(obj) { return JSON.stringify(obj); }",
168+
args: ["$metadata"],
169+
lang: "js"
170+
}
171+
}
172+
}}
173+
])
174+
175+
// $function returning object
176+
db.coordinates.aggregate([
177+
{ $addFields: {
178+
point: {
179+
$function: {
180+
body: "function(lat, lng) { return { type: 'Point', coordinates: [lng, lat] }; }",
181+
args: ["$latitude", "$longitude"],
182+
lang: "js"
183+
}
184+
}
185+
}}
186+
])
187+
188+
// Nested $function in $facet
189+
db.analytics.aggregate([
190+
{ $facet: {
191+
processed: [
192+
{ $addFields: {
193+
normalized: {
194+
$function: {
195+
body: "function(v, min, max) { return (v - min) / (max - min); }",
196+
args: ["$value", "$minValue", "$maxValue"],
197+
lang: "js"
198+
}
199+
}
200+
}}
201+
],
202+
summary: [
203+
{ $group: { _id: null, total: { $sum: "$value" } } }
204+
]
205+
}}
206+
])

mongodb/examples/unicode.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Unicode support - emoji characters
2+
db.posts.find({ reaction: "👍" })
3+
db.posts.find({ emoji: "🎉🎊🎁" })
4+
db.posts.insertOne({ title: "Hello 🌍", content: "Testing emoji support 😀" })
5+
db.messages.updateOne({ _id: 1 }, { $set: { status: "✅ completed" } })
6+
7+
// Unicode - emoji in field names (quoted)
8+
db.reactions.find({ "👍": { $gt: 10 } })
9+
db.reactions.insertOne({ "👍": 100, "👎": 5, "❤️": 50 })
10+
11+
// Unicode - Hindi (Devanagari script)
12+
db.users.find({ name: "नमस्ते" })
13+
db.users.find({ greeting: "आपका स्वागत है" })
14+
db.products.insertOne({ name: "चाय", description: "भारतीय मसाला चाय" })
15+
db.articles.find({ title: "हिंदी में लेख" })
16+
17+
// Unicode - Chinese (Simplified)
18+
db.users.find({ name: "你好世界" })
19+
db.products.find({ category: "电子产品" })
20+
db.orders.insertOne({ item: "笔记本电脑", price: 5999 })
21+
22+
// Unicode - Chinese (Traditional)
23+
db.users.find({ name: "繁體中文" })
24+
db.products.find({ description: "傳統工藝品" })
25+
26+
// Unicode - Japanese (Hiragana, Katakana, Kanji)
27+
db.users.find({ greeting: "こんにちは" })
28+
db.products.find({ name: "カタカナ" })
29+
db.articles.find({ title: "日本語の記事" })
30+
31+
// Unicode - Korean
32+
db.users.find({ name: "안녕하세요" })
33+
db.products.find({ category: "한국 음식" })
34+
35+
// Unicode - Arabic (right-to-left)
36+
db.users.find({ name: "مرحبا بالعالم" })
37+
db.articles.find({ title: "مقال باللغة العربية" })
38+
39+
// Unicode - Thai
40+
db.users.find({ greeting: "สวัสดี" })
41+
db.products.find({ name: "อาหารไทย" })
42+
43+
// Unicode - Russian (Cyrillic)
44+
db.users.find({ name: "Привет мир" })
45+
db.articles.find({ title: "Русский текст" })
46+
47+
// Unicode - Greek
48+
db.users.find({ name: "Γειά σου κόσμε" })
49+
db.math.find({ symbol: "π", value: 3.14159 })
50+
51+
// Unicode - Hebrew
52+
db.users.find({ greeting: "שלום עולם" })
53+
54+
// Unicode - mixed scripts in single document
55+
db.international.insertOne({
56+
english: "Hello",
57+
hindi: "नमस्ते",
58+
chinese: "你好",
59+
japanese: "こんにちは",
60+
korean: "안녕하세요",
61+
arabic: "مرحبا",
62+
russian: "Привет",
63+
emoji: "👋🌍"
64+
})
65+
66+
// Unicode - special characters and symbols
67+
db.math.find({ formula: "∑∏∫∂√∞" })
68+
db.currency.find({ symbols: "€£¥₹₽₿" })
69+
db.symbols.find({ arrows: "←→↑↓↔↕" })
70+
db.music.find({ notes: "♪♫♬♩" })
71+
72+
// Unicode - combining characters and diacritics
73+
db.users.find({ name: "José García" })
74+
db.users.find({ name: "François Müller" })
75+
db.users.find({ name: "Søren Kierkegaard" })
76+
db.users.find({ name: "Nguyễn Văn A" })
77+
78+
// Unicode in aggregation pipelines
79+
db.posts.aggregate([
80+
{ $match: { reaction: "👍" } },
81+
{ $group: { _id: "$emoji", count: { $sum: 1 } } }
82+
])
83+
84+
db.international.aggregate([
85+
{ $project: { greeting: { $concat: ["Hello ", "$hindi", " ", "$emoji"] } } }
86+
])
87+
88+
// Unicode in array values
89+
db.tags.find({ labels: { $in: ["重要", "紧急", "🔥"] } })
90+
db.posts.insertOne({ tags: ["日本語", "한국어", "中文", "हिंदी"] })
91+
92+
// Unicode escape sequences (existing support)
93+
db.users.find({ escaped: "\u0048\u0065\u006C\u006C\u006F" })
94+
db.users.find({ mixed: "Hello \u4E16\u754C" })
95+
96+
// Unicode in regex patterns
97+
db.articles.find({ title: // })
98+
db.products.find({ name: /^/ })
99+
100+
// Unicode in index and collection names (via bracket notation)
101+
db["文章"].find({})
102+
db["статьи"].insertOne({ title: "Тест" })
103+
db["記事"].aggregate([{ $match: { published: true } }])

0 commit comments

Comments
 (0)