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