Skip to main content

rudb_functions/
table.rs

1//! What a table function call resolves to.
2//!
3//! A table function is a function written where a table goes, so `FROM range(10)` produces ten rows
4//! of one column the same way `FROM t` produces whatever is in `t`. That makes it a different
5//! resolution problem from [`crate::signature`]: the answer is not a return type, it is a list of
6//! columns, because the caller can alias them and select from them and join against them.
7//!
8//! Two of them are here, `range` and `generate_series`, which between them account for two thousand
9//! records in DuckDB's `sqllogictest` corpus. They exist because a test that needs a thousand rows
10//! should not have to write a thousand rows, and the corpus uses them the way a person uses a for
11//! loop. Everything else DuckDB has under this name reads a file or reads the catalog, and both of
12//! those are their own piece of work rather than an entry in a table.
13//!
14//! The difference between the two is one row. `range` stops before the end and `generate_series`
15//! stops on it, which is the difference between a half open interval and a closed one, and it is
16//! the only difference. Nothing else about them differs, including the name of the column, which is
17//! the function's own name in both cases.
18
19use rudb_common::{Error, Field, LogicalType, Result};
20
21/// Which table function a call resolved to.
22///
23/// An enum rather than a name, because the executor dispatches on this and a string comparison per
24/// operator build is a string comparison that can be spelled wrong.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum TableFunction {
27    /// `range(stop)`, `range(start, stop)`, `range(start, stop, step)`, stopping before the end.
28    Range,
29    /// The same three, stopping on the end.
30    GenerateSeries,
31}
32
33impl TableFunction {
34    /// The name the plan records and an error message says.
35    #[must_use]
36    pub const fn name(self) -> &'static str {
37        match self {
38            Self::Range => "range",
39            Self::GenerateSeries => "generate_series",
40        }
41    }
42
43    /// Whether the last value is produced.
44    #[must_use]
45    pub const fn inclusive(self) -> bool {
46        matches!(self, Self::GenerateSeries)
47    }
48
49    /// The function of that name, if there is one.
50    #[must_use]
51    pub fn lookup(name: &str) -> Option<Self> {
52        if name.eq_ignore_ascii_case("range") {
53            return Some(Self::Range);
54        }
55        if name.eq_ignore_ascii_case("generate_series") {
56            return Some(Self::GenerateSeries);
57        }
58        None
59    }
60}
61
62/// A resolved table function call.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct ResolvedTable {
65    /// Which function.
66    pub function: TableFunction,
67    /// What each argument has to be cast to, the same length as what was passed in.
68    pub arguments: Vec<LogicalType>,
69    /// The columns the call produces, with the names an unaliased call gives them.
70    pub columns: Vec<Field>,
71}
72
73/// Resolve a table function call by name and argument count.
74///
75/// The types are not consulted, only the count. Both of these take integers in every position and
76/// the binder casts to that, so there is nothing here for a type to choose between. DuckDB also has
77/// a timestamp and interval form of both, which is a second set of columns rather than a second
78/// overload of the same ones, and adding it means adding it rather than widening this.
79///
80/// # Errors
81///
82/// When no table function has that name, or when it has that name and not that many arguments.
83pub fn resolve_table(name: &str, arity: usize) -> Result<ResolvedTable> {
84    let Some(function) = TableFunction::lookup(name) else {
85        return Err(Error::catalog(format!("Table Function with name {name} does not exist!")));
86    };
87    if !(1..=3).contains(&arity) {
88        return Err(Error::binder(format!(
89            "Table function {}() takes between 1 and 3 arguments, {arity} were given",
90            function.name()
91        )));
92    }
93    Ok(ResolvedTable {
94        function,
95        arguments: vec![LogicalType::BigInt; arity],
96        columns: vec![Field::new(function.name(), LogicalType::BigInt)],
97    })
98}
99
100/// The values `start`, `stop` and `step` produce, in order.
101///
102/// Whole rather than an iterator because the caller wants them in a vector to build a vector out
103/// of, and because the count is known up front, which is what keeps a three million row `range`
104/// from growing a `Vec` twenty times on the way there.
105///
106/// A step of zero is an error and is the one case that is not simply an empty result. Everything
107/// else that produces nothing produces nothing: a start past a stop with a positive step, a start
108/// before a stop with a negative one, and the two of them equal under `range`.
109///
110/// # Errors
111///
112/// When the step is zero, with DuckDB's own wording.
113pub fn series(function: TableFunction, start: i64, stop: i64, step: i64) -> Result<Vec<i64>> {
114    let count = series_length(function, start, stop, step)?;
115    let mut out = Vec::with_capacity(count);
116    let mut at = start;
117    for _ in 0..count {
118        out.push(at);
119        // The count was worked out from the same three numbers, so this cannot pass the stop, and
120        // a saturating add is what keeps a step near the end of the range from wrapping into a
121        // value on the wrong side of it rather than stopping.
122        at = at.saturating_add(step);
123    }
124    Ok(out)
125}
126
127/// How many values the series has, without producing any of them.
128///
129/// The executor wants this and not the values. `range(100000000)` is a hundred row chunks a
130/// hundred thousand times over, and building the whole run first to find out how long it is would
131/// be eight hundred megabytes for a query whose answer is one number.
132///
133/// This is also where the step is checked, so the check happens once rather than in each of the
134/// two callers.
135///
136/// # Errors
137///
138/// When the step is zero, with DuckDB's own wording.
139pub fn series_length(function: TableFunction, start: i64, stop: i64, step: i64) -> Result<usize> {
140    if step == 0 {
141        return Err(Error::binder("interval cannot be 0!"));
142    }
143    Ok(length(function, start, stop, step))
144}
145
146/// How many values the series has.
147///
148/// In `i128` because `range(-9223372036854775808, 9223372036854775807)` is a legal call whose
149/// length does not fit in an `i64`, and a length that overflows into a negative is a `Vec` capacity
150/// that panics rather than a query that fails.
151fn length(function: TableFunction, start: i64, stop: i64, step: i64) -> usize {
152    let start = i128::from(start);
153    let stop = i128::from(stop);
154    let step = i128::from(step);
155    let span = if function.inclusive() {
156        if step > 0 { stop - start + 1 } else { stop - start - 1 }
157    } else {
158        stop - start
159    };
160    if (span > 0) != (step > 0) {
161        return 0;
162    }
163    // Rounding away from zero, since a span of five over a step of two is three values and not two.
164    let count = (span + step - step.signum()) / step;
165    usize::try_from(count).unwrap_or(usize::MAX)
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn a_name_that_is_not_a_table_function_says_so_rather_than_binding() {
174        let error = resolve_table("read_csv", 1).unwrap_err();
175        assert!(error.to_string().contains("read_csv"), "{error}");
176    }
177
178    #[test]
179    fn both_names_resolve_and_each_one_names_its_own_column() {
180        let range = resolve_table("range", 1).unwrap();
181        assert_eq!(range.columns[0].name, "range");
182        let series = resolve_table("GENERATE_SERIES", 3).unwrap();
183        assert_eq!(series.columns[0].name, "generate_series");
184        assert_eq!(series.arguments.len(), 3);
185    }
186
187    #[test]
188    fn no_arguments_and_four_arguments_are_both_the_arity_error() {
189        assert!(resolve_table("range", 0).is_err());
190        assert!(resolve_table("range", 4).is_err());
191    }
192
193    #[test]
194    fn range_stops_before_the_end_and_generate_series_stops_on_it() {
195        assert_eq!(series(TableFunction::Range, 0, 3, 1).unwrap(), vec![0, 1, 2]);
196        assert_eq!(series(TableFunction::GenerateSeries, 0, 3, 1).unwrap(), vec![0, 1, 2, 3]);
197    }
198
199    #[test]
200    fn a_step_that_does_not_divide_the_span_stops_before_the_end_of_it() {
201        // DuckDB gives 2, 4, 6 for both of these. The seven is not reached by either, which is
202        // where the two functions stop being different.
203        assert_eq!(series(TableFunction::Range, 2, 7, 2).unwrap(), vec![2, 4, 6]);
204        assert_eq!(series(TableFunction::GenerateSeries, 2, 7, 2).unwrap(), vec![2, 4, 6]);
205    }
206
207    #[test]
208    fn a_negative_step_counts_down_and_stops_on_the_same_rule() {
209        assert_eq!(series(TableFunction::Range, 5, 1, -2).unwrap(), vec![5, 3]);
210        assert_eq!(series(TableFunction::GenerateSeries, 5, 1, -2).unwrap(), vec![5, 3, 1]);
211    }
212
213    #[test]
214    fn a_step_going_the_wrong_way_produces_nothing_rather_than_running_forever() {
215        assert!(series(TableFunction::Range, 0, 10, -1).unwrap().is_empty());
216        assert!(series(TableFunction::Range, 10, 0, 1).unwrap().is_empty());
217    }
218
219    #[test]
220    fn an_empty_range_and_a_single_value_series_are_the_boundary_between_the_two() {
221        assert!(series(TableFunction::Range, 4, 4, 1).unwrap().is_empty());
222        assert_eq!(series(TableFunction::GenerateSeries, 4, 4, 1).unwrap(), vec![4]);
223    }
224
225    #[test]
226    fn a_step_of_zero_is_the_one_case_that_is_an_error_rather_than_nothing() {
227        let error = series(TableFunction::Range, 1, 5, 0).unwrap_err();
228        assert!(error.to_string().contains("interval cannot be 0"), "{error}");
229    }
230
231    #[test]
232    fn a_span_that_does_not_fit_in_an_i64_does_not_overflow_the_length() {
233        // Not run, only counted. The point is that the count is worked out in i128, so this comes
234        // out as a huge number rather than as a negative one that becomes a capacity panic.
235        assert_eq!(length(TableFunction::Range, i64::MIN, i64::MAX, 1), usize::MAX);
236    }
237}