use std::{collections::HashMap, sync::Arc};
use async_trait::async_trait;
#[cfg(feature = "arrow")]
use fraiseql_arrow::db::{ArrowDatabaseAdapter, DatabaseError};
#[cfg(feature = "wire-backend")]
use fraiseql_core::db::FraiseWireAdapter;
#[cfg(not(feature = "wire-backend"))]
use fraiseql_core::db::postgres::PostgresAdapter;
use fraiseql_core::db::traits::DatabaseAdapter as CoreDatabaseAdapter;
#[cfg(not(feature = "wire-backend"))]
pub struct FlightDatabaseAdapter {
inner: Arc<PostgresAdapter>,
}
#[cfg(feature = "wire-backend")]
pub struct FlightDatabaseAdapter {
inner: Arc<FraiseWireAdapter>,
}
#[cfg(not(feature = "wire-backend"))]
impl FlightDatabaseAdapter {
#[must_use]
pub fn new(adapter: PostgresAdapter) -> Self {
Self {
inner: Arc::new(adapter),
}
}
#[must_use]
pub const fn from_arc(adapter: Arc<PostgresAdapter>) -> Self {
Self { inner: adapter }
}
#[must_use]
pub const fn inner(&self) -> &Arc<PostgresAdapter> {
&self.inner
}
}
#[cfg(feature = "wire-backend")]
impl FlightDatabaseAdapter {
#[must_use]
pub fn new(adapter: FraiseWireAdapter) -> Self {
Self {
inner: Arc::new(adapter),
}
}
#[must_use]
pub const fn from_arc(adapter: Arc<FraiseWireAdapter>) -> Self {
Self { inner: adapter }
}
#[must_use]
pub const fn inner(&self) -> &Arc<FraiseWireAdapter> {
&self.inner
}
}
#[cfg(all(feature = "arrow", not(feature = "wire-backend")))]
#[async_trait]
impl ArrowDatabaseAdapter for FlightDatabaseAdapter {
async fn execute_raw_query(
&self,
sql: &str,
) -> Result<Vec<HashMap<String, serde_json::Value>>, DatabaseError> {
self.inner
.execute_raw_query(sql)
.await
.map_err(|e: fraiseql_core::error::FraiseQLError| DatabaseError::new(e.to_string()))
}
async fn execute_gated_upload(
&self,
upload: &fraiseql_arrow::db::GatedUpload<'_>,
) -> Result<u64, DatabaseError> {
let sql = build_upload_outbox_cte(upload.insert_sql);
let mut client = self
.inner
.pool()
.get()
.await
.map_err(|e| DatabaseError::new(format!("Failed to acquire connection: {e}")))?;
let tx = client
.build_transaction()
.start()
.await
.map_err(|e| DatabaseError::new(format!("Failed to begin transaction: {e}")))?;
let row = tx
.query_one(sql.as_str(), &[&upload.table, &upload.tenant_id, &upload.user_id])
.await
.map_err(|e| DatabaseError::new(format!("Upload failed: {e}")))?;
let inserted: i64 = row.get("n");
tx.commit()
.await
.map_err(|e| DatabaseError::new(format!("Failed to commit Upload: {e}")))?;
Ok(inserted.unsigned_abs())
}
}
#[cfg(all(feature = "arrow", not(feature = "wire-backend")))]
fn build_upload_outbox_cte(insert_sql: &str) -> String {
format!(
"WITH r AS ({insert_sql} RETURNING *), \
_changelog AS ( \
INSERT INTO core.tb_entity_change_log \
(object_type, modification_type, object_id, object_data, tenant_id, \
extra_metadata, commit_time) \
SELECT \
$1, 'INSERT', \
CASE WHEN to_jsonb(r)->>'id' ~ \
'^[0-9a-fA-F]{{8}}-[0-9a-fA-F]{{4}}-[0-9a-fA-F]{{4}}-[0-9a-fA-F]{{4}}-[0-9a-fA-F]{{12}}$' \
THEN (to_jsonb(r)->>'id')::uuid ELSE NULL END, \
to_jsonb(r), $2::text::uuid, \
jsonb_build_object('transport', 'flight', 'flight_user_id', $3::text), \
clock_timestamp() \
FROM r \
RETURNING 1 \
) \
SELECT count(*)::bigint AS n FROM r"
)
}
#[cfg(all(feature = "arrow", feature = "wire-backend"))]
#[async_trait]
impl ArrowDatabaseAdapter for FlightDatabaseAdapter {
async fn execute_raw_query(
&self,
sql: &str,
) -> Result<Vec<HashMap<String, serde_json::Value>>, DatabaseError> {
self.inner
.execute_raw_query(sql)
.await
.map_err(|e: fraiseql_core::error::FraiseQLError| DatabaseError::new(e.to_string()))
}
}