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}