use std::{
any::Any,
sync::{Arc, Mutex, OnceLock, PoisonError},
};
use datafusion::{
common::{
DataFusionError, Result as DataFusionResult,
config::{ConfigEntry, ConfigExtension, ConfigOptions, ExtensionOptions},
},
execution::TaskContext,
prelude::SessionConfig,
};
use query_planning::admission::AttemptCapabilityEvidence;
#[cfg(feature = "server")]
use query_runtime::runtime::IoLifecycle;
use super::NativePlanningDiagnostics;
use crate::{
attempt::InvocationAttempt,
native::{FooterCache, FooterScope},
native_exec::{
AlgeonNativeExecutionConfig,
admission::{QueryAdmissionSource, QueryAdmissionTicket},
},
};
#[derive(Debug, Clone)]
pub(crate) struct Invocation(Arc<InvocationState>);
#[derive(Debug)]
struct InvocationState {
attempt: Option<InvocationAttempt>,
admission: OnceLock<Arc<QueryAdmissionTicket>>,
#[cfg(feature = "server")]
execution: Mutex<Option<InvocationExecution>>,
planning: Mutex<Vec<NativePlanningDiagnostics>>,
footers: Mutex<Option<Arc<FooterCache>>>,
}
#[cfg(feature = "server")]
#[derive(Debug, Clone)]
pub(crate) struct InvocationExecution {
pub(crate) io_lifecycle: IoLifecycle,
pub(crate) trace_id: Option<String>,
}
impl Invocation {
pub(crate) fn detached() -> Self {
Self::new(None)
}
pub(crate) fn for_attempt(attempt: InvocationAttempt) -> Self {
Self::new(Some(attempt))
}
fn new(attempt: Option<InvocationAttempt>) -> Self {
Self(Arc::new(InvocationState {
attempt,
admission: OnceLock::new(),
#[cfg(feature = "server")]
execution: Mutex::new(None),
planning: Mutex::new(Vec::new()),
footers: Mutex::new(None),
}))
}
pub(crate) fn of_options(options: &ConfigOptions) -> Option<&Self> {
options.extensions.get::<Self>()
}
pub(crate) fn of_task(task: &TaskContext) -> Option<&Self> {
task.session_config().options().extensions.get::<Self>()
}
pub(crate) fn install(&self, config: &mut SessionConfig) {
config.options_mut().extensions.insert(self.clone());
}
pub(crate) fn attempt(&self) -> Option<&InvocationAttempt> {
self.0.attempt.as_ref()
}
pub(crate) fn minted_admission_ticket(&self) -> Option<&Arc<QueryAdmissionTicket>> {
self.0.admission.get()
}
pub(crate) fn attempt_admission_ticket(
&self,
source: QueryAdmissionSource,
evidence: Option<AttemptCapabilityEvidence>,
) -> Option<(Arc<QueryAdmissionTicket>, bool)> {
let attempt = self.attempt()?;
let mut minted = false;
let ticket = self.0.admission.get_or_init(|| {
minted = true;
QueryAdmissionTicket::new(source, evidence, attempt.ticket_binding())
});
Some((Arc::clone(ticket), minted))
}
#[cfg(feature = "server")]
pub(crate) fn set_execution(&self, execution: InvocationExecution) {
*self.0.execution.lock().unwrap_or_else(PoisonError::into_inner) = Some(execution);
}
pub(crate) fn execution_config(&self, config: AlgeonNativeExecutionConfig) -> AlgeonNativeExecutionConfig {
#[cfg(feature = "server")]
if let Some(execution) = self.0.execution.lock().unwrap_or_else(PoisonError::into_inner).clone() {
let config = config.with_io_lifecycle(execution.io_lifecycle).with_host_output_memory_pool_accounting(true);
return match execution.trace_id {
Some(trace_id) => config.with_trace_id(trace_id),
None => config,
};
}
config
}
pub(crate) fn footer_scope(&self) -> FooterScope {
let cache = Arc::clone(
self.0
.footers
.lock()
.unwrap_or_else(PoisonError::into_inner)
.get_or_insert_with(|| Arc::new(FooterCache::for_scope())),
);
FooterScope::join(cache)
}
pub(crate) fn end_planning_pass(&self) {
self.0.footers.lock().unwrap_or_else(PoisonError::into_inner).take();
}
pub(crate) fn record_planning(&self, diagnostics: NativePlanningDiagnostics) {
self.0.planning.lock().unwrap_or_else(PoisonError::into_inner).push(diagnostics);
}
pub(crate) fn planning_diagnostics(&self) -> Option<NativePlanningDiagnostics> {
self.0.planning.lock().unwrap_or_else(PoisonError::into_inner).last().cloned()
}
}
impl ConfigExtension for Invocation {
const PREFIX: &'static str = "algeon_invocation";
}
impl ExtensionOptions for Invocation {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn cloned(&self) -> Box<dyn ExtensionOptions> {
Box::new(self.clone())
}
fn set(&mut self, _key: &str, _value: &str) -> DataFusionResult<()> {
Err(DataFusionError::Configuration("invocation extension has no options".to_string()))
}
fn entries(&self) -> Vec<ConfigEntry> {
Vec::new()
}
}