use std::collections::HashSet;
use linkrs_core::types::{EdgeIdentifier, VertexId};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WriteSet {
pub vertices: HashSet<VertexId>,
pub edges: HashSet<EdgeIdentifier>,
pub edge_endpoints: HashSet<VertexId>,
pub deleted_vertices: HashSet<VertexId>,
pub schema_resources: HashSet<String>,
pub index_resources: HashSet<String>,
pub read_ranges: Vec<ReadRange>,
}
impl WriteSet {
pub fn new() -> Self {
Self::default()
}
pub fn record_vertex(&mut self, vid: VertexId) {
self.vertices.insert(vid);
}
pub fn record_vertex_delete(&mut self, vid: VertexId) {
self.vertices.insert(vid);
self.deleted_vertices.insert(vid);
}
pub fn record_edge(&mut self, edge: EdgeIdentifier) {
self.edge_endpoints.insert(edge.src_vid);
self.edge_endpoints.insert(edge.dst_vid);
self.edges.insert(edge);
}
pub fn record_schema_resource(&mut self, resource: impl Into<String>) {
self.schema_resources.insert(resource.into());
}
pub fn record_index_resource(&mut self, resource: impl Into<String>) {
self.index_resources.insert(resource.into());
}
pub fn record_read_range(&mut self, range: ReadRange) {
self.read_ranges.push(range);
}
pub fn covers_vertex(&self, vid: &VertexId) -> bool {
self.vertices.contains(vid)
}
pub fn covers_edge(&self, edge: &EdgeIdentifier) -> bool {
self.edges.contains(edge)
}
pub fn has_read_range_conflict_with(&self, committed: &WriteSet) -> bool {
for range in &self.read_ranges {
for vid in &committed.vertices {
if range.contains(vid) {
return true;
}
}
for edge in &committed.edges {
if range.contains(&edge.src_vid) || range.contains(&edge.dst_vid) {
return true;
}
}
}
false
}
pub fn has_read_conflict_with(&self, committed: &WriteSet) -> bool {
if committed
.vertices
.iter()
.any(|vid| self.vertices.contains(vid))
{
return true;
}
if committed.edges.iter().any(|edge| self.edges.contains(edge)) {
return true;
}
if !self
.schema_resources
.is_disjoint(&committed.schema_resources)
|| !self.index_resources.is_disjoint(&committed.index_resources)
{
return true;
}
self.has_read_range_conflict_with(committed)
}
pub fn is_empty(&self) -> bool {
self.vertices.is_empty()
&& self.edges.is_empty()
&& self.schema_resources.is_empty()
&& self.index_resources.is_empty()
&& self.read_ranges.is_empty()
}
pub fn is_empty_for_certification(&self) -> bool {
self.is_empty()
}
pub fn size(&self) -> usize {
self.vertices.len() + self.edges.len()
}
pub fn ssi_resources(&self) -> Vec<ResourceId> {
let mut resources = Vec::new();
for vid in &self.vertices {
resources.push(ResourceId::Vertex(*vid));
}
for edge in &self.edges {
resources.push(ResourceId::Edge(*edge));
}
for res in &self.schema_resources {
resources.push(ResourceId::Schema(res.clone()));
}
for res in &self.index_resources {
resources.push(ResourceId::Index(res.clone()));
}
resources
}
pub fn has_conflict_with(&self, other: &WriteSet) -> bool {
if !self.vertices.is_disjoint(&other.vertices) {
return true;
}
if !self.edges.is_disjoint(&other.edges) {
return true;
}
if !self.deleted_vertices.is_disjoint(&other.edge_endpoints)
|| !other.deleted_vertices.is_disjoint(&self.edge_endpoints)
{
return true;
}
if !self.schema_resources.is_disjoint(&other.schema_resources)
|| !self.index_resources.is_disjoint(&other.index_resources)
{
return true;
}
if (!self.schema_resources.is_empty()
&& (!other.vertices.is_empty() || !other.edges.is_empty()))
|| (!other.schema_resources.is_empty()
&& (!self.vertices.is_empty() || !self.edges.is_empty()))
{
return true;
}
false
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadRange {
pub label: String,
pub column: Option<String>,
pub start: Option<VertexId>,
pub end: Option<VertexId>,
}
impl ReadRange {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
column: None,
start: None,
end: None,
}
}
pub fn with_column(mut self, column: impl Into<String>) -> Self {
self.column = Some(column.into());
self
}
pub fn with_start(mut self, start: VertexId) -> Self {
self.start = Some(start);
self
}
pub fn with_end(mut self, end: VertexId) -> Self {
self.end = Some(end);
self
}
pub fn contains(&self, vid: &VertexId) -> bool {
if let Some(ref start) = self.start {
let cmp = vid.as_bytes().cmp(start.as_bytes());
if cmp == std::cmp::Ordering::Less {
return false;
}
}
if let Some(ref end) = self.end {
let cmp = vid.as_bytes().cmp(end.as_bytes());
if cmp == std::cmp::Ordering::Greater {
return false;
}
}
true
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ResourceId {
Vertex(VertexId),
Edge(linkrs_core::types::EdgeIdentifier),
Schema(String),
Index(String),
}
#[derive(Debug, Clone, Default)]
pub struct SsiState {
read_resources: HashSet<ResourceId>,
write_resources: HashSet<ResourceId>,
}
impl SsiState {
pub fn new() -> Self {
Self {
read_resources: HashSet::new(),
write_resources: HashSet::new(),
}
}
pub fn record_read(&mut self, resource: ResourceId) {
self.read_resources.insert(resource);
}
pub fn record_write(&mut self, resource: ResourceId) {
self.write_resources.insert(resource);
}
pub fn read_resources(&self) -> &HashSet<ResourceId> {
&self.read_resources
}
pub fn write_resources(&self) -> &HashSet<ResourceId> {
&self.write_resources
}
pub fn is_empty(&self) -> bool {
self.read_resources.is_empty() && self.write_resources.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use linkrs_core::types::EdgeIdentifier;
fn vid(n: i64) -> VertexId {
VertexId::try_from_int64(n).expect("test vertex id")
}
#[test]
fn test_read_range_conflict_with_vertex_write() {
let mut reader = WriteSet::new();
reader.record_read_range(
ReadRange::new("person")
.with_start(vid(1))
.with_end(vid(10)),
);
let mut committed = WriteSet::new();
committed.record_vertex(vid(5));
assert!(reader.has_read_range_conflict_with(&committed));
let mut outside = WriteSet::new();
outside.record_vertex(vid(50));
assert!(!reader.has_read_range_conflict_with(&outside));
}
#[test]
fn test_read_range_conflict_with_edge_phantom() {
let mut reader = WriteSet::new();
reader.record_read_range(
ReadRange::new("person")
.with_start(vid(1))
.with_end(vid(10)),
);
let mut committed = WriteSet::new();
committed.record_edge(EdgeIdentifier::new(0, vid(3), 0, vid(99), 0, 0));
assert!(reader.has_read_range_conflict_with(&committed));
let mut outside = WriteSet::new();
outside.record_edge(EdgeIdentifier::new(0, vid(50), 0, vid(60), 0, 0));
assert!(!reader.has_read_range_conflict_with(&outside));
}
}