use std::sync::Arc;
use std::time::Duration;
use tonic::transport::{Channel, Endpoint};
use tracing::{error, info, instrument};
use crate::databricks::zerobus::zerobus_client::ZerobusClient;
use crate::proxy::{self, ConnectorFactory, ProxyConnector};
use crate::stream::ZerobusStream;
use crate::{StreamBuilder, TlsConfig, ZerobusError, ZerobusResult, ZerobusSdkBuilder};
#[cfg(feature = "arrow-flight")]
use crate::arrow_stream::ZerobusArrowStream;
pub const DEFAULT_SDK_IDENTIFIER: &str = concat!("zerobus-sdk-rs/", env!("CARGO_PKG_VERSION"));
#[non_exhaustive]
pub struct ZerobusSdk {
pub zerobus_endpoint: String,
pub unity_catalog_url: String,
shared_channel: tokio::sync::Mutex<Option<ZerobusClient<Channel>>>,
pub(crate) workspace_id: String,
pub(crate) tls_config: Arc<dyn TlsConfig>,
connector_factory: Option<ConnectorFactory>,
pub(crate) sdk_identifier: Arc<str>,
pub(crate) token_cache: Arc<crate::token_cache::TokenCache>,
}
impl ZerobusSdk {
pub fn builder() -> ZerobusSdkBuilder {
ZerobusSdkBuilder::new()
}
pub fn stream_builder(&self) -> StreamBuilder<'_> {
StreamBuilder::new(self)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_with_config(
zerobus_endpoint: String,
unity_catalog_url: String,
workspace_id: String,
tls_config: Arc<dyn TlsConfig>,
connector_factory: Option<ConnectorFactory>,
sdk_identifier: Arc<str>,
token_cache_enabled: bool,
token_refresh_buffer: Duration,
) -> Self {
ZerobusSdk {
zerobus_endpoint,
unity_catalog_url,
workspace_id,
shared_channel: tokio::sync::Mutex::new(None),
tls_config,
connector_factory,
sdk_identifier,
token_cache: Arc::new(crate::token_cache::TokenCache::new(
token_cache_enabled,
token_refresh_buffer,
)),
}
}
#[instrument(level = "debug", skip_all)]
pub async fn recreate_stream(&self, stream: &ZerobusStream) -> ZerobusResult<ZerobusStream> {
let batches = stream.get_unacked_batches().await?;
let channel = self.get_or_create_channel_zerobus_client().await?;
let new_stream = ZerobusStream::new_stream(
channel,
stream.table_properties.clone(),
Arc::clone(&stream.headers_provider),
stream.options.clone(),
)
.await;
match new_stream {
Ok(new_stream) => {
if let Some(stream_id) = new_stream.stream_id.as_ref() {
info!(stream_id = %stream_id, "Successfully recreated ephemeral stream");
} else {
error!("Successfully recreated a stream but stream_id is None");
}
for batch in batches {
let ack = new_stream.ingest_internal(batch).await?;
tokio::spawn(ack);
}
Ok(new_stream)
}
Err(e) => {
error!("Stream recreation failed with error: {}", e);
Err(e)
}
}
}
#[cfg(feature = "arrow-flight")]
#[instrument(level = "debug", skip_all)]
pub async fn recreate_arrow_stream(
&self,
stream: &ZerobusArrowStream,
) -> ZerobusResult<ZerobusArrowStream> {
let batches = stream.get_unacked_batches().await?;
let new_stream = ZerobusArrowStream::new(
&self.zerobus_endpoint,
Arc::clone(&self.tls_config),
stream.table_properties.clone(),
stream.headers_provider(),
stream.options().clone(),
Arc::clone(&self.sdk_identifier),
)
.await;
match new_stream {
Ok(new_stream) => {
info!(
table_name = %new_stream.table_name(),
"Successfully recreated Arrow Flight stream"
);
for batch in batches {
let _offset = new_stream.ingest_batch(batch).await?;
}
Ok(new_stream)
}
Err(e) => {
error!("Arrow Flight stream recreation failed: {}", e);
Err(e)
}
}
}
pub(crate) async fn get_or_create_channel_zerobus_client(
&self,
) -> ZerobusResult<ZerobusClient<Channel>> {
let mut guard = self.shared_channel.lock().await;
if guard.is_none() {
let endpoint = Endpoint::from_shared(self.zerobus_endpoint.clone())
.map_err(|err| ZerobusError::ChannelCreationError(err.to_string()))?
.user_agent(self.sdk_identifier.as_ref())
.map_err(|err| ZerobusError::ChannelCreationError(err.to_string()))?;
let endpoint = self.tls_config.configure_endpoint(endpoint)?;
let host = endpoint.uri().host().unwrap_or_default().to_string();
let proxy_connector = match &self.connector_factory {
Some(factory) => factory(&host).map(ProxyConnector::into_inner),
None if !proxy::is_no_proxy(&host) => proxy::create_proxy_connector(),
None => None,
};
let channel = match proxy_connector {
Some(pc) => endpoint.connect_with_connector_lazy(pc),
None => endpoint.connect_lazy(),
};
let client = ZerobusClient::new(channel)
.max_decoding_message_size(usize::MAX)
.max_encoding_message_size(usize::MAX);
*guard = Some(client);
}
Ok(guard
.as_ref()
.expect("Channel was just initialized")
.clone())
}
}