1use std::sync::Arc;
4
5use arrow_array::RecordBatch;
6use arrow_schema::{Schema, SchemaRef};
7
8use crate::errors::{Result, RpcError};
9use crate::log::{LogLevel, LogMessage};
10use crate::wire::Metadata;
11
12pub(crate) enum Emitted {
14 Batch {
15 batch: RecordBatch,
16 metadata: Option<Metadata>,
17 },
18 Log(LogMessage),
19}
20
21pub struct OutputCollector {
23 schema: SchemaRef,
24 pub(crate) items: Vec<Emitted>,
25 data_emitted: bool,
26 finished: bool,
27 is_producer: bool,
28}
29
30impl OutputCollector {
31 pub(crate) fn new(schema: SchemaRef, is_producer: bool) -> Self {
32 Self {
33 schema,
34 items: Vec::new(),
35 data_emitted: false,
36 finished: false,
37 is_producer,
38 }
39 }
40
41 pub fn schema(&self) -> SchemaRef {
43 self.schema.clone()
44 }
45
46 pub fn emit(&mut self, batch: RecordBatch) -> Result<()> {
48 self.ensure_data_slot()?;
49 if batch.schema() != self.schema {
50 return Err(RpcError::runtime_error(format!(
51 "emit(): schema mismatch — expected {:?}, got {:?}",
52 self.schema.fields(),
53 batch.schema().fields()
54 )));
55 }
56 self.items.push(Emitted::Batch {
57 batch,
58 metadata: None,
59 });
60 self.data_emitted = true;
61 Ok(())
62 }
63
64 pub fn emit_with_metadata(&mut self, batch: RecordBatch, metadata: Metadata) -> Result<()> {
67 self.ensure_data_slot()?;
68 if batch.schema() != self.schema {
69 return Err(RpcError::runtime_error(format!(
70 "emit_with_metadata(): schema mismatch — expected {:?}, got {:?}",
71 self.schema.fields(),
72 batch.schema().fields()
73 )));
74 }
75 self.items.push(Emitted::Batch {
76 batch,
77 metadata: Some(metadata),
78 });
79 self.data_emitted = true;
80 Ok(())
81 }
82
83 fn ensure_data_slot(&self) -> Result<()> {
84 if self.data_emitted {
85 return Err(RpcError::protocol_error(
86 "only one data batch may be emitted per stream turn",
87 ));
88 }
89 Ok(())
90 }
91
92 pub fn finish(&mut self) {
94 self.finished = true;
95 }
96
97 pub fn finished(&self) -> bool {
98 self.finished
99 }
100
101 pub fn client_log(&mut self, level: LogLevel, message: impl Into<String>) {
103 self.items
104 .push(Emitted::Log(LogMessage::new(level, message)));
105 }
106
107 pub fn client_log_with(&mut self, msg: LogMessage) {
109 self.items.push(Emitted::Log(msg));
110 }
111
112 pub fn is_producer(&self) -> bool {
113 self.is_producer
114 }
115}
116
117pub trait ProducerState: Send {
119 fn produce(&mut self, out: &mut OutputCollector, ctx: &CallContext) -> Result<()>;
120
121 fn on_cancel(&mut self, _ctx: &CallContext) {}
123
124 fn encode_state(&self) -> Result<Vec<u8>> {
129 Err(RpcError::runtime_error(
130 "producer state does not implement encode_state(); \
131 override this method or register the method via MethodInfo::stream_with_codec",
132 ))
133 }
134}
135
136pub trait ExchangeState: Send {
138 fn exchange(
139 &mut self,
140 input: &RecordBatch,
141 out: &mut OutputCollector,
142 ctx: &CallContext,
143 ) -> Result<()>;
144
145 fn on_cancel(&mut self, _ctx: &CallContext) {}
146
147 fn encode_state(&self) -> Result<Vec<u8>> {
150 Err(RpcError::runtime_error(
151 "exchange state does not implement encode_state(); \
152 override this method or register the method via MethodInfo::stream_with_codec",
153 ))
154 }
155}
156
157pub struct StreamResult {
160 pub output_schema: SchemaRef,
161 pub input_schema: Option<SchemaRef>,
163 pub state: StreamStateKind,
164 pub header: Option<RecordBatch>,
166 pub header_metadata: Option<Metadata>,
168}
169
170pub enum StreamStateKind {
171 Producer(Box<dyn ProducerState>),
172 Exchange(Box<dyn ExchangeState>),
173}
174
175impl StreamResult {
176 pub fn producer(schema: SchemaRef, state: Box<dyn ProducerState>) -> Self {
177 Self {
178 output_schema: schema,
179 input_schema: None,
180 state: StreamStateKind::Producer(state),
181 header: None,
182 header_metadata: None,
183 }
184 }
185
186 pub fn exchange(
187 output_schema: SchemaRef,
188 input_schema: SchemaRef,
189 state: Box<dyn ExchangeState>,
190 ) -> Self {
191 Self {
192 output_schema,
193 input_schema: Some(input_schema),
194 state: StreamStateKind::Exchange(state),
195 header: None,
196 header_metadata: None,
197 }
198 }
199
200 pub fn with_header(mut self, header: RecordBatch) -> Self {
201 self.header = Some(header);
202 self
203 }
204}
205
206#[cfg(feature = "stream-codec")]
214#[doc(hidden)]
215pub fn producer_decoder<S>() -> crate::server::StateDecoder
216where
217 S: ProducerState + crate::stream_codec::StreamStateCodec + 'static,
218{
219 Arc::new(|bytes: &[u8]| Ok(StreamStateKind::Producer(Box::new(S::decode(bytes)?))))
220}
221
222#[cfg(feature = "stream-codec")]
227#[doc(hidden)]
228pub fn exchange_decoder<S>() -> crate::server::StateDecoder
229where
230 S: ExchangeState + crate::stream_codec::StreamStateCodec + 'static,
231{
232 Arc::new(|bytes: &[u8]| Ok(StreamStateKind::Exchange(Box::new(S::decode(bytes)?))))
233}
234
235pub(crate) fn empty_schema() -> SchemaRef {
236 Arc::new(Schema::empty())
237}
238
239pub use crate::server::CallContext;
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use arrow_array::Int64Array;
246 use arrow_schema::{DataType, Field};
247
248 fn batch(schema: SchemaRef, value: i64) -> RecordBatch {
249 RecordBatch::try_new(schema, vec![Arc::new(Int64Array::from(vec![value]))]).unwrap()
250 }
251
252 #[test]
253 fn collector_rejects_a_second_data_batch_as_protocol_error() {
254 let schema = Arc::new(Schema::new(vec![Field::new(
255 "value",
256 DataType::Int64,
257 false,
258 )]));
259 let mut out = OutputCollector::new(schema.clone(), true);
260
261 out.emit(batch(schema.clone(), 1)).unwrap();
262 let err = out
263 .emit_with_metadata(batch(schema, 2), Metadata::default())
264 .unwrap_err();
265
266 assert_eq!(err.error_type, "ProtocolError");
267 assert_eq!(out.items.len(), 1);
268 }
269
270 #[test]
271 fn collector_allows_logs_after_data() {
272 let schema = Arc::new(Schema::new(vec![Field::new(
273 "value",
274 DataType::Int64,
275 false,
276 )]));
277 let mut out = OutputCollector::new(schema.clone(), true);
278
279 out.emit(batch(schema, 1)).unwrap();
280 out.client_log(LogLevel::Info, "still allowed");
281
282 assert_eq!(out.items.len(), 2);
283 }
284}