use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use async_trait::async_trait;
use datafusion::common::{DFSchemaRef, Result};
use datafusion::execution::context::{QueryPlanner, SessionState};
use datafusion::logical_expr::{
Expr, Extension, InvariantLevel, LogicalPlan, UserDefinedLogicalNode,
UserDefinedLogicalNodeCore,
};
use datafusion::physical_plan::ExecutionPlan;
use datafusion::physical_planner::{DefaultPhysicalPlanner, ExtensionPlanner, PhysicalPlanner};
use uuid::Uuid;
use crate::builder::{complete_event, fail_event, start_event};
use crate::client::OpenLineageClient;
use crate::config::OpenLineageConfig;
use crate::context::LineageContextProvider;
use crate::event::RunEvent;
use crate::exec::OpenLineageExec;
use crate::extract::extract;
#[derive(Clone)]
pub struct LineageMarker {
input: LogicalPlan,
complete: RunEvent,
client: OpenLineageClient,
producer: String,
}
impl fmt::Debug for LineageMarker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LineageMarker").finish_non_exhaustive()
}
}
impl PartialEq for LineageMarker {
fn eq(&self, other: &Self) -> bool {
self.complete.run.run_id == other.complete.run.run_id && self.input == other.input
}
}
impl Eq for LineageMarker {}
impl PartialOrd for LineageMarker {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.complete
.run
.run_id
.partial_cmp(&other.complete.run.run_id)
}
}
impl Hash for LineageMarker {
fn hash<H: Hasher>(&self, state: &mut H) {
self.complete.run.run_id.hash(state);
}
}
impl UserDefinedLogicalNodeCore for LineageMarker {
fn name(&self) -> &str {
"LineageMarker"
}
fn inputs(&self) -> Vec<&LogicalPlan> {
vec![&self.input]
}
fn schema(&self) -> &DFSchemaRef {
self.input.schema()
}
fn check_invariants(&self, _check: InvariantLevel) -> Result<()> {
Ok(())
}
fn expressions(&self) -> Vec<Expr> {
vec![]
}
fn fmt_for_explain(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "LineageMarker")
}
fn with_exprs_and_inputs(
&self,
_exprs: Vec<Expr>,
mut inputs: Vec<LogicalPlan>,
) -> Result<Self> {
Ok(Self {
input: inputs.pop().expect("LineageMarker has one input"),
complete: self.complete.clone(),
client: self.client.clone(),
producer: self.producer.clone(),
})
}
}
#[derive(Debug, Default)]
pub struct LineageExtensionPlanner;
#[async_trait]
impl ExtensionPlanner for LineageExtensionPlanner {
async fn plan_extension(
&self,
_planner: &dyn PhysicalPlanner,
node: &dyn UserDefinedLogicalNode,
_logical_inputs: &[&LogicalPlan],
physical_inputs: &[Arc<dyn ExecutionPlan>],
_session_state: &SessionState,
) -> Result<Option<Arc<dyn ExecutionPlan>>> {
let Some(marker) = node.as_any().downcast_ref::<LineageMarker>() else {
return Ok(None);
};
let inner = physical_inputs
.first()
.expect("LineageMarker has one physical input")
.clone();
Ok(Some(OpenLineageExec::new(
inner,
marker.client.clone(),
marker.complete.clone(),
marker.producer.clone(),
)))
}
}
pub struct OpenLineageQueryPlanner {
client: OpenLineageClient,
context: Arc<dyn LineageContextProvider>,
config: OpenLineageConfig,
physical: Arc<DefaultPhysicalPlanner>,
}
impl OpenLineageQueryPlanner {
pub fn new(
client: OpenLineageClient,
context: Arc<dyn LineageContextProvider>,
config: OpenLineageConfig,
extra_extension_planners: Vec<Arc<dyn ExtensionPlanner + Send + Sync>>,
) -> Self {
let mut planners: Vec<Arc<dyn ExtensionPlanner + Send + Sync>> =
vec![Arc::new(LineageExtensionPlanner)];
planners.extend(extra_extension_planners);
Self {
client,
context,
config,
physical: Arc::new(DefaultPhysicalPlanner::with_extension_planners(planners)),
}
}
}
impl fmt::Debug for OpenLineageQueryPlanner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OpenLineageQueryPlanner")
.finish_non_exhaustive()
}
}
#[async_trait]
impl QueryPlanner for OpenLineageQueryPlanner {
async fn create_physical_plan(
&self,
logical_plan: &LogicalPlan,
session_state: &SessionState,
) -> Result<Arc<dyn ExecutionPlan>> {
let mut lineage = extract(logical_plan, &self.config);
let cx = self.context.context(session_state).await;
lineage.sql = cx.sql.clone();
if lineage.inputs.is_empty() && lineage.outputs.is_empty() {
return self
.physical
.create_physical_plan(logical_plan, session_state)
.await;
}
let run_id = cx.run_id.unwrap_or_else(Uuid::now_v7);
self.client
.emit(start_event(run_id, &lineage, &cx, &self.config));
let marker = LineageMarker {
input: logical_plan.clone(),
complete: complete_event(run_id, &lineage, &cx, &self.config),
client: self.client.clone(),
producer: self.config.producer.clone(),
};
let wrapped = LogicalPlan::Extension(Extension {
node: Arc::new(marker),
});
match self
.physical
.create_physical_plan(&wrapped, session_state)
.await
{
Ok(plan) => Ok(plan),
Err(err) => {
self.client.emit(fail_event(
run_id,
&lineage,
&cx,
&self.config,
&err.to_string(),
));
Err(err)
}
}
}
}