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//! Thirteen 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` and `read_csv` are the other two and they are a different kind of thing, because
17//! their columns are in the file rather than in this table. That is what [`Columns`] exists to say.
18//! A caller that resolves one of those has to open the file to finish resolving it, and
19//! [`crate::file`] is where that happens. For CSV there is nothing in the file that states the
20//! columns either, so opening it means sniffing it.
21//!
22//! The other nine are the third kind, a table whose rows are a fact about the engine rather than
23//! data somebody stored. All nine take no arguments and all nine know their own columns, so
24//! resolving one is the simplest case in this file and they share an arm. `rudb_strategies()` is not
25//! a DuckDB function at all: it lists every seam in the engine and every implementation registered
26//! against it, which is how a reader finds out what this engine will let them swap and what it lets
27//! them swap today. `duckdb_keywords()` is every word the grammar knows about, which this crate can
28//! answer because the grammar is vendored. `duckdb_types()` is every type name the engine has and
29//! `duckdb_functions()` is every function, and both of their lists are in this crate because which
30//! names exist is a fact about the type system and the function library rather than about the
31//! executor. `duckdb_settings()` is every setting `SET` will take, and it is the one of the five
32//! whose rows are not all known here: the names and the descriptions are, and the values come from
33//! the session the query is running in. `duckdb_databases()`, `duckdb_schemas()`, `duckdb_tables()`
34//! and `duckdb_columns()` are the last four and they are further from this crate again, because
35//! their rows are whatever somebody created, so only their columns are here and
36//! [`crate::entrycatalog`] says why.
37//!
38//! D2 adds a few more of that third kind, `duckdb_views()` and the extension and optimizer tables
39//! among them. Each one is a column list here and a list of rows in `rudb_exec::metadata`, and
40//! nothing else.
41
42use rudb_common::{Error, Field, LogicalType, Result};
43
44use crate::entrycatalog::{column_fields, database_fields, schema_fields, table_fields};
45use crate::functioncatalog::function_fields;
46use crate::settingcatalog::setting_fields;
47use crate::typecatalog::type_fields;
48
49/// Which table function a call resolved to.
50///
51/// An enum rather than a name, because the executor dispatches on this and a string comparison per
52/// operator build is a string comparison that can be spelled wrong.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum TableFunction {
55    /// `range(stop)`, `range(start, stop)`, `range(start, stop, step)`, stopping before the end.
56    Range,
57    /// The same three, stopping on the end.
58    GenerateSeries,
59    /// `read_parquet(path)`, the rows of a Parquet file.
60    ReadParquet,
61    /// `read_csv(path)`, the rows of a CSV file, with everything about how it is written sniffed.
62    ReadCsv,
63    /// `rudb_strategies()`, every seam and every implementation registered against it.
64    RudbStrategies,
65    /// `duckdb_keywords()`, every word the grammar knows and which class each one is in.
66    DuckdbKeywords,
67    /// `duckdb_types()`, every type name the engine knows and what each one stands for.
68    DuckdbTypes,
69    /// `duckdb_functions()`, every function the engine knows and what each one takes.
70    DuckdbFunctions,
71    /// `duckdb_settings()`, every setting `SET` will take and what each one is now.
72    DuckdbSettings,
73    /// `duckdb_databases()`, every database attached to this session.
74    DuckdbDatabases,
75    /// `duckdb_schemas()`, every schema in every one of them.
76    DuckdbSchemas,
77    /// `duckdb_tables()`, every base table somebody created.
78    DuckdbTables,
79    /// `duckdb_columns()`, every column of every one of those.
80    DuckdbColumns,
81}
82
83/// The name of the column `file_row_number=True` adds.
84///
85/// Here rather than in the binder because the executor is the half that fills it in and the two
86/// have to agree on the spelling. It is DuckDB's name for it, and the column is a row's ordinal
87/// inside its own file rather than inside the read, so a glob of three files counts from zero three
88/// times.
89pub const FILE_ROW_NUMBER: &str = "file_row_number";
90
91impl TableFunction {
92    /// The name the plan records and an error message says.
93    #[must_use]
94    pub const fn name(self) -> &'static str {
95        match self {
96            Self::Range => "range",
97            Self::GenerateSeries => "generate_series",
98            Self::ReadParquet => "read_parquet",
99            Self::ReadCsv => "read_csv",
100            Self::RudbStrategies => "rudb_strategies",
101            Self::DuckdbKeywords => "duckdb_keywords",
102            Self::DuckdbTypes => "duckdb_types",
103            Self::DuckdbFunctions => "duckdb_functions",
104            Self::DuckdbSettings => "duckdb_settings",
105            Self::DuckdbDatabases => "duckdb_databases",
106            Self::DuckdbSchemas => "duckdb_schemas",
107            Self::DuckdbTables => "duckdb_tables",
108            Self::DuckdbColumns => "duckdb_columns",
109        }
110    }
111
112    /// Whether the last value is produced.
113    ///
114    /// Only the two series functions differ here. The file readers answer false and nothing asks
115    /// them.
116    #[must_use]
117    pub const fn inclusive(self) -> bool {
118        matches!(self, Self::GenerateSeries)
119    }
120
121    /// The named parameters the call takes, and the type each one wants.
122    ///
123    /// This is the list rudb acts on and not the list DuckDB prints, and the difference is worth
124    /// being plain about. `read_parquet` there takes seventeen named parameters and `read_csv`
125    /// takes around thirty. One of the Parquet ones is on the critical path, since the ClickBench
126    /// entry reads its file with `binary_as_string=True` and without it every string column in
127    /// `hits.parquet` comes back as `BLOB`, and the other sixteen have no caller here yet. A
128    /// parameter that is listed is one that does something, so this list grows as they land rather
129    /// than accepting names and ignoring them, which is the failure mode that makes an option look
130    /// supported when it is not.
131    ///
132    /// The CSV ones here are the ones that say how the file is written, which are the ones where
133    /// guessing wrong changes the answer rather than the speed. `sep` is DuckDB's other name for
134    /// `delim` and is a separate row rather than an alias, because the list is also what the
135    /// candidates on a misspelling are read out of and the binary prints both of them.
136    #[must_use]
137    pub fn parameters(self) -> &'static [(&'static str, LogicalType)] {
138        static READ_PARQUET: &[(&str, LogicalType)] = &[
139            ("binary_as_string", LogicalType::Boolean),
140            ("file_row_number", LogicalType::Boolean),
141        ];
142        static READ_CSV: &[(&str, LogicalType)] = &[
143            ("all_varchar", LogicalType::Boolean),
144            ("delim", LogicalType::Varchar),
145            ("escape", LogicalType::Varchar),
146            ("header", LogicalType::Boolean),
147            ("quote", LogicalType::Varchar),
148            ("sep", LogicalType::Varchar),
149        ];
150        match self {
151            Self::ReadParquet => READ_PARQUET,
152            Self::ReadCsv => READ_CSV,
153            _ => &[],
154        }
155    }
156
157    /// The function of that name, if there is one.
158    #[must_use]
159    pub fn lookup(name: &str) -> Option<Self> {
160        if name.eq_ignore_ascii_case("range") {
161            return Some(Self::Range);
162        }
163        if name.eq_ignore_ascii_case("generate_series") {
164            return Some(Self::GenerateSeries);
165        }
166        if name.eq_ignore_ascii_case("read_parquet") || name.eq_ignore_ascii_case("parquet_scan") {
167            return Some(Self::ReadParquet);
168        }
169        // `read_csv_auto` is the older spelling and DuckDB still answers to it. It meant sniffing
170        // back when `read_csv` did not sniff unless it was told to, and today they are the same
171        // function, which is why they are the same variant here.
172        if name.eq_ignore_ascii_case("read_csv") || name.eq_ignore_ascii_case("read_csv_auto") {
173            return Some(Self::ReadCsv);
174        }
175        if name.eq_ignore_ascii_case("rudb_strategies") {
176            return Some(Self::RudbStrategies);
177        }
178        if name.eq_ignore_ascii_case("duckdb_keywords") {
179            return Some(Self::DuckdbKeywords);
180        }
181        if name.eq_ignore_ascii_case("duckdb_types") {
182            return Some(Self::DuckdbTypes);
183        }
184        if name.eq_ignore_ascii_case("duckdb_functions") {
185            return Some(Self::DuckdbFunctions);
186        }
187        if name.eq_ignore_ascii_case("duckdb_settings") {
188            return Some(Self::DuckdbSettings);
189        }
190        if name.eq_ignore_ascii_case("duckdb_databases") {
191            return Some(Self::DuckdbDatabases);
192        }
193        if name.eq_ignore_ascii_case("duckdb_schemas") {
194            return Some(Self::DuckdbSchemas);
195        }
196        if name.eq_ignore_ascii_case("duckdb_tables") {
197            return Some(Self::DuckdbTables);
198        }
199        if name.eq_ignore_ascii_case("duckdb_columns") {
200            return Some(Self::DuckdbColumns);
201        }
202        None
203    }
204}
205
206/// Where a call's columns come from.
207///
208/// A table function that produces a fixed set of columns is resolved by this crate and nothing
209/// else has to be consulted. One that reads a file is not, because the columns are in the file, so
210/// the answer here is which file to open rather than what is in it. An enum rather than an empty
211/// column list, because an empty list is what `read_parquet` of a file with no columns would also
212/// give and a caller that forgot to handle the case would get an empty table instead of an error.
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub enum Columns {
215    /// The columns this call produces, with the names an unaliased call gives them.
216    Fixed(Vec<Field>),
217    /// The columns of the Parquet file the first argument names.
218    Parquet,
219    /// The columns of the CSV file the first argument names, which are sniffed out of its front.
220    Csv,
221}
222
223/// A resolved table function call.
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct ResolvedTable {
226    /// Which function.
227    pub function: TableFunction,
228    /// What each argument has to be cast to, the same length as what was passed in.
229    pub arguments: Vec<LogicalType>,
230    /// Where the columns the call produces come from.
231    pub columns: Columns,
232}
233
234/// Resolve a table function call by name and the types of its arguments.
235///
236/// The series pair does not consult the types, only the count, because it takes integers in every
237/// position and the binder casts to that, so there is nothing there for a type to choose between.
238/// DuckDB also has a timestamp and interval form of both, which is a second set of columns rather
239/// than a second overload of the same ones, and adding it means adding it rather than widening this.
240///
241/// The file readers do consult them, because DuckDB does. `read_parquet(3)` and `read_csv(3)` are
242/// binder errors there rather than reads of a file called `3`, which was measured against the binary
243/// rather than assumed, and it is the right answer: a path that arrived as a number is a query that
244/// meant something else.
245///
246/// # Errors
247///
248/// When no table function has that name, or when it has that name and not those arguments.
249pub fn resolve_table(name: &str, arguments: &[LogicalType]) -> Result<ResolvedTable> {
250    let Some(function) = TableFunction::lookup(name) else {
251        return Err(Error::catalog(format!("Table Function with name {name} does not exist!")));
252    };
253    if let Some(columns) = file_columns(function) {
254        // Two overloads, one path and a list of them, which is DuckDB's pair. The list is where
255        // `read_parquet(['a.parquet', 'b.parquet'])` binds, and an empty list arrives typed
256        // `INTEGER[]` there and here, so it lands on the no overload message rather than on a read
257        // of nothing.
258        let list = LogicalType::list(LogicalType::Varchar);
259        let single = arguments.len() == 1 && arguments[0] == LogicalType::Varchar;
260        let many = arguments.len() == 1 && arguments[0] == list;
261        // A bare null matches, and is a sentence about nulls rather than about overloads, which is
262        // what DuckDB answers `read_parquet(NULL)` with. It is left as a null rather than cast to a
263        // path so that the binder still has a null to recognise when it goes looking for the name.
264        let nothing = arguments.len() == 1 && arguments[0] == LogicalType::Null;
265        if !single && !many && !nothing {
266            return Err(no_overload(function, arguments));
267        }
268        let wanted = if many {
269            list
270        } else if nothing {
271            LogicalType::Null
272        } else {
273            LogicalType::Varchar
274        };
275        return Ok(ResolvedTable { function, arguments: vec![wanted], columns });
276    }
277    let arity = arguments.len();
278    // The metadata tables take nothing and their columns are fixed, which makes them the simplest
279    // case here. They are one arm rather than one each because the only thing that differs is the
280    // column list, and a name that is added to this list and not to `lookup` cannot be reached.
281    if let Some(columns) = fixed_columns(function) {
282        if arity != 0 {
283            return Err(Error::binder(format!(
284                "Table function {}() takes no arguments, {arity} were given",
285                function.name()
286            )));
287        }
288        return Ok(ResolvedTable {
289            function,
290            arguments: Vec::new(),
291            columns: Columns::Fixed(columns),
292        });
293    }
294    if !(1..=3).contains(&arity) {
295        return Err(Error::binder(format!(
296            "Table function {}() takes between 1 and 3 arguments, {arity} were given",
297            function.name()
298        )));
299    }
300    Ok(ResolvedTable {
301        function,
302        arguments: vec![LogicalType::BigInt; arity],
303        columns: Columns::Fixed(vec![Field::new(function.name(), LogicalType::BigInt)]),
304    })
305}
306
307/// Where a file reading table function's columns come from, and `None` for one that does not read
308/// a file.
309fn file_columns(function: TableFunction) -> Option<Columns> {
310    match function {
311        TableFunction::ReadParquet => Some(Columns::Parquet),
312        TableFunction::ReadCsv => Some(Columns::Csv),
313        TableFunction::Range
314        | TableFunction::GenerateSeries
315        | TableFunction::RudbStrategies
316        | TableFunction::DuckdbKeywords
317        | TableFunction::DuckdbTypes
318        | TableFunction::DuckdbFunctions
319        | TableFunction::DuckdbSettings
320        | TableFunction::DuckdbDatabases
321        | TableFunction::DuckdbSchemas
322        | TableFunction::DuckdbTables
323        | TableFunction::DuckdbColumns => None,
324    }
325}
326
327/// The columns of a table function that takes no arguments and knows its own, and `None` for one
328/// that has to look at what it was called with.
329fn fixed_columns(function: TableFunction) -> Option<Vec<Field>> {
330    match function {
331        TableFunction::RudbStrategies => Some(strategy_fields()),
332        TableFunction::DuckdbKeywords => Some(keyword_fields()),
333        TableFunction::DuckdbTypes => Some(type_fields()),
334        TableFunction::DuckdbFunctions => Some(function_fields()),
335        TableFunction::DuckdbSettings => Some(setting_fields()),
336        TableFunction::DuckdbDatabases => Some(database_fields()),
337        TableFunction::DuckdbSchemas => Some(schema_fields()),
338        TableFunction::DuckdbTables => Some(table_fields()),
339        TableFunction::DuckdbColumns => Some(column_fields()),
340        TableFunction::Range
341        | TableFunction::GenerateSeries
342        | TableFunction::ReadParquet
343        | TableFunction::ReadCsv => None,
344    }
345}
346
347/// The columns `rudb_strategies()` produces.
348///
349/// Named here rather than in the executor because the binder resolves the call and the executor
350/// fills it, and a table whose two halves disagree about its own columns is a bug that shows up as
351/// a wrong answer rather than as a compile error.
352///
353/// Nine columns and every one of them earns its place at a seam that has no implementations yet,
354/// which is twenty six of the twenty seven today. `seam`, `milestone` and `seam_description` say
355/// what the seam is and which milestone owes it its first two implementations, and they are filled
356/// whether or not anything is registered. The other six describe an implementation and are null
357/// when there is none, which is how the table says that a seam is planned rather than built without
358/// anybody having to read a design document to find out.
359#[must_use]
360pub fn strategy_fields() -> Vec<Field> {
361    vec![
362        Field::new("seam", LogicalType::Varchar),
363        Field::new("milestone", LogicalType::Varchar),
364        Field::new("seam_description", LogicalType::Varchar),
365        Field::new("implementation", LogicalType::Varchar),
366        Field::new("implementation_description", LogicalType::Varchar),
367        Field::new("provenance", LogicalType::Varchar),
368        Field::new("determinism", LogicalType::Varchar),
369        Field::new("is_reference", LogicalType::Boolean),
370        Field::new("is_default", LogicalType::Boolean),
371    ]
372}
373
374/// The columns `duckdb_keywords()` produces, which is DuckDB's two.
375#[must_use]
376pub fn keyword_fields() -> Vec<Field> {
377    vec![
378        Field::new("keyword_name", LogicalType::Varchar),
379        Field::new("keyword_category", LogicalType::Varchar),
380    ]
381}
382
383/// The four categories DuckDB sorts a keyword into.
384///
385/// The vendored grammar does not carry these. It carries five keyword rules, `reserved_keyword`,
386/// `unreserved_keyword`, `column_name_keyword`, `func_name_keyword` and `type_name_keyword`, and
387/// `rudb_parse::KEYWORDS` is a mask over those five because they are not disjoint. DuckDB's table
388/// reports PostgreSQL's four categories instead, where `type_function` is the one category that the
389/// grammar spells as two rules, because a word usable as a type name is usable as a function name.
390///
391/// So a word can produce two rows, and six of them do: `columns`, `generated`, `map`, `struct`,
392/// `try_cast` and `tuple` are each in the column name class and in the type function class. That is
393/// why the pinned binary returns 505 rows over 499 distinct words, and a table that deduplicated
394/// them would be 499 rows and wrong.
395///
396/// A word whose mask is zero is in no class at all. The grammar spells fifteen words directly in
397/// some rule, `ascending` and `variant` among them, which makes them matchable as literals and
398/// keywords nowhere, and the pinned binary leaves all fifteen out of this table.
399#[must_use]
400pub fn keyword_categories(classes: u8) -> Vec<&'static str> {
401    use rudb_parse::{COLUMN_NAME, FUNC_NAME, RESERVED, TYPE_NAME, UNRESERVED};
402    let mut out = Vec::new();
403    if classes & RESERVED != 0 {
404        out.push("reserved");
405    }
406    if classes & UNRESERVED != 0 {
407        out.push("unreserved");
408    }
409    if classes & COLUMN_NAME != 0 {
410        out.push("column_name");
411    }
412    if classes & (FUNC_NAME | TYPE_NAME) != 0 {
413        out.push("type_function");
414    }
415    out
416}
417
418/// DuckDB's message for a call that matched a name and no overload of it.
419///
420/// The candidate list it prints carries fifteen named parameters that none of them accept here, so
421/// what is listed is the two overloads that exist. The first line is the one a test in the wild
422/// asserts on and it is reproduced exactly.
423fn no_overload(function: TableFunction, arguments: &[LogicalType]) -> Error {
424    let written: Vec<String> = arguments.iter().map(ToString::to_string).collect();
425    let name = function.name();
426    Error::binder(format!(
427        "No function matches the given name and argument types '{name}({})'. You might need to \
428         add explicit type casts.\n\tCandidate functions:\n\t{name}(VARCHAR)\n\t{name}(VARCHAR[])\n",
429        written.join(", ")
430    ))
431}
432
433/// The values `start`, `stop` and `step` produce, in order.
434///
435/// Whole rather than an iterator because the caller wants them in a vector to build a vector out
436/// of, and because the count is known up front, which is what keeps a three million row `range`
437/// from growing a `Vec` twenty times on the way there.
438///
439/// A step of zero is an error and is the one case that is not simply an empty result. Everything
440/// else that produces nothing produces nothing: a start past a stop with a positive step, a start
441/// before a stop with a negative one, and the two of them equal under `range`.
442///
443/// # Errors
444///
445/// When the step is zero, with DuckDB's own wording.
446pub fn series(function: TableFunction, start: i64, stop: i64, step: i64) -> Result<Vec<i64>> {
447    let count = series_length(function, start, stop, step)?;
448    let mut out = Vec::with_capacity(count);
449    let mut at = start;
450    for _ in 0..count {
451        out.push(at);
452        // The count was worked out from the same three numbers, so this cannot pass the stop, and
453        // a saturating add is what keeps a step near the end of the range from wrapping into a
454        // value on the wrong side of it rather than stopping.
455        at = at.saturating_add(step);
456    }
457    Ok(out)
458}
459
460/// How many values the series has, without producing any of them.
461///
462/// The executor wants this and not the values. `range(100000000)` is a hundred row chunks a
463/// hundred thousand times over, and building the whole run first to find out how long it is would
464/// be eight hundred megabytes for a query whose answer is one number.
465///
466/// This is also where the step is checked, so the check happens once rather than in each of the
467/// two callers.
468///
469/// # Errors
470///
471/// When the step is zero, with DuckDB's own wording.
472pub fn series_length(function: TableFunction, start: i64, stop: i64, step: i64) -> Result<usize> {
473    if step == 0 {
474        return Err(Error::binder("interval cannot be 0!"));
475    }
476    Ok(length(function, start, stop, step))
477}
478
479/// How many values the series has.
480///
481/// In `i128` because `range(-9223372036854775808, 9223372036854775807)` is a legal call whose
482/// length does not fit in an `i64`, and a length that overflows into a negative is a `Vec` capacity
483/// that panics rather than a query that fails.
484fn length(function: TableFunction, start: i64, stop: i64, step: i64) -> usize {
485    let start = i128::from(start);
486    let stop = i128::from(stop);
487    let step = i128::from(step);
488    let span = if function.inclusive() {
489        if step > 0 { stop - start + 1 } else { stop - start - 1 }
490    } else {
491        stop - start
492    };
493    if (span > 0) != (step > 0) {
494        return 0;
495    }
496    // Rounding away from zero, since a span of five over a step of two is three values and not two.
497    let count = (span + step - step.signum()) / step;
498    usize::try_from(count).unwrap_or(usize::MAX)
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504
505    /// The fixed columns of a resolved call, which every function that does not read a file has.
506    fn fixed(resolved: &ResolvedTable) -> &[Field] {
507        match &resolved.columns {
508            Columns::Fixed(fields) => fields,
509            Columns::Parquet | Columns::Csv => {
510                panic!("{} resolves to a file", resolved.function.name())
511            }
512        }
513    }
514
515    /// A call of `count` integer arguments, which is what every series call looks like.
516    fn integers(count: usize) -> Vec<LogicalType> {
517        vec![LogicalType::BigInt; count]
518    }
519
520    #[test]
521    fn a_name_that_is_not_a_table_function_says_so_rather_than_binding() {
522        let error = resolve_table("read_csv", &integers(1)).unwrap_err();
523        assert!(error.to_string().contains("read_csv"), "{error}");
524    }
525
526    #[test]
527    fn both_names_resolve_and_each_one_names_its_own_column() {
528        let range = resolve_table("range", &integers(1)).unwrap();
529        assert_eq!(fixed(&range)[0].name, "range");
530        let series = resolve_table("GENERATE_SERIES", &integers(3)).unwrap();
531        assert_eq!(fixed(&series)[0].name, "generate_series");
532        assert_eq!(series.arguments.len(), 3);
533    }
534
535    #[test]
536    fn no_arguments_and_four_arguments_are_both_the_arity_error() {
537        assert!(resolve_table("range", &integers(0)).is_err());
538        assert!(resolve_table("range", &integers(4)).is_err());
539    }
540
541    #[test]
542    fn a_series_call_ignores_the_types_it_was_given_and_casts_them_all_to_bigint() {
543        let resolved =
544            resolve_table("range", &[LogicalType::Varchar, LogicalType::Double]).unwrap();
545        assert_eq!(resolved.arguments, integers(2));
546    }
547
548    #[test]
549    fn read_parquet_takes_one_string_and_says_its_columns_are_in_the_file() {
550        let resolved = resolve_table("read_parquet", &[LogicalType::Varchar]).unwrap();
551        assert_eq!(resolved.function, TableFunction::ReadParquet);
552        assert_eq!(resolved.arguments, vec![LogicalType::Varchar]);
553        assert_eq!(resolved.columns, Columns::Parquet);
554    }
555
556    #[test]
557    fn parquet_scan_is_the_same_function_under_duckdbs_other_name_for_it() {
558        assert_eq!(TableFunction::lookup("parquet_scan"), Some(TableFunction::ReadParquet));
559        // And it records itself under the one name, so a plan does not have two spellings in it.
560        let resolved = resolve_table("parquet_scan", &[LogicalType::Varchar]).unwrap();
561        assert_eq!(resolved.function.name(), "read_parquet");
562    }
563
564    #[test]
565    fn a_path_that_is_not_a_string_is_the_message_duckdb_gives_for_it() {
566        // Measured against v1.4.1 on server3: `read_parquet(3)` does not cast, it fails to match.
567        let error = resolve_table("read_parquet", &[LogicalType::Integer]).unwrap_err();
568        assert!(
569            error.message().starts_with(
570                "No function matches the given name and argument types 'read_parquet(INTEGER)'."
571            ),
572            "{error}"
573        );
574        assert!(error.message().contains("read_parquet(VARCHAR)"), "{error}");
575    }
576
577    #[test]
578    fn read_parquet_of_no_arguments_or_two_is_the_same_no_overload_message() {
579        let two = resolve_table("read_parquet", &[LogicalType::Varchar, LogicalType::Varchar]);
580        assert!(two.unwrap_err().message().contains("read_parquet(VARCHAR, VARCHAR)"));
581        let none = resolve_table("read_parquet", &[]);
582        assert!(none.unwrap_err().message().contains("read_parquet()"));
583    }
584
585    #[test]
586    fn range_stops_before_the_end_and_generate_series_stops_on_it() {
587        assert_eq!(series(TableFunction::Range, 0, 3, 1).unwrap(), vec![0, 1, 2]);
588        assert_eq!(series(TableFunction::GenerateSeries, 0, 3, 1).unwrap(), vec![0, 1, 2, 3]);
589    }
590
591    #[test]
592    fn a_step_that_does_not_divide_the_span_stops_before_the_end_of_it() {
593        // DuckDB gives 2, 4, 6 for both of these. The seven is not reached by either, which is
594        // where the two functions stop being different.
595        assert_eq!(series(TableFunction::Range, 2, 7, 2).unwrap(), vec![2, 4, 6]);
596        assert_eq!(series(TableFunction::GenerateSeries, 2, 7, 2).unwrap(), vec![2, 4, 6]);
597    }
598
599    #[test]
600    fn the_four_categories_come_out_of_the_grammars_five_rules() {
601        use rudb_parse::{COLUMN_NAME, FUNC_NAME, RESERVED, TYPE_NAME, UNRESERVED};
602        assert_eq!(keyword_categories(RESERVED), ["reserved"]);
603        assert_eq!(keyword_categories(UNRESERVED), ["unreserved"]);
604        assert_eq!(keyword_categories(COLUMN_NAME), ["column_name"]);
605        // The two rules that are one category. A word usable as a type name is usable as a function
606        // name, which is why the grammar has two rules where PostgreSQL has one category, and either
607        // rule on its own is still that one category rather than half of it.
608        assert_eq!(keyword_categories(FUNC_NAME | TYPE_NAME), ["type_function"]);
609        assert_eq!(keyword_categories(TYPE_NAME), ["type_function"]);
610        assert_eq!(keyword_categories(FUNC_NAME), ["type_function"]);
611        // Both, which is the case that makes one word two rows.
612        assert_eq!(keyword_categories(COLUMN_NAME | FUNC_NAME), ["column_name", "type_function"]);
613        // A word the grammar spells directly in a rule is in no class, and the pinned binary leaves
614        // all fifteen of those out of the table rather than giving them a category of their own.
615        assert!(keyword_categories(0).is_empty());
616    }
617
618    #[test]
619    fn a_metadata_table_given_an_argument_says_it_takes_none() {
620        for name in [
621            "rudb_strategies",
622            "duckdb_keywords",
623            "duckdb_types",
624            "duckdb_functions",
625            "duckdb_settings",
626            "duckdb_databases",
627            "duckdb_schemas",
628            "duckdb_tables",
629            "duckdb_columns",
630        ] {
631            let function = TableFunction::lookup(name).expect("a known function");
632            let error = resolve_table(name, &[LogicalType::BigInt]).expect_err("takes none");
633            assert!(
634                error.to_string().contains(&format!("{}() takes no arguments", function.name())),
635                "{error}"
636            );
637            let resolved = resolve_table(name, &[]).expect("takes none, and none were given");
638            assert_eq!(resolved.function, function);
639            assert!(matches!(resolved.columns, Columns::Fixed(_)));
640        }
641    }
642
643    #[test]
644    fn duckdb_keywords_has_duckdbs_two_columns_under_that_name() {
645        let resolved = resolve_table("DuckDB_Keywords", &[]).expect("a case insensitive name");
646        assert_eq!(resolved.function, TableFunction::DuckdbKeywords);
647        let Columns::Fixed(fields) = resolved.columns else { panic!("fixed columns") };
648        let names: Vec<&str> = fields.iter().map(|field| field.name.as_str()).collect();
649        assert_eq!(names, ["keyword_name", "keyword_category"]);
650        assert!(fields.iter().all(|field| field.ty == LogicalType::Varchar));
651    }
652
653    #[test]
654    fn duckdb_types_has_duckdbs_seventeen_columns_under_that_name() {
655        let resolved = resolve_table("DuckDB_Types", &[]).expect("a case insensitive name");
656        assert_eq!(resolved.function, TableFunction::DuckdbTypes);
657        let Columns::Fixed(fields) = resolved.columns else { panic!("fixed columns") };
658        let names: Vec<&str> = fields.iter().map(|field| field.name.as_str()).collect();
659        assert_eq!(names.len(), 17);
660        assert_eq!(names[0], "database_name");
661        assert_eq!(names[16], "varargs");
662        // The one column that is not a varchar, a bigint or a boolean, and the reason this table
663        // waited on the map vector.
664        let tags = fields.iter().find(|field| field.name == "tags").expect("a tags column");
665        assert_eq!(tags.ty, LogicalType::map(LogicalType::Varchar, LogicalType::Varchar));
666    }
667
668    #[test]
669    fn duckdb_settings_has_duckdbs_seven_columns_under_that_name() {
670        let resolved = resolve_table("DuckDB_Settings", &[]).expect("a case insensitive name");
671        assert_eq!(resolved.function, TableFunction::DuckdbSettings);
672        let Columns::Fixed(fields) = resolved.columns else { panic!("fixed columns") };
673        let names: Vec<&str> = fields.iter().map(|field| field.name.as_str()).collect();
674        assert_eq!(
675            names,
676            ["name", "value", "description", "input_type", "scope", "aliases", "typed_value"]
677        );
678        // The last one is a VARIANT in the pin and rudb has no such type, so it is text here.
679        assert_eq!(fields[6].ty, LogicalType::Varchar);
680    }
681
682    #[test]
683    fn a_negative_step_counts_down_and_stops_on_the_same_rule() {
684        assert_eq!(series(TableFunction::Range, 5, 1, -2).unwrap(), vec![5, 3]);
685        assert_eq!(series(TableFunction::GenerateSeries, 5, 1, -2).unwrap(), vec![5, 3, 1]);
686    }
687
688    #[test]
689    fn a_step_going_the_wrong_way_produces_nothing_rather_than_running_forever() {
690        assert!(series(TableFunction::Range, 0, 10, -1).unwrap().is_empty());
691        assert!(series(TableFunction::Range, 10, 0, 1).unwrap().is_empty());
692    }
693
694    #[test]
695    fn an_empty_range_and_a_single_value_series_are_the_boundary_between_the_two() {
696        assert!(series(TableFunction::Range, 4, 4, 1).unwrap().is_empty());
697        assert_eq!(series(TableFunction::GenerateSeries, 4, 4, 1).unwrap(), vec![4]);
698    }
699
700    #[test]
701    fn a_step_of_zero_is_the_one_case_that_is_an_error_rather_than_nothing() {
702        let error = series(TableFunction::Range, 1, 5, 0).unwrap_err();
703        assert!(error.to_string().contains("interval cannot be 0"), "{error}");
704    }
705
706    #[test]
707    fn a_span_that_does_not_fit_in_an_i64_does_not_overflow_the_length() {
708        // Not run, only counted. The point is that the count is worked out in i128, so this comes
709        // out as a huge number rather than as a negative one that becomes a capacity panic.
710        assert_eq!(length(TableFunction::Range, i64::MIN, i64::MAX, 1), usize::MAX);
711    }
712
713    #[test]
714    fn rudb_strategies_takes_no_arguments_and_produces_a_fixed_table() {
715        let resolved = resolve_table("rudb_strategies", &[]).unwrap();
716        assert_eq!(resolved.function, TableFunction::RudbStrategies);
717        assert!(resolved.arguments.is_empty());
718        assert_eq!(fixed(&resolved), strategy_fields());
719    }
720
721    #[test]
722    fn rudb_strategies_with_an_argument_says_it_takes_none() {
723        let error = resolve_table("rudb_strategies", &[LogicalType::BigInt]).unwrap_err();
724        assert!(error.to_string().contains("takes no arguments"), "{error}");
725    }
726}