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(
201 schema: &Schema,
202 filter: &Value,
203 update: &Value,
204 now_stamp: i64,
205) -> Value {
206 with_loc(
207 json!({
208 "kind": "compareAndSet",
209 "collection": schema.collection,
210 "filter": filter,
211 "update": update,
212 "nowStamp": now_stamp,
213 })
214 .as_object()
215 .cloned()
216 .unwrap_or_default(),
217 schema,
218 )
219 .into()
220}
221
222pub fn apply_route_override(plan: &mut Value, route_override: &Value) {
234 let ov = match route_override.as_object() {
235 Some(o) if !o.is_empty() => o,
236 _ => return,
237 };
238 let src = ov
239 .get("source")
240 .and_then(|v| v.as_str())
241 .filter(|s| !s.is_empty());
242 let db_present = ov.contains_key("database");
243 let db = ov.get("database");
244 let sc_present = ov.contains_key("schema");
245 let sc = ov.get("schema");
246 override_walk(plan, src, db_present, db, sc_present, sc);
247}
248
249fn override_walk(
250 v: &mut Value,
251 src: Option<&str>,
252 db_present: bool,
253 db: Option<&Value>,
254 sc_present: bool,
255 sc: Option<&Value>,
256) {
257 match v {
258 Value::Object(map) => {
259 let is_cmd = map.contains_key("kind") && map.contains_key("collection");
260 if is_cmd {
261 if let Some(s) = src {
262 map.insert("source".to_string(), json!(s));
263 }
264 if db_present {
265 map.insert("database".to_string(), db.cloned().unwrap_or(Value::Null));
266 }
267 if sc_present {
268 map.insert("schema".to_string(), sc.cloned().unwrap_or(Value::Null));
269 }
270 }
271 for (_, child) in map.iter_mut() {
272 override_walk(child, src, db_present, db, sc_present, sc);
273 }
274 }
275 Value::Array(items) => {
276 for it in items.iter_mut() {
277 override_walk(it, src, db_present, db, sc_present, sc);
278 }
279 }
280 _ => {}
281 }
282}
283
284pub(super) fn to_number(v: Option<&Value>) -> f64 {
288 match v {
289 Some(Value::Number(n)) => {
290 let f = n.as_f64().unwrap_or(0.0);
291 if f.is_nan() {
292 0.0
293 } else {
294 f.trunc()
295 }
296 }
297 Some(Value::Bool(b)) => {
298 if *b {
299 1.0
300 } else {
301 0.0
302 }
303 }
304 Some(Value::String(s)) => {
305 let t = s.trim();
306 if t.is_empty() {
307 return 0.0;
308 }
309 if is_int_str(t) {
310 t.parse::<i64>().map(|i| i as f64).unwrap_or(0.0)
311 } else {
312 match t.parse::<f64>() {
313 Ok(f) if !f.is_nan() => f,
314 _ => 0.0,
315 }
316 }
317 }
318 _ => 0.0,
319 }
320}
321
322fn is_int_str(s: &str) -> bool {
323 let body = s.strip_prefix(['+', '-']).unwrap_or(s);
324 !body.is_empty() && body.bytes().all(|b| b.is_ascii_digit())
325}
326
327pub(super) fn num_value(f: f64) -> Value {
329 if !f.is_finite() {
330 return json!(0);
331 }
332 if f.fract() == 0.0 && f.abs() <= 9_007_199_254_740_992.0 {
333 json!(f as i64)
334 } else {
335 Value::Number(Number::from_f64(f).unwrap_or_else(|| Number::from(0)))
336 }
337}