datafusion_expr/udwf.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! [`WindowUDF`]: User Defined Window Functions
19
20use arrow::compute::SortOptions;
21use std::cmp::Ordering;
22use std::hash::{DefaultHasher, Hash, Hasher};
23use std::{
24 any::Any,
25 fmt::{self, Debug, Display, Formatter},
26 sync::Arc,
27};
28
29use arrow::datatypes::{DataType, Field};
30
31use crate::expr::WindowFunction;
32use crate::{
33 function::WindowFunctionSimplification, Expr, PartitionEvaluator, Signature,
34};
35use datafusion_common::{not_impl_err, Result};
36use datafusion_doc::Documentation;
37use datafusion_functions_window_common::expr::ExpressionArgs;
38use datafusion_functions_window_common::field::WindowUDFFieldArgs;
39use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
40use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
41
42/// Logical representation of a user-defined window function (UDWF).
43///
44/// A Window Function is called via the SQL `OVER` clause:
45///
46/// ```sql
47/// SELECT first_value(col) OVER (PARTITION BY a, b ORDER BY c) FROM foo;
48/// ```
49///
50/// A UDWF is different from a user defined function (UDF) in that it is
51/// stateful across batches.
52///
53/// See the documentation on [`PartitionEvaluator`] for more details
54///
55/// 1. For simple use cases, use [`create_udwf`] (examples in
56/// [`simple_udwf.rs`]).
57///
58/// 2. For advanced use cases, use [`WindowUDFImpl`] which provides full API
59/// access (examples in [`advanced_udwf.rs`]).
60///
61/// # API Note
62/// This is a separate struct from `WindowUDFImpl` to maintain backwards
63/// compatibility with the older API.
64///
65/// [`PartitionEvaluator`]: crate::PartitionEvaluator
66/// [`create_udwf`]: crate::expr_fn::create_udwf
67/// [`simple_udwf.rs`]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/simple_udwf.rs
68/// [`advanced_udwf.rs`]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/advanced_udwf.rs
69#[derive(Debug, Clone, PartialOrd)]
70pub struct WindowUDF {
71 inner: Arc<dyn WindowUDFImpl>,
72}
73
74/// Defines how the WindowUDF is shown to users
75impl Display for WindowUDF {
76 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
77 write!(f, "{}", self.name())
78 }
79}
80
81impl PartialEq for WindowUDF {
82 fn eq(&self, other: &Self) -> bool {
83 self.inner.equals(other.inner.as_ref())
84 }
85}
86
87impl Eq for WindowUDF {}
88
89impl Hash for WindowUDF {
90 fn hash<H: Hasher>(&self, state: &mut H) {
91 self.inner.hash_value().hash(state)
92 }
93}
94
95impl WindowUDF {
96 /// Create a new `WindowUDF` from a `[WindowUDFImpl]` trait object
97 ///
98 /// Note this is the same as using the `From` impl (`WindowUDF::from`)
99 pub fn new_from_impl<F>(fun: F) -> WindowUDF
100 where
101 F: WindowUDFImpl + 'static,
102 {
103 Self::new_from_shared_impl(Arc::new(fun))
104 }
105
106 /// Create a new `WindowUDF` from a `[WindowUDFImpl]` trait object
107 pub fn new_from_shared_impl(fun: Arc<dyn WindowUDFImpl>) -> WindowUDF {
108 Self { inner: fun }
109 }
110
111 /// Return the underlying [`WindowUDFImpl`] trait object for this function
112 pub fn inner(&self) -> &Arc<dyn WindowUDFImpl> {
113 &self.inner
114 }
115
116 /// Adds additional names that can be used to invoke this function, in
117 /// addition to `name`
118 ///
119 /// If you implement [`WindowUDFImpl`] directly you should return aliases directly.
120 pub fn with_aliases(self, aliases: impl IntoIterator<Item = &'static str>) -> Self {
121 Self::new_from_impl(AliasedWindowUDFImpl::new(Arc::clone(&self.inner), aliases))
122 }
123
124 /// creates a [`Expr`] that calls the window function with default
125 /// values for `order_by`, `partition_by`, `window_frame`.
126 ///
127 /// See [`ExprFunctionExt`] for details on setting these values.
128 ///
129 /// This utility allows using a user defined window function without
130 /// requiring access to the registry, such as with the DataFrame API.
131 ///
132 /// [`ExprFunctionExt`]: crate::expr_fn::ExprFunctionExt
133 pub fn call(&self, args: Vec<Expr>) -> Expr {
134 let fun = crate::WindowFunctionDefinition::WindowUDF(Arc::new(self.clone()));
135
136 Expr::WindowFunction(WindowFunction::new(fun, args))
137 }
138
139 /// Returns this function's name
140 ///
141 /// See [`WindowUDFImpl::name`] for more details.
142 pub fn name(&self) -> &str {
143 self.inner.name()
144 }
145
146 /// Returns the aliases for this function.
147 pub fn aliases(&self) -> &[String] {
148 self.inner.aliases()
149 }
150
151 /// Returns this function's signature (what input types are accepted)
152 ///
153 /// See [`WindowUDFImpl::signature`] for more details.
154 pub fn signature(&self) -> &Signature {
155 self.inner.signature()
156 }
157
158 /// Do the function rewrite
159 ///
160 /// See [`WindowUDFImpl::simplify`] for more details.
161 pub fn simplify(&self) -> Option<WindowFunctionSimplification> {
162 self.inner.simplify()
163 }
164
165 /// Expressions that are passed to the [`PartitionEvaluator`].
166 ///
167 /// See [`WindowUDFImpl::expressions`] for more details.
168 pub fn expressions(&self, expr_args: ExpressionArgs) -> Vec<Arc<dyn PhysicalExpr>> {
169 self.inner.expressions(expr_args)
170 }
171 /// Return a `PartitionEvaluator` for evaluating this window function
172 pub fn partition_evaluator_factory(
173 &self,
174 partition_evaluator_args: PartitionEvaluatorArgs,
175 ) -> Result<Box<dyn PartitionEvaluator>> {
176 self.inner.partition_evaluator(partition_evaluator_args)
177 }
178
179 /// Returns the field of the final result of evaluating this window function.
180 ///
181 /// See [`WindowUDFImpl::field`] for more details.
182 pub fn field(&self, field_args: WindowUDFFieldArgs) -> Result<Field> {
183 self.inner.field(field_args)
184 }
185
186 /// Returns custom result ordering introduced by this window function
187 /// which is used to update ordering equivalences.
188 ///
189 /// See [`WindowUDFImpl::sort_options`] for more details.
190 pub fn sort_options(&self) -> Option<SortOptions> {
191 self.inner.sort_options()
192 }
193
194 /// See [`WindowUDFImpl::coerce_types`] for more details.
195 pub fn coerce_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
196 self.inner.coerce_types(arg_types)
197 }
198
199 /// Returns the reversed user-defined window function when the
200 /// order of evaluation is reversed.
201 ///
202 /// See [`WindowUDFImpl::reverse_expr`] for more details.
203 pub fn reverse_expr(&self) -> ReversedUDWF {
204 self.inner.reverse_expr()
205 }
206
207 /// Returns the documentation for this Window UDF.
208 ///
209 /// Documentation can be accessed programmatically as well as
210 /// generating publicly facing documentation.
211 pub fn documentation(&self) -> Option<&Documentation> {
212 self.inner.documentation()
213 }
214}
215
216impl<F> From<F> for WindowUDF
217where
218 F: WindowUDFImpl + Send + Sync + 'static,
219{
220 fn from(fun: F) -> Self {
221 Self::new_from_impl(fun)
222 }
223}
224
225/// Trait for implementing [`WindowUDF`].
226///
227/// This trait exposes the full API for implementing user defined window functions and
228/// can be used to implement any function.
229///
230/// See [`advanced_udwf.rs`] for a full example with complete implementation and
231/// [`WindowUDF`] for other available options.
232///
233///
234/// [`advanced_udwf.rs`]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/advanced_udwf.rs
235/// # Basic Example
236/// ```
237/// # use std::any::Any;
238/// # use std::sync::LazyLock;
239/// # use arrow::datatypes::{DataType, Field};
240/// # use datafusion_common::{DataFusionError, plan_err, Result};
241/// # use datafusion_expr::{col, Signature, Volatility, PartitionEvaluator, WindowFrame, ExprFunctionExt, Documentation};
242/// # use datafusion_expr::{WindowUDFImpl, WindowUDF};
243/// # use datafusion_functions_window_common::field::WindowUDFFieldArgs;
244/// # use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
245/// # use datafusion_expr::window_doc_sections::DOC_SECTION_ANALYTICAL;
246///
247/// #[derive(Debug, Clone)]
248/// struct SmoothIt {
249/// signature: Signature,
250/// }
251///
252/// impl SmoothIt {
253/// fn new() -> Self {
254/// Self {
255/// signature: Signature::uniform(1, vec![DataType::Int32], Volatility::Immutable),
256/// }
257/// }
258/// }
259///
260/// static DOCUMENTATION: LazyLock<Documentation> = LazyLock::new(|| {
261/// Documentation::builder(DOC_SECTION_ANALYTICAL, "smooths the windows", "smooth_it(2)")
262/// .with_argument("arg1", "The int32 number to smooth by")
263/// .build()
264/// });
265///
266/// fn get_doc() -> &'static Documentation {
267/// &DOCUMENTATION
268/// }
269///
270/// /// Implement the WindowUDFImpl trait for SmoothIt
271/// impl WindowUDFImpl for SmoothIt {
272/// fn as_any(&self) -> &dyn Any { self }
273/// fn name(&self) -> &str { "smooth_it" }
274/// fn signature(&self) -> &Signature { &self.signature }
275/// // The actual implementation would smooth the window
276/// fn partition_evaluator(
277/// &self,
278/// _partition_evaluator_args: PartitionEvaluatorArgs,
279/// ) -> Result<Box<dyn PartitionEvaluator>> {
280/// unimplemented!()
281/// }
282/// fn field(&self, field_args: WindowUDFFieldArgs) -> Result<Field> {
283/// if let Some(DataType::Int32) = field_args.get_input_type(0) {
284/// Ok(Field::new(field_args.name(), DataType::Int32, false))
285/// } else {
286/// plan_err!("smooth_it only accepts Int32 arguments")
287/// }
288/// }
289/// fn documentation(&self) -> Option<&Documentation> {
290/// Some(get_doc())
291/// }
292/// }
293///
294/// // Create a new WindowUDF from the implementation
295/// let smooth_it = WindowUDF::from(SmoothIt::new());
296///
297/// // Call the function `add_one(col)`
298/// // smooth_it(speed) OVER (PARTITION BY car ORDER BY time ASC)
299/// let expr = smooth_it.call(vec![col("speed")])
300/// .partition_by(vec![col("car")])
301/// .order_by(vec![col("time").sort(true, true)])
302/// .window_frame(WindowFrame::new(None))
303/// .build()
304/// .unwrap();
305/// ```
306pub trait WindowUDFImpl: Debug + Send + Sync {
307 // Note: When adding any methods (with default implementations), remember to add them also
308 // into the AliasedWindowUDFImpl below!
309
310 /// Returns this object as an [`Any`] trait object
311 fn as_any(&self) -> &dyn Any;
312
313 /// Returns this function's name
314 fn name(&self) -> &str;
315
316 /// Returns the function's [`Signature`] for information about what input
317 /// types are accepted and the function's Volatility.
318 fn signature(&self) -> &Signature;
319
320 /// Returns the expressions that are passed to the [`PartitionEvaluator`].
321 fn expressions(&self, expr_args: ExpressionArgs) -> Vec<Arc<dyn PhysicalExpr>> {
322 expr_args.input_exprs().into()
323 }
324
325 /// Invoke the function, returning the [`PartitionEvaluator`] instance
326 fn partition_evaluator(
327 &self,
328 partition_evaluator_args: PartitionEvaluatorArgs,
329 ) -> Result<Box<dyn PartitionEvaluator>>;
330
331 /// Returns any aliases (alternate names) for this function.
332 ///
333 /// Note: `aliases` should only include names other than [`Self::name`].
334 /// Defaults to `[]` (no aliases)
335 fn aliases(&self) -> &[String] {
336 &[]
337 }
338
339 /// Optionally apply per-UDWF simplification / rewrite rules.
340 ///
341 /// This can be used to apply function specific simplification rules during
342 /// optimization. The default implementation does nothing.
343 ///
344 /// Note that DataFusion handles simplifying arguments and "constant
345 /// folding" (replacing a function call with constant arguments such as
346 /// `my_add(1,2) --> 3` ). Thus, there is no need to implement such
347 /// optimizations manually for specific UDFs.
348 ///
349 /// Example:
350 /// [`advanced_udwf.rs`]: <https://github.com/apache/arrow-datafusion/blob/main/datafusion-examples/examples/advanced_udwf.rs>
351 ///
352 /// # Returns
353 /// [None] if simplify is not defined or,
354 ///
355 /// Or, a closure with two arguments:
356 /// * 'window_function': [crate::expr::WindowFunction] for which simplified has been invoked
357 /// * 'info': [crate::simplify::SimplifyInfo]
358 fn simplify(&self) -> Option<WindowFunctionSimplification> {
359 None
360 }
361
362 /// Return true if this window UDF is equal to the other.
363 ///
364 /// Allows customizing the equality of window UDFs.
365 /// Must be consistent with [`Self::hash_value`] and follow the same rules as [`Eq`]:
366 ///
367 /// - reflexive: `a.equals(a)`;
368 /// - symmetric: `a.equals(b)` implies `b.equals(a)`;
369 /// - transitive: `a.equals(b)` and `b.equals(c)` implies `a.equals(c)`.
370 ///
371 /// By default, compares [`Self::name`] and [`Self::signature`].
372 fn equals(&self, other: &dyn WindowUDFImpl) -> bool {
373 self.name() == other.name() && self.signature() == other.signature()
374 }
375
376 /// Returns a hash value for this window UDF.
377 ///
378 /// Allows customizing the hash code of window UDFs. Similarly to [`Hash`] and [`Eq`],
379 /// if [`Self::equals`] returns true for two UDFs, their `hash_value`s must be the same.
380 ///
381 /// By default, hashes [`Self::name`] and [`Self::signature`].
382 fn hash_value(&self) -> u64 {
383 let hasher = &mut DefaultHasher::new();
384 self.name().hash(hasher);
385 self.signature().hash(hasher);
386 hasher.finish()
387 }
388
389 /// The [`Field`] of the final result of evaluating this window function.
390 ///
391 /// Call `field_args.name()` to get the fully qualified name for defining
392 /// the [`Field`]. For a complete example see the implementation in the
393 /// [Basic Example](WindowUDFImpl#basic-example) section.
394 fn field(&self, field_args: WindowUDFFieldArgs) -> Result<Field>;
395
396 /// Allows the window UDF to define a custom result ordering.
397 ///
398 /// By default, a window UDF doesn't introduce an ordering.
399 /// But when specified by a window UDF this is used to update
400 /// ordering equivalences.
401 fn sort_options(&self) -> Option<SortOptions> {
402 None
403 }
404
405 /// Coerce arguments of a function call to types that the function can evaluate.
406 ///
407 /// This function is only called if [`WindowUDFImpl::signature`] returns [`crate::TypeSignature::UserDefined`]. Most
408 /// UDWFs should return one of the other variants of `TypeSignature` which handle common
409 /// cases
410 ///
411 /// See the [type coercion module](crate::type_coercion)
412 /// documentation for more details on type coercion
413 ///
414 /// For example, if your function requires a floating point arguments, but the user calls
415 /// it like `my_func(1::int)` (aka with `1` as an integer), coerce_types could return `[DataType::Float64]`
416 /// to ensure the argument was cast to `1::double`
417 ///
418 /// # Parameters
419 /// * `arg_types`: The argument types of the arguments this function with
420 ///
421 /// # Return value
422 /// A Vec the same length as `arg_types`. DataFusion will `CAST` the function call
423 /// arguments to these specific types.
424 fn coerce_types(&self, _arg_types: &[DataType]) -> Result<Vec<DataType>> {
425 not_impl_err!("Function {} does not implement coerce_types", self.name())
426 }
427
428 /// Allows customizing the behavior of the user-defined window
429 /// function when it is evaluated in reverse order.
430 fn reverse_expr(&self) -> ReversedUDWF {
431 ReversedUDWF::NotSupported
432 }
433
434 /// Returns the documentation for this Window UDF.
435 ///
436 /// Documentation can be accessed programmatically as well as
437 /// generating publicly facing documentation.
438 fn documentation(&self) -> Option<&Documentation> {
439 None
440 }
441}
442
443pub enum ReversedUDWF {
444 /// The result of evaluating the user-defined window function
445 /// remains identical when reversed.
446 Identical,
447 /// A window function which does not support evaluating the result
448 /// in reverse order.
449 NotSupported,
450 /// Customize the user-defined window function for evaluating the
451 /// result in reverse order.
452 Reversed(Arc<WindowUDF>),
453}
454
455impl PartialEq for dyn WindowUDFImpl {
456 fn eq(&self, other: &Self) -> bool {
457 self.equals(other)
458 }
459}
460
461impl PartialOrd for dyn WindowUDFImpl {
462 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
463 match self.name().partial_cmp(other.name()) {
464 Some(Ordering::Equal) => self.signature().partial_cmp(other.signature()),
465 cmp => cmp,
466 }
467 }
468}
469
470/// WindowUDF that adds an alias to the underlying function. It is better to
471/// implement [`WindowUDFImpl`], which supports aliases, directly if possible.
472#[derive(Debug)]
473struct AliasedWindowUDFImpl {
474 inner: Arc<dyn WindowUDFImpl>,
475 aliases: Vec<String>,
476}
477
478impl AliasedWindowUDFImpl {
479 pub fn new(
480 inner: Arc<dyn WindowUDFImpl>,
481 new_aliases: impl IntoIterator<Item = &'static str>,
482 ) -> Self {
483 let mut aliases = inner.aliases().to_vec();
484 aliases.extend(new_aliases.into_iter().map(|s| s.to_string()));
485
486 Self { inner, aliases }
487 }
488}
489
490impl WindowUDFImpl for AliasedWindowUDFImpl {
491 fn as_any(&self) -> &dyn Any {
492 self
493 }
494
495 fn name(&self) -> &str {
496 self.inner.name()
497 }
498
499 fn signature(&self) -> &Signature {
500 self.inner.signature()
501 }
502
503 fn expressions(&self, expr_args: ExpressionArgs) -> Vec<Arc<dyn PhysicalExpr>> {
504 expr_args
505 .input_exprs()
506 .first()
507 .map_or(vec![], |expr| vec![Arc::clone(expr)])
508 }
509
510 fn partition_evaluator(
511 &self,
512 partition_evaluator_args: PartitionEvaluatorArgs,
513 ) -> Result<Box<dyn PartitionEvaluator>> {
514 self.inner.partition_evaluator(partition_evaluator_args)
515 }
516
517 fn aliases(&self) -> &[String] {
518 &self.aliases
519 }
520
521 fn simplify(&self) -> Option<WindowFunctionSimplification> {
522 self.inner.simplify()
523 }
524
525 fn equals(&self, other: &dyn WindowUDFImpl) -> bool {
526 if let Some(other) = other.as_any().downcast_ref::<AliasedWindowUDFImpl>() {
527 self.inner.equals(other.inner.as_ref()) && self.aliases == other.aliases
528 } else {
529 false
530 }
531 }
532
533 fn hash_value(&self) -> u64 {
534 let hasher = &mut DefaultHasher::new();
535 self.inner.hash_value().hash(hasher);
536 self.aliases.hash(hasher);
537 hasher.finish()
538 }
539
540 fn field(&self, field_args: WindowUDFFieldArgs) -> Result<Field> {
541 self.inner.field(field_args)
542 }
543
544 fn sort_options(&self) -> Option<SortOptions> {
545 self.inner.sort_options()
546 }
547
548 fn coerce_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
549 self.inner.coerce_types(arg_types)
550 }
551
552 fn documentation(&self) -> Option<&Documentation> {
553 self.inner.documentation()
554 }
555}
556
557// Window UDF doc sections for use in public documentation
558pub mod window_doc_sections {
559 use datafusion_doc::DocSection;
560
561 pub fn doc_sections() -> Vec<DocSection> {
562 vec![
563 DOC_SECTION_AGGREGATE,
564 DOC_SECTION_RANKING,
565 DOC_SECTION_ANALYTICAL,
566 ]
567 }
568
569 pub const DOC_SECTION_AGGREGATE: DocSection = DocSection {
570 include: true,
571 label: "Aggregate Functions",
572 description: Some("All aggregate functions can be used as window functions."),
573 };
574
575 pub const DOC_SECTION_RANKING: DocSection = DocSection {
576 include: true,
577 label: "Ranking Functions",
578 description: None,
579 };
580
581 pub const DOC_SECTION_ANALYTICAL: DocSection = DocSection {
582 include: true,
583 label: "Analytical Functions",
584 description: None,
585 };
586}
587
588#[cfg(test)]
589mod test {
590 use crate::{PartitionEvaluator, WindowUDF, WindowUDFImpl};
591 use arrow::datatypes::{DataType, Field};
592 use datafusion_common::Result;
593 use datafusion_expr_common::signature::{Signature, Volatility};
594 use datafusion_functions_window_common::field::WindowUDFFieldArgs;
595 use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
596 use std::any::Any;
597 use std::cmp::Ordering;
598
599 #[derive(Debug, Clone)]
600 struct AWindowUDF {
601 signature: Signature,
602 }
603
604 impl AWindowUDF {
605 fn new() -> Self {
606 Self {
607 signature: Signature::uniform(
608 1,
609 vec![DataType::Int32],
610 Volatility::Immutable,
611 ),
612 }
613 }
614 }
615
616 /// Implement the WindowUDFImpl trait for AddOne
617 impl WindowUDFImpl for AWindowUDF {
618 fn as_any(&self) -> &dyn Any {
619 self
620 }
621 fn name(&self) -> &str {
622 "a"
623 }
624 fn signature(&self) -> &Signature {
625 &self.signature
626 }
627 fn partition_evaluator(
628 &self,
629 _partition_evaluator_args: PartitionEvaluatorArgs,
630 ) -> Result<Box<dyn PartitionEvaluator>> {
631 unimplemented!()
632 }
633 fn field(&self, _field_args: WindowUDFFieldArgs) -> Result<Field> {
634 unimplemented!()
635 }
636 }
637
638 #[derive(Debug, Clone)]
639 struct BWindowUDF {
640 signature: Signature,
641 }
642
643 impl BWindowUDF {
644 fn new() -> Self {
645 Self {
646 signature: Signature::uniform(
647 1,
648 vec![DataType::Int32],
649 Volatility::Immutable,
650 ),
651 }
652 }
653 }
654
655 /// Implement the WindowUDFImpl trait for AddOne
656 impl WindowUDFImpl for BWindowUDF {
657 fn as_any(&self) -> &dyn Any {
658 self
659 }
660 fn name(&self) -> &str {
661 "b"
662 }
663 fn signature(&self) -> &Signature {
664 &self.signature
665 }
666 fn partition_evaluator(
667 &self,
668 _partition_evaluator_args: PartitionEvaluatorArgs,
669 ) -> Result<Box<dyn PartitionEvaluator>> {
670 unimplemented!()
671 }
672 fn field(&self, _field_args: WindowUDFFieldArgs) -> Result<Field> {
673 unimplemented!()
674 }
675 }
676
677 #[test]
678 fn test_partial_ord() {
679 let a1 = WindowUDF::from(AWindowUDF::new());
680 let a2 = WindowUDF::from(AWindowUDF::new());
681 assert_eq!(a1.partial_cmp(&a2), Some(Ordering::Equal));
682
683 let b1 = WindowUDF::from(BWindowUDF::new());
684 assert!(a1 < b1);
685 assert!(!(a1 == b1));
686 }
687}