use rudb_catalog::{Catalog, Database, Schema, Table};
use rudb_common::{LogicalType, Result, Value};
use rudb_functions::{
DUCKDB, canonical, column_fields, database_fields, numeric_facts, schema_fields,
show_database_fields, show_expanded_fields, show_table_fields, table_fields, type_oid,
view_fields,
};
use rudb_parse::quoted;
use rudb_plan::{Plan, Slice};
use crate::metadata::{Metadata, text};
pub(crate) fn databasenames(
catalog: &Catalog,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let mut rows = Vec::with_capacity(catalog.databases().len());
for database in catalog.databases() {
rows.push(vec![
text(database.name()),
Value::BigInt(database.oid()),
Value::Null,
Value::Null,
empty(),
Value::Boolean(database.internal()),
text(DUCKDB),
Value::Boolean(false),
Value::Boolean(false),
Value::Null,
empty(),
]);
}
Metadata::new("duckdb_databases", &database_fields(), &rows, plan, index, columns)
}
pub(crate) fn schemanames(
catalog: &Catalog,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let mut rows = Vec::new();
for database in catalog.databases() {
for schema in database.schemas() {
rows.push(vec![
Value::BigInt(schema.oid()),
text(database.name()),
Value::BigInt(database.oid()),
text(schema.name()),
Value::Null,
empty(),
Value::Boolean(true),
Value::Null,
Value::Null,
Value::Null,
]);
}
}
Metadata::new("duckdb_schemas", &schema_fields(), &rows, plan, index, columns)
}
pub(crate) fn tablenames(
catalog: &Catalog,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let mut rows = Vec::new();
for (database, schema, table) in entries(catalog) {
let count = i64::try_from(table.columns().len()).unwrap_or(i64::MAX);
rows.push(vec![
text(database.name()),
Value::BigInt(database.oid()),
text(schema.name()),
Value::BigInt(schema.oid()),
text(&table.name().table),
Value::BigInt(table.oid()),
Value::Null,
empty(),
Value::Boolean(database.internal()),
Value::Boolean(false),
Value::Boolean(false),
Value::BigInt(i64::try_from(table.rows().len()).unwrap_or(i64::MAX)),
Value::BigInt(count),
Value::BigInt(0),
Value::BigInt(0),
text(&create_table(table)),
]);
}
Metadata::new("duckdb_tables", &table_fields(), &rows, plan, index, columns)
}
pub(crate) fn viewnames(
catalog: &Catalog,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let mut rows = Vec::new();
for database in catalog.databases() {
for schema in database.schemas() {
for view in schema.views() {
let columns = view.columns();
rows.push(vec![
text(database.name()),
Value::BigInt(database.oid()),
text(schema.name()),
Value::BigInt(schema.oid()),
text(&view.name().table),
Value::BigInt(view.oid()),
Value::Null,
empty(),
Value::Boolean(database.internal()),
Value::Boolean(database.internal()),
if columns.is_empty() {
Value::Null
} else {
Value::BigInt(i64::try_from(columns.len()).unwrap_or(i64::MAX))
},
text(view.statement()),
Value::Boolean(!columns.is_empty()),
]);
}
}
}
Metadata::new("duckdb_views", &view_fields(), &rows, plan, index, columns)
}
pub(crate) fn columnnames(
catalog: &Catalog,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let mut rows = Vec::new();
for database in catalog.databases() {
for schema in database.schemas() {
for table in schema.tables() {
for (at, column) in table.columns().iter().enumerate() {
rows.push(column_row(
database,
schema,
&table.name().table,
table.oid(),
at,
&column.name,
&column.ty,
!column.not_null,
));
}
}
for view in schema.views() {
for (at, field) in view.columns().iter().enumerate() {
rows.push(column_row(
database,
schema,
&view.name().table,
view.oid(),
at,
&field.name,
&field.ty,
true,
));
}
}
}
}
Metadata::new("duckdb_columns", &column_fields(), &rows, plan, index, columns)
}
#[expect(clippy::too_many_arguments, reason = "a row of a twenty one column table")]
fn column_row(
database: &Database,
schema: &Schema,
table: &str,
oid: i64,
at: usize,
name: &str,
ty: &LogicalType,
nullable: bool,
) -> Vec<Value> {
let (precision, radix, scale) = numeric_facts(ty);
vec![
text(database.name()),
Value::BigInt(database.oid()),
text(schema.name()),
Value::BigInt(schema.oid()),
text(table),
Value::BigInt(oid),
text(name),
Value::Integer(i32::try_from(at + 1).unwrap_or(i32::MAX)),
Value::Null,
Value::Boolean(database.internal()),
Value::Null,
Value::Boolean(nullable),
text(&ty.to_string()),
type_oid(&canonical(ty)).map_or(Value::Null, Value::BigInt),
Value::Null,
precision.map_or(Value::Null, Value::Integer),
radix.map_or(Value::Null, Value::Integer),
scale.map_or(Value::Null, Value::Integer),
empty(),
Value::Boolean(false),
Value::Null,
]
}
pub(crate) fn showtables(
catalog: &Catalog,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let mut names = Vec::new();
for database in catalog.databases() {
if !database.name().eq_ignore_ascii_case(catalog.default_catalog()) {
continue;
}
for schema in database.schemas() {
if !schema.name().eq_ignore_ascii_case(catalog.default_schema()) {
continue;
}
names.extend(schema.tables().iter().map(|table| table.name().table.clone()));
names.extend(schema.views().iter().map(|view| view.name().table.clone()));
}
}
names.sort();
let rows: Vec<Vec<Value>> = names.iter().map(|name| vec![text(name)]).collect();
Metadata::new("pragma_show_tables", &show_table_fields(), &rows, plan, index, columns)
}
pub(crate) fn showdatabases(
catalog: &Catalog,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let mut names: Vec<&str> = catalog
.databases()
.iter()
.filter(|database| !database.internal())
.map(Database::name)
.collect();
names.sort_unstable();
let rows: Vec<Vec<Value>> = names.iter().map(|name| vec![text(name)]).collect();
Metadata::new("pragma_show_databases", &show_database_fields(), &rows, plan, index, columns)
}
pub(crate) fn showtablesexpanded(
catalog: &Catalog,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let mut rows = Vec::new();
for database in catalog.databases() {
if database.internal() {
continue;
}
for schema in database.schemas() {
for table in schema.tables() {
rows.push(expanded_row(database, schema, &table.name().table, table.columns()));
}
for view in schema.views() {
rows.push(expanded_row(database, schema, &view.name().table, &view.columns()));
}
}
}
rows.sort_by_key(|row| {
let at = |index: usize| match &row[index] {
Value::Varchar(name) => name.clone(),
_ => String::new(),
};
(at(0), at(1), at(2))
});
Metadata::new(
"pragma_show_tables_expanded",
&show_expanded_fields(),
&rows,
plan,
index,
columns,
)
}
fn expanded_row(
database: &Database,
schema: &Schema,
name: &str,
columns: &[rudb_common::Field],
) -> Vec<Value> {
vec![
text(database.name()),
text(schema.name()),
text(name),
names_of(columns.iter().map(|field| field.name.clone())),
names_of(columns.iter().map(|field| field.ty.to_string())),
Value::Boolean(false),
]
}
fn names_of(values: impl Iterator<Item = String>) -> Value {
Value::List { element: LogicalType::Varchar, values: values.map(Value::Varchar).collect() }
}
fn entries(catalog: &Catalog) -> impl Iterator<Item = (&Database, &Schema, &Table)> {
catalog.databases().iter().flat_map(|database| {
database.schemas().iter().flat_map(move |schema| {
schema.tables().iter().map(move |table| (database, schema, table))
})
})
}
fn create_table(table: &Table) -> String {
let columns: Vec<String> = table
.columns()
.iter()
.map(|column| {
let null = if column.not_null { " NOT NULL" } else { "" };
format!("{} {}{null}", quoted(&column.name), column.ty)
})
.collect();
format!("CREATE TABLE {}({});", quoted(&table.name().table), columns.join(", "))
}
fn empty() -> Value {
Value::map(LogicalType::Varchar, LogicalType::Varchar, Vec::new())
}