use std::{
collections::{BTreeMap, BTreeSet, HashMap},
sync::Arc,
};
use super::{GpuFunctionDescriptor, GpuFunctionFamily};
use crate::error::{Error, ErrorCode, Result};
#[derive(Clone)]
pub(crate) struct GpuFunctionCatalogEntry {
pub(crate) descriptor: GpuFunctionDescriptor,
pub(crate) family: Arc<dyn GpuFunctionFamily>,
}
#[derive(Clone)]
pub(crate) struct GpuFunctionCatalog {
families: Vec<Arc<dyn GpuFunctionFamily>>,
entries: Vec<GpuFunctionCatalogEntry>,
by_function_name: HashMap<String, usize>,
family_ids: BTreeSet<&'static str>,
}
impl std::fmt::Debug for GpuFunctionCatalog {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GpuFunctionCatalog")
.field("function_count", &self.entries.len())
.field("family_ids", &self.family_ids)
.finish()
}
}
impl GpuFunctionCatalog {
pub(crate) fn try_new(families: Vec<Arc<dyn GpuFunctionFamily>>) -> Result<Self> {
let mut family_ids = BTreeSet::new();
let mut providers = BTreeMap::new();
let mut entries = Vec::new();
for family in &families {
let family_id = family.family_id();
if !is_identifier(family_id) || !family_ids.insert(family_id) {
return Err(catalog_error(format!("duplicate or invalid GPU function family id '{family_id}'")));
}
for descriptor in family.descriptors() {
validate_descriptor(&descriptor)?;
match providers.entry(descriptor.provider.clone()) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(family_id);
},
std::collections::btree_map::Entry::Occupied(entry) if *entry.get() == family_id => {},
std::collections::btree_map::Entry::Occupied(_) => {
return Err(catalog_error(format!(
"GPU provider '{}' is declared by more than one family",
descriptor.provider
)));
},
}
entries.push(GpuFunctionCatalogEntry { descriptor, family: Arc::clone(family) });
}
}
entries.sort_by(|left, right| left.descriptor.function_name.cmp(&right.descriptor.function_name));
let mut by_function_name = HashMap::with_capacity(entries.len());
for (index, entry) in entries.iter().enumerate() {
let function_name = entry.descriptor.function_name.clone();
if by_function_name.insert(function_name.clone(), index).is_some() {
return Err(catalog_error(format!("duplicate GPU function name '{function_name}'")));
}
}
Ok(Self { families, entries, by_function_name, family_ids })
}
pub(crate) fn entries(&self) -> &[GpuFunctionCatalogEntry] {
&self.entries
}
pub(crate) fn get(&self, function_name: &str) -> Option<&GpuFunctionCatalogEntry> {
self.by_function_name.get(function_name).map(|index| &self.entries[*index])
}
pub(crate) fn contains_family(&self, family_id: &str) -> bool {
self.family_ids.contains(family_id)
}
pub(crate) fn families(&self) -> &[Arc<dyn GpuFunctionFamily>] {
&self.families
}
}
fn validate_descriptor(descriptor: &GpuFunctionDescriptor) -> Result<()> {
for (field, value) in
[("function_name", descriptor.function_name.as_str()), ("provider", descriptor.provider.as_str())]
{
if !is_identifier(value) {
return Err(catalog_error(format!("GPU descriptor {field} must be a non-empty lowercase identifier")));
}
}
if !descriptor.function_name.starts_with(&format!("{}_", descriptor.provider)) {
return Err(catalog_error("GPU descriptor function_name must be provider-qualified"));
}
if descriptor.signature.trim().is_empty() || descriptor.summary.trim().is_empty() {
return Err(catalog_error("GPU descriptor signature and summary must be non-empty"));
}
if let Some(reason) = descriptor.availability.unavailable_reason()
&& !is_identifier(reason)
{
return Err(catalog_error("GPU descriptor unavailable reason must be a lowercase identifier"));
}
if !descriptor.argument_descriptions.is_array()
|| !descriptor.options_schema.is_object()
|| !descriptor.result_schemas.is_object()
|| !descriptor.validation_request_schema.is_object()
|| !descriptor.examples.is_array()
|| !descriptor.limitations.is_array()
{
return Err(catalog_error("GPU descriptor JSON values must use the common array/object contract"));
}
let roles = descriptor.relation_role_names().map_err(catalog_error)?;
let mut unique_roles = BTreeSet::new();
for role in roles {
if !is_identifier(role) || !unique_roles.insert(role) {
return Err(catalog_error("GPU descriptor relation roles must be unique non-empty lowercase identifiers"));
}
}
Ok(())
}
fn is_identifier(value: &str) -> bool {
!value.is_empty() && value.bytes().all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
}
fn catalog_error(message: impl Into<String>) -> Error {
Error::new(ErrorCode::InvalidRequest, "algeon_datafusion.gpu_functions.catalog", message)
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use datafusion::catalog::Session;
use serde_json::json;
use super::*;
use crate::gpu_functions::{GpuCallRequest, GpuCallValidation, GpuFunctionAvailability};
#[derive(Debug)]
struct FakeFamily {
id: &'static str,
names: Vec<&'static str>,
}
#[async_trait::async_trait]
impl GpuFunctionFamily for FakeFamily {
fn family_id(&self) -> &'static str {
self.id
}
fn descriptors(&self) -> Vec<GpuFunctionDescriptor> {
self.names.iter().map(|name| descriptor(name)).collect()
}
async fn validate(&self, _: &dyn Session, function_name: &str, request: &GpuCallRequest) -> GpuCallValidation {
GpuCallValidation::invalid("fake", "fake", function_name, request.normalized_json())
}
}
fn descriptor(name: &str) -> GpuFunctionDescriptor {
GpuFunctionDescriptor {
function_name: format!("fake_{name}"),
provider: "fake".to_string(),
signature: format!("fake_{name}()"),
summary: "test function".to_string(),
relation_roles: json!([]),
argument_descriptions: json!([]),
options_schema: json!({}),
result_schemas: json!({}),
validation_request_schema: json!({}),
examples: json!([]),
limitations: json!([]),
availability: GpuFunctionAvailability::Available,
}
}
#[test]
fn catalog_sorts_and_rejects_duplicate_names_and_families() {
let alpha = Arc::new(FakeFamily { id: "alpha", names: vec!["z", "a"] });
let catalog = GpuFunctionCatalog::try_new(vec![alpha]).unwrap();
assert_eq!(
catalog.entries().iter().map(|entry| entry.descriptor.function_name.as_str()).collect::<Vec<_>>(),
vec!["fake_a", "fake_z"]
);
assert!(catalog.get("fake_a").is_some());
assert!(catalog.get("a").is_none());
let duplicate_name = GpuFunctionCatalog::try_new(vec![
Arc::new(FakeFamily { id: "one", names: vec!["one"] }),
Arc::new(FakeFamily { id: "two", names: vec!["one"] }),
]);
assert!(duplicate_name.is_err());
let duplicate_provider = GpuFunctionCatalog::try_new(vec![
Arc::new(FakeFamily { id: "one", names: vec!["one"] }),
Arc::new(FakeFamily { id: "two", names: vec!["two"] }),
]);
assert!(duplicate_provider.is_err());
let duplicate_family = GpuFunctionCatalog::try_new(vec![
Arc::new(FakeFamily { id: "one", names: vec!["one"] }),
Arc::new(FakeFamily { id: "one", names: vec!["two"] }),
]);
assert!(duplicate_family.is_err());
}
}