1use nodedb_types::id::TxnId;
8
9use super::discriminants::*;
10use super::header::write_frame;
11use super::raft_rpc::RaftRpc;
12use crate::error::{ClusterError, Result};
13
14#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
21pub struct DescriptorVersionEntry {
22 pub collection: String,
23 pub version: u64,
24}
25
26#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
28pub struct ExecuteRequest {
29 pub plan_bytes: Vec<u8>,
31 pub tenant_id: u64,
33 pub database_id: u64,
36 pub deadline_remaining_ms: u64,
39 pub trace_id: [u8; 16],
41 pub descriptor_versions: Vec<DescriptorVersionEntry>,
43 pub txn_id: Option<TxnId>,
48}
49
50#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
52pub struct ExecuteResponse {
53 pub success: bool,
54 pub payloads: Vec<Vec<u8>>,
56 pub error: Option<TypedClusterError>,
57 pub watermark_lsn: u64,
61 pub read_version_lsn: u64,
67}
68
69#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
71pub enum TypedClusterError {
72 NotLeader {
73 group_id: u64,
74 leader_node_id: Option<u64>,
75 leader_addr: Option<String>,
76 term: u64,
77 },
78 DescriptorMismatch {
79 collection: String,
80 expected_version: u64,
81 actual_version: u64,
82 },
83 DeadlineExceeded {
84 elapsed_ms: u64,
85 },
86 Internal {
88 code: u32,
89 message: String,
90 },
91}
92
93#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
101pub struct ExecuteStreamChunk {
102 pub payload: Vec<u8>,
103 pub watermark_lsn: u64,
104}
105
106#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
112pub struct ExecuteStreamEnd {
113 pub error: Option<TypedClusterError>,
114}
115
116impl ExecuteResponse {
117 pub fn ok(payloads: Vec<Vec<u8>>, watermark_lsn: u64, read_version_lsn: u64) -> Self {
118 Self {
119 success: true,
120 payloads,
121 error: None,
122 watermark_lsn,
123 read_version_lsn,
124 }
125 }
126 pub fn err(error: TypedClusterError) -> Self {
127 Self {
128 success: false,
129 payloads: vec![],
130 error: Some(error),
131 watermark_lsn: 0,
132 read_version_lsn: 0,
133 }
134 }
135}
136
137macro_rules! to_bytes {
140 ($msg:expr) => {
141 rkyv::to_bytes::<rkyv::rancor::Error>($msg)
142 .map(|b| b.to_vec())
143 .map_err(|e| ClusterError::Codec {
144 detail: format!("rkyv serialize: {e}"),
145 })
146 };
147}
148
149macro_rules! from_bytes {
150 ($payload:expr, $T:ty, $name:expr) => {{
151 let mut aligned = rkyv::util::AlignedVec::<16>::with_capacity($payload.len());
152 aligned.extend_from_slice($payload);
153 rkyv::from_bytes::<$T, rkyv::rancor::Error>(&aligned).map_err(|e| ClusterError::Codec {
154 detail: format!("rkyv deserialize {}: {e}", $name),
155 })
156 }};
157}
158
159pub(super) fn encode_execute_req(msg: &ExecuteRequest, out: &mut Vec<u8>) -> Result<()> {
160 write_frame(RPC_EXECUTE_REQ, &to_bytes!(msg)?, out)
161}
162pub(super) fn encode_execute_resp(msg: &ExecuteResponse, out: &mut Vec<u8>) -> Result<()> {
163 write_frame(RPC_EXECUTE_RESP, &to_bytes!(msg)?, out)
164}
165
166pub(super) fn decode_execute_req(payload: &[u8]) -> Result<RaftRpc> {
167 Ok(RaftRpc::ExecuteRequest(from_bytes!(
168 payload,
169 ExecuteRequest,
170 "ExecuteRequest"
171 )?))
172}
173pub(super) fn decode_execute_resp(payload: &[u8]) -> Result<RaftRpc> {
174 Ok(RaftRpc::ExecuteResponse(from_bytes!(
175 payload,
176 ExecuteResponse,
177 "ExecuteResponse"
178 )?))
179}
180
181pub(super) fn encode_execute_stream_req(msg: &ExecuteRequest, out: &mut Vec<u8>) -> Result<()> {
182 write_frame(RPC_EXECUTE_STREAM_REQ, &to_bytes!(msg)?, out)
183}
184pub(super) fn encode_execute_stream_chunk(
185 msg: &ExecuteStreamChunk,
186 out: &mut Vec<u8>,
187) -> Result<()> {
188 write_frame(RPC_EXECUTE_STREAM_CHUNK, &to_bytes!(msg)?, out)
189}
190pub(super) fn encode_execute_stream_end(msg: &ExecuteStreamEnd, out: &mut Vec<u8>) -> Result<()> {
191 write_frame(RPC_EXECUTE_STREAM_END, &to_bytes!(msg)?, out)
192}
193
194pub(super) fn decode_execute_stream_req(payload: &[u8]) -> Result<RaftRpc> {
195 Ok(RaftRpc::ExecuteStreamRequest(from_bytes!(
196 payload,
197 ExecuteRequest,
198 "ExecuteStreamRequest"
199 )?))
200}
201pub(super) fn decode_execute_stream_chunk(payload: &[u8]) -> Result<RaftRpc> {
202 Ok(RaftRpc::ExecuteStreamChunk(from_bytes!(
203 payload,
204 ExecuteStreamChunk,
205 "ExecuteStreamChunk"
206 )?))
207}
208pub(super) fn decode_execute_stream_end(payload: &[u8]) -> Result<RaftRpc> {
209 Ok(RaftRpc::ExecuteStreamEnd(from_bytes!(
210 payload,
211 ExecuteStreamEnd,
212 "ExecuteStreamEnd"
213 )?))
214}
215
216pub const PLAN_DECODE_FAILED: u32 = 0x_CE00_0001;
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 fn roundtrip_req(req: ExecuteRequest) -> ExecuteRequest {
224 let rpc = RaftRpc::ExecuteRequest(req);
225 let encoded = super::super::encode(&rpc).unwrap();
226 match super::super::decode(&encoded).unwrap() {
227 RaftRpc::ExecuteRequest(r) => r,
228 other => panic!("expected ExecuteRequest, got {other:?}"),
229 }
230 }
231
232 fn roundtrip_resp(resp: ExecuteResponse) -> ExecuteResponse {
233 let rpc = RaftRpc::ExecuteResponse(resp);
234 let encoded = super::super::encode(&rpc).unwrap();
235 match super::super::decode(&encoded).unwrap() {
236 RaftRpc::ExecuteResponse(r) => r,
237 other => panic!("expected ExecuteResponse, got {other:?}"),
238 }
239 }
240
241 #[test]
242 fn roundtrip_execute_request_basic() {
243 let req = ExecuteRequest {
244 plan_bytes: b"msgpack-plan-bytes".to_vec(),
245 tenant_id: 7,
246 database_id: 0,
247 deadline_remaining_ms: 5000,
248 trace_id: [
249 0xDE, 0xAD, 0xBE, 0xEF, 0x12, 0x34, 0x56, 0x78, 0xDE, 0xAD, 0xBE, 0xEF, 0x12, 0x34,
250 0x56, 0x78,
251 ],
252 descriptor_versions: vec![
253 DescriptorVersionEntry {
254 collection: "orders".into(),
255 version: 42,
256 },
257 DescriptorVersionEntry {
258 collection: "users".into(),
259 version: 1,
260 },
261 ],
262 txn_id: None,
263 };
264 let decoded = roundtrip_req(req.clone());
265 assert_eq!(decoded.plan_bytes, req.plan_bytes);
266 assert_eq!(decoded.tenant_id, 7);
267 assert_eq!(decoded.deadline_remaining_ms, 5000);
268 assert_eq!(
269 decoded.trace_id, req.trace_id,
270 "trace_id roundtrips correctly"
271 );
272 assert_eq!(decoded.descriptor_versions.len(), 2);
273 assert_eq!(decoded.descriptor_versions[0].collection, "orders");
274 assert_eq!(decoded.descriptor_versions[0].version, 42);
275 }
276
277 #[test]
278 fn roundtrip_execute_request_empty_descriptors() {
279 let req = ExecuteRequest {
280 plan_bytes: vec![0xAB, 0xCD],
281 tenant_id: 0,
282 database_id: 0,
283 deadline_remaining_ms: 1000,
284 trace_id: [0u8; 16],
285 descriptor_versions: vec![],
286 txn_id: None,
287 };
288 let decoded = roundtrip_req(req);
289 assert!(decoded.descriptor_versions.is_empty());
290 }
291
292 #[test]
293 fn roundtrip_execute_response_success() {
294 let resp = ExecuteResponse::ok(
295 vec![b"row1".to_vec(), b"row2".to_vec()],
296 0xCAFE_1234,
297 0xBEEF_5678,
298 );
299 let decoded = roundtrip_resp(resp);
300 assert!(decoded.success);
301 assert_eq!(decoded.payloads.len(), 2);
302 assert_eq!(decoded.payloads[0], b"row1");
303 assert!(decoded.error.is_none());
304 assert_eq!(
305 decoded.watermark_lsn, 0xCAFE_1234,
306 "read watermark roundtrips on the response body"
307 );
308 assert_eq!(
309 decoded.read_version_lsn, 0xBEEF_5678,
310 "per-collection read-version LSN roundtrips distinct from the watermark"
311 );
312 }
313
314 #[test]
315 fn roundtrip_execute_response_not_leader() {
316 let resp = ExecuteResponse::err(TypedClusterError::NotLeader {
317 group_id: 3,
318 leader_node_id: Some(1),
319 leader_addr: Some("10.0.0.1:9400".into()),
320 term: 7,
321 });
322 let decoded = roundtrip_resp(resp);
323 assert!(!decoded.success);
324 assert_eq!(
325 decoded.watermark_lsn, 0,
326 "error responses carry no watermark"
327 );
328 assert_eq!(
329 decoded.read_version_lsn, 0,
330 "error responses carry no read-version LSN"
331 );
332 match decoded.error {
333 Some(TypedClusterError::NotLeader {
334 group_id,
335 leader_node_id,
336 leader_addr,
337 term,
338 }) => {
339 assert_eq!(group_id, 3);
340 assert_eq!(leader_node_id, Some(1));
341 assert_eq!(leader_addr.as_deref(), Some("10.0.0.1:9400"));
342 assert_eq!(term, 7);
343 }
344 other => panic!("expected NotLeader, got {other:?}"),
345 }
346 }
347
348 #[test]
349 fn roundtrip_execute_response_descriptor_mismatch() {
350 let resp = ExecuteResponse::err(TypedClusterError::DescriptorMismatch {
351 collection: "orders".into(),
352 expected_version: 5,
353 actual_version: 6,
354 });
355 let decoded = roundtrip_resp(resp);
356 match decoded.error {
357 Some(TypedClusterError::DescriptorMismatch {
358 collection,
359 expected_version,
360 actual_version,
361 }) => {
362 assert_eq!(collection, "orders");
363 assert_eq!(expected_version, 5);
364 assert_eq!(actual_version, 6);
365 }
366 other => panic!("expected DescriptorMismatch, got {other:?}"),
367 }
368 }
369
370 #[test]
371 fn roundtrip_execute_response_deadline_exceeded() {
372 let resp = ExecuteResponse::err(TypedClusterError::DeadlineExceeded { elapsed_ms: 3000 });
373 let decoded = roundtrip_resp(resp);
374 match decoded.error {
375 Some(TypedClusterError::DeadlineExceeded { elapsed_ms }) => {
376 assert_eq!(elapsed_ms, 3000)
377 }
378 other => panic!("expected DeadlineExceeded, got {other:?}"),
379 }
380 }
381
382 #[test]
383 fn roundtrip_execute_response_internal_error() {
384 let resp = ExecuteResponse::err(TypedClusterError::Internal {
385 code: PLAN_DECODE_FAILED,
386 message: "failed to decode plan".into(),
387 });
388 let decoded = roundtrip_resp(resp);
389 match decoded.error {
390 Some(TypedClusterError::Internal { code, message }) => {
391 assert_eq!(code, PLAN_DECODE_FAILED);
392 assert!(message.contains("plan"));
393 }
394 other => panic!("expected Internal, got {other:?}"),
395 }
396 }
397
398 fn roundtrip_stream_chunk(chunk: ExecuteStreamChunk) -> ExecuteStreamChunk {
399 let rpc = RaftRpc::ExecuteStreamChunk(chunk);
400 let encoded = super::super::encode(&rpc).unwrap();
401 match super::super::decode(&encoded).unwrap() {
402 RaftRpc::ExecuteStreamChunk(c) => c,
403 other => panic!("expected ExecuteStreamChunk, got {other:?}"),
404 }
405 }
406
407 fn roundtrip_stream_end(end: ExecuteStreamEnd) -> ExecuteStreamEnd {
408 let rpc = RaftRpc::ExecuteStreamEnd(end);
409 let encoded = super::super::encode(&rpc).unwrap();
410 match super::super::decode(&encoded).unwrap() {
411 RaftRpc::ExecuteStreamEnd(e) => e,
412 other => panic!("expected ExecuteStreamEnd, got {other:?}"),
413 }
414 }
415
416 #[test]
417 fn roundtrip_execute_stream_request_reuses_execute_request_body() {
418 let req = ExecuteRequest {
419 plan_bytes: b"streaming-plan".to_vec(),
420 tenant_id: 11,
421 database_id: 2,
422 deadline_remaining_ms: 4242,
423 trace_id: [9u8; 16],
424 descriptor_versions: vec![DescriptorVersionEntry {
425 collection: "wide".into(),
426 version: 3,
427 }],
428 txn_id: None,
429 };
430 let rpc = RaftRpc::ExecuteStreamRequest(req.clone());
431 let encoded = super::super::encode(&rpc).unwrap();
432 match super::super::decode(&encoded).unwrap() {
433 RaftRpc::ExecuteStreamRequest(r) => {
434 assert_eq!(r.plan_bytes, req.plan_bytes);
435 assert_eq!(r.tenant_id, 11);
436 assert_eq!(r.database_id, 2);
437 assert_eq!(r.deadline_remaining_ms, 4242);
438 assert_eq!(r.trace_id, req.trace_id);
439 assert_eq!(r.descriptor_versions.len(), 1);
440 assert_eq!(r.descriptor_versions[0].collection, "wide");
441 assert_eq!(r.descriptor_versions[0].version, 3);
442 }
443 other => panic!("expected ExecuteStreamRequest, got {other:?}"),
444 }
445 }
446
447 #[test]
448 fn roundtrip_execute_stream_chunk_payload_and_lsn() {
449 let chunk = ExecuteStreamChunk {
450 payload: vec![0x91, 0x01, 0x02, 0x03],
451 watermark_lsn: 0xDEAD_BEEF,
452 };
453 let decoded = roundtrip_stream_chunk(chunk.clone());
454 assert_eq!(decoded.payload, chunk.payload);
455 assert_eq!(decoded.watermark_lsn, 0xDEAD_BEEF);
456 }
457
458 #[test]
459 fn roundtrip_execute_stream_end_clean_eof() {
460 let decoded = roundtrip_stream_end(ExecuteStreamEnd { error: None });
461 assert!(decoded.error.is_none());
462 }
463
464 #[test]
465 fn roundtrip_execute_stream_end_terminal_error() {
466 let decoded = roundtrip_stream_end(ExecuteStreamEnd {
467 error: Some(TypedClusterError::Internal {
468 code: PLAN_DECODE_FAILED,
469 message: "stream failed mid-flight".into(),
470 }),
471 });
472 match decoded.error {
473 Some(TypedClusterError::Internal { code, message }) => {
474 assert_eq!(code, PLAN_DECODE_FAILED);
475 assert!(message.contains("stream failed"));
476 }
477 other => panic!("expected Internal, got {other:?}"),
478 }
479 }
480
481 #[test]
482 fn roundtrip_execute_response_not_leader_no_hint() {
483 let resp = ExecuteResponse::err(TypedClusterError::NotLeader {
484 group_id: 0,
485 leader_node_id: None,
486 leader_addr: None,
487 term: 0,
488 });
489 let decoded = roundtrip_resp(resp);
490 match decoded.error {
491 Some(TypedClusterError::NotLeader {
492 leader_node_id,
493 leader_addr,
494 ..
495 }) => {
496 assert!(leader_node_id.is_none());
497 assert!(leader_addr.is_none());
498 }
499 other => panic!("expected NotLeader, got {other:?}"),
500 }
501 }
502}