1use crate::batcher::WriteOp;
7use crate::client::{OxiaClient, check_status};
8use crate::errors::OxiaError;
9use crate::proto;
10use crate::streams::{ListStream, Notifications, RangeScanStream, SequenceUpdates};
11use crate::types::{ComparisonType, GetResult, PutResult, VERSION_ID_NOT_EXISTS};
12use bytes::Bytes;
13use futures::TryStreamExt;
14use futures::future::BoxFuture;
15use std::future::IntoFuture;
16
17const DEFAULT_SUBSCRIPTION_BUFFER: usize = 100;
18
19#[derive(Debug)]
22#[must_use = "builders do nothing unless awaited"]
23pub struct PutBuilder {
24 client: OxiaClient,
25 key: String,
26 value: Bytes,
27 expected_version_id: Option<i64>,
28 partition_key: Option<String>,
29 sequence_key_deltas: Vec<u64>,
30 secondary_indexes: Vec<proto::SecondaryIndex>,
31 ephemeral: bool,
32}
33
34impl PutBuilder {
35 pub(crate) fn new(client: OxiaClient, key: String, value: Bytes) -> Self {
36 PutBuilder {
37 client,
38 key,
39 value,
40 expected_version_id: None,
41 partition_key: None,
42 sequence_key_deltas: Vec::new(),
43 secondary_indexes: Vec::new(),
44 ephemeral: false,
45 }
46 }
47
48 pub fn expected_version_id(mut self, version_id: i64) -> Self {
52 self.expected_version_id = Some(version_id);
53 self
54 }
55
56 pub fn expected_record_not_exists(mut self) -> Self {
59 self.expected_version_id = Some(VERSION_ID_NOT_EXISTS);
60 self
61 }
62
63 pub fn partition_key(mut self, partition_key: impl Into<String>) -> Self {
66 self.partition_key = Some(partition_key.into());
67 self
68 }
69
70 pub fn sequence_key_deltas(mut self, deltas: impl IntoIterator<Item = u64>) -> Self {
79 self.sequence_key_deltas = deltas.into_iter().collect();
80 self
81 }
82
83 pub fn secondary_index(
86 mut self,
87 index_name: impl Into<String>,
88 secondary_key: impl Into<String>,
89 ) -> Self {
90 self.secondary_indexes.push(proto::SecondaryIndex {
91 index_name: index_name.into(),
92 secondary_key: secondary_key.into(),
93 });
94 self
95 }
96
97 pub fn ephemeral(mut self) -> Self {
100 self.ephemeral = true;
101 self
102 }
103
104 async fn execute(self) -> Result<PutResult, OxiaError> {
105 let client = self.client;
106 client.ensure_open()?;
107 if !self.sequence_key_deltas.is_empty() {
112 if self.partition_key.is_none() {
113 return Err(OxiaError::InvalidArgument(
114 "sequence_key_deltas requires a partition_key".to_string(),
115 ));
116 }
117 if self.expected_version_id.is_some() {
118 return Err(OxiaError::InvalidArgument(
119 "sequence_key_deltas cannot be combined with expected_version_id".to_string(),
120 ));
121 }
122 if self.sequence_key_deltas.contains(&0) {
123 return Err(OxiaError::InvalidArgument(
124 "every sequence delta must be greater than zero".to_string(),
125 ));
126 }
127 }
128 let routing_key = self
129 .partition_key
130 .as_deref()
131 .unwrap_or(&self.key)
132 .to_string();
133 let PutBuilder {
134 key,
135 value,
136 expected_version_id,
137 partition_key,
138 sequence_key_deltas,
139 secondary_indexes,
140 ephemeral,
141 ..
142 } = self;
143 let identity = client.identity().to_string();
144 let response = client
147 .with_shard_retry(&routing_key, |shard| {
148 let client = client.clone();
149 let key = key.clone();
150 let value = value.clone();
151 let partition_key = partition_key.clone();
152 let sequence_key_deltas = sequence_key_deltas.clone();
153 let secondary_indexes = secondary_indexes.clone();
154 let identity = identity.clone();
155 async move {
156 let session_id = if ephemeral {
157 Some(client.session_id_for(shard).await?)
158 } else {
159 None
160 };
161 let request = proto::PutRequest {
162 key,
163 value,
164 expected_version_id,
165 session_id,
166 client_identity: Some(identity),
167 partition_key,
168 sequence_key_delta: sequence_key_deltas,
169 secondary_indexes,
170 override_version_id: None,
171 override_modifications_count: None,
172 };
173 client.submit_write(shard, request, WriteOp::Put).await
174 }
175 })
176 .await?;
177 check_status(response.status)?;
178 let version = response
179 .version
180 .ok_or_else(|| OxiaError::Decode("missing version in put response".to_string()))?;
181 Ok(PutResult {
182 key: response.key.unwrap_or(key),
185 version: version.into(),
186 })
187 }
188}
189
190impl IntoFuture for PutBuilder {
191 type Output = Result<PutResult, OxiaError>;
192 type IntoFuture = BoxFuture<'static, Self::Output>;
193
194 fn into_future(self) -> Self::IntoFuture {
195 Box::pin(self.execute())
196 }
197}
198
199#[derive(Debug)]
202#[must_use = "builders do nothing unless awaited"]
203pub struct GetBuilder {
204 client: OxiaClient,
205 key: String,
206 comparison: ComparisonType,
207 partition_key: Option<String>,
208 include_value: bool,
209 secondary_index_name: Option<String>,
210}
211
212impl GetBuilder {
213 pub(crate) fn new(client: OxiaClient, key: String) -> Self {
214 GetBuilder {
215 client,
216 key,
217 comparison: ComparisonType::Equal,
218 partition_key: None,
219 include_value: true,
220 secondary_index_name: None,
221 }
222 }
223
224 pub fn comparison(mut self, comparison: ComparisonType) -> Self {
227 self.comparison = comparison;
228 self
229 }
230
231 pub fn partition_key(mut self, partition_key: impl Into<String>) -> Self {
234 self.partition_key = Some(partition_key.into());
235 self
236 }
237
238 pub fn include_value(mut self, include_value: bool) -> Self {
241 self.include_value = include_value;
242 self
243 }
244
245 pub fn use_index(mut self, index_name: impl Into<String>) -> Self {
247 self.secondary_index_name = Some(index_name.into());
248 self
249 }
250
251 async fn execute(self) -> Result<GetResult, OxiaError> {
252 let client = self.client;
253 client.ensure_open()?;
254 let single_shard = is_single_shard_get(
255 self.partition_key.is_some(),
256 self.comparison,
257 self.secondary_index_name.is_some(),
258 );
259 let request = proto::GetRequest {
260 key: self.key.clone(),
261 include_value: self.include_value,
262 comparison_type: self.comparison.to_proto() as i32,
263 secondary_index_name: self.secondary_index_name,
264 };
265 if single_shard {
266 let route_key = self.partition_key.unwrap_or_else(|| self.key.clone());
267 let response = client
268 .with_shard_retry(&route_key, |shard| {
269 let client = client.clone();
270 let request = request.clone();
271 async move { client.submit_get(shard, request).await }
272 })
273 .await?;
274 check_status(response.status)?;
275 GetResult::from_proto(response, Some(self.key))
276 } else {
277 client.broadcast_get(request, self.comparison).await
278 }
279 }
280}
281
282impl IntoFuture for GetBuilder {
283 type Output = Result<GetResult, OxiaError>;
284 type IntoFuture = BoxFuture<'static, Self::Output>;
285
286 fn into_future(self) -> Self::IntoFuture {
287 Box::pin(self.execute())
288 }
289}
290
291fn is_single_shard_get(
299 has_partition_key: bool,
300 comparison: ComparisonType,
301 has_secondary_index: bool,
302) -> bool {
303 has_partition_key || (comparison == ComparisonType::Equal && !has_secondary_index)
304}
305
306#[derive(Debug)]
309#[must_use = "builders do nothing unless awaited"]
310pub struct DeleteBuilder {
311 client: OxiaClient,
312 key: String,
313 expected_version_id: Option<i64>,
314 partition_key: Option<String>,
315}
316
317impl DeleteBuilder {
318 pub(crate) fn new(client: OxiaClient, key: String) -> Self {
319 DeleteBuilder {
320 client,
321 key,
322 expected_version_id: None,
323 partition_key: None,
324 }
325 }
326
327 pub fn expected_version_id(mut self, version_id: i64) -> Self {
331 self.expected_version_id = Some(version_id);
332 self
333 }
334
335 pub fn partition_key(mut self, partition_key: impl Into<String>) -> Self {
338 self.partition_key = Some(partition_key.into());
339 self
340 }
341
342 async fn execute(self) -> Result<(), OxiaError> {
343 let client = self.client;
344 client.ensure_open()?;
345 let routing_key = self
346 .partition_key
347 .as_deref()
348 .unwrap_or(&self.key)
349 .to_string();
350 let request = proto::DeleteRequest {
351 key: self.key,
352 expected_version_id: self.expected_version_id,
353 };
354 let response = client
355 .with_shard_retry(&routing_key, |shard| {
356 let client = client.clone();
357 let request = request.clone();
358 async move { client.submit_write(shard, request, WriteOp::Delete).await }
359 })
360 .await?;
361 check_status(response.status)
362 }
363}
364
365impl IntoFuture for DeleteBuilder {
366 type Output = Result<(), OxiaError>;
367 type IntoFuture = BoxFuture<'static, Self::Output>;
368
369 fn into_future(self) -> Self::IntoFuture {
370 Box::pin(self.execute())
371 }
372}
373
374#[derive(Debug)]
377#[must_use = "builders do nothing unless awaited"]
378pub struct DeleteRangeBuilder {
379 client: OxiaClient,
380 min_key_inclusive: String,
381 max_key_exclusive: String,
382 partition_key: Option<String>,
383}
384
385impl DeleteRangeBuilder {
386 pub(crate) fn new(
387 client: OxiaClient,
388 min_key_inclusive: String,
389 max_key_exclusive: String,
390 ) -> Self {
391 DeleteRangeBuilder {
392 client,
393 min_key_inclusive,
394 max_key_exclusive,
395 partition_key: None,
396 }
397 }
398
399 pub fn partition_key(mut self, partition_key: impl Into<String>) -> Self {
402 self.partition_key = Some(partition_key.into());
403 self
404 }
405
406 async fn execute(self) -> Result<(), OxiaError> {
407 let client = self.client;
408 client.ensure_open()?;
409 let request = proto::DeleteRangeRequest {
410 start_inclusive: self.min_key_inclusive,
411 end_exclusive: self.max_key_exclusive,
412 };
413 match self.partition_key {
414 Some(partition_key) => {
415 let response = client
416 .with_shard_retry(&partition_key, |shard| {
417 let client = client.clone();
418 let request = request.clone();
419 async move {
420 client
421 .submit_write(shard, request, WriteOp::DeleteRange)
422 .await
423 }
424 })
425 .await?;
426 check_status(response.status)
427 }
428 None => client.broadcast_delete_range(request).await,
429 }
430 }
431}
432
433impl IntoFuture for DeleteRangeBuilder {
434 type Output = Result<(), OxiaError>;
435 type IntoFuture = BoxFuture<'static, Self::Output>;
436
437 fn into_future(self) -> Self::IntoFuture {
438 Box::pin(self.execute())
439 }
440}
441
442#[derive(Debug)]
448#[must_use = "builders do nothing unless awaited"]
449pub struct ListBuilder {
450 client: OxiaClient,
451 min_key_inclusive: String,
452 max_key_exclusive: String,
453 partition_key: Option<String>,
454 secondary_index_name: Option<String>,
455 include_internal_keys: bool,
456}
457
458impl ListBuilder {
459 pub(crate) fn new(
460 client: OxiaClient,
461 min_key_inclusive: String,
462 max_key_exclusive: String,
463 ) -> Self {
464 ListBuilder {
465 client,
466 min_key_inclusive,
467 max_key_exclusive,
468 partition_key: None,
469 secondary_index_name: None,
470 include_internal_keys: false,
471 }
472 }
473
474 pub fn partition_key(mut self, partition_key: impl Into<String>) -> Self {
476 self.partition_key = Some(partition_key.into());
477 self
478 }
479
480 pub fn use_index(mut self, index_name: impl Into<String>) -> Self {
483 self.secondary_index_name = Some(index_name.into());
484 self
485 }
486
487 pub fn include_internal_keys(mut self, include: bool) -> Self {
489 self.include_internal_keys = include;
490 self
491 }
492
493 fn into_request(self) -> (OxiaClient, Option<String>, proto::ListRequest) {
494 let request = proto::ListRequest {
495 shard: None,
496 start_inclusive: self.min_key_inclusive,
497 end_exclusive: self.max_key_exclusive,
498 secondary_index_name: self.secondary_index_name,
499 include_internal_keys: self.include_internal_keys,
500 };
501 (self.client, self.partition_key, request)
502 }
503
504 pub async fn stream(self) -> Result<ListStream, OxiaError> {
506 let (client, partition_key, request) = self.into_request();
507 client
508 .open_list_stream(request, partition_key.as_deref())
509 .await
510 }
511
512 async fn execute(self) -> Result<Vec<String>, OxiaError> {
513 let timeout = self.client.request_timeout();
514 let stream = self.stream();
515 tokio::time::timeout(timeout, async move { stream.await?.try_collect().await })
516 .await
517 .map_err(|_| OxiaError::Timeout)?
518 }
519}
520
521impl IntoFuture for ListBuilder {
522 type Output = Result<Vec<String>, OxiaError>;
523 type IntoFuture = BoxFuture<'static, Self::Output>;
524
525 fn into_future(self) -> Self::IntoFuture {
526 Box::pin(self.execute())
527 }
528}
529
530#[derive(Debug)]
536#[must_use = "builders do nothing unless awaited"]
537pub struct RangeScanBuilder {
538 client: OxiaClient,
539 min_key_inclusive: String,
540 max_key_exclusive: String,
541 partition_key: Option<String>,
542 secondary_index_name: Option<String>,
543 include_internal_keys: bool,
544}
545
546impl RangeScanBuilder {
547 pub(crate) fn new(
548 client: OxiaClient,
549 min_key_inclusive: String,
550 max_key_exclusive: String,
551 ) -> Self {
552 RangeScanBuilder {
553 client,
554 min_key_inclusive,
555 max_key_exclusive,
556 partition_key: None,
557 secondary_index_name: None,
558 include_internal_keys: false,
559 }
560 }
561
562 pub fn partition_key(mut self, partition_key: impl Into<String>) -> Self {
564 self.partition_key = Some(partition_key.into());
565 self
566 }
567
568 pub fn use_index(mut self, index_name: impl Into<String>) -> Self {
570 self.secondary_index_name = Some(index_name.into());
571 self
572 }
573
574 pub fn include_internal_keys(mut self, include: bool) -> Self {
576 self.include_internal_keys = include;
577 self
578 }
579
580 fn into_request(self) -> (OxiaClient, Option<String>, proto::RangeScanRequest) {
581 let request = proto::RangeScanRequest {
582 shard: None,
583 start_inclusive: self.min_key_inclusive,
584 end_exclusive: self.max_key_exclusive,
585 secondary_index_name: self.secondary_index_name,
586 include_internal_keys: self.include_internal_keys,
587 };
588 (self.client, self.partition_key, request)
589 }
590
591 pub async fn stream(self) -> Result<RangeScanStream, OxiaError> {
593 let (client, partition_key, request) = self.into_request();
594 client
595 .open_range_scan_stream(request, partition_key.as_deref())
596 .await
597 }
598
599 async fn execute(self) -> Result<Vec<GetResult>, OxiaError> {
600 let timeout = self.client.request_timeout();
601 let stream = self.stream();
602 tokio::time::timeout(timeout, async move { stream.await?.try_collect().await })
603 .await
604 .map_err(|_| OxiaError::Timeout)?
605 }
606}
607
608impl IntoFuture for RangeScanBuilder {
609 type Output = Result<Vec<GetResult>, OxiaError>;
610 type IntoFuture = BoxFuture<'static, Self::Output>;
611
612 fn into_future(self) -> Self::IntoFuture {
613 Box::pin(self.execute())
614 }
615}
616
617#[derive(Debug)]
621#[must_use = "builders do nothing unless awaited"]
622pub struct NotificationsBuilder {
623 client: OxiaClient,
624 buffer_size: usize,
625}
626
627impl NotificationsBuilder {
628 pub(crate) fn new(client: OxiaClient) -> Self {
629 NotificationsBuilder {
630 client,
631 buffer_size: DEFAULT_SUBSCRIPTION_BUFFER,
632 }
633 }
634
635 pub fn buffer_size(mut self, buffer_size: usize) -> Self {
638 self.buffer_size = buffer_size;
639 self
640 }
641}
642
643impl IntoFuture for NotificationsBuilder {
644 type Output = Result<Notifications, OxiaError>;
645 type IntoFuture = BoxFuture<'static, Self::Output>;
646
647 fn into_future(self) -> Self::IntoFuture {
648 Box::pin(async move { self.client.open_notifications(self.buffer_size).await })
649 }
650}
651
652#[derive(Debug)]
656#[must_use = "builders do nothing unless awaited"]
657pub struct SequenceUpdatesBuilder {
658 client: OxiaClient,
659 key: String,
660 partition_key: String,
661 buffer_size: usize,
662}
663
664impl SequenceUpdatesBuilder {
665 pub(crate) fn new(client: OxiaClient, key: String, partition_key: String) -> Self {
666 SequenceUpdatesBuilder {
667 client,
668 key,
669 partition_key,
670 buffer_size: DEFAULT_SUBSCRIPTION_BUFFER,
671 }
672 }
673
674 pub fn buffer_size(mut self, buffer_size: usize) -> Self {
676 self.buffer_size = buffer_size;
677 self
678 }
679}
680
681impl IntoFuture for SequenceUpdatesBuilder {
682 type Output = Result<SequenceUpdates, OxiaError>;
683 type IntoFuture = BoxFuture<'static, Self::Output>;
684
685 fn into_future(self) -> Self::IntoFuture {
686 Box::pin(async move {
687 self.client
688 .open_sequence_updates(self.key, self.partition_key, self.buffer_size)
689 .await
690 })
691 }
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697
698 #[test]
699 fn equal_primary_key_get_is_single_shard() {
700 assert!(is_single_shard_get(false, ComparisonType::Equal, false));
702 }
703
704 #[test]
705 fn a_partition_key_always_pins_the_shard() {
706 for comparison in [
707 ComparisonType::Equal,
708 ComparisonType::Floor,
709 ComparisonType::Ceiling,
710 ComparisonType::Lower,
711 ComparisonType::Higher,
712 ] {
713 assert!(is_single_shard_get(true, comparison, false));
714 assert!(is_single_shard_get(true, comparison, true));
716 }
717 }
718
719 #[test]
720 fn inequality_gets_without_partition_key_broadcast() {
721 for comparison in [
722 ComparisonType::Floor,
723 ComparisonType::Ceiling,
724 ComparisonType::Lower,
725 ComparisonType::Higher,
726 ] {
727 assert!(!is_single_shard_get(false, comparison, false));
728 }
729 }
730
731 #[test]
732 fn secondary_index_get_without_partition_key_broadcasts() {
733 assert!(!is_single_shard_get(false, ComparisonType::Equal, true));
735 }
736}