use std::collections::HashMap;
use std::future::{Future, IntoFuture};
use std::marker::PhantomData;
use std::pin::Pin;
use std::time::Duration;
use crate::errors::{YdbError, YdbResult};
use crate::result::{ResultSet, Row};
use crate::types::Value;
use crate::TxMode;
use super::exec::{resolve_commit_tx, CallOptions, ClientExecContext, TransactionExecContext};
use super::internal::ExecCoreRef;
use super::stream_facade::{materialize_query, QueryStream};
use super::FromYdbRow;
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub enum ExecCall {}
pub struct OneRow<T>(PhantomData<T>);
pub struct OptionalRow<T>(PhantomData<T>);
pub enum OneResultSet {}
pub enum Streamed {}
pub struct ClientOneShot;
pub struct Interactive;
pub type ExecBuilder<'a, S = ClientOneShot> = CallBuilder<'a, ExecCall, S>;
pub type QueryRowBuilder<'a, T = Row, S = ClientOneShot> = CallBuilder<'a, OneRow<T>, S>;
pub type OptionalRowBuilder<'a, T = Row, S = ClientOneShot> = CallBuilder<'a, OptionalRow<T>, S>;
pub type ResultSetBuilder<'a, S = ClientOneShot> = CallBuilder<'a, OneResultSet, S>;
pub type QueryStreamBuilder<'a, S = ClientOneShot> = CallBuilder<'a, Streamed, S>;
pub struct CallBuilder<'a, K, S = ClientOneShot> {
core: ExecCoreRef<'a>,
text: String,
params: HashMap<String, Value>,
opts: CallOptions,
_kind: PhantomData<fn() -> K>,
_scope: PhantomData<S>,
}
impl<'a, K> CallBuilder<'a, K, ClientOneShot> {
pub(crate) fn new_client(ctx: &'a mut ClientExecContext, text: String) -> Self {
Self {
core: ExecCoreRef::Client(ctx),
text,
params: HashMap::new(),
opts: CallOptions::default(),
_kind: PhantomData,
_scope: PhantomData,
}
}
pub fn with_implicit_session(mut self) -> Self {
self.opts.implicit_session = true;
self
}
}
impl<'a, K> CallBuilder<'a, K, Interactive> {
pub(crate) fn new_transaction(ctx: &'a mut TransactionExecContext, text: String) -> Self {
Self {
core: ExecCoreRef::Transaction(ctx),
text,
params: HashMap::new(),
opts: CallOptions::default(),
_kind: PhantomData,
_scope: PhantomData,
}
}
}
impl<'a, K, S> CallBuilder<'a, K, S> {
fn into_kind<K2>(self) -> CallBuilder<'a, K2, S> {
CallBuilder {
core: self.core,
text: self.text,
params: self.params,
opts: self.opts,
_kind: PhantomData,
_scope: PhantomData,
}
}
pub fn param(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
self.params.insert(name.into(), value.into());
self
}
pub fn params(mut self, params: HashMap<String, Value>) -> Self {
self.params.extend(params);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.opts.timeout = Some(timeout);
self
}
pub fn idempotent(mut self, idempotent: bool) -> Self {
self.opts.idempotent = Some(idempotent);
self
}
pub fn collect_stats(mut self) -> Self {
self.opts.collect_stats = true;
self
}
pub fn with_commit(mut self, commit: bool) -> Self {
self.opts.commit_tx = Some(commit);
self
}
pub fn with_tx_mode(mut self, mode: TxMode) -> Self {
self.opts.tx_mode = Some(mode);
self
}
pub fn implicit_tx(self) -> Self {
self.with_tx_mode(TxMode::Implicit)
}
}
impl<'a, T, S> CallBuilder<'a, OneRow<T>, S> {
pub fn typed<U: FromYdbRow>(self) -> CallBuilder<'a, OneRow<U>, S> {
self.into_kind()
}
pub fn optional(self) -> CallBuilder<'a, OptionalRow<T>, S> {
self.into_kind()
}
}
pub(crate) fn exactly_one_set(mut sets: Vec<ResultSet>) -> YdbResult<ResultSet> {
match sets.len() {
0 => Err(YdbError::Custom("expected 1 result set, got 0".to_string())),
1 => Ok(sets.pop().expect("len checked")),
count => Err(YdbError::Custom(format!(
"expected 1 result set, got {count}"
))),
}
}
pub(crate) fn take_single_row(result_set: ResultSet) -> YdbResult<Option<Row>> {
let mut rows = result_set.into_iter();
let row = rows.next();
if rows.next().is_some() {
return Err(YdbError::Custom(
"expected at most 1 row in result set, got more".to_string(),
));
}
Ok(row)
}
impl<'a, S> IntoFuture for CallBuilder<'a, ExecCall, S> {
type Output = YdbResult<()>;
type IntoFuture = BoxFuture<'a, Self::Output>;
fn into_future(mut self) -> Self::IntoFuture {
Box::pin(async move {
materialize_query(&mut self.core, self.text, self.params, self.opts).await?;
Ok(())
})
}
}
impl<'a, T: FromYdbRow + 'a, S> IntoFuture for CallBuilder<'a, OneRow<T>, S> {
type Output = YdbResult<T>;
type IntoFuture = BoxFuture<'a, Self::Output>;
fn into_future(mut self) -> Self::IntoFuture {
Box::pin(async move {
let set = exactly_one_set(
materialize_query(&mut self.core, self.text, self.params, self.opts).await?,
)?;
let row = take_single_row(set)?.ok_or(YdbError::NoRows)?;
T::from_row(row)
})
}
}
impl<'a, T: FromYdbRow + 'a, S> IntoFuture for CallBuilder<'a, OptionalRow<T>, S> {
type Output = YdbResult<Option<T>>;
type IntoFuture = BoxFuture<'a, Self::Output>;
fn into_future(mut self) -> Self::IntoFuture {
Box::pin(async move {
let set = exactly_one_set(
materialize_query(&mut self.core, self.text, self.params, self.opts).await?,
)?;
take_single_row(set)?.map(T::from_row).transpose()
})
}
}
impl<'a, S> IntoFuture for CallBuilder<'a, OneResultSet, S> {
type Output = YdbResult<ResultSet>;
type IntoFuture = BoxFuture<'a, Self::Output>;
fn into_future(mut self) -> Self::IntoFuture {
Box::pin(async move {
exactly_one_set(
materialize_query(&mut self.core, self.text, self.params, self.opts).await?,
)
})
}
}
impl<'a, S> IntoFuture for CallBuilder<'a, Streamed, S> {
type Output = YdbResult<QueryStream<'a>>;
type IntoFuture = BoxFuture<'a, Self::Output>;
fn into_future(mut self) -> Self::IntoFuture {
Box::pin(async move {
let commit_tx = resolve_commit_tx(&self.core, &self.opts);
let stream = self
.core
.begin_stream(self.text, self.params, self.opts, false)
.await?;
Ok(QueryStream {
core: self.core,
stream,
commit_tx,
})
})
}
}
pub trait QueryExecutor {
type Scope;
fn exec(&mut self, text: impl Into<String>) -> ExecBuilder<'_, Self::Scope>;
fn query(&mut self, text: impl Into<String>) -> QueryStreamBuilder<'_, Self::Scope>;
fn query_result_set(&mut self, text: impl Into<String>) -> ResultSetBuilder<'_, Self::Scope>;
fn query_row(&mut self, text: impl Into<String>) -> QueryRowBuilder<'_, Row, Self::Scope>;
}
macro_rules! impl_client_query_methods {
() => {
pub fn exec(&mut self, text: impl Into<String>) -> ExecBuilder<'_, ClientOneShot> {
CallBuilder::new_client(&mut self.ctx, text.into())
}
pub fn query(&mut self, text: impl Into<String>) -> QueryStreamBuilder<'_, ClientOneShot> {
CallBuilder::new_client(&mut self.ctx, text.into())
}
pub fn query_result_set(
&mut self,
text: impl Into<String>,
) -> ResultSetBuilder<'_, ClientOneShot> {
CallBuilder::new_client(&mut self.ctx, text.into())
}
pub fn query_row(
&mut self,
text: impl Into<String>,
) -> QueryRowBuilder<'_, Row, ClientOneShot> {
CallBuilder::new_client(&mut self.ctx, text.into())
}
};
}
macro_rules! impl_transaction_query_methods {
() => {
pub fn exec(&mut self, text: impl Into<String>) -> ExecBuilder<'_, Interactive> {
CallBuilder::new_transaction(&mut self.ctx, text.into())
}
pub fn query(&mut self, text: impl Into<String>) -> QueryStreamBuilder<'_, Interactive> {
CallBuilder::new_transaction(&mut self.ctx, text.into())
}
pub fn query_result_set(
&mut self,
text: impl Into<String>,
) -> ResultSetBuilder<'_, Interactive> {
CallBuilder::new_transaction(&mut self.ctx, text.into())
}
pub fn query_row(
&mut self,
text: impl Into<String>,
) -> QueryRowBuilder<'_, Row, Interactive> {
CallBuilder::new_transaction(&mut self.ctx, text.into())
}
};
}
pub(crate) use impl_client_query_methods;
pub(crate) use impl_transaction_query_methods;