1use arrow::array::{
8 Array, BooleanArray, Float32Array, Float64Array, Int16Array, Int32Array, Int64Array, Int8Array,
9 NullArray, StringArray, UInt16Array, UInt32Array, UInt64Array, UInt8Array,
10};
11use arrow::ipc::reader::FileReader;
12use arrow::ipc::writer::FileWriter;
13use arrow::record_batch::RecordBatch;
14use serde_json::{json, Map, Value};
15
16use crate::error::{boxed_query_metadata, KitError, QueryExecutionOutcome, Result};
17use crate::SqlOutputLimits;
18
19pub fn read_arrow_ipc(bytes: &[u8]) -> Result<Vec<RecordBatch>> {
22 if bytes.is_empty() {
23 return Ok(Vec::new());
24 }
25 let cursor = std::io::Cursor::new(bytes);
26 let reader = FileReader::try_new(cursor, None)
27 .map_err(|e| KitError::Storage(format!("arrow ipc decode: {e}")))?;
28 reader
29 .into_iter()
30 .collect::<std::result::Result<Vec<_>, _>>()
31 .map_err(|e| KitError::Storage(format!("arrow ipc decode: {e}")))
32}
33
34pub fn batches_to_ipc(batches: &[RecordBatch]) -> Result<Vec<u8>> {
38 batches_to_ipc_with_checkpoint(batches, || Ok(()))
39}
40
41#[doc(hidden)]
42pub fn batches_to_ipc_controlled(
43 batches: &[RecordBatch],
44 query: &mongreldb_query::RegisteredSqlQuery,
45) -> Result<Vec<u8>> {
46 batches_to_ipc_controlled_with_limits(batches, query, SqlOutputLimits::default())
47}
48
49#[doc(hidden)]
50pub fn batches_to_ipc_controlled_with_limits(
51 batches: &[RecordBatch],
52 query: &mongreldb_query::RegisteredSqlQuery,
53 limits: SqlOutputLimits,
54) -> Result<Vec<u8>> {
55 let schema = batches
56 .first()
57 .map(|batch| batch.schema())
58 .unwrap_or_else(|| std::sync::Arc::new(arrow::datatypes::Schema::empty()));
59 let exceeded = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
60 let mut output = LimitedIpcOutput {
61 bytes: Vec::new(),
62 max_bytes: limits.max_bytes,
63 exceeded: std::sync::Arc::clone(&exceeded),
64 };
65 let mut rows = 0usize;
66 {
67 let mut writer = match FileWriter::try_new(&mut output, &schema) {
68 Ok(writer) => writer,
69 Err(error) => {
70 return serialization_or_limit_error(query, limits, &exceeded, error.to_string())
71 }
72 };
73 for batch in batches {
74 for offset in (0..batch.num_rows()).step_by(256) {
75 query.checkpoint().map_err(KitError::from)?;
76 let length = 256.min(batch.num_rows() - offset);
77 rows = rows.saturating_add(length);
78 if rows > limits.max_rows {
79 return result_limit_error(query, limits);
80 }
81 if let Err(error) = writer.write(&batch.slice(offset, length)) {
82 return serialization_or_limit_error(
83 query,
84 limits,
85 &exceeded,
86 error.to_string(),
87 );
88 }
89 }
90 }
91 if let Err(error) = writer.finish() {
92 return serialization_or_limit_error(query, limits, &exceeded, error.to_string());
93 }
94 }
95 Ok(output.bytes)
96}
97
98fn batches_to_ipc_with_checkpoint(
99 batches: &[RecordBatch],
100 mut checkpoint: impl FnMut() -> Result<()>,
101) -> Result<Vec<u8>> {
102 let schema = batches
103 .first()
104 .map(|b| b.schema())
105 .unwrap_or_else(|| std::sync::Arc::new(arrow::datatypes::Schema::empty()));
106 let mut out = Vec::new();
107 let mut writer =
108 FileWriter::try_new(&mut out, &schema).map_err(|e| KitError::Storage(e.to_string()))?;
109 for batch in batches {
110 for offset in (0..batch.num_rows()).step_by(256) {
111 checkpoint()?;
112 let length = 256.min(batch.num_rows() - offset);
113 writer
114 .write(&batch.slice(offset, length))
115 .map_err(|e| KitError::Storage(format!("arrow ipc encode: {e}")))?;
116 }
117 }
118 writer
119 .finish()
120 .map_err(|e| KitError::Storage(format!("arrow ipc encode: {e}")))?;
121 Ok(out)
122}
123
124pub fn batch_to_rows(b: &RecordBatch) -> Result<Vec<Map<String, Value>>> {
126 let schema = b.schema();
127 let mut rows = Vec::with_capacity(b.num_rows());
128 for r in 0..b.num_rows() {
129 let mut row = Map::new();
130 for (c, field) in schema.fields().iter().enumerate() {
131 let name = field.name();
132 let arr = b.column(c);
133 row.insert(name.clone(), cell_value(arr.as_ref(), r));
134 }
135 rows.push(row);
136 }
137 Ok(rows)
138}
139
140pub fn batches_to_rows(batches: &[RecordBatch]) -> Result<Vec<Map<String, Value>>> {
142 batches_to_rows_with_checkpoint(batches, || Ok(()))
143}
144
145#[doc(hidden)]
146pub fn batches_to_rows_controlled(
147 batches: &[RecordBatch],
148 query: &mongreldb_query::RegisteredSqlQuery,
149) -> Result<Vec<Map<String, Value>>> {
150 batches_to_rows_controlled_with_limits(batches, query, SqlOutputLimits::default())
151}
152
153#[doc(hidden)]
154pub fn batches_to_rows_controlled_with_limits(
155 batches: &[RecordBatch],
156 query: &mongreldb_query::RegisteredSqlQuery,
157 limits: SqlOutputLimits,
158) -> Result<Vec<Map<String, Value>>> {
159 let mut rows = Vec::new();
160 let mut bytes = 2usize;
161 for batch in batches {
162 let schema = batch.schema();
163 for row_index in 0..batch.num_rows() {
164 if row_index % 256 == 0 {
165 query.checkpoint().map_err(KitError::from)?;
166 }
167 if rows.len() >= limits.max_rows {
168 return result_limit_error(query, limits);
169 }
170 let mut row = Map::new();
171 for (column_index, field) in schema.fields().iter().enumerate() {
172 row.insert(
173 field.name().clone(),
174 cell_value(batch.column(column_index).as_ref(), row_index),
175 );
176 }
177 let row_bytes = serde_json::to_vec(&row)
178 .map_err(|error| controlled_serialization_error(query, error.to_string()))?
179 .len();
180 bytes = bytes
181 .saturating_add(row_bytes)
182 .saturating_add(usize::from(!rows.is_empty()));
183 if bytes > limits.max_bytes {
184 return result_limit_error(query, limits);
185 }
186 rows.push(row);
187 }
188 }
189 Ok(rows)
190}
191
192fn result_limit_error<T>(
193 query: &mongreldb_query::RegisteredSqlQuery,
194 limits: SqlOutputLimits,
195) -> Result<T> {
196 Err(controlled_result_limit_error(query, limits))
197}
198
199#[doc(hidden)]
200pub fn controlled_result_limit_error(
201 query: &mongreldb_query::RegisteredSqlQuery,
202 limits: SqlOutputLimits,
203) -> KitError {
204 let status = query.status();
205 KitError::ResultLimitExceeded {
206 query_id: Some(query.id().to_string().into_boxed_str()),
207 max_rows: Some(Box::new(limits.max_rows)),
208 max_bytes: Some(Box::new(limits.max_bytes)),
209 outcome: Box::new(QueryExecutionOutcome {
210 committed: status.durable_outcome.committed,
211 committed_statements: Some(status.durable_outcome.committed_statements),
212 last_commit_epoch: status.durable_outcome.last_commit_epoch,
213 first_commit_statement_index: status.durable_outcome.first_commit_statement_index,
214 last_commit_statement_index: status.durable_outcome.last_commit_statement_index,
215 completed_statements: status.completed_statements,
216 statement_index: status.statement_index,
217 }),
218 message: format!(
219 "SQL result exceeds {} rows or {} bytes",
220 limits.max_rows, limits.max_bytes
221 )
222 .into_boxed_str(),
223 metadata: boxed_query_metadata(None, None, Some(false), Some("serializing")),
224 }
225}
226
227fn serialization_or_limit_error<T>(
228 query: &mongreldb_query::RegisteredSqlQuery,
229 limits: SqlOutputLimits,
230 exceeded: &std::sync::atomic::AtomicBool,
231 message: String,
232) -> Result<T> {
233 if exceeded.load(std::sync::atomic::Ordering::Acquire) {
234 return result_limit_error(query, limits);
235 }
236 Err(controlled_serialization_error(query, message))
237}
238
239#[doc(hidden)]
240pub fn controlled_serialization_error(
241 query: &mongreldb_query::RegisteredSqlQuery,
242 message: String,
243) -> KitError {
244 let status = query.status();
245 KitError::SerializationFailed {
246 query_id: Some(query.id().to_string()),
247 outcome: Box::new(QueryExecutionOutcome {
248 committed: status.durable_outcome.committed,
249 committed_statements: Some(status.durable_outcome.committed_statements),
250 last_commit_epoch: status.durable_outcome.last_commit_epoch,
251 first_commit_statement_index: status.durable_outcome.first_commit_statement_index,
252 last_commit_statement_index: status.durable_outcome.last_commit_statement_index,
253 completed_statements: status.completed_statements,
254 statement_index: status.statement_index,
255 }),
256 message: message.into_boxed_str(),
257 metadata: boxed_query_metadata(None, None, Some(false), Some("serializing")),
258 }
259}
260
261struct LimitedIpcOutput {
262 bytes: Vec<u8>,
263 max_bytes: usize,
264 exceeded: std::sync::Arc<std::sync::atomic::AtomicBool>,
265}
266
267impl std::io::Write for LimitedIpcOutput {
268 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
269 if self.bytes.len().saturating_add(bytes.len()) > self.max_bytes {
270 self.exceeded
271 .store(true, std::sync::atomic::Ordering::Release);
272 return Err(std::io::Error::other("SQL result byte limit exceeded"));
273 }
274 self.bytes.extend_from_slice(bytes);
275 Ok(bytes.len())
276 }
277
278 fn flush(&mut self) -> std::io::Result<()> {
279 Ok(())
280 }
281}
282
283fn batches_to_rows_with_checkpoint(
284 batches: &[RecordBatch],
285 mut checkpoint: impl FnMut() -> Result<()>,
286) -> Result<Vec<Map<String, Value>>> {
287 let mut rows = Vec::new();
288 for batch in batches {
289 let schema = batch.schema();
290 for row_index in 0..batch.num_rows() {
291 if row_index % 256 == 0 {
292 checkpoint()?;
293 }
294 let mut row = Map::new();
295 for (column_index, field) in schema.fields().iter().enumerate() {
296 row.insert(
297 field.name().clone(),
298 cell_value(batch.column(column_index).as_ref(), row_index),
299 );
300 }
301 rows.push(row);
302 }
303 }
304 Ok(rows)
305}
306
307fn cell_value(arr: &dyn Array, r: usize) -> Value {
308 if arr.is_null(r) {
309 return Value::Null;
310 }
311 if let Some(a) = arr.as_any().downcast_ref::<Int64Array>() {
313 return json!(a.value(r));
314 }
315 if let Some(a) = arr.as_any().downcast_ref::<Int32Array>() {
316 return json!(a.value(r));
317 }
318 if let Some(a) = arr.as_any().downcast_ref::<Int16Array>() {
319 return json!(a.value(r));
320 }
321 if let Some(a) = arr.as_any().downcast_ref::<Int8Array>() {
322 return json!(a.value(r));
323 }
324 if let Some(a) = arr.as_any().downcast_ref::<UInt64Array>() {
326 return json!(a.value(r) as i64);
327 }
328 if let Some(a) = arr.as_any().downcast_ref::<UInt32Array>() {
329 return json!(a.value(r) as i64);
330 }
331 if let Some(a) = arr.as_any().downcast_ref::<UInt16Array>() {
332 return json!(a.value(r) as i64);
333 }
334 if let Some(a) = arr.as_any().downcast_ref::<UInt8Array>() {
335 return json!(a.value(r) as i64);
336 }
337 if let Some(a) = arr.as_any().downcast_ref::<Float64Array>() {
339 return serde_json::Number::from_f64(a.value(r))
340 .map(Value::Number)
341 .unwrap_or(Value::Null);
342 }
343 if let Some(a) = arr.as_any().downcast_ref::<Float32Array>() {
344 return serde_json::Number::from_f64(a.value(r) as f64)
345 .map(Value::Number)
346 .unwrap_or(Value::Null);
347 }
348 if let Some(a) = arr.as_any().downcast_ref::<BooleanArray>() {
350 return Value::Bool(a.value(r));
351 }
352 if let Some(a) = arr.as_any().downcast_ref::<StringArray>() {
354 return Value::String(a.value(r).to_string());
355 }
356 if arr.as_any().downcast_ref::<NullArray>().is_some() {
357 return Value::Null;
358 }
359 Value::String(format!("{arr:?}"))
361}
362
363#[cfg(test)]
364mod tests {
365 use super::{batches_to_ipc_controlled_with_limits, batches_to_rows_controlled_with_limits};
366 use crate::{KitError, SqlOutputLimits};
367 use arrow::array::Int64Array;
368 use arrow::record_batch::RecordBatch;
369 use mongreldb_query::{SqlQueryOptions, SqlQueryRegistry};
370 use std::sync::Arc;
371
372 fn batch() -> RecordBatch {
373 RecordBatch::try_from_iter([(
374 "value",
375 Arc::new(Int64Array::from(vec![1, 2])) as arrow::array::ArrayRef,
376 )])
377 .unwrap()
378 }
379
380 #[test]
381 fn controlled_converters_enforce_row_and_byte_limits() {
382 for convert in ["arrow", "rows"] {
383 for limits in [
384 SqlOutputLimits {
385 max_rows: 1,
386 max_bytes: 1_024,
387 },
388 SqlOutputLimits {
389 max_rows: 10,
390 max_bytes: 1,
391 },
392 ] {
393 let registry = Arc::new(SqlQueryRegistry::default());
394 let query = registry.register(SqlQueryOptions::default()).unwrap();
395 query.record_commit(2, 17);
396 let batch = batch();
397 let error = if convert == "arrow" {
398 batches_to_ipc_controlled_with_limits(
399 std::slice::from_ref(&batch),
400 &query,
401 limits,
402 )
403 .map(|_| ())
404 .unwrap_err()
405 } else {
406 batches_to_rows_controlled_with_limits(
407 std::slice::from_ref(&batch),
408 &query,
409 limits,
410 )
411 .map(|_| ())
412 .unwrap_err()
413 };
414 assert!(matches!(
415 error,
416 KitError::ResultLimitExceeded {
417 outcome,
418 ..
419 } if outcome.committed
420 && outcome.committed_statements == Some(1)
421 && outcome.last_commit_epoch == Some(17)
422 && outcome.first_commit_statement_index == Some(2)
423 && outcome.last_commit_statement_index == Some(2)
424 ));
425 query.fail_result_limit();
426 assert_eq!(
427 query.status().terminal_error.unwrap().code,
428 "RESULT_LIMIT_EXCEEDED"
429 );
430 }
431 }
432 }
433}