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::QuerySessionMode;
use crate::QueryTxMode;
use super::exec::{resolve_commit_tx, CallOptions};
use super::internal::{ExecCoreRef, HasCore};
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 type ExecBuilder<'a> = CallBuilder<'a, ExecCall>;
pub type QueryRowBuilder<'a, T = Row> = CallBuilder<'a, OneRow<T>>;
pub type OptionalRowBuilder<'a, T = Row> = CallBuilder<'a, OptionalRow<T>>;
pub type ResultSetBuilder<'a> = CallBuilder<'a, OneResultSet>;
pub type QueryStreamBuilder<'a> = CallBuilder<'a, Streamed>;
pub struct CallBuilder<'a, K> {
core: ExecCoreRef<'a>,
text: String,
params: HashMap<String, Value>,
opts: CallOptions,
_kind: PhantomData<fn() -> K>,
}
impl<'a, K> CallBuilder<'a, K> {
pub(crate) fn new(core: ExecCoreRef<'a>, text: String) -> Self {
Self {
core,
text,
params: HashMap::new(),
opts: CallOptions::default(),
_kind: PhantomData,
}
}
fn into_kind<K2>(self) -> CallBuilder<'a, K2> {
CallBuilder {
core: self.core,
text: self.text,
params: self.params,
opts: self.opts,
_kind: 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: QueryTxMode) -> Self {
self.opts.tx_mode = Some(mode);
self
}
pub fn implicit_tx(self) -> Self {
self.with_tx_mode(QueryTxMode::Implicit)
}
pub fn session_mode(mut self, mode: QuerySessionMode) -> Self {
self.opts.session_mode = Some(mode);
self
}
pub fn implicit_session(self) -> Self {
self.session_mode(QuerySessionMode::Implicit)
}
pub fn pooled_session(self) -> Self {
self.session_mode(QuerySessionMode::Pool)
}
}
impl<'a, T> CallBuilder<'a, OneRow<T>> {
pub fn typed<U: FromYdbRow>(self) -> CallBuilder<'a, OneRow<U>> {
self.into_kind()
}
pub fn optional(self) -> CallBuilder<'a, OptionalRow<T>> {
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> IntoFuture for CallBuilder<'a, ExecCall> {
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> IntoFuture for CallBuilder<'a, OneRow<T>> {
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> IntoFuture for CallBuilder<'a, OptionalRow<T>> {
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> IntoFuture for CallBuilder<'a, OneResultSet> {
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> IntoFuture for CallBuilder<'a, Streamed> {
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,
})
})
}
}
#[allow(private_bounds)]
pub trait QueryExecutor: HasCore {
fn exec(&mut self, text: impl Into<String>) -> ExecBuilder<'_> {
CallBuilder::new(self.core_mut(), text.into())
}
fn query(&mut self, text: impl Into<String>) -> QueryStreamBuilder<'_> {
CallBuilder::new(self.core_mut(), text.into())
}
fn query_result_set(&mut self, text: impl Into<String>) -> ResultSetBuilder<'_> {
CallBuilder::new(self.core_mut(), text.into())
}
fn query_row(&mut self, text: impl Into<String>) -> QueryRowBuilder<'_, Row> {
CallBuilder::new(self.core_mut(), text.into())
}
}
macro_rules! impl_query_methods {
() => {
pub fn exec(&mut self, text: impl Into<String>) -> ExecBuilder<'_> {
QueryExecutor::exec(self, text)
}
pub fn query(&mut self, text: impl Into<String>) -> QueryStreamBuilder<'_> {
QueryExecutor::query(self, text)
}
pub fn query_result_set(&mut self, text: impl Into<String>) -> ResultSetBuilder<'_> {
QueryExecutor::query_result_set(self, text)
}
pub fn query_row(&mut self, text: impl Into<String>) -> QueryRowBuilder<'_, Row> {
QueryExecutor::query_row(self, text)
}
};
}
pub(crate) use impl_query_methods;