use std::sync::Arc;
use datafusion::arrow::array::{ArrayRef, RecordBatch, StringArray};
use datafusion::arrow::datatypes::{DataType, Field, Schema};
use datafusion::common::TableReference;
use datafusion::error::{DataFusionError, Result};
use datafusion::execution::context::SessionState;
use super::define::unknown_graph;
use super::schema::{edges_plan, graph_table, nodes_plan, EDGES, ID, NODES};
use super::{registry, GraphRegistry, PropertyGraph};
pub fn show_graphs(state: &SessionState) -> Result<RecordBatch> {
let graphs = registry(state);
let mut rows: Vec<[Option<String>; 6]> = Vec::new();
for g in graphs.all() {
if g.edge_tables.is_empty() {
for v in &g.vertex_tables {
rows.push([
Some(g.name.clone()),
None,
None,
None,
Some(table_name(&graphs, &g, &v.table)),
Some(v.key.clone()),
]);
}
continue;
}
for e in &g.edge_tables {
let vertex = g
.endpoint_vertex(e, true)
.or_else(|| g.endpoint_vertex(e, false));
rows.push([
Some(g.name.clone()),
Some(table_name(&graphs, &g, &e.table)),
Some(e.source.clone()),
Some(e.destination.clone()),
vertex.map(|v| table_name(&graphs, &g, &v.table)),
vertex.map(|v| v.key.clone()),
]);
}
}
let columns = (0..6)
.map(|i| Arc::new(rows.iter().map(|r| r[i].clone()).collect::<StringArray>()) as ArrayRef)
.collect();
let schema = Schema::new(vec![
Field::new("name", DataType::Utf8, false),
Field::new("edge_table", DataType::Utf8, true),
Field::new("source", DataType::Utf8, true),
Field::new("destination", DataType::Utf8, true),
Field::new("vertex_table", DataType::Utf8, true),
Field::new("vertex_key", DataType::Utf8, true),
]);
Ok(RecordBatch::try_new(Arc::new(schema), columns)?)
}
pub async fn describe_graph(state: &SessionState, name: &str) -> Result<RecordBatch> {
let graphs = registry(state);
let graph = graphs
.get(name)
.ok_or_else(|| unknown_graph(name, &graphs))?;
let Some(def) = graph.default_pair() else {
return describe_elements(state, &graphs, &graph).await;
};
let edge_table = table_name(&graphs, &graph, &def.edges.table);
let vertex_table = def
.vertices
.as_ref()
.map(|v| (table_name(&graphs, &graph, &v.table), &v.key));
let unreadable = |e: DataFusionError| {
DataFusionError::Plan(format!(
"cannot describe graph '{name}': {}",
crate::user_message(&e)
))
};
let mut rows: Vec<[Option<String>; 5]> = Vec::new();
let nodes = nodes_plan(state, &def).await.map_err(unreadable)?;
for field in nodes.schema().fields() {
let column = field.name();
let (role, origin) = match (&vertex_table, column == ID) {
(Some((table, key)), true) => (Some("node id"), format!("{table}.{key}")),
(Some((table, _)), false) => (None, format!("{table}.{column}")),
(None, _) => (
Some("node id"),
format!(
"{edge_table}.{}, {edge_table}.{}",
def.edges.source, def.edges.destination
),
),
};
rows.push(row(NODES, column, field.data_type(), role, origin));
}
let edges = edges_plan(state, &def).await.map_err(unreadable)?;
for field in edges.schema().fields() {
let column = field.name();
let role = match (
column == &def.edges.source,
column == &def.edges.destination,
) {
(true, true) => Some("source, destination"),
(true, false) => Some("source"),
(false, true) => Some("destination"),
(false, false) => None,
};
rows.push(row(
EDGES,
column,
field.data_type(),
role,
format!("{edge_table}.{column}"),
));
}
describe_batch(rows)
}
async fn describe_elements(
state: &SessionState,
graphs: &GraphRegistry,
graph: &PropertyGraph,
) -> Result<RecordBatch> {
let unreadable = |e: DataFusionError| {
DataFusionError::Plan(format!(
"cannot describe graph '{}': {}",
graph.name,
crate::user_message(&e)
))
};
let mut rows: Vec<[Option<String>; 5]> = Vec::new();
for v in &graph.vertex_tables {
let schema = graph_table(state, &graph.name, &v.table)
.await
.map_err(unreadable)?
.schema();
let table = table_name(graphs, graph, &v.table);
for field in schema.fields() {
let role = (field.name() == &v.key).then_some("key");
rows.push(row(
&v.name,
field.name(),
field.data_type(),
role,
format!("{table}.{}", field.name()),
));
}
}
for e in &graph.edge_tables {
let schema = graph_table(state, &graph.name, &e.table)
.await
.map_err(unreadable)?
.schema();
let table = table_name(graphs, graph, &e.table);
let target = |source: bool| {
graph
.endpoint_vertex(e, source)
.map(|v| format!(" \u{2192} {}", v.name))
.unwrap_or_default()
};
let (source, destination) = (
format!("source{}", target(true)),
format!("destination{}", target(false)),
);
for field in schema.fields() {
let column = field.name();
let both = format!("{source}, {destination}");
let role = match (column == &e.source, column == &e.destination) {
(true, true) => Some(both.as_str()),
(true, false) => Some(source.as_str()),
(false, true) => Some(destination.as_str()),
(false, false) => None,
};
rows.push(row(
&e.name,
column,
field.data_type(),
role,
format!("{table}.{column}"),
));
}
}
describe_batch(rows)
}
fn describe_batch(rows: Vec<[Option<String>; 5]>) -> Result<RecordBatch> {
let columns = (0..5)
.map(|i| Arc::new(rows.iter().map(|r| r[i].clone()).collect::<StringArray>()) as ArrayRef)
.collect();
let schema = Schema::new(vec![
Field::new("table_name", DataType::Utf8, false),
Field::new("column_name", DataType::Utf8, false),
Field::new("data_type", DataType::Utf8, false),
Field::new("role", DataType::Utf8, true),
Field::new("origin", DataType::Utf8, false),
]);
Ok(RecordBatch::try_new(Arc::new(schema), columns)?)
}
fn row(
table: &str,
column: &str,
data_type: &DataType,
role: Option<&str>,
origin: String,
) -> [Option<String>; 5] {
[
Some(table.to_string()),
Some(column.to_string()),
Some(data_type.to_string()),
role.map(str::to_string),
Some(origin),
]
}
fn table_name(graphs: &GraphRegistry, graph: &PropertyGraph, table: &TableReference) -> String {
if graphs.file_table(&graph.name, table).is_some() {
format!("'{}'", table.table().replace('\'', "''"))
} else {
table.to_string()
}
}