1#![allow(clippy::result_large_err)]
7
8pub mod proto {
33 #![allow(clippy::all)]
38 #![allow(clippy::doc_lazy_continuation)]
39
40 tonic::include_proto!("statelet.v1");
41}
42
43pub mod cdc;
44pub use cdc::{
45 CheckpointStore, CommittedChange, ConsumeError, FeedItem, FeedStream, FeedTransport,
46 FileCheckpointStore, SubscribeCommittedOptions,
47};
48
49use proto::statelet_client::StateletClient as GrpcClient;
50use tonic::transport::Channel;
51
52#[derive(Debug, Clone)]
54pub struct VectorSearchResult {
55 pub id: u64,
56 pub distance: f32,
57 pub group_key: String,
62}
63
64#[derive(Debug, Clone, Default)]
69pub struct GroupSpec {
70 pub field: String,
72 pub group_size: u32,
74 pub groups: u32,
76 pub overfetch: u32,
78 pub missing_as_own: bool,
81}
82
83#[derive(Debug, Clone)]
85pub struct VectorIndexConfig {
86 pub dim: u32,
87 pub metric: i32, pub m: u32,
89 pub m_max0: u32,
90 pub ef_construction: u32,
91 pub ef_search: u32,
92}
93
94impl Default for VectorIndexConfig {
95 fn default() -> Self {
96 Self {
97 dim: 128,
98 metric: 0,
99 m: 16,
100 m_max0: 0,
101 ef_construction: 200,
102 ef_search: 64,
103 }
104 }
105}
106
107pub enum WriteOp {
109 Put {
110 cf: u32,
111 key: Vec<u8>,
112 value: Vec<u8>,
113 },
114 Delete {
115 cf: u32,
116 key: Vec<u8>,
117 },
118 Merge {
119 cf: u32,
120 key: Vec<u8>,
121 value: Vec<u8>,
122 },
123}
124
125#[derive(Debug, Clone, Default)]
129pub struct GraphQueryOptions {
130 pub graph_name: String,
132 pub max_rows: u32,
135 pub as_of: u64,
138 pub tx_as_of: u64,
140}
141
142#[derive(Debug, Clone, PartialEq)]
145pub enum GraphValue {
146 Null,
147 Int(i64),
148 Double(f64),
149 Str(String),
150 Bool(bool),
151 Json(Vec<u8>),
152}
153
154impl GraphValue {
155 fn from_proto(value: proto::GraphQueryValue) -> Self {
158 use proto::graph_query_value::Kind;
159 match Kind::try_from(value.kind) {
160 Ok(Kind::Int) => GraphValue::Int(value.int_value),
161 Ok(Kind::Double) => GraphValue::Double(value.dbl_value),
162 Ok(Kind::String) => GraphValue::Str(value.str_value),
163 Ok(Kind::Bool) => GraphValue::Bool(value.bool_value),
164 Ok(Kind::Json) => GraphValue::Json(value.json_value),
165 Ok(Kind::Null) | Err(_) => GraphValue::Null,
166 }
167 }
168}
169
170#[derive(Debug, Clone, Default)]
175pub struct GraphQueryResult {
176 pub columns: Vec<String>,
178 pub rows: Vec<Vec<GraphValue>>,
180 pub warnings: Vec<String>,
182}
183
184pub struct StateletClient {
186 inner: GrpcClient<Channel>,
187 default_cf: u32,
188}
189
190impl StateletClient {
191 pub async fn connect(addr: &str) -> Result<Self, tonic::transport::Error> {
193 let inner = GrpcClient::connect(addr.to_string()).await?;
194 Ok(Self {
195 inner,
196 default_cf: 0,
197 })
198 }
199
200 pub fn set_default_cf(&mut self, cf: u32) {
202 self.default_cf = cf;
203 }
204
205 pub async fn ping(&mut self) -> Result<String, tonic::Status> {
209 let resp = self.inner.ping(proto::PingRequest {}).await?;
210 Ok(resp.into_inner().message)
211 }
212
213 pub async fn put(
215 &mut self,
216 key: &[u8],
217 value: &[u8],
218 cf: Option<u32>,
219 ) -> Result<(), tonic::Status> {
220 self.inner
221 .put(proto::PutRequest {
222 cf: cf.unwrap_or(self.default_cf),
223 key: key.to_vec(),
224 value: value.to_vec(),
225 ..Default::default()
226 })
227 .await?;
228 Ok(())
229 }
230
231 pub async fn get(
233 &mut self,
234 key: &[u8],
235 cf: Option<u32>,
236 ) -> Result<Option<Vec<u8>>, tonic::Status> {
237 let resp = self
238 .inner
239 .get(proto::GetRequest {
240 cf: cf.unwrap_or(self.default_cf),
241 key: key.to_vec(),
242 ..Default::default()
243 })
244 .await?
245 .into_inner();
246 Ok(if resp.found { Some(resp.value) } else { None })
247 }
248
249 pub async fn delete(&mut self, key: &[u8], cf: Option<u32>) -> Result<(), tonic::Status> {
251 self.inner
252 .delete(proto::DeleteRequest {
253 cf: cf.unwrap_or(self.default_cf),
254 key: key.to_vec(),
255 ..Default::default()
256 })
257 .await?;
258 Ok(())
259 }
260
261 pub async fn merge(
263 &mut self,
264 key: &[u8],
265 value: &[u8],
266 cf: Option<u32>,
267 ) -> Result<(), tonic::Status> {
268 self.inner
269 .merge(proto::MergeRequest {
270 cf: cf.unwrap_or(self.default_cf),
271 key: key.to_vec(),
272 value: value.to_vec(),
273 ..Default::default()
274 })
275 .await?;
276 Ok(())
277 }
278
279 pub async fn batch_write(&mut self, ops: Vec<WriteOp>) -> Result<(), tonic::Status> {
281 let entries = ops
282 .into_iter()
283 .map(|op| match op {
284 WriteOp::Put { cf, key, value } => proto::WriteEntry {
285 cf,
286 op: proto::WriteOp::Put as i32,
287 key,
288 value,
289 ..Default::default()
290 },
291 WriteOp::Delete { cf, key } => proto::WriteEntry {
292 cf,
293 op: proto::WriteOp::Delete as i32,
294 key,
295 value: vec![],
296 ..Default::default()
297 },
298 WriteOp::Merge { cf, key, value } => proto::WriteEntry {
299 cf,
300 op: proto::WriteOp::Merge as i32,
301 key,
302 value,
303 ..Default::default()
304 },
305 })
306 .collect();
307 self.inner
308 .batch_write(proto::BatchWriteRequest {
309 entries,
310 ..Default::default()
311 })
312 .await?;
313 Ok(())
314 }
315
316 pub async fn scan(
318 &mut self,
319 prefix: &[u8],
320 cursor: Option<&[u8]>,
321 limit: u32,
322 cf: Option<u32>,
323 ) -> Result<(Vec<(Vec<u8>, Vec<u8>)>, Option<Vec<u8>>), tonic::Status> {
324 let resp = self
325 .inner
326 .scan(proto::ScanRequest {
327 cf: cf.unwrap_or(self.default_cf),
328 prefix: prefix.to_vec(),
329 cursor: cursor.unwrap_or(&[]).to_vec(),
330 limit,
331 ..Default::default()
332 })
333 .await?
334 .into_inner();
335 let entries = resp.entries.into_iter().map(|e| (e.key, e.value)).collect();
336 let next = if resp.next_cursor.is_empty() {
337 None
338 } else {
339 Some(resp.next_cursor)
340 };
341 Ok((entries, next))
342 }
343
344 pub async fn delete_by_prefix(
346 &mut self,
347 prefix: &[u8],
348 cf: Option<u32>,
349 ) -> Result<u32, tonic::Status> {
350 let resp = self
351 .inner
352 .delete_by_prefix(proto::DeleteByPrefixRequest {
353 cf: cf.unwrap_or(self.default_cf),
354 prefix: prefix.to_vec(),
355 ..Default::default()
356 })
357 .await?
358 .into_inner();
359 Ok(resp.deleted)
360 }
361
362 pub async fn create_vector_index(
366 &mut self,
367 name: &str,
368 config: VectorIndexConfig,
369 ) -> Result<(), tonic::Status> {
370 self.inner
371 .create_vector_index(proto::CreateVectorIndexRequest {
372 index_name: name.to_string(),
373 config: Some(proto::VectorIndexConfig {
374 dim: config.dim,
375 metric: config.metric,
376 m: config.m,
377 m_max0: config.m_max0,
378 ef_construction: config.ef_construction,
379 ef_search: config.ef_search,
380 ..Default::default()
381 }),
382 })
383 .await?;
384 Ok(())
385 }
386
387 pub async fn drop_vector_index(&mut self, name: &str) -> Result<(), tonic::Status> {
389 self.inner
390 .drop_vector_index(proto::DropVectorIndexRequest {
391 index_name: name.to_string(),
392 })
393 .await?;
394 Ok(())
395 }
396
397 pub async fn vector_put(
399 &mut self,
400 index_name: &str,
401 vector_id: u64,
402 vector: Vec<f32>,
403 ) -> Result<(), tonic::Status> {
404 self.inner
405 .vector_put(proto::VectorPutRequest {
406 index_name: index_name.to_string(),
407 vector_id,
408 vector,
409 attributes: Default::default(),
410 })
411 .await?;
412 Ok(())
413 }
414
415 pub async fn vector_delete(
417 &mut self,
418 index_name: &str,
419 vector_id: u64,
420 ) -> Result<(), tonic::Status> {
421 self.inner
422 .vector_delete(proto::VectorDeleteRequest {
423 index_name: index_name.to_string(),
424 vector_id,
425 })
426 .await?;
427 Ok(())
428 }
429
430 pub async fn vector_search(
432 &mut self,
433 index_name: &str,
434 query: Vec<f32>,
435 k: u32,
436 ef_search: Option<u32>,
437 ) -> Result<Vec<VectorSearchResult>, tonic::Status> {
438 self.vector_search_reranked(index_name, query, k, ef_search, None)
439 .await
440 }
441
442 pub async fn vector_search_reranked(
451 &mut self,
452 index_name: &str,
453 query: Vec<f32>,
454 k: u32,
455 ef_search: Option<u32>,
456 rerank: Option<proto::RerankSpec>,
457 ) -> Result<Vec<VectorSearchResult>, tonic::Status> {
458 let resp = self
459 .inner
460 .vector_search(proto::VectorSearchRequest {
461 index_name: index_name.to_string(),
462 query,
463 k,
464 ef_search: ef_search.unwrap_or(0),
465 filter: None,
466 query_payload: None, mmr: false, mmr_lambda: 0.0,
469 mmr_pool: 0,
470 rerank, planner_override: 0, group_field: String::new(), group_size: 0,
474 groups: 0,
475 group_overfetch: 0,
476 group_missing_as_own: false,
477 })
478 .await?
479 .into_inner();
480 Ok(resp
481 .results
482 .into_iter()
483 .map(|r| VectorSearchResult {
484 id: r.id,
485 distance: r.distance,
486 group_key: r.group_key,
487 })
488 .collect())
489 }
490
491 pub async fn vector_search_grouped(
502 &mut self,
503 index_name: &str,
504 query: Vec<f32>,
505 k: u32,
506 ef_search: Option<u32>,
507 group: GroupSpec,
508 ) -> Result<Vec<VectorSearchResult>, tonic::Status> {
509 let resp = self
510 .inner
511 .vector_search(proto::VectorSearchRequest {
512 index_name: index_name.to_string(),
513 query,
514 k,
515 ef_search: ef_search.unwrap_or(0),
516 filter: None,
517 query_payload: None,
518 mmr: false,
519 mmr_lambda: 0.0,
520 mmr_pool: 0,
521 rerank: None,
522 planner_override: 0,
523 group_field: group.field,
524 group_size: group.group_size,
525 groups: group.groups,
526 group_overfetch: group.overfetch,
527 group_missing_as_own: group.missing_as_own,
528 })
529 .await?
530 .into_inner();
531 Ok(resp
532 .results
533 .into_iter()
534 .map(|r| VectorSearchResult {
535 id: r.id,
536 distance: r.distance,
537 group_key: r.group_key,
538 })
539 .collect())
540 }
541
542 pub async fn rerank_validate(
552 &mut self,
553 index_name: &str,
554 mut rerank: proto::RerankSpec,
555 ) -> Result<(), tonic::Status> {
556 rerank.enabled = true;
557 rerank.validate_only = true;
558 self.inner
559 .vector_search(proto::VectorSearchRequest {
560 index_name: index_name.to_string(),
561 query: Vec::new(),
562 k: 1,
563 ef_search: 0,
564 filter: None,
565 query_payload: None,
566 mmr: false,
567 mmr_lambda: 0.0,
568 mmr_pool: 0,
569 rerank: Some(rerank),
570 planner_override: 0,
571 group_field: String::new(),
572 group_size: 0,
573 groups: 0,
574 group_overfetch: 0,
575 group_missing_as_own: false,
576 })
577 .await?;
578 Ok(())
579 }
580
581 pub async fn vector_get(
583 &mut self,
584 index_name: &str,
585 vector_id: u64,
586 ) -> Result<Option<Vec<f32>>, tonic::Status> {
587 let resp = self
588 .inner
589 .vector_get(proto::VectorGetRequest {
590 index_name: index_name.to_string(),
591 vector_id,
592 })
593 .await?
594 .into_inner();
595 Ok(if resp.found { Some(resp.vector) } else { None })
596 }
597
598 pub async fn graph_query(
616 &mut self,
617 cypher: &str,
618 options: GraphQueryOptions,
619 ) -> Result<GraphQueryResult, tonic::Status> {
620 let resp = self
621 .inner
622 .graph_query(proto::GraphQueryRequest {
623 graph_name: options.graph_name,
624 cypher: cypher.to_string(),
625 max_rows: options.max_rows,
626 as_of: options.as_of,
627 tx_as_of: options.tx_as_of,
628 })
629 .await?
630 .into_inner();
631 Ok(GraphQueryResult {
632 columns: resp.columns,
633 rows: resp
634 .rows
635 .into_iter()
636 .map(|row| row.values.into_iter().map(GraphValue::from_proto).collect())
637 .collect(),
638 warnings: resp.warnings,
639 })
640 }
641
642 pub async fn subscribe_committed<H, E>(
658 &mut self,
659 opts: cdc::SubscribeCommittedOptions<'_>,
660 handler: H,
661 ) -> Result<(), cdc::ConsumeError<E>>
662 where
663 H: FnMut(cdc::CommittedChange) -> Result<bool, E>,
664 {
665 let default_cf = self.default_cf;
666 let mut sleeper = cdc::TokioSleeper;
667 cdc::run_consumer(self, &mut sleeper, opts, default_cf, handler).await
668 }
669}
670
671pub struct GrpcFeedStream {
673 inner: tonic::Streaming<proto::CommittedFeedItem>,
674}
675
676#[tonic::async_trait]
677impl cdc::FeedStream for GrpcFeedStream {
678 async fn recv(&mut self) -> Result<Option<cdc::FeedItem>, tonic::Status> {
679 match self.inner.message().await? {
680 Some(item) => Ok(cdc::FeedItem::from_proto(item)),
681 None => Ok(None),
682 }
683 }
684}
685
686#[tonic::async_trait]
687impl cdc::FeedTransport for StateletClient {
688 type Stream = GrpcFeedStream;
689
690 async fn open_feed(
691 &mut self,
692 shard_id: u64,
693 from_offset: u64,
694 cf: u32,
695 key_prefix: &[u8],
696 include_values: bool,
697 ) -> Result<Self::Stream, tonic::Status> {
698 let resp = self
699 .inner
700 .subscribe_committed(proto::SubscribeCommittedRequest {
701 shard_id,
702 from_offset,
703 cf,
704 key_prefix: key_prefix.to_vec(),
705 include_values,
706 })
707 .await?;
708 Ok(GrpcFeedStream {
709 inner: resp.into_inner(),
710 })
711 }
712
713 async fn scan_page(
714 &mut self,
715 prefix: &[u8],
716 cursor: Option<&[u8]>,
717 limit: u32,
718 cf: u32,
719 ) -> Result<(Vec<(Vec<u8>, Vec<u8>)>, Option<Vec<u8>>), tonic::Status> {
720 self.scan(prefix, cursor, limit, Some(cf)).await
721 }
722}
723
724#[cfg(test)]
725mod graph_query_tests {
726 use super::*;
727
728 fn value(kind: proto::graph_query_value::Kind) -> proto::GraphQueryValue {
729 proto::GraphQueryValue {
730 kind: kind as i32,
731 int_value: 42,
732 dbl_value: 0.5,
733 str_value: "knows".to_string(),
734 bool_value: true,
735 json_value: br#"{"name":"ada"}"#.to_vec(),
736 }
737 }
738
739 #[test]
740 fn decodes_every_value_kind() {
741 use proto::graph_query_value::Kind;
742 assert_eq!(GraphValue::from_proto(value(Kind::Null)), GraphValue::Null);
743 assert_eq!(
744 GraphValue::from_proto(value(Kind::Int)),
745 GraphValue::Int(42)
746 );
747 assert_eq!(
748 GraphValue::from_proto(value(Kind::Double)),
749 GraphValue::Double(0.5)
750 );
751 assert_eq!(
752 GraphValue::from_proto(value(Kind::String)),
753 GraphValue::Str("knows".to_string())
754 );
755 assert_eq!(
756 GraphValue::from_proto(value(Kind::Bool)),
757 GraphValue::Bool(true)
758 );
759 assert_eq!(
760 GraphValue::from_proto(value(Kind::Json)),
761 GraphValue::Json(br#"{"name":"ada"}"#.to_vec())
762 );
763 }
764
765 #[test]
766 fn unknown_kind_from_a_newer_server_decodes_to_null() {
767 let mut v = value(proto::graph_query_value::Kind::Int);
768 v.kind = 99;
769 assert_eq!(GraphValue::from_proto(v), GraphValue::Null);
770 }
771
772 #[test]
773 fn default_options_leave_every_knob_at_the_server_default() {
774 let o = GraphQueryOptions::default();
775 assert!(o.graph_name.is_empty());
776 assert_eq!((o.max_rows, o.as_of, o.tx_as_of), (0, 0, 0));
777 }
778}