use super::core::EdgeStore;
use crate::edge::FreezeFeasibility;
use linkrs_core::types::{EdgeId, Timestamp};
use linkrs_core::StorageResult;
impl EdgeStore {
pub fn freeze_feasibility(&self, outgoing: bool, gid: usize) -> FreezeFeasibility {
if outgoing {
self.out_csr.freeze_feasibility(gid)
} else {
self.in_csr.freeze_feasibility(gid)
}
}
pub fn freeze_group(
&mut self,
outgoing: bool,
gid: usize,
cutoff: Timestamp,
reserve_ratio: f32,
) -> StorageResult<u64> {
let packed = if outgoing {
let shards = &mut self.out_csr;
let mvcc = &mut self.mvcc;
shards.freeze_group(
gid,
cutoff,
reserve_ratio,
&mut |edge_id: EdgeId, delete_ts: Timestamp| {
mvcc.record_deletion(edge_id, delete_ts);
},
)?
} else {
let shards = &mut self.in_csr;
let mvcc = &mut self.mvcc;
shards.freeze_group(
gid,
cutoff,
reserve_ratio,
&mut |edge_id: EdgeId, delete_ts: Timestamp| {
mvcc.record_deletion(edge_id, delete_ts);
},
)?
};
let drift = self.audit_copy_drift();
if !drift.is_empty() {
return Err(linkrs_core::StorageError::data_corruption(format!(
"freeze_group drift after pack: {}",
drift.join("; ")
)));
}
Ok(packed)
}
pub fn unfreeze_group(&mut self, outgoing: bool, gid: usize) -> StorageResult<u64> {
if outgoing {
self.out_csr.unfreeze_group(gid)
} else {
self.in_csr.unfreeze_group(gid)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::edge::{EdgeRecord, EdgeSchema, EdgeStrategy, RecordForm};
use crate::types::StoragePropertyDef;
use linkrs_core::types::{DataType, Timestamp, VertexId};
use linkrs_core::Value;
use super::super::config::EdgeTableConfig;
fn frozen_test_schema() -> EdgeSchema {
EdgeSchema {
label_id: 0,
label_name: "knows".into(),
src_label: 0,
dst_label: 0,
properties: vec![StoragePropertyDef {
name: "weight".into(),
data_type: DataType::Double,
nullable: false,
default_value: Some(Value::Double(0.0)),
}],
oe_strategy: EdgeStrategy::Multiple,
ie_strategy: EdgeStrategy::Multiple,
schema_version: 1,
record_form: RecordForm::default(),
}
}
fn sample_table() -> EdgeStore {
let mut table =
EdgeStore::with_config(frozen_test_schema(), EdgeTableConfig::default()).unwrap();
for src in 0..8u32 {
for k in 0..2u32 {
table
.insert_edge(
src,
src + k + 1,
0,
&[("weight".into(), Value::Double(1.0))],
100,
)
.unwrap();
}
}
assert!(table.delete_edge(0, 1, 0, 200).unwrap());
table
}
fn snapshot(table: &EdgeStore, ts: Timestamp) -> Vec<Vec<VertexId>> {
(0..8u32)
.map(|src| {
table
.out_edges(src, ts)
.into_iter()
.map(|edge| edge.dst_vid)
.collect()
})
.collect()
}
#[test]
fn freeze_keeps_reads_and_rejects_writes() {
let mut table = sample_table();
let before_freeze = snapshot(&table, 300);
let before_old = snapshot(&table, 150);
let out_packed = table.freeze_group(true, 0, Timestamp::MAX, 0.0).unwrap();
let in_packed = table.freeze_group(false, 0, Timestamp::MAX, 0.0).unwrap();
assert_eq!(out_packed, 15);
assert_eq!(in_packed, 15);
assert_eq!(snapshot(&table, 300), before_freeze);
assert_eq!(snapshot(&table, 150), before_old);
assert!(table.has_edge(0, 2, 0, 300));
assert!(!table.has_edge(0, 1, 0, 300));
assert!(table.has_edge(0, 1, 0, 150));
assert!(table.insert_edge(1, 9, 0, &[], 400).is_err());
assert!(table.delete_edge(1, 2, 0, 400).is_err());
assert_eq!(table.unfreeze_group(true, 0).unwrap(), 15);
assert_eq!(table.unfreeze_group(false, 0).unwrap(), 15);
assert_eq!(snapshot(&table, 300), before_freeze);
table.insert_edge(1, 9, 0, &[], 400).unwrap();
assert!(table.has_edge(1, 9, 0, 400));
}
#[test]
fn freeze_guards_bad_states() {
let mut table = sample_table();
assert!(table.freeze_group(true, 41, Timestamp::MAX, 0.0).is_err());
assert!(table.unfreeze_group(true, 0).is_err());
table.freeze_group(true, 0, Timestamp::MAX, 0.0).unwrap();
assert!(table.freeze_group(true, 0, Timestamp::MAX, 0.0).is_err());
}
#[test]
fn valued_bundled_freeze_preserves_values() {
use crate::edge::RecordFormPreference;
let config = EdgeTableConfig {
record_form: RecordFormPreference::Bundled,
..Default::default()
};
let mut bundled =
EdgeStore::with_config(frozen_test_schema(), config).expect("bundled table builds");
assert_eq!(bundled.schema().record_form, RecordForm::Bundled);
bundled
.insert_edge(0, 1, 0, &[("weight".into(), Value::Double(1.0))], 100)
.expect("valued insert");
assert!(bundled.freeze_feasibility(true, 0).is_ready());
assert!(bundled.freeze_feasibility(false, 0).is_ready());
let before: Vec<EdgeRecord> =
crate::edge::edge_table::iterator::EdgeTableScanIterator::new(&bundled, 200).collect();
assert_eq!(before.len(), 1);
bundled.freeze_group(true, 0, Timestamp::MAX, 0.0).unwrap();
bundled.freeze_group(false, 0, Timestamp::MAX, 0.0).unwrap();
assert!(bundled.out_csr.is_frozen(0));
assert!(bundled.in_csr.is_frozen(0));
let frozen: Vec<EdgeRecord> =
crate::edge::edge_table::iterator::EdgeTableScanIterator::new(&bundled, 200).collect();
assert_eq!(frozen.len(), 1);
assert_eq!(frozen[0].properties, before[0].properties);
assert_eq!(
bundled
.get_edge(0, 1, 0, 200)
.expect("frozen edge readable")
.properties,
vec![("weight".into(), Value::Double(1.0))]
);
assert!(bundled.insert_edge(1, 9, 0, &[], 400).is_err());
bundled.unfreeze_group(true, 0).unwrap();
bundled.unfreeze_group(false, 0).unwrap();
let after: Vec<EdgeRecord> =
crate::edge::edge_table::iterator::EdgeTableScanIterator::new(&bundled, 200).collect();
assert_eq!(after.len(), 1);
assert_eq!(after[0].properties, before[0].properties);
bundled
.insert_edge(1, 9, 0, &[("weight".into(), Value::Double(2.0))], 400)
.expect("writes resume after unfreeze");
assert_eq!(
bundled
.get_edge(1, 9, 0, 500)
.expect("new edge readable")
.properties,
vec![("weight".into(), Value::Double(2.0))]
);
}
#[test]
fn row_ordering_contract_mutable_unordered_frozen_sorted() {
use crate::edge::{EdgeSchema, EdgeStrategy, RecordForm};
use crate::types::StoragePropertyDef;
use linkrs_core::types::DataType;
let schema = EdgeSchema {
label_id: 0,
label_name: "ordered".into(),
src_label: 0,
dst_label: 0,
properties: vec![StoragePropertyDef {
name: "weight".into(),
data_type: DataType::Double,
nullable: false,
default_value: Some(Value::Double(0.0)),
}],
oe_strategy: EdgeStrategy::Multiple,
ie_strategy: EdgeStrategy::Multiple,
schema_version: 1,
record_form: RecordForm::default(),
};
let mut table = EdgeStore::with_config(schema, EdgeTableConfig::default()).unwrap();
for dst in [3u32, 1, 2] {
table
.insert_edge(0, dst, 0, &[], 100)
.expect("insert out of key order");
}
let mutable_order: Vec<i64> = table
.out_edges(0, 200)
.iter()
.map(|e| e.dst_vid.as_int64().unwrap_or(-1))
.collect();
assert_eq!(mutable_order, vec![3, 1, 2]);
table.freeze_group(true, 0, Timestamp::MAX, 0.0).unwrap();
let frozen_order: Vec<i64> = table
.out_edges(0, 200)
.iter()
.map(|e| e.dst_vid.as_int64().unwrap_or(-1))
.collect();
assert_eq!(frozen_order, vec![1, 2, 3]);
table.unfreeze_group(true, 0).unwrap();
let mut restored: Vec<i64> = table
.out_edges(0, 200)
.iter()
.map(|e| e.dst_vid.as_int64().unwrap_or(-1))
.collect();
restored.sort_unstable();
assert_eq!(restored, vec![1, 2, 3]);
}
}