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//! Three of them are here. `range` and `generate_series` between them account for two thousand
9//! records in DuckDB's `sqllogictest` corpus, because a test that needs a thousand rows should not
10//! have to write a thousand rows, and the corpus uses them the way a person uses a for loop. The
11//! difference between those two is one row: `range` stops before the end and `generate_series`
12//! stops on it, which is the difference between a half open interval and a closed one, and it is
13//! the only difference. Nothing else about them differs, including the name of the column, which is
14//! the function's own name in both cases.
15//!
16//! `read_parquet` is the third and it is a different kind of thing, because its columns are in the
17//! file rather than in this table. That is what [`Columns`] exists to say. A caller that resolves a
18//! call has to open the file to finish resolving it, and [`crate::file`] is where that happens.
19
20use rudb_common::{Error, Field, LogicalType, Result};
21
22/// Which table function a call resolved to.
23///
24/// An enum rather than a name, because the executor dispatches on this and a string comparison per
25/// operator build is a string comparison that can be spelled wrong.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum TableFunction {
28    /// `range(stop)`, `range(start, stop)`, `range(start, stop, step)`, stopping before the end.
29    Range,
30    /// The same three, stopping on the end.
31    GenerateSeries,
32    /// `read_parquet(path)`, the rows of a Parquet file.
33    ReadParquet,
34}
35
36impl TableFunction {
37    /// The name the plan records and an error message says.
38    #[must_use]
39    pub const fn name(self) -> &'static str {
40        match self {
41            Self::Range => "range",
42            Self::GenerateSeries => "generate_series",
43            Self::ReadParquet => "read_parquet",
44        }
45    }
46
47    /// Whether the last value is produced.
48    ///
49    /// Only the two series functions differ here. `read_parquet` answers false and nothing asks it.
50    #[must_use]
51    pub const fn inclusive(self) -> bool {
52        matches!(self, Self::GenerateSeries)
53    }
54
55    /// The function of that name, if there is one.
56    #[must_use]
57    pub fn lookup(name: &str) -> Option<Self> {
58        if name.eq_ignore_ascii_case("range") {
59            return Some(Self::Range);
60        }
61        if name.eq_ignore_ascii_case("generate_series") {
62            return Some(Self::GenerateSeries);
63        }
64        if name.eq_ignore_ascii_case("read_parquet") || name.eq_ignore_ascii_case("parquet_scan") {
65            return Some(Self::ReadParquet);
66        }
67        None
68    }
69}
70
71/// Where a call's columns come from.
72///
73/// A table function that produces a fixed set of columns is resolved by this crate and nothing
74/// else has to be consulted. One that reads a file is not, because the columns are in the file, so
75/// the answer here is which file to open rather than what is in it. An enum rather than an empty
76/// column list, because an empty list is what `read_parquet` of a file with no columns would also
77/// give and a caller that forgot to handle the case would get an empty table instead of an error.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum Columns {
80    /// The columns this call produces, with the names an unaliased call gives them.
81    Fixed(Vec<Field>),
82    /// The columns of the Parquet file the first argument names.
83    Parquet,
84}
85
86/// A resolved table function call.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ResolvedTable {
89    /// Which function.
90    pub function: TableFunction,
91    /// What each argument has to be cast to, the same length as what was passed in.
92    pub arguments: Vec<LogicalType>,
93    /// Where the columns the call produces come from.
94    pub columns: Columns,
95}
96
97/// Resolve a table function call by name and the types of its arguments.
98///
99/// The series pair does not consult the types, only the count, because it takes integers in every
100/// position and the binder casts to that, so there is nothing there for a type to choose between.
101/// DuckDB also has a timestamp and interval form of both, which is a second set of columns rather
102/// than a second overload of the same ones, and adding it means adding it rather than widening this.
103///
104/// `read_parquet` does consult them, because DuckDB does. `read_parquet(3)` is a binder error there
105/// rather than a read of a file called `3`, which was measured against the binary rather than
106/// assumed, and it is the right answer: a path that arrived as a number is a query that meant
107/// something else.
108///
109/// # Errors
110///
111/// When no table function has that name, or when it has that name and not those arguments.
112pub fn resolve_table(name: &str, arguments: &[LogicalType]) -> Result<ResolvedTable> {
113    let Some(function) = TableFunction::lookup(name) else {
114        return Err(Error::catalog(format!("Table Function with name {name} does not exist!")));
115    };
116    if function == TableFunction::ReadParquet {
117        if arguments.len() != 1 || arguments[0] != LogicalType::Varchar {
118            return Err(no_overload(function, arguments));
119        }
120        return Ok(ResolvedTable {
121            function,
122            arguments: vec![LogicalType::Varchar],
123            columns: Columns::Parquet,
124        });
125    }
126    let arity = arguments.len();
127    if !(1..=3).contains(&arity) {
128        return Err(Error::binder(format!(
129            "Table function {}() takes between 1 and 3 arguments, {arity} were given",
130            function.name()
131        )));
132    }
133    Ok(ResolvedTable {
134        function,
135        arguments: vec![LogicalType::BigInt; arity],
136        columns: Columns::Fixed(vec![Field::new(function.name(), LogicalType::BigInt)]),
137    })
138}
139
140/// DuckDB's message for a call that matched a name and no overload of it.
141///
142/// The candidate list it prints carries fifteen named parameters that none of them accept here, so
143/// what is listed is the one overload that exists. The first line is the one a test in the wild
144/// asserts on and it is reproduced exactly.
145fn no_overload(function: TableFunction, arguments: &[LogicalType]) -> Error {
146    let written: Vec<String> = arguments.iter().map(ToString::to_string).collect();
147    Error::binder(format!(
148        "No function matches the given name and argument types '{}({})'. You might need to add \
149         explicit type casts.\n\tCandidate functions:\n\t{}(VARCHAR)\n",
150        function.name(),
151        written.join(", "),
152        function.name()
153    ))
154}
155
156/// The values `start`, `stop` and `step` produce, in order.
157///
158/// Whole rather than an iterator because the caller wants them in a vector to build a vector out
159/// of, and because the count is known up front, which is what keeps a three million row `range`
160/// from growing a `Vec` twenty times on the way there.
161///
162/// A step of zero is an error and is the one case that is not simply an empty result. Everything
163/// else that produces nothing produces nothing: a start past a stop with a positive step, a start
164/// before a stop with a negative one, and the two of them equal under `range`.
165///
166/// # Errors
167///
168/// When the step is zero, with DuckDB's own wording.
169pub fn series(function: TableFunction, start: i64, stop: i64, step: i64) -> Result<Vec<i64>> {
170    let count = series_length(function, start, stop, step)?;
171    let mut out = Vec::with_capacity(count);
172    let mut at = start;
173    for _ in 0..count {
174        out.push(at);
175        // The count was worked out from the same three numbers, so this cannot pass the stop, and
176        // a saturating add is what keeps a step near the end of the range from wrapping into a
177        // value on the wrong side of it rather than stopping.
178        at = at.saturating_add(step);
179    }
180    Ok(out)
181}
182
183/// How many values the series has, without producing any of them.
184///
185/// The executor wants this and not the values. `range(100000000)` is a hundred row chunks a
186/// hundred thousand times over, and building the whole run first to find out how long it is would
187/// be eight hundred megabytes for a query whose answer is one number.
188///
189/// This is also where the step is checked, so the check happens once rather than in each of the
190/// two callers.
191///
192/// # Errors
193///
194/// When the step is zero, with DuckDB's own wording.
195pub fn series_length(function: TableFunction, start: i64, stop: i64, step: i64) -> Result<usize> {
196    if step == 0 {
197        return Err(Error::binder("interval cannot be 0!"));
198    }
199    Ok(length(function, start, stop, step))
200}
201
202/// How many values the series has.
203///
204/// In `i128` because `range(-9223372036854775808, 9223372036854775807)` is a legal call whose
205/// length does not fit in an `i64`, and a length that overflows into a negative is a `Vec` capacity
206/// that panics rather than a query that fails.
207fn length(function: TableFunction, start: i64, stop: i64, step: i64) -> usize {
208    let start = i128::from(start);
209    let stop = i128::from(stop);
210    let step = i128::from(step);
211    let span = if function.inclusive() {
212        if step > 0 { stop - start + 1 } else { stop - start - 1 }
213    } else {
214        stop - start
215    };
216    if (span > 0) != (step > 0) {
217        return 0;
218    }
219    // Rounding away from zero, since a span of five over a step of two is three values and not two.
220    let count = (span + step - step.signum()) / step;
221    usize::try_from(count).unwrap_or(usize::MAX)
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    /// The fixed columns of a resolved call, which every function but `read_parquet` has.
229    fn fixed(resolved: &ResolvedTable) -> &[Field] {
230        match &resolved.columns {
231            Columns::Fixed(fields) => fields,
232            Columns::Parquet => panic!("{} resolves to a file", resolved.function.name()),
233        }
234    }
235
236    /// A call of `count` integer arguments, which is what every series call looks like.
237    fn integers(count: usize) -> Vec<LogicalType> {
238        vec![LogicalType::BigInt; count]
239    }
240
241    #[test]
242    fn a_name_that_is_not_a_table_function_says_so_rather_than_binding() {
243        let error = resolve_table("read_csv", &integers(1)).unwrap_err();
244        assert!(error.to_string().contains("read_csv"), "{error}");
245    }
246
247    #[test]
248    fn both_names_resolve_and_each_one_names_its_own_column() {
249        let range = resolve_table("range", &integers(1)).unwrap();
250        assert_eq!(fixed(&range)[0].name, "range");
251        let series = resolve_table("GENERATE_SERIES", &integers(3)).unwrap();
252        assert_eq!(fixed(&series)[0].name, "generate_series");
253        assert_eq!(series.arguments.len(), 3);
254    }
255
256    #[test]
257    fn no_arguments_and_four_arguments_are_both_the_arity_error() {
258        assert!(resolve_table("range", &integers(0)).is_err());
259        assert!(resolve_table("range", &integers(4)).is_err());
260    }
261
262    #[test]
263    fn a_series_call_ignores_the_types_it_was_given_and_casts_them_all_to_bigint() {
264        let resolved =
265            resolve_table("range", &[LogicalType::Varchar, LogicalType::Double]).unwrap();
266        assert_eq!(resolved.arguments, integers(2));
267    }
268
269    #[test]
270    fn read_parquet_takes_one_string_and_says_its_columns_are_in_the_file() {
271        let resolved = resolve_table("read_parquet", &[LogicalType::Varchar]).unwrap();
272        assert_eq!(resolved.function, TableFunction::ReadParquet);
273        assert_eq!(resolved.arguments, vec![LogicalType::Varchar]);
274        assert_eq!(resolved.columns, Columns::Parquet);
275    }
276
277    #[test]
278    fn parquet_scan_is_the_same_function_under_duckdbs_other_name_for_it() {
279        assert_eq!(TableFunction::lookup("parquet_scan"), Some(TableFunction::ReadParquet));
280        // And it records itself under the one name, so a plan does not have two spellings in it.
281        let resolved = resolve_table("parquet_scan", &[LogicalType::Varchar]).unwrap();
282        assert_eq!(resolved.function.name(), "read_parquet");
283    }
284
285    #[test]
286    fn a_path_that_is_not_a_string_is_the_message_duckdb_gives_for_it() {
287        // Measured against v1.4.1 on server3: `read_parquet(3)` does not cast, it fails to match.
288        let error = resolve_table("read_parquet", &[LogicalType::Integer]).unwrap_err();
289        assert!(
290            error.message().starts_with(
291                "No function matches the given name and argument types 'read_parquet(INTEGER)'."
292            ),
293            "{error}"
294        );
295        assert!(error.message().contains("read_parquet(VARCHAR)"), "{error}");
296    }
297
298    #[test]
299    fn read_parquet_of_no_arguments_or_two_is_the_same_no_overload_message() {
300        let two = resolve_table("read_parquet", &[LogicalType::Varchar, LogicalType::Varchar]);
301        assert!(two.unwrap_err().message().contains("read_parquet(VARCHAR, VARCHAR)"));
302        let none = resolve_table("read_parquet", &[]);
303        assert!(none.unwrap_err().message().contains("read_parquet()"));
304    }
305
306    #[test]
307    fn range_stops_before_the_end_and_generate_series_stops_on_it() {
308        assert_eq!(series(TableFunction::Range, 0, 3, 1).unwrap(), vec![0, 1, 2]);
309        assert_eq!(series(TableFunction::GenerateSeries, 0, 3, 1).unwrap(), vec![0, 1, 2, 3]);
310    }
311
312    #[test]
313    fn a_step_that_does_not_divide_the_span_stops_before_the_end_of_it() {
314        // DuckDB gives 2, 4, 6 for both of these. The seven is not reached by either, which is
315        // where the two functions stop being different.
316        assert_eq!(series(TableFunction::Range, 2, 7, 2).unwrap(), vec![2, 4, 6]);
317        assert_eq!(series(TableFunction::GenerateSeries, 2, 7, 2).unwrap(), vec![2, 4, 6]);
318    }
319
320    #[test]
321    fn a_negative_step_counts_down_and_stops_on_the_same_rule() {
322        assert_eq!(series(TableFunction::Range, 5, 1, -2).unwrap(), vec![5, 3]);
323        assert_eq!(series(TableFunction::GenerateSeries, 5, 1, -2).unwrap(), vec![5, 3, 1]);
324    }
325
326    #[test]
327    fn a_step_going_the_wrong_way_produces_nothing_rather_than_running_forever() {
328        assert!(series(TableFunction::Range, 0, 10, -1).unwrap().is_empty());
329        assert!(series(TableFunction::Range, 10, 0, 1).unwrap().is_empty());
330    }
331
332    #[test]
333    fn an_empty_range_and_a_single_value_series_are_the_boundary_between_the_two() {
334        assert!(series(TableFunction::Range, 4, 4, 1).unwrap().is_empty());
335        assert_eq!(series(TableFunction::GenerateSeries, 4, 4, 1).unwrap(), vec![4]);
336    }
337
338    #[test]
339    fn a_step_of_zero_is_the_one_case_that_is_an_error_rather_than_nothing() {
340        let error = series(TableFunction::Range, 1, 5, 0).unwrap_err();
341        assert!(error.to_string().contains("interval cannot be 0"), "{error}");
342    }
343
344    #[test]
345    fn a_span_that_does_not_fit_in_an_i64_does_not_overflow_the_length() {
346        // Not run, only counted. The point is that the count is worked out in i128, so this comes
347        // out as a huge number rather than as a negative one that becomes a capacity panic.
348        assert_eq!(length(TableFunction::Range, i64::MIN, i64::MAX, 1), usize::MAX);
349    }
350}