use std::collections::HashMap;
use std::sync::atomic::AtomicU64;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use crate::graph::node::Span;
use crate::graph::unified::edge::EdgeKind;
use crate::graph::unified::edge::delta::DeltaOp;
use crate::graph::unified::edge::kind::{
DbQueryType, ExportKind, FfiConvention, HttpMethod, LifetimeConstraintKind, MacroExpansionKind,
MqProtocol, ResolvedVia, TableWriteOp, TypeOfContext,
};
use crate::graph::unified::file::id::FileId;
use crate::graph::unified::node::id::NodeId;
use crate::graph::unified::storage::{
ClasspathNodeMetadata, MacroNodeMetadata, NodeFlags, NodeMetadataStore, StoredEntry,
TypedMetadata,
};
use crate::graph::unified::string::StringId;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum EdgeKindV10 {
Defines,
Contains,
Calls {
argument_count: u8,
is_async: bool,
},
References,
Imports {
alias: Option<StringId>,
is_wildcard: bool,
},
Exports {
kind: ExportKind,
alias: Option<StringId>,
},
TypeOf {
context: Option<TypeOfContext>,
index: Option<u16>,
name: Option<StringId>,
},
Inherits,
Implements,
LifetimeConstraint {
constraint_kind: LifetimeConstraintKind,
},
TraitMethodBinding {
trait_name: StringId,
impl_type: StringId,
is_ambiguous: bool,
},
MacroExpansion {
expansion_kind: MacroExpansionKind,
is_verified: bool,
},
FfiCall {
convention: FfiConvention,
},
HttpRequest {
method: HttpMethod,
url: Option<StringId>,
},
GrpcCall {
service: StringId,
method: StringId,
},
WebAssemblyCall,
DbQuery {
query_type: DbQueryType,
table: Option<StringId>,
},
TableRead {
table_name: StringId,
schema: Option<StringId>,
},
TableWrite {
table_name: StringId,
schema: Option<StringId>,
operation: TableWriteOp,
},
TriggeredBy {
trigger_name: StringId,
schema: Option<StringId>,
},
MessageQueue {
protocol: MqProtocol,
topic: Option<StringId>,
},
WebSocket {
event: Option<StringId>,
},
GraphQLOperation {
operation: StringId,
},
ProcessExec {
command: StringId,
},
FileIpc {
path_pattern: Option<StringId>,
},
ProtocolCall {
protocol: StringId,
metadata: Option<StringId>,
},
GenericBound,
AnnotatedWith,
AnnotationParam,
LambdaCaptures,
ModuleExports,
ModuleRequires,
ModuleOpens,
ModuleProvides,
TypeArgument,
ExtensionReceiver,
CompanionOf,
SealedPermit,
}
pub(crate) fn translate_edge_v10_to_v11(v10: EdgeKindV10) -> EdgeKind {
match v10 {
EdgeKindV10::Defines => EdgeKind::Defines,
EdgeKindV10::Contains => EdgeKind::Contains,
EdgeKindV10::Calls {
argument_count,
is_async,
} => EdgeKind::Calls {
argument_count,
is_async,
resolved_via: ResolvedVia::Direct,
},
EdgeKindV10::References => EdgeKind::References,
EdgeKindV10::Imports { alias, is_wildcard } => EdgeKind::Imports { alias, is_wildcard },
EdgeKindV10::Exports { kind, alias } => EdgeKind::Exports { kind, alias },
EdgeKindV10::TypeOf {
context,
index,
name,
} => EdgeKind::TypeOf {
context,
index,
name,
},
EdgeKindV10::Inherits => EdgeKind::Inherits,
EdgeKindV10::Implements => EdgeKind::Implements,
EdgeKindV10::LifetimeConstraint { constraint_kind } => {
EdgeKind::LifetimeConstraint { constraint_kind }
}
EdgeKindV10::TraitMethodBinding {
trait_name,
impl_type,
is_ambiguous,
} => EdgeKind::TraitMethodBinding {
trait_name,
impl_type,
is_ambiguous,
},
EdgeKindV10::MacroExpansion {
expansion_kind,
is_verified,
} => EdgeKind::MacroExpansion {
expansion_kind,
is_verified,
},
EdgeKindV10::FfiCall { convention } => EdgeKind::FfiCall { convention },
EdgeKindV10::HttpRequest { method, url } => EdgeKind::HttpRequest { method, url },
EdgeKindV10::GrpcCall { service, method } => EdgeKind::GrpcCall { service, method },
EdgeKindV10::WebAssemblyCall => EdgeKind::WebAssemblyCall,
EdgeKindV10::DbQuery { query_type, table } => EdgeKind::DbQuery { query_type, table },
EdgeKindV10::TableRead { table_name, schema } => EdgeKind::TableRead { table_name, schema },
EdgeKindV10::TableWrite {
table_name,
schema,
operation,
} => EdgeKind::TableWrite {
table_name,
schema,
operation,
},
EdgeKindV10::TriggeredBy {
trigger_name,
schema,
} => EdgeKind::TriggeredBy {
trigger_name,
schema,
},
EdgeKindV10::MessageQueue { protocol, topic } => EdgeKind::MessageQueue { protocol, topic },
EdgeKindV10::WebSocket { event } => EdgeKind::WebSocket { event },
EdgeKindV10::GraphQLOperation { operation } => EdgeKind::GraphQLOperation { operation },
EdgeKindV10::ProcessExec { command } => EdgeKind::ProcessExec { command },
EdgeKindV10::FileIpc { path_pattern } => EdgeKind::FileIpc { path_pattern },
EdgeKindV10::ProtocolCall { protocol, metadata } => {
EdgeKind::ProtocolCall { protocol, metadata }
}
EdgeKindV10::GenericBound => EdgeKind::GenericBound,
EdgeKindV10::AnnotatedWith => EdgeKind::AnnotatedWith,
EdgeKindV10::AnnotationParam => EdgeKind::AnnotationParam,
EdgeKindV10::LambdaCaptures => EdgeKind::LambdaCaptures,
EdgeKindV10::ModuleExports => EdgeKind::ModuleExports,
EdgeKindV10::ModuleRequires => EdgeKind::ModuleRequires,
EdgeKindV10::ModuleOpens => EdgeKind::ModuleOpens,
EdgeKindV10::ModuleProvides => EdgeKind::ModuleProvides,
EdgeKindV10::TypeArgument => EdgeKind::TypeArgument,
EdgeKindV10::ExtensionReceiver => EdgeKind::ExtensionReceiver,
EdgeKindV10::CompanionOf => EdgeKind::CompanionOf,
EdgeKindV10::SealedPermit => EdgeKind::SealedPermit,
}
}
pub(crate) fn translate_edge_v11_to_v10(v11: EdgeKind) -> EdgeKindV10 {
match v11 {
EdgeKind::Defines => EdgeKindV10::Defines,
EdgeKind::Contains => EdgeKindV10::Contains,
EdgeKind::Calls {
argument_count,
is_async,
resolved_via: _,
} => EdgeKindV10::Calls {
argument_count,
is_async,
},
EdgeKind::References => EdgeKindV10::References,
EdgeKind::Imports { alias, is_wildcard } => EdgeKindV10::Imports { alias, is_wildcard },
EdgeKind::Exports { kind, alias } => EdgeKindV10::Exports { kind, alias },
EdgeKind::TypeOf {
context,
index,
name,
} => EdgeKindV10::TypeOf {
context,
index,
name,
},
EdgeKind::Inherits => EdgeKindV10::Inherits,
EdgeKind::Implements => EdgeKindV10::Implements,
EdgeKind::LifetimeConstraint { constraint_kind } => {
EdgeKindV10::LifetimeConstraint { constraint_kind }
}
EdgeKind::TraitMethodBinding {
trait_name,
impl_type,
is_ambiguous,
} => EdgeKindV10::TraitMethodBinding {
trait_name,
impl_type,
is_ambiguous,
},
EdgeKind::MacroExpansion {
expansion_kind,
is_verified,
} => EdgeKindV10::MacroExpansion {
expansion_kind,
is_verified,
},
EdgeKind::FfiCall { convention } => EdgeKindV10::FfiCall { convention },
EdgeKind::HttpRequest { method, url } => EdgeKindV10::HttpRequest { method, url },
EdgeKind::GrpcCall { service, method } => EdgeKindV10::GrpcCall { service, method },
EdgeKind::WebAssemblyCall => EdgeKindV10::WebAssemblyCall,
EdgeKind::DbQuery { query_type, table } => EdgeKindV10::DbQuery { query_type, table },
EdgeKind::TableRead { table_name, schema } => EdgeKindV10::TableRead { table_name, schema },
EdgeKind::TableWrite {
table_name,
schema,
operation,
} => EdgeKindV10::TableWrite {
table_name,
schema,
operation,
},
EdgeKind::TriggeredBy {
trigger_name,
schema,
} => EdgeKindV10::TriggeredBy {
trigger_name,
schema,
},
EdgeKind::MessageQueue { protocol, topic } => EdgeKindV10::MessageQueue { protocol, topic },
EdgeKind::WebSocket { event } => EdgeKindV10::WebSocket { event },
EdgeKind::GraphQLOperation { operation } => EdgeKindV10::GraphQLOperation { operation },
EdgeKind::ProcessExec { command } => EdgeKindV10::ProcessExec { command },
EdgeKind::FileIpc { path_pattern } => EdgeKindV10::FileIpc { path_pattern },
EdgeKind::ProtocolCall { protocol, metadata } => {
EdgeKindV10::ProtocolCall { protocol, metadata }
}
EdgeKind::GenericBound => EdgeKindV10::GenericBound,
EdgeKind::AnnotatedWith => EdgeKindV10::AnnotatedWith,
EdgeKind::AnnotationParam => EdgeKindV10::AnnotationParam,
EdgeKind::LambdaCaptures => EdgeKindV10::LambdaCaptures,
EdgeKind::ModuleExports => EdgeKindV10::ModuleExports,
EdgeKind::ModuleRequires => EdgeKindV10::ModuleRequires,
EdgeKind::ModuleOpens => EdgeKindV10::ModuleOpens,
EdgeKind::ModuleProvides => EdgeKindV10::ModuleProvides,
EdgeKind::TypeArgument => EdgeKindV10::TypeArgument,
EdgeKind::ExtensionReceiver => EdgeKindV10::ExtensionReceiver,
EdgeKind::CompanionOf => EdgeKindV10::CompanionOf,
EdgeKind::SealedPermit => EdgeKindV10::SealedPermit,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct DeltaEdgeV10 {
pub(crate) source: NodeId,
pub(crate) target: NodeId,
pub(crate) kind: EdgeKindV10,
pub(crate) seq: u64,
pub(crate) op: DeltaOp,
pub(crate) file: FileId,
pub(crate) spans: Vec<Span>,
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct DeltaBufferV10 {
pub(crate) edges: HashMap<FileId, Vec<DeltaEdgeV10>>,
pub(crate) edge_count: usize,
pub(crate) byte_size: usize,
#[serde(with = "atomic_u64_serde_v10")]
pub(crate) seq_counter: AtomicU64,
}
pub(crate) mod atomic_u64_serde_v10 {
use std::sync::atomic::{AtomicU64, Ordering};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(value: &AtomicU64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
value.load(Ordering::SeqCst).serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<AtomicU64, D::Error>
where
D: Deserializer<'de>,
{
let value = u64::deserialize(deserializer)?;
Ok(AtomicU64::new(value))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct CsrGraphV10 {
pub(crate) node_count: usize,
pub(crate) row_ptr: Vec<u32>,
pub(crate) col_idx: Vec<NodeId>,
pub(crate) edge_kind: Vec<EdgeKindV10>,
pub(crate) edge_seq: Vec<u64>,
pub(crate) edge_spans: Vec<Vec<Span>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct EdgeStoreV10 {
pub(crate) csr: Option<CsrGraphV10>,
pub(crate) csr_tombstones: Vec<bool>,
pub(crate) csr_version: u64,
pub(crate) delta: DeltaBufferV10,
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct BidirectionalEdgeStoreV10 {
#[serde(with = "rwlock_edge_store_serde_v10")]
pub(crate) forward: RwLock<EdgeStoreV10>,
#[serde(with = "rwlock_edge_store_serde_v10")]
pub(crate) reverse: RwLock<EdgeStoreV10>,
}
pub(crate) mod rwlock_edge_store_serde_v10 {
use parking_lot::RwLock;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::EdgeStoreV10;
pub fn serialize<S>(value: &RwLock<EdgeStoreV10>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
value.read().serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<RwLock<EdgeStoreV10>, D::Error>
where
D: Deserializer<'de>,
{
let store = EdgeStoreV10::deserialize(deserializer)?;
Ok(RwLock::new(store))
}
}
pub(crate) fn translate_delta_edge_v10_to_v11(
v10: DeltaEdgeV10,
) -> crate::graph::unified::edge::delta::DeltaEdge {
use crate::graph::unified::edge::delta::DeltaEdge;
DeltaEdge {
source: v10.source,
target: v10.target,
kind: translate_edge_v10_to_v11(v10.kind),
seq: v10.seq,
op: v10.op,
file: v10.file,
spans: v10.spans,
}
}
pub(crate) fn translate_delta_buffer_v10_to_v11(
v10: DeltaBufferV10,
) -> crate::graph::unified::edge::delta::DeltaBuffer {
use std::sync::atomic::Ordering;
use crate::graph::unified::edge::delta::DeltaBuffer;
let mut buffer = DeltaBuffer::new();
let DeltaBufferV10 {
edges,
edge_count: _,
byte_size: _,
seq_counter,
} = v10;
for (_file, edge_vec) in edges {
for edge in edge_vec {
buffer.push(translate_delta_edge_v10_to_v11(edge));
}
}
buffer.advance_seq_to(seq_counter.load(Ordering::SeqCst));
buffer
}
pub(crate) fn translate_csr_graph_v10_to_v11(
v10: CsrGraphV10,
) -> crate::graph::unified::storage::csr::CsrGraph {
use crate::graph::unified::storage::csr::CsrGraph;
let edge_kind_v11: Vec<EdgeKind> = v10
.edge_kind
.into_iter()
.map(translate_edge_v10_to_v11)
.collect();
CsrGraph::from_raw(
v10.node_count,
v10.row_ptr,
v10.col_idx,
edge_kind_v11,
v10.edge_seq,
v10.edge_spans,
)
}
pub(crate) fn translate_edge_store_v10_to_v11(
v10: EdgeStoreV10,
) -> crate::graph::unified::edge::store::EdgeStore {
use crate::graph::unified::edge::store::EdgeStore;
let csr = v10.csr.map(translate_csr_graph_v10_to_v11);
let delta = translate_delta_buffer_v10_to_v11(v10.delta);
EdgeStore::from_parts_v10_upconvert(csr, v10.csr_tombstones, v10.csr_version, delta)
}
pub(crate) fn translate_bidirectional_edge_store_v10_to_v11(
v10: BidirectionalEdgeStoreV10,
) -> crate::graph::unified::BidirectionalEdgeStore {
use crate::graph::unified::BidirectionalEdgeStore;
let forward = translate_edge_store_v10_to_v11(v10.forward.into_inner());
let reverse = translate_edge_store_v10_to_v11(v10.reverse.into_inner());
BidirectionalEdgeStore::from_parts_v10_upconvert(forward, reverse)
}
pub(crate) fn translate_delta_edge_v11_to_v10(
v11: crate::graph::unified::edge::delta::DeltaEdge,
) -> DeltaEdgeV10 {
let crate::graph::unified::edge::delta::DeltaEdge {
source,
target,
kind,
seq,
op,
file,
spans,
} = v11;
DeltaEdgeV10 {
source,
target,
kind: translate_edge_v11_to_v10(kind),
seq,
op,
file,
spans,
}
}
pub(crate) fn translate_delta_buffer_v11_to_v10(
v11: &crate::graph::unified::edge::delta::DeltaBuffer,
) -> DeltaBufferV10 {
let mut edges: HashMap<FileId, Vec<DeltaEdgeV10>> = HashMap::new();
let mut edge_count: usize = 0;
let mut byte_size: usize = 0;
for edge in v11.iter() {
let v10_edge = translate_delta_edge_v11_to_v10(edge.clone());
byte_size += edge.byte_size();
edge_count += 1;
edges.entry(v10_edge.file).or_default().push(v10_edge);
}
DeltaBufferV10 {
edges,
edge_count,
byte_size,
seq_counter: AtomicU64::new(v11.current_seq()),
}
}
pub(crate) fn translate_csr_graph_v11_to_v10(
v11: &crate::graph::unified::storage::csr::CsrGraph,
) -> CsrGraphV10 {
let edge_kind_v10: Vec<EdgeKindV10> = v11
.edge_kind_slice()
.iter()
.cloned()
.map(translate_edge_v11_to_v10)
.collect();
CsrGraphV10 {
node_count: v11.node_count(),
row_ptr: v11.row_ptr_slice().to_vec(),
col_idx: v11.col_idx_slice().to_vec(),
edge_kind: edge_kind_v10,
edge_seq: v11.edge_seq_slice().to_vec(),
edge_spans: v11.edge_spans_slice().to_vec(),
}
}
pub(crate) fn translate_edge_store_v11_to_v10(
v11: &crate::graph::unified::edge::store::EdgeStore,
) -> EdgeStoreV10 {
let csr = v11.csr().map(translate_csr_graph_v11_to_v10);
let csr_tombstones = v11.csr_tombstones_slice().to_vec();
let csr_version = v11.csr_version();
let delta = translate_delta_buffer_v11_to_v10(v11.delta());
EdgeStoreV10 {
csr,
csr_tombstones,
csr_version,
delta,
}
}
pub(crate) fn translate_bidirectional_edge_store_v11_to_v10(
v11: &crate::graph::unified::BidirectionalEdgeStore,
) -> BidirectionalEdgeStoreV10 {
let forward = translate_edge_store_v11_to_v10(&v11.forward());
let reverse = translate_edge_store_v11_to_v10(&v11.reverse());
BidirectionalEdgeStoreV10 {
forward: RwLock::new(forward),
reverse: RwLock::new(reverse),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[allow(dead_code)] pub(crate) enum NodeMetadataV10 {
Macro(MacroNodeMetadata),
Classpath(ClasspathNodeMetadata),
Synthetic,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct NodeMetadataEntryV10 {
pub(crate) index: u32,
pub(crate) generation: u64,
pub(crate) kind: u8,
pub(crate) macro_data: Option<MacroNodeMetadata>,
pub(crate) classpath_data: Option<ClasspathNodeMetadata>,
}
pub(crate) const LEGACY_V7_KIND_MACRO: u8 = 0;
pub(crate) const LEGACY_V7_KIND_CLASSPATH: u8 = 1;
pub(crate) const LEGACY_V7_KIND_SYNTHETIC: u8 = 2;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub(crate) struct NodeMetadataStoreV10 {
pub(crate) entries: Vec<NodeMetadataEntryV10>,
}
pub(crate) fn translate_metadata_store_v10_to_v11(
v10: NodeMetadataStoreV10,
) -> Result<NodeMetadataStore, super::snapshot::PersistenceError> {
use super::snapshot::PersistenceError;
let mut store = NodeMetadataStore::new();
for entry in v10.entries {
let node_id = NodeId::new(entry.index, entry.generation);
let stored = match entry.kind {
LEGACY_V7_KIND_MACRO => {
let payload = entry.macro_data.ok_or_else(|| {
PersistenceError::Serialization(format!(
"V10 metadata entry ({}, {}) declared `Macro` kind but carried \
no `macro_data` payload",
entry.index, entry.generation,
))
})?;
StoredEntry {
typed: Some(TypedMetadata::Macro(payload)),
flags: NodeFlags::EMPTY,
}
}
LEGACY_V7_KIND_CLASSPATH => {
let payload = entry.classpath_data.ok_or_else(|| {
PersistenceError::Serialization(format!(
"V10 metadata entry ({}, {}) declared `Classpath` kind but \
carried no `classpath_data` payload",
entry.index, entry.generation,
))
})?;
StoredEntry {
typed: Some(TypedMetadata::Classpath(payload)),
flags: NodeFlags::EMPTY,
}
}
LEGACY_V7_KIND_SYNTHETIC => StoredEntry {
typed: None,
flags: NodeFlags::SYNTHETIC,
},
other => {
return Err(PersistenceError::Serialization(format!(
"V10 metadata entry ({}, {}) carried unknown legacy kind \
discriminant {other}",
entry.index, entry.generation,
)));
}
};
store.insert_entry(node_id, stored);
}
Ok(store)
}
pub(crate) fn translate_metadata_store_v11_to_v10(
v11: &NodeMetadataStore,
) -> Result<NodeMetadataStoreV10, super::snapshot::PersistenceError> {
use super::snapshot::PersistenceError;
let mut entries = Vec::new();
for ((index, generation), stored) in v11.iter_entries() {
let (kind, macro_data, classpath_data) = match (&stored.typed, stored.flags) {
(Some(TypedMetadata::Macro(m)), flags) if flags == NodeFlags::EMPTY => {
(LEGACY_V7_KIND_MACRO, Some(m.clone()), None)
}
(Some(TypedMetadata::Classpath(c)), flags) if flags == NodeFlags::EMPTY => {
(LEGACY_V7_KIND_CLASSPATH, None, Some(c.clone()))
}
(None, flags) if flags == NodeFlags::SYNTHETIC => {
(LEGACY_V7_KIND_SYNTHETIC, None, None)
}
(typed, flags) => {
return Err(PersistenceError::Serialization(format!(
"cannot encode V11 metadata entry ({index}, {generation}) into V10 \
wire format: typed={typed:?}, flags=0x{:02x} — V10 wire format only \
supports `Macro` / `Classpath` / `Synthetic` with no flag co-occurrence",
flags.bits(),
)));
}
};
entries.push(NodeMetadataEntryV10 {
index,
generation,
kind,
macro_data,
classpath_data,
});
}
Ok(NodeMetadataStoreV10 { entries })
}