1use std::cell::Cell;
15use std::sync::Arc;
16
17use crate::change::{Change, Constraint, FetchRequest, Node, NodeStream, OutEdge};
18use crate::graph::{Graph, NodeId, StorageId};
19use crate::op::partition::{
20 constraint_matches_partition_key, encode_state_key, partition_key_unchanged,
21};
22use crate::storage::StorageValue;
23use crate::value::{ColId, OwnedRow as Row, OwnedValue, Value};
24
25pub struct Cap {
27 pub input: NodeId,
29 pub storage: StorageId,
32 pub limit: u32,
34 pub partition_key: Option<Vec<ColId>>,
37 pub primary_key: Vec<ColId>,
40 pub output: Cell<Option<OutEdge>>,
45}
46
47impl Cap {
48 pub fn new(
49 input: NodeId,
50 storage: StorageId,
51 limit: u32,
52 partition_key: Option<Vec<ColId>>,
53 primary_key: Vec<ColId>,
54 ) -> Cap {
55 Cap {
56 input,
57 storage,
58 limit,
59 partition_key,
60 primary_key,
61 output: Cell::new(None),
62 }
63 }
64
65 fn get_state(&self, g: &Graph, key: &str) -> Option<(u32, Vec<Box<str>>)> {
68 match g.storage(self.storage).get(key) {
69 None => None,
70 Some(StorageValue::Cap { size, pks }) => Some((size, pks)),
71 Some(other) => unreachable!("cap state slot held {other:?}"),
72 }
73 }
74
75 fn set_state(&self, g: &Graph, key: &str, size: u32, pks: Vec<Box<str>>) {
76 g.storage(self.storage)
77 .set(key, StorageValue::Cap { size, pks });
78 }
79
80 fn key_from_row(&self, row: &Row) -> String {
83 match &self.partition_key {
84 None => encode_state_key("cap", &[]),
85 Some(pk) => {
86 let vals: Vec<OwnedValue> = pk.iter().map(|&c| row.col(c).to_owned()).collect();
87 encode_state_key("cap", &vals)
88 }
89 }
90 }
91
92 fn key_from_constraint(&self, c: Option<&Constraint>) -> String {
94 match (&self.partition_key, c) {
95 (Some(pk), Some(c)) => {
96 let vals: Vec<OwnedValue> = pk
97 .iter()
98 .map(|&col| {
99 c.iter()
100 .find(|(cc, _)| *cc == col)
101 .map(|(_, v)| v.clone())
102 .unwrap_or(OwnedValue::Null)
103 })
104 .collect();
105 encode_state_key("cap", &vals)
106 }
107 _ => encode_state_key("cap", &[]),
108 }
109 }
110
111 fn constraint_for(&self, row: &Row) -> Option<Constraint> {
114 self.partition_key
115 .as_ref()
116 .map(|pk| pk.iter().map(|&c| (c, row.col(c).to_owned())).collect())
117 }
118
119 pub fn evict_partition(&self, g: &Graph, constraint: &Constraint) {
133 let Some(pk) = &self.partition_key else {
134 return;
135 };
136 if !constraint_matches_partition_key(constraint, pk) {
137 return;
138 }
139 let key = self.key_from_constraint(Some(constraint));
140 g.storage(self.storage).del(&key);
141 }
142
143 pub fn fetch<'g>(&'g self, g: &'g Graph, req: &FetchRequest) -> NodeStream<'g> {
148 assert!(req.start.is_none(), "Cap does not support start");
149 assert!(!req.reverse, "Cap does not support reverse");
150 debug_assert!(
151 self.partition_key.as_ref().is_none_or(|pk| req
152 .constraint
153 .as_ref()
154 .is_some_and(|c| constraint_matches_partition_key(c, pk))),
155 "Cap fetch: constraint must match partition key when partitioned"
156 );
157
158 let key = self.key_from_constraint(req.constraint.as_ref());
159 match self.get_state(g, &key) {
160 None => self.initial_fetch(g, req),
161 Some((0, _)) => Box::new(std::iter::empty()),
162 Some((_, pks)) => {
163 let mut out: Vec<Node<'g>> = Vec::new();
166 for pk in &pks {
167 let c = deserialize_pk(pk, &self.primary_key);
168 out.extend(g.fetch(self.input, &FetchRequest::with_constraint(c)));
169 }
170 Box::new(out.into_iter())
171 }
172 }
173 }
174
175 fn initial_fetch<'g>(&'g self, g: &'g Graph, req: &FetchRequest) -> NodeStream<'g> {
179 if self.limit == 0 {
180 return Box::new(std::iter::empty());
181 }
182 let key = self.key_from_constraint(req.constraint.as_ref());
183 debug_assert!(
184 self.get_state(g, &key).is_none(),
185 "Cap initial fetch: state should be undefined"
186 );
187 let mut size = 0u32;
188 let mut pks: Vec<Box<str>> = Vec::new();
189 let mut out: Vec<Node<'g>> = Vec::new();
190 for node in g.fetch(self.input, req) {
191 pks.push(serialize_pk(&node.row, &self.primary_key));
192 out.push(node);
193 size += 1;
194 if size == self.limit {
195 break;
196 }
197 }
198 self.set_state(g, &key, size, pks);
199 Box::new(out.into_iter())
200 }
201
202 pub fn push<'g>(&'g self, g: &'g Graph, change: Change<'g>) {
208 if matches!(change, Change::Edit { .. }) {
209 return self.push_edit(g, change);
210 }
211
212 let row = match &change {
213 Change::Add(n) | Change::Remove(n) => &n.row,
214 Change::Child { node, .. } => &node.row,
215 Change::Edit { .. } => unreachable!(),
216 };
217 let key = self.key_from_row(row);
218 let Some((size, pks)) = self.get_state(g, &key) else {
219 return;
220 };
221 let pk = serialize_pk(row, &self.primary_key);
222 let out = self.output.get().expect("Cap output not wired");
223
224 match change {
225 Change::Add(node) => {
226 if size < self.limit {
227 let mut pks = pks;
228 pks.push(pk);
229 self.set_state(g, &key, size + 1, pks);
230 g.push(out.node, Change::Add(node), out.port);
231 }
232 }
234 Change::Remove(node) => self.push_remove(g, node, out, &key, size, pks, &pk),
235 Change::Child { node, rel, child } => {
236 if pks.iter().any(|p| **p == *pk) {
237 g.push(out.node, Change::Child { node, rel, child }, out.port);
238 }
239 }
240 Change::Edit { .. } => unreachable!(),
241 }
242 }
243
244 #[allow(clippy::too_many_arguments)]
250 fn push_remove<'g>(
251 &'g self,
252 g: &'g Graph,
253 node: Node<'g>,
254 out: OutEdge,
255 key: &str,
256 size: u32,
257 pks: Vec<Box<str>>,
258 pk: &str,
259 ) {
260 let Some(idx) = pks.iter().position(|p| **p == *pk) else {
261 return; };
263 let mut pks = pks;
264 pks.remove(idx);
265 let new_size = size - 1;
266
267 let req = FetchRequest {
269 constraint: self.constraint_for(&node.row),
270 ..Default::default()
271 };
272 let mut replacement: Option<Node<'g>> = None;
273 for n in g.fetch(self.input, &req) {
274 let npk = serialize_pk(&n.row, &self.primary_key);
275 if !pks.iter().any(|p| **p == *npk) {
276 replacement = Some(n);
277 break;
278 }
279 }
280
281 match replacement {
282 Some(repl) => {
283 self.set_state(g, key, new_size, pks.clone());
285 g.push(out.node, Change::Remove(node), out.port);
286 let repl_pk = serialize_pk(&repl.row, &self.primary_key);
287 let mut pks = pks;
288 pks.push(repl_pk);
289 self.set_state(g, key, new_size + 1, pks);
290 g.push(out.node, Change::Add(repl), out.port);
291 }
292 None => {
293 self.set_state(g, key, new_size, pks);
294 g.push(out.node, Change::Remove(node), out.port);
295 }
296 }
297 }
298
299 fn push_edit<'g>(&'g self, g: &'g Graph, change: Change<'g>) {
303 let Change::Edit { node, old } = change else {
304 unreachable!()
305 };
306 if let Some(pk) = &self.partition_key {
307 debug_assert!(
308 partition_key_unchanged(&old.row, &node.row, pk),
309 "Cap: unexpected change of partition key"
310 );
311 }
312 let key = self.key_from_row(&old.row);
313 let Some((size, pks)) = self.get_state(g, &key) else {
314 return;
315 };
316 let old_pk = serialize_pk(&old.row, &self.primary_key);
317 let out = self.output.get().expect("Cap output not wired");
318
319 if pks.iter().any(|p| **p == *old_pk) {
320 let new_pk = serialize_pk(&node.row, &self.primary_key);
321 if new_pk != old_pk {
322 let pks: Vec<Box<str>> = pks
323 .into_iter()
324 .map(|p| if *p == *old_pk { new_pk.clone() } else { p })
325 .collect();
326 self.set_state(g, &key, size, pks);
327 }
328 g.push(out.node, Change::Edit { node, old }, out.port);
329 }
330 }
332}
333
334pub(crate) fn serialize_pk(row: &Row, pk: &[ColId]) -> Box<str> {
340 let mut s = String::new();
341 for &c in pk {
342 let (tag, content): (char, std::borrow::Cow<'_, str>) = match row.col(c) {
343 Value::Absent => ('a', "".into()),
346 Value::Null => ('n', "".into()),
347 Value::Bool(b) => ('b', if b { "1".into() } else { "0".into() }),
348 Value::Int(i) => ('i', i.to_string().into()),
349 Value::Float(f) => ('f', f.to_bits().to_string().into()),
350 Value::Str(x) => ('s', String::from_utf8_lossy(x)),
352 Value::Json(x) => ('j', String::from_utf8_lossy(x)),
353 };
354 s.push(tag);
355 s.push_str(&content.len().to_string());
356 s.push(':');
357 s.push_str(&content);
358 }
359 s.into_boxed_str()
360}
361
362pub(crate) fn deserialize_pk(pk: &str, primary_key: &[ColId]) -> Constraint {
366 let bytes = pk.as_bytes();
367 let mut i = 0;
368 let mut out = Vec::with_capacity(primary_key.len());
369 for &col in primary_key {
370 let tag = bytes[i];
371 i += 1;
372 let mut len = 0usize;
373 while bytes[i] != b':' {
374 len = len * 10 + usize::from(bytes[i] - b'0');
375 i += 1;
376 }
377 i += 1; let content = &pk[i..i + len];
379 i += len;
380 let v = match tag {
381 b'a' => OwnedValue::Absent,
382 b'n' => OwnedValue::Null,
383 b'b' => OwnedValue::Bool(content == "1"),
384 b'i' => OwnedValue::Int(content.parse().expect("pk int")),
385 b'f' => OwnedValue::Float(f64::from_bits(content.parse().expect("pk float bits"))),
386 b's' => OwnedValue::str(content),
387 b'j' => OwnedValue::Json(Arc::from(content)),
388 other => unreachable!("bad pk tag {other}"),
389 };
390 out.push((col, v));
391 }
392 out
393}
394
395#[cfg(test)]
396mod tests {
397 use super::{deserialize_pk, serialize_pk};
398 use crate::change::constraint_matches;
399 use crate::value::{owned_row, ColId, OwnedValue};
400
401 #[test]
402 fn pk_codec_round_trips() {
403 let cases: Vec<(Vec<OwnedValue>, Vec<ColId>)> = vec![
406 (vec![OwnedValue::Int(42)], vec![0]),
407 (vec![OwnedValue::Int(-7)], vec![0]),
408 (vec![OwnedValue::str("héllo:1")], vec![0]),
409 (vec![OwnedValue::str("a"), OwnedValue::Int(2)], vec![0, 1]),
410 (vec![OwnedValue::Int(1), OwnedValue::str("x:y")], vec![0, 1]),
411 ];
412 for (vals, pk) in cases {
413 let r = owned_row(vals.clone());
414 let s = serialize_pk(&r, &pk);
415 let c = deserialize_pk(&s, &pk);
416 assert!(
417 constraint_matches(&r, &c),
418 "round-trip failed for {vals:?} -> {s:?}"
419 );
420 }
421 }
422
423 #[test]
424 fn pk_codec_distinguishes_tuples_and_types() {
425 let r2 = owned_row(vec![OwnedValue::str("a"), OwnedValue::str("b")]);
428 let r1 = owned_row(vec![OwnedValue::str("ab")]);
429 assert_ne!(serialize_pk(&r2, &[0, 1]), serialize_pk(&r1, &[0]));
430
431 let int1 = owned_row(vec![OwnedValue::Int(1)]);
432 let str1 = owned_row(vec![OwnedValue::str("1")]);
433 assert_ne!(serialize_pk(&int1, &[0]), serialize_pk(&str1, &[0]));
434 }
435}