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//! Twenty two 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 next ten are the third kind, a table whose rows are a fact about the engine rather than data
23//! somebody stored. All ten take no arguments and all ten know their own columns, so resolving one
24//! 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//! `duckdb_views()` and `duckdb_columns()` are the last five and they are further from this crate
35//! again, because their rows are whatever somebody created, so only their columns are here and
36//! [`crate::entrycatalog`] says why.
37//!
38//! `duckdb_extensions()` and `duckdb_optimizers()` are two more of that third kind and they are the
39//! two where rudb has to answer about itself rather than reproduce a list. `duckdb_optimizers()` is
40//! every name `SET disabled_optimizers` takes, which is DuckDB's forty four, because rudb takes all
41//! forty four and turning off a pass that was never written is a request that has already been
42//! granted. `duckdb_extensions()` is the same names DuckDB's default build advertises with rudb's own
43//! answer in the two boolean columns, and `rudb_exec` says which two are true and why.
44//!
45//! `pragma_version()`, `pragma_platform()`, `pragma_user_agent()` and `pragma_database_size()` are
46//! four more of that third kind and they are the four where the fact is about this build and this
47//! process rather than about the language. The first three are a constant worked out from the crate
48//! version and the target, and the fourth reads the catalog and the memory budget, so like
49//! `duckdb_settings()` its rows are not all known here. `rudb_exec::enginenames` decides all four
50//! values and argues there for why they describe rudb rather than reporting DuckDB's answers.
51//!
52//! D2 adds a few more of that third kind. Each one is a column list here and a list of rows in
53//! `rudb_exec::metadata`, and nothing else.
54//!
55//! `rudb_device_card(path)` is the one of that kind that takes an argument, because the fact it
56//! reports is about a directory rather than the process: what a sync costs on the device under it,
57//! measured the way `engine-v4/16-measurement.md` section 16.3 says. It resolves in its own arm
58//! because its argument is a path and an optional iteration count, which is neither a table name
59//! nor nothing.
60//!
61//! `pragma_table_info()` and `pragma_show()` are a fourth kind and the first two of the pragma
62//! family. They take one table name and describe whatever it names, so their columns are fixed and
63//! their rows are not a fact about the engine at all, they are a fact about one entry in a catalog.
64//! That makes them the first table functions here whose answer the binder settles on its own: it
65//! binds the name the way `DESCRIBE` binds one and hands back the rows, which is why nothing in
66//! `rudb_exec` knows either name.
67
68use rudb_common::{Error, Field, LogicalType, Result};
69
70use crate::entrycatalog::{
71    column_fields, database_fields, schema_fields, show_database_fields, show_expanded_fields,
72    show_table_fields, table_fields, view_fields,
73};
74use crate::functioncatalog::function_fields;
75use crate::settingcatalog::setting_fields;
76use crate::typecatalog::type_fields;
77
78/// Which table function a call resolved to.
79///
80/// An enum rather than a name, because the executor dispatches on this and a string comparison per
81/// operator build is a string comparison that can be spelled wrong.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum TableFunction {
84    /// `range(stop)`, `range(start, stop)`, `range(start, stop, step)`, stopping before the end.
85    Range,
86    /// The same three, stopping on the end.
87    GenerateSeries,
88    /// `read_parquet(path)`, the rows of a Parquet file.
89    ReadParquet,
90    /// `read_csv(path)`, the rows of a CSV file, with everything about how it is written sniffed.
91    ReadCsv,
92    /// `rudb_strategies()`, every seam and every implementation registered against it.
93    RudbStrategies,
94    /// `rudb_links()`, every relationship declared and what is stored for it.
95    RudbLinks,
96    /// `rudb_device_card(path)`, what a sync costs on the device a directory is on.
97    RudbDeviceCard,
98    /// `rudb_write_metrics()`, what each stage of the recent bulk loads cost.
99    RudbWriteMetrics,
100    /// `duckdb_keywords()`, every word the grammar knows and which class each one is in.
101    DuckdbKeywords,
102    /// `duckdb_types()`, every type name the engine knows and what each one stands for.
103    DuckdbTypes,
104    /// `duckdb_functions()`, every function the engine knows and what each one takes.
105    DuckdbFunctions,
106    /// `duckdb_settings()`, every setting `SET` will take and what each one is now.
107    DuckdbSettings,
108    /// `duckdb_databases()`, every database attached to this session.
109    DuckdbDatabases,
110    /// `duckdb_schemas()`, every schema in every one of them.
111    DuckdbSchemas,
112    /// `duckdb_tables()`, every base table somebody created.
113    DuckdbTables,
114    /// `duckdb_views()`, every view somebody created.
115    DuckdbViews,
116    /// `duckdb_columns()`, every column of every one of those.
117    DuckdbColumns,
118    /// `duckdb_extensions()`, every extension DuckDB names and whether this engine has it.
119    DuckdbExtensions,
120    /// `duckdb_optimizers()`, every name `SET disabled_optimizers` takes.
121    DuckdbOptimizers,
122    /// `duckdb_dialects()`, every installed SQL parser dialect.
123    DuckdbDialects,
124    /// `duckdb_grammar_extensions()`, every installed grammar extension.
125    DuckdbGrammarExtensions,
126    /// `pragma_table_info(name)`, the columns of one table or view, in SQLite's six columns.
127    PragmaTableInfo,
128    /// `pragma_show(name)`, the same columns again in the six `DESCRIBE` answers with.
129    PragmaShow,
130    /// `pragma_storage_info(name)`, what every stored part of every column of one table is.
131    PragmaStorageInfo,
132    /// `pragma_version()`, the version of the engine answering, in three columns.
133    PragmaVersion,
134    /// `pragma_platform()`, the operating system and processor this build was made for.
135    PragmaPlatform,
136    /// `pragma_user_agent()`, the one line a client sends when it says who it is.
137    PragmaUserAgent,
138    /// `pragma_database_size()`, what each attached database costs on disk and in memory.
139    PragmaDatabaseSize,
140    /// `PRAGMA show_tables`, the name of everything an unqualified name can reach.
141    PragmaShowTables,
142    /// `PRAGMA show_databases`, the name of everything that is attached.
143    PragmaShowDatabases,
144    /// `PRAGMA show_tables_expanded`, every table and view anywhere with its columns beside it.
145    PragmaShowTablesExpanded,
146}
147
148/// The name of the column `file_row_number=True` adds.
149///
150/// Here rather than in the binder because the executor is the half that fills it in and the two
151/// have to agree on the spelling. It is DuckDB's name for it, and the column is a row's ordinal
152/// inside its own file rather than inside the read, so a glob of three files counts from zero three
153/// times.
154pub const FILE_ROW_NUMBER: &str = "file_row_number";
155
156impl TableFunction {
157    /// The name the plan records and an error message says.
158    #[must_use]
159    pub const fn name(self) -> &'static str {
160        match self {
161            Self::Range => "range",
162            Self::GenerateSeries => "generate_series",
163            Self::ReadParquet => "read_parquet",
164            Self::ReadCsv => "read_csv",
165            Self::RudbStrategies => "rudb_strategies",
166            Self::RudbLinks => "rudb_links",
167            Self::RudbDeviceCard => "rudb_device_card",
168            Self::RudbWriteMetrics => "rudb_write_metrics",
169            Self::DuckdbKeywords => "duckdb_keywords",
170            Self::DuckdbTypes => "duckdb_types",
171            Self::DuckdbFunctions => "duckdb_functions",
172            Self::DuckdbSettings => "duckdb_settings",
173            Self::DuckdbDatabases => "duckdb_databases",
174            Self::DuckdbSchemas => "duckdb_schemas",
175            Self::DuckdbTables => "duckdb_tables",
176            Self::DuckdbViews => "duckdb_views",
177            Self::DuckdbColumns => "duckdb_columns",
178            Self::DuckdbExtensions => "duckdb_extensions",
179            Self::DuckdbOptimizers => "duckdb_optimizers",
180            Self::DuckdbDialects => "duckdb_dialects",
181            Self::DuckdbGrammarExtensions => "duckdb_grammar_extensions",
182            Self::PragmaTableInfo => "pragma_table_info",
183            Self::PragmaShow => "pragma_show",
184            Self::PragmaStorageInfo => "pragma_storage_info",
185            Self::PragmaVersion => "pragma_version",
186            Self::PragmaPlatform => "pragma_platform",
187            Self::PragmaUserAgent => "pragma_user_agent",
188            Self::PragmaDatabaseSize => "pragma_database_size",
189            Self::PragmaShowTables => "pragma_show_tables",
190            Self::PragmaShowDatabases => "pragma_show_databases",
191            Self::PragmaShowTablesExpanded => "pragma_show_tables_expanded",
192        }
193    }
194
195    /// Whether the name can be written where a table goes, rather than only after the word `PRAGMA`.
196    ///
197    /// Nine of the pin's nineteen query pragmas answer to `pragma_name()` in a `FROM` clause and ten
198    /// do not, and which is which was measured rather than guessed. `SELECT * FROM
199    /// pragma_show_tables()` on the pin is `Catalog Error: Table Function with name
200    /// pragma_show_tables does not exist!` while `PRAGMA show_tables` returns rows, so the two
201    /// namespaces really are separate and a name in one is not a name in the other. These three are
202    /// the ones rudb has from the pragma only half of that, and the rest of that half are `ATTACH`
203    /// and `COPY` in disguise or want something rudb has not written.
204    #[must_use]
205    pub const fn reachable_as_a_function(self) -> bool {
206        !matches!(
207            self,
208            Self::PragmaShowTables | Self::PragmaShowDatabases | Self::PragmaShowTablesExpanded
209        )
210    }
211
212    /// Whether the call takes one table name and answers about whatever that names.
213    ///
214    /// The two pragmas are the only ones, and they are a family rather than a pair because the rest
215    /// of the `pragma_*` functions that take a name are the storage ones, which land here the day
216    /// rudb has storage to describe.
217    #[must_use]
218    pub const fn takes_a_name(self) -> bool {
219        matches!(self, Self::PragmaTableInfo | Self::PragmaShow | Self::PragmaStorageInfo)
220    }
221
222    /// Whether the answer is settled while the call is bound rather than while the query runs.
223    ///
224    /// The two column describing pragmas are, because the columns of a table are known by the time
225    /// its name has resolved, so the rows are constants from there on and the call comes out as a
226    /// `VALUES`. `pragma_storage_info` is not, because its rows are read off the file and there are
227    /// as many of them as the table has parts times columns, which at SF1 is six thousand for
228    /// lineitem alone. Folding that into the plan would put six thousand rows of constants through
229    /// every pass the optimizer has, to produce a table the executor can hand back a chunk at a
230    /// time.
231    #[must_use]
232    pub const fn answered_when_bound(self) -> bool {
233        matches!(self, Self::PragmaTableInfo | Self::PragmaShow)
234    }
235
236    /// Whether the last value is produced.
237    ///
238    /// Only the two series functions differ here. The file readers answer false and nothing asks
239    /// them.
240    #[must_use]
241    pub const fn inclusive(self) -> bool {
242        matches!(self, Self::GenerateSeries)
243    }
244
245    /// The named parameters the call takes, and the type each one wants.
246    ///
247    /// This is the list rudb acts on and not the list DuckDB prints, and the difference is worth
248    /// being plain about. `read_parquet` there takes seventeen named parameters and `read_csv`
249    /// takes around thirty. One of the Parquet ones is on the critical path, since the ClickBench
250    /// entry reads its file with `binary_as_string=True` and without it every string column in
251    /// `hits.parquet` comes back as `BLOB`, and the other sixteen have no caller here yet. A
252    /// parameter that is listed is one that does something, so this list grows as they land rather
253    /// than accepting names and ignoring them, which is the failure mode that makes an option look
254    /// supported when it is not.
255    ///
256    /// The CSV ones here are the ones that say how the file is written, which are the ones where
257    /// guessing wrong changes the answer rather than the speed. `sep` is DuckDB's other name for
258    /// `delim` and is a separate row rather than an alias, because the list is also what the
259    /// candidates on a misspelling are read out of and the binary prints both of them.
260    #[must_use]
261    pub fn parameters(self) -> &'static [(&'static str, LogicalType)] {
262        static READ_PARQUET: &[(&str, LogicalType)] = &[
263            ("binary_as_string", LogicalType::Boolean),
264            ("file_row_number", LogicalType::Boolean),
265        ];
266        static READ_CSV: &[(&str, LogicalType)] = &[
267            ("all_varchar", LogicalType::Boolean),
268            ("delim", LogicalType::Varchar),
269            ("escape", LogicalType::Varchar),
270            ("header", LogicalType::Boolean),
271            ("quote", LogicalType::Varchar),
272            ("sep", LogicalType::Varchar),
273        ];
274        match self {
275            Self::ReadParquet => READ_PARQUET,
276            Self::ReadCsv => READ_CSV,
277            _ => &[],
278        }
279    }
280
281    /// The function of that name, if there is one.
282    #[must_use]
283    pub fn lookup(name: &str) -> Option<Self> {
284        if name.eq_ignore_ascii_case("range") {
285            return Some(Self::Range);
286        }
287        if name.eq_ignore_ascii_case("generate_series") {
288            return Some(Self::GenerateSeries);
289        }
290        if name.eq_ignore_ascii_case("read_parquet") || name.eq_ignore_ascii_case("parquet_scan") {
291            return Some(Self::ReadParquet);
292        }
293        // `read_csv_auto` is the older spelling and DuckDB still answers to it. It meant sniffing
294        // back when `read_csv` did not sniff unless it was told to, and today they are the same
295        // function, which is why they are the same variant here.
296        if name.eq_ignore_ascii_case("read_csv") || name.eq_ignore_ascii_case("read_csv_auto") {
297            return Some(Self::ReadCsv);
298        }
299        if name.eq_ignore_ascii_case("rudb_strategies") {
300            return Some(Self::RudbStrategies);
301        }
302        if name.eq_ignore_ascii_case("rudb_links") {
303            return Some(Self::RudbLinks);
304        }
305        if name.eq_ignore_ascii_case("rudb_device_card") {
306            return Some(Self::RudbDeviceCard);
307        }
308        if name.eq_ignore_ascii_case("rudb_write_metrics") {
309            return Some(Self::RudbWriteMetrics);
310        }
311        if name.eq_ignore_ascii_case("duckdb_keywords") {
312            return Some(Self::DuckdbKeywords);
313        }
314        if name.eq_ignore_ascii_case("duckdb_types") {
315            return Some(Self::DuckdbTypes);
316        }
317        if name.eq_ignore_ascii_case("duckdb_functions") {
318            return Some(Self::DuckdbFunctions);
319        }
320        if name.eq_ignore_ascii_case("duckdb_settings") {
321            return Some(Self::DuckdbSettings);
322        }
323        if name.eq_ignore_ascii_case("duckdb_databases") {
324            return Some(Self::DuckdbDatabases);
325        }
326        if name.eq_ignore_ascii_case("duckdb_schemas") {
327            return Some(Self::DuckdbSchemas);
328        }
329        if name.eq_ignore_ascii_case("duckdb_tables") {
330            return Some(Self::DuckdbTables);
331        }
332        if name.eq_ignore_ascii_case("duckdb_views") {
333            return Some(Self::DuckdbViews);
334        }
335        if name.eq_ignore_ascii_case("duckdb_columns") {
336            return Some(Self::DuckdbColumns);
337        }
338        if name.eq_ignore_ascii_case("duckdb_extensions") {
339            return Some(Self::DuckdbExtensions);
340        }
341        if name.eq_ignore_ascii_case("duckdb_optimizers") {
342            return Some(Self::DuckdbOptimizers);
343        }
344        if name.eq_ignore_ascii_case("duckdb_dialects") {
345            return Some(Self::DuckdbDialects);
346        }
347        if name.eq_ignore_ascii_case("duckdb_grammar_extensions") {
348            return Some(Self::DuckdbGrammarExtensions);
349        }
350        if name.eq_ignore_ascii_case("pragma_table_info") {
351            return Some(Self::PragmaTableInfo);
352        }
353        if name.eq_ignore_ascii_case("pragma_show") {
354            return Some(Self::PragmaShow);
355        }
356        if name.eq_ignore_ascii_case("pragma_storage_info") {
357            return Some(Self::PragmaStorageInfo);
358        }
359        if name.eq_ignore_ascii_case("pragma_version") {
360            return Some(Self::PragmaVersion);
361        }
362        if name.eq_ignore_ascii_case("pragma_platform") {
363            return Some(Self::PragmaPlatform);
364        }
365        if name.eq_ignore_ascii_case("pragma_user_agent") {
366            return Some(Self::PragmaUserAgent);
367        }
368        if name.eq_ignore_ascii_case("pragma_database_size") {
369            return Some(Self::PragmaDatabaseSize);
370        }
371        if name.eq_ignore_ascii_case("pragma_show_tables") {
372            return Some(Self::PragmaShowTables);
373        }
374        if name.eq_ignore_ascii_case("pragma_show_databases") {
375            return Some(Self::PragmaShowDatabases);
376        }
377        if name.eq_ignore_ascii_case("pragma_show_tables_expanded") {
378            return Some(Self::PragmaShowTablesExpanded);
379        }
380        None
381    }
382}
383
384/// Where a call's columns come from.
385///
386/// A table function that produces a fixed set of columns is resolved by this crate and nothing
387/// else has to be consulted. One that reads a file is not, because the columns are in the file, so
388/// the answer here is which file to open rather than what is in it. An enum rather than an empty
389/// column list, because an empty list is what `read_parquet` of a file with no columns would also
390/// give and a caller that forgot to handle the case would get an empty table instead of an error.
391#[derive(Debug, Clone, PartialEq, Eq)]
392pub enum Columns {
393    /// The columns this call produces, with the names an unaliased call gives them.
394    Fixed(Vec<Field>),
395    /// The columns of the Parquet file the first argument names.
396    Parquet,
397    /// The columns of the CSV file the first argument names, which are sniffed out of its front.
398    Csv,
399}
400
401/// A resolved table function call.
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct ResolvedTable {
404    /// Which function.
405    pub function: TableFunction,
406    /// What each argument has to be cast to, the same length as what was passed in.
407    pub arguments: Vec<LogicalType>,
408    /// Where the columns the call produces come from.
409    pub columns: Columns,
410}
411
412/// Resolve a table function call by name and the types of its arguments.
413///
414/// The series pair does not consult the types, only the count, because it takes integers in every
415/// position and the binder casts to that, so there is nothing there for a type to choose between.
416/// DuckDB also has a timestamp and interval form of both, which is a second set of columns rather
417/// than a second overload of the same ones, and adding it means adding it rather than widening this.
418///
419/// The file readers do consult them, because DuckDB does. `read_parquet(3)` and `read_csv(3)` are
420/// binder errors there rather than reads of a file called `3`, which was measured against the binary
421/// rather than assumed, and it is the right answer: a path that arrived as a number is a query that
422/// meant something else.
423///
424/// # Errors
425///
426/// When no table function has that name, or when it has that name and not those arguments.
427pub fn resolve_table(name: &str, arguments: &[LogicalType]) -> Result<ResolvedTable> {
428    let function = match TableFunction::lookup(name) {
429        // A pragma only name written in a `FROM` clause is a name that does not exist there, which
430        // is the pin's answer and not a shortcut: the two namespaces are separate and this is the
431        // side of the fence the caller is standing on.
432        Some(function) if function.reachable_as_a_function() => function,
433        _ => {
434            return Err(Error::catalog(format!("Table Function with name {name} does not exist!")));
435        }
436    };
437    resolve_found(function, arguments)
438}
439
440/// The same resolution once the name has been settled, which is where the two spellings meet.
441///
442/// Split out of [`resolve_table`] because a pragma only name has to get here without going past the
443/// check that turns it down in a `FROM` clause.
444fn resolve_found(function: TableFunction, arguments: &[LogicalType]) -> Result<ResolvedTable> {
445    if let Some(columns) = file_columns(function) {
446        // Two overloads, one path and a list of them, which is DuckDB's pair. The list is where
447        // `read_parquet(['a.parquet', 'b.parquet'])` binds. An empty list is a list of the untyped
448        // null and it binds here too, because a list with nothing in it is a fine list and the
449        // objection to it is that it names no file, which is what the reader says about it rather
450        // than what this table says.
451        let single = arguments.len() == 1 && arguments[0] == LogicalType::Varchar;
452        let many = arguments.len() == 1
453            && matches!(&arguments[0], LogicalType::List(element)
454                if **element == LogicalType::Varchar || **element == LogicalType::Null);
455        // A bare null matches, and is a sentence about nulls rather than about overloads, which is
456        // what DuckDB answers `read_parquet(NULL)` with. It is left as a null rather than cast to a
457        // path so that the binder still has a null to recognise when it goes looking for the name.
458        let nothing = arguments.len() == 1 && arguments[0] == LogicalType::Null;
459        if !single && !many && !nothing {
460            return Err(no_overload(function, arguments));
461        }
462        // A list keeps the type it arrived with rather than being cast to a list of strings, because
463        // the two that reach here are already one of those and a cast between two list types is
464        // machinery this does not need. The reader reads the values and not the declaration.
465        let wanted = if many {
466            arguments[0].clone()
467        } else if nothing {
468            LogicalType::Null
469        } else {
470            LogicalType::Varchar
471        };
472        return Ok(ResolvedTable { function, arguments: vec![wanted], columns });
473    }
474    if function.takes_a_name() {
475        // One name, and a null is one of them. `pragma_table_info(NULL)` is a catalog error about a
476        // table called NULL on the pin rather than a complaint about the argument, because the
477        // pragma turns whatever it was given into text before it goes looking, so the null is left
478        // as a null here and the binder does the same thing with it.
479        let single = arguments.len() == 1
480            && matches!(arguments[0], LogicalType::Varchar | LogicalType::Null);
481        if !single {
482            return Err(one_name(function, arguments));
483        }
484        return Ok(ResolvedTable {
485            function,
486            arguments: vec![arguments[0].clone()],
487            columns: Columns::Fixed(name_columns(function)),
488        });
489    }
490    if function == TableFunction::RudbDeviceCard {
491        return device_card(arguments);
492    }
493    let arity = arguments.len();
494    // The metadata tables take nothing and their columns are fixed, which makes them the simplest
495    // case here. They are one arm rather than one each because the only thing that differs is the
496    // column list, and a name that is added to this list and not to `lookup` cannot be reached.
497    if let Some(columns) = fixed_columns(function) {
498        if arity != 0 {
499            return Err(nothing_at_all(function, arguments));
500        }
501        return Ok(ResolvedTable {
502            function,
503            arguments: Vec::new(),
504            columns: Columns::Fixed(columns),
505        });
506    }
507    if !(1..=3).contains(&arity) {
508        return Err(Error::binder(format!(
509            "Table function {}() takes between 1 and 3 arguments, {arity} were given",
510            function.name()
511        )));
512    }
513    Ok(ResolvedTable {
514        function,
515        arguments: vec![LogicalType::BigInt; arity],
516        columns: Columns::Fixed(vec![Field::new(function.name(), LogicalType::BigInt)]),
517    })
518}
519
520/// The same resolution for a call the user wrote as `PRAGMA name`, whose messages spell it so.
521///
522/// Every pragma is an ordinary table function under a longer name, so the resolution is
523/// [`resolve_table`] and nothing else. What changes is what a bad call says. Upstream writes both
524/// halves of that message in the form the user used, so `PRAGMA table_info('a', 'b')` is
525/// `'table_info(VARCHAR, VARCHAR)'` with a candidate line reading `PRAGMA "table_info"(VARCHAR)`.
526/// Handing back a complaint about a `pragma_table_info` nobody typed would be handing the user the
527/// rewrite to debug rather than their own statement.
528///
529/// Only two shapes can reach this. A pragma never reads a file and is never `range`, so the
530/// overload it has is either one name or nothing at all, and the candidate line says which.
531///
532/// # Errors
533///
534/// When the function has that name and not those arguments, and otherwise whatever
535/// [`resolve_table`] says.
536pub fn resolve_pragma(name: &str, arguments: &[LogicalType]) -> Result<ResolvedTable> {
537    let Some(function) = TableFunction::lookup(name) else {
538        return Err(Error::catalog(format!("Table Function with name {name} does not exist!")));
539    };
540    // Through [`resolve_found`] rather than [`resolve_table`], because three of these names only
541    // exist after the word `PRAGMA` and the other spelling is where they are turned down.
542    if let Ok(resolved) = resolve_found(function, arguments) {
543        return Ok(resolved);
544    }
545    let spelled = name.strip_prefix("pragma_").unwrap_or(name);
546    // A pragma that takes nothing prints no parentheses at all on the candidate line, where the
547    // function spelling of the same complaint prints an empty pair. Measured on the pin, which
548    // answers `PRAGMA version(1)` with a candidate reading `PRAGMA "version"` and stopping there.
549    let takes = if function.takes_a_name() { "(VARCHAR)" } else { "" };
550    let written: Vec<String> = arguments.iter().map(ToString::to_string).collect();
551    Err(Error::binder(format!(
552        "No function matches the given name and argument types '{spelled}({})'. You might need to \
553         add explicit type casts.\n\tCandidate functions:\n\tPRAGMA \"{spelled}\"{takes}\n",
554        written.join(", ")
555    )))
556}
557
558/// Where a file reading table function's columns come from, and `None` for one that does not read
559/// a file.
560fn file_columns(function: TableFunction) -> Option<Columns> {
561    match function {
562        TableFunction::ReadParquet => Some(Columns::Parquet),
563        TableFunction::ReadCsv => Some(Columns::Csv),
564        TableFunction::Range
565        | TableFunction::GenerateSeries
566        | TableFunction::RudbStrategies
567        | TableFunction::RudbLinks
568        | TableFunction::RudbDeviceCard
569        | TableFunction::RudbWriteMetrics
570        | TableFunction::DuckdbKeywords
571        | TableFunction::DuckdbTypes
572        | TableFunction::DuckdbFunctions
573        | TableFunction::DuckdbSettings
574        | TableFunction::DuckdbDatabases
575        | TableFunction::DuckdbSchemas
576        | TableFunction::DuckdbTables
577        | TableFunction::DuckdbViews
578        | TableFunction::DuckdbColumns
579        | TableFunction::DuckdbExtensions
580        | TableFunction::DuckdbOptimizers
581        | TableFunction::DuckdbDialects
582        | TableFunction::DuckdbGrammarExtensions
583        | TableFunction::PragmaTableInfo
584        | TableFunction::PragmaShow
585        | TableFunction::PragmaStorageInfo
586        | TableFunction::PragmaVersion
587        | TableFunction::PragmaPlatform
588        | TableFunction::PragmaUserAgent
589        | TableFunction::PragmaDatabaseSize
590        | TableFunction::PragmaShowTables
591        | TableFunction::PragmaShowDatabases
592        | TableFunction::PragmaShowTablesExpanded => None,
593    }
594}
595
596/// The columns of a table function that takes no arguments and knows its own, and `None` for one
597/// that has to look at what it was called with.
598fn fixed_columns(function: TableFunction) -> Option<Vec<Field>> {
599    match function {
600        TableFunction::RudbStrategies => Some(strategy_fields()),
601        TableFunction::RudbLinks => Some(link_fields()),
602        TableFunction::RudbWriteMetrics => Some(write_metric_fields()),
603        TableFunction::DuckdbKeywords => Some(keyword_fields()),
604        TableFunction::DuckdbTypes => Some(type_fields()),
605        TableFunction::DuckdbFunctions => Some(function_fields()),
606        TableFunction::DuckdbSettings => Some(setting_fields()),
607        TableFunction::DuckdbDatabases => Some(database_fields()),
608        TableFunction::DuckdbSchemas => Some(schema_fields()),
609        TableFunction::DuckdbTables => Some(table_fields()),
610        TableFunction::DuckdbViews => Some(view_fields()),
611        TableFunction::DuckdbColumns => Some(column_fields()),
612        TableFunction::DuckdbExtensions => Some(extension_fields()),
613        TableFunction::DuckdbOptimizers => Some(optimizer_fields()),
614        TableFunction::DuckdbDialects => Some(dialect_fields()),
615        TableFunction::DuckdbGrammarExtensions => Some(grammar_extension_fields()),
616        TableFunction::PragmaVersion => Some(version_fields()),
617        TableFunction::PragmaPlatform => Some(platform_fields()),
618        TableFunction::PragmaUserAgent => Some(user_agent_fields()),
619        TableFunction::PragmaDatabaseSize => Some(database_size_fields()),
620        TableFunction::PragmaShowTables => Some(show_table_fields()),
621        TableFunction::PragmaShowDatabases => Some(show_database_fields()),
622        TableFunction::PragmaShowTablesExpanded => Some(show_expanded_fields()),
623        TableFunction::Range
624        | TableFunction::GenerateSeries
625        | TableFunction::ReadParquet
626        | TableFunction::ReadCsv
627        | TableFunction::RudbDeviceCard
628        | TableFunction::PragmaTableInfo
629        | TableFunction::PragmaShow
630        | TableFunction::PragmaStorageInfo => None,
631    }
632}
633
634/// `rudb_device_card(path)` and `rudb_device_card(path, iterations)`.
635///
636/// The path is a directory and the card is about the device under it. The second argument is how
637/// many timed iterations each sync probe runs, and giving it at all means measuring again rather
638/// than reading the card this process already has for that device, which is what somebody passing
639/// `2000` to get the spec's precision wants. A null path is left a null so the executor can say
640/// what is wrong with it in its own words, the same as the file readers do.
641fn device_card(arguments: &[LogicalType]) -> Result<ResolvedTable> {
642    let function = TableFunction::RudbDeviceCard;
643    let path = matches!(arguments.first(), Some(LogicalType::Varchar | LogicalType::Null));
644    let count = arguments.get(1).is_none_or(LogicalType::is_integer);
645    if !path || !count || arguments.len() > 2 {
646        return Err(Error::binder(format!(
647            "No function matches the given name and argument types '{}({})'. You might need to \
648             add explicit type casts.\n\tCandidate functions:\n\t{0}(VARCHAR)\n\t{0}(VARCHAR, \
649             BIGINT)\n",
650            function.name(),
651            arguments.iter().map(ToString::to_string).collect::<Vec<_>>().join(", ")
652        )));
653    }
654    let mut wanted = vec![arguments[0].clone()];
655    if arguments.len() == 2 {
656        wanted.push(LogicalType::BigInt);
657    }
658    Ok(ResolvedTable { function, arguments: wanted, columns: Columns::Fixed(device_card_fields()) })
659}
660
661/// The columns `rudb_device_card()` produces, one row per sync call the platform has.
662///
663/// The first row is the call `commit_sync = full` uses, and the columns after `plausible` are about
664/// the device rather than the call, so they repeat on every row. One row per call rather than one
665/// row with a column per call, because which calls there are depends on the platform and a column
666/// that is null on every Linux machine is a column nobody can write a query against. The latencies
667/// are microseconds as doubles, because that is the unit the spec states them in and a p50 of 24 µs
668/// and one of 3,347 µs should both read as what they are.
669#[must_use]
670pub fn device_card_fields() -> Vec<Field> {
671    vec![
672        Field::new("path", LogicalType::Varchar),
673        Field::new("device", LogicalType::Varchar),
674        Field::new("filesystem", LogicalType::Varchar),
675        Field::new("sync_call", LogicalType::Varchar),
676        Field::new("chosen", LogicalType::Boolean),
677        Field::new("p50_4k_us", LogicalType::Double),
678        Field::new("p99_4k_us", LogicalType::Double),
679        Field::new("p50_64k_us", LogicalType::Double),
680        Field::new("p99_64k_us", LogicalType::Double),
681        Field::new("plausible", LogicalType::Boolean),
682        Field::new("write_mib_s", LogicalType::Double),
683        Field::new("syncs_1", LogicalType::BigInt),
684        Field::new("syncs_2", LogicalType::BigInt),
685        Field::new("syncs_4", LogicalType::BigInt),
686        Field::new("syncs_8", LogicalType::BigInt),
687        Field::new("scaling", LogicalType::Double),
688        Field::new("plp", LogicalType::Varchar),
689        Field::new("memory_backed", LogicalType::Boolean),
690        Field::new("lanes", LogicalType::Integer),
691        Field::new("iterations", LogicalType::Integer),
692    ]
693}
694
695/// The columns one of the two name taking pragmas produces.
696fn name_columns(function: TableFunction) -> Vec<Field> {
697    match function {
698        TableFunction::PragmaShow => describe_fields(),
699        TableFunction::PragmaStorageInfo => storage_info_fields(),
700        _ => table_info_fields(),
701    }
702}
703
704/// The columns `pragma_table_info()` produces, which is SQLite's six.
705///
706/// DuckDB answers to this because SQLite did, and the six are SQLite's names, its order and its
707/// types right down to `cid` being a 32 bit integer where everything else in these tables is a
708/// bigint. The one departure from SQLite is that `notnull` and `pk` are booleans rather than the
709/// zero or one SQLite prints, which was measured rather than assumed.
710///
711/// `dflt_value` and `pk` are null and false on everything rudb can declare, because `DEFAULT`,
712/// `PRIMARY KEY` and `UNIQUE` are all refused by `CREATE TABLE` today. They are here rather than
713/// left out because the width of a result is part of the result. `DESCRIBE` says the same three
714/// nothings in its own three columns and for the same reason.
715#[must_use]
716pub fn table_info_fields() -> Vec<Field> {
717    vec![
718        Field::new("cid", LogicalType::Integer),
719        Field::new("name", LogicalType::Varchar),
720        Field::new("type", LogicalType::Varchar),
721        Field::new("notnull", LogicalType::Boolean),
722        Field::new("dflt_value", LogicalType::Varchar),
723        Field::new("pk", LogicalType::Boolean),
724    ]
725}
726
727/// The columns `DESCRIBE` answers with, which is what `pragma_show()` produces too.
728///
729/// One list rather than two because the two really are the same six columns: `pragma_show('t')` and
730/// `DESCRIBE t` return the same rows on the pin, which is what you would expect of a pragma that
731/// exists so a client can write the describe as a function call and select from it.
732#[must_use]
733pub fn describe_fields() -> Vec<Field> {
734    ["column_name", "column_type", "null", "key", "default", "extra"]
735        .iter()
736        .map(|name| Field::new(*name, LogicalType::Varchar))
737        .collect()
738}
739
740/// The columns `pragma_storage_info()` produces, which is DuckDB's sixteen.
741///
742/// The names, the order and the types are the pin's, measured against 1.5.5 rather than read off
743/// the documentation, down to `additional_block_ids` being a list of bigints on a table that has
744/// nothing to put in it.
745///
746/// What each one means here is the interesting part, because the words are DuckDB's and the
747/// storage is ours. A row group is a stripe and a segment is a part, which is the same split under
748/// both names: the unit a file is written in and the unit a scan reads. `compression` is what the
749/// encoder chose for that part, spelled the way `rudb-encoding` spells a cascade, so it reads
750/// `DICT(PACKED, PACKED)` rather than one of DuckDB's single words. `block_id` is where in the file
751/// the column page holding the part starts, because a page is what a read actually moves, and
752/// `block_offset` is where the part sits inside it. `segment_info` carries the stored size of the
753/// part, which is the one number in the row nothing else says.
754///
755/// `has_updates` is false and `persistent` is true on everything, and both will mean something the
756/// day a native table has a delta region to report. They are here rather than left out because the
757/// width of a result is part of the result.
758#[must_use]
759pub fn storage_info_fields() -> Vec<Field> {
760    vec![
761        Field::new("row_group_id", LogicalType::BigInt),
762        Field::new("column_name", LogicalType::Varchar),
763        Field::new("column_id", LogicalType::BigInt),
764        Field::new("column_path", LogicalType::Varchar),
765        Field::new("segment_id", LogicalType::BigInt),
766        Field::new("segment_type", LogicalType::Varchar),
767        Field::new("start", LogicalType::BigInt),
768        Field::new("count", LogicalType::BigInt),
769        Field::new("compression", LogicalType::Varchar),
770        Field::new("stats", LogicalType::Varchar),
771        Field::new("has_updates", LogicalType::Boolean),
772        Field::new("persistent", LogicalType::Boolean),
773        Field::new("block_id", LogicalType::BigInt),
774        Field::new("block_offset", LogicalType::BigInt),
775        Field::new("segment_info", LogicalType::Varchar),
776        Field::new("additional_block_ids", LogicalType::list(LogicalType::BigInt)),
777    ]
778}
779
780/// The columns `pragma_version()` produces.
781///
782/// Three columns rather than one, because a build has three things worth asking about: which release
783/// it is, which source it was made from and what that release is called. rudb answers all three
784/// about itself rather than reporting a DuckDB version, for the reason `crate` level compatibility
785/// does not extend to lying about which engine is running. `crates/rudb-exec/src/enginenames.rs` is
786/// where the three values are decided and it argues the case there.
787#[must_use]
788pub fn version_fields() -> Vec<Field> {
789    ["library_version", "source_id", "codename"]
790        .iter()
791        .map(|name| Field::new(*name, LogicalType::Varchar))
792        .collect()
793}
794
795/// The column `pragma_platform()` produces, which is the name a build is published under.
796#[must_use]
797pub fn platform_fields() -> Vec<Field> {
798    vec![Field::new("platform", LogicalType::Varchar)]
799}
800
801/// The column `pragma_user_agent()` produces, which is the line a client sends to say who it is.
802#[must_use]
803pub fn user_agent_fields() -> Vec<Field> {
804    vec![Field::new("user_agent", LogicalType::Varchar)]
805}
806
807/// The columns `pragma_database_size()` produces, one row per attached database.
808///
809/// Three of the nine are a size written for a person to read rather than a number, which is DuckDB's
810/// choice and not a helpful one for a client doing arithmetic, but the width and the types of a
811/// result are part of the result. The four block columns are the ones that mean something only once
812/// there is a file underneath, so they are the ones rudb answers zero to and says why.
813#[must_use]
814pub fn database_size_fields() -> Vec<Field> {
815    vec![
816        Field::new("database_name", LogicalType::Varchar),
817        Field::new("database_size", LogicalType::Varchar),
818        Field::new("block_size", LogicalType::BigInt),
819        Field::new("total_blocks", LogicalType::BigInt),
820        Field::new("used_blocks", LogicalType::BigInt),
821        Field::new("free_blocks", LogicalType::BigInt),
822        Field::new("wal_size", LogicalType::Varchar),
823        Field::new("memory_usage", LogicalType::Varchar),
824        Field::new("memory_limit", LogicalType::Varchar),
825    ]
826}
827
828/// The columns `rudb_strategies()` produces.
829///
830/// Named here rather than in the executor because the binder resolves the call and the executor
831/// fills it, and a table whose two halves disagree about its own columns is a bug that shows up as
832/// a wrong answer rather than as a compile error.
833///
834/// Nine columns and every one of them earns its place at a seam that has no implementations yet,
835/// which is twenty six of the twenty seven today. `seam`, `milestone` and `seam_description` say
836/// what the seam is and which milestone owes it its first two implementations, and they are filled
837/// whether or not anything is registered. The other six describe an implementation and are null
838/// when there is none, which is how the table says that a seam is planned rather than built without
839/// anybody having to read a design document to find out.
840#[must_use]
841pub fn strategy_fields() -> Vec<Field> {
842    vec![
843        Field::new("seam", LogicalType::Varchar),
844        Field::new("milestone", LogicalType::Varchar),
845        Field::new("seam_description", LogicalType::Varchar),
846        Field::new("implementation", LogicalType::Varchar),
847        Field::new("implementation_description", LogicalType::Varchar),
848        Field::new("provenance", LogicalType::Varchar),
849        Field::new("determinism", LogicalType::Varchar),
850        Field::new("is_reference", LogicalType::Boolean),
851        Field::new("is_default", LogicalType::Boolean),
852    ]
853}
854
855/// The columns `rudb_write_metrics()` produces.
856///
857/// One row per stage that ran in each of the loads the process has kept, and one `total` row per
858/// load, which is section 16.2 of `engine-v4/16-measurement.md`. `load` is the number the process
859/// gave the load, so the rows of one load group together and a later load has a larger number.
860/// Wall time in a stage row is summed over the workers that ran it, which is what makes it
861/// comparable with CPU time, and the `total` row's wall time is how long the statement took.
862///
863/// `waits` and `wait_ms` are the time a stage spent on something other than its own work. For
864/// write it is instances queued on the writer's lock, and for publish it is the syncs.
865#[must_use]
866pub fn write_metric_fields() -> Vec<Field> {
867    vec![
868        Field::new("load", LogicalType::BigInt),
869        Field::new("target", LogicalType::Varchar),
870        Field::new("stage", LogicalType::Varchar),
871        Field::new("wall_ms", LogicalType::Double),
872        Field::new("cpu_ms", LogicalType::Double),
873        Field::new("bytes_in", LogicalType::BigInt),
874        Field::new("bytes_out", LogicalType::BigInt),
875        Field::new("rows", LogicalType::BigInt),
876        Field::new("waits", LogicalType::BigInt),
877        Field::new("wait_ms", LogicalType::Double),
878        Field::new("finished", LogicalType::Boolean),
879    ]
880}
881
882/// The columns `rudb_links()` produces.
883///
884/// Section 2.6 of spec/graph/02-the-data-model.md asks this table for what was declared, what was
885/// verified, and what is physically there, and the three are separate columns because they are
886/// separate claims. `cardinality` is what the build observed and not what a declaration asserted:
887/// section 2.3 says a declared relationship whose parent side turns out not to be unique is
888/// reported `unverified` and gets no structure, so a reader who sees `unverified` here is being told
889/// why the join they expected to be fast is not.
890///
891/// `key_map_bytes` is filled whether or not the map was kept, which is the whole point of section
892/// 3.7's budget record: a relationship that did not fit is a number rather than a silence, so
893/// raising `graph_budget` is a decision somebody can make from what this says.
894///
895/// The link columns are what milestone G2 fills. They are here and null rather than absent for the
896/// reason `rudb_strategies()` lists a seam with no implementations: a structure that is planned and
897/// not built is a commitment, and a table that showed only what exists would make the layer look
898/// finished.
899///
900/// The five degree columns are section 7.2 and 7.4 of spec/stats/07-graph-statistics.md, and they
901/// are five rather than a histogram because a histogram in a cell is something nobody reads. They
902/// are the numbers a reader acts on: the mean says how far a traversal expands, the maximum and the
903/// ninety ninth percentile together say whether that expansion is even, and `gather_locality` says
904/// whether following the link touches cache or memory. `degree_p99` is a bucket's upper bound
905/// rather than an exact percentile, which is what a log bucketed histogram holds.
906///
907/// `parent_unique` and `child_total` are section 7.3's two certificates, which are what license
908/// join elimination, outer to inner and semi join removal. They are null rather than false when
909/// nothing was measured, because an unproven certificate and a disproven one lead a planner to the
910/// same place by different roads and only one of them is a fact about the data.
911#[must_use]
912pub fn link_fields() -> Vec<Field> {
913    vec![
914        Field::new("name", LogicalType::Varchar),
915        Field::new("child_table", LogicalType::Varchar),
916        Field::new("child_key", LogicalType::Varchar),
917        Field::new("parent_table", LogicalType::Varchar),
918        Field::new("parent_key", LogicalType::Varchar),
919        Field::new("cardinality", LogicalType::Varchar),
920        Field::new("key_map", LogicalType::Varchar),
921        Field::new("key_map_bytes", LogicalType::BigInt),
922        Field::new("link", LogicalType::Varchar),
923        Field::new("link_bytes", LogicalType::BigInt),
924        Field::new("degree_mean", LogicalType::Double),
925        Field::new("degree_max", LogicalType::BigInt),
926        Field::new("degree_p99", LogicalType::BigInt),
927        Field::new("gather_locality", LogicalType::Double),
928        Field::new("parent_unique", LogicalType::Boolean),
929        Field::new("child_total", LogicalType::Boolean),
930        Field::new("note", LogicalType::Varchar),
931    ]
932}
933
934/// The columns `duckdb_keywords()` produces, which is DuckDB's two.
935#[must_use]
936pub fn keyword_fields() -> Vec<Field> {
937    vec![
938        Field::new("keyword_name", LogicalType::Varchar),
939        Field::new("keyword_category", LogicalType::Varchar),
940    ]
941}
942
943/// The columns `duckdb_extensions()` produces, which is DuckDB's ten in its order.
944///
945/// `aliases` is the one list column in any of these tables. It is the other names an extension
946/// answers to, so `httpfs` carries `[http, https, s3]` and most of them carry an empty list, and an
947/// empty list is not a null: the pin returns `[]` on every row that has no alias.
948#[must_use]
949pub fn extension_fields() -> Vec<Field> {
950    vec![
951        Field::new("extension_name", LogicalType::Varchar),
952        Field::new("loaded", LogicalType::Boolean),
953        Field::new("installed", LogicalType::Boolean),
954        Field::new("install_path", LogicalType::Varchar),
955        Field::new("description", LogicalType::Varchar),
956        Field::new("aliases", LogicalType::list(LogicalType::Varchar)),
957        Field::new("extension_version", LogicalType::Varchar),
958        Field::new("install_mode", LogicalType::Varchar),
959        Field::new("installed_from", LogicalType::Varchar),
960        Field::new("signature_key_fingerprint", LogicalType::Varchar),
961    ]
962}
963
964/// The columns `duckdb_optimizers()` produces, which is DuckDB's one.
965#[must_use]
966pub fn optimizer_fields() -> Vec<Field> {
967    vec![Field::new("name", LogicalType::Varchar)]
968}
969
970/// The column `duckdb_dialects()` produces.
971#[must_use]
972pub fn dialect_fields() -> Vec<Field> {
973    vec![Field::new("dialect_name", LogicalType::Varchar)]
974}
975
976/// The columns `duckdb_grammar_extensions()` produces.
977#[must_use]
978pub fn grammar_extension_fields() -> Vec<Field> {
979    vec![Field::new("name", LogicalType::Varchar), Field::new("description", LogicalType::Varchar)]
980}
981
982/// The four categories DuckDB sorts a keyword into.
983///
984/// The vendored grammar does not carry these. It carries five keyword rules, `reserved_keyword`,
985/// `unreserved_keyword`, `column_name_keyword`, `func_name_keyword` and `type_name_keyword`, and
986/// `rudb_parse::KEYWORDS` is a mask over those five because they are not disjoint. DuckDB's table
987/// reports PostgreSQL's four categories instead, where `type_function` is the one category that the
988/// grammar spells as two rules, because a word usable as a type name is usable as a function name.
989///
990/// So a word can produce two rows, and six of them do: `columns`, `generated`, `map`, `struct`,
991/// `try_cast` and `tuple` are each in the column name class and in the type function class. That is
992/// why the pinned binary returns 505 rows over 499 distinct words, and a table that deduplicated
993/// them would be 499 rows and wrong.
994///
995/// A word whose mask is zero is in no class at all. The grammar spells fifteen words directly in
996/// some rule, `ascending` and `variant` among them, which makes them matchable as literals and
997/// keywords nowhere, and the pinned binary leaves all fifteen out of this table.
998#[must_use]
999pub fn keyword_categories(classes: u8) -> Vec<&'static str> {
1000    use rudb_parse::{COLUMN_NAME, FUNC_NAME, RESERVED, TYPE_NAME, UNRESERVED};
1001    let mut out = Vec::new();
1002    if classes & RESERVED != 0 {
1003        out.push("reserved");
1004    }
1005    if classes & UNRESERVED != 0 {
1006        out.push("unreserved");
1007    }
1008    if classes & COLUMN_NAME != 0 {
1009        out.push("column_name");
1010    }
1011    if classes & (FUNC_NAME | TYPE_NAME) != 0 {
1012        out.push("type_function");
1013    }
1014    out
1015}
1016
1017/// DuckDB's message for a call that matched a name and no overload of it.
1018///
1019/// The candidate list it prints carries fifteen named parameters that none of them accept here, so
1020/// what is listed is the two overloads that exist. The first line is the one a test in the wild
1021/// asserts on and it is reproduced exactly.
1022fn no_overload(function: TableFunction, arguments: &[LogicalType]) -> Error {
1023    let written: Vec<String> = arguments.iter().map(ToString::to_string).collect();
1024    let name = function.name();
1025    Error::binder(format!(
1026        "No function matches the given name and argument types '{name}({})'. You might need to \
1027         add explicit type casts.\n\tCandidate functions:\n\t{name}(VARCHAR)\n\t{name}(VARCHAR[])\n",
1028        written.join(", ")
1029    ))
1030}
1031
1032/// The same message for a pragma, which has one overload and prints its own name quoted.
1033///
1034/// The quoting is upstream's and is not a mistake being copied for its own sake. A pragma is
1035/// registered under a name the parser also spells as a statement, so the binary writes the
1036/// candidate through its identifier rule and gets `"pragma_table_info"(VARCHAR)` where
1037/// `read_parquet` gets no quotes. A client that matches on the line has to see the quotes.
1038fn one_name(function: TableFunction, arguments: &[LogicalType]) -> Error {
1039    let written: Vec<String> = arguments.iter().map(ToString::to_string).collect();
1040    let name = function.name();
1041    Error::binder(format!(
1042        "No function matches the given name and argument types '{name}({})'. You might need to \
1043         add explicit type casts.\n\tCandidate functions:\n\t\"{name}\"(VARCHAR)\n",
1044        written.join(", ")
1045    ))
1046}
1047
1048/// The same message again for a table function whose one overload takes nothing at all.
1049///
1050/// Every metadata table is one of these and upstream quotes all of their names, not only the ones
1051/// the parser also spells as a statement, so `"duckdb_extensions"()` reads the same way
1052/// `"pragma_version"()` does. Saying how many arguments were given instead would be a shorter
1053/// sentence and a worse one, because a client that reads the candidate line to find out what it may
1054/// call learns nothing from a count.
1055fn nothing_at_all(function: TableFunction, arguments: &[LogicalType]) -> Error {
1056    let written: Vec<String> = arguments.iter().map(ToString::to_string).collect();
1057    let name = function.name();
1058    Error::binder(format!(
1059        "No function matches the given name and argument types '{name}({})'. You might need to \
1060         add explicit type casts.\n\tCandidate functions:\n\t\"{name}\"()\n",
1061        written.join(", ")
1062    ))
1063}
1064
1065/// The values `start`, `stop` and `step` produce, in order.
1066///
1067/// Whole rather than an iterator because the caller wants them in a vector to build a vector out
1068/// of, and because the count is known up front, which is what keeps a three million row `range`
1069/// from growing a `Vec` twenty times on the way there.
1070///
1071/// A step of zero is an error and is the one case that is not simply an empty result. Everything
1072/// else that produces nothing produces nothing: a start past a stop with a positive step, a start
1073/// before a stop with a negative one, and the two of them equal under `range`.
1074///
1075/// # Errors
1076///
1077/// When the step is zero, with DuckDB's own wording.
1078pub fn series(function: TableFunction, start: i64, stop: i64, step: i64) -> Result<Vec<i64>> {
1079    let count = series_length(function, start, stop, step)?;
1080    let mut out = Vec::with_capacity(count);
1081    let mut at = start;
1082    for _ in 0..count {
1083        out.push(at);
1084        // The count was worked out from the same three numbers, so this cannot pass the stop, and
1085        // a saturating add is what keeps a step near the end of the range from wrapping into a
1086        // value on the wrong side of it rather than stopping.
1087        at = at.saturating_add(step);
1088    }
1089    Ok(out)
1090}
1091
1092/// How many values the series has, without producing any of them.
1093///
1094/// The executor wants this and not the values. `range(100000000)` is a hundred row chunks a
1095/// hundred thousand times over, and building the whole run first to find out how long it is would
1096/// be eight hundred megabytes for a query whose answer is one number.
1097///
1098/// This is also where the step is checked, so the check happens once rather than in each of the
1099/// two callers.
1100///
1101/// # Errors
1102///
1103/// When the step is zero, with DuckDB's own wording.
1104pub fn series_length(function: TableFunction, start: i64, stop: i64, step: i64) -> Result<usize> {
1105    if step == 0 {
1106        return Err(Error::binder("interval cannot be 0!"));
1107    }
1108    Ok(length(function, start, stop, step))
1109}
1110
1111/// How many values the series has.
1112///
1113/// In `i128` because `range(-9223372036854775808, 9223372036854775807)` is a legal call whose
1114/// length does not fit in an `i64`, and a length that overflows into a negative is a `Vec` capacity
1115/// that panics rather than a query that fails.
1116fn length(function: TableFunction, start: i64, stop: i64, step: i64) -> usize {
1117    let start = i128::from(start);
1118    let stop = i128::from(stop);
1119    let step = i128::from(step);
1120    let span = if function.inclusive() {
1121        if step > 0 { stop - start + 1 } else { stop - start - 1 }
1122    } else {
1123        stop - start
1124    };
1125    if (span > 0) != (step > 0) {
1126        return 0;
1127    }
1128    // Rounding away from zero, since a span of five over a step of two is three values and not two.
1129    let count = (span + step - step.signum()) / step;
1130    usize::try_from(count).unwrap_or(usize::MAX)
1131}
1132
1133#[cfg(test)]
1134mod tests {
1135    use super::*;
1136
1137    /// The fixed columns of a resolved call, which every function that does not read a file has.
1138    fn fixed(resolved: &ResolvedTable) -> &[Field] {
1139        match &resolved.columns {
1140            Columns::Fixed(fields) => fields,
1141            Columns::Parquet | Columns::Csv => {
1142                panic!("{} resolves to a file", resolved.function.name())
1143            }
1144        }
1145    }
1146
1147    /// A call of `count` integer arguments, which is what every series call looks like.
1148    fn integers(count: usize) -> Vec<LogicalType> {
1149        vec![LogicalType::BigInt; count]
1150    }
1151
1152    #[test]
1153    fn a_name_that_is_not_a_table_function_says_so_rather_than_binding() {
1154        let error = resolve_table("read_csv", &integers(1)).unwrap_err();
1155        assert!(error.to_string().contains("read_csv"), "{error}");
1156    }
1157
1158    #[test]
1159    fn both_names_resolve_and_each_one_names_its_own_column() {
1160        let range = resolve_table("range", &integers(1)).unwrap();
1161        assert_eq!(fixed(&range)[0].name, "range");
1162        let series = resolve_table("GENERATE_SERIES", &integers(3)).unwrap();
1163        assert_eq!(fixed(&series)[0].name, "generate_series");
1164        assert_eq!(series.arguments.len(), 3);
1165    }
1166
1167    #[test]
1168    fn no_arguments_and_four_arguments_are_both_the_arity_error() {
1169        assert!(resolve_table("range", &integers(0)).is_err());
1170        assert!(resolve_table("range", &integers(4)).is_err());
1171    }
1172
1173    #[test]
1174    fn a_series_call_ignores_the_types_it_was_given_and_casts_them_all_to_bigint() {
1175        let resolved =
1176            resolve_table("range", &[LogicalType::Varchar, LogicalType::Double]).unwrap();
1177        assert_eq!(resolved.arguments, integers(2));
1178    }
1179
1180    #[test]
1181    fn read_parquet_takes_one_string_and_says_its_columns_are_in_the_file() {
1182        let resolved = resolve_table("read_parquet", &[LogicalType::Varchar]).unwrap();
1183        assert_eq!(resolved.function, TableFunction::ReadParquet);
1184        assert_eq!(resolved.arguments, vec![LogicalType::Varchar]);
1185        assert_eq!(resolved.columns, Columns::Parquet);
1186    }
1187
1188    #[test]
1189    fn parquet_scan_is_the_same_function_under_duckdbs_other_name_for_it() {
1190        assert_eq!(TableFunction::lookup("parquet_scan"), Some(TableFunction::ReadParquet));
1191        // And it records itself under the one name, so a plan does not have two spellings in it.
1192        let resolved = resolve_table("parquet_scan", &[LogicalType::Varchar]).unwrap();
1193        assert_eq!(resolved.function.name(), "read_parquet");
1194    }
1195
1196    #[test]
1197    fn a_path_that_is_not_a_string_is_the_message_duckdb_gives_for_it() {
1198        // Measured against v1.4.1 on server3: `read_parquet(3)` does not cast, it fails to match.
1199        let error = resolve_table("read_parquet", &[LogicalType::Integer]).unwrap_err();
1200        assert!(
1201            error.message().starts_with(
1202                "No function matches the given name and argument types 'read_parquet(INTEGER)'."
1203            ),
1204            "{error}"
1205        );
1206        assert!(error.message().contains("read_parquet(VARCHAR)"), "{error}");
1207    }
1208
1209    #[test]
1210    fn read_parquet_of_no_arguments_or_two_is_the_same_no_overload_message() {
1211        let two = resolve_table("read_parquet", &[LogicalType::Varchar, LogicalType::Varchar]);
1212        assert!(two.unwrap_err().message().contains("read_parquet(VARCHAR, VARCHAR)"));
1213        let none = resolve_table("read_parquet", &[]);
1214        assert!(none.unwrap_err().message().contains("read_parquet()"));
1215    }
1216
1217    #[test]
1218    fn range_stops_before_the_end_and_generate_series_stops_on_it() {
1219        assert_eq!(series(TableFunction::Range, 0, 3, 1).unwrap(), vec![0, 1, 2]);
1220        assert_eq!(series(TableFunction::GenerateSeries, 0, 3, 1).unwrap(), vec![0, 1, 2, 3]);
1221    }
1222
1223    #[test]
1224    fn a_step_that_does_not_divide_the_span_stops_before_the_end_of_it() {
1225        // DuckDB gives 2, 4, 6 for both of these. The seven is not reached by either, which is
1226        // where the two functions stop being different.
1227        assert_eq!(series(TableFunction::Range, 2, 7, 2).unwrap(), vec![2, 4, 6]);
1228        assert_eq!(series(TableFunction::GenerateSeries, 2, 7, 2).unwrap(), vec![2, 4, 6]);
1229    }
1230
1231    #[test]
1232    fn the_four_categories_come_out_of_the_grammars_five_rules() {
1233        use rudb_parse::{COLUMN_NAME, FUNC_NAME, RESERVED, TYPE_NAME, UNRESERVED};
1234        assert_eq!(keyword_categories(RESERVED), ["reserved"]);
1235        assert_eq!(keyword_categories(UNRESERVED), ["unreserved"]);
1236        assert_eq!(keyword_categories(COLUMN_NAME), ["column_name"]);
1237        // The two rules that are one category. A word usable as a type name is usable as a function
1238        // name, which is why the grammar has two rules where PostgreSQL has one category, and either
1239        // rule on its own is still that one category rather than half of it.
1240        assert_eq!(keyword_categories(FUNC_NAME | TYPE_NAME), ["type_function"]);
1241        assert_eq!(keyword_categories(TYPE_NAME), ["type_function"]);
1242        assert_eq!(keyword_categories(FUNC_NAME), ["type_function"]);
1243        // Both, which is the case that makes one word two rows.
1244        assert_eq!(keyword_categories(COLUMN_NAME | FUNC_NAME), ["column_name", "type_function"]);
1245        // A word the grammar spells directly in a rule is in no class, and the pinned binary leaves
1246        // all fifteen of those out of the table rather than giving them a category of their own.
1247        assert!(keyword_categories(0).is_empty());
1248    }
1249
1250    #[test]
1251    fn a_metadata_table_given_an_argument_says_it_takes_none() {
1252        for name in [
1253            "rudb_strategies",
1254            "duckdb_keywords",
1255            "duckdb_types",
1256            "duckdb_functions",
1257            "duckdb_settings",
1258            "duckdb_databases",
1259            "duckdb_schemas",
1260            "duckdb_tables",
1261            "duckdb_columns",
1262        ] {
1263            let function = TableFunction::lookup(name).expect("a known function");
1264            let error = resolve_table(name, &[LogicalType::BigInt]).expect_err("takes none");
1265            assert!(error.to_string().contains(&format!("\"{}\"()", function.name())), "{error}");
1266            let resolved = resolve_table(name, &[]).expect("takes none, and none were given");
1267            assert_eq!(resolved.function, function);
1268            assert!(matches!(resolved.columns, Columns::Fixed(_)));
1269        }
1270    }
1271
1272    #[test]
1273    fn duckdb_keywords_has_duckdbs_two_columns_under_that_name() {
1274        let resolved = resolve_table("DuckDB_Keywords", &[]).expect("a case insensitive name");
1275        assert_eq!(resolved.function, TableFunction::DuckdbKeywords);
1276        let Columns::Fixed(fields) = resolved.columns else { panic!("fixed columns") };
1277        let names: Vec<&str> = fields.iter().map(|field| field.name.as_str()).collect();
1278        assert_eq!(names, ["keyword_name", "keyword_category"]);
1279        assert!(fields.iter().all(|field| field.ty == LogicalType::Varchar));
1280    }
1281
1282    #[test]
1283    fn duckdb_types_has_duckdbs_seventeen_columns_under_that_name() {
1284        let resolved = resolve_table("DuckDB_Types", &[]).expect("a case insensitive name");
1285        assert_eq!(resolved.function, TableFunction::DuckdbTypes);
1286        let Columns::Fixed(fields) = resolved.columns else { panic!("fixed columns") };
1287        let names: Vec<&str> = fields.iter().map(|field| field.name.as_str()).collect();
1288        assert_eq!(names.len(), 17);
1289        assert_eq!(names[0], "database_name");
1290        assert_eq!(names[16], "varargs");
1291        // The one column that is not a varchar, a bigint or a boolean, and the reason this table
1292        // waited on the map vector.
1293        let tags = fields.iter().find(|field| field.name == "tags").expect("a tags column");
1294        assert_eq!(tags.ty, LogicalType::map(LogicalType::Varchar, LogicalType::Varchar));
1295    }
1296
1297    #[test]
1298    fn duckdb_settings_has_duckdbs_seven_columns_under_that_name() {
1299        let resolved = resolve_table("DuckDB_Settings", &[]).expect("a case insensitive name");
1300        assert_eq!(resolved.function, TableFunction::DuckdbSettings);
1301        let Columns::Fixed(fields) = resolved.columns else { panic!("fixed columns") };
1302        let names: Vec<&str> = fields.iter().map(|field| field.name.as_str()).collect();
1303        assert_eq!(
1304            names,
1305            ["name", "value", "description", "input_type", "scope", "aliases", "typed_value"]
1306        );
1307        // The last one is a VARIANT in the pin and rudb has no such type, so it is text here.
1308        assert_eq!(fields[6].ty, LogicalType::Varchar);
1309    }
1310
1311    #[test]
1312    fn a_negative_step_counts_down_and_stops_on_the_same_rule() {
1313        assert_eq!(series(TableFunction::Range, 5, 1, -2).unwrap(), vec![5, 3]);
1314        assert_eq!(series(TableFunction::GenerateSeries, 5, 1, -2).unwrap(), vec![5, 3, 1]);
1315    }
1316
1317    #[test]
1318    fn a_step_going_the_wrong_way_produces_nothing_rather_than_running_forever() {
1319        assert!(series(TableFunction::Range, 0, 10, -1).unwrap().is_empty());
1320        assert!(series(TableFunction::Range, 10, 0, 1).unwrap().is_empty());
1321    }
1322
1323    #[test]
1324    fn an_empty_range_and_a_single_value_series_are_the_boundary_between_the_two() {
1325        assert!(series(TableFunction::Range, 4, 4, 1).unwrap().is_empty());
1326        assert_eq!(series(TableFunction::GenerateSeries, 4, 4, 1).unwrap(), vec![4]);
1327    }
1328
1329    #[test]
1330    fn a_step_of_zero_is_the_one_case_that_is_an_error_rather_than_nothing() {
1331        let error = series(TableFunction::Range, 1, 5, 0).unwrap_err();
1332        assert!(error.to_string().contains("interval cannot be 0"), "{error}");
1333    }
1334
1335    #[test]
1336    fn a_span_that_does_not_fit_in_an_i64_does_not_overflow_the_length() {
1337        // Not run, only counted. The point is that the count is worked out in i128, so this comes
1338        // out as a huge number rather than as a negative one that becomes a capacity panic.
1339        assert_eq!(length(TableFunction::Range, i64::MIN, i64::MAX, 1), usize::MAX);
1340    }
1341
1342    #[test]
1343    fn rudb_write_metrics_takes_no_arguments() {
1344        let resolved = resolve_table("rudb_write_metrics", &[]).unwrap();
1345        assert_eq!(resolved.function, TableFunction::RudbWriteMetrics);
1346        assert_eq!(resolved.columns, Columns::Fixed(write_metric_fields()));
1347        let error = resolve_table("rudb_write_metrics", &[LogicalType::BigInt]).unwrap_err();
1348        assert!(error.to_string().contains("\"rudb_write_metrics\"()"), "{error}");
1349    }
1350
1351    #[test]
1352    fn rudb_strategies_takes_no_arguments_and_produces_a_fixed_table() {
1353        let resolved = resolve_table("rudb_strategies", &[]).unwrap();
1354        assert_eq!(resolved.function, TableFunction::RudbStrategies);
1355        assert!(resolved.arguments.is_empty());
1356        assert_eq!(fixed(&resolved), strategy_fields());
1357    }
1358
1359    #[test]
1360    fn rudb_strategies_with_an_argument_says_it_takes_none() {
1361        let error = resolve_table("rudb_strategies", &[LogicalType::BigInt]).unwrap_err();
1362        assert!(error.to_string().contains("\"rudb_strategies\"()"), "{error}");
1363        assert!(error.to_string().contains("'rudb_strategies(BIGINT)'"), "{error}");
1364    }
1365
1366    #[test]
1367    fn the_two_pragmas_take_a_name_and_nothing_else_does() {
1368        assert!(TableFunction::PragmaTableInfo.takes_a_name());
1369        assert!(TableFunction::PragmaShow.takes_a_name());
1370        for other in [TableFunction::Range, TableFunction::DuckdbTables, TableFunction::ReadParquet]
1371        {
1372            assert!(!other.takes_a_name(), "{}", other.name());
1373        }
1374    }
1375
1376    #[test]
1377    fn pragma_table_info_answers_in_sqlites_six_columns() {
1378        let resolved = resolve_table("PRAGMA_Table_Info", &[LogicalType::Varchar])
1379            .expect("a case insensitive name");
1380        assert_eq!(resolved.function, TableFunction::PragmaTableInfo);
1381        assert_eq!(resolved.arguments, vec![LogicalType::Varchar]);
1382        let names: Vec<&str> = fixed(&resolved).iter().map(|field| field.name.as_str()).collect();
1383        assert_eq!(names, ["cid", "name", "type", "notnull", "dflt_value", "pk"]);
1384    }
1385
1386    #[test]
1387    fn pragma_show_answers_in_the_six_columns_describe_answers_in() {
1388        let resolved =
1389            resolve_table("pragma_show", &[LogicalType::Varchar]).expect("one name, one overload");
1390        assert_eq!(resolved.function, TableFunction::PragmaShow);
1391        let names: Vec<&str> = fixed(&resolved).iter().map(|field| field.name.as_str()).collect();
1392        assert_eq!(names, ["column_name", "column_type", "null", "key", "default", "extra"]);
1393        assert!(fixed(&resolved).iter().all(|field| field.ty == LogicalType::Varchar));
1394    }
1395
1396    #[test]
1397    fn a_null_name_resolves_because_the_catalog_is_what_turns_it_down() {
1398        let resolved = resolve_table("pragma_table_info", &[LogicalType::Null]).expect("a null");
1399        assert_eq!(resolved.arguments, vec![LogicalType::Null]);
1400    }
1401
1402    #[test]
1403    fn a_pragma_given_the_wrong_arguments_lists_its_one_overload() {
1404        for count in [0, 2] {
1405            let error = resolve_table("pragma_table_info", &integers(count)).expect_err("one name");
1406            assert!(
1407                error.message().starts_with(
1408                    "No function matches the given name and argument types 'pragma_table_info("
1409                ),
1410                "{error}"
1411            );
1412            assert!(error.message().contains("\"pragma_table_info\"(VARCHAR)"), "{error}");
1413        }
1414        // A single argument of the wrong type is the same message, because the pin does not cast
1415        // an integer to a name any more than it casts one to a path.
1416        let error = resolve_table("pragma_show", &[LogicalType::Integer]).expect_err("a name");
1417        assert!(error.message().contains("'pragma_show(INTEGER)'"), "{error}");
1418    }
1419
1420    /// The same call written as a statement gets the same complaint spelled the way it was written.
1421    #[test]
1422    fn a_pragma_written_as_a_statement_is_complained_about_as_one() {
1423        let error = resolve_pragma("pragma_table_info", &integers(2)).expect_err("one name");
1424        assert!(
1425            error.message().starts_with(
1426                "No function matches the given name and argument types 'table_info(BIGINT, \
1427                 BIGINT)'"
1428            ),
1429            "{error}"
1430        );
1431        assert!(error.message().contains("\tPRAGMA \"table_info\"(VARCHAR)\n"), "{error}");
1432        // A pragma that takes nothing prints no parentheses on the candidate line at all, which is
1433        // the pin's spelling and is not the same as the empty pair the function form prints.
1434        let error = resolve_pragma("pragma_version", &integers(1)).expect_err("nothing");
1435        assert!(error.message().contains("'version(BIGINT)'"), "{error}");
1436        assert!(error.message().ends_with("\tPRAGMA \"version\"\n"), "{error}");
1437    }
1438
1439    /// A call that resolves comes back the same either way, because it is the same function.
1440    #[test]
1441    fn a_pragma_that_resolves_resolves_to_what_the_function_spelling_does() {
1442        let name = [LogicalType::Varchar];
1443        let written = resolve_pragma("pragma_table_info", &name).expect("one name");
1444        let called = resolve_table("pragma_table_info", &name).expect("one name");
1445        assert_eq!(written.function, called.function);
1446        assert_eq!(written.arguments, called.arguments);
1447        let written = resolve_pragma("pragma_version", &[]).expect("nothing");
1448        assert_eq!(written.function, TableFunction::PragmaVersion);
1449    }
1450
1451    #[test]
1452    fn the_four_pragmas_about_the_build_take_nothing_and_name_their_own_columns() {
1453        let wanted: [(&str, TableFunction, &[&str]); 4] = [
1454            (
1455                "PRAGMA_Version",
1456                TableFunction::PragmaVersion,
1457                &["library_version", "source_id", "codename"],
1458            ),
1459            ("pragma_platform", TableFunction::PragmaPlatform, &["platform"]),
1460            ("pragma_user_agent", TableFunction::PragmaUserAgent, &["user_agent"]),
1461            (
1462                "pragma_database_size",
1463                TableFunction::PragmaDatabaseSize,
1464                &[
1465                    "database_name",
1466                    "database_size",
1467                    "block_size",
1468                    "total_blocks",
1469                    "used_blocks",
1470                    "free_blocks",
1471                    "wal_size",
1472                    "memory_usage",
1473                    "memory_limit",
1474                ],
1475            ),
1476        ];
1477        for (name, function, columns) in wanted {
1478            let resolved = resolve_table(name, &[]).expect("takes none, and none were given");
1479            assert_eq!(resolved.function, function);
1480            assert!(resolved.arguments.is_empty());
1481            assert!(!function.takes_a_name(), "{name}");
1482            let written: Vec<&str> =
1483                fixed(&resolved).iter().map(|field| field.name.as_str()).collect();
1484            assert_eq!(written, columns);
1485            let error = resolve_table(name, &[LogicalType::Varchar]).expect_err("takes none");
1486            assert!(error.to_string().contains(&format!("\"{}\"()", function.name())), "{error}");
1487        }
1488    }
1489
1490    #[test]
1491    fn the_four_block_columns_are_the_only_numbers_pragma_database_size_reports() {
1492        // The pin writes three of the nine as text a person reads rather than as a number, which is
1493        // worth a test because a client doing arithmetic on `database_size` gets a cast error on
1494        // both engines and that is the compatible answer rather than a bug in either.
1495        let fields = database_size_fields();
1496        let numbers: Vec<&str> = fields
1497            .iter()
1498            .filter(|field| field.ty == LogicalType::BigInt)
1499            .map(|field| field.name.as_str())
1500            .collect();
1501        assert_eq!(numbers, ["block_size", "total_blocks", "used_blocks", "free_blocks"]);
1502        assert!(fields.iter().filter(|field| field.ty == LogicalType::Varchar).count() == 5);
1503    }
1504
1505    #[test]
1506    fn the_device_card_takes_a_path_and_maybe_a_count() {
1507        let found = resolve_table("rudb_device_card", &[LogicalType::Varchar]).unwrap();
1508        assert_eq!(found.function, TableFunction::RudbDeviceCard);
1509        assert_eq!(found.columns, Columns::Fixed(device_card_fields()));
1510        let counted =
1511            resolve_table("RUDB_DEVICE_CARD", &[LogicalType::Varchar, LogicalType::Integer])
1512                .unwrap();
1513        assert_eq!(counted.arguments, [LogicalType::Varchar, LogicalType::BigInt]);
1514        for wrong in [
1515            &[][..],
1516            &[LogicalType::Integer][..],
1517            &[LogicalType::Varchar, LogicalType::Varchar][..],
1518            &[LogicalType::Varchar, LogicalType::BigInt, LogicalType::BigInt][..],
1519        ] {
1520            let message = resolve_table("rudb_device_card", wrong).unwrap_err().to_string();
1521            assert!(message.contains("rudb_device_card(VARCHAR, BIGINT)"), "{message}");
1522        }
1523    }
1524}