1use serde_json::{json, Map, Number, Value};
8
9use crate::schema::Schema;
10
11fn with_loc(mut m: Map<String, Value>, schema: &Schema) -> Map<String, Value> {
13 m.insert("source".to_string(), json!(schema.source()));
14 m.insert(
15 "database".to_string(),
16 schema.database().map(|s| json!(s)).unwrap_or(Value::Null),
17 );
18 m.insert(
19 "schema".to_string(),
20 schema.schema().map(|s| json!(s)).unwrap_or(Value::Null),
21 );
22 m
23}
24
25pub fn cmd_find(schema: &Schema, filter: &Value, projection: Option<&Value>) -> Value {
26 with_loc(
27 json!({
28 "kind": "find",
29 "collection": schema.collection,
30 "filter": filter,
31 "projection": projection.cloned().unwrap_or(Value::Null),
32 })
33 .as_object()
34 .cloned()
35 .unwrap_or_default(),
36 schema,
37 )
38 .into()
39}
40
41pub fn cmd_aggregate(schema: &Schema, pipeline: &[Value]) -> Value {
42 with_loc(
43 json!({
44 "kind": "aggregate",
45 "collection": schema.collection,
46 "pipeline": pipeline,
47 })
48 .as_object()
49 .cloned()
50 .unwrap_or_default(),
51 schema,
52 )
53 .into()
54}
55
56pub fn cmd_count_documents(schema: &Schema, filter: &Value) -> Value {
57 with_loc(
58 json!({
59 "kind": "countDocuments",
60 "collection": schema.collection,
61 "filter": filter,
62 })
63 .as_object()
64 .cloned()
65 .unwrap_or_default(),
66 schema,
67 )
68 .into()
69}
70
71pub fn cmd_find_one(schema: &Schema, filter: &Value, projection: Option<&Value>) -> Value {
72 with_loc(
73 json!({
74 "kind": "findOne",
75 "collection": schema.collection,
76 "filter": filter,
77 "projection": projection.cloned().unwrap_or(Value::Null),
78 })
79 .as_object()
80 .cloned()
81 .unwrap_or_default(),
82 schema,
83 )
84 .into()
85}
86
87pub fn cmd_insert_one(schema: &Schema, doc: &Value) -> Value {
88 with_loc(
89 json!({
90 "kind": "insertOne",
91 "collection": schema.collection,
92 "doc": doc,
93 })
94 .as_object()
95 .cloned()
96 .unwrap_or_default(),
97 schema,
98 )
99 .into()
100}
101
102pub fn cmd_insert_many(schema: &Schema, docs: &[Value]) -> Value {
103 with_loc(
104 json!({
105 "kind": "insertMany",
106 "collection": schema.collection,
107 "docs": docs,
108 })
109 .as_object()
110 .cloned()
111 .unwrap_or_default(),
112 schema,
113 )
114 .into()
115}
116
117pub fn cmd_find_one_and_update(
118 schema: &Schema,
119 filter: &Value,
120 update: &Value,
121 options: &Value,
122) -> Value {
123 with_loc(
124 json!({
125 "kind": "findOneAndUpdate",
126 "collection": schema.collection,
127 "filter": filter,
128 "update": update,
129 "options": options,
130 })
131 .as_object()
132 .cloned()
133 .unwrap_or_default(),
134 schema,
135 )
136 .into()
137}
138
139pub fn cmd_update_many(schema: &Schema, filter: &Value, update: &Value) -> Value {
140 with_loc(
141 json!({
142 "kind": "updateMany",
143 "collection": schema.collection,
144 "filter": filter,
145 "update": update,
146 })
147 .as_object()
148 .cloned()
149 .unwrap_or_default(),
150 schema,
151 )
152 .into()
153}
154
155pub fn cmd_delete_many(schema: &Schema, filter: &Value) -> Value {
156 with_loc(
157 json!({
158 "kind": "deleteMany",
159 "collection": schema.collection,
160 "filter": filter,
161 })
162 .as_object()
163 .cloned()
164 .unwrap_or_default(),
165 schema,
166 )
167 .into()
168}
169
170pub fn cmd_claim(
174 schema: &Schema,
175 filter: &Value,
176 sort: &Value,
177 limit: i64,
178 update: &Value,
179 now_stamp: i64,
180) -> Value {
181 with_loc(
182 json!({
183 "kind": "claim",
184 "collection": schema.collection,
185 "filter": filter,
186 "sort": sort,
187 "limit": limit,
188 "update": update,
189 "nowStamp": now_stamp,
190 })
191 .as_object()
192 .cloned()
193 .unwrap_or_default(),
194 schema,
195 )
196 .into()
197}
198
199pub fn cmd_compare_and_set(schema: &Schema, filter: &Value, update: &Value, now_stamp: i64) -> Value {
201 with_loc(
202 json!({
203 "kind": "compareAndSet",
204 "collection": schema.collection,
205 "filter": filter,
206 "update": update,
207 "nowStamp": now_stamp,
208 })
209 .as_object()
210 .cloned()
211 .unwrap_or_default(),
212 schema,
213 )
214 .into()
215}
216
217pub fn apply_route_override(plan: &mut Value, route_override: &Value) {
229 let ov = match route_override.as_object() {
230 Some(o) if !o.is_empty() => o,
231 _ => return,
232 };
233 let src = ov
234 .get("source")
235 .and_then(|v| v.as_str())
236 .filter(|s| !s.is_empty());
237 let db_present = ov.contains_key("database");
238 let db = ov.get("database");
239 let sc_present = ov.contains_key("schema");
240 let sc = ov.get("schema");
241 override_walk(plan, src, db_present, db, sc_present, sc);
242}
243
244fn override_walk(
245 v: &mut Value,
246 src: Option<&str>,
247 db_present: bool,
248 db: Option<&Value>,
249 sc_present: bool,
250 sc: Option<&Value>,
251) {
252 match v {
253 Value::Object(map) => {
254 let is_cmd = map.contains_key("kind") && map.contains_key("collection");
255 if is_cmd {
256 if let Some(s) = src {
257 map.insert("source".to_string(), json!(s));
258 }
259 if db_present {
260 map.insert("database".to_string(), db.cloned().unwrap_or(Value::Null));
261 }
262 if sc_present {
263 map.insert("schema".to_string(), sc.cloned().unwrap_or(Value::Null));
264 }
265 }
266 for (_, child) in map.iter_mut() {
267 override_walk(child, src, db_present, db, sc_present, sc);
268 }
269 }
270 Value::Array(items) => {
271 for it in items.iter_mut() {
272 override_walk(it, src, db_present, db, sc_present, sc);
273 }
274 }
275 _ => {}
276 }
277}
278
279pub(super) fn to_number(v: Option<&Value>) -> f64 {
283 match v {
284 Some(Value::Number(n)) => {
285 let f = n.as_f64().unwrap_or(0.0);
286 if f.is_nan() {
287 0.0
288 } else {
289 f.trunc()
290 }
291 }
292 Some(Value::Bool(b)) => {
293 if *b {
294 1.0
295 } else {
296 0.0
297 }
298 }
299 Some(Value::String(s)) => {
300 let t = s.trim();
301 if t.is_empty() {
302 return 0.0;
303 }
304 if is_int_str(t) {
305 t.parse::<i64>().map(|i| i as f64).unwrap_or(0.0)
306 } else {
307 match t.parse::<f64>() {
308 Ok(f) if !f.is_nan() => f,
309 _ => 0.0,
310 }
311 }
312 }
313 _ => 0.0,
314 }
315}
316
317fn is_int_str(s: &str) -> bool {
318 let body = s.strip_prefix(['+', '-']).unwrap_or(s);
319 !body.is_empty() && body.bytes().all(|b| b.is_ascii_digit())
320}
321
322pub(super) fn num_value(f: f64) -> Value {
324 if !f.is_finite() {
325 return json!(0);
326 }
327 if f.fract() == 0.0 && f.abs() <= 9_007_199_254_740_992.0 {
328 json!(f as i64)
329 } else {
330 Value::Number(Number::from_f64(f).unwrap_or_else(|| Number::from(0)))
331 }
332}