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