plugmem_core/index/
postings.rs1use plugmem_arena::{
15 Arena, ArenaCfg, ChunkIter, ChunkPool, ChunkPoolCfg, ListHandle, ShardMode, Slot, key,
16};
17
18use crate::error::Error;
19use crate::id::FactId;
20use crate::index::varint::{MAX_VARINT, decode_u32, encode_u32};
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct IdListSlot {
28 pub key: u32,
31 pub handle: ListHandle,
33 pub count: u32,
35 pub last: u32,
37}
38
39impl Slot for IdListSlot {
40 const SIZE: usize = 24;
41 const KEY_LEN: usize = 4;
42
43 fn write(&self, out: &mut [u8]) {
44 key::write_u32(out, self.key);
45 out[4..16].copy_from_slice(&self.handle.to_bytes());
46 key::write_u32(&mut out[16..], self.count);
47 key::write_u32(&mut out[20..], self.last);
48 }
49
50 fn read(bytes: &[u8]) -> Self {
51 Self {
52 key: key::read_u32(bytes),
53 handle: ListHandle::from_bytes(bytes[4..16].try_into().unwrap()),
54 count: key::read_u32(&bytes[16..]),
55 last: key::read_u32(&bytes[20..]),
56 }
57 }
58}
59
60#[derive(Debug)]
63pub struct PostingStore<'a, const TF: bool> {
64 handles: Arena<'a, IdListSlot>,
65 pool: ChunkPool<'a>,
66}
67
68impl<'a, const TF: bool> PostingStore<'a, TF> {
69 pub fn new(shards: usize, max_bytes: usize) -> Result<Self, Error> {
72 Ok(Self {
73 handles: Arena::new(
74 ArenaCfg::new(shards, ShardMode::Uniform).with_max_bytes(max_bytes),
75 )?,
76 pool: ChunkPool::new(ChunkPoolCfg::new().with_max_bytes(max_bytes)),
77 })
78 }
79
80 pub fn push(&mut self, raw_key: u32, id: FactId, tf: u8) -> Result<(), Error> {
89 let mut kb = [0u8; 4];
90 key::write_u32(&mut kb, raw_key);
91 let slot = self.handles.get(&kb);
92 let (mut handle, count, last) = match slot {
93 Some(s) => (s.handle, s.count, s.last),
94 None => (ListHandle::EMPTY, 0, 0),
95 };
96 debug_assert!(
97 count == 0 || id.0 > last,
98 "posting ids must be appended in ascending order"
99 );
100 let delta = id.0.wrapping_sub(if count == 0 { 0 } else { last });
101 let mut buf = [0u8; MAX_VARINT + 1];
102 let mut head = [0u8; MAX_VARINT];
103 let mut n = encode_u32(delta, &mut head);
104 buf[..n].copy_from_slice(&head[..n]);
105 if TF {
106 buf[n] = tf;
107 n += 1;
108 }
109 self.pool.push(&mut handle, &buf[..n])?;
110 let updated = IdListSlot {
111 key: raw_key,
112 handle,
113 count: count + 1,
114 last: id.0,
115 };
116 if slot.is_some() {
117 let payload = self
118 .handles
119 .payload_mut(&kb)
120 .expect("slot existed just above");
121 let mut full = [0u8; IdListSlot::SIZE];
122 updated.write(&mut full);
123 payload.copy_from_slice(&full[IdListSlot::KEY_LEN..]);
124 } else {
125 self.handles.insert(&updated)?;
126 }
127 Ok(())
128 }
129
130 pub fn count(&self, raw_key: u32) -> u32 {
133 let mut kb = [0u8; 4];
134 key::write_u32(&mut kb, raw_key);
135 self.handles.get(&kb).map_or(0, |s| s.count)
136 }
137
138 pub fn entries(&self, raw_key: u32) -> Entries<'_, TF> {
141 let mut kb = [0u8; 4];
142 key::write_u32(&mut kb, raw_key);
143 let handle = self
144 .handles
145 .get(&kb)
146 .map_or(ListHandle::EMPTY, |s| s.handle);
147 Entries {
148 chunks: self.pool.iter(&handle),
149 cur: &[],
150 prev: 0,
151 first: true,
152 }
153 }
154
155 pub fn pool_bytes(&self) -> usize {
157 self.handles.pool_bytes() + self.pool.pool_bytes()
158 }
159
160 pub fn keys(&self) -> usize {
162 self.handles.len()
163 }
164
165 pub(crate) fn handles_meta(&self) -> alloc::vec::Vec<u8> {
167 let mut out = alloc::vec::Vec::new();
168 self.handles.dump_meta(&mut out);
169 out
170 }
171
172 pub(crate) fn handles_pool(&self) -> alloc::vec::Vec<u8> {
174 let mut out = alloc::vec::Vec::new();
175 self.handles.dump_pool(&mut out);
176 out
177 }
178
179 pub(crate) fn chunks_meta(&self) -> alloc::vec::Vec<u8> {
181 let mut out = alloc::vec::Vec::new();
182 self.pool.dump_meta(&mut out);
183 out
184 }
185
186 pub(crate) fn chunks_pool(&self) -> alloc::vec::Vec<u8> {
188 let mut out = alloc::vec::Vec::new();
189 self.pool.dump_pool(&mut out);
190 out
191 }
192
193 pub(crate) fn from_parts(handles: Arena<'a, IdListSlot>, pool: ChunkPool<'a>) -> Self {
195 Self { handles, pool }
196 }
197}
198
199pub struct Entries<'a, const TF: bool> {
202 chunks: ChunkIter<'a>,
203 cur: &'a [u8],
204 prev: u32,
205 first: bool,
206}
207
208impl<const TF: bool> Iterator for Entries<'_, TF> {
209 type Item = (FactId, u8);
210
211 fn next(&mut self) -> Option<(FactId, u8)> {
212 while self.cur.is_empty() {
213 self.cur = self.chunks.next()?;
214 }
215 let (delta, used) = decode_u32(self.cur).expect("posting entry is well-formed");
218 let mut at = used;
219 let tf = if TF {
220 let tf = self.cur[at];
221 at += 1;
222 tf
223 } else {
224 1
225 };
226 self.cur = &self.cur[at..];
227 let id = if self.first {
228 self.first = false;
229 delta
230 } else {
231 self.prev + delta
232 };
233 self.prev = id;
234 Some((FactId(id), tf))
235 }
236}