Skip to main content

datafusion_catalog/
information_schema.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! [`InformationSchemaProvider`] that implements the SQL [Information Schema] for DataFusion.
19//!
20//! [Information Schema]: https://en.wikipedia.org/wiki/Information_schema
21
22use crate::streaming::StreamingTable;
23use crate::table::TableFunction;
24use crate::{CatalogProviderList, SchemaProvider, TableProvider};
25use arrow::array::builder::{BooleanBuilder, UInt8Builder};
26use arrow::{
27    array::{StringBuilder, UInt64Builder},
28    datatypes::{DataType, Field, FieldRef, Schema, SchemaRef},
29    record_batch::RecordBatch,
30};
31use async_trait::async_trait;
32use datafusion_common::DataFusionError;
33use datafusion_common::config::{ConfigEntry, ConfigOptions};
34use datafusion_common::error::Result;
35use datafusion_common::types::NativeType;
36use datafusion_execution::TaskContext;
37use datafusion_execution::runtime_env::RuntimeEnv;
38use datafusion_expr::function::WindowUDFFieldArgs;
39use datafusion_expr::{
40    AggregateUDF, ReturnFieldArgs, ScalarUDF, Signature, TypeSignature, WindowUDF,
41};
42use datafusion_expr::{TableType, Volatility};
43use datafusion_physical_plan::SendableRecordBatchStream;
44use datafusion_physical_plan::stream::RecordBatchStreamAdapter;
45use datafusion_physical_plan::streaming::PartitionStream;
46use std::collections::{BTreeSet, HashMap, HashSet};
47use std::fmt::Debug;
48use std::sync::Arc;
49
50pub const INFORMATION_SCHEMA: &str = "information_schema";
51pub(crate) const TABLES: &str = "tables";
52pub(crate) const VIEWS: &str = "views";
53pub(crate) const COLUMNS: &str = "columns";
54pub(crate) const DF_SETTINGS: &str = "df_settings";
55pub(crate) const SCHEMATA: &str = "schemata";
56pub(crate) const ROUTINES: &str = "routines";
57pub(crate) const PARAMETERS: &str = "parameters";
58
59/// All information schema tables
60pub const INFORMATION_SCHEMA_TABLES: &[&str] = &[
61    TABLES,
62    VIEWS,
63    COLUMNS,
64    DF_SETTINGS,
65    SCHEMATA,
66    ROUTINES,
67    PARAMETERS,
68];
69
70/// Implements the `information_schema` virtual schema and tables
71///
72/// The underlying tables in the `information_schema` are created on
73/// demand. This means that if more tables are added to the underlying
74/// providers, they will appear the next time the `information_schema`
75/// table is queried.
76#[derive(Debug)]
77pub struct InformationSchemaProvider {
78    config: InformationSchemaConfig,
79}
80
81impl InformationSchemaProvider {
82    /// Creates a new [`InformationSchemaProvider`] for the provided `catalog_list`
83    pub fn new(catalog_list: Arc<dyn CatalogProviderList>) -> Self {
84        Self {
85            config: InformationSchemaConfig {
86                catalog_list,
87                table_functions: HashMap::new(),
88            },
89        }
90    }
91
92    /// Attach the session's table (UDTF) functions so that they appear in
93    /// `information_schema.routines` / `SHOW FUNCTIONS`.
94    pub fn with_table_functions(
95        mut self,
96        table_functions: HashMap<String, Arc<TableFunction>>,
97    ) -> Self {
98        self.config.table_functions = table_functions;
99        self
100    }
101}
102
103#[derive(Clone, Debug)]
104struct InformationSchemaConfig {
105    catalog_list: Arc<dyn CatalogProviderList>,
106    table_functions: HashMap<String, Arc<TableFunction>>,
107}
108
109impl InformationSchemaConfig {
110    /// Construct the `information_schema.tables` virtual table
111    async fn make_tables(
112        &self,
113        builder: &mut InformationSchemaTablesBuilder,
114    ) -> Result<(), DataFusionError> {
115        // create a mem table with the names of tables
116
117        for catalog_name in self.catalog_list.catalog_names() {
118            let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
119
120            for schema_name in catalog.schema_names() {
121                if schema_name != INFORMATION_SCHEMA {
122                    // schema name may not exist in the catalog, so we need to check
123                    if let Some(schema) = catalog.schema(&schema_name) {
124                        for table_name in schema.table_names() {
125                            if let Some(table_type) =
126                                schema.table_type(&table_name).await?
127                            {
128                                builder.add_table(
129                                    &catalog_name,
130                                    &schema_name,
131                                    &table_name,
132                                    table_type,
133                                );
134                            }
135                        }
136                    }
137                }
138            }
139
140            // Add a final list for the information schema tables themselves
141            for table_name in INFORMATION_SCHEMA_TABLES {
142                builder.add_table(
143                    &catalog_name,
144                    INFORMATION_SCHEMA,
145                    table_name,
146                    TableType::View,
147                );
148            }
149        }
150
151        Ok(())
152    }
153
154    fn make_schemata(&self, builder: &mut InformationSchemataBuilder) {
155        for catalog_name in self.catalog_list.catalog_names() {
156            let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
157
158            for schema_name in catalog.schema_names() {
159                if schema_name != INFORMATION_SCHEMA
160                    && let Some(schema) = catalog.schema(&schema_name)
161                {
162                    let schema_owner = schema.owner_name();
163                    builder.add_schemata(&catalog_name, &schema_name, schema_owner);
164                }
165            }
166        }
167    }
168
169    async fn make_views(
170        &self,
171        builder: &mut InformationSchemaViewBuilder,
172    ) -> Result<(), DataFusionError> {
173        for catalog_name in self.catalog_list.catalog_names() {
174            let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
175
176            for schema_name in catalog.schema_names() {
177                if schema_name != INFORMATION_SCHEMA {
178                    // schema name may not exist in the catalog, so we need to check
179                    if let Some(schema) = catalog.schema(&schema_name) {
180                        for table_name in schema.table_names() {
181                            if let Some(table) = schema.table(&table_name).await? {
182                                builder.add_view(
183                                    &catalog_name,
184                                    &schema_name,
185                                    &table_name,
186                                    table.get_table_definition(),
187                                )
188                            }
189                        }
190                    }
191                }
192            }
193        }
194
195        Ok(())
196    }
197
198    /// Construct the `information_schema.columns` virtual table
199    async fn make_columns(
200        &self,
201        builder: &mut InformationSchemaColumnsBuilder,
202    ) -> Result<(), DataFusionError> {
203        for catalog_name in self.catalog_list.catalog_names() {
204            let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
205
206            for schema_name in catalog.schema_names() {
207                if schema_name != INFORMATION_SCHEMA {
208                    // schema name may not exist in the catalog, so we need to check
209                    if let Some(schema) = catalog.schema(&schema_name) {
210                        for table_name in schema.table_names() {
211                            if let Some(table) = schema.table(&table_name).await? {
212                                for (field_position, field) in
213                                    table.schema().fields().iter().enumerate()
214                                {
215                                    builder.add_column(
216                                        &catalog_name,
217                                        &schema_name,
218                                        &table_name,
219                                        field_position,
220                                        field,
221                                    )
222                                }
223                            }
224                        }
225                    }
226                }
227            }
228        }
229
230        Ok(())
231    }
232
233    /// Construct the `information_schema.df_settings` virtual table
234    fn make_df_settings(
235        &self,
236        config_options: &ConfigOptions,
237        runtime_env: &Arc<RuntimeEnv>,
238        builder: &mut InformationSchemaDfSettingsBuilder,
239    ) {
240        for entry in config_options.entries() {
241            builder.add_setting(entry);
242        }
243        // Add runtime configuration entries
244        for entry in runtime_env.config_entries() {
245            builder.add_setting(entry);
246        }
247    }
248
249    fn make_routines(
250        &self,
251        udfs: &HashMap<String, Arc<ScalarUDF>>,
252        udafs: &HashMap<String, Arc<AggregateUDF>>,
253        udwfs: &HashMap<String, Arc<WindowUDF>>,
254        config_options: &ConfigOptions,
255        builder: &mut InformationSchemaRoutinesBuilder,
256    ) -> Result<()> {
257        let catalog_name = &config_options.catalog.default_catalog;
258        let schema_name = &config_options.catalog.default_schema;
259
260        for (name, udf) in udfs {
261            let return_types = get_udf_args_and_return_types(udf)?
262                .into_iter()
263                .map(|(_, return_type)| return_type)
264                .collect::<HashSet<_>>();
265            for return_type in return_types {
266                builder.add_routine(
267                    catalog_name,
268                    schema_name,
269                    name,
270                    "FUNCTION",
271                    Self::is_deterministic(udf.signature()),
272                    return_type.as_ref(),
273                    "SCALAR",
274                    udf.documentation().map(|d| d.description.to_string()),
275                    udf.documentation().map(|d| d.syntax_example.to_string()),
276                )
277            }
278        }
279
280        for (name, udaf) in udafs {
281            let return_types = get_udaf_args_and_return_types(udaf)?
282                .into_iter()
283                .map(|(_, return_type)| return_type)
284                .collect::<HashSet<_>>();
285            for return_type in return_types {
286                builder.add_routine(
287                    catalog_name,
288                    schema_name,
289                    name,
290                    "FUNCTION",
291                    Self::is_deterministic(udaf.signature()),
292                    return_type.as_ref(),
293                    "AGGREGATE",
294                    udaf.documentation().map(|d| d.description.to_string()),
295                    udaf.documentation().map(|d| d.syntax_example.to_string()),
296                )
297            }
298        }
299
300        for (name, udwf) in udwfs {
301            let return_types = get_udwf_args_and_return_types(udwf)?
302                .into_iter()
303                .map(|(_, return_type)| return_type)
304                .collect::<HashSet<_>>();
305            for return_type in return_types {
306                builder.add_routine(
307                    catalog_name,
308                    schema_name,
309                    name,
310                    "FUNCTION",
311                    Self::is_deterministic(udwf.signature()),
312                    return_type.as_ref(),
313                    "WINDOW",
314                    udwf.documentation().map(|d| d.description.to_string()),
315                    udwf.documentation().map(|d| d.syntax_example.to_string()),
316                )
317            }
318        }
319
320        // Table functions (UDTFs) don't have scalar signatures; their return
321        // type is always a table, so emit a single row per UDTF with
322        // routine_type = "FUNCTION", function_type = "TABLE" and
323        // data_type = "TABLE".
324        for name in self.table_functions.keys() {
325            builder.add_routine(
326                catalog_name,
327                schema_name,
328                name,
329                "FUNCTION",
330                // No signature is available for UDTFs; report deterministic
331                // = false to stay conservative.
332                false,
333                Some(&"TABLE"),
334                "TABLE",
335                None::<String>,
336                None::<String>,
337            )
338        }
339        Ok(())
340    }
341
342    fn is_deterministic(signature: &Signature) -> bool {
343        signature.volatility == Volatility::Immutable
344    }
345    fn make_parameters(
346        &self,
347        udfs: &HashMap<String, Arc<ScalarUDF>>,
348        udafs: &HashMap<String, Arc<AggregateUDF>>,
349        udwfs: &HashMap<String, Arc<WindowUDF>>,
350        config_options: &ConfigOptions,
351        builder: &mut InformationSchemaParametersBuilder,
352    ) -> Result<()> {
353        let catalog_name = &config_options.catalog.default_catalog;
354        let schema_name = &config_options.catalog.default_schema;
355        let mut add_parameters = |func_name: &str,
356                                  args: Option<&Vec<(String, String)>>,
357                                  arg_types: Vec<String>,
358                                  return_type: Option<String>,
359                                  is_variadic: bool,
360                                  rid: u8| {
361            for (position, type_name) in arg_types.iter().enumerate() {
362                let param_name =
363                    args.and_then(|a| a.get(position).map(|arg| arg.0.as_str()));
364                builder.add_parameter(
365                    catalog_name,
366                    schema_name,
367                    func_name,
368                    position as u64 + 1,
369                    "IN",
370                    param_name,
371                    type_name,
372                    None::<&str>,
373                    is_variadic,
374                    rid,
375                );
376            }
377            if let Some(return_type) = return_type {
378                builder.add_parameter(
379                    catalog_name,
380                    schema_name,
381                    func_name,
382                    1,
383                    "OUT",
384                    None::<&str>,
385                    return_type.as_str(),
386                    None::<&str>,
387                    false,
388                    rid,
389                );
390            }
391        };
392
393        for (func_name, udf) in udfs {
394            let args = udf.documentation().and_then(|d| d.arguments.clone());
395            let combinations = get_udf_args_and_return_types(udf)?;
396            for (rid, (arg_types, return_type)) in combinations.into_iter().enumerate() {
397                add_parameters(
398                    func_name,
399                    args.as_ref(),
400                    arg_types,
401                    return_type,
402                    Self::is_variadic(udf.signature()),
403                    rid as u8,
404                );
405            }
406        }
407
408        for (func_name, udaf) in udafs {
409            let args = udaf.documentation().and_then(|d| d.arguments.clone());
410            let combinations = get_udaf_args_and_return_types(udaf)?;
411            for (rid, (arg_types, return_type)) in combinations.into_iter().enumerate() {
412                add_parameters(
413                    func_name,
414                    args.as_ref(),
415                    arg_types,
416                    return_type,
417                    Self::is_variadic(udaf.signature()),
418                    rid as u8,
419                );
420            }
421        }
422
423        for (func_name, udwf) in udwfs {
424            let args = udwf.documentation().and_then(|d| d.arguments.clone());
425            let combinations = get_udwf_args_and_return_types(udwf)?;
426            for (rid, (arg_types, return_type)) in combinations.into_iter().enumerate() {
427                add_parameters(
428                    func_name,
429                    args.as_ref(),
430                    arg_types,
431                    return_type,
432                    Self::is_variadic(udwf.signature()),
433                    rid as u8,
434                );
435            }
436        }
437
438        // UDTFs deliberately do NOT appear in `information_schema.parameters`.
439        // A same-named scalar UDF (e.g. `generate_series` exists as both a
440        // scalar UDF in functions-nested and a UDTF in functions-table) would
441        // cross-join with a UDTF row keyed only by (name, rid) and produce
442        // spurious `TABLE`-typed variants of every scalar signature in
443        // SHOW FUNCTIONS. `show_functions_to_plan` sources UDTFs directly
444        // from `information_schema.routines` via a UNION branch instead.
445
446        Ok(())
447    }
448
449    fn is_variadic(signature: &Signature) -> bool {
450        matches!(
451            signature.type_signature,
452            TypeSignature::Variadic(_) | TypeSignature::VariadicAny
453        )
454    }
455}
456
457/// get the arguments and return types of a UDF
458/// returns a tuple of (arg_types, return_type)
459fn get_udf_args_and_return_types(
460    udf: &Arc<ScalarUDF>,
461) -> Result<BTreeSet<(Vec<String>, Option<String>)>> {
462    let signature = udf.signature();
463    let arg_types = signature.type_signature.get_example_types();
464    if arg_types.is_empty() {
465        Ok(vec![(vec![], None)].into_iter().collect::<BTreeSet<_>>())
466    } else {
467        Ok(arg_types
468            .into_iter()
469            .map(|arg_types| {
470                let arg_fields: Vec<FieldRef> = arg_types
471                    .iter()
472                    .enumerate()
473                    .map(|(i, t)| {
474                        Arc::new(Field::new(format!("arg_{i}"), t.clone(), true))
475                    })
476                    .collect();
477                let scalar_arguments = vec![None; arg_fields.len()];
478                let return_type = udf
479                    .return_field_from_args(ReturnFieldArgs {
480                        arg_fields: &arg_fields,
481                        scalar_arguments: &scalar_arguments,
482                    })
483                    .map(|f| {
484                        remove_native_type_prefix(&NativeType::from(
485                            f.data_type().clone(),
486                        ))
487                    })
488                    .ok();
489                let arg_types = arg_types
490                    .into_iter()
491                    .map(|t| remove_native_type_prefix(&NativeType::from(t)))
492                    .collect::<Vec<_>>();
493                (arg_types, return_type)
494            })
495            .collect::<BTreeSet<_>>())
496    }
497}
498
499fn get_udaf_args_and_return_types(
500    udaf: &Arc<AggregateUDF>,
501) -> Result<BTreeSet<(Vec<String>, Option<String>)>> {
502    let signature = udaf.signature();
503    let arg_types = signature.type_signature.get_example_types();
504    if arg_types.is_empty() {
505        Ok(vec![(vec![], None)].into_iter().collect::<BTreeSet<_>>())
506    } else {
507        Ok(arg_types
508            .into_iter()
509            .map(|arg_types| {
510                let arg_fields: Vec<FieldRef> = arg_types
511                    .iter()
512                    .enumerate()
513                    .map(|(i, t)| {
514                        Arc::new(Field::new(format!("arg_{i}"), t.clone(), true))
515                    })
516                    .collect();
517                let return_type = udaf
518                    .return_field(&arg_fields)
519                    .map(|f| {
520                        remove_native_type_prefix(&NativeType::from(
521                            f.data_type().clone(),
522                        ))
523                    })
524                    .ok();
525                let arg_types = arg_types
526                    .into_iter()
527                    .map(|t| remove_native_type_prefix(&NativeType::from(t)))
528                    .collect::<Vec<_>>();
529                (arg_types, return_type)
530            })
531            .collect::<BTreeSet<_>>())
532    }
533}
534
535fn get_udwf_args_and_return_types(
536    udwf: &Arc<WindowUDF>,
537) -> Result<BTreeSet<(Vec<String>, Option<String>)>> {
538    let signature = udwf.signature();
539    let arg_types = signature.type_signature.get_example_types();
540    if arg_types.is_empty() {
541        Ok(vec![(vec![], None)].into_iter().collect::<BTreeSet<_>>())
542    } else {
543        Ok(arg_types
544            .into_iter()
545            .map(|arg_types| {
546                let arg_fields: Vec<FieldRef> = arg_types
547                    .iter()
548                    .enumerate()
549                    .map(|(i, t)| {
550                        Arc::new(Field::new(format!("arg_{i}"), t.clone(), true))
551                    })
552                    .collect();
553                let return_type = udwf
554                    .field(WindowUDFFieldArgs::new(&arg_fields, udwf.name()))
555                    .map(|f| {
556                        remove_native_type_prefix(&NativeType::from(
557                            f.data_type().clone(),
558                        ))
559                    })
560                    .ok();
561                let arg_types = arg_types
562                    .into_iter()
563                    .map(|t| remove_native_type_prefix(&NativeType::from(t)))
564                    .collect::<Vec<_>>();
565                (arg_types, return_type)
566            })
567            .collect::<BTreeSet<_>>())
568    }
569}
570
571#[inline]
572fn remove_native_type_prefix(native_type: &NativeType) -> String {
573    format!("{native_type}")
574}
575
576#[async_trait]
577impl SchemaProvider for InformationSchemaProvider {
578    fn table_names(&self) -> Vec<String> {
579        INFORMATION_SCHEMA_TABLES
580            .iter()
581            .map(|t| (*t).to_string())
582            .collect()
583    }
584
585    async fn table(
586        &self,
587        name: &str,
588    ) -> Result<Option<Arc<dyn TableProvider>>, DataFusionError> {
589        let config = self.config.clone();
590        let table: Arc<dyn PartitionStream> = match name.to_ascii_lowercase().as_str() {
591            TABLES => Arc::new(InformationSchemaTables::new(config)),
592            COLUMNS => Arc::new(InformationSchemaColumns::new(config)),
593            VIEWS => Arc::new(InformationSchemaViews::new(config)),
594            DF_SETTINGS => Arc::new(InformationSchemaDfSettings::new(config)),
595            SCHEMATA => Arc::new(InformationSchemata::new(config)),
596            ROUTINES => Arc::new(InformationSchemaRoutines::new(config)),
597            PARAMETERS => Arc::new(InformationSchemaParameters::new(config)),
598            _ => return Ok(None),
599        };
600
601        Ok(Some(Arc::new(
602            StreamingTable::try_new(Arc::clone(table.schema()), vec![table]).unwrap(),
603        )))
604    }
605
606    fn table_exist(&self, name: &str) -> bool {
607        INFORMATION_SCHEMA_TABLES.contains(&name.to_ascii_lowercase().as_str())
608    }
609}
610
611#[derive(Debug)]
612struct InformationSchemaTables {
613    schema: SchemaRef,
614    config: InformationSchemaConfig,
615}
616
617impl InformationSchemaTables {
618    fn new(config: InformationSchemaConfig) -> Self {
619        let schema = Arc::new(Schema::new(vec![
620            Field::new("table_catalog", DataType::Utf8, false),
621            Field::new("table_schema", DataType::Utf8, false),
622            Field::new("table_name", DataType::Utf8, false),
623            Field::new("table_type", DataType::Utf8, false),
624        ]));
625
626        Self { schema, config }
627    }
628
629    fn builder(&self) -> InformationSchemaTablesBuilder {
630        InformationSchemaTablesBuilder {
631            catalog_names: StringBuilder::new(),
632            schema_names: StringBuilder::new(),
633            table_names: StringBuilder::new(),
634            table_types: StringBuilder::new(),
635            schema: Arc::clone(&self.schema),
636        }
637    }
638}
639
640impl PartitionStream for InformationSchemaTables {
641    fn schema(&self) -> &SchemaRef {
642        &self.schema
643    }
644
645    fn execute(&self, _ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
646        let mut builder = self.builder();
647        let config = self.config.clone();
648        Box::pin(RecordBatchStreamAdapter::new(
649            Arc::clone(&self.schema),
650            // TODO: Stream this
651            futures::stream::once(async move {
652                config.make_tables(&mut builder).await?;
653                Ok(builder.finish())
654            }),
655        ))
656    }
657}
658
659/// Builds the `information_schema.TABLE` table row by row
660///
661/// Columns are based on <https://www.postgresql.org/docs/current/infoschema-columns.html>
662struct InformationSchemaTablesBuilder {
663    schema: SchemaRef,
664    catalog_names: StringBuilder,
665    schema_names: StringBuilder,
666    table_names: StringBuilder,
667    table_types: StringBuilder,
668}
669
670impl InformationSchemaTablesBuilder {
671    fn add_table(
672        &mut self,
673        catalog_name: impl AsRef<str>,
674        schema_name: impl AsRef<str>,
675        table_name: impl AsRef<str>,
676        table_type: TableType,
677    ) {
678        // Note: append_value is actually infallible.
679        self.catalog_names.append_value(catalog_name.as_ref());
680        self.schema_names.append_value(schema_name.as_ref());
681        self.table_names.append_value(table_name.as_ref());
682        self.table_types.append_value(match table_type {
683            TableType::Base => "BASE TABLE",
684            TableType::View => "VIEW",
685            TableType::Temporary => "LOCAL TEMPORARY",
686        });
687    }
688
689    fn finish(&mut self) -> RecordBatch {
690        RecordBatch::try_new(
691            Arc::clone(&self.schema),
692            vec![
693                Arc::new(self.catalog_names.finish()),
694                Arc::new(self.schema_names.finish()),
695                Arc::new(self.table_names.finish()),
696                Arc::new(self.table_types.finish()),
697            ],
698        )
699        .unwrap()
700    }
701}
702
703#[derive(Debug)]
704struct InformationSchemaViews {
705    schema: SchemaRef,
706    config: InformationSchemaConfig,
707}
708
709impl InformationSchemaViews {
710    fn new(config: InformationSchemaConfig) -> Self {
711        let schema = Arc::new(Schema::new(vec![
712            Field::new("table_catalog", DataType::Utf8, false),
713            Field::new("table_schema", DataType::Utf8, false),
714            Field::new("table_name", DataType::Utf8, false),
715            Field::new("definition", DataType::Utf8, true),
716        ]));
717
718        Self { schema, config }
719    }
720
721    fn builder(&self) -> InformationSchemaViewBuilder {
722        InformationSchemaViewBuilder {
723            catalog_names: StringBuilder::new(),
724            schema_names: StringBuilder::new(),
725            table_names: StringBuilder::new(),
726            definitions: StringBuilder::new(),
727            schema: Arc::clone(&self.schema),
728        }
729    }
730}
731
732impl PartitionStream for InformationSchemaViews {
733    fn schema(&self) -> &SchemaRef {
734        &self.schema
735    }
736
737    fn execute(&self, _ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
738        let mut builder = self.builder();
739        let config = self.config.clone();
740        Box::pin(RecordBatchStreamAdapter::new(
741            Arc::clone(&self.schema),
742            // TODO: Stream this
743            futures::stream::once(async move {
744                config.make_views(&mut builder).await?;
745                Ok(builder.finish())
746            }),
747        ))
748    }
749}
750
751/// Builds the `information_schema.VIEWS` table row by row
752///
753/// Columns are based on <https://www.postgresql.org/docs/current/infoschema-columns.html>
754struct InformationSchemaViewBuilder {
755    schema: SchemaRef,
756    catalog_names: StringBuilder,
757    schema_names: StringBuilder,
758    table_names: StringBuilder,
759    definitions: StringBuilder,
760}
761
762impl InformationSchemaViewBuilder {
763    fn add_view(
764        &mut self,
765        catalog_name: impl AsRef<str>,
766        schema_name: impl AsRef<str>,
767        table_name: impl AsRef<str>,
768        definition: Option<&(impl AsRef<str> + ?Sized)>,
769    ) {
770        // Note: append_value is actually infallible.
771        self.catalog_names.append_value(catalog_name.as_ref());
772        self.schema_names.append_value(schema_name.as_ref());
773        self.table_names.append_value(table_name.as_ref());
774        self.definitions.append_option(definition.as_ref());
775    }
776
777    fn finish(&mut self) -> RecordBatch {
778        RecordBatch::try_new(
779            Arc::clone(&self.schema),
780            vec![
781                Arc::new(self.catalog_names.finish()),
782                Arc::new(self.schema_names.finish()),
783                Arc::new(self.table_names.finish()),
784                Arc::new(self.definitions.finish()),
785            ],
786        )
787        .unwrap()
788    }
789}
790
791#[derive(Debug)]
792struct InformationSchemaColumns {
793    schema: SchemaRef,
794    config: InformationSchemaConfig,
795}
796
797impl InformationSchemaColumns {
798    fn new(config: InformationSchemaConfig) -> Self {
799        let schema = Arc::new(Schema::new(vec![
800            Field::new("table_catalog", DataType::Utf8, false),
801            Field::new("table_schema", DataType::Utf8, false),
802            Field::new("table_name", DataType::Utf8, false),
803            Field::new("column_name", DataType::Utf8, false),
804            Field::new("ordinal_position", DataType::UInt64, false),
805            Field::new("column_default", DataType::Utf8, true),
806            Field::new("is_nullable", DataType::Utf8, false),
807            Field::new("data_type", DataType::Utf8, false),
808            Field::new("character_maximum_length", DataType::UInt64, true),
809            Field::new("character_octet_length", DataType::UInt64, true),
810            Field::new("numeric_precision", DataType::UInt64, true),
811            Field::new("numeric_precision_radix", DataType::UInt64, true),
812            Field::new("numeric_scale", DataType::UInt64, true),
813            Field::new("datetime_precision", DataType::UInt64, true),
814            Field::new("interval_type", DataType::Utf8, true),
815        ]));
816
817        Self { schema, config }
818    }
819
820    fn builder(&self) -> InformationSchemaColumnsBuilder {
821        // StringBuilder requires providing an initial capacity, so
822        // pick 10 here arbitrarily as this is not performance
823        // critical code and the number of tables is unavailable here.
824        let default_capacity = 10;
825
826        InformationSchemaColumnsBuilder {
827            catalog_names: StringBuilder::new(),
828            schema_names: StringBuilder::new(),
829            table_names: StringBuilder::new(),
830            column_names: StringBuilder::new(),
831            ordinal_positions: UInt64Builder::with_capacity(default_capacity),
832            column_defaults: StringBuilder::new(),
833            is_nullables: StringBuilder::new(),
834            data_types: StringBuilder::new(),
835            character_maximum_lengths: UInt64Builder::with_capacity(default_capacity),
836            character_octet_lengths: UInt64Builder::with_capacity(default_capacity),
837            numeric_precisions: UInt64Builder::with_capacity(default_capacity),
838            numeric_precision_radixes: UInt64Builder::with_capacity(default_capacity),
839            numeric_scales: UInt64Builder::with_capacity(default_capacity),
840            datetime_precisions: UInt64Builder::with_capacity(default_capacity),
841            interval_types: StringBuilder::new(),
842            schema: Arc::clone(&self.schema),
843        }
844    }
845}
846
847impl PartitionStream for InformationSchemaColumns {
848    fn schema(&self) -> &SchemaRef {
849        &self.schema
850    }
851
852    fn execute(&self, _ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
853        let mut builder = self.builder();
854        let config = self.config.clone();
855        Box::pin(RecordBatchStreamAdapter::new(
856            Arc::clone(&self.schema),
857            // TODO: Stream this
858            futures::stream::once(async move {
859                config.make_columns(&mut builder).await?;
860                Ok(builder.finish())
861            }),
862        ))
863    }
864}
865
866/// Builds the `information_schema.COLUMNS` table row by row
867///
868/// Columns are based on <https://www.postgresql.org/docs/current/infoschema-columns.html>
869struct InformationSchemaColumnsBuilder {
870    schema: SchemaRef,
871    catalog_names: StringBuilder,
872    schema_names: StringBuilder,
873    table_names: StringBuilder,
874    column_names: StringBuilder,
875    ordinal_positions: UInt64Builder,
876    column_defaults: StringBuilder,
877    is_nullables: StringBuilder,
878    data_types: StringBuilder,
879    character_maximum_lengths: UInt64Builder,
880    character_octet_lengths: UInt64Builder,
881    numeric_precisions: UInt64Builder,
882    numeric_precision_radixes: UInt64Builder,
883    numeric_scales: UInt64Builder,
884    datetime_precisions: UInt64Builder,
885    interval_types: StringBuilder,
886}
887
888impl InformationSchemaColumnsBuilder {
889    fn add_column(
890        &mut self,
891        catalog_name: &str,
892        schema_name: &str,
893        table_name: &str,
894        field_position: usize,
895        field: &Field,
896    ) {
897        use DataType::*;
898
899        // Note: append_value is actually infallible.
900        self.catalog_names.append_value(catalog_name);
901        self.schema_names.append_value(schema_name);
902        self.table_names.append_value(table_name);
903
904        self.column_names.append_value(field.name());
905
906        self.ordinal_positions.append_value(field_position as u64);
907
908        // DataFusion does not support column default values, so null
909        self.column_defaults.append_null();
910
911        // "YES if the column is possibly nullable, NO if it is known not nullable. "
912        let nullable_str = if field.is_nullable() { "YES" } else { "NO" };
913        self.is_nullables.append_value(nullable_str);
914
915        // "System supplied type" --> Use debug format of the datatype
916        self.data_types.append_value(field.data_type().to_string());
917
918        // "If data_type identifies a character or bit string type, the
919        // declared maximum length; null for all other data types or
920        // if no maximum length was declared."
921        //
922        // Arrow has no equivalent of VARCHAR(20), so we leave this as Null
923        let max_chars = None;
924        self.character_maximum_lengths.append_option(max_chars);
925
926        // "Maximum length, in bytes, for binary data, character data,
927        // or text and image data."
928        let char_len: Option<u64> = match field.data_type() {
929            Utf8 | Binary => Some(i32::MAX as u64),
930            LargeBinary | LargeUtf8 => Some(i64::MAX as u64),
931            _ => None,
932        };
933        self.character_octet_lengths.append_option(char_len);
934
935        // numeric_precision: "If data_type identifies a numeric type, this column
936        // contains the (declared or implicit) precision of the type
937        // for this column. The precision indicates the number of
938        // significant digits. It can be expressed in decimal (base
939        // 10) or binary (base 2) terms, as specified in the column
940        // numeric_precision_radix. For all other data types, this
941        // column is null."
942        //
943        // numeric_radix: If data_type identifies a numeric type, this
944        // column indicates in which base the values in the columns
945        // numeric_precision and numeric_scale are expressed. The
946        // value is either 2 or 10. For all other data types, this
947        // column is null.
948        //
949        // numeric_scale: If data_type identifies an exact numeric
950        // type, this column contains the (declared or implicit) scale
951        // of the type for this column. The scale indicates the number
952        // of significant digits to the right of the decimal point. It
953        // can be expressed in decimal (base 10) or binary (base 2)
954        // terms, as specified in the column
955        // numeric_precision_radix. For all other data types, this
956        // column is null.
957        let (numeric_precision, numeric_radix, numeric_scale) = match field.data_type() {
958            Int8 | UInt8 => (Some(8), Some(2), None),
959            Int16 | UInt16 => (Some(16), Some(2), None),
960            Int32 | UInt32 => (Some(32), Some(2), None),
961            // From max value of 65504 as explained on
962            // https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
963            Float16 => (Some(15), Some(2), None),
964            // Numbers from postgres `real` type
965            Float32 => (Some(24), Some(2), None),
966            // Numbers from postgres `double` type
967            Float64 => (Some(24), Some(2), None),
968            Decimal128(precision, scale) => {
969                (Some(*precision as u64), Some(10), Some(*scale as u64))
970            }
971            _ => (None, None, None),
972        };
973
974        self.numeric_precisions.append_option(numeric_precision);
975        self.numeric_precision_radixes.append_option(numeric_radix);
976        self.numeric_scales.append_option(numeric_scale);
977
978        self.datetime_precisions.append_option(None);
979        self.interval_types.append_null();
980    }
981
982    fn finish(&mut self) -> RecordBatch {
983        RecordBatch::try_new(
984            Arc::clone(&self.schema),
985            vec![
986                Arc::new(self.catalog_names.finish()),
987                Arc::new(self.schema_names.finish()),
988                Arc::new(self.table_names.finish()),
989                Arc::new(self.column_names.finish()),
990                Arc::new(self.ordinal_positions.finish()),
991                Arc::new(self.column_defaults.finish()),
992                Arc::new(self.is_nullables.finish()),
993                Arc::new(self.data_types.finish()),
994                Arc::new(self.character_maximum_lengths.finish()),
995                Arc::new(self.character_octet_lengths.finish()),
996                Arc::new(self.numeric_precisions.finish()),
997                Arc::new(self.numeric_precision_radixes.finish()),
998                Arc::new(self.numeric_scales.finish()),
999                Arc::new(self.datetime_precisions.finish()),
1000                Arc::new(self.interval_types.finish()),
1001            ],
1002        )
1003        .unwrap()
1004    }
1005}
1006
1007#[derive(Debug)]
1008struct InformationSchemata {
1009    schema: SchemaRef,
1010    config: InformationSchemaConfig,
1011}
1012
1013/// The Arrow schema of [`information_schema.schemata`] rows.
1014///
1015/// Useful for downstream catalog implementations that want to declare a
1016/// `TableProvider` for `schemata` before populating any rows via
1017/// [`InformationSchemataBuilder`].
1018///
1019/// Columns and nullability match
1020/// <https://www.postgresql.org/docs/current/infoschema-schemata.html>.
1021///
1022/// [`information_schema.schemata`]: https://www.postgresql.org/docs/current/infoschema-schemata.html
1023pub fn schemata_schema() -> SchemaRef {
1024    Arc::new(Schema::new(vec![
1025        Field::new("catalog_name", DataType::Utf8, false),
1026        Field::new("schema_name", DataType::Utf8, false),
1027        Field::new("schema_owner", DataType::Utf8, true),
1028        Field::new("default_character_set_catalog", DataType::Utf8, true),
1029        Field::new("default_character_set_schema", DataType::Utf8, true),
1030        Field::new("default_character_set_name", DataType::Utf8, true),
1031        Field::new("sql_path", DataType::Utf8, true),
1032    ]))
1033}
1034
1035impl InformationSchemata {
1036    fn new(config: InformationSchemaConfig) -> Self {
1037        Self {
1038            schema: schemata_schema(),
1039            config,
1040        }
1041    }
1042
1043    fn builder(&self) -> InformationSchemataBuilder {
1044        InformationSchemataBuilder {
1045            schema: Arc::clone(&self.schema),
1046            catalog_name: StringBuilder::new(),
1047            schema_name: StringBuilder::new(),
1048            schema_owner: StringBuilder::new(),
1049            default_character_set_catalog: StringBuilder::new(),
1050            default_character_set_schema: StringBuilder::new(),
1051            default_character_set_name: StringBuilder::new(),
1052            sql_path: StringBuilder::new(),
1053        }
1054    }
1055}
1056
1057/// Builder that produces [`RecordBatch`] values matching the schema of
1058/// `information_schema.schemata` (see [`schemata_schema`]).
1059///
1060/// Intended for downstream catalog implementations that need to emit
1061/// `schemata` rows from their own metadata source rather than going
1062/// through DataFusion's `InformationSchemaProvider`, which enumerates
1063/// schemas synchronously via `CatalogProviderList` and so is unsuitable
1064/// for catalog backends that resolve asynchronously.
1065#[derive(Debug)]
1066pub struct InformationSchemataBuilder {
1067    schema: SchemaRef,
1068    catalog_name: StringBuilder,
1069    schema_name: StringBuilder,
1070    schema_owner: StringBuilder,
1071    default_character_set_catalog: StringBuilder,
1072    default_character_set_schema: StringBuilder,
1073    default_character_set_name: StringBuilder,
1074    sql_path: StringBuilder,
1075}
1076
1077impl Default for InformationSchemataBuilder {
1078    fn default() -> Self {
1079        Self::new()
1080    }
1081}
1082
1083impl InformationSchemataBuilder {
1084    /// Construct an empty builder.
1085    pub fn new() -> Self {
1086        Self {
1087            schema: schemata_schema(),
1088            catalog_name: StringBuilder::new(),
1089            schema_name: StringBuilder::new(),
1090            schema_owner: StringBuilder::new(),
1091            default_character_set_catalog: StringBuilder::new(),
1092            default_character_set_schema: StringBuilder::new(),
1093            default_character_set_name: StringBuilder::new(),
1094            sql_path: StringBuilder::new(),
1095        }
1096    }
1097
1098    /// Append one row to the builder. `schema_owner` is the optional SQL
1099    /// schema owner; the three `default_character_set_*` columns and
1100    /// `sql_path` are written as null (DataFusion does not model those
1101    /// concepts; see the PostgreSQL docs link on [`schemata_schema`]).
1102    pub fn add_schemata(
1103        &mut self,
1104        catalog_name: &str,
1105        schema_name: &str,
1106        schema_owner: Option<&str>,
1107    ) {
1108        self.catalog_name.append_value(catalog_name);
1109        self.schema_name.append_value(schema_name);
1110        match schema_owner {
1111            Some(owner) => self.schema_owner.append_value(owner),
1112            None => self.schema_owner.append_null(),
1113        }
1114        self.default_character_set_catalog.append_null();
1115        self.default_character_set_schema.append_null();
1116        self.default_character_set_name.append_null();
1117        self.sql_path.append_null();
1118    }
1119
1120    /// Finalize the builder into a [`RecordBatch`].
1121    ///
1122    /// Returns an error only if Arrow buffer construction fails, which
1123    /// the builder's column-count and type invariants make unreachable
1124    /// under normal use. The `Result` return type preserves room to add
1125    /// validation in the future without a breaking API change.
1126    pub fn finish(&mut self) -> Result<RecordBatch> {
1127        RecordBatch::try_new(
1128            Arc::clone(&self.schema),
1129            vec![
1130                Arc::new(self.catalog_name.finish()),
1131                Arc::new(self.schema_name.finish()),
1132                Arc::new(self.schema_owner.finish()),
1133                Arc::new(self.default_character_set_catalog.finish()),
1134                Arc::new(self.default_character_set_schema.finish()),
1135                Arc::new(self.default_character_set_name.finish()),
1136                Arc::new(self.sql_path.finish()),
1137            ],
1138        )
1139        .map_err(DataFusionError::from)
1140    }
1141}
1142
1143impl PartitionStream for InformationSchemata {
1144    fn schema(&self) -> &SchemaRef {
1145        &self.schema
1146    }
1147
1148    fn execute(&self, _ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
1149        let mut builder = self.builder();
1150        let config = self.config.clone();
1151        Box::pin(RecordBatchStreamAdapter::new(
1152            Arc::clone(&self.schema),
1153            // TODO: Stream this
1154            futures::stream::once(async move {
1155                config.make_schemata(&mut builder);
1156                builder.finish()
1157            }),
1158        ))
1159    }
1160}
1161
1162#[derive(Debug)]
1163struct InformationSchemaDfSettings {
1164    schema: SchemaRef,
1165    config: InformationSchemaConfig,
1166}
1167
1168impl InformationSchemaDfSettings {
1169    fn new(config: InformationSchemaConfig) -> Self {
1170        let schema = Arc::new(Schema::new(vec![
1171            Field::new("name", DataType::Utf8, false),
1172            Field::new("value", DataType::Utf8, true),
1173            Field::new("description", DataType::Utf8, true),
1174        ]));
1175
1176        Self { schema, config }
1177    }
1178
1179    fn builder(&self) -> InformationSchemaDfSettingsBuilder {
1180        InformationSchemaDfSettingsBuilder {
1181            names: StringBuilder::new(),
1182            values: StringBuilder::new(),
1183            descriptions: StringBuilder::new(),
1184            schema: Arc::clone(&self.schema),
1185        }
1186    }
1187}
1188
1189impl PartitionStream for InformationSchemaDfSettings {
1190    fn schema(&self) -> &SchemaRef {
1191        &self.schema
1192    }
1193
1194    fn execute(&self, ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
1195        let config = self.config.clone();
1196        let mut builder = self.builder();
1197        Box::pin(RecordBatchStreamAdapter::new(
1198            Arc::clone(&self.schema),
1199            // TODO: Stream this
1200            futures::stream::once(async move {
1201                // create a mem table with the names of tables
1202                let runtime_env = ctx.runtime_env();
1203                config.make_df_settings(
1204                    ctx.session_config().options(),
1205                    &runtime_env,
1206                    &mut builder,
1207                );
1208                Ok(builder.finish())
1209            }),
1210        ))
1211    }
1212}
1213
1214struct InformationSchemaDfSettingsBuilder {
1215    schema: SchemaRef,
1216    names: StringBuilder,
1217    values: StringBuilder,
1218    descriptions: StringBuilder,
1219}
1220
1221impl InformationSchemaDfSettingsBuilder {
1222    fn add_setting(&mut self, entry: ConfigEntry) {
1223        self.names.append_value(entry.key);
1224        self.values.append_option(entry.value);
1225        self.descriptions.append_value(entry.description);
1226    }
1227
1228    fn finish(&mut self) -> RecordBatch {
1229        RecordBatch::try_new(
1230            Arc::clone(&self.schema),
1231            vec![
1232                Arc::new(self.names.finish()),
1233                Arc::new(self.values.finish()),
1234                Arc::new(self.descriptions.finish()),
1235            ],
1236        )
1237        .unwrap()
1238    }
1239}
1240
1241#[derive(Debug)]
1242struct InformationSchemaRoutines {
1243    schema: SchemaRef,
1244    config: InformationSchemaConfig,
1245}
1246
1247impl InformationSchemaRoutines {
1248    fn new(config: InformationSchemaConfig) -> Self {
1249        let schema = Arc::new(Schema::new(vec![
1250            Field::new("specific_catalog", DataType::Utf8, false),
1251            Field::new("specific_schema", DataType::Utf8, false),
1252            Field::new("specific_name", DataType::Utf8, false),
1253            Field::new("routine_catalog", DataType::Utf8, false),
1254            Field::new("routine_schema", DataType::Utf8, false),
1255            Field::new("routine_name", DataType::Utf8, false),
1256            Field::new("routine_type", DataType::Utf8, false),
1257            Field::new("is_deterministic", DataType::Boolean, true),
1258            Field::new("data_type", DataType::Utf8, true),
1259            Field::new("function_type", DataType::Utf8, true),
1260            Field::new("description", DataType::Utf8, true),
1261            Field::new("syntax_example", DataType::Utf8, true),
1262        ]));
1263
1264        Self { schema, config }
1265    }
1266
1267    fn builder(&self) -> InformationSchemaRoutinesBuilder {
1268        InformationSchemaRoutinesBuilder {
1269            schema: Arc::clone(&self.schema),
1270            specific_catalog: StringBuilder::new(),
1271            specific_schema: StringBuilder::new(),
1272            specific_name: StringBuilder::new(),
1273            routine_catalog: StringBuilder::new(),
1274            routine_schema: StringBuilder::new(),
1275            routine_name: StringBuilder::new(),
1276            routine_type: StringBuilder::new(),
1277            is_deterministic: BooleanBuilder::new(),
1278            data_type: StringBuilder::new(),
1279            function_type: StringBuilder::new(),
1280            description: StringBuilder::new(),
1281            syntax_example: StringBuilder::new(),
1282        }
1283    }
1284}
1285
1286struct InformationSchemaRoutinesBuilder {
1287    schema: SchemaRef,
1288    specific_catalog: StringBuilder,
1289    specific_schema: StringBuilder,
1290    specific_name: StringBuilder,
1291    routine_catalog: StringBuilder,
1292    routine_schema: StringBuilder,
1293    routine_name: StringBuilder,
1294    routine_type: StringBuilder,
1295    is_deterministic: BooleanBuilder,
1296    data_type: StringBuilder,
1297    function_type: StringBuilder,
1298    description: StringBuilder,
1299    syntax_example: StringBuilder,
1300}
1301
1302impl InformationSchemaRoutinesBuilder {
1303    #[expect(clippy::too_many_arguments)]
1304    fn add_routine(
1305        &mut self,
1306        catalog_name: impl AsRef<str>,
1307        schema_name: impl AsRef<str>,
1308        routine_name: impl AsRef<str>,
1309        routine_type: impl AsRef<str>,
1310        is_deterministic: bool,
1311        data_type: Option<&impl AsRef<str>>,
1312        function_type: impl AsRef<str>,
1313        description: Option<impl AsRef<str>>,
1314        syntax_example: Option<impl AsRef<str>>,
1315    ) {
1316        self.specific_catalog.append_value(catalog_name.as_ref());
1317        self.specific_schema.append_value(schema_name.as_ref());
1318        self.specific_name.append_value(routine_name.as_ref());
1319        self.routine_catalog.append_value(catalog_name.as_ref());
1320        self.routine_schema.append_value(schema_name.as_ref());
1321        self.routine_name.append_value(routine_name.as_ref());
1322        self.routine_type.append_value(routine_type.as_ref());
1323        self.is_deterministic.append_value(is_deterministic);
1324        self.data_type.append_option(data_type.as_ref());
1325        self.function_type.append_value(function_type.as_ref());
1326        self.description.append_option(description);
1327        self.syntax_example.append_option(syntax_example);
1328    }
1329
1330    fn finish(&mut self) -> RecordBatch {
1331        RecordBatch::try_new(
1332            Arc::clone(&self.schema),
1333            vec![
1334                Arc::new(self.specific_catalog.finish()),
1335                Arc::new(self.specific_schema.finish()),
1336                Arc::new(self.specific_name.finish()),
1337                Arc::new(self.routine_catalog.finish()),
1338                Arc::new(self.routine_schema.finish()),
1339                Arc::new(self.routine_name.finish()),
1340                Arc::new(self.routine_type.finish()),
1341                Arc::new(self.is_deterministic.finish()),
1342                Arc::new(self.data_type.finish()),
1343                Arc::new(self.function_type.finish()),
1344                Arc::new(self.description.finish()),
1345                Arc::new(self.syntax_example.finish()),
1346            ],
1347        )
1348        .unwrap()
1349    }
1350}
1351
1352impl PartitionStream for InformationSchemaRoutines {
1353    fn schema(&self) -> &SchemaRef {
1354        &self.schema
1355    }
1356
1357    fn execute(&self, ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
1358        let config = self.config.clone();
1359        let mut builder = self.builder();
1360        Box::pin(RecordBatchStreamAdapter::new(
1361            Arc::clone(&self.schema),
1362            futures::stream::once(async move {
1363                config.make_routines(
1364                    ctx.scalar_functions(),
1365                    ctx.aggregate_functions(),
1366                    ctx.window_functions(),
1367                    ctx.session_config().options(),
1368                    &mut builder,
1369                )?;
1370                Ok(builder.finish())
1371            }),
1372        ))
1373    }
1374}
1375
1376#[derive(Debug)]
1377struct InformationSchemaParameters {
1378    schema: SchemaRef,
1379    config: InformationSchemaConfig,
1380}
1381
1382impl InformationSchemaParameters {
1383    fn new(config: InformationSchemaConfig) -> Self {
1384        let schema = Arc::new(Schema::new(vec![
1385            Field::new("specific_catalog", DataType::Utf8, false),
1386            Field::new("specific_schema", DataType::Utf8, false),
1387            Field::new("specific_name", DataType::Utf8, false),
1388            Field::new("ordinal_position", DataType::UInt64, false),
1389            Field::new("parameter_mode", DataType::Utf8, false),
1390            Field::new("parameter_name", DataType::Utf8, true),
1391            Field::new("data_type", DataType::Utf8, false),
1392            Field::new("parameter_default", DataType::Utf8, true),
1393            Field::new("is_variadic", DataType::Boolean, false),
1394            // `rid` (short for `routine id`) is used to differentiate parameters from different signatures
1395            // (It serves as the group-by key when generating the `SHOW FUNCTIONS` query).
1396            // For example, the following signatures have different `rid` values:
1397            //     - `datetrunc(Utf8, Timestamp(Microsecond, Some("+TZ"))) -> Timestamp(Microsecond, Some("+TZ"))`
1398            //     - `datetrunc(Utf8View, Timestamp(Nanosecond, None)) -> Timestamp(Nanosecond, None)`
1399            Field::new("rid", DataType::UInt8, false),
1400        ]));
1401
1402        Self { schema, config }
1403    }
1404
1405    fn builder(&self) -> InformationSchemaParametersBuilder {
1406        InformationSchemaParametersBuilder {
1407            schema: Arc::clone(&self.schema),
1408            specific_catalog: StringBuilder::new(),
1409            specific_schema: StringBuilder::new(),
1410            specific_name: StringBuilder::new(),
1411            ordinal_position: UInt64Builder::new(),
1412            parameter_mode: StringBuilder::new(),
1413            parameter_name: StringBuilder::new(),
1414            data_type: StringBuilder::new(),
1415            parameter_default: StringBuilder::new(),
1416            is_variadic: BooleanBuilder::new(),
1417            rid: UInt8Builder::new(),
1418        }
1419    }
1420}
1421
1422struct InformationSchemaParametersBuilder {
1423    schema: SchemaRef,
1424    specific_catalog: StringBuilder,
1425    specific_schema: StringBuilder,
1426    specific_name: StringBuilder,
1427    ordinal_position: UInt64Builder,
1428    parameter_mode: StringBuilder,
1429    parameter_name: StringBuilder,
1430    data_type: StringBuilder,
1431    parameter_default: StringBuilder,
1432    is_variadic: BooleanBuilder,
1433    rid: UInt8Builder,
1434}
1435
1436impl InformationSchemaParametersBuilder {
1437    #[expect(clippy::too_many_arguments)]
1438    fn add_parameter(
1439        &mut self,
1440        specific_catalog: impl AsRef<str>,
1441        specific_schema: impl AsRef<str>,
1442        specific_name: impl AsRef<str>,
1443        ordinal_position: u64,
1444        parameter_mode: impl AsRef<str>,
1445        parameter_name: Option<&(impl AsRef<str> + ?Sized)>,
1446        data_type: impl AsRef<str>,
1447        parameter_default: Option<impl AsRef<str>>,
1448        is_variadic: bool,
1449        rid: u8,
1450    ) {
1451        self.specific_catalog
1452            .append_value(specific_catalog.as_ref());
1453        self.specific_schema.append_value(specific_schema.as_ref());
1454        self.specific_name.append_value(specific_name.as_ref());
1455        self.ordinal_position.append_value(ordinal_position);
1456        self.parameter_mode.append_value(parameter_mode.as_ref());
1457        self.parameter_name.append_option(parameter_name.as_ref());
1458        self.data_type.append_value(data_type.as_ref());
1459        self.parameter_default.append_option(parameter_default);
1460        self.is_variadic.append_value(is_variadic);
1461        self.rid.append_value(rid);
1462    }
1463
1464    fn finish(&mut self) -> RecordBatch {
1465        RecordBatch::try_new(
1466            Arc::clone(&self.schema),
1467            vec![
1468                Arc::new(self.specific_catalog.finish()),
1469                Arc::new(self.specific_schema.finish()),
1470                Arc::new(self.specific_name.finish()),
1471                Arc::new(self.ordinal_position.finish()),
1472                Arc::new(self.parameter_mode.finish()),
1473                Arc::new(self.parameter_name.finish()),
1474                Arc::new(self.data_type.finish()),
1475                Arc::new(self.parameter_default.finish()),
1476                Arc::new(self.is_variadic.finish()),
1477                Arc::new(self.rid.finish()),
1478            ],
1479        )
1480        .unwrap()
1481    }
1482}
1483
1484impl PartitionStream for InformationSchemaParameters {
1485    fn schema(&self) -> &SchemaRef {
1486        &self.schema
1487    }
1488
1489    fn execute(&self, ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
1490        let config = self.config.clone();
1491        let mut builder = self.builder();
1492        Box::pin(RecordBatchStreamAdapter::new(
1493            Arc::clone(&self.schema),
1494            futures::stream::once(async move {
1495                config.make_parameters(
1496                    ctx.scalar_functions(),
1497                    ctx.aggregate_functions(),
1498                    ctx.window_functions(),
1499                    ctx.session_config().options(),
1500                    &mut builder,
1501                )?;
1502                Ok(builder.finish())
1503            }),
1504        ))
1505    }
1506}
1507
1508#[cfg(test)]
1509mod tests {
1510    use super::*;
1511    use crate::CatalogProvider;
1512    use arrow::array::Array;
1513
1514    #[test]
1515    fn schemata_builder_emits_canonical_schema_and_rows() {
1516        // Construct via `Default` so the test exercises both `new()` (via
1517        // the `Default` impl) and the public column-layout contract.
1518        let mut builder = InformationSchemataBuilder::default();
1519        builder.add_schemata("cat", "schema_one", Some("alice"));
1520        builder.add_schemata("cat", "schema_two", None);
1521        let batch = builder.finish().expect("finish should not fail");
1522
1523        assert_eq!(batch.schema(), schemata_schema());
1524        assert_eq!(batch.num_rows(), 2);
1525
1526        let col = |name: &str| {
1527            batch
1528                .column_by_name(name)
1529                .unwrap_or_else(|| panic!("missing column {name}"))
1530        };
1531        let string_col = |name: &str| {
1532            col(name)
1533                .as_any()
1534                .downcast_ref::<arrow::array::StringArray>()
1535                .unwrap_or_else(|| panic!("{name} should be a StringArray"))
1536        };
1537
1538        let catalog = string_col("catalog_name");
1539        assert_eq!(catalog.value(0), "cat");
1540        assert_eq!(catalog.value(1), "cat");
1541
1542        let schema = string_col("schema_name");
1543        assert_eq!(schema.value(0), "schema_one");
1544        assert_eq!(schema.value(1), "schema_two");
1545
1546        let owner = string_col("schema_owner");
1547        assert_eq!(owner.value(0), "alice");
1548        assert!(owner.is_null(1));
1549
1550        // The three character-set columns and sql_path are unconditionally
1551        // null — they exist for SQL-standard column-layout compatibility.
1552        for name in [
1553            "default_character_set_catalog",
1554            "default_character_set_schema",
1555            "default_character_set_name",
1556            "sql_path",
1557        ] {
1558            let c = string_col(name);
1559            assert!(c.is_null(0), "{name} row 0 should be null");
1560            assert!(c.is_null(1), "{name} row 1 should be null");
1561        }
1562    }
1563
1564    #[tokio::test]
1565    async fn make_tables_uses_table_type() {
1566        let config = InformationSchemaConfig {
1567            catalog_list: Arc::new(Fixture),
1568            table_functions: HashMap::new(),
1569        };
1570        let mut builder = InformationSchemaTablesBuilder {
1571            catalog_names: StringBuilder::new(),
1572            schema_names: StringBuilder::new(),
1573            table_names: StringBuilder::new(),
1574            table_types: StringBuilder::new(),
1575            schema: Arc::new(Schema::empty()),
1576        };
1577
1578        assert!(config.make_tables(&mut builder).await.is_ok());
1579
1580        assert_eq!("BASE TABLE", builder.table_types.finish().value(0));
1581    }
1582
1583    #[derive(Debug)]
1584    struct Fixture;
1585
1586    #[async_trait]
1587    impl SchemaProvider for Fixture {
1588        // InformationSchemaConfig::make_tables should use this.
1589        async fn table_type(&self, _: &str) -> Result<Option<TableType>> {
1590            Ok(Some(TableType::Base))
1591        }
1592
1593        // InformationSchemaConfig::make_tables used this before `table_type`
1594        // existed but should not, as it may be expensive.
1595        async fn table(&self, _: &str) -> Result<Option<Arc<dyn TableProvider>>> {
1596            panic!(
1597                "InformationSchemaConfig::make_tables called SchemaProvider::table instead of table_type"
1598            )
1599        }
1600
1601        fn table_names(&self) -> Vec<String> {
1602            vec!["atable".to_string()]
1603        }
1604
1605        fn table_exist(&self, _: &str) -> bool {
1606            unimplemented!("not required for these tests")
1607        }
1608    }
1609
1610    impl CatalogProviderList for Fixture {
1611        fn register_catalog(
1612            &self,
1613            _: String,
1614            _: Arc<dyn CatalogProvider>,
1615        ) -> Option<Arc<dyn CatalogProvider>> {
1616            unimplemented!("not required for these tests")
1617        }
1618
1619        fn catalog_names(&self) -> Vec<String> {
1620            vec!["acatalog".to_string()]
1621        }
1622
1623        fn catalog(&self, _: &str) -> Option<Arc<dyn CatalogProvider>> {
1624            Some(Arc::new(Self))
1625        }
1626    }
1627
1628    impl CatalogProvider for Fixture {
1629        fn schema_names(&self) -> Vec<String> {
1630            vec!["aschema".to_string()]
1631        }
1632
1633        fn schema(&self, _: &str) -> Option<Arc<dyn SchemaProvider>> {
1634            Some(Arc::new(Self))
1635        }
1636    }
1637}