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 #[must_use]
279 pub fn new() -> Self {
280 Self::default()
281 }
282
283 pub fn insert(
291 &mut self,
292 refset: u64,
293 fields: &[(String, FieldKind)],
294 rows: Vec<MemberRow>,
295 ) -> Result<(), RefsetsError> {
296 let mut table = Table {
297 fields: fields.iter().map(|(name, _)| name.clone()).collect(),
298 kinds: fields.iter().map(|(_, kind)| *kind).collect(),
299 ..Table::default()
300 };
301 let mut interned: BTreeMap<String, u32> = BTreeMap::new();
302 let mut longs_seen: BTreeMap<u64, u32> = BTreeMap::new();
303 for row in rows {
304 if row.values.len() != table.fields.len() {
305 return Err(RefsetsError::Arity {
306 refset,
307 values: row.values.len(),
308 fields: table.fields.len(),
309 });
310 }
311 table.concepts.push(row.concept.index());
312 table.members.insert(row.concept.index());
313 table.times.push(row.effective_time);
314 table.modules.push(row.module);
315 for value in row.values {
316 let (tag, payload) = match value {
317 FieldValue::Concept(concept) => (TAG_CONCEPT, concept.index()),
318 FieldValue::Component(id) => (
319 TAG_COMPONENT,
320 intern_long(&mut table.longs, &mut longs_seen, id)?,
321 ),
322 FieldValue::Integer(value) => (
323 TAG_INTEGER,
324 intern_long(
325 &mut table.longs,
326 &mut longs_seen,
327 u64::from_le_bytes(value.to_le_bytes()),
328 )?,
329 ),
330 FieldValue::String(text) => {
331 let index = if let Some(&index) = interned.get(&text) {
332 index
333 } else {
334 let index = u32::try_from(table.strings.len())
335 .map_err(RefsetsError::TooMany)?;
336 table.strings.push(text.clone());
337 interned.insert(text, index);
338 index
339 };
340 (TAG_STRING, index)
341 }
342 };
343 table.tags.push(tag);
344 table.payloads.push(payload);
345 }
346 }
347 u32::try_from(table.concepts.len()).map_err(RefsetsError::TooMany)?;
348 self.tables.insert(refset, table);
349 Ok(())
350 }
351
352 #[must_use]
354 pub fn table(&self, refset: u64) -> Option<&Table> {
355 self.tables.get(&refset)
356 }
357
358 pub fn refsets(&self) -> impl Iterator<Item = u64> + '_ {
360 self.tables.keys().copied()
361 }
362
363 #[must_use]
365 pub fn len(&self) -> usize {
366 self.tables.len()
367 }
368
369 #[must_use]
371 pub fn is_empty(&self) -> bool {
372 self.tables.is_empty()
373 }
374
375 #[must_use]
377 pub fn total(&self) -> u64 {
378 self.tables
379 .values()
380 .map(|t| u64::try_from(t.len()).unwrap_or(u64::MAX))
381 .sum()
382 }
383
384 pub fn write_to(&self, out: &mut impl Write) -> Result<(), RefsetsError> {
390 let count = |len: usize| u32::try_from(len).map_err(RefsetsError::TooMany);
391 out.write_all(MAGIC)?;
392 out.write_all(&VERSION.to_le_bytes())?;
393 out.write_all(&count(self.tables.len())?.to_le_bytes())?;
394 for (refset, table) in &self.tables {
395 out.write_all(&refset.to_le_bytes())?;
396 out.write_all(&count(table.fields.len())?.to_le_bytes())?;
397 for (name, kind) in table.fields.iter().zip(&table.kinds) {
398 write_text(out, name)?;
399 out.write_all(&[match kind {
400 FieldKind::Component => 0,
401 FieldKind::Integer => 1,
402 FieldKind::String => 2,
403 }])?;
404 }
405 out.write_all(&count(table.concepts.len())?.to_le_bytes())?;
406 write_u32s(out, &table.concepts)?;
407 write_u32s(out, &table.times)?;
408 for module in &table.modules {
409 out.write_all(&module.to_le_bytes())?;
410 }
411 out.write_all(&table.tags)?;
412 write_u32s(out, &table.payloads)?;
413 out.write_all(&count(table.longs.len())?.to_le_bytes())?;
414 for long in &table.longs {
415 out.write_all(&long.to_le_bytes())?;
416 }
417 out.write_all(&count(table.strings.len())?.to_le_bytes())?;
418 for text in &table.strings {
419 write_text(out, text)?;
420 }
421 }
422 Ok(())
423 }
424
425 pub fn read_from(input: &mut impl Read) -> Result<Self, RefsetsError> {
432 let mut magic = [0_u8; 8];
433 input.read_exact(&mut magic)?;
434 if &magic != MAGIC {
435 return Err(RefsetsError::Magic);
436 }
437 let version = read_u32(input)?;
438 if version != VERSION {
439 return Err(RefsetsError::Version {
440 found: version,
441 expected: VERSION,
442 });
443 }
444 let mut tables = BTreeMap::new();
445 for _ in 0..read_u32(input)? {
446 let refset = read_u64(input)?;
447 let field_count = to_usize(read_u32(input)?);
448 let mut fields = Vec::with_capacity(field_count);
449 let mut kinds = Vec::with_capacity(field_count);
450 for _ in 0..field_count {
451 fields.push(read_text(input)?);
452 let mut kind = [0_u8; 1];
453 input.read_exact(&mut kind)?;
454 kinds.push(match kind[0] {
455 0 => FieldKind::Component,
456 1 => FieldKind::Integer,
457 2 => FieldKind::String,
458 _ => return Err(RefsetsError::Inconsistent),
459 });
460 }
461 let rows = to_usize(read_u32(input)?);
462 let concepts = read_u32s(input, rows)?;
463 let times = read_u32s(input, rows)?;
464 let mut modules = Vec::with_capacity(rows);
465 for _ in 0..rows {
466 modules.push(read_u64(input)?);
467 }
468 let cells = rows.saturating_mul(field_count);
469 let mut tags = vec![0_u8; cells];
470 input.read_exact(&mut tags)?;
471 let payloads = read_u32s(input, cells)?;
472 let mut longs = Vec::new();
473 for _ in 0..read_u32(input)? {
474 longs.push(read_u64(input)?);
475 }
476 let mut strings = Vec::new();
477 for _ in 0..read_u32(input)? {
478 strings.push(read_text(input)?);
479 }
480 let members = concepts.iter().copied().collect();
481 let table = Table {
482 fields,
483 kinds,
484 concepts,
485 times,
486 modules,
487 tags,
488 payloads,
489 longs,
490 strings,
491 members,
492 };
493 table.check()?;
494 tables.insert(refset, table);
495 }
496 Ok(Self { tables })
497 }
498}
499
500fn intern_long(
501 longs: &mut Vec<u64>,
502 seen: &mut BTreeMap<u64, u32>,
503 value: u64,
504) -> Result<u32, RefsetsError> {
505 if let Some(&index) = seen.get(&value) {
506 return Ok(index);
507 }
508 let index = u32::try_from(longs.len()).map_err(RefsetsError::TooMany)?;
509 longs.push(value);
510 seen.insert(value, index);
511 Ok(index)
512}
513
514fn write_text(out: &mut impl Write, text: &str) -> Result<(), RefsetsError> {
515 let len = u32::try_from(text.len()).map_err(RefsetsError::TooMany)?;
516 out.write_all(&len.to_le_bytes())?;
517 out.write_all(text.as_bytes())?;
518 Ok(())
519}
520
521fn read_text(input: &mut impl Read) -> Result<String, RefsetsError> {
522 let len = to_usize(read_u32(input)?);
523 let mut bytes = vec![0_u8; len];
524 input.read_exact(&mut bytes)?;
525 Ok(String::from_utf8(bytes)?)
526}
527
528fn write_u32s(out: &mut impl Write, values: &[u32]) -> Result<(), RefsetsError> {
529 for value in values {
530 out.write_all(&value.to_le_bytes())?;
531 }
532 Ok(())
533}
534
535fn read_u32(input: &mut impl Read) -> Result<u32, RefsetsError> {
536 let mut bytes = [0_u8; 4];
537 input.read_exact(&mut bytes)?;
538 Ok(u32::from_le_bytes(bytes))
539}
540
541fn read_u64(input: &mut impl Read) -> Result<u64, RefsetsError> {
542 let mut bytes = [0_u8; 8];
543 input.read_exact(&mut bytes)?;
544 Ok(u64::from_le_bytes(bytes))
545}
546
547fn read_u32s(input: &mut impl Read, count: usize) -> Result<Vec<u32>, RefsetsError> {
548 let mut bytes = vec![0_u8; count.saturating_mul(4)];
549 input.read_exact(&mut bytes)?;
550 Ok(bytes
551 .as_chunks::<4>()
552 .0
553 .iter()
554 .map(|chunk| u32::from_le_bytes(*chunk))
555 .collect())
556}
557
558#[cfg(test)]
559mod tests {
560 use super::{FieldKind, FieldValue, MemberRow, RefsetMembers, RefsetsError, ValueRef};
561 use crate::ordinal::Ordinal;
562
563 fn sample() -> RefsetMembers {
564 let mut members = RefsetMembers::new();
565 members
566 .insert(
567 42,
568 &[
569 (String::from("mapGroup"), FieldKind::Integer),
570 (String::from("mapTarget"), FieldKind::String),
571 (String::from("correlationId"), FieldKind::Component),
572 ],
573 vec![
574 MemberRow {
575 concept: Ordinal::new(2),
576 effective_time: 20_240_101,
577 module: 99,
578 values: vec![
579 FieldValue::Integer(1),
580 FieldValue::String(String::from("J45.9")),
581 FieldValue::Concept(Ordinal::new(7)),
582 ],
583 },
584 MemberRow {
585 concept: Ordinal::new(3),
586 effective_time: 20_230_731,
587 module: 99,
588 values: vec![
589 FieldValue::Integer(-2),
590 FieldValue::String(String::from("J45.9")),
591 FieldValue::Component(123_456_789_012),
592 ],
593 },
594 ],
595 )
596 .expect("inserts");
597 members
598 .insert(7, &[], vec![])
599 .expect("an empty simple reference set");
600 members
601 }
602
603 #[test]
604 fn rows_answer_by_field_and_the_layout_round_trips() {
605 let members = sample();
606 let table = members.table(42).expect("table");
607 assert_eq!(table.len(), 2);
608 assert_eq!(table.field("MAPTARGET"), Some(1));
609 assert_eq!(table.value(0, 0), Some(ValueRef::Integer(1)));
610 assert_eq!(table.value(1, 0), Some(ValueRef::Integer(-2)));
611 assert_eq!(table.value(1, 1), Some(ValueRef::String("J45.9")));
612 assert_eq!(table.value(0, 2), Some(ValueRef::Concept(Ordinal::new(7))));
613 assert_eq!(
614 table.value(1, 2),
615 Some(ValueRef::Component(123_456_789_012))
616 );
617 assert_eq!(table.value(1, 3), None);
618 assert_eq!(table.effective_time(1), Some(20_230_731));
619 assert_eq!(table.members().iter().collect::<Vec<_>>(), [2, 3]);
620 assert_eq!(table.rows_with(2, Ordinal::new(7)).collect::<Vec<_>>(), [0]);
621 assert_eq!(table.rows_of(Ordinal::new(3)).collect::<Vec<_>>(), [1]);
622 assert_eq!(
623 table.rows_of(Ordinal::new(9)).collect::<Vec<_>>(),
624 Vec::<usize>::new(),
625 "a concept the reference set does not name owns no row"
626 );
627 assert_eq!(members.total(), 2);
628 let mut bytes = Vec::new();
629 members.write_to(&mut bytes).expect("writes");
630 assert_eq!(
631 RefsetMembers::read_from(&mut bytes.as_slice()).expect("reads"),
632 members
633 );
634 assert!(matches!(
635 RefsetMembers::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
636 Err(RefsetsError::Magic)
637 ));
638 let mut bad = RefsetMembers::new();
639 assert!(matches!(
640 bad.insert(
641 1,
642 &[(String::from("f"), FieldKind::String)],
643 vec![MemberRow {
644 concept: Ordinal::new(0),
645 effective_time: 0,
646 module: 0,
647 values: Vec::new(),
648 }]
649 ),
650 Err(RefsetsError::Arity { .. })
651 ));
652 }
653}