use std::collections::BTreeMap;
use std::sync::{Arc, OnceLock, RwLock};
use datafusion::catalog::{CatalogProviderList, TableProvider};
use datafusion::common::TableReference;
use datafusion::execution::context::SessionState;
use datafusion::prelude::{SessionConfig, SessionContext};
mod define;
mod describe;
pub mod schema;
pub use define::{
check_ddl, check_drop, check_drop_schema, check_replace, closest_name, create_graph,
drop_graph, unknown_graph,
};
pub use describe::{describe_graph, show_graphs};
pub use schema::{edges_plan, edges_plan_from, nodes_plan, nodes_plan_from, GraphSchema};
mod catalog_table;
mod exec;
pub mod functions;
mod index;
mod kernels;
mod node;
mod relation;
mod session;
pub mod traversal;
pub use catalog_table::UrsaFunctions;
pub use exec::{
GraphAlgorithmExec, GraphPlanner, GraphTraversalExec, KeepEdgeOrder, UrsaQueryPlanner,
};
pub use index::{index_builds, index_loads, FileIdentity, GraphIndex, IndexCache, Weights};
pub use node::{GraphAlgorithmNode, GraphTraversalNode, Output};
pub(crate) use session::{edge_order_rule, query_planner, register_functions, with_algorithms};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphDef {
pub name: String,
pub edges: EdgeTable,
pub vertices: Option<VertexTable>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EdgeTable {
pub table: TableReference,
pub source: String,
pub destination: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VertexTable {
pub table: TableReference,
pub key: String,
}
#[derive(Debug, Clone)]
pub struct FileTable {
pub table: TableReference,
pub provider: Arc<dyn TableProvider>,
}
#[derive(Debug, Default)]
pub struct GraphRegistry {
graphs: RwLock<BTreeMap<String, Arc<GraphDef>>>,
files: RwLock<BTreeMap<String, Vec<FileTable>>>,
catalogs: OnceLock<Arc<dyn CatalogProviderList>>,
}
impl GraphRegistry {
pub fn get(&self, name: &str) -> Option<Arc<GraphDef>> {
self.graphs
.read()
.unwrap_or_else(|e| e.into_inner())
.get(name)
.cloned()
}
pub fn all(&self) -> Vec<Arc<GraphDef>> {
self.graphs
.read()
.unwrap_or_else(|e| e.into_inner())
.values()
.cloned()
.collect()
}
pub fn put(&self, def: GraphDef) -> Option<Arc<GraphDef>> {
self.put_with_files(def, Vec::new())
}
pub fn put_with_files(&self, def: GraphDef, files: Vec<FileTable>) -> Option<Arc<GraphDef>> {
let mut graphs = self.graphs.write().unwrap_or_else(|e| e.into_inner());
let mut all_files = self.files.write().unwrap_or_else(|e| e.into_inner());
if files.is_empty() {
all_files.remove(&def.name);
} else {
all_files.insert(def.name.clone(), files);
}
graphs.insert(def.name.clone(), Arc::new(def))
}
pub fn remove(&self, name: &str) -> Option<Arc<GraphDef>> {
let mut graphs = self.graphs.write().unwrap_or_else(|e| e.into_inner());
self.files
.write()
.unwrap_or_else(|e| e.into_inner())
.remove(name);
graphs.remove(name)
}
pub fn file_table(
&self,
graph: &str,
table: &TableReference,
) -> Option<Arc<dyn TableProvider>> {
self.files
.read()
.unwrap_or_else(|e| e.into_inner())
.get(graph)?
.iter()
.find(|f| &f.table == table)
.map(|f| Arc::clone(&f.provider))
}
pub(crate) fn set_catalogs(&self, catalogs: Arc<dyn CatalogProviderList>) {
let _ = self.catalogs.set(catalogs);
}
pub(crate) fn catalogs(&self) -> Option<&Arc<dyn CatalogProviderList>> {
self.catalogs.get()
}
pub fn names(&self) -> Vec<String> {
self.graphs
.read()
.unwrap_or_else(|e| e.into_inner())
.keys()
.cloned()
.collect()
}
}
pub(crate) fn with_registry(config: SessionConfig) -> SessionConfig {
config.with_extension(Arc::new(GraphRegistry::default()))
}
pub fn registry(state: &SessionState) -> Arc<GraphRegistry> {
state
.config()
.get_extension::<GraphRegistry>()
.expect("an Ursa session has a graph registry (built by ursa_engine::new_session)")
}
pub fn registry_of(ctx: &SessionContext) -> Arc<GraphRegistry> {
registry(&ctx.state())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_session_has_an_empty_registry() {
let ctx = crate::new_session();
let graphs = registry_of(&ctx);
assert!(graphs.all().is_empty());
graphs.put(GraphDef {
name: "g".into(),
edges: EdgeTable {
table: TableReference::bare("e"),
source: "a".into(),
destination: "b".into(),
},
vertices: None,
});
assert_eq!(registry_of(&ctx).names(), vec!["g".to_string()]);
}
}