micromegas_analytics/lakehouse/
get_payload_function.rs1use super::audience_guard::{AudienceGuard, IdKind};
2use async_trait::async_trait;
3use datafusion::{
4 arrow::{
5 array::{Array, BinaryBuilder, StringArray},
6 datatypes::DataType,
7 },
8 common::{internal_err, not_impl_err, plan_err},
9 error::DataFusionError,
10 logical_expr::{
11 ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
12 async_udf::AsyncScalarUDFImpl,
13 },
14};
15use futures::stream::StreamExt;
16use micromegas_ingestion::data_lake_connection::DataLakeConnection;
17use micromegas_tracing::prelude::*;
18use std::collections::HashSet;
19use std::sync::Arc;
20use uuid::Uuid;
21
22#[derive(Debug)]
24pub struct GetPayload {
25 signature: Signature,
26 lake: Arc<DataLakeConnection>,
27 guard: Arc<AudienceGuard>,
28}
29
30impl PartialEq for GetPayload {
31 fn eq(&self, other: &Self) -> bool {
32 self.signature == other.signature
33 }
34}
35
36impl Eq for GetPayload {}
37
38impl std::hash::Hash for GetPayload {
39 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
40 self.signature.hash(state);
41 }
42}
43
44impl GetPayload {
45 pub fn new(lake: Arc<DataLakeConnection>, guard: Arc<AudienceGuard>) -> Self {
46 Self {
47 signature: Signature::exact(
48 vec![DataType::Utf8, DataType::Utf8, DataType::Utf8],
49 Volatility::Immutable,
50 ),
51 lake,
52 guard,
53 }
54 }
55}
56
57impl ScalarUDFImpl for GetPayload {
58 fn name(&self) -> &str {
59 "get_payload"
60 }
61
62 fn signature(&self) -> &Signature {
63 &self.signature
64 }
65
66 fn return_type(&self, _arg_types: &[DataType]) -> datafusion::error::Result<DataType> {
67 Ok(DataType::Binary)
68 }
69
70 fn invoke_with_args(
71 &self,
72 _args: datafusion::logical_expr::ScalarFunctionArgs,
73 ) -> datafusion::error::Result<ColumnarValue> {
74 not_impl_err!("GetPayload can only be called from async contexts")
75 }
76}
77
78#[async_trait]
79impl AsyncScalarUDFImpl for GetPayload {
80 async fn invoke_async_with_args(
81 &self,
82 args: ScalarFunctionArgs,
83 ) -> datafusion::error::Result<ColumnarValue> {
84 let args = ColumnarValue::values_to_arrays(&args.args)?;
85 if args.len() != 3 {
86 return internal_err!("wrong number of arguments to get_payload()");
87 }
88 let process_ids = args[0]
89 .as_any()
90 .downcast_ref::<StringArray>()
91 .ok_or_else(|| {
92 DataFusionError::Internal("downcasting process_ids in GetPayload".into())
93 })?
94 .clone();
95 let stream_ids = args[1]
96 .as_any()
97 .downcast_ref::<StringArray>()
98 .ok_or_else(|| {
99 DataFusionError::Internal("downcasting stream_ids in GetPayload".into())
100 })?
101 .clone();
102 let block_ids = args[2]
103 .as_any()
104 .downcast_ref::<StringArray>()
105 .ok_or_else(|| DataFusionError::Internal("downcasting block_ids in GetPayload".into()))?
106 .clone();
107
108 let mut distinct_process_ids: Vec<Uuid> = Vec::new();
119 let mut seen: HashSet<Uuid> = HashSet::new();
120 for i in 0..process_ids.len() {
121 let raw = process_ids.value(i);
122 let Ok(id) = Uuid::parse_str(raw) else {
123 return plan_err!("get_payload: '{raw}' is not a valid process id");
124 };
125 if seen.insert(id) {
126 distinct_process_ids.push(id);
127 }
128 }
129 let readable = self
130 .guard
131 .readable_ids(&distinct_process_ids, IdKind::Process)
132 .await?;
133 for id in &distinct_process_ids {
134 if !readable.contains(id) {
135 return plan_err!("get_payload: process '{id}' not found or not accessible");
136 }
137 }
138
139 let lake = self.lake.clone();
140 let mut stream = futures::stream::iter(0..process_ids.len())
141 .map(|i| {
142 let process_id = process_ids.value(i);
143 let stream_id = stream_ids.value(i);
144 let block_id = block_ids.value(i);
145 let obj_path = format!("blobs/{process_id}/{stream_id}/{block_id}");
146 let lake = lake.clone();
147 spawn_with_context(async move { lake.blob_storage.read_blob(&obj_path).await })
148 })
149 .buffered(10);
150 let mut result_builder = BinaryBuilder::with_capacity(block_ids.len(), 1024 * 1024);
151 while let Some(res) = stream.next().await {
152 result_builder.append_value(
153 res.map_err(|e| {
154 DataFusionError::Internal(format!("error downloading payload: {e:?}"))
155 })?
156 .map_err(|e| {
157 DataFusionError::Internal(format!("error downloading payload: {e:?}"))
158 })?,
159 );
160 }
161 Ok(ColumnarValue::Array(Arc::new(result_builder.finish())))
162 }
163}