Skip to main content

ravenrs_criteria/
functions.rs

1//! The function catalog: one table that drives parsing, arity checks and the
2//! documentation an expression editor shows.
3
4/// What a function is for, as an expression editor groups them.
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6#[non_exhaustive]
7pub enum Category {
8    /// Conditions and null handling.
9    Logical,
10    /// Text.
11    String,
12    /// Numbers.
13    Math,
14    /// Dates and times.
15    DateTime,
16    /// Formatting and the page or row being printed.
17    Reporting,
18    /// Summaries over rows.
19    Aggregate,
20}
21
22/// A catalog entry.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct FunctionInfo {
25    /// The name, as written in expressions.
26    pub name: &'static str,
27    /// The group it belongs to.
28    pub category: Category,
29    /// How it is called, such as `Round(value, digits)`.
30    pub signature: &'static str,
31    /// What it does.
32    pub description: &'static str,
33}
34
35macro_rules! functions {
36    ($($variant:ident $name:literal $min:literal $max:literal $cat:ident $sig:literal $doc:literal;)*) => {
37        /// The built-in scalar functions. Names are case-insensitive in expressions.
38        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39        #[non_exhaustive]
40        #[allow(missing_docs, reason = "documented by the catalog, see Function::info")]
41        pub enum Function { $($variant,)* }
42
43        const FUNCTIONS: &[(Function, usize, usize, FunctionInfo)] = &[
44            $((Function::$variant, $min, $max, FunctionInfo {
45                name: $name, category: Category::$cat, signature: $sig, description: $doc,
46            }),)*
47        ];
48    };
49}
50
51functions! {
52    // Logical
53    Iif "Iif" 3 255 Logical "Iif(cond, value, ..., else)" "The value of the first condition that holds, else the last argument.";
54    IsNull "IsNull" 1 2 Logical "IsNull(value[, fallback])" "With one argument, whether it is null; with two, the first that is not null.";
55    IsNullOrEmpty "IsNullOrEmpty" 1 1 Logical "IsNullOrEmpty(text)" "Whether the text is null or empty.";
56    // String
57    Ascii "Ascii" 1 1 String "Ascii(text)" "The code of the first character.";
58    Char "Char" 1 1 String "Char(code)" "The character with this code.";
59    CharIndex "CharIndex" 2 3 String "CharIndex(needle, text[, start])" "The position of needle in text, or -1.";
60    Concat "Concat" 1 255 String "Concat(a, b, ...)" "Joins values as text.";
61    Contains "Contains" 2 2 String "Contains(text, part)" "Whether text contains part.";
62    EndsWith "EndsWith" 2 2 String "EndsWith(text, part)" "Whether text ends with part.";
63    Insert "Insert" 3 3 String "Insert(text, position, part)" "Inserts part at a position.";
64    Len "Len" 1 1 String "Len(text)" "The number of characters.";
65    Lower "Lower" 1 1 String "Lower(text)" "Lowercase.";
66    NewLine "NewLine" 0 0 String "NewLine()" "A line break.";
67    PadLeft "PadLeft" 2 3 String "PadLeft(text, width[, char])" "Pads on the left up to a width.";
68    PadRight "PadRight" 2 3 String "PadRight(text, width[, char])" "Pads on the right up to a width.";
69    Remove "Remove" 2 3 String "Remove(text, start[, count])" "Removes characters.";
70    Replace "Replace" 3 3 String "Replace(text, old, new)" "Replaces every occurrence.";
71    Reverse "Reverse" 1 1 String "Reverse(text)" "The characters in reverse order.";
72    StartsWith "StartsWith" 2 2 String "StartsWith(text, part)" "Whether text starts with part.";
73    Substring "Substring" 2 3 String "Substring(text, start[, count])" "Part of a text, from position 0.";
74    ToStr "ToStr" 1 1 String "ToStr(value)" "The value as text.";
75    Trim "Trim" 1 1 String "Trim(text)" "Removes spaces around the text.";
76    Upper "Upper" 1 1 String "Upper(text)" "Uppercase.";
77    // Math
78    Abs "Abs" 1 1 Math "Abs(x)" "Absolute value.";
79    Acos "Acos" 1 1 Math "Acos(x)" "Arc cosine.";
80    Asin "Asin" 1 1 Math "Asin(x)" "Arc sine.";
81    Atn "Atn" 1 1 Math "Atn(x)" "Arc tangent.";
82    Atn2 "Atn2" 2 2 Math "Atn2(y, x)" "Arc tangent of y/x.";
83    BigMul "BigMul" 2 2 Math "BigMul(a, b)" "The product of two integers.";
84    Ceiling "Ceiling" 1 1 Math "Ceiling(x)" "The next integer up.";
85    Cos "Cos" 1 1 Math "Cos(x)" "Cosine.";
86    Cosh "Cosh" 1 1 Math "Cosh(x)" "Hyperbolic cosine.";
87    Exp "Exp" 1 1 Math "Exp(x)" "e to the power x.";
88    Floor "Floor" 1 1 Math "Floor(x)" "The next integer down.";
89    Log "Log" 1 2 Math "Log(x[, base])" "Logarithm, natural by default.";
90    Log10 "Log10" 1 1 Math "Log10(x)" "Base 10 logarithm.";
91    Max2 "Max" 2 2 Math "Max(a, b)" "The larger of two values.";
92    Min2 "Min" 2 2 Math "Min(a, b)" "The smaller of two values.";
93    Power "Power" 2 2 Math "Power(x, y)" "x to the power y.";
94    Rnd "Rnd" 0 0 Math "Rnd()" "A random number from 0 up to 1.";
95    Round "Round" 1 2 Math "Round(x[, digits])" "Rounds halves away from zero.";
96    Sign "Sign" 1 1 Math "Sign(x)" "-1, 0 or 1.";
97    Sin "Sin" 1 1 Math "Sin(x)" "Sine.";
98    Sinh "Sinh" 1 1 Math "Sinh(x)" "Hyperbolic sine.";
99    Sqr "Sqr" 1 1 Math "Sqr(x)" "Square root.";
100    Sqrt "Sqrt" 1 1 Math "Sqrt(x)" "Square root.";
101    Tan "Tan" 1 1 Math "Tan(x)" "Tangent.";
102    Tanh "Tanh" 1 1 Math "Tanh(x)" "Hyperbolic tangent.";
103    ToDecimal "ToDecimal" 1 1 Math "ToDecimal(x)" "The value as a number.";
104    ToDouble "ToDouble" 1 1 Math "ToDouble(x)" "The value as a number.";
105    ToFloat "ToFloat" 1 1 Math "ToFloat(x)" "The value as a number.";
106    ToInt "ToInt" 1 1 Math "ToInt(x)" "The value as an integer, truncated.";
107    ToLong "ToLong" 1 1 Math "ToLong(x)" "The value as an integer, truncated.";
108    ToNumber "ToNumber" 1 1 Math "ToNumber(x)" "The value as a number.";
109    // Date and time: arithmetic
110    AddDays "AddDays" 2 2 DateTime "AddDays(date, n)" "n days later.";
111    AddHours "AddHours" 2 2 DateTime "AddHours(date, n)" "n hours later.";
112    AddMinutes "AddMinutes" 2 2 DateTime "AddMinutes(date, n)" "n minutes later.";
113    AddMonths "AddMonths" 2 2 DateTime "AddMonths(date, n)" "n months later.";
114    AddSeconds "AddSeconds" 2 2 DateTime "AddSeconds(date, n)" "n seconds later.";
115    AddYears "AddYears" 2 2 DateTime "AddYears(date, n)" "n years later.";
116    DateDiffDay "DateDiffDay" 2 2 DateTime "DateDiffDay(from, to)" "Day boundaries between two dates.";
117    DateDiffHour "DateDiffHour" 2 2 DateTime "DateDiffHour(from, to)" "Whole hours between two dates.";
118    DateDiffMinute "DateDiffMinute" 2 2 DateTime "DateDiffMinute(from, to)" "Whole minutes between two dates.";
119    DateDiffMonth "DateDiffMonth" 2 2 DateTime "DateDiffMonth(from, to)" "Month boundaries between two dates.";
120    DateDiffSecond "DateDiffSecond" 2 2 DateTime "DateDiffSecond(from, to)" "Whole seconds between two dates.";
121    DateDiffYear "DateDiffYear" 2 2 DateTime "DateDiffYear(from, to)" "Year boundaries between two dates.";
122    DateTimeFromParts "DateTimeFromParts" 3 6 DateTime "DateTimeFromParts(y, m, d[, h, min, s])" "A date from its parts.";
123    // Date and time: parts
124    GetDate "GetDate" 1 1 DateTime "GetDate(date)" "The date without the time.";
125    GetDay "GetDay" 1 1 DateTime "GetDay(date)" "The day of the month.";
126    GetDayOfWeek "GetDayOfWeek" 1 1 DateTime "GetDayOfWeek(date)" "The day of the week, 0 is Sunday.";
127    GetDayOfYear "GetDayOfYear" 1 1 DateTime "GetDayOfYear(date)" "The day of the year, from 1.";
128    GetHour "GetHour" 1 1 DateTime "GetHour(date)" "The hour.";
129    GetMinute "GetMinute" 1 1 DateTime "GetMinute(date)" "The minute.";
130    GetMonth "GetMonth" 1 1 DateTime "GetMonth(date)" "The month, 1 to 12.";
131    GetSecond "GetSecond" 1 1 DateTime "GetSecond(date)" "The second.";
132    GetYear "GetYear" 1 1 DateTime "GetYear(date)" "The year.";
133    Now "Now" 0 0 DateTime "Now()" "The current date and time.";
134    Today "Today" 0 0 DateTime "Today()" "The current date.";
135    UtcNow "UtcNow" 0 0 DateTime "UtcNow()" "The current date and time; the environment's clock.";
136    // Date and time: relative dates
137    LocalDateTimeToday "LocalDateTimeToday" 0 0 DateTime "LocalDateTimeToday()" "The start of today.";
138    LocalDateTimeNow "LocalDateTimeNow" 0 0 DateTime "LocalDateTimeNow()" "Now.";
139    LocalDateTimeTomorrow "LocalDateTimeTomorrow" 0 0 DateTime "LocalDateTimeTomorrow()" "The start of tomorrow.";
140    LocalDateTimeYesterday "LocalDateTimeYesterday" 0 0 DateTime "LocalDateTimeYesterday()" "The start of yesterday.";
141    LocalDateTimeDayAfterTomorrow "LocalDateTimeDayAfterTomorrow" 0 0 DateTime "LocalDateTimeDayAfterTomorrow()" "The start of the day after tomorrow.";
142    LocalDateTimeThisWeek "LocalDateTimeThisWeek" 0 0 DateTime "LocalDateTimeThisWeek()" "The start of this week (Sunday).";
143    LocalDateTimeLastWeek "LocalDateTimeLastWeek" 0 0 DateTime "LocalDateTimeLastWeek()" "The start of last week.";
144    LocalDateTimeNextWeek "LocalDateTimeNextWeek" 0 0 DateTime "LocalDateTimeNextWeek()" "The start of next week.";
145    LocalDateTimeTwoWeeksAway "LocalDateTimeTwoWeeksAway" 0 0 DateTime "LocalDateTimeTwoWeeksAway()" "The start of the week after next.";
146    LocalDateTimeThisMonth "LocalDateTimeThisMonth" 0 0 DateTime "LocalDateTimeThisMonth()" "The first day of this month.";
147    LocalDateTimeLastMonth "LocalDateTimeLastMonth" 0 0 DateTime "LocalDateTimeLastMonth()" "The first day of last month.";
148    LocalDateTimeNextMonth "LocalDateTimeNextMonth" 0 0 DateTime "LocalDateTimeNextMonth()" "The first day of next month.";
149    LocalDateTimeTwoMonthsAway "LocalDateTimeTwoMonthsAway" 0 0 DateTime "LocalDateTimeTwoMonthsAway()" "The first day of the month after next.";
150    LocalDateTimeThisYear "LocalDateTimeThisYear" 0 0 DateTime "LocalDateTimeThisYear()" "The first day of this year.";
151    LocalDateTimeLastYear "LocalDateTimeLastYear" 0 0 DateTime "LocalDateTimeLastYear()" "The first day of last year.";
152    LocalDateTimeNextYear "LocalDateTimeNextYear" 0 0 DateTime "LocalDateTimeNextYear()" "The first day of next year.";
153    LocalDateTimeTwoYearsAway "LocalDateTimeTwoYearsAway" 0 0 DateTime "LocalDateTimeTwoYearsAway()" "The first day of the year after next.";
154    LocalDateTimeYearBeforeToday "LocalDateTimeYearBeforeToday" 0 0 DateTime "LocalDateTimeYearBeforeToday()" "Today, one year ago.";
155    // Date and time: predicates
156    InDateRange "InDateRange" 3 3 DateTime "InDateRange(date, from, to)" "Whether a date's day is in a range, ends included.";
157    InTimeRange "InTimeRange" 3 3 DateTime "InTimeRange(date, from, to)" "Whether a time of day is in a range, which may span midnight.";
158    OutOfTimeRange "OutOfTimeRange" 3 3 DateTime "OutOfTimeRange(date, from, to)" "Whether a time of day is outside a range.";
159    IsSameDay "IsSameDay" 2 255 DateTime "IsSameDay(date, other, ...)" "Whether a date falls on the same day as any other.";
160    IsThisWeek "IsThisWeek" 1 1 DateTime "IsThisWeek(date)" "Whether the date is in this week.";
161    IsThisMonth "IsThisMonth" 1 1 DateTime "IsThisMonth(date)" "Whether the date is in this month.";
162    IsLastMonth "IsLastMonth" 1 1 DateTime "IsLastMonth(date)" "Whether the date is in last month.";
163    IsNextMonth "IsNextMonth" 1 1 DateTime "IsNextMonth(date)" "Whether the date is in next month.";
164    IsThisYear "IsThisYear" 1 1 DateTime "IsThisYear(date)" "Whether the date is in this year.";
165    IsLastYear "IsLastYear" 1 1 DateTime "IsLastYear(date)" "Whether the date is in last year.";
166    IsNextYear "IsNextYear" 1 1 DateTime "IsNextYear(date)" "Whether the date is in next year.";
167    IsYearToDate "IsYearToDate" 1 1 DateTime "IsYearToDate(date)" "Whether the date is from the start of this year up to today.";
168    IsJanuary "IsJanuary" 1 1 DateTime "IsJanuary(date)" "Whether the date is in January.";
169    IsFebruary "IsFebruary" 1 1 DateTime "IsFebruary(date)" "Whether the date is in February.";
170    IsMarch "IsMarch" 1 1 DateTime "IsMarch(date)" "Whether the date is in March.";
171    IsApril "IsApril" 1 1 DateTime "IsApril(date)" "Whether the date is in April.";
172    IsMay "IsMay" 1 1 DateTime "IsMay(date)" "Whether the date is in May.";
173    IsJune "IsJune" 1 1 DateTime "IsJune(date)" "Whether the date is in June.";
174    IsJuly "IsJuly" 1 1 DateTime "IsJuly(date)" "Whether the date is in July.";
175    IsAugust "IsAugust" 1 1 DateTime "IsAugust(date)" "Whether the date is in August.";
176    IsSeptember "IsSeptember" 1 1 DateTime "IsSeptember(date)" "Whether the date is in September.";
177    IsOctober "IsOctober" 1 1 DateTime "IsOctober(date)" "Whether the date is in October.";
178    IsNovember "IsNovember" 1 1 DateTime "IsNovember(date)" "Whether the date is in November.";
179    IsDecember "IsDecember" 1 1 DateTime "IsDecember(date)" "Whether the date is in December.";
180    // Reporting and row context
181    FormatString "FormatString" 1 255 Reporting "FormatString('{0:C2}', value, ...)" "Formats values with .NET-style format strings.";
182    Rgb "Rgb" 3 3 Reporting "Rgb(r, g, b)" "A color as #rrggbb text.";
183    Argb "Argb" 4 4 Reporting "Argb(a, r, g, b)" "A color as #rrggbbaa text.";
184    PageNumber "PageNumber" 0 0 Reporting "PageNumber()" "The page being printed.";
185    PageCount "PageCount" 0 0 Reporting "PageCount()" "The number of pages.";
186    CurrentRowIndexInGroup "CurrentRowIndexInGroup" 0 0 Reporting "CurrentRowIndexInGroup()" "The row's position in its group, from 0.";
187    PrevRowColumnValue "PrevRowColumnValue" 1 1 Reporting "PrevRowColumnValue(value)" "The value on the previous row of the scope.";
188    NextRowColumnValue "NextRowColumnValue" 1 1 Reporting "NextRowColumnValue(value)" "The value on the next row of the scope.";
189}
190
191impl Function {
192    /// The catalog entry.
193    pub fn info(self) -> &'static FunctionInfo {
194        // The table holds every variant, in declaration order.
195        &FUNCTIONS[self as usize].3
196    }
197
198    /// The name, as written in expressions.
199    pub fn name(self) -> &'static str {
200        self.info().name
201    }
202
203    /// The fewest and most arguments it takes.
204    pub fn arity(self) -> (usize, usize) {
205        let e = &FUNCTIONS[self as usize];
206        (e.1, e.2)
207    }
208
209    /// The function with this name, in any case. `Min` and `Max` are the
210    /// two-argument forms; with one argument they are aggregates.
211    pub fn from_name(name: &str) -> Option<Self> {
212        FUNCTIONS.iter().find(|e| e.3.name.eq_ignore_ascii_case(name)).map(|e| e.0)
213    }
214
215    /// Every built-in function.
216    pub fn all() -> impl Iterator<Item = Function> {
217        FUNCTIONS.iter().map(|e| e.0)
218    }
219}
220
221/// Aggregate functions: summaries over the rows in scope or a nested collection.
222#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
223#[non_exhaustive]
224#[allow(missing_docs, reason = "documented by Aggregate::info")]
225pub enum Aggregate {
226    Sum,
227    Count,
228    Avg,
229    Min,
230    Max,
231    Exists,
232    Single,
233    Join,
234    First,
235    Last,
236    Median,
237    Var,
238    VarP,
239    StdDev,
240    StdDevP,
241    RunningSum,
242    RecordNumber,
243    Percentage,
244    WeightedAvg,
245    DistinctSum,
246    DistinctCount,
247    DistinctAvg,
248    DistinctVar,
249    DistinctVarP,
250    DistinctStdDev,
251    DistinctStdDevP,
252}
253
254/// (aggregate, name, alias, fewest arguments, most arguments, signature, description)
255const AGGREGATES: &[(Aggregate, &str, &str, usize, usize, &str, &str)] = &[
256    (Aggregate::Sum, "Sum", "sumSum", 1, 1, "Sum(value)", "The total."),
257    (
258        Aggregate::Count,
259        "Count",
260        "sumCount",
261        0,
262        1,
263        "Count([value])",
264        "The number of rows, or of non-null values.",
265    ),
266    (Aggregate::Avg, "Avg", "sumAvg", 1, 1, "Avg(value)", "The mean."),
267    (Aggregate::Min, "Min", "sumMin", 1, 1, "Min(value)", "The smallest value."),
268    (Aggregate::Max, "Max", "sumMax", 1, 1, "Max(value)", "The largest value."),
269    (
270        Aggregate::Exists,
271        "Exists",
272        "",
273        0,
274        1,
275        "Exists([condition])",
276        "Whether any row exists, or satisfies the condition.",
277    ),
278    (Aggregate::Single, "Single", "", 0, 1, "Single([value])", "The value of the only row; null otherwise."),
279    (Aggregate::Join, "Join", "", 1, 2, "Join(value[, separator])", "The values joined as text."),
280    (Aggregate::First, "First", "", 1, 1, "First(value)", "The value on the first row."),
281    (Aggregate::Last, "Last", "", 1, 1, "Last(value)", "The value on the last row."),
282    (Aggregate::Median, "Median", "sumMedian", 1, 1, "Median(value)", "The middle value."),
283    (Aggregate::Var, "Var", "sumVar", 1, 1, "Var(value)", "Sample variance."),
284    (Aggregate::VarP, "VarP", "sumVarP", 1, 1, "VarP(value)", "Population variance."),
285    (Aggregate::StdDev, "StdDev", "sumStdDev", 1, 1, "StdDev(value)", "Sample standard deviation."),
286    (Aggregate::StdDevP, "StdDevP", "sumStdDevP", 1, 1, "StdDevP(value)", "Population standard deviation."),
287    (
288        Aggregate::RunningSum,
289        "RunningSum",
290        "sumRunningSum",
291        1,
292        1,
293        "RunningSum(value)",
294        "The total up to the current row.",
295    ),
296    (
297        Aggregate::RecordNumber,
298        "RecordNumber",
299        "sumRecordNumber",
300        0,
301        0,
302        "RecordNumber()",
303        "The row's number in the scope, from 1.",
304    ),
305    (
306        Aggregate::Percentage,
307        "Percentage",
308        "sumPercentage",
309        1,
310        1,
311        "Percentage(value)",
312        "The row's value as a fraction of the total.",
313    ),
314    (
315        Aggregate::WeightedAvg,
316        "WeightedAvg",
317        "sumWAvg",
318        2,
319        2,
320        "WeightedAvg(value, weight)",
321        "The mean weighted by a second value.",
322    ),
323    (
324        Aggregate::DistinctSum,
325        "DistinctSum",
326        "sumDSum",
327        1,
328        1,
329        "DistinctSum(value)",
330        "The total of the distinct values.",
331    ),
332    (
333        Aggregate::DistinctCount,
334        "DistinctCount",
335        "sumDCount",
336        1,
337        1,
338        "DistinctCount(value)",
339        "The number of distinct values.",
340    ),
341    (
342        Aggregate::DistinctAvg,
343        "DistinctAvg",
344        "sumDAvg",
345        1,
346        1,
347        "DistinctAvg(value)",
348        "The mean of the distinct values.",
349    ),
350    (
351        Aggregate::DistinctVar,
352        "DistinctVar",
353        "sumDVar",
354        1,
355        1,
356        "DistinctVar(value)",
357        "Sample variance of the distinct values.",
358    ),
359    (
360        Aggregate::DistinctVarP,
361        "DistinctVarP",
362        "sumDVarP",
363        1,
364        1,
365        "DistinctVarP(value)",
366        "Population variance of the distinct values.",
367    ),
368    (
369        Aggregate::DistinctStdDev,
370        "DistinctStdDev",
371        "sumDStdDev",
372        1,
373        1,
374        "DistinctStdDev(value)",
375        "Sample standard deviation of the distinct values.",
376    ),
377    (
378        Aggregate::DistinctStdDevP,
379        "DistinctStdDevP",
380        "sumDStdDevP",
381        1,
382        1,
383        "DistinctStdDevP(value)",
384        "Population standard deviation of the distinct values.",
385    ),
386];
387
388impl Aggregate {
389    fn entry(
390        self,
391    ) -> &'static (Aggregate, &'static str, &'static str, usize, usize, &'static str, &'static str) {
392        &AGGREGATES[self as usize]
393    }
394
395    /// The name, as written in expressions.
396    pub fn name(self) -> &'static str {
397        self.entry().1
398    }
399
400    /// The fewest and most arguments it takes.
401    pub fn arity(self) -> (usize, usize) {
402        (self.entry().3, self.entry().4)
403    }
404
405    /// The catalog entry.
406    pub fn info(self) -> FunctionInfo {
407        let e = self.entry();
408        FunctionInfo { name: e.1, category: Category::Aggregate, signature: e.5, description: e.6 }
409    }
410
411    /// The aggregate with this name or `sum`-prefixed alias, in any case.
412    pub fn from_name(name: &str) -> Option<Self> {
413        AGGREGATES
414            .iter()
415            .find(|e| e.1.eq_ignore_ascii_case(name) || (!e.2.is_empty() && e.2.eq_ignore_ascii_case(name)))
416            .map(|e| e.0)
417    }
418
419    /// Every aggregate.
420    pub fn all() -> impl Iterator<Item = Aggregate> {
421        AGGREGATES.iter().map(|e| e.0)
422    }
423}
424
425/// The whole catalog, for expression editors: every function and aggregate.
426pub fn catalog() -> Vec<FunctionInfo> {
427    Function::all().map(|f| *f.info()).chain(Aggregate::all().map(Aggregate::info)).collect()
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433
434    #[test]
435    fn tables_match_their_enums() {
436        for (i, f) in Function::all().enumerate() {
437            assert_eq!(f as usize, i, "{f:?}");
438            assert_eq!(Function::from_name(f.name()).map(Function::name), Some(f.name()));
439            assert!(f.info().signature.starts_with(f.name()), "{f:?}");
440        }
441        for (i, a) in Aggregate::all().enumerate() {
442            assert_eq!(a as usize, i, "{a:?}");
443            assert_eq!(Aggregate::from_name(a.name()), Some(a));
444        }
445        assert_eq!(Aggregate::from_name("SUMSUM"), Some(Aggregate::Sum));
446        assert_eq!(Aggregate::from_name("sumWAvg"), Some(Aggregate::WeightedAvg));
447        assert!(catalog().len() > 130);
448    }
449}