(&self, key: &str, seqno: SeqNo)
| 654 | } |
| 655 | |
| 656 | async fn truncate(&self, key: &str, seqno: SeqNo) -> Result<Option<usize>, ExternalError> { |
| 657 | // `sequence_number >= 0` keeps the seqno -1 sentinel (see `compare_and_set`); it is a no-op |
| 658 | // for shards that have no sentinel, since all of their seqnos are already >= 0. |
| 659 | static TRUNCATE_QUERY: &str = " |
| 660 | DELETE FROM consensus |
| 661 | WHERE shard = $1 AND sequence_number >= 0 AND sequence_number < $2 AND |
| 662 | EXISTS ( |
| 663 | SELECT * FROM consensus WHERE shard = $1 AND sequence_number >= $2 |
| 664 | ) |
| 665 | "; |
| 666 | |
| 667 | let result = { |
| 668 | let client = self.get_connection().await?; |
| 669 | let statement = client.prepare_cached(TRUNCATE_QUERY).await?; |
| 670 | pg_execute_prepared(&client, &statement, &[&key, &seqno]).await? |
| 671 | }; |
| 672 | if result == 0 { |
| 673 | // We weren't able to successfully truncate any rows inspect head to |
| 674 | // determine whether the request was valid and there were no records in |
| 675 | // the provided range, or the request was invalid because it would have |
| 676 | // also deleted head. |
| 677 | |
| 678 | // It's safe to call head in a subsequent transaction rather than doing |
| 679 | // so directly in the same transaction because, once a given (seqno, data) |
| 680 | // pair exists for our shard, we enforce the invariants that |
| 681 | // 1. Our shard will always have _some_ data mapped to it. |
| 682 | // 2. All operations that modify the (seqno, data) can only increase |
| 683 | // the sequence number. |
| 684 | let current = self.head(key).await?; |
| 685 | if current.map_or(true, |data| data.seqno < seqno) { |
| 686 | return Err(ExternalError::from(anyhow!( |
| 687 | "upper bound too high for truncate: {:?}", |
| 688 | seqno |
| 689 | ))); |
| 690 | } |
| 691 | } |
| 692 | |
| 693 | Ok(Some(usize::cast_from(result))) |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | #[cfg(test)] |
nothing calls this directly
no test coverage detected