Skip to main content

mangle_db/
backend.rs

1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! IDB backend trait and supporting types for persistent IDB caching.
16
17use anyhow::Result;
18use mangle_common::Value;
19use serde::{Deserialize, Serialize};
20
21/// Metadata about a cached IDB snapshot.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct CacheMeta {
24    /// SHA-256 of the program source text.
25    #[serde(with = "hex_array")]
26    pub program_hash: [u8; 32],
27    /// Combined fingerprint of all EDB sources.
28    #[serde(with = "hex_vec")]
29    pub edb_fingerprint: Vec<u8>,
30    /// Unix timestamp when the cache was created.
31    pub created_at: u64,
32}
33
34/// A snapshot of derived (IDB) facts.
35pub struct IdbSnapshot {
36    /// (relation_name, facts) pairs.
37    pub relations: Vec<(String, Vec<Vec<Value>>)>,
38}
39
40/// Backend for persistent IDB caching.
41///
42/// Implementations store and retrieve IDB snapshots keyed by database name.
43/// The `CacheMeta` is used to determine if a cached snapshot is still valid.
44pub trait IdbBackend: Send + Sync {
45    /// Load a cached IDB snapshot for the given database.
46    /// Returns `None` if no cache exists.
47    fn load(&self, db_name: &str) -> Result<Option<(CacheMeta, IdbSnapshot)>>;
48
49    /// Save an IDB snapshot for the given database.
50    fn save(&self, db_name: &str, meta: &CacheMeta, snapshot: &IdbSnapshot) -> Result<()>;
51
52    /// Invalidate (delete) the cached IDB for the given database.
53    fn invalidate(&self, db_name: &str) -> Result<()>;
54}
55
56mod hex_array {
57    use serde::{Deserialize, Deserializer, Serializer};
58
59    pub fn serialize<S: Serializer>(bytes: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
60        let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
61        s.serialize_str(&hex)
62    }
63
64    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
65        let hex = String::deserialize(d)?;
66        let bytes: Vec<u8> = (0..hex.len())
67            .step_by(2)
68            .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).map_err(serde::de::Error::custom))
69            .collect::<Result<_, _>>()?;
70        let arr: [u8; 32] = bytes
71            .try_into()
72            .map_err(|_| serde::de::Error::custom("expected 32 bytes"))?;
73        Ok(arr)
74    }
75}
76
77mod hex_vec {
78    use serde::{Deserialize, Deserializer, Serializer};
79
80    pub fn serialize<S: Serializer>(bytes: &[u8], s: S) -> Result<S::Ok, S::Error> {
81        let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
82        s.serialize_str(&hex)
83    }
84
85    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
86        let hex = String::deserialize(d)?;
87        let bytes: Vec<u8> = (0..hex.len())
88            .step_by(2)
89            .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).map_err(serde::de::Error::custom))
90            .collect::<Result<_, _>>()?;
91        Ok(bytes)
92    }
93}