1#![warn(missing_docs)]
4
5use crate::{Context, G, GTrait, QString};
9use core::fmt;
10use jiff::{Zoned, tz::TimeZone};
11use std::any::Any;
12
13#[derive(Debug)]
17pub enum ExtDataType {
18 Str,
20 Num,
22 Bool,
24 Timestamp,
27 Date,
30 Geom,
32}
33
34type GenericFn = Box<dyn Fn(Vec<Box<dyn Any>>) -> Option<Box<dyn Any>> + Send + Sync + 'static>;
37
38pub struct FnInfo {
40 pub(crate) closure: GenericFn,
41 pub(crate) arg_types: Vec<ExtDataType>,
42 pub(crate) result_type: ExtDataType,
43}
44
45impl fmt::Debug for FnInfo {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("FnInfo")
48 .field("arg_types", &self.arg_types)
49 .field("return_type", &self.result_type)
50 .finish()
51 }
52}
53
54pub(crate) fn add_builtins(ctx: &mut Context) {
56 let abs = |x: f64| x.abs();
58 ctx.register(
59 "abs",
60 vec![ExtDataType::Num],
61 ExtDataType::Num,
62 move |args| {
63 let x = args.first()?.downcast_ref::<f64>()?;
64 Some(Box::new(abs(*x)))
65 },
66 );
67
68 let acos = |x: f64| x.acos();
69 ctx.register(
70 "acos",
71 vec![ExtDataType::Num],
72 ExtDataType::Num,
73 move |args| {
74 let x = args.first()?.downcast_ref::<f64>()?;
75 Some(Box::new(acos(*x)))
76 },
77 );
78
79 let asin = |x: f64| x.asin();
80 ctx.register(
81 "asin",
82 vec![ExtDataType::Num],
83 ExtDataType::Num,
84 move |args| {
85 let x = args.first()?.downcast_ref::<f64>()?;
86 Some(Box::new(asin(*x)))
87 },
88 );
89
90 let atan = |x: f64| x.atan();
91 ctx.register(
92 "atan",
93 vec![ExtDataType::Num],
94 ExtDataType::Num,
95 move |args| {
96 let x = args.first()?.downcast_ref::<f64>()?;
97 Some(Box::new(atan(*x)))
98 },
99 );
100
101 let cbrt = |x: f64| x.cbrt();
102 ctx.register(
103 "cbrt",
104 vec![ExtDataType::Num],
105 ExtDataType::Num,
106 move |args| {
107 let x = args.first()?.downcast_ref::<f64>()?;
108 Some(Box::new(cbrt(*x)))
109 },
110 );
111
112 let ceil = |x: f64| x.ceil();
113 ctx.register(
114 "ceil",
115 vec![ExtDataType::Num],
116 ExtDataType::Num,
117 move |args| {
118 let x = args.first()?.downcast_ref::<f64>()?;
119 Some(Box::new(ceil(*x)))
120 },
121 );
122
123 let cos = |x: f64| x.cos();
124 ctx.register(
125 "cos",
126 vec![ExtDataType::Num],
127 ExtDataType::Num,
128 move |args| {
129 let x = args.first()?.downcast_ref::<f64>()?;
130 Some(Box::new(cos(*x)))
131 },
132 );
133
134 let floor = |x: f64| x.floor();
135 ctx.register(
136 "floor",
137 vec![ExtDataType::Num],
138 ExtDataType::Num,
139 move |args| {
140 let x = args.first()?.downcast_ref::<f64>()?;
141 Some(Box::new(floor(*x)))
142 },
143 );
144
145 let ln = |x: f64| x.ln();
146 ctx.register(
147 "ln",
148 vec![ExtDataType::Num],
149 ExtDataType::Num,
150 move |args| {
151 let x = args.first()?.downcast_ref::<f64>()?;
152 Some(Box::new(ln(*x)))
153 },
154 );
155
156 let sin = |x: f64| x.sin();
157 ctx.register(
158 "sin",
159 vec![ExtDataType::Num],
160 ExtDataType::Num,
161 move |args| {
162 let x = args.first()?.downcast_ref::<f64>()?;
163 Some(Box::new(sin(*x)))
164 },
165 );
166
167 let sqrt = |x: f64| x.sqrt();
168 ctx.register(
169 "sqrt",
170 vec![ExtDataType::Num],
171 ExtDataType::Num,
172 move |args| {
173 let x = args.first()?.downcast_ref::<f64>()?;
174 Some(Box::new(sqrt(*x)))
175 },
176 );
177
178 let tan = |x: f64| x.tan();
179 ctx.register(
180 "tan",
181 vec![ExtDataType::Num],
182 ExtDataType::Num,
183 move |args| {
184 let x = args.first()?.downcast_ref::<f64>()?;
185 Some(Box::new(tan(*x)))
186 },
187 );
188
189 let max = |x: f64, y: f64| x.max(y);
190 ctx.register(
191 "max",
192 vec![ExtDataType::Num, ExtDataType::Num],
193 ExtDataType::Num,
194 move |args| {
195 let x = args.first()?.downcast_ref::<f64>()?;
196 let y = args.get(1)?.downcast_ref::<f64>()?;
197 Some(Box::new(max(*x, *y)))
198 },
199 );
200
201 let avg = |x: f64, y: f64| x.midpoint(y);
202 ctx.register(
203 "avg",
204 vec![ExtDataType::Num, ExtDataType::Num],
205 ExtDataType::Num,
206 move |args| {
207 let x = args.first()?.downcast_ref::<f64>()?;
208 let y = args.get(1)?.downcast_ref::<f64>()?;
209 Some(Box::new(avg(*x, *y)))
210 },
211 );
212
213 let min = |x: f64, y: f64| x.min(y);
214 ctx.register(
215 "min",
216 vec![ExtDataType::Num, ExtDataType::Num],
217 ExtDataType::Num,
218 move |args| {
219 let x = args.first()?.downcast_ref::<f64>()?;
220 let y = args.get(1)?.downcast_ref::<f64>()?;
221 Some(Box::new(min(*x, *y)))
222 },
223 );
224
225 let trim = |x: &QString| x.as_str().trim().to_owned();
227 ctx.register(
228 "trim",
229 vec![ExtDataType::Str],
230 ExtDataType::Str,
231 move |args| {
232 let x = args.first()?.downcast_ref::<QString>()?;
233 Some(Box::new(trim(x)))
234 },
235 );
236
237 let len = |x: &QString| x.as_str().len();
238 ctx.register(
239 "len",
240 vec![ExtDataType::Str],
241 ExtDataType::Num,
242 move |args| {
243 let x = args.first()?.downcast_ref::<QString>()?;
244 Some(Box::new(len(x)))
245 },
246 );
247
248 let concat = |x: &QString, y: &QString| format!("{}{}", x.as_str(), y.as_str());
249 ctx.register(
250 "concat",
251 vec![ExtDataType::Str, ExtDataType::Str],
252 ExtDataType::Str,
253 move |args| {
254 let x = args.first()?.downcast_ref::<QString>()?;
255 let y = args.get(1)?.downcast_ref::<QString>()?;
256 Some(Box::new(concat(x, y)))
257 },
258 );
259
260 let starts_with = |x: &str, y: &str| x.starts_with(y);
261 ctx.register(
262 "starts_with",
263 vec![ExtDataType::Str, ExtDataType::Str],
264 ExtDataType::Bool,
265 move |args| {
266 let x = args.first()?.downcast_ref::<QString>()?.as_str();
267 let y = args.get(1)?.downcast_ref::<QString>()?.as_str();
268 Some(Box::new(starts_with(x, y)))
269 },
270 );
271
272 let ends_with = |x: &str, y: &str| x.ends_with(y);
273 ctx.register(
274 "ends_with",
275 vec![ExtDataType::Str, ExtDataType::Str],
276 ExtDataType::Bool,
277 move |args| {
278 let x = args.first()?.downcast_ref::<QString>()?.as_str();
279 let y = args.get(1)?.downcast_ref::<QString>()?.as_str();
280 Some(Box::new(ends_with(x, y)))
281 },
282 );
283
284 let now = || Zoned::now().with_time_zone(TimeZone::UTC);
286 ctx.register("now", vec![], ExtDataType::Timestamp, move |_| {
287 Some(Box::new(now()))
288 });
289
290 let today = || {
291 let noon = Zoned::now()
292 .with()
293 .hour(12)
294 .minute(0)
295 .second(0)
296 .subsec_nanosecond(0)
297 .build()
298 .expect("Failed finding today's date");
299 noon.with_time_zone(TimeZone::UTC)
300 };
301 ctx.register("today", vec![], ExtDataType::Timestamp, move |_| {
302 Some(Box::new(today()))
303 });
304
305 let boundary = |x: &G| x.boundary().expect("Failed finding boundary");
307 ctx.register(
308 "boundary",
309 vec![ExtDataType::Geom],
310 ExtDataType::Geom,
311 move |args| {
312 let x = args.first()?.downcast_ref::<G>()?;
313 Some(Box::new(boundary(x)))
314 },
315 );
316
317 let buffer = |x: &G, y: &f64| x.buffer(*y, 8).expect("Failed computing buffer");
318 ctx.register(
319 "buffer",
320 vec![ExtDataType::Geom, ExtDataType::Num],
321 ExtDataType::Geom,
322 move |args| {
323 let x = args.first()?.downcast_ref::<G>()?;
324 let y = args.get(1)?.downcast_ref::<f64>()?;
325 Some(Box::new(buffer(x, y)))
326 },
327 );
328
329 let envelope = |x: &G| x.envelope().expect("Failed finding envelope");
330 ctx.register(
331 "envelope",
332 vec![ExtDataType::Geom],
333 ExtDataType::Geom,
334 move |args| {
335 let x = args.first()?.downcast_ref::<G>()?;
336 Some(Box::new(envelope(x)))
337 },
338 );
339
340 let centroid = |x: &G| x.centroid().expect("Failed finding centroid");
341 ctx.register(
342 "centroid",
343 vec![ExtDataType::Geom],
344 ExtDataType::Geom,
345 move |args| {
346 let x = args.first()?.downcast_ref::<G>()?;
347 Some(Box::new(centroid(x)))
348 },
349 );
350
351 let convex_hull = |x: &G| x.convex_hull().expect("Failed finding convex hull");
352 ctx.register(
353 "convex_hull",
354 vec![ExtDataType::Geom],
355 ExtDataType::Geom,
356 move |args| {
357 let x = args.first()?.downcast_ref::<G>()?;
358 Some(Box::new(convex_hull(x)))
359 },
360 );
361
362 let get_x = |x: &G| x.get_x().expect("Failed isolating X");
363 ctx.register(
364 "get_x",
365 vec![ExtDataType::Geom],
366 ExtDataType::Num,
367 move |args| {
368 let x = args.first()?.downcast_ref::<G>()?;
369 Some(Box::new(get_x(x)))
370 },
371 );
372
373 let get_y = |x: &G| x.get_y().expect("Failed isolating Y");
374 ctx.register(
375 "get_y",
376 vec![ExtDataType::Geom],
377 ExtDataType::Num,
378 move |args| {
379 let x = args.first()?.downcast_ref::<G>()?;
380 Some(Box::new(get_y(x)))
381 },
382 );
383
384 let get_z = |x: &G| x.get_z().expect("Failed isolating Z");
385 ctx.register(
386 "get_z",
387 vec![ExtDataType::Geom],
388 ExtDataType::Num,
389 move |args| {
390 let x = args.first()?.downcast_ref::<G>()?;
391 Some(Box::new(get_z(x)))
392 },
393 );
394
395 let wkt = |x: &G, p: &f64| x.to_wkt_fmt(*p as usize);
396 ctx.register(
397 "wkt",
398 vec![ExtDataType::Geom, ExtDataType::Num],
399 ExtDataType::Str,
400 move |args| {
401 let x = args.first()?.downcast_ref::<G>()?;
402 let p = args.get(1)?.downcast_ref::<f64>()?;
403 Some(Box::new(wkt(x, p)))
404 },
405 );
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411 use crate::prelude::*;
412 use std::error::Error;
413
414 #[test]
415 fn test_unregistered() -> Result<(), Box<dyn Error>> {
416 let shared_ctx = Context::new().freeze();
417
418 let expr = Expression::try_from_text("sum(a, b)")?;
419 let mut eval = ExEvaluator::new(shared_ctx);
420 eval.setup(expr)?;
421
422 let feat = Resource::new();
423 let res = eval.evaluate(&feat)?;
424 assert!(matches!(res, Outcome::N));
425
426 Ok(())
427 }
428
429 #[test]
430 fn test_literals() -> Result<(), Box<dyn Error>> {
431 let sum = |x: f64, y: f64| x + y;
432
433 let mut ctx = Context::new();
434 ctx.register(
435 "sum",
436 vec![ExtDataType::Num, ExtDataType::Num],
437 ExtDataType::Num,
438 move |args| {
439 let a1 = args.get(0)?.downcast_ref::<f64>()?;
440 let a2 = args.get(1)?.downcast_ref::<f64>()?;
441 Some(Box::new(sum(*a1, *a2))) },
443 );
444 let shared_ctx = ctx.freeze();
445
446 let expr = Expression::try_from_text("3 = sum(1, 2)")?;
447 let mut eval = ExEvaluator::new(shared_ctx);
448 eval.setup(expr)?;
449
450 let feat = Resource::new();
451
452 let res = eval.evaluate(&feat)?;
453 assert!(matches!(res, Outcome::T));
454
455 Ok(())
456 }
457
458 #[test]
459 fn test_queryables() -> Result<(), Box<dyn Error>> {
460 let sum = |x: f64, y: f64| x + y;
461
462 let mut ctx = Context::new();
463 ctx.register(
464 "sum",
465 vec![ExtDataType::Num, ExtDataType::Num],
466 ExtDataType::Num,
467 move |args| {
468 let a1 = args.get(0)?.downcast_ref::<f64>()?;
469 let a2 = args.get(1)?.downcast_ref::<f64>()?;
470 Some(Box::new(sum(*a1, *a2))) },
472 );
473 let shared_ctx = ctx.freeze();
474
475 let expr = Expression::try_from_text("30 = sum(a, b)")?;
476 let mut eval = ExEvaluator::new(shared_ctx);
477 eval.setup(expr)?;
478
479 let feat = Resource::from([
480 ("fid".into(), Q::try_from(1)?),
481 ("a".into(), Q::try_from(10)?),
482 ("b".into(), Q::try_from(20.0)?),
483 ]);
484
485 let res = eval.evaluate(&feat)?;
486 assert!(matches!(res, Outcome::T));
487
488 Ok(())
489 }
490
491 #[test]
492 fn test_wrong_data_type() -> Result<(), Box<dyn Error>> {
493 let sum = |x: f64, y: f64| x + y;
494
495 let mut ctx = Context::new();
496 ctx.register(
497 "sum",
498 vec![ExtDataType::Num, ExtDataType::Num],
499 ExtDataType::Num,
500 move |args| {
501 let a1 = args.get(0)?.downcast_ref::<f64>()?;
502 let a2 = args.get(1)?.downcast_ref::<f64>()?;
503 Some(Box::new(sum(*a1, *a2))) },
505 );
506 let shared_ctx = ctx.freeze();
507
508 let expr = Expression::try_from_text("30 = sum(a, b)")?;
509 let mut eval = ExEvaluator::new(shared_ctx);
510 eval.setup(expr)?;
511
512 let feat = Resource::from([
513 ("fid".into(), Q::try_from(1)?),
514 ("a".into(), Q::try_from(10)?),
515 ("b".into(), Q::new_plain_str("20.0")),
516 ]);
517
518 let res = eval.evaluate(&feat);
519 assert!(res.is_err());
520
521 Ok(())
522 }
523
524 #[test]
525 fn test_num_builtins() -> Result<(), Box<dyn Error>> {
526 let mut ctx = Context::new();
527 ctx.register_builtins();
528 let shared_ctx = ctx.freeze();
529
530 let expr = Expression::try_from_text("min(a, b) + max(a, b) = 2 * avg(a, b)")?;
531 let mut eval = ExEvaluator::new(shared_ctx);
532 eval.setup(expr)?;
533
534 let feat = Resource::from([
535 ("fid".into(), Q::try_from(1)?),
536 ("a".into(), Q::try_from(10)?),
537 ("b".into(), Q::try_from(20)?),
538 ]);
539
540 let res = eval.evaluate(&feat)?;
541 assert!(matches!(res, Outcome::T));
542
543 Ok(())
544 }
545
546 #[test]
547 fn test_geom_builtins() -> Result<(), Box<dyn Error>> {
548 let mut ctx = Context::try_with_crs("epsg:4326")?;
553 ctx.register_builtins();
554 let shared_ctx = ctx.freeze();
555
556 let expr = Expression::try_from_text(
557 "wkt(centroid(envelope(MULTIPOINT(0 90, 90 0))), 0) = 'POINT (45 45)'",
558 )?;
559 let mut eval = ExEvaluator::new(shared_ctx);
560 eval.setup(expr)?;
561
562 let feat = Resource::new();
563
564 let res = eval.evaluate(&feat)?;
565 assert!(matches!(res, Outcome::T));
566
567 Ok(())
568 }
569
570 #[test]
571 fn test_str_builtins() -> Result<(), Box<dyn Error>> {
572 let mut ctx = Context::new();
573 ctx.register_builtins();
574 let shared_ctx = ctx.freeze();
575
576 let expr = Expression::try_from_text("starts_with(concat('foo', 'bar'), 'fo')")?;
577 let mut eval = ExEvaluator::new(shared_ctx);
578 eval.setup(expr)?;
579
580 let feat = Resource::new();
581
582 let res = eval.evaluate(&feat)?;
583 tracing::debug!("res = {res:?}");
584 assert!(matches!(res, Outcome::T));
585
586 Ok(())
587 }
588}