()
| 604 | /// and verify the dictionary and IDs match the original values. |
| 605 | #[test] |
| 606 | fn dict_encoded_roundtrip() { |
| 607 | use crate::memtable::{ColumnData, ColumnarMemtable, DICT_ENCODE_MAX_CARDINALITY}; |
| 608 | use crate::writer::SegmentWriter; |
| 609 | |
| 610 | let schema = ColumnarSchema::new(vec![ |
| 611 | ColumnDef::required("id", ColumnType::Int64).with_primary_key(), |
| 612 | ColumnDef::required("qtype", ColumnType::String), |
| 613 | ]) |
| 614 | .expect("valid"); |
| 615 | |
| 616 | let qtypes = ["A", "AAAA", "MX", "NS", "SOA", "CNAME", "PTR", "TXT"]; |
| 617 | let mut mt = ColumnarMemtable::new(&schema); |
| 618 | for (i, &q) in qtypes.iter().cycle().take(100).enumerate() { |
| 619 | mt.append_row(&[Value::Integer(i as i64), Value::String(q.into())]) |
| 620 | .expect("append"); |
| 621 | } |
| 622 | |
| 623 | // Convert low-cardinality string column to dict-encoded. |
| 624 | mt.try_dict_encode_columns(DICT_ENCODE_MAX_CARDINALITY); |
| 625 | |
| 626 | // Verify it converted. |
| 627 | assert!(matches!(mt.columns()[1], ColumnData::DictEncoded { .. })); |
| 628 | |
| 629 | let (schema, columns, row_count) = mt.drain(); |
| 630 | let segment = SegmentWriter::plain() |
| 631 | .write_segment(&schema, &columns, row_count, None) |
| 632 | .expect("write segment"); |
| 633 | |
| 634 | // Read back. |
| 635 | let reader = SegmentReader::open(&segment).expect("open"); |
| 636 | assert_eq!(reader.row_count(), 100); |
| 637 | |
| 638 | // The footer should record the dictionary. |
| 639 | let dict_in_meta = reader.footer().columns[1].dictionary.as_deref(); |
| 640 | assert!(dict_in_meta.is_some(), "dictionary should be in ColumnMeta"); |
| 641 | let meta_dict = dict_in_meta.expect("present"); |
| 642 | assert_eq!(meta_dict.len(), 8, "8 distinct qtypes"); |
| 643 | |
| 644 | // Read the qtype column — should come back as DictEncoded. |
| 645 | let col = reader.read_column(1).expect("read qtype column"); |
| 646 | match col { |
| 647 | DecodedColumn::DictEncoded { |
| 648 | ids, |
| 649 | dictionary, |
| 650 | valid, |
| 651 | } => { |
| 652 | assert_eq!(ids.len(), 100); |
| 653 | assert_eq!(valid.len(), 100); |
| 654 | assert!(valid.iter().all(|&v| v)); |
| 655 | assert_eq!(dictionary.len(), 8); |
| 656 | |
| 657 | // Verify round-trip: each row's ID resolves to the original qtype. |
| 658 | for (i, &q) in qtypes.iter().cycle().take(100).enumerate() { |
| 659 | let resolved = &dictionary[ids[i] as usize]; |
| 660 | assert_eq!(resolved, q, "row {i}: expected {q}, got {resolved}"); |
| 661 | } |
| 662 | } |
| 663 | _ => panic!("expected DictEncoded, got {col:?}"), |
nothing calls this directly
no test coverage detected