1use std::collections::HashSet;
12
13use byteorder::{LittleEndian, WriteBytesExt};
14use rustc_hash::{FxHashMap, FxHashSet};
15
16use crate::dsl::Field;
17use crate::error::{Error, Result};
18use crate::segment::SegmentSnapshot;
19use crate::structures::BloomFilter;
20
21const BLOOM_BITS_PER_KEY: usize = 10;
23
24const BLOOM_HEADROOM: usize = 100_000;
26
27pub const PK_BLOOM_FILE: &str = "pk_bloom.bin";
29
30const PK_BLOOM_MAGIC: u32 = 0x504B424C; pub struct PkSegmentData {
38 pub segment_id: String,
39 pub fast_fields: FxHashMap<u32, crate::structures::fast_field::FastFieldReader>,
40}
41
42pub struct PrimaryKeyIndex {
51 field: Field,
52 state: parking_lot::Mutex<PrimaryKeyState>,
53 committed_data: Vec<PkSegmentData>,
56 _snapshot: Option<SegmentSnapshot>,
58}
59
60struct PrimaryKeyState {
61 bloom: BloomFilter,
62 uncommitted: FxHashSet<Vec<u8>>,
63}
64
65impl PrimaryKeyIndex {
66 pub fn new(field: Field, pk_data: Vec<PkSegmentData>, snapshot: SegmentSnapshot) -> Self {
74 let mut total_keys: usize = 0;
76 for data in &pk_data {
77 if let Some(ff) = data.fast_fields.get(&field.0)
78 && let Some(dict) = ff.text_dict()
79 {
80 total_keys += dict.len() as usize;
81 }
82 }
83
84 let mut bloom = BloomFilter::new(total_keys + BLOOM_HEADROOM, BLOOM_BITS_PER_KEY);
85
86 for data in &pk_data {
88 if let Some(ff) = data.fast_fields.get(&field.0)
89 && let Some(dict) = ff.text_dict()
90 {
91 for key in dict.iter() {
92 bloom.insert(key.as_bytes());
93 }
94 }
95 }
96
97 let bloom_bytes = bloom.size_bytes();
98 log::info!(
99 "[primary_key] bloom filter: {} keys, {}",
100 total_keys,
101 crate::format_bytes(bloom_bytes as u64),
102 );
103
104 Self {
105 field,
106 state: parking_lot::Mutex::new(PrimaryKeyState {
107 bloom,
108 uncommitted: FxHashSet::default(),
109 }),
110 committed_data: pk_data,
111 _snapshot: Some(snapshot),
112 }
113 }
114
115 pub fn from_persisted(
121 field: Field,
122 bloom: BloomFilter,
123 pk_data: Vec<PkSegmentData>,
124 snapshot: SegmentSnapshot,
125 ) -> Self {
126 log::info!(
127 "[primary_key] bloom filter loaded from cache: {}",
128 crate::format_bytes(bloom.size_bytes() as u64),
129 );
130
131 Self {
132 field,
133 state: parking_lot::Mutex::new(PrimaryKeyState {
134 bloom,
135 uncommitted: FxHashSet::default(),
136 }),
137 committed_data: pk_data,
138 _snapshot: Some(snapshot),
139 }
140 }
141
142 pub fn write_bloom_cache(
145 &self,
146 segment_ids: &[String],
147 writer: &mut (impl std::io::Write + ?Sized),
148 ) -> std::io::Result<()> {
149 let state = self.state.lock();
150 write_pk_bloom(writer, segment_ids, &state.bloom)
151 }
152
153 pub fn memory_bytes(&self) -> usize {
155 let state = self.state.lock();
156 state.bloom.size_bytes() + state.uncommitted.len() * 32 }
158
159 pub fn check_and_insert(&self, doc: &crate::dsl::Document) -> Result<()> {
165 let value = doc
166 .get_first(self.field)
167 .ok_or_else(|| Error::Document("Missing primary key field".into()))?;
168 let key = value
169 .as_text()
170 .ok_or_else(|| Error::Document("Primary key must be text".into()))?;
171 if key.is_empty() {
172 return Err(Error::Document("Primary key must not be empty".into()));
173 }
174
175 let key_bytes = key.as_bytes();
176
177 {
178 let mut state = self.state.lock();
179
180 if !state.bloom.may_contain(key_bytes) {
182 state.bloom.insert(key_bytes);
183 state.uncommitted.insert(key_bytes.to_vec());
184 return Ok(());
185 }
186
187 if state.uncommitted.contains(key_bytes) {
189 return Err(Error::DuplicatePrimaryKey(key.to_string()));
190 }
191 }
192 for data in &self.committed_data {
195 if let Some(ff) = data.fast_fields.get(&self.field.0)
196 && let Some(dict) = ff.text_dict()
197 && dict.ordinal(key).is_some()
198 {
199 return Err(Error::DuplicatePrimaryKey(key.to_string()));
200 }
201 }
202
203 let mut state = self.state.lock();
206 if state.uncommitted.contains(key_bytes) {
207 return Err(Error::DuplicatePrimaryKey(key.to_string()));
208 }
209
210 state.bloom.insert(key_bytes);
212 state.uncommitted.insert(key_bytes.to_vec());
213 Ok(())
214 }
215
216 pub fn refresh_incremental(&mut self, new_data: Vec<PkSegmentData>, snapshot: SegmentSnapshot) {
223 let state = self.state.get_mut();
226 for data in &new_data {
227 if let Some(ff) = data.fast_fields.get(&self.field.0)
228 && let Some(dict) = ff.text_dict()
229 {
230 for key in dict.iter() {
231 state.bloom.insert(key.as_bytes());
232 }
233 }
234 }
235 state.uncommitted.clear();
236 self.replace_committed_data(new_data, snapshot);
237 }
238
239 pub fn refresh_replacement(&mut self, new_data: Vec<PkSegmentData>, snapshot: SegmentSnapshot) {
246 self.replace_committed_data(new_data, snapshot);
247 }
248
249 fn replace_committed_data(&mut self, new_data: Vec<PkSegmentData>, snapshot: SegmentSnapshot) {
250 let new_seg_ids: HashSet<&str> =
251 snapshot.segment_ids().iter().map(|s| s.as_str()).collect();
252 let mut kept: Vec<PkSegmentData> = self
253 .committed_data
254 .drain(..)
255 .filter(|d| new_seg_ids.contains(d.segment_id.as_str()))
256 .collect();
257 kept.extend(new_data);
258 self.committed_data = kept;
259 self._snapshot = Some(snapshot);
260 }
261
262 pub fn committed_segment_ids(&self) -> impl Iterator<Item = &str> {
264 self.committed_data.iter().map(|d| d.segment_id.as_str())
265 }
266
267 pub fn rollback_uncommitted_key(&self, doc: &crate::dsl::Document) {
271 if let Some(value) = doc.get_first(self.field)
272 && let Some(key) = value.as_text()
273 {
274 self.state.lock().uncommitted.remove(key.as_bytes());
275 }
276 }
277
278 pub fn clear_uncommitted(&mut self) {
282 self.state.get_mut().uncommitted.clear();
283 }
284}
285
286fn write_pk_bloom(
290 writer: &mut (impl std::io::Write + ?Sized),
291 segment_ids: &[String],
292 bloom: &BloomFilter,
293) -> std::io::Result<()> {
294 writer.write_u32::<LittleEndian>(PK_BLOOM_MAGIC)?;
295 writer.write_u32::<LittleEndian>(u32::try_from(segment_ids.len()).map_err(|_| {
296 std::io::Error::new(
297 std::io::ErrorKind::InvalidInput,
298 "primary-key bloom segment count exceeds u32::MAX",
299 )
300 })?)?;
301 for seg_id in segment_ids {
302 let bytes = seg_id.as_bytes();
303 if bytes.len() > 32 {
304 return Err(std::io::Error::new(
305 std::io::ErrorKind::InvalidInput,
306 "primary-key bloom segment ID exceeds 32 bytes",
307 ));
308 }
309 writer.write_all(bytes)?;
310 writer.write_all(&[0u8; 32][..32 - bytes.len()])?;
312 }
313 bloom.write_to(writer)
314}
315
316pub fn deserialize_pk_bloom(data: &[u8]) -> Option<(HashSet<String>, BloomFilter)> {
319 if data.len() < 8 {
320 return None;
321 }
322 let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
323 if magic != PK_BLOOM_MAGIC {
324 return None;
325 }
326 let num_segments = u32::from_le_bytes([data[4], data[5], data[6], data[7]]) as usize;
327 let header_end = 8 + num_segments * 32;
328 if data.len() < header_end + BloomFilter::SERIALIZED_HEADER_SIZE {
329 return None;
330 }
331 let mut segment_ids = HashSet::with_capacity(num_segments);
332 for i in 0..num_segments {
333 let start = 8 + i * 32;
334 let raw = &data[start..start + 32];
335 let end = raw.iter().position(|&b| b == 0).unwrap_or(32);
336 let hex = std::str::from_utf8(&raw[..end]).ok()?;
337 segment_ids.insert(hex.to_string());
338 }
339 let bloom = BloomFilter::from_bytes_mutable(&data[header_end..]).ok()?;
340 Some((segment_ids, bloom))
341}
342
343#[cfg(test)]
344mod tests {
345 use std::sync::Arc;
346
347 use super::*;
348 use crate::dsl::{Document, Field};
349 use crate::segment::SegmentTracker;
350
351 fn make_doc(field: Field, key: &str) -> Document {
352 let mut doc = Document::new();
353 doc.add_text(field, key);
354 doc
355 }
356
357 fn empty_snapshot() -> SegmentSnapshot {
358 SegmentSnapshot::new(Arc::new(SegmentTracker::new()), vec![])
359 }
360
361 #[test]
362 fn test_new_empty_readers() {
363 let field = Field(0);
364 let pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
365 let doc = make_doc(field, "key1");
367 assert!(pk.check_and_insert(&doc).is_ok());
368 }
369
370 #[test]
371 fn test_unique_keys_accepted() {
372 let field = Field(0);
373 let pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
374
375 assert!(pk.check_and_insert(&make_doc(field, "a")).is_ok());
376 assert!(pk.check_and_insert(&make_doc(field, "b")).is_ok());
377 assert!(pk.check_and_insert(&make_doc(field, "c")).is_ok());
378 }
379
380 #[test]
381 fn test_duplicate_uncommitted_rejected() {
382 let field = Field(0);
383 let pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
384
385 assert!(pk.check_and_insert(&make_doc(field, "key1")).is_ok());
386 let result = pk.check_and_insert(&make_doc(field, "key1"));
387 assert!(result.is_err());
388 match result.unwrap_err() {
389 Error::DuplicatePrimaryKey(k) => assert_eq!(k, "key1"),
390 other => panic!("Expected DuplicatePrimaryKey, got {:?}", other),
391 }
392 }
393
394 #[test]
395 fn test_missing_field_rejected() {
396 let field = Field(0);
397 let other_field = Field(1);
398 let pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
399
400 let doc = make_doc(other_field, "value");
402 let result = pk.check_and_insert(&doc);
403 assert!(result.is_err());
404 match result.unwrap_err() {
405 Error::Document(msg) => assert!(msg.contains("Missing"), "{}", msg),
406 other => panic!("Expected Document error, got {:?}", other),
407 }
408 }
409
410 #[test]
411 fn test_empty_key_rejected() {
412 let field = Field(0);
413 let pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
414
415 let result = pk.check_and_insert(&make_doc(field, ""));
416 assert!(result.is_err());
417 match result.unwrap_err() {
418 Error::Document(msg) => assert!(msg.contains("empty"), "{}", msg),
419 other => panic!("Expected Document error, got {:?}", other),
420 }
421 }
422
423 #[test]
424 fn test_clear_uncommitted() {
425 let field = Field(0);
426 let mut pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
427
428 assert!(pk.check_and_insert(&make_doc(field, "key1")).is_ok());
430 assert!(pk.check_and_insert(&make_doc(field, "key1")).is_err());
432
433 pk.clear_uncommitted();
435
436 assert!(pk.check_and_insert(&make_doc(field, "key1")).is_ok());
440 }
441
442 #[test]
443 fn test_many_unique_keys() {
444 let field = Field(0);
445 let pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
446
447 for i in 0..1000 {
448 let key = format!("key_{}", i);
449 assert!(pk.check_and_insert(&make_doc(field, &key)).is_ok());
450 }
451
452 for i in 0..1000 {
454 let key = format!("key_{}", i);
455 assert!(pk.check_and_insert(&make_doc(field, &key)).is_err());
456 }
457 }
458
459 #[test]
460 fn test_refresh_clears_uncommitted() {
461 let field = Field(0);
462 let mut pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
463
464 assert!(pk.check_and_insert(&make_doc(field, "key1")).is_ok());
465 assert!(pk.check_and_insert(&make_doc(field, "key1")).is_err());
466
467 pk.refresh_incremental(vec![], empty_snapshot());
470
471 assert!(pk.check_and_insert(&make_doc(field, "key1")).is_ok());
474 }
475
476 #[test]
477 fn replacement_refresh_preserves_uncommitted_reservations() {
478 let field = Field(0);
479 let mut pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
480
481 assert!(pk.check_and_insert(&make_doc(field, "queued")).is_ok());
482 pk.refresh_replacement(vec![], empty_snapshot());
483
484 assert!(
485 pk.check_and_insert(&make_doc(field, "queued")).is_err(),
486 "topology-only BP refresh must not erase a queued key reservation"
487 );
488 }
489
490 #[test]
491 fn test_pk_bloom_serialize_roundtrip() {
492 let field = Field(0);
493 let pk = PrimaryKeyIndex::new(field, vec![], empty_snapshot());
494 for i in 0..100 {
495 pk.check_and_insert(&make_doc(field, &format!("key_{}", i)))
496 .unwrap();
497 }
498
499 let seg_ids = vec![
500 "00000000000000000000000000000001".to_string(),
501 "00000000000000000000000000000002".to_string(),
502 ];
503 let mut data = Vec::new();
504 pk.write_bloom_cache(&seg_ids, &mut data).unwrap();
505 let (got_ids, got_bloom) = deserialize_pk_bloom(&data).expect("deserialize failed");
506
507 assert_eq!(got_ids.len(), 2);
508 assert!(got_ids.contains(&seg_ids[0]));
509 assert!(got_ids.contains(&seg_ids[1]));
510
511 for i in 0..100 {
513 let key = format!("key_{}", i);
514 assert!(
515 got_bloom.may_contain(key.as_bytes()),
516 "bloom miss for {}",
517 key
518 );
519 }
520 }
521
522 #[test]
523 fn test_pk_bloom_deserialize_bad_data() {
524 assert!(deserialize_pk_bloom(&[]).is_none());
525 assert!(deserialize_pk_bloom(&[0; 7]).is_none());
526 assert!(deserialize_pk_bloom(&[0; 8]).is_none()); }
528
529 #[test]
530 fn test_concurrent_access() {
531 use std::sync::Arc;
532
533 let field = Field(0);
534 let pk = Arc::new(PrimaryKeyIndex::new(field, vec![], empty_snapshot()));
535
536 let mut handles = vec![];
538 for _ in 0..10 {
539 let pk = Arc::clone(&pk);
540 handles.push(std::thread::spawn(move || {
541 pk.check_and_insert(&make_doc(field, "contested_key"))
542 }));
543 }
544
545 let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
546 let successes = results.iter().filter(|r| r.is_ok()).count();
547 let failures = results.iter().filter(|r| r.is_err()).count();
548
549 assert_eq!(successes, 1, "Exactly one insert should succeed");
551 assert_eq!(failures, 9, "Rest should fail with duplicate");
552 }
553}