use std::marker::PhantomData;
use turso_orm_driver::ConnectionTrait;
use turso_sql::{Condition, Expr, Order, Statement, Value};
use crate::Result;
use crate::entity::{ColumnTrait, EntityTrait, FromQueryResult, IdenStatic};
use crate::query::select::Selector;
use crate::types::IntoValueTuple;
#[derive(Clone, Debug)]
pub struct Cursor<E: EntityTrait, M> {
query: turso_sql::Select,
columns: Vec<E::Column>,
after: Option<Vec<Value>>,
before: Option<Vec<Value>>,
page: Option<(u64, bool)>,
descending: bool,
_m: PhantomData<(E, M)>,
}
impl<E: EntityTrait, M: FromQueryResult> Cursor<E, M> {
pub(crate) fn new(query: turso_sql::Select, columns: Vec<E::Column>) -> Self {
Self {
query,
columns,
after: None,
before: None,
page: None,
descending: false,
_m: PhantomData,
}
}
#[must_use]
pub fn after(mut self, key: impl IntoValueTuple) -> Self {
self.after = Some(key.into_value_tuple());
self
}
#[must_use]
pub fn before(mut self, key: impl IntoValueTuple) -> Self {
self.before = Some(key.into_value_tuple());
self
}
#[must_use]
pub fn first(mut self, n: u64) -> Self {
self.page = Some((n, true));
self
}
#[must_use]
pub fn last(mut self, n: u64) -> Self {
self.page = Some((n, false));
self
}
#[must_use]
pub fn asc(mut self) -> Self {
self.descending = false;
self
}
#[must_use]
pub fn desc(mut self) -> Self {
self.descending = true;
self
}
pub fn into_model<T: FromQueryResult>(self) -> Cursor<E, T> {
Cursor {
query: self.query,
columns: self.columns,
after: self.after,
before: self.before,
page: self.page,
descending: self.descending,
_m: PhantomData,
}
}
fn boundary(&self, values: &[Value], greater: bool) -> Condition {
let mut any = Condition::any();
for (i, column) in self.columns.iter().enumerate() {
let mut all = Condition::all();
for (prefix, value) in self.columns.iter().zip(values).take(i) {
all = all.add(prefix.into_expr().eq(Expr::val(value.clone())));
}
let Some(value) = values.get(i) else {
break;
};
let bound = if greater {
column.into_expr().gt(Expr::val(value.clone()))
} else {
column.into_expr().lt(Expr::val(value.clone()))
};
any = any.add(all.add(bound));
}
any
}
fn assemble(&self) -> (turso_sql::Select, bool) {
let mut query = self.query.clone().clear_order_by();
if let Some(after) = &self.after {
query = query.and_where(self.boundary(after, !self.descending));
}
if let Some(before) = &self.before {
query = query.and_where(self.boundary(before, self.descending));
}
let from_start = self.page.is_none_or(|(_, from_start)| from_start);
let forward = from_start != self.descending;
let order = if forward { Order::Asc } else { Order::Desc };
for column in &self.columns {
query = query.order_by_expr(column.into_expr(), order);
}
if let Some((n, _)) = self.page {
query = query.limit(n);
}
(query, !from_start)
}
pub fn build(&self) -> Statement {
turso_sql::Build::to_statement(&self.assemble().0)
}
pub async fn all<C: ConnectionTrait>(&self, db: &C) -> Result<Vec<M>> {
let (query, reversed) = self.assemble();
let mut rows = Selector::<M>::from_query(query).all(db).await?;
if reversed {
rows.reverse();
}
Ok(rows)
}
pub fn key_columns(&self) -> Vec<&'static str> {
self.columns.iter().map(IdenStatic::as_str).collect()
}
}