use rudb_catalog::Catalog;
use rudb_common::{LogicalType, Memory, Result, Value, human};
use rudb_functions::{
database_size_fields, dialect_fields, extension_fields, grammar_extension_fields,
optimizer_fields, platform_fields, user_agent_fields, version_fields,
};
use rudb_opt::UPSTREAM;
use rudb_plan::{Plan, Slice};
use crate::metadata::{Metadata, text};
const BUILT_IN: &str = "(BUILT-IN)";
const EXTENSIONS: &[(&str, bool, &[&str], &str)] = &[
("autocomplete", false, &[], "Adds support for autocomplete in the shell"),
("avro", false, &[], "Adds support for reading Avro files"),
("aws", false, &[], "Provides features that depend on the AWS SDK"),
("azure", false, &[], "Adds a filesystem abstraction for Azure blob storage to DuckDB"),
("core_functions", true, &[], "Core function library"),
("delta", false, &[], "Adds support for Delta Lake"),
("ducklake", false, &[], "Adds support for DuckLake, SQL as a Lakehouse Format"),
("encodings", false, &[], "All unicode encodings to UTF-8"),
("excel", false, &[], "Adds support for Excel-like format strings"),
("fts", false, &[], "Adds support for Full-Text Search Indexes"),
(
"httpfs",
false,
&["http", "https", "s3"],
"Adds support for reading and writing files over a HTTP(S) connection",
),
("iceberg", false, &[], "Adds support for Apache Iceberg"),
("icu", false, &[], "Adds support for time zones and collations using the ICU library"),
("inet", false, &[], "Adds support for IP-related data types and functions"),
("json", false, &[], "Adds support for JSON operations"),
("lance", false, &[], "Adds support for querying Lance datasets"),
("motherduck", false, &["md"], "Enables motherduck integration with the system"),
("mysql_scanner", false, &["mysql"], "Adds support for connecting to a MySQL database"),
("odbc_scanner", false, &["odbc"], "Adds support for connecting to remote databases over ODBC"),
("parquet", true, &[], "Adds support for reading and writing parquet files"),
(
"postgres_scanner",
false,
&["postgres"],
"Adds support for connecting to a Postgres database",
),
("quack", false, &[], "The DuckDB 'Quack' Client/Server Protocol"),
("shell", false, &[], "Adds CLI-specific support and functionalities"),
(
"spatial",
false,
&[],
"Geospatial extension that adds support for working with spatial data and functions",
),
(
"sqlite_scanner",
false,
&["sqlite", "sqlite3"],
"Adds support for reading and writing SQLite database files",
),
("tpcds", false, &[], "Adds TPC-DS data generation and query support"),
("tpch", false, &[], "Adds TPC-H data generation and query support"),
("ui", false, &[], "Adds local UI for DuckDB"),
("unity_catalog", false, &["uc_catalog"], "Adds support for connecting to Unity Catalog"),
(
"vortex",
false,
&[],
"Adds support for reading and writing files using the Vortex file format",
),
("vss", false, &[], "Adds indexing support to accelerate Vector Similarity Search"),
];
pub(crate) fn extensions(plan: &Plan, index: u32, columns: Slice) -> Result<Metadata> {
let mut rows = Vec::with_capacity(EXTENSIONS.len());
for (name, held, aliases, description) in EXTENSIONS {
let version = if *held { env!("CARGO_PKG_VERSION") } else { "" };
rows.push(vec![
text(name),
Value::Boolean(*held),
Value::Boolean(*held),
text(if *held { BUILT_IN } else { "" }),
text(description),
Value::List {
element: LogicalType::Varchar,
values: aliases.iter().map(|alias| text(alias)).collect(),
},
text(version),
text(if *held { "STATICALLY_LINKED" } else { "NOT_INSTALLED" }),
text(""),
Value::Null,
]);
}
Metadata::new("duckdb_extensions", &extension_fields(), &rows, plan, index, columns)
}
pub(crate) fn optimizers(plan: &Plan, index: u32, columns: Slice) -> Result<Metadata> {
let rows: Vec<Vec<Value>> = UPSTREAM.iter().map(|name| vec![text(name)]).collect();
Metadata::new("duckdb_optimizers", &optimizer_fields(), &rows, plan, index, columns)
}
pub(crate) fn dialects(plan: &Plan, index: u32, columns: Slice) -> Result<Metadata> {
let rows: Vec<Vec<Value>> =
rudb_parse::dialect::DIALECTS.iter().map(|dialect| vec![text(dialect.name())]).collect();
Metadata::new("duckdb_dialects", &dialect_fields(), &rows, plan, index, columns)
}
pub(crate) fn grammar_extensions(plan: &Plan, index: u32, columns: Slice) -> Result<Metadata> {
Metadata::new(
"duckdb_grammar_extensions",
&grammar_extension_fields(),
&[],
plan,
index,
columns,
)
}
fn library_version() -> String {
format!("v{}", env!("CARGO_PKG_VERSION"))
}
fn source_id() -> &'static str {
option_env!("RUDB_SOURCE_ID").unwrap_or("")
}
const CODENAME: &str = "Development Version";
fn platform_name() -> String {
let os = match std::env::consts::OS {
"macos" => "osx",
other => other,
};
let arch = match std::env::consts::ARCH {
"x86_64" => "amd64",
"aarch64" => "arm64",
other => other,
};
format!("{os}_{arch}")
}
pub(crate) fn version(plan: &Plan, index: u32, columns: Slice) -> Result<Metadata> {
let rows = vec![vec![text(&library_version()), text(source_id()), text(CODENAME)]];
Metadata::new("pragma_version", &version_fields(), &rows, plan, index, columns)
}
pub(crate) fn platform(plan: &Plan, index: u32, columns: Slice) -> Result<Metadata> {
let rows = vec![vec![text(&platform_name())]];
Metadata::new("pragma_platform", &platform_fields(), &rows, plan, index, columns)
}
pub(crate) fn user_agent(plan: &Plan, index: u32, columns: Slice) -> Result<Metadata> {
let written = format!("rudb/{}({})", library_version(), platform_name());
let rows = vec![vec![text(&written)]];
Metadata::new("pragma_user_agent", &user_agent_fields(), &rows, plan, index, columns)
}
pub(crate) fn database_size(
catalog: &Catalog,
memory: &Memory,
plan: &Plan,
index: u32,
columns: Slice,
) -> Result<Metadata> {
let limit = memory.limit().map_or_else(|| "unlimited".to_string(), human);
let used = human(memory.used());
let nothing = human(0);
let mut rows = Vec::new();
for database in catalog.databases() {
if database.internal() {
continue;
}
rows.push(vec![
text(database.name()),
text(¬hing),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
text(¬hing),
text(&used),
text(&limit),
]);
}
Metadata::new("pragma_database_size", &database_size_fields(), &rows, plan, index, columns)
}
#[cfg(test)]
mod tests {
use super::{CODENAME, EXTENSIONS, library_version, platform_name, source_id};
#[test]
fn the_extension_list_is_the_pins_list_in_the_pins_order() {
assert_eq!(EXTENSIONS.len(), 31);
let mut sorted: Vec<&str> = EXTENSIONS.iter().map(|(name, ..)| *name).collect();
let written = sorted.clone();
sorted.sort_unstable();
assert_eq!(written, sorted);
}
#[test]
fn the_two_extensions_this_engine_claims_are_the_two_it_has() {
let held: Vec<&str> =
EXTENSIONS.iter().filter(|(_, held, ..)| *held).map(|(name, ..)| *name).collect();
assert_eq!(held, vec!["core_functions", "parquet"]);
}
#[test]
fn the_version_is_written_the_way_the_pin_writes_one() {
let written = library_version();
let number = written.strip_prefix('v').expect("a leading v, which is the pin's spelling");
assert_eq!(number, env!("CARGO_PKG_VERSION"));
assert_eq!(number.split('.').count(), 3);
assert!(number.split('.').all(|part| part.parse::<u32>().is_ok()), "{written}");
}
#[test]
fn the_source_id_is_empty_unless_the_build_was_told_one() {
let written = source_id();
assert!(written.is_empty() || written.chars().all(|c| c.is_ascii_hexdigit()), "{written}");
assert_eq!(CODENAME, "Development Version");
}
#[test]
fn the_platform_is_the_os_and_the_processor_in_duckdbs_spelling() {
let written = platform_name();
let (os, arch) = written.split_once('_').expect("an os and an arch");
assert!(!os.is_empty() && !arch.is_empty(), "{written}");
assert_ne!(os, "macos");
assert_ne!(arch, "x86_64");
assert_ne!(arch, "aarch64");
}
}