1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024-present, fjall-rs
// Copyright (c) 2026-present, Structured World Foundation
use crate::{
AnyTree, UserKey, UserValue, blob_tree::ingest::BlobIngestion, tree::ingest::Ingestion,
};
/// Unified ingestion builder over `AnyTree`
// Keep zero allocations and direct dispatch; boxing introduces heap indirection and `dyn` adds virtual dispatch.
// Ingestion calls use `&mut self` in tight loops; the active variant is stable and branch prediction makes the match cheap.
// Allowing this lint preserves hot-path performance at the cost of a larger enum size.
#[expect(clippy::large_enum_variant)]
pub enum AnyIngestion<'a> {
/// Ingestion for a standard LSM-tree
Standard(Ingestion<'a>),
/// Ingestion for a [`BlobTree`](crate::BlobTree) with KV separation
Blob(BlobIngestion<'a>),
}
impl AnyIngestion<'_> {
/// Writes a key-value pair.
///
/// # Errors
///
/// Will return `Err` if an IO error occurs.
pub fn write<K: Into<UserKey>, V: Into<UserValue>>(
&mut self,
key: K,
value: V,
) -> crate::Result<()> {
match self {
Self::Standard(i) => i.write(key.into(), value.into()),
Self::Blob(b) => b.write(key.into(), value.into()),
}
}
/// Writes a tombstone for a key.
///
/// # Errors
///
/// Will return `Err` if an IO error occurs.
pub fn write_tombstone<K: Into<UserKey>>(&mut self, key: K) -> crate::Result<()> {
match self {
Self::Standard(i) => i.write_tombstone(key.into()),
Self::Blob(b) => b.write_tombstone(key.into()),
}
}
/// Writes a weak tombstone for a key.
///
/// # Examples
///
/// ```
/// # use lsm_tree::Config;
/// # let folder = tempfile::tempdir()?;
/// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
/// #
/// let mut ingestion = tree.ingestion()?;
/// ingestion.write("a", "abc")?;
/// ingestion.write_weak_tombstone("b")?;
/// ingestion.finish()?;
/// #
/// # Ok::<(), lsm_tree::Error>(())
/// ```
///
/// # Errors
///
/// Will return `Err` if an IO error occurs.
pub fn write_weak_tombstone<K: Into<UserKey>>(&mut self, key: K) -> crate::Result<()> {
match self {
Self::Standard(i) => i.write_weak_tombstone(key.into()),
Self::Blob(b) => b.write_weak_tombstone(key.into()),
}
}
/// Writes a consumer-provided columnar batch (its value sub-columns) as one
/// columnar block, stored directly without re-transposing the value.
///
/// The batch carries the three intrinsic columns (`[key, seqno, value-type]`)
/// plus one or more value sub-columns. Its keys must be strictly increasing
/// (by the tree comparator) within the batch and after any previously written
/// data, and every per-row seqno must be `0`: [`finish`](Self::finish) assigns
/// the atomic global sequence number. The columnar layout must be enabled
/// (`columnar` in the runtime config) on a standard tree; a row-mode or blob
/// tree rejects the batch.
///
/// # Errors
///
/// Returns an error if the batch shape is invalid, the keys are not strictly
/// increasing, any row carries a non-zero seqno, the layout is not columnar,
/// a block write fails, or the tree is a blob tree (columnar ingest does not
/// support KV separation).
///
/// # Examples
///
/// ```
/// use lsm_tree::table::columnar::{Column, TypeTag, entries_to_column_batch};
/// use lsm_tree::{AnyTree, Config, InternalValue, ValueType};
///
/// let folder = tempfile::tempdir()?;
/// let any = Config::new(folder, Default::default(), Default::default()).open()?;
/// if let AnyTree::Standard(tree) = &any {
/// tree.update_runtime_config(|cfg| cfg.columnar = true)?;
/// }
///
/// // One row whose value is a single fixed-4 sub-column (id 3).
/// let mut batch = entries_to_column_batch(&[InternalValue::from_components(
/// b"k0".to_vec(),
/// b"x".to_vec(),
/// 0,
/// ValueType::Value,
/// )])?;
/// batch.columns.pop();
/// batch.columns.push(Column {
/// column_id: 3,
/// type_tag: TypeTag::Fixed(4),
/// validity: None,
/// data: vec![1, 0, 0, 0],
/// });
///
/// let mut ingestion = any.ingestion()?;
/// ingestion.write_columnar_batch(&batch)?;
/// ingestion.finish()?;
/// # Ok::<(), lsm_tree::Error>(())
/// ```
#[cfg(feature = "columnar")]
pub fn write_columnar_batch(
&mut self,
batch: &crate::table::columnar::ColumnBatch,
) -> crate::Result<()> {
match self {
Self::Standard(i) => i.write_columnar_batch(batch),
Self::Blob(_) => Err(crate::Error::FeatureUnsupported(
"columnar batch ingest is not supported for blob trees",
)),
}
}
/// Finalizes ingestion and registers created tables (and blob files if present).
///
/// # Errors
///
/// Will return `Err` if an IO error occurs.
pub fn finish(self) -> crate::Result<()> {
match self {
Self::Standard(i) => i.finish(),
Self::Blob(b) => b.finish(),
}
}
}
impl AnyTree {
/// Starts an ingestion for any tree type (standard or blob).
///
/// # Errors
///
/// Will return `Err` if an IO error occurs.
pub fn ingestion(&self) -> crate::Result<AnyIngestion<'_>> {
match self {
Self::Standard(t) => Ok(AnyIngestion::Standard(Ingestion::new(t)?)),
Self::Blob(b) => Ok(AnyIngestion::Blob(BlobIngestion::new(b)?)),
}
}
}