1use arrow::array::RecordBatch;
5use arrow::datatypes::SchemaRef;
6use datafusion::physical_plan::SendableRecordBatchStream;
7use tokio::sync::mpsc;
8use tokio_stream::StreamExt;
9use tokio_util::sync::CancellationToken;
10
11use crate::error::DbError;
12
13const QUEUED_BATCHES: usize = 8;
15
16enum Delivery {
17 Batch(RecordBatch),
18 Failed(DbError),
19 End,
20}
21
22impl std::fmt::Debug for Delivery {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 Self::Batch(batch) => write!(f, "Batch({} rows)", batch.num_rows()),
26 Self::Failed(error) => write!(f, "Failed({error})"),
27 Self::End => f.write_str("End"),
28 }
29 }
30}
31
32#[derive(Debug)]
35pub struct QueryRows {
36 schema: SchemaRef,
37 receiver: mpsc::Receiver<Delivery>,
38 ended: bool,
39}
40
41impl QueryRows {
42 pub(crate) fn spawn(
46 mut stream: SendableRecordBatchStream,
47 cancel: CancellationToken,
48 finished: impl FnOnce() + Send + 'static,
49 ) -> Self {
50 let schema = stream.schema();
51 let (sender, receiver) = mpsc::channel(QUEUED_BATCHES);
52 tokio::spawn(async move {
53 loop {
54 let delivery = tokio::select! {
55 () = cancel.cancelled() => break,
56 next = stream.next() => match next {
57 Some(Ok(batch)) => Delivery::Batch(batch),
58 Some(Err(error)) => Delivery::Failed(execution_error(error)),
59 None => Delivery::End,
60 },
61 };
62 let last = !matches!(delivery, Delivery::Batch(_));
63 let sent = tokio::select! {
64 () = cancel.cancelled() => break,
65 sent = sender.send(delivery) => sent,
66 };
67 if last || sent.is_err() {
68 break;
69 }
70 }
71 finished();
72 });
73 Self {
74 schema,
75 receiver,
76 ended: false,
77 }
78 }
79
80 #[cfg(feature = "api")]
82 pub(crate) fn ready(schema: SchemaRef, batches: Vec<RecordBatch>) -> Self {
83 let (sender, receiver) = mpsc::channel(batches.len() + 1);
84 for delivery in batches
85 .into_iter()
86 .map(Delivery::Batch)
87 .chain(std::iter::once(Delivery::End))
88 {
89 let _ = sender.try_send(delivery);
91 }
92 Self {
93 schema,
94 receiver,
95 ended: false,
96 }
97 }
98
99 #[must_use]
101 pub fn schema(&self) -> SchemaRef {
102 SchemaRef::clone(&self.schema)
103 }
104
105 pub async fn next_batch(&mut self) -> Result<Option<RecordBatch>, DbError> {
111 if self.ended {
112 return Ok(None);
113 }
114 let delivery = self.receiver.recv().await;
115 self.deliver(delivery)
116 }
117
118 pub fn try_next_batch(&mut self) -> Result<Option<RecordBatch>, DbError> {
123 if self.ended {
124 return Ok(None);
125 }
126 match self.receiver.try_recv() {
127 Ok(delivery) => self.deliver(Some(delivery)),
128 Err(mpsc::error::TryRecvError::Empty) => Ok(None),
129 Err(mpsc::error::TryRecvError::Disconnected) => self.deliver(None),
130 }
131 }
132
133 pub fn blocking_next_batch(&mut self) -> Result<Option<RecordBatch>, DbError> {
142 if self.ended {
143 return Ok(None);
144 }
145 let delivery = self.receiver.blocking_recv();
146 self.deliver(delivery)
147 }
148
149 pub async fn read_all(mut self) -> Result<Vec<RecordBatch>, DbError> {
154 let mut batches = Vec::new();
155 while let Some(batch) = self.next_batch().await? {
156 batches.push(batch);
157 }
158 Ok(batches)
159 }
160
161 fn deliver(&mut self, delivery: Option<Delivery>) -> Result<Option<RecordBatch>, DbError> {
162 match delivery {
163 Some(Delivery::Batch(batch)) => Ok(Some(batch)),
164 Some(Delivery::End) => {
165 self.ended = true;
166 Ok(None)
167 }
168 Some(Delivery::Failed(error)) => {
169 self.ended = true;
170 Err(error)
171 }
172 None => {
173 self.ended = true;
174 Err(DbError::InvalidOperation(
175 "the query was cancelled before its last row".into(),
176 ))
177 }
178 }
179 }
180}
181
182pub(crate) fn execution_error(error: datafusion_common::DataFusionError) -> DbError {
185 match error {
186 datafusion_common::DataFusionError::External(inner) => match inner.downcast::<DbError>() {
187 Ok(error) => *error,
188 Err(inner) => datafusion_common::DataFusionError::External(inner).into(),
189 },
190 error => error.into(),
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use std::sync::Arc;
197
198 use arrow::array::Int64Array;
199 use arrow::datatypes::{DataType, Field, Schema};
200 use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
201
202 use super::*;
203
204 fn batches(count: i64) -> (SchemaRef, Vec<RecordBatch>) {
205 let schema = Arc::new(Schema::new(vec![Field::new("n", DataType::Int64, false)]));
206 let batches = (0..count)
207 .map(|n| {
208 RecordBatch::try_new(
209 Arc::clone(&schema),
210 vec![Arc::new(Int64Array::from(vec![n]))],
211 )
212 .unwrap()
213 })
214 .collect();
215 (schema, batches)
216 }
217
218 fn rows_of(
219 schema: SchemaRef,
220 items: Vec<Result<RecordBatch, datafusion_common::DataFusionError>>,
221 cancel: CancellationToken,
222 ) -> QueryRows {
223 let stream = RecordBatchStreamAdapter::new(schema, futures::stream::iter(items));
224 QueryRows::spawn(Box::pin(stream), cancel, || {})
225 }
226
227 #[tokio::test]
229 async fn a_slow_reader_receives_every_batch() {
230 let (schema, batches) = batches(5_000);
231 let rows = rows_of(
232 schema,
233 batches.into_iter().map(Ok).collect(),
234 CancellationToken::new(),
235 );
236 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
237 let read = rows.read_all().await.unwrap();
238 let values: Vec<i64> = read
239 .iter()
240 .map(|batch| {
241 batch
242 .column(0)
243 .as_any()
244 .downcast_ref::<Int64Array>()
245 .unwrap()
246 .value(0)
247 })
248 .collect();
249 assert_eq!(values, (0..5_000).collect::<Vec<_>>());
250 }
251
252 #[tokio::test]
253 async fn a_failure_or_a_cancellation_ends_the_rows_with_an_error() {
254 let (schema, mut batches) = batches(2);
255 let mut failed = rows_of(
256 Arc::clone(&schema),
257 vec![
258 Ok(batches.remove(0)),
259 Err(datafusion_common::DataFusionError::External(Box::new(
260 DbError::MaterializedView("scan failed".into()),
261 ))),
262 Ok(batches.remove(0)),
263 ],
264 CancellationToken::new(),
265 );
266 assert!(failed.next_batch().await.unwrap().is_some());
267 let error = failed.next_batch().await.unwrap_err();
268 assert!(matches!(error, DbError::MaterializedView(_)), "{error}");
269 assert!(failed.next_batch().await.unwrap().is_none());
270
271 let cancel = CancellationToken::new();
272 let pending = RecordBatchStreamAdapter::new(schema, futures::stream::pending());
273 let mut cancelled = QueryRows::spawn(Box::pin(pending), cancel.clone(), || {});
274 cancel.cancel();
275 let error = cancelled.next_batch().await.unwrap_err();
276 assert!(error.to_string().contains("cancelled"), "{error}");
277 }
278}