1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
use futures_util::stream::StreamExt;
use serde::de::DeserializeOwned;
use crate::{
change_stream::{
event::ResumeToken,
session::SessionChangeStream as AsyncSessionChangeStream,
ChangeStream as AsyncChangeStream,
},
error::Result,
runtime,
};
use super::ClientSession;
/// A `ChangeStream` streams the ongoing changes of its associated collection, database or
/// deployment. `ChangeStream` instances should be created with method `watch` against the relevant
/// target.
///
/// `ChangeStream`s are "resumable", meaning that they can be restarted at a given place in the
/// stream of events. This is done automatically when the `ChangeStream` encounters certain
/// ["resumable"](https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resumable-error)
/// errors, such as transient network failures. It can also be done manually by passing
/// a [`ResumeToken`] retrieved from a past event into either the
/// [`resume_after`](crate::options::ChangeStreamOptions::resume_after) or
/// [`start_after`](crate::options::ChangeStreamOptions::start_after) (4.2+) options used to create
/// the `ChangeStream`. Issuing a raw change stream aggregation is discouraged unless users wish to
/// explicitly opt out of resumability.
///
/// A `ChangeStream` can be iterated like any other [`Iterator`]:
///
/// ```
/// # use mongodb::{sync::Client, error::Result, bson::doc,
/// # change_stream::event::ChangeStreamEvent};
/// #
/// # fn func() -> Result<()> {
/// # let client = Client::with_uri_str("mongodb://example.com")?;
/// # let coll = client.database("foo").collection("bar");
/// let mut change_stream = coll.watch(None, None)?;
/// coll.insert_one(doc! { "x": 1 }, None)?;
/// for event in change_stream {
/// let event = event?;
/// println!("operation performed: {:?}, document: {:?}", event.operation_type, event.full_document);
/// // operation performed: Insert, document: Some(Document({"x": Int32(1)}))
/// }
/// #
/// # Ok(())
/// # }
/// ```
///
/// See the documentation [here](https://docs.mongodb.com/manual/changeStreams) for more
/// details. Also see the documentation on [usage recommendations](https://docs.mongodb.com/manual/administration/change-streams-production-recommendations/).
pub struct ChangeStream<T>
where
T: DeserializeOwned + Unpin + Send + Sync,
{
async_stream: AsyncChangeStream<T>,
}
impl<T> ChangeStream<T>
where
T: DeserializeOwned + Unpin + Send + Sync,
{
pub(crate) fn new(async_stream: AsyncChangeStream<T>) -> Self {
Self { async_stream }
}
/// Returns the cached resume token that can be used to resume after the most recently returned
/// change.
///
/// See the documentation
/// [here](https://docs.mongodb.com/manual/changeStreams/#change-stream-resume-token) for more
/// information on change stream resume tokens.
pub fn resume_token(&self) -> Option<ResumeToken> {
self.async_stream.resume_token()
}
/// Update the type streamed values will be parsed as.
pub fn with_type<D: DeserializeOwned + Unpin + Send + Sync>(self) -> ChangeStream<D> {
ChangeStream {
async_stream: self.async_stream.with_type(),
}
}
/// Returns whether the change stream will continue to receive events.
pub fn is_alive(&self) -> bool {
self.async_stream.is_alive()
}
/// Retrieves the next result from the change stream, if any.
///
/// Where calling `Iterator::next` will internally loop until a change document is received,
/// this will make at most one request and return `None` if the returned document batch is
/// empty. This method should be used when storing the resume token in order to ensure the
/// most up to date token is received, e.g.
///
/// ```
/// # use mongodb::{bson::Document, sync::{Client, Collection}, error::Result};
/// # fn func() -> Result<()> {
/// # let client = Client::with_uri_str("mongodb://example.com")?;
/// # let coll: Collection<Document> = client.database("foo").collection("bar");
/// let mut change_stream = coll.watch(None, None)?;
/// let mut resume_token = None;
/// while change_stream.is_alive() {
/// if let Some(event) = change_stream.next_if_any()? {
/// // process event
/// }
/// resume_token = change_stream.resume_token();
/// }
/// #
/// # Ok(())
/// # }
/// ```
pub fn next_if_any(&mut self) -> Result<Option<T>> {
runtime::block_on(self.async_stream.next_if_any())
}
}
impl<T> Iterator for ChangeStream<T>
where
T: DeserializeOwned + Unpin + Send + Sync,
{
type Item = Result<T>;
fn next(&mut self) -> Option<Self::Item> {
runtime::block_on(self.async_stream.next())
}
}
/// A [`SessionChangeStream`] is a change stream that was created with a [`ClientSession`] that must
/// be iterated using one. To iterate, use [`SessionChangeStream::next`]:
///
/// ```
/// # use mongodb::{bson::Document, sync::Client, error::Result};
/// #
/// # async fn do_stuff() -> Result<()> {
/// # let client = Client::with_uri_str("mongodb://example.com")?;
/// # let mut session = client.start_session(None)?;
/// # let coll = client.database("foo").collection::<Document>("bar");
/// #
/// let mut cs = coll.watch_with_session(None, None, &mut session)?;
/// while let Some(event) = cs.next(&mut session)? {
/// println!("{:?}", event)
/// }
/// #
/// # Ok(())
/// # }
/// ```
pub struct SessionChangeStream<T>
where
T: DeserializeOwned + Unpin,
{
async_stream: AsyncSessionChangeStream<T>,
}
impl<T> SessionChangeStream<T>
where
T: DeserializeOwned + Unpin + Send + Sync,
{
pub(crate) fn new(async_stream: AsyncSessionChangeStream<T>) -> Self {
Self { async_stream }
}
/// Returns the cached resume token that can be used to resume after the most recently returned
/// change.
///
/// See the documentation
/// [here](https://docs.mongodb.com/manual/changeStreams/#change-stream-resume-token) for more
/// information on change stream resume tokens.
pub fn resume_token(&self) -> Option<ResumeToken> {
self.async_stream.resume_token()
}
/// Update the type streamed values will be parsed as.
pub fn with_type<D: DeserializeOwned + Unpin + Send + Sync>(self) -> SessionChangeStream<D> {
SessionChangeStream {
async_stream: self.async_stream.with_type(),
}
}
/// Retrieve the next result from the change stream.
/// The session provided must be the same session used to create the change stream.
///
/// ```
/// # use bson::{doc, Document};
/// # use mongodb::sync::Client;
/// # fn main() {
/// # async {
/// # let client = Client::with_uri_str("foo")?;
/// # let coll = client.database("foo").collection::<Document>("bar");
/// # let other_coll = coll.clone();
/// # let mut session = client.start_session(None)?;
/// let mut cs = coll.watch_with_session(None, None, &mut session)?;
/// while let Some(event) = cs.next(&mut session)? {
/// let id = bson::to_bson(&event.id)?;
/// other_coll.insert_one_with_session(doc! { "id": id }, None, &mut session)?;
/// }
/// # Ok::<(), mongodb::error::Error>(())
/// # };
/// # }
/// ```
pub fn next(&mut self, session: &mut ClientSession) -> Result<Option<T>> {
runtime::block_on(self.async_stream.next(&mut session.async_client_session))
}
/// Returns whether the change stream will continue to receive events.
pub fn is_alive(&self) -> bool {
self.async_stream.is_alive()
}
/// Retrieve the next result from the change stream, if any.
///
/// Where calling `next` will internally loop until a change document is received,
/// this will make at most one request and return `None` if the returned document batch is
/// empty. This method should be used when storing the resume token in order to ensure the
/// most up to date token is received, e.g.
///
/// ```
/// # use mongodb::{bson::Document, sync::{Client, Collection}, error::Result};
/// # async fn func() -> Result<()> {
/// # let client = Client::with_uri_str("mongodb://example.com")?;
/// # let coll: Collection<Document> = client.database("foo").collection("bar");
/// # let mut session = client.start_session(None)?;
/// let mut change_stream = coll.watch_with_session(None, None, &mut session)?;
/// let mut resume_token = None;
/// while change_stream.is_alive() {
/// if let Some(event) = change_stream.next_if_any(&mut session)? {
/// // process event
/// }
/// resume_token = change_stream.resume_token();
/// }
/// #
/// # Ok(())
/// # }
/// ```
pub fn next_if_any(&mut self, session: &mut ClientSession) -> Result<Option<T>> {
runtime::block_on(
self.async_stream
.next_if_any(&mut session.async_client_session),
)
}
}