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_db::tenant::TenantContext;
use jammi_db::tenant_scope::TenantBinding;
use tonic::transport::Server;
use tonic::{Request, Status};
use crate::grpc::catalog::CatalogServer;
use crate::grpc::proto::catalog::catalog_service_server::CatalogServiceServer;
use crate::grpc::session::{SessionId, 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_catalog_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 catalog_svc = CatalogServiceServer::with_interceptor(
CatalogServer::new(
store,
crate::tiers::TierSet::resolve(std::iter::empty())?,
None,
),
interceptor,
);
tracing::info!(
"Flight SQL + CatalogService listening on {addr} \
(tenant binding via jammi-session-id header)"
);
Server::builder()
.add_service(flight_svc)
.add_service(catalog_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())
}
}