1use std::collections::HashMap;
5
6use lance_core::Error;
7use lance_core::deepsize::DeepSizeOf;
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11use crate::format::pb;
12
13pub const MEM_WAL_INDEX_NAME: &str = "__lance_mem_wal";
14
15pub type ShardId = Uuid;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeepSizeOf)]
20pub struct SsTable {
21 pub generation: u64,
22 pub path: String,
23}
24
25impl From<&SsTable> for pb::SsTable {
26 fn from(sstable: &SsTable) -> Self {
27 Self {
28 generation: sstable.generation,
29 path: sstable.path.clone(),
30 }
31 }
32}
33
34impl From<pb::SsTable> for SsTable {
35 fn from(sstable: pb::SsTable) -> Self {
36 Self {
37 generation: sstable.generation,
38 path: sstable.path,
39 }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)]
45pub struct CompactedSsTable {
46 pub shard_id: Uuid,
47 pub generation: u64,
48}
49
50impl DeepSizeOf for CompactedSsTable {
51 fn deep_size_of_children(&self, _context: &mut lance_core::deepsize::Context) -> usize {
52 0 }
54}
55
56impl CompactedSsTable {
57 pub fn new(shard_id: Uuid, generation: u64) -> Self {
58 Self {
59 shard_id,
60 generation,
61 }
62 }
63}
64
65impl From<&CompactedSsTable> for pb::CompactedSsTable {
66 fn from(sstable: &CompactedSsTable) -> Self {
67 Self {
68 shard_id: Some((&sstable.shard_id).into()),
69 generation: sstable.generation,
70 }
71 }
72}
73
74impl TryFrom<pb::CompactedSsTable> for CompactedSsTable {
75 type Error = Error;
76
77 fn try_from(sstable: pb::CompactedSsTable) -> lance_core::Result<Self> {
78 let shard_id = sstable
79 .shard_id
80 .as_ref()
81 .map(Uuid::try_from)
82 .ok_or_else(|| Error::invalid_input("Missing shard_id in CompactedSsTable"))??;
83 Ok(Self {
84 shard_id,
85 generation: sstable.generation,
86 })
87 }
88}
89
90#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, DeepSizeOf)]
93pub struct IndexCatchupProgress {
94 pub index_name: String,
95 pub caught_up_generations: Vec<CompactedSsTable>,
96}
97
98impl IndexCatchupProgress {
99 pub fn new(index_name: String, caught_up_generations: Vec<CompactedSsTable>) -> Self {
100 Self {
101 index_name,
102 caught_up_generations,
103 }
104 }
105
106 pub fn caught_up_generation_for_shard(&self, shard_id: &Uuid) -> Option<u64> {
109 self.caught_up_generations
110 .iter()
111 .find(|sstable| &sstable.shard_id == shard_id)
112 .map(|sstable| sstable.generation)
113 }
114}
115
116impl From<&IndexCatchupProgress> for pb::IndexCatchupProgress {
117 fn from(icp: &IndexCatchupProgress) -> Self {
118 Self {
119 index_name: icp.index_name.clone(),
120 caught_up_generations: icp
121 .caught_up_generations
122 .iter()
123 .map(|sstable| sstable.into())
124 .collect(),
125 }
126 }
127}
128
129impl TryFrom<pb::IndexCatchupProgress> for IndexCatchupProgress {
130 type Error = Error;
131
132 fn try_from(icp: pb::IndexCatchupProgress) -> lance_core::Result<Self> {
133 Ok(Self {
134 index_name: icp.index_name,
135 caught_up_generations: icp
136 .caught_up_generations
137 .into_iter()
138 .map(CompactedSsTable::try_from)
139 .collect::<lance_core::Result<_>>()?,
140 })
141 }
142}
143
144#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
150pub enum ShardStatus {
151 #[default]
153 Active,
154 Sealed,
156}
157
158impl ShardStatus {
159 fn to_i32(self) -> i32 {
161 match self {
162 Self::Active => 0,
163 Self::Sealed => 1,
164 }
165 }
166
167 fn from_i32(v: i32) -> Self {
170 match v {
171 1 => Self::Sealed,
172 _ => Self::Active,
173 }
174 }
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
180pub struct ShardManifest {
181 pub shard_id: Uuid,
182 pub version: u64,
183 pub shard_spec_id: u32,
184 pub shard_field_values: HashMap<String, Vec<u8>>,
189 pub writer_epoch: u64,
190 pub replay_after_wal_entry_position: u64,
196 pub wal_entry_position_last_seen: u64,
200 pub current_generation: u64,
201 pub sstables: Vec<SsTable>,
202 pub status: ShardStatus,
205}
206
207impl DeepSizeOf for ShardManifest {
208 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
209 self.shard_field_values.deep_size_of_children(context)
210 + self.sstables.deep_size_of_children(context)
211 }
212}
213
214impl From<&ShardManifest> for pb::ShardManifest {
215 fn from(rm: &ShardManifest) -> Self {
216 Self {
217 shard_id: Some((&rm.shard_id).into()),
218 version: rm.version,
219 shard_spec_id: rm.shard_spec_id,
220 shard_field_entries: rm
221 .shard_field_values
222 .iter()
223 .map(|(k, v)| pb::ShardFieldEntry {
224 field_id: k.clone(),
225 value: v.clone(),
226 })
227 .collect(),
228 writer_epoch: rm.writer_epoch,
229 replay_after_wal_entry_position: rm.replay_after_wal_entry_position,
230 wal_entry_position_last_seen: rm.wal_entry_position_last_seen,
231 current_generation: rm.current_generation,
232 sstables: rm.sstables.iter().map(|sstable| sstable.into()).collect(),
233 status: rm.status.to_i32(),
234 }
235 }
236}
237
238impl TryFrom<pb::ShardManifest> for ShardManifest {
239 type Error = Error;
240
241 fn try_from(rm: pb::ShardManifest) -> lance_core::Result<Self> {
242 let shard_id = rm
243 .shard_id
244 .as_ref()
245 .map(Uuid::try_from)
246 .ok_or_else(|| Error::invalid_input("Missing shard_id in ShardManifest"))??;
247 let shard_field_values = rm
248 .shard_field_entries
249 .into_iter()
250 .map(|e| (e.field_id, e.value))
251 .collect();
252 Ok(Self {
253 shard_id,
254 version: rm.version,
255 shard_spec_id: rm.shard_spec_id,
256 shard_field_values,
257 writer_epoch: rm.writer_epoch,
258 replay_after_wal_entry_position: rm.replay_after_wal_entry_position,
259 wal_entry_position_last_seen: rm.wal_entry_position_last_seen,
260 current_generation: rm.current_generation,
261 sstables: rm.sstables.into_iter().map(SsTable::from).collect(),
262 status: ShardStatus::from_i32(rm.status),
263 })
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeepSizeOf)]
269pub struct ShardingField {
270 pub field_id: String,
271 pub source_ids: Vec<i32>,
272 pub transform: Option<String>,
273 pub expression: Option<String>,
274 pub result_type: String,
275 pub parameters: HashMap<String, String>,
276}
277
278impl From<&ShardingField> for pb::ShardingField {
279 fn from(rf: &ShardingField) -> Self {
280 Self {
281 field_id: rf.field_id.clone(),
282 source_ids: rf.source_ids.clone(),
283 transform: rf.transform.clone(),
284 expression: rf.expression.clone(),
285 result_type: rf.result_type.clone(),
286 parameters: rf.parameters.clone(),
287 }
288 }
289}
290
291impl From<pb::ShardingField> for ShardingField {
292 fn from(rf: pb::ShardingField) -> Self {
293 Self {
294 field_id: rf.field_id,
295 source_ids: rf.source_ids,
296 transform: rf.transform,
297 expression: rf.expression,
298 result_type: rf.result_type,
299 parameters: rf.parameters,
300 }
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DeepSizeOf)]
306pub struct ShardingSpec {
307 pub spec_id: u32,
308 pub fields: Vec<ShardingField>,
309}
310
311impl From<&ShardingSpec> for pb::ShardingSpec {
312 fn from(rs: &ShardingSpec) -> Self {
313 Self {
314 spec_id: rs.spec_id,
315 fields: rs.fields.iter().map(|f| f.into()).collect(),
316 }
317 }
318}
319
320impl From<pb::ShardingSpec> for ShardingSpec {
321 fn from(rs: pb::ShardingSpec) -> Self {
322 Self {
323 spec_id: rs.spec_id,
324 fields: rs.fields.into_iter().map(ShardingField::from).collect(),
325 }
326 }
327}
328
329#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, DeepSizeOf)]
331pub struct MemWalIndexDetails {
332 pub snapshot_ts_millis: i64,
333 pub num_shards: u32,
334 pub inline_snapshots: Option<Vec<u8>>,
335 pub sharding_specs: Vec<ShardingSpec>,
336 pub maintained_indexes: Vec<String>,
337 pub compacted_sstables: Vec<CompactedSsTable>,
338 pub index_catchup: Vec<IndexCatchupProgress>,
339 pub writer_config_defaults: HashMap<String, String>,
346}
347
348impl From<&MemWalIndexDetails> for pb::MemWalIndexDetails {
349 fn from(details: &MemWalIndexDetails) -> Self {
350 Self {
351 snapshot_ts_millis: details.snapshot_ts_millis,
352 num_shards: details.num_shards,
353 inline_snapshots: details.inline_snapshots.clone(),
354 sharding_specs: details.sharding_specs.iter().map(|rs| rs.into()).collect(),
355 maintained_indexes: details.maintained_indexes.clone(),
356 compacted_sstables: details
357 .compacted_sstables
358 .iter()
359 .map(|sstable| sstable.into())
360 .collect(),
361 index_catchup: details.index_catchup.iter().map(|icp| icp.into()).collect(),
362 writer_config_defaults: details.writer_config_defaults.clone(),
363 }
364 }
365}
366
367impl TryFrom<pb::MemWalIndexDetails> for MemWalIndexDetails {
368 type Error = Error;
369
370 fn try_from(details: pb::MemWalIndexDetails) -> lance_core::Result<Self> {
371 Ok(Self {
372 snapshot_ts_millis: details.snapshot_ts_millis,
373 num_shards: details.num_shards,
374 inline_snapshots: details.inline_snapshots,
375 sharding_specs: details
376 .sharding_specs
377 .into_iter()
378 .map(ShardingSpec::from)
379 .collect(),
380 maintained_indexes: details.maintained_indexes,
381 compacted_sstables: details
382 .compacted_sstables
383 .into_iter()
384 .map(CompactedSsTable::try_from)
385 .collect::<lance_core::Result<_>>()?,
386 index_catchup: details
387 .index_catchup
388 .into_iter()
389 .map(IndexCatchupProgress::try_from)
390 .collect::<lance_core::Result<_>>()?,
391 writer_config_defaults: details.writer_config_defaults,
392 })
393 }
394}
395
396#[derive(Debug, Clone, PartialEq, Eq, DeepSizeOf)]
398pub struct MemWalIndex {
399 pub details: MemWalIndexDetails,
400}
401
402impl MemWalIndex {
403 pub fn new(details: MemWalIndexDetails) -> Self {
404 Self { details }
405 }
406
407 pub fn compacted_generation_for_shard(&self, shard_id: &Uuid) -> Option<u64> {
408 self.details
409 .compacted_sstables
410 .iter()
411 .find(|sstable| &sstable.shard_id == shard_id)
412 .map(|sstable| sstable.generation)
413 }
414
415 pub fn index_caught_up_generation(&self, index_name: &str, shard_id: &Uuid) -> Option<u64> {
418 self.details
419 .index_catchup
420 .iter()
421 .find(|icp| icp.index_name == index_name)
422 .and_then(|icp| icp.caught_up_generation_for_shard(shard_id))
423 }
424
425 pub fn is_index_caught_up(&self, index_name: &str, shard_id: &Uuid) -> bool {
428 let compacted_gen = self.compacted_generation_for_shard(shard_id).unwrap_or(0);
429 let caught_up_gen = self.index_caught_up_generation(index_name, shard_id);
430
431 caught_up_gen.is_none_or(|generation| generation >= compacted_gen)
433 }
434}