pub struct NodeStore { /* private fields */ }Expand description
Persistent node property store rooted at a database directory.
On-disk layout:
{root}/nodes/{label_id}/hwm.bin — high-water mark (u64 LE)
{root}/nodes/{label_id}/col_{col_id}.bin — flat u64 column array
{root}/strings.bin — overflow string heap (SPA-212)The overflow heap is an append-only byte file. Each entry is a raw byte
sequence (no length prefix); the offset and length are encoded into the
TAG_BYTES_OVERFLOW u64 stored in the column file.
Implementations§
Source§impl NodeStore
impl NodeStore
Sourcepub fn encode_value(&self, val: &Value) -> Result<u64>
pub fn encode_value(&self, val: &Value) -> Result<u64>
Encode a Value for column storage, writing long Bytes strings to
the overflow heap (SPA-212).
Int64→ identical toValue::to_u64().Bytes≤ 7 B → inlineTAG_BYTESencoding, identical toValue::to_u64().Bytes> 7 B → appended tostrings.bin; returnsTAG_BYTES_OVERFLOWu64.Float→ 8 raw IEEE-754 bytes appended tostrings.bin; returns aTAG_FLOATu64 so all 64 float bits are preserved (SPA-267).
Sourcepub fn decode_raw_value(&self, raw: u64) -> Value
pub fn decode_raw_value(&self, raw: u64) -> Value
Decode a raw u64 column value back to a Value, reading the
overflow string heap when the tag is TAG_BYTES_OVERFLOW or TAG_FLOAT (SPA-212, SPA-267).
Handles all four tags:
TAG_INT64→Value::Int64TAG_BYTES→Value::Bytes(inline, ≤ 7 bytes)TAG_BYTES_OVERFLOW→Value::Bytes(from heap)TAG_FLOAT→Value::Float(8 raw IEEE-754 bytes from heap)
Sourcepub fn raw_str_matches(&self, raw: u64, s: &str) -> bool
pub fn raw_str_matches(&self, raw: u64, s: &str) -> bool
Check whether a raw stored u64 encodes a string equal to s.
Handles both inline (TAG_BYTES) and overflow (TAG_BYTES_OVERFLOW)
encodings (SPA-212). Used by WHERE-clause and prop-filter comparison.
Sourcepub fn hwm_for_label(&self, label_id: u32) -> Result<u64>
pub fn hwm_for_label(&self, label_id: u32) -> Result<u64>
Return the high-water mark (slot count) for a label.
Returns 0 if no nodes have been created for that label yet.
Sourcepub fn col_ids_for_label(&self, label_id: u32) -> Result<Vec<u32>>
pub fn col_ids_for_label(&self, label_id: u32) -> Result<Vec<u32>>
Discover all column IDs that currently exist on disk for label_id.
Scans the label directory for col_{id}.bin files and returns the
parsed col_id values. Used by create_node to zero-pad columns
that are not supplied for a new node (SPA-187).
Returns Err when the directory exists but cannot be read (e.g.
permissions failure or I/O error). A missing directory is not an
error — it simply means no nodes of this label have been created yet.
Sourcepub fn disk_hwm_for_label(&self, label_id: u32) -> Result<u64>
pub fn disk_hwm_for_label(&self, label_id: u32) -> Result<u64>
Return the on-disk high-water mark for a label, bypassing any
in-memory advances made by peek_next_slot.
Used by [WriteTx::merge_node] to limit the disk scan to only slots
that have actually been persisted.
Sourcepub fn peek_next_slot(&mut self, label_id: u32) -> Result<u32>
pub fn peek_next_slot(&mut self, label_id: u32) -> Result<u32>
Reserve the slot index that the next create_node call will use for
label_id, advancing the in-memory HWM so that the slot is not
assigned again within the same NodeStore instance.
This is used by [WriteTx::create_node] to pre-compute a NodeId
before the actual disk write, so the ID can be returned to the caller
while the write is deferred until commit (SPA-181).
The on-disk HWM is not updated here; it is updated when the
buffered NodeCreate operation is applied in commit().
Sourcepub fn create_node_at_slot(
&mut self,
label_id: u32,
slot: u32,
props: &[(u32, Value)],
) -> Result<NodeId>
pub fn create_node_at_slot( &mut self, label_id: u32, slot: u32, props: &[(u32, Value)], ) -> Result<NodeId>
Write a node at a pre-reserved slot (SPA-181 commit path).
Like [create_node] but uses the caller-specified slot index instead
of deriving it from the HWM. Used by [WriteTx::commit] to flush
buffered node-create operations in the exact order they were issued,
with slots that were already pre-allocated by [peek_next_slot].
Advances the on-disk HWM to slot + 1 (or higher if already past that).
Sourcepub fn create_node(
&mut self,
label_id: u32,
props: &[(u32, Value)],
) -> Result<NodeId>
pub fn create_node( &mut self, label_id: u32, props: &[(u32, Value)], ) -> Result<NodeId>
Create a new node in label_id with the given properties.
Returns the new NodeId packed as (label_id << 32) | slot.
§Slot alignment guarantee (SPA-187)
Every column file for label_id must have exactly node_count * 8
bytes so that slot N always refers to node N across all columns. When
a node is created without a value for an already-known column, that
column file is zero-padded to (slot + 1) * 8 bytes. The zero
sentinel is recognised by read_col_slot_nullable as “absent” and
surfaces as Value::Null in query results.
Sourcepub fn tombstone_node(&self, node_id: NodeId) -> Result<()>
pub fn tombstone_node(&self, node_id: NodeId) -> Result<()>
Write a deletion tombstone (u64::MAX) into col_0.bin for node_id.
Creates col_0.bin (and its parent directory) if it does not exist,
zero-padding all preceding slots. This ensures that nodes which were
created without any col_0 property are still properly marked as deleted
and become invisible to subsequent scans.
Called from [WriteTx::commit] when flushing a buffered NodeDelete.
Sourcepub fn set_node_col(
&self,
node_id: NodeId,
col_id: u32,
value: &Value,
) -> Result<()>
pub fn set_node_col( &self, node_id: NodeId, col_id: u32, value: &Value, ) -> Result<()>
Overwrite the value of a single column for an existing node.
Seeks to the slot’s offset within col_{col_id}.bin and writes the new
8-byte little-endian value in-place. Returns Err(NotFound) if the
slot does not exist yet.
Sourcepub fn upsert_node_col(
&self,
node_id: NodeId,
col_id: u32,
value: &Value,
) -> Result<()>
pub fn upsert_node_col( &self, node_id: NodeId, col_id: u32, value: &Value, ) -> Result<()>
Write or create a column value for a node, creating and zero-padding the column file if it does not yet exist.
Unlike [set_node_col], this method creates the column file and fills all
slots from 0 to slot - 1 with zeros before writing the target value.
This supports adding new property columns to existing nodes (Phase 7
set_property semantics).
Sourcepub fn get_node_raw(
&self,
node_id: NodeId,
col_ids: &[u32],
) -> Result<Vec<(u32, u64)>>
pub fn get_node_raw( &self, node_id: NodeId, col_ids: &[u32], ) -> Result<Vec<(u32, u64)>>
Retrieve all stored properties of a node.
Returns (col_id, raw_u64) pairs in the order the columns were defined.
The caller knows the schema (col IDs) from the catalog.
Sourcepub fn get_node_raw_nullable(
&self,
node_id: NodeId,
col_ids: &[u32],
) -> Result<Vec<(u32, Option<u64>)>>
pub fn get_node_raw_nullable( &self, node_id: NodeId, col_ids: &[u32], ) -> Result<Vec<(u32, Option<u64>)>>
Like [get_node_raw] but treats absent columns as None rather than
propagating Error::NotFound.
A column is considered absent when:
- Its column file does not exist (property never written for the label).
- Its column file is shorter than
slot + 1entries (sparse write — an earlier node never wrote this column; a later node that did write it padded the file, but this slot’s value was never explicitly stored).
This is the correct read path for IS NULL evaluation: absent properties
must appear as Value::Null, not as an error or as integer 0.
Sourcepub fn get_node(
&self,
node_id: NodeId,
col_ids: &[u32],
) -> Result<Vec<(u32, Value)>>
pub fn get_node( &self, node_id: NodeId, col_ids: &[u32], ) -> Result<Vec<(u32, Value)>>
Retrieve the typed property values for a node.
Convenience wrapper over [get_node_raw] that decodes every raw u64
back to a Value, reading the overflow string heap when needed (SPA-212).
Sourcepub fn read_col_all(&self, label_id: u32, col_id: u32) -> Result<Vec<u64>>
pub fn read_col_all(&self, label_id: u32, col_id: u32) -> Result<Vec<u64>>
Read the entire contents of col_{col_id}.bin for label_id as a
flat Vec<u64>. Returns an empty vec when the file does not exist yet.
This is used by crate::property_index::PropertyIndex::build to scan
all slot values in one fs::read call rather than one read_col_slot
per node, making index construction O(n) rather than O(n * syscall-overhead).