sift-query-sqlx 0.1.0

PostgreSQL renderer and sqlx executor for SiftQuery
Documentation
use sift_query_core::{QueryPlan, SearchEntity};
use sqlx::postgres::PgRow;
use sqlx::{FromRow, Postgres, Row};
use tokio::time::{Instant, timeout_at};

use crate::execution::io::{
    PreparedPgQuery, close_hard, configure_statement_timeout, fetch_cluster, finish_statement,
};
use crate::postgres::decode::decode_map;
use crate::{Page, PgSearchError, PgSiftQuery, RowMap, TimeoutStage};

impl PgSiftQuery {
    pub(crate) async fn execute_window_typed<E, R>(
        &self,
        window: PreparedPgQuery,
        fallback: PreparedPgQuery,
        deadline: Instant,
    ) -> Result<Page<R>, PgSearchError>
    where
        E: SearchEntity,
        R: Send + Unpin + for<'row> FromRow<'row, PgRow>,
    {
        let connection = self.acquire(deadline).await?;
        let (connection, rows) = fetch_window_rows(connection, window, deadline).await?;
        let total_count = rows
            .first()
            .map(|row| row.try_get::<i64, _>("sq_total_count"))
            .transpose()?;
        let data = rows
            .iter()
            .map(R::from_row)
            .collect::<Result<Vec<_>, _>>()?;
        let total_count = match total_count {
            Some(count) => count,
            None => {
                fetch_cluster::<E>(connection, fallback, None, E::META, deadline)
                    .await?
                    .1
                    .0
            }
        };
        Ok(Page {
            data,
            total_count,
            summaries: Vec::new(),
            next_cursor: None,
        })
    }

    pub(crate) async fn execute_window_map<E: SearchEntity>(
        &self,
        plan: &QueryPlan<E>,
        window: PreparedPgQuery,
        fallback: PreparedPgQuery,
        deadline: Instant,
    ) -> Result<Page<RowMap>, PgSearchError> {
        let connection = self.acquire(deadline).await?;
        let (connection, rows) = fetch_window_rows(connection, window, deadline).await?;
        let total_count = rows
            .first()
            .map(|row| row.try_get::<i64, _>("sq_total_count"))
            .transpose()?;
        let data = rows
            .iter()
            .map(|row| decode_map(row, plan))
            .collect::<Result<Vec<_>, _>>()?;
        let total_count = match total_count {
            Some(count) => count,
            None => {
                fetch_cluster::<E>(connection, fallback, None, E::META, deadline)
                    .await?
                    .1
                    .0
            }
        };
        Ok(Page {
            data,
            total_count,
            summaries: Vec::new(),
            next_cursor: None,
        })
    }
}

async fn fetch_window_rows(
    connection: sqlx::pool::PoolConnection<Postgres>,
    prepared: PreparedPgQuery,
    deadline: Instant,
) -> Result<(sqlx::pool::PoolConnection<Postgres>, Vec<PgRow>), PgSearchError> {
    let mut connection =
        configure_statement_timeout(connection, deadline, TimeoutStage::ListQuery).await?;
    let result = timeout_at(
        deadline,
        sqlx::query_with(prepared.sql(), prepared.arguments).fetch_all(&mut *connection),
    )
    .await;
    let Ok(result) = result else {
        close_hard(connection).await;
        return Err(PgSearchError::Timeout(TimeoutStage::ListQuery));
    };
    finish_statement(connection, result, TimeoutStage::ListQuery).await
}