Skip to main content

common_recordbatch/
cursor.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use datatypes::schema::SchemaRef;
16use futures::StreamExt;
17use tokio::sync::Mutex;
18
19use crate::error::Result;
20use crate::recordbatch::merge_record_batches;
21use crate::{RecordBatch, SendableRecordBatchStream};
22
23struct Inner {
24    stream: SendableRecordBatchStream,
25    current_row_index: usize,
26    current_batch: Option<RecordBatch>,
27    total_rows_in_current_batch: usize,
28}
29
30/// A cursor on RecordBatchStream that fetches data batch by batch
31pub struct RecordBatchStreamCursor {
32    schema: SchemaRef,
33    inner: Mutex<Inner>,
34}
35
36impl RecordBatchStreamCursor {
37    pub fn new(stream: SendableRecordBatchStream) -> RecordBatchStreamCursor {
38        let schema = stream.schema();
39        Self {
40            schema,
41            inner: Mutex::new(Inner {
42                stream,
43                current_row_index: 0,
44                current_batch: None,
45                total_rows_in_current_batch: 0,
46            }),
47        }
48    }
49
50    pub fn schema(&self) -> SchemaRef {
51        self.schema.clone()
52    }
53
54    /// Take `size` of row from the `RecordBatchStream` and create a new
55    /// `RecordBatch` for these rows.
56    pub async fn take(&self, size: usize) -> Result<RecordBatch> {
57        let mut remaining_rows_to_take = size;
58        let mut accumulated_rows = Vec::new();
59
60        let mut inner = self.inner.lock().await;
61
62        while remaining_rows_to_take > 0 {
63            // Ensure we have a current batch or fetch the next one
64            if inner.current_batch.is_none()
65                || inner.current_row_index >= inner.total_rows_in_current_batch
66            {
67                match inner.stream.next().await {
68                    Some(Ok(batch)) => {
69                        inner.total_rows_in_current_batch = batch.num_rows();
70                        inner.current_batch = Some(batch);
71                        inner.current_row_index = 0;
72                    }
73                    Some(Err(e)) => return Err(e),
74                    None => {
75                        // Stream is exhausted
76                        break;
77                    }
78                }
79            }
80
81            // If we still have no batch after attempting to fetch
82            let current_batch = match &inner.current_batch {
83                Some(batch) => batch,
84                None => break,
85            };
86
87            // Calculate how many rows we can take from this batch
88            let rows_to_take_from_batch = remaining_rows_to_take
89                .min(inner.total_rows_in_current_batch - inner.current_row_index);
90
91            // Slice the current batch to get the desired rows
92            let taken_batch =
93                current_batch.slice(inner.current_row_index, rows_to_take_from_batch)?;
94
95            // Add the taken batch to accumulated rows
96            accumulated_rows.push(taken_batch);
97
98            // Update cursor and remaining rows
99            inner.current_row_index += rows_to_take_from_batch;
100            remaining_rows_to_take -= rows_to_take_from_batch;
101        }
102
103        // If no rows were accumulated, return empty
104        if accumulated_rows.is_empty() {
105            return Ok(RecordBatch::new_empty(inner.stream.schema()));
106        }
107
108        // If only one batch was accumulated, return it directly
109        if accumulated_rows.len() == 1 {
110            return Ok(accumulated_rows.remove(0));
111        }
112
113        // Merge multiple batches
114        merge_record_batches(inner.stream.schema(), &accumulated_rows)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use std::sync::Arc;
121
122    use datatypes::prelude::ConcreteDataType;
123    use datatypes::schema::{ColumnSchema, Schema};
124    use datatypes::vectors::StringVector;
125
126    use super::*;
127    use crate::RecordBatches;
128
129    #[tokio::test]
130    async fn test_cursor() {
131        let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
132            "a",
133            ConcreteDataType::string_datatype(),
134            false,
135        )]));
136
137        let rbs = RecordBatches::try_from_columns(
138            schema.clone(),
139            vec![Arc::new(StringVector::from(vec!["hello", "world"])) as _],
140        )
141        .unwrap();
142
143        let cursor = RecordBatchStreamCursor::new(rbs.as_stream());
144        let result_rb = cursor.take(1).await.expect("take from cursor failed");
145        assert_eq!(result_rb.num_rows(), 1);
146
147        let result_rb = cursor.take(1).await.expect("take from cursor failed");
148        assert_eq!(result_rb.num_rows(), 1);
149
150        let result_rb = cursor.take(1).await.expect("take from cursor failed");
151        assert_eq!(result_rb.num_rows(), 0);
152
153        let rb = RecordBatch::new(
154            schema.clone(),
155            vec![Arc::new(StringVector::from(vec!["hello", "world"])) as _],
156        )
157        .unwrap();
158        let rbs2 =
159            RecordBatches::try_new(schema.clone(), vec![rb.clone(), rb.clone(), rb]).unwrap();
160        let cursor = RecordBatchStreamCursor::new(rbs2.as_stream());
161        let result_rb = cursor.take(3).await.expect("take from cursor failed");
162        assert_eq!(result_rb.num_rows(), 3);
163        let result_rb = cursor.take(2).await.expect("take from cursor failed");
164        assert_eq!(result_rb.num_rows(), 2);
165        let result_rb = cursor.take(2).await.expect("take from cursor failed");
166        assert_eq!(result_rb.num_rows(), 1);
167        let result_rb = cursor.take(2).await.expect("take from cursor failed");
168        assert_eq!(result_rb.num_rows(), 0);
169
170        let rb = RecordBatch::new(
171            schema.clone(),
172            vec![Arc::new(StringVector::from(vec!["hello", "world"])) as _],
173        )
174        .unwrap();
175        let rbs3 =
176            RecordBatches::try_new(schema.clone(), vec![rb.clone(), rb.clone(), rb]).unwrap();
177        let cursor = RecordBatchStreamCursor::new(rbs3.as_stream());
178        let result_rb = cursor.take(10).await.expect("take from cursor failed");
179        assert_eq!(result_rb.num_rows(), 6);
180    }
181}