sqlite_knowledge_graph/version/mod.rs
1//! QuaQue-inspired versioning for the knowledge graph.
2//!
3//! Based on arXiv:2603.18654 — condensed relational model using bitstring validity.
4//! Each entity/relation row carries a `validity` bitstring; bit position is determined
5//! by a version's `bit_slot` (0–63), NOT by its `id`. Slots are reclaimable: when a
6//! version is deleted its slot is freed and the next `create_version` call reuses the
7//! lowest available slot. A row belongs to version V when
8//! `(validity & (1 << V.bit_slot)) != 0`. Version filtering uses bitwise operations.
9
10pub mod diff;
11pub mod merge;
12pub mod query;
13pub mod snapshot;
14pub mod store;
15
16use serde::{Deserialize, Serialize};
17
18/// Metadata for a named version/snapshot of the knowledge graph.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct Version {
21 pub id: i64,
22 pub name: String,
23 pub branch: String,
24 pub parent_id: Option<i64>,
25 pub description: Option<String>,
26 pub created_at: Option<i64>,
27 pub is_merged: bool,
28}
29
30/// Result of comparing two versions.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct VersionDiff {
33 pub added_entities: Vec<crate::graph::Entity>,
34 pub removed_entities: Vec<crate::graph::Entity>,
35 pub common_entities: Vec<crate::graph::Entity>,
36 pub added_relations: Vec<crate::graph::Relation>,
37 pub removed_relations: Vec<crate::graph::Relation>,
38 pub common_relations: Vec<crate::graph::Relation>,
39}
40
41/// Strategy for merging versions.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum MergeStrategy {
44 /// Entity/relation exists in merged version if it exists in ANY source version.
45 Union,
46 /// Entity/relation exists in merged version only if it exists in ALL source versions.
47 Intersection,
48}
49
50/// Maximum number of concurrent versions, bounded by the 64 usable bits of a
51/// signed 64-bit `validity` column.
52pub(crate) const MAX_VERSIONS: i64 = 64;
53
54/// Compute the validity bitmask for a `bit_slot`, or `None` if `slot` is outside
55/// `[0, 63]`.
56///
57/// Slots — not version ids — drive the bit position so the limit is exactly 64
58/// *concurrent* versions (slots are reclaimed on delete) instead of 64 version
59/// *creations* over the database lifetime. Returning `None` for an out-of-range
60/// slot keeps the helper panic-free even in release builds: a corrupted/manual
61/// DB row is surfaced as a regular error by the caller (see
62/// [`store::version_bit_for`]) instead of crashing on `1 << slot`.
63#[inline]
64pub(crate) fn bit_from_slot(slot: i64) -> Option<i64> {
65 if (0..MAX_VERSIONS).contains(&slot) {
66 Some(1 << slot)
67 } else {
68 None
69 }
70}