1use std::collections::BTreeMap;
17use std::io::{self, Read, Write};
18
19use roaring::RoaringBitmap;
20
21use crate::ordinal::{Ordinal, to_usize};
22
23const MAGIC: &[u8; 8] = b"FTMBRS\0\0";
24const VERSION: u32 = 1;
25const TAG_CONCEPT: u8 = 0;
26const TAG_COMPONENT: u8 = 1;
27const TAG_INTEGER: u8 = 2;
28const TAG_STRING: u8 = 3;
29
30#[derive(Debug, thiserror::Error)]
32pub enum RefsetsError {
33 #[error("reference set {refset} row has {values} values for {fields} fields")]
35 Arity {
36 refset: u64,
38 values: usize,
40 fields: usize,
42 },
43 #[error("too many reference set rows")]
45 TooMany(#[source] std::num::TryFromIntError),
46 #[error("reference set members I/O failed")]
48 Io(#[from] io::Error),
49 #[error("not a reference set members artifact")]
51 Magic,
52 #[error("reference set members layout version {found}, expected {expected}")]
54 Version {
55 found: u32,
57 expected: u32,
59 },
60 #[error("the reference set member arrays are inconsistent")]
62 Inconsistent,
63 #[error("a reference set field is not UTF-8")]
65 Text(#[from] std::string::FromUtf8Error),
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum FieldKind {
71 Component,
73 Integer,
75 String,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum FieldValue {
82 Concept(Ordinal),
84 Component(u64),
86 Integer(i64),
88 String(String),
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum ValueRef<'a> {
95 Concept(Ordinal),
97 Component(u64),
99 Integer(i64),
101 String(&'a str),
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct MemberRow {
108 pub concept: Ordinal,
110 pub effective_time: u32,
112 pub module: u64,
114 pub values: Vec<FieldValue>,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Default)]
120pub struct Table {
121 fields: Vec<String>,
122 kinds: Vec<FieldKind>,
123 concepts: Vec<u32>,
124 times: Vec<u32>,
125 modules: Vec<u64>,
126 tags: Vec<u8>,
127 payloads: Vec<u32>,
128 longs: Vec<u64>,
129 strings: Vec<String>,
130 members: RoaringBitmap,
132}
133
134impl Table {
135 #[must_use]
137 pub fn size_in_bytes(&self) -> usize {
138 crate::footprint::strings(&self.fields)
139 .saturating_add(crate::footprint::vector(&self.kinds))
140 .saturating_add(crate::footprint::vector(&self.concepts))
141 .saturating_add(crate::footprint::vector(&self.times))
142 .saturating_add(crate::footprint::vector(&self.modules))
143 .saturating_add(crate::footprint::vector(&self.tags))
144 .saturating_add(crate::footprint::vector(&self.payloads))
145 .saturating_add(crate::footprint::vector(&self.longs))
146 .saturating_add(crate::footprint::strings(&self.strings))
147 .saturating_add(crate::footprint::bitmap(&self.members))
148 }
149
150 #[must_use]
152 pub fn fields(&self) -> &[String] {
153 &self.fields
154 }
155
156 #[must_use]
158 pub fn kinds(&self) -> &[FieldKind] {
159 &self.kinds
160 }
161
162 #[must_use]
164 pub fn field(&self, name: &str) -> Option<usize> {
165 self.fields
166 .iter()
167 .position(|f| f.eq_ignore_ascii_case(name))
168 }
169
170 #[must_use]
172 pub fn len(&self) -> usize {
173 self.concepts.len()
174 }
175
176 #[must_use]
178 pub fn is_empty(&self) -> bool {
179 self.concepts.is_empty()
180 }
181
182 #[must_use]
184 pub fn members(&self) -> &RoaringBitmap {
185 &self.members
186 }
187
188 #[must_use]
190 pub fn concept(&self, row: usize) -> Option<Ordinal> {
191 self.concepts.get(row).map(|&c| Ordinal::new(c))
192 }
193
194 #[must_use]
196 pub fn effective_time(&self, row: usize) -> Option<u32> {
197 self.times.get(row).copied()
198 }
199
200 #[must_use]
202 pub fn module(&self, row: usize) -> Option<u64> {
203 self.modules.get(row).copied()
204 }
205
206 #[must_use]
208 pub fn value(&self, row: usize, field: usize) -> Option<ValueRef<'_>> {
209 if field >= self.fields.len() {
210 return None;
211 }
212 let index = row.checked_mul(self.fields.len())?.checked_add(field)?;
213 let payload = to_usize(*self.payloads.get(index)?);
214 Some(match *self.tags.get(index)? {
215 TAG_CONCEPT => ValueRef::Concept(Ordinal::new(*self.payloads.get(index)?)),
216 TAG_COMPONENT => ValueRef::Component(*self.longs.get(payload)?),
217 TAG_INTEGER => {
218 ValueRef::Integer(i64::from_le_bytes(self.longs.get(payload)?.to_le_bytes()))
219 }
220 _ => ValueRef::String(self.strings.get(payload)?),
221 })
222 }
223
224 pub fn rows_with(&self, field: usize, target: Ordinal) -> impl Iterator<Item = usize> + '_ {
226 (0..self.len())
227 .filter(move |&row| self.value(row, field) == Some(ValueRef::Concept(target)))
228 }
229
230 pub fn rows_of(&self, concept: Ordinal) -> impl Iterator<Item = usize> + '_ {
232 self.concepts
233 .iter()
234 .enumerate()
235 .filter(move |&(_, &held)| held == concept.index())
236 .map(|(row, _)| row)
237 }
238
239 fn check(&self) -> Result<(), RefsetsError> {
240 let rows = self.concepts.len();
241 let cells = rows.saturating_mul(self.fields.len());
242 let consistent = self.kinds.len() == self.fields.len()
243 && self.times.len() == rows
244 && self.modules.len() == rows
245 && self.tags.len() == cells
246 && self.payloads.len() == cells
247 && self
248 .tags
249 .iter()
250 .zip(&self.payloads)
251 .all(|(&tag, &payload)| match tag {
252 TAG_CONCEPT => true,
253 TAG_COMPONENT | TAG_INTEGER => to_usize(payload) < self.longs.len(),
254 TAG_STRING => to_usize(payload) < self.strings.len(),
255 _ => false,
256 });
257 consistent.then_some(()).ok_or(RefsetsError::Inconsistent)
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Default)]
263pub struct RefsetMembers {
264 tables: BTreeMap<u64, Table>,
265}
266
267impl RefsetMembers {
268 #[must_use]
270 pub fn size_in_bytes(&self) -> usize {
271 self.tables.values().fold(
272 crate::footprint::entries::<u64, Table>(self.tables.len()),
273 |total, table| total.saturating_add(table.size_in_bytes()),
274 )
275 }
276
277 pub fn insert(
285 &mut self,
286 refset: u64,
287 fields: &[(String, FieldKind)],
288 rows: Vec<MemberRow>,
289 ) -> Result<(), RefsetsError> {
290 let mut table = Table {
291 fields: fields.iter().map(|(name, _)| name.clone()).collect(),
292 kinds: fields.iter().map(|(_, kind)| *kind).collect(),
293 ..Table::default()
294 };
295 let mut interned: BTreeMap<String, u32> = BTreeMap::new();
296 let mut longs_seen: BTreeMap<u64, u32> = BTreeMap::new();
297 for row in rows {
298 if row.values.len() != table.fields.len() {
299 return Err(RefsetsError::Arity {
300 refset,
301 values: row.values.len(),
302 fields: table.fields.len(),
303 });
304 }
305 table.concepts.push(row.concept.index());
306 table.members.insert(row.concept.index());
307 table.times.push(row.effective_time);
308 table.modules.push(row.module);
309 for value in row.values {
310 let (tag, payload) = match value {
311 FieldValue::Concept(concept) => (TAG_CONCEPT, concept.index()),
312 FieldValue::Component(id) => (
313 TAG_COMPONENT,
314 intern_long(&mut table.longs, &mut longs_seen, id)?,
315 ),
316 FieldValue::Integer(value) => (
317 TAG_INTEGER,
318 intern_long(
319 &mut table.longs,
320 &mut longs_seen,
321 u64::from_le_bytes(value.to_le_bytes()),
322 )?,
323 ),
324 FieldValue::String(text) => {
325 let index = if let Some(&index) = interned.get(&text) {
326 index
327 } else {
328 let index = u32::try_from(table.strings.len())
329 .map_err(RefsetsError::TooMany)?;
330 table.strings.push(text.clone());
331 interned.insert(text, index);
332 index
333 };
334 (TAG_STRING, index)
335 }
336 };
337 table.tags.push(tag);
338 table.payloads.push(payload);
339 }
340 }
341 u32::try_from(table.concepts.len()).map_err(RefsetsError::TooMany)?;
342 self.tables.insert(refset, table);
343 Ok(())
344 }
345
346 #[must_use]
348 pub fn table(&self, refset: u64) -> Option<&Table> {
349 self.tables.get(&refset)
350 }
351
352 pub fn refsets(&self) -> impl Iterator<Item = u64> + '_ {
354 self.tables.keys().copied()
355 }
356
357 #[must_use]
359 pub fn len(&self) -> usize {
360 self.tables.len()
361 }
362
363 #[must_use]
365 pub fn is_empty(&self) -> bool {
366 self.tables.is_empty()
367 }
368
369 #[must_use]
371 pub fn total(&self) -> u64 {
372 self.tables
373 .values()
374 .map(|t| u64::try_from(t.len()).unwrap_or(u64::MAX))
375 .sum()
376 }
377
378 pub fn write_to(&self, out: &mut impl Write) -> Result<(), RefsetsError> {
384 let count = |len: usize| u32::try_from(len).map_err(RefsetsError::TooMany);
385 out.write_all(MAGIC)?;
386 out.write_all(&VERSION.to_le_bytes())?;
387 out.write_all(&count(self.tables.len())?.to_le_bytes())?;
388 for (refset, table) in &self.tables {
389 out.write_all(&refset.to_le_bytes())?;
390 out.write_all(&count(table.fields.len())?.to_le_bytes())?;
391 for (name, kind) in table.fields.iter().zip(&table.kinds) {
392 write_text(out, name)?;
393 out.write_all(&[match kind {
394 FieldKind::Component => 0,
395 FieldKind::Integer => 1,
396 FieldKind::String => 2,
397 }])?;
398 }
399 out.write_all(&count(table.concepts.len())?.to_le_bytes())?;
400 write_u32s(out, &table.concepts)?;
401 write_u32s(out, &table.times)?;
402 for module in &table.modules {
403 out.write_all(&module.to_le_bytes())?;
404 }
405 out.write_all(&table.tags)?;
406 write_u32s(out, &table.payloads)?;
407 out.write_all(&count(table.longs.len())?.to_le_bytes())?;
408 for long in &table.longs {
409 out.write_all(&long.to_le_bytes())?;
410 }
411 out.write_all(&count(table.strings.len())?.to_le_bytes())?;
412 for text in &table.strings {
413 write_text(out, text)?;
414 }
415 }
416 Ok(())
417 }
418
419 pub fn read_from(input: &mut impl Read) -> Result<Self, RefsetsError> {
426 let mut magic = [0_u8; 8];
427 input.read_exact(&mut magic)?;
428 if &magic != MAGIC {
429 return Err(RefsetsError::Magic);
430 }
431 let version = read_u32(input)?;
432 if version != VERSION {
433 return Err(RefsetsError::Version {
434 found: version,
435 expected: VERSION,
436 });
437 }
438 let mut tables = BTreeMap::new();
439 for _ in 0..read_u32(input)? {
440 let refset = read_u64(input)?;
441 let field_count = to_usize(read_u32(input)?);
442 let mut fields = Vec::with_capacity(field_count);
443 let mut kinds = Vec::with_capacity(field_count);
444 for _ in 0..field_count {
445 fields.push(read_text(input)?);
446 let mut kind = [0_u8; 1];
447 input.read_exact(&mut kind)?;
448 kinds.push(match kind[0] {
449 0 => FieldKind::Component,
450 1 => FieldKind::Integer,
451 2 => FieldKind::String,
452 _ => return Err(RefsetsError::Inconsistent),
453 });
454 }
455 let rows = to_usize(read_u32(input)?);
456 let concepts = read_u32s(input, rows)?;
457 let times = read_u32s(input, rows)?;
458 let mut modules = Vec::with_capacity(rows);
459 for _ in 0..rows {
460 modules.push(read_u64(input)?);
461 }
462 let cells = rows.saturating_mul(field_count);
463 let mut tags = vec![0_u8; cells];
464 input.read_exact(&mut tags)?;
465 let payloads = read_u32s(input, cells)?;
466 let mut longs = Vec::new();
467 for _ in 0..read_u32(input)? {
468 longs.push(read_u64(input)?);
469 }
470 let mut strings = Vec::new();
471 for _ in 0..read_u32(input)? {
472 strings.push(read_text(input)?);
473 }
474 let members = concepts.iter().copied().collect();
475 let table = Table {
476 fields,
477 kinds,
478 concepts,
479 times,
480 modules,
481 tags,
482 payloads,
483 longs,
484 strings,
485 members,
486 };
487 table.check()?;
488 tables.insert(refset, table);
489 }
490 Ok(Self { tables })
491 }
492}
493
494fn intern_long(
495 longs: &mut Vec<u64>,
496 seen: &mut BTreeMap<u64, u32>,
497 value: u64,
498) -> Result<u32, RefsetsError> {
499 if let Some(&index) = seen.get(&value) {
500 return Ok(index);
501 }
502 let index = u32::try_from(longs.len()).map_err(RefsetsError::TooMany)?;
503 longs.push(value);
504 seen.insert(value, index);
505 Ok(index)
506}
507
508fn write_text(out: &mut impl Write, text: &str) -> Result<(), RefsetsError> {
509 let len = u32::try_from(text.len()).map_err(RefsetsError::TooMany)?;
510 out.write_all(&len.to_le_bytes())?;
511 out.write_all(text.as_bytes())?;
512 Ok(())
513}
514
515fn read_text(input: &mut impl Read) -> Result<String, RefsetsError> {
516 let len = to_usize(read_u32(input)?);
517 let mut bytes = vec![0_u8; len];
518 input.read_exact(&mut bytes)?;
519 Ok(String::from_utf8(bytes)?)
520}
521
522fn write_u32s(out: &mut impl Write, values: &[u32]) -> Result<(), RefsetsError> {
523 for value in values {
524 out.write_all(&value.to_le_bytes())?;
525 }
526 Ok(())
527}
528
529fn read_u32(input: &mut impl Read) -> Result<u32, RefsetsError> {
530 let mut bytes = [0_u8; 4];
531 input.read_exact(&mut bytes)?;
532 Ok(u32::from_le_bytes(bytes))
533}
534
535fn read_u64(input: &mut impl Read) -> Result<u64, RefsetsError> {
536 let mut bytes = [0_u8; 8];
537 input.read_exact(&mut bytes)?;
538 Ok(u64::from_le_bytes(bytes))
539}
540
541fn read_u32s(input: &mut impl Read, count: usize) -> Result<Vec<u32>, RefsetsError> {
542 let mut bytes = vec![0_u8; count.saturating_mul(4)];
543 input.read_exact(&mut bytes)?;
544 Ok(bytes
545 .as_chunks::<4>()
546 .0
547 .iter()
548 .map(|chunk| u32::from_le_bytes(*chunk))
549 .collect())
550}
551
552#[cfg(test)]
553mod tests {
554 use super::{FieldKind, FieldValue, MemberRow, RefsetMembers, RefsetsError, ValueRef};
555 use crate::ordinal::Ordinal;
556
557 fn sample() -> RefsetMembers {
558 let mut members = RefsetMembers::default();
559 members
560 .insert(
561 42,
562 &[
563 (String::from("mapGroup"), FieldKind::Integer),
564 (String::from("mapTarget"), FieldKind::String),
565 (String::from("correlationId"), FieldKind::Component),
566 ],
567 vec![
568 MemberRow {
569 concept: Ordinal::new(2),
570 effective_time: 20_240_101,
571 module: 99,
572 values: vec![
573 FieldValue::Integer(1),
574 FieldValue::String(String::from("J45.9")),
575 FieldValue::Concept(Ordinal::new(7)),
576 ],
577 },
578 MemberRow {
579 concept: Ordinal::new(3),
580 effective_time: 20_230_731,
581 module: 99,
582 values: vec![
583 FieldValue::Integer(-2),
584 FieldValue::String(String::from("J45.9")),
585 FieldValue::Component(123_456_789_012),
586 ],
587 },
588 ],
589 )
590 .expect("inserts");
591 members
592 .insert(7, &[], vec![])
593 .expect("an empty simple reference set");
594 members
595 }
596
597 #[test]
598 fn rows_answer_by_field_and_the_layout_round_trips() {
599 let members = sample();
600 let table = members.table(42).expect("table");
601 assert_eq!(table.len(), 2);
602 assert_eq!(table.field("MAPTARGET"), Some(1));
603 assert_eq!(table.value(0, 0), Some(ValueRef::Integer(1)));
604 assert_eq!(table.value(1, 0), Some(ValueRef::Integer(-2)));
605 assert_eq!(table.value(1, 1), Some(ValueRef::String("J45.9")));
606 assert_eq!(table.value(0, 2), Some(ValueRef::Concept(Ordinal::new(7))));
607 assert_eq!(
608 table.value(1, 2),
609 Some(ValueRef::Component(123_456_789_012))
610 );
611 assert_eq!(table.value(1, 3), None);
612 assert_eq!(table.effective_time(1), Some(20_230_731));
613 assert_eq!(table.members().iter().collect::<Vec<_>>(), [2, 3]);
614 assert_eq!(table.rows_with(2, Ordinal::new(7)).collect::<Vec<_>>(), [0]);
615 assert_eq!(table.rows_of(Ordinal::new(3)).collect::<Vec<_>>(), [1]);
616 assert_eq!(
617 table.rows_of(Ordinal::new(9)).collect::<Vec<_>>(),
618 Vec::<usize>::new(),
619 "a concept the reference set does not name owns no row"
620 );
621 assert_eq!(members.total(), 2);
622 let mut bytes = Vec::new();
623 members.write_to(&mut bytes).expect("writes");
624 assert_eq!(
625 RefsetMembers::read_from(&mut bytes.as_slice()).expect("reads"),
626 members
627 );
628 assert!(matches!(
629 RefsetMembers::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
630 Err(RefsetsError::Magic)
631 ));
632 let mut bad = RefsetMembers::default();
633 assert!(matches!(
634 bad.insert(
635 1,
636 &[(String::from("f"), FieldKind::String)],
637 vec![MemberRow {
638 concept: Ordinal::new(0),
639 effective_time: 0,
640 module: 0,
641 values: Vec::new(),
642 }]
643 ),
644 Err(RefsetsError::Arity { .. })
645 ));
646 }
647}