1use std::collections::HashMap;
8use std::sync::Arc;
9
10use xlog_core::{Schema, XlogError};
11use xlog_cuda::{CudaBuffer, CudaKernelProvider};
12
13pub struct RelationStore {
45 provider: Arc<CudaKernelProvider>,
47 relations: HashMap<String, VersionedCudaBuffer>,
49 mutation_epoch: u64,
55}
56
57struct VersionedCudaBuffer {
58 buffer: CudaBuffer,
59 version: u64,
60}
61
62impl RelationStore {
63 pub fn new(provider: Arc<CudaKernelProvider>) -> Self {
65 Self {
66 provider,
67 relations: HashMap::new(),
68 mutation_epoch: 0,
69 }
70 }
71
72 fn advance_mutation_epoch(&mut self) {
73 self.mutation_epoch = self
74 .mutation_epoch
75 .checked_add(1)
76 .expect("relation-store mutation epoch exhausted");
77 }
78
79 pub(crate) fn mutation_epoch(&self) -> u64 {
81 self.mutation_epoch
82 }
83
84 pub fn get(&self, name: &str) -> Option<&CudaBuffer> {
92 self.relations.get(name).map(|e| &e.buffer)
93 }
94
95 pub fn get_mut(&mut self, name: &str) -> Option<&mut CudaBuffer> {
103 if self.relations.contains_key(name) {
104 self.advance_mutation_epoch();
105 }
106 self.relations.get_mut(name).map(|e| {
107 e.version = e.version.saturating_add(1);
110 &mut e.buffer
111 })
112 }
113
114 pub fn get_with_version(&self, name: &str) -> Option<(&CudaBuffer, u64)> {
116 self.relations.get(name).map(|e| (&e.buffer, e.version))
117 }
118
119 pub fn version(&self, name: &str) -> Option<u64> {
121 self.relations.get(name).map(|e| e.version)
122 }
123
124 pub fn put(&mut self, name: &str, buffer: CudaBuffer) {
132 self.put_owned(name.to_string(), buffer);
133 }
134
135 pub fn put_owned(&mut self, name: String, buffer: CudaBuffer) {
142 self.advance_mutation_epoch();
143 let version = self
144 .relations
145 .get(name.as_str())
146 .map(|e| e.version.saturating_add(1))
147 .unwrap_or(1);
148 self.relations
149 .insert(name, VersionedCudaBuffer { buffer, version });
150 }
151
152 pub fn try_reserve_relations(&mut self, additional: usize) -> xlog_core::Result<()> {
158 self.relations.try_reserve(additional).map_err(|error| {
159 XlogError::Execution(format!(
160 "Failed to reserve capacity for {additional} relation entries: {error}"
161 ))
162 })
163 }
164
165 pub fn get_or_insert_empty(
178 &mut self,
179 name: &str,
180 schema: &Schema,
181 ) -> xlog_core::Result<&CudaBuffer> {
182 if !self.relations.contains_key(name) {
183 let buffer = self.provider.create_empty_buffer(schema.clone())?;
184 self.relations
185 .insert(name.to_string(), VersionedCudaBuffer { buffer, version: 1 });
186 self.advance_mutation_epoch();
187 }
188 Ok(&self
189 .relations
190 .get(name)
191 .expect("Relation must exist after insertion")
192 .buffer)
193 }
194
195 pub fn get_or_insert_empty_mut(
208 &mut self,
209 name: &str,
210 schema: &Schema,
211 ) -> xlog_core::Result<&mut CudaBuffer> {
212 if !self.relations.contains_key(name) {
213 let buffer = self.provider.create_empty_buffer(schema.clone())?;
214 self.relations
215 .insert(name.to_string(), VersionedCudaBuffer { buffer, version: 1 });
216 }
217 self.advance_mutation_epoch();
218 let entry = self
219 .relations
220 .get_mut(name)
221 .expect("Relation must exist after insertion");
222 entry.version = entry.version.saturating_add(1);
223 Ok(&mut entry.buffer)
224 }
225
226 pub fn contains(&self, name: &str) -> bool {
234 self.relations.contains_key(name)
235 }
236
237 pub fn remove(&mut self, name: &str) -> Option<CudaBuffer> {
245 let removed = self.relations.remove(name).map(|e| e.buffer);
246 if removed.is_some() {
247 self.advance_mutation_epoch();
248 }
249 removed
250 }
251
252 pub fn clear(&mut self) {
257 if !self.relations.is_empty() {
258 self.relations.clear();
259 self.advance_mutation_epoch();
260 }
261 }
262
263 pub fn len(&self) -> usize {
265 self.relations.len()
266 }
267
268 pub fn is_empty(&self) -> bool {
270 self.relations.is_empty()
271 }
272
273 pub fn names(&self) -> impl Iterator<Item = &str> {
275 self.relations.keys().map(|s| s.as_str())
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use std::sync::Arc;
283 use xlog_core::{MemoryBudget, ScalarType};
284 use xlog_cuda::{CudaKernelProvider, CudaProviderBuilder};
285
286 fn setup_provider() -> Option<Arc<CudaKernelProvider>> {
287 CudaProviderBuilder::new(0, MemoryBudget::with_limit(1024 * 1024 * 1024))
288 .build()
289 .ok()
290 .map(Arc::new)
291 }
292
293 fn setup_store() -> Option<(RelationStore, Arc<CudaKernelProvider>)> {
294 let provider = setup_provider()?;
295 let store = RelationStore::new(provider.clone());
296 Some((store, provider))
297 }
298
299 fn test_schema() -> Schema {
300 Schema::new(vec![
301 ("a".to_string(), ScalarType::U32),
302 ("b".to_string(), ScalarType::U64),
303 ])
304 }
305
306 fn device_row_count(provider: &CudaKernelProvider, buffer: &CudaBuffer) -> u32 {
307 let mut host_rows = [0u32];
308 provider
309 .device()
310 .inner()
311 .dtoh_sync_copy_into(buffer.num_rows_device(), &mut host_rows)
312 .expect("dtoh row count");
313 host_rows[0]
314 }
315
316 fn make_buffer(provider: &CudaKernelProvider, schema: Schema, rows: usize) -> CudaBuffer {
317 if schema.arity() == 0 {
318 if rows == 0 {
319 return provider.create_empty_buffer(schema).expect("empty buffer");
320 }
321 let rows_u32 = u32::try_from(rows).expect("row count fits u32");
322 let mut d_num_rows = provider.memory().alloc::<u32>(1).expect("alloc");
323 provider
324 .device()
325 .inner()
326 .htod_sync_copy_into(&[rows_u32], &mut d_num_rows)
327 .expect("htod row count");
328 return CudaBuffer::from_columns(Vec::new(), rows as u64, d_num_rows, schema);
329 }
330 if rows == 0 {
331 return provider.create_empty_buffer(schema).expect("empty buffer");
332 }
333 let mut columns: Vec<Vec<u8>> = Vec::with_capacity(schema.arity());
334 for col_idx in 0..schema.arity() {
335 let size = schema
336 .column_type(col_idx)
337 .map(|t| t.size_bytes())
338 .unwrap_or(4);
339 columns.push(vec![0u8; rows * size]);
340 }
341 let slices: Vec<&[u8]> = columns.iter().map(|c| c.as_slice()).collect();
342 provider
343 .create_buffer_from_slices(&slices, schema)
344 .expect("buffer")
345 }
346
347 #[test]
348 fn mutation_epoch_detects_clear_reinsert_aba_without_counting_reserve() {
349 let Some((mut store, provider)) = setup_store() else {
350 return;
351 };
352 let schema = test_schema();
353 assert_eq!(store.mutation_epoch(), 0);
354 store
355 .try_reserve_relations(4)
356 .expect("reserve must not be a semantic mutation");
357 assert_eq!(store.mutation_epoch(), 0);
358
359 store.put("rows", make_buffer(&provider, schema.clone(), 1));
360 let after_first_put = store.mutation_epoch();
361 assert_eq!(after_first_put, 1);
362 assert_eq!(store.version("rows"), Some(1));
363
364 store.clear();
365 let after_clear = store.mutation_epoch();
366 assert!(after_clear > after_first_put);
367 store.put("rows", make_buffer(&provider, schema, 1));
368 assert_eq!(store.version("rows"), Some(1));
369 assert!(
370 store.mutation_epoch() > after_clear,
371 "clear-and-reinsert must not recreate the original transaction revision"
372 );
373 }
374
375 #[test]
376 fn test_new_store_is_empty() {
377 let Some((store, _provider)) = setup_store() else {
378 return;
379 };
380 assert!(store.is_empty());
381 assert_eq!(store.len(), 0);
382 }
383
384 #[test]
385 fn test_put_and_get() {
386 let Some((mut store, provider)) = setup_store() else {
387 return;
388 };
389 let buffer = provider
390 .create_empty_buffer(Schema::new(vec![]))
391 .expect("empty");
392
393 store.put("test_rel", buffer);
394
395 assert!(store.contains("test_rel"));
396 assert!(!store.is_empty());
397 assert_eq!(store.len(), 1);
398
399 let retrieved = store.get("test_rel");
400 assert!(retrieved.is_some());
401 }
402
403 #[test]
404 fn test_get_nonexistent() {
405 let Some((store, _provider)) = setup_store() else {
406 return;
407 };
408 assert!(store.get("nonexistent").is_none());
409 }
410
411 #[test]
412 fn test_contains() {
413 let Some((mut store, provider)) = setup_store() else {
414 return;
415 };
416
417 assert!(!store.contains("test"));
418
419 store.put(
420 "test",
421 provider
422 .create_empty_buffer(Schema::new(vec![]))
423 .expect("empty"),
424 );
425
426 assert!(store.contains("test"));
427 assert!(!store.contains("other"));
428 }
429
430 #[test]
431 fn test_remove() {
432 let Some((mut store, provider)) = setup_store() else {
433 return;
434 };
435 store.put(
436 "test",
437 provider
438 .create_empty_buffer(Schema::new(vec![]))
439 .expect("empty"),
440 );
441
442 assert!(store.contains("test"));
443
444 let removed = store.remove("test");
445 assert!(removed.is_some());
446 assert!(!store.contains("test"));
447 assert!(store.is_empty());
448 }
449
450 #[test]
451 fn test_remove_nonexistent() {
452 let Some((mut store, _provider)) = setup_store() else {
453 return;
454 };
455 let removed = store.remove("nonexistent");
456 assert!(removed.is_none());
457 }
458
459 #[test]
460 fn test_clear() {
461 let Some((mut store, provider)) = setup_store() else {
462 return;
463 };
464 let empty = provider
465 .create_empty_buffer(Schema::new(vec![]))
466 .expect("empty");
467 store.put("rel1", empty);
468 store.put(
469 "rel2",
470 provider
471 .create_empty_buffer(Schema::new(vec![]))
472 .expect("empty"),
473 );
474 store.put(
475 "rel3",
476 provider
477 .create_empty_buffer(Schema::new(vec![]))
478 .expect("empty"),
479 );
480
481 assert_eq!(store.len(), 3);
482
483 store.clear();
484
485 assert!(store.is_empty());
486 assert_eq!(store.len(), 0);
487 }
488
489 #[test]
490 fn test_get_or_insert_empty_existing() {
491 let Some((mut store, provider)) = setup_store() else {
492 return;
493 };
494 let schema = test_schema();
495
496 let buffer = make_buffer(&provider, schema.clone(), 100);
497 store.put("existing", buffer);
498
499 let result = store.get_or_insert_empty("existing", &schema).unwrap();
500 assert_eq!(device_row_count(&provider, result), 100);
501 assert_eq!(result.schema(), &schema);
502 assert_eq!(store.len(), 1);
503 }
504
505 #[test]
506 fn test_get_or_insert_empty_nonexistent() {
507 let Some((mut store, provider)) = setup_store() else {
508 return;
509 };
510 let schema = test_schema();
511
512 assert!(store.is_empty());
513
514 let result = store.get_or_insert_empty("nonexistent", &schema).unwrap();
515 assert_eq!(device_row_count(&provider, result), 0);
516 assert_eq!(result.schema(), &schema);
517 assert!(result.is_empty());
518
519 assert!(store.contains("nonexistent"));
520 assert_eq!(store.len(), 1);
521 }
522
523 #[test]
524 fn test_get_mut() {
525 let Some((mut store, provider)) = setup_store() else {
526 return;
527 };
528 let buffer = make_buffer(&provider, Schema::new(vec![]), 10);
529 store.put("test", buffer);
530
531 {
532 let buf_mut = store.get_mut("test").unwrap();
533 buf_mut.set_row_capacity(50);
534 provider
535 .device()
536 .inner()
537 .htod_sync_copy_into(&[50u32], buf_mut.num_rows_device_mut())
538 .expect("htod row count");
539 }
540
541 assert_eq!(device_row_count(&provider, store.get("test").unwrap()), 50);
542 }
543
544 #[test]
545 fn test_get_mut_nonexistent() {
546 let Some((mut store, _provider)) = setup_store() else {
547 return;
548 };
549 assert!(store.get_mut("nonexistent").is_none());
550 }
551
552 #[test]
553 fn test_get_or_insert_empty_mut() {
554 let Some((mut store, provider)) = setup_store() else {
555 return;
556 };
557 let schema = test_schema();
558
559 {
560 let buf_mut = store.get_or_insert_empty_mut("new_rel", &schema).unwrap();
561 assert_eq!(device_row_count(&provider, buf_mut), 0);
562 buf_mut.set_row_capacity(42);
563 provider
564 .device()
565 .inner()
566 .htod_sync_copy_into(&[42u32], buf_mut.num_rows_device_mut())
567 .expect("htod row count");
568 }
569
570 assert!(store.contains("new_rel"));
571 assert_eq!(
572 device_row_count(&provider, store.get("new_rel").unwrap()),
573 42
574 );
575 }
576
577 #[test]
578 fn test_put_replaces_existing() {
579 let Some((mut store, provider)) = setup_store() else {
580 return;
581 };
582
583 let buffer1 = make_buffer(&provider, Schema::new(vec![]), 10);
584 let buffer2 = make_buffer(&provider, Schema::new(vec![]), 20);
585
586 store.put("test", buffer1);
587 assert_eq!(device_row_count(&provider, store.get("test").unwrap()), 10);
588
589 store.put("test", buffer2);
590 assert_eq!(device_row_count(&provider, store.get("test").unwrap()), 20);
591 assert_eq!(store.len(), 1);
592 }
593
594 #[test]
595 fn reserved_owned_put_reuses_capacity_and_versions_replacements() {
596 let Some((mut store, provider)) = setup_store() else {
597 return;
598 };
599 let capacity_before = store.relations.capacity();
600
601 store
602 .try_reserve_relations(1)
603 .expect("reserve one relation entry");
604 let reserved_capacity = store.relations.capacity();
605 assert!(reserved_capacity > capacity_before);
606
607 store.put_owned(
608 "owned_relation".to_string(),
609 make_buffer(&provider, Schema::new(vec![]), 10),
610 );
611 assert_eq!(store.relations.capacity(), reserved_capacity);
612 assert_eq!(store.version("owned_relation"), Some(1));
613
614 store.put_owned(
615 "owned_relation".to_string(),
616 make_buffer(&provider, Schema::new(vec![]), 20),
617 );
618 assert_eq!(store.relations.capacity(), reserved_capacity);
619 assert_eq!(store.version("owned_relation"), Some(2));
620 assert_eq!(
621 device_row_count(&provider, store.get("owned_relation").unwrap()),
622 20
623 );
624 }
625
626 #[test]
627 fn test_names_iterator() {
628 let Some((mut store, provider)) = setup_store() else {
629 return;
630 };
631 store.put(
632 "alpha",
633 provider
634 .create_empty_buffer(Schema::new(vec![]))
635 .expect("empty"),
636 );
637 store.put(
638 "beta",
639 provider
640 .create_empty_buffer(Schema::new(vec![]))
641 .expect("empty"),
642 );
643 store.put(
644 "gamma",
645 provider
646 .create_empty_buffer(Schema::new(vec![]))
647 .expect("empty"),
648 );
649
650 let mut names: Vec<&str> = store.names().collect();
651 names.sort();
652
653 assert_eq!(names, vec!["alpha", "beta", "gamma"]);
654 }
655
656 #[test]
657 fn test_multiple_operations() {
658 let Some((mut store, provider)) = setup_store() else {
659 return;
660 };
661
662 let empty = provider
663 .create_empty_buffer(Schema::new(vec![]))
664 .expect("empty");
665 store.put("a", empty);
666 store.put(
667 "b",
668 provider
669 .create_empty_buffer(Schema::new(vec![]))
670 .expect("empty"),
671 );
672 store.put(
673 "c",
674 provider
675 .create_empty_buffer(Schema::new(vec![]))
676 .expect("empty"),
677 );
678 assert_eq!(store.len(), 3);
679
680 store.remove("b");
681 assert_eq!(store.len(), 2);
682 assert!(!store.contains("b"));
683
684 store.put("a", make_buffer(&provider, Schema::new(vec![]), 50));
685 assert_eq!(store.len(), 2);
686 assert_eq!(device_row_count(&provider, store.get("a").unwrap()), 50);
687
688 store.clear();
689 assert!(store.is_empty());
690 }
691}