1use crate::{MergeError, Timestamp};
2use coven_foundation::id_source::{CircleId, DeviceId};
3use std::collections::{BTreeMap, BTreeSet};
4
5#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct ConstraintColumns(
8 pub Vec<String>,
10);
11
12impl<const N: usize> From<[&str; N]> for ConstraintColumns {
13 fn from(columns: [&str; N]) -> Self {
14 Self(columns.into_iter().map(str::to_owned).collect())
15 }
16}
17
18#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub struct UniqueConstraint {
22 pub terms: Vec<String>,
24 pub partial: Option<String>,
26}
27
28impl<const N: usize> From<[&str; N]> for UniqueConstraint {
29 fn from(terms: [&str; N]) -> Self {
30 Self { terms: terms.into_iter().map(str::to_owned).collect(), partial: None }
31 }
32}
33
34#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct ForeignKey {
37 pub columns: ConstraintColumns,
39 pub parent: String,
41 pub parent_columns: ConstraintColumns,
43}
44
45impl ForeignKey {
46 pub fn new(
48 columns: impl Into<ConstraintColumns>,
49 parent: impl Into<String>,
50 parent_columns: impl Into<ConstraintColumns>,
51 ) -> Self {
52 Self {
53 columns: columns.into(),
54 parent: parent.into(),
55 parent_columns: parent_columns.into(),
56 }
57 }
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct WriteId {
63 pub device: DeviceId,
65 pub number: u64,
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
73pub struct Slot(std::num::NonZeroU64);
74
75impl Slot {
76 pub const FIRST: Self = Self(std::num::NonZeroU64::MIN);
78
79 pub fn new(number: u64) -> Option<Self> {
81 std::num::NonZeroU64::new(number).map(Self)
82 }
83
84 pub fn get(self) -> u64 {
86 self.0.get()
87 }
88}
89
90#[derive(Clone, Copy, Debug)]
94pub struct SlotRange {
95 next: Option<Slot>,
96 last: u64,
97}
98
99impl SlotRange {
100 pub fn new(first: Slot, last: u64) -> Self {
102 Self { next: Some(first).filter(|first| first.get() <= last), last }
103 }
104}
105
106impl Iterator for SlotRange {
107 type Item = Slot;
108
109 fn next(&mut self) -> Option<Slot> {
110 let slot = self.next?;
111 self.next = slot.get().checked_add(1).filter(|next| *next <= self.last).and_then(Slot::new);
112 Some(slot)
113 }
114}
115
116impl From<std::num::NonZeroU64> for Slot {
117 fn from(number: std::num::NonZeroU64) -> Self {
118 Self(number)
119 }
120}
121
122impl From<Slot> for std::num::NonZeroU64 {
123 fn from(slot: Slot) -> Self {
124 slot.0
125 }
126}
127
128#[cfg(any(test, feature = "test-utils"))]
130pub fn slot(number: u64) -> Slot {
131 Slot::new(number).expect("a test slot number is positive")
132}
133
134#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
136pub enum Audience {
137 Store,
139 Circle(CircleId),
141}
142
143#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
145pub struct RowId {
146 pub table: String,
148 pub key: Vec<u8>,
150 pub audience: Audience,
152}
153
154#[derive(Clone, Debug, PartialEq, Eq)]
156pub struct Parent {
157 pub row: RowId,
159 pub generation: u64,
161}
162
163impl Parent {
164 pub fn validate_written(&self, child: &RowId) -> Result<(), MergeError> {
167 if !crate::generation_present(self.generation) {
168 return Err(MergeError::ParentGeneration(self.generation));
169 }
170 validate_audience(child, &self.row)
171 }
172}
173
174#[derive(Clone, Debug, PartialEq, Eq)]
177pub struct ColumnValue<V> {
178 pub value: V,
180 pub parents: BTreeMap<ForeignKey, Parent>,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
187pub enum Operation<V> {
188 Insert(BTreeMap<String, ColumnValue<V>>),
190 Update(BTreeMap<String, ColumnValue<V>>),
192 Delete,
194}
195
196impl<V> Operation<V> {
197 pub(crate) fn columns(&self) -> Option<&BTreeMap<String, ColumnValue<V>>> {
198 match self {
199 Self::Insert(c) | Self::Update(c) => Some(c),
200 Self::Delete => None,
201 }
202 }
203 pub(crate) fn advances(&self) -> bool {
204 !matches!(self, Self::Update(_))
205 }
206}
207
208#[derive(Clone, Debug, PartialEq, Eq)]
210pub struct Change<V> {
211 pub generation: u64,
213 pub operation: Operation<V>,
215}
216
217impl<V> Change<V> {
218 pub fn validate(&self, row: &RowId) -> Result<(), MergeError> {
224 self.incarnation()?;
225 self.after()?;
226 if let Some(columns) = self.operation.columns() {
227 for value in columns.values() {
228 for parent in value.parents.values() {
229 parent.validate_written(row)?;
230 }
231 }
232 }
233 Ok(())
234 }
235
236 pub fn after(&self) -> Result<u64, MergeError> {
239 if self.operation.advances() {
240 self.generation.checked_add(1).ok_or(MergeError::GenerationExhausted)
241 } else {
242 Ok(self.generation)
243 }
244 }
245
246 pub fn incarnation(&self) -> Result<u64, MergeError> {
249 if matches!(self.operation, Operation::Insert(_))
250 == crate::generation_present(self.generation)
251 {
252 return Err(MergeError::GenerationParity(self.generation));
253 }
254 if matches!(self.operation, Operation::Insert(_)) {
255 self.generation.checked_add(1).ok_or(MergeError::GenerationExhausted)
256 } else {
257 Ok(self.generation)
258 }
259 }
260}
261
262#[derive(Clone, Debug, PartialEq, Eq)]
264pub struct Write<V, P = BTreeSet<WriteId>> {
265 pub id: WriteId,
267 pub timestamp: Timestamp,
269 pub had_read: P,
273 pub changes: BTreeMap<RowId, Change<V>>,
275}
276
277impl<V, P: WritePast> Write<V, P> {
278 pub fn validate_metadata<O: WriteOracle>(&self, oracle: &O) -> Result<(), O::Error> {
281 if oracle.timestamp(self.id)?.is_some() {
282 return Err(MergeError::DuplicateWrite(self.id).into());
283 }
284 if self.timestamp.device() != self.id.device {
285 return Err(MergeError::TimestampDevice(self.id).into());
286 }
287 for past in self.had_read.frontier() {
288 if crate::state::timestamp(oracle, *past)? >= self.timestamp {
289 return Err(MergeError::CausalTimestamp(self.id).into());
290 }
291 }
292 Ok(())
293 }
294}
295
296pub trait WritePast {
299 fn contains(&self, write: &WriteId) -> bool;
301 fn frontier(&self) -> impl Iterator<Item = &WriteId>;
303}
304
305impl WritePast for BTreeSet<WriteId> {
306 fn contains(&self, write: &WriteId) -> bool {
307 self.contains(write)
308 }
309 fn frontier(&self) -> impl Iterator<Item = &WriteId> {
310 self.iter()
311 }
312}
313
314pub trait WriteOracle {
319 type Error: From<MergeError>;
321 fn timestamp(&self, write: WriteId) -> Result<Option<Timestamp>, Self::Error>;
323 fn had_read(&self, reader: WriteId, earlier: WriteId) -> Result<bool, Self::Error>;
325}
326
327pub(crate) fn validate_audience(row: &RowId, parent: &RowId) -> Result<(), MergeError> {
328 if parent.audience != Audience::Store && parent.audience != row.audience {
329 return Err(MergeError::ReferenceAudience(row.clone()));
330 }
331 Ok(())
332}
333
334#[cfg(test)]
335#[path = "input_tests.rs"]
336mod tests;