use std::net::SocketAddr;
use async_trait::async_trait;
use datafusion::execution::context::{SessionContext, SessionState};
use datafusion_flight_sql_server::service::FlightSqlService;
use datafusion_flight_sql_server::session::SessionStateProvider;
use jammi_engine::tenant::TenantContext;
use jammi_engine::tenant_scope::TenantBinding;
use tonic::transport::Server;
use tonic::{Request, Status};
use crate::grpc::proto::session::session_service_server::SessionServiceServer;
use crate::grpc::session::{
SessionId, SessionServer, SessionStore, TenantInterceptor, SESSION_HEADER,
};
pub async fn serve_flight(
ctx: &SessionContext,
addr: SocketAddr,
) -> Result<(), Box<dyn std::error::Error>> {
let service = FlightSqlService::new(ctx.state());
tracing::info!("Flight SQL server listening on {addr}");
service.serve(addr.to_string()).await
}
pub async fn serve_flight_with_session_service(
base_ctx: &SessionContext,
base_tenant_binding: TenantBinding,
addr: SocketAddr,
store: SessionStore,
) -> Result<(), Box<dyn std::error::Error>> {
let provider =
TenantBoundProvider::new(base_ctx.state(), base_tenant_binding.clone(), store.clone());
let flight = FlightSqlService::new_with_provider(Box::new(provider));
let flight_svc = arrow_flight::flight_service_server::FlightServiceServer::new(flight);
let interceptor = TenantInterceptor::new(store.clone());
let session_svc =
SessionServiceServer::with_interceptor(SessionServer::new(store), interceptor);
tracing::info!(
"Flight SQL + SessionService listening on {addr} \
(tenant binding via jammi-session-id header)"
);
Server::builder()
.add_service(flight_svc)
.add_service(session_svc)
.serve(addr)
.await?;
Ok(())
}
pub struct TenantBoundProvider {
base_state: SessionState,
binding: TenantBinding,
store: SessionStore,
}
impl TenantBoundProvider {
pub fn new(base_state: SessionState, binding: TenantBinding, store: SessionStore) -> Self {
Self {
base_state,
binding,
store,
}
}
}
#[async_trait]
impl SessionStateProvider for TenantBoundProvider {
async fn new_context(&self, request: &Request<()>) -> Result<SessionState, Status> {
let tenant = request
.metadata()
.get(SESSION_HEADER)
.and_then(|v| v.to_str().ok())
.map(SessionId::new)
.and_then(|sid| self.store.get(&sid));
self.binding.set_shared(TenantContext::from_option(tenant));
Ok(self.base_state.clone())
}
}