1use std::collections::HashSet;
8use std::convert::TryFrom;
9use std::io::{Cursor, Read};
10
11use crc32fast::Hasher;
12use serde::{Deserialize, Serialize};
13
14use crate::columnar::encoding::{Column, LogicalType};
15use crate::columnar::encoding_v2::{
16 create_decoder, create_encoder, select_encoding, Bitmap, Decoder, Encoder, EncodingHints,
17 EncodingV2,
18};
19use crate::columnar::error::{ColumnarError, Result};
20use crate::storage::compression::{create_compressor, CompressionV2};
21
22pub const SEGMENT_MAGIC: &[u8; 4] = b"ALXC";
24pub const SEGMENT_FORMAT_VERSION_V2: u16 = 2;
26pub const SEGMENT_HEADER_SIZE: usize = 24;
28pub const ROW_ID_SEGMENT_BITS: u8 = 20;
30pub const ROW_ID_OFFSET_BITS: u8 = 44;
32const ROW_ID_OFFSET_MASK: u64 = (1u64 << ROW_ID_OFFSET_BITS) - 1;
33const ROW_ID_SEGMENT_MASK: u64 = (1u64 << ROW_ID_SEGMENT_BITS) - 1;
34
35pub fn encode_row_id(segment_id: u64, local_offset: u64) -> Result<u64> {
37 if segment_id > ROW_ID_SEGMENT_MASK {
38 return Err(ColumnarError::InvalidFormat(format!(
39 "segment_id overflow for RowID: {segment_id} > {ROW_ID_SEGMENT_MASK}"
40 )));
41 }
42 if local_offset > ROW_ID_OFFSET_MASK {
43 return Err(ColumnarError::InvalidFormat(format!(
44 "row offset overflow for RowID: {local_offset} > {ROW_ID_OFFSET_MASK}"
45 )));
46 }
47 Ok((segment_id << ROW_ID_OFFSET_BITS) | local_offset)
48}
49
50pub fn decode_row_id(row_id: u64) -> (u64, u64) {
52 let segment_id = row_id >> ROW_ID_OFFSET_BITS;
53 let local_offset = row_id & ROW_ID_OFFSET_MASK;
54 (segment_id, local_offset)
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
59pub enum ChecksumScope {
60 None = 0,
62 Footer = 1,
64 Chunk = 2,
66}
67
68impl From<ChecksumScope> for u8 {
69 fn from(scope: ChecksumScope) -> Self {
70 scope as u8
71 }
72}
73
74impl TryFrom<u8> for ChecksumScope {
75 type Error = ColumnarError;
76
77 fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
78 match value {
79 0 => Ok(ChecksumScope::None),
80 1 => Ok(ChecksumScope::Footer),
81 2 => Ok(ChecksumScope::Chunk),
82 other => Err(ColumnarError::InvalidFormat(format!(
83 "unknown checksum scope: {other}"
84 ))),
85 }
86 }
87}
88
89#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
91pub struct SegmentHeader {
92 pub magic: [u8; 4],
94 pub format_version: u16,
96 pub column_count: u16,
98 pub row_count: u64,
100 pub row_group_size: u32,
102 pub checksum_scope: ChecksumScope,
104 pub compression: CompressionV2,
106 pub reserved: [u8; 2],
108}
109
110impl SegmentHeader {
111 pub fn new(
113 column_count: u16,
114 row_count: u64,
115 row_group_size: u32,
116 checksum_scope: ChecksumScope,
117 compression: CompressionV2,
118 ) -> Self {
119 Self {
120 magic: *SEGMENT_MAGIC,
121 format_version: SEGMENT_FORMAT_VERSION_V2,
122 column_count,
123 row_count,
124 row_group_size,
125 checksum_scope,
126 compression,
127 reserved: [0u8; 2],
128 }
129 }
130}
131
132fn write_header(header: &SegmentHeader, buf: &mut Vec<u8>) {
134 buf.extend_from_slice(&header.magic);
135 buf.extend_from_slice(&header.format_version.to_le_bytes());
136 buf.extend_from_slice(&header.column_count.to_le_bytes());
137 buf.extend_from_slice(&header.row_count.to_le_bytes());
138 buf.extend_from_slice(&header.row_group_size.to_le_bytes());
139 buf.push(header.checksum_scope as u8);
140 buf.push(match header.compression {
141 CompressionV2::None => 0,
142 CompressionV2::Lz4 => 1,
143 CompressionV2::Zstd { .. } => 2,
144 });
145 buf.extend_from_slice(&header.reserved);
146}
147
148fn column_len(column: &Column) -> usize {
150 match column {
151 Column::Int64(v) => v.len(),
152 Column::Float32(v) => v.len(),
153 Column::Float64(v) => v.len(),
154 Column::Bool(v) => v.len(),
155 Column::Binary(v) => v.len(),
156 Column::Fixed { values, .. } => values.len(),
157 }
158}
159
160fn slice_column(column: &Column, start: usize, len: usize) -> Result<Column> {
162 match column {
163 Column::Int64(v) => Ok(Column::Int64(v[start..start + len].to_vec())),
164 Column::Float32(v) => Ok(Column::Float32(v[start..start + len].to_vec())),
165 Column::Float64(v) => Ok(Column::Float64(v[start..start + len].to_vec())),
166 Column::Bool(v) => Ok(Column::Bool(v[start..start + len].to_vec())),
167 Column::Binary(v) => Ok(Column::Binary(v[start..start + len].to_vec())),
168 Column::Fixed { values, len: fixed } => Ok(Column::Fixed {
169 len: *fixed,
170 values: values[start..start + len].to_vec(),
171 }),
172 }
173}
174
175fn slice_bitmap(bitmap: &Bitmap, start: usize, len: usize) -> Bitmap {
176 let mut v = Vec::with_capacity(len);
177 for i in start..start + len {
178 v.push(bitmap.get(i));
179 }
180 Bitmap::from_bools(&v)
181}
182
183fn build_encoding_hints(column: &Column, bitmap: Option<&Bitmap>) -> EncodingHints {
185 let mut hints = EncodingHints::default();
186 let mut last: Option<i64> = None;
187 let mut distinct_int = HashSet::new();
188 let mut distinct_bool = HashSet::new();
189
190 match column {
191 Column::Int64(values) => {
192 let mut min = i64::MAX;
193 let mut max = i64::MIN;
194 for (i, v) in values.iter().enumerate() {
195 if let Some(bm) = bitmap {
196 if !bm.get(i) {
197 continue;
198 }
199 }
200 min = min.min(*v);
201 max = max.max(*v);
202 hints.total_count += 1;
203 distinct_int.insert(*v);
204 if let Some(prev) = last {
205 if prev > *v {
206 hints.is_sorted = false;
207 }
208 } else {
209 hints.is_sorted = true;
210 }
211 last = Some(*v);
212 }
213 hints.distinct_count = distinct_int.len();
214 if hints.total_count > 0 {
215 hints.value_range = Some((max - min) as u64);
216 }
217 }
218 Column::Float64(values) => {
219 hints.total_count = values.len();
220 hints.distinct_count = values.len(); hints.is_sorted = values.windows(2).all(|w| {
222 w[0].partial_cmp(&w[1])
223 .map(|o| o != std::cmp::Ordering::Greater)
224 .unwrap_or(true)
225 });
226 }
227 Column::Float32(values) => {
228 hints.total_count = values.len();
229 hints.distinct_count = values.len();
230 hints.is_sorted = values.windows(2).all(|w| {
231 w[0].partial_cmp(&w[1])
232 .map(|o| o != std::cmp::Ordering::Greater)
233 .unwrap_or(true)
234 });
235 }
236 Column::Bool(values) => {
237 for (i, v) in values.iter().enumerate() {
238 if let Some(bm) = bitmap {
239 if !bm.get(i) {
240 continue;
241 }
242 }
243 hints.total_count += 1;
244 distinct_bool.insert(*v);
245 }
246 hints.distinct_count = distinct_bool.len();
247 hints.is_sorted = true;
248 }
249 Column::Binary(values) => {
250 hints.total_count = values.len();
251 hints.distinct_count = values.len(); hints.is_sorted = values.windows(2).all(|w| w[0] <= w[1]);
253 }
254 Column::Fixed { values, .. } => {
255 hints.total_count = values.len();
256 hints.distinct_count = values.len();
257 hints.is_sorted = values.windows(2).all(|w| w[0] <= w[1]);
258 }
259 }
260
261 hints
262}
263
264fn read_header(bytes: &[u8]) -> Result<SegmentHeader> {
266 if bytes.len() < SEGMENT_HEADER_SIZE {
267 return Err(ColumnarError::InvalidFormat("header too short".into()));
268 }
269 let mut cur = Cursor::new(bytes);
270 let mut magic = [0u8; 4];
271 cur.read_exact(&mut magic)
272 .map_err(|e| ColumnarError::InvalidFormat(format!("read magic failed: {e}")))?;
273 if &magic != SEGMENT_MAGIC {
274 return Err(ColumnarError::InvalidFormat("invalid segment magic".into()));
275 }
276 let mut u16buf = [0u8; 2];
277 cur.read_exact(&mut u16buf)
278 .map_err(|e| ColumnarError::InvalidFormat(format!("read version failed: {e}")))?;
279 let format_version = u16::from_le_bytes(u16buf);
280
281 let mut u16buf = [0u8; 2];
282 cur.read_exact(&mut u16buf)
283 .map_err(|e| ColumnarError::InvalidFormat(format!("read column_count failed: {e}")))?;
284 let column_count = u16::from_le_bytes(u16buf);
285
286 let mut u64buf = [0u8; 8];
287 cur.read_exact(&mut u64buf)
288 .map_err(|e| ColumnarError::InvalidFormat(format!("read row_count failed: {e}")))?;
289 let row_count = u64::from_le_bytes(u64buf);
290
291 let mut u32buf = [0u8; 4];
292 cur.read_exact(&mut u32buf)
293 .map_err(|e| ColumnarError::InvalidFormat(format!("read row_group_size failed: {e}")))?;
294 let row_group_size = u32::from_le_bytes(u32buf);
295
296 let mut scope_byte = [0u8; 1];
297 cur.read_exact(&mut scope_byte)
298 .map_err(|e| ColumnarError::InvalidFormat(format!("read checksum_scope failed: {e}")))?;
299 let checksum_scope = ChecksumScope::try_from(scope_byte[0])?;
300
301 let mut comp_byte = [0u8; 1];
302 cur.read_exact(&mut comp_byte)
303 .map_err(|e| ColumnarError::InvalidFormat(format!("read compression failed: {e}")))?;
304 let compression = match comp_byte[0] {
305 0 => CompressionV2::None,
306 1 => CompressionV2::Lz4,
307 2 => CompressionV2::Zstd { level: 3 },
308 other => {
309 return Err(ColumnarError::InvalidFormat(format!(
310 "unknown compression id: {other}"
311 )))
312 }
313 };
314
315 let mut reserved = [0u8; 2];
316 cur.read_exact(&mut reserved)
317 .map_err(|e| ColumnarError::InvalidFormat(format!("read reserved failed: {e}")))?;
318
319 Ok(SegmentHeader {
320 magic,
321 format_version,
322 column_count,
323 row_count,
324 row_group_size,
325 checksum_scope,
326 compression,
327 reserved,
328 })
329}
330
331#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
333pub struct ColumnSchema {
334 pub name: String,
336 pub logical_type: LogicalType,
338 pub nullable: bool,
340 pub fixed_len: Option<u32>,
342}
343
344#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
346pub struct Schema {
347 pub columns: Vec<ColumnSchema>,
349}
350
351impl Schema {
352 pub fn column_count(&self) -> usize {
354 self.columns.len()
355 }
356}
357
358#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
360pub struct SegmentMetaV2 {
361 pub format_version: u16,
363 pub schema: Schema,
365 pub num_rows: u64,
367 pub created_at: u64,
369 pub uncompressed_size: u64,
371 pub compressed_size: u64,
373 pub row_groups: Vec<RowGroupMeta>,
375}
376
377#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
379pub struct RowGroupMeta {
380 pub row_start: u64,
382 pub row_count: u64,
384 pub compressed_size: u64,
386 pub column_chunks: Vec<ColumnChunkMeta>,
388 pub checksum: Option<u32>,
390}
391
392#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
394pub struct ColumnChunkMeta {
395 pub column_index: u16,
397 pub encoding: EncodingV2,
399 pub compression: CompressionV2,
401 pub offset: u64,
403 pub compressed_size: u64,
405 pub uncompressed_size: u64,
407 pub null_count: u64,
409 pub dictionary_offset: Option<u64>,
411}
412
413#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
415pub struct RowGroupTable {
416 pub entries: Vec<RowGroupTableEntry>,
418}
419
420#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
422pub struct RowGroupTableEntry {
423 pub row_start: u64,
425 pub row_count: u64,
427 pub data_offset: u64,
429 pub compressed_size: u64,
431 pub column_chunk_offsets: Vec<ColumnChunkOffset>,
433 pub checksum: Option<u32>,
435}
436
437#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
439pub struct ColumnChunkOffset {
440 pub column_idx: u16,
442 pub offset: u64,
444 pub length: u64,
446 pub uncompressed_length: u64,
448 pub checksum: Option<u32>,
450}
451
452#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
454pub struct ColumnDescriptors {
455 pub columns: Vec<ColumnDescriptor>,
457}
458
459#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
461pub struct ColumnDescriptor {
462 pub column_idx: u16,
464 pub logical_type: LogicalType,
466 pub encoding: EncodingV2,
468 pub compression: CompressionV2,
470 pub nullable: bool,
472 pub fixed_len: Option<u32>,
474 pub dictionary_offset: Option<u64>,
476 pub data_offset: u64,
478 pub data_length: u64,
480}
481
482#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
484pub struct SegmentFooter {
485 pub row_group_table: RowGroupTable,
487 pub column_descriptors: ColumnDescriptors,
489}
490
491#[derive(Clone, Debug)]
493pub struct RecordBatch {
494 pub schema: Schema,
496 pub columns: Vec<Column>,
498 pub null_bitmaps: Vec<Option<Bitmap>>,
500 pub row_ids: Option<Vec<u64>>,
502}
503
504impl RecordBatch {
505 pub fn new(schema: Schema, columns: Vec<Column>, null_bitmaps: Vec<Option<Bitmap>>) -> Self {
507 Self {
508 schema,
509 columns,
510 null_bitmaps,
511 row_ids: None,
512 }
513 }
514
515 pub fn num_rows(&self) -> usize {
517 self.columns.first().map(column_len).unwrap_or_default()
518 }
519
520 pub fn with_row_ids(mut self, row_ids: Option<Vec<u64>>) -> Self {
522 self.row_ids = row_ids;
523 self
524 }
525}
526
527#[derive(Clone, Debug)]
529pub struct SegmentConfigV2 {
530 pub row_group_size: u64,
532 pub compression: CompressionV2,
534 pub checksum_scope: ChecksumScope,
536 pub max_row_group_bytes: u64,
538}
539
540impl Default for SegmentConfigV2 {
541 fn default() -> Self {
542 Self {
543 row_group_size: 100_000,
544 compression: CompressionV2::None,
545 checksum_scope: ChecksumScope::Footer,
546 max_row_group_bytes: 16 * 1024 * 1024,
547 }
548 }
549}
550
551#[derive(Clone, Debug, Serialize, Deserialize)]
553pub struct ColumnSegmentV2 {
554 pub header: SegmentHeader,
556 pub meta: SegmentMetaV2,
558 pub data: Vec<u8>,
560 #[serde(default)]
562 pub row_ids: Vec<u64>,
563}
564
565#[derive(Debug)]
567pub struct InMemorySegmentSource {
568 data: Vec<u8>,
569}
570
571impl InMemorySegmentSource {
572 pub fn new(data: Vec<u8>) -> Self {
574 Self { data }
575 }
576}
577
578pub trait SegmentSource: Send + Sync + std::fmt::Debug {
580 fn read_range(&self, offset: u64, len: u64) -> Result<Vec<u8>>;
582 fn total_size(&self) -> u64;
584}
585
586impl SegmentSource for InMemorySegmentSource {
587 fn read_range(&self, offset: u64, len: u64) -> Result<Vec<u8>> {
588 let start = offset as usize;
589 let end = start + len as usize;
590 if end > self.data.len() {
591 return Err(ColumnarError::InvalidFormat("range out of bounds".into()));
592 }
593 Ok(self.data[start..end].to_vec())
594 }
595
596 fn total_size(&self) -> u64 {
597 self.data.len() as u64
598 }
599}
600
601#[derive(Debug)]
603pub struct SegmentWriterV2 {
604 config: SegmentConfigV2,
605 buffer: Vec<RecordBatch>,
606}
607
608impl SegmentWriterV2 {
609 pub fn new(config: SegmentConfigV2) -> Self {
611 Self {
612 config,
613 buffer: Vec::new(),
614 }
615 }
616
617 pub fn write_batch(&mut self, batch: RecordBatch) -> Result<()> {
619 self.buffer.push(batch);
620 Ok(())
621 }
622
623 pub fn finish(mut self) -> Result<ColumnSegmentV2> {
625 let schema = self
627 .buffer
628 .first()
629 .ok_or_else(|| ColumnarError::InvalidFormat("no batches".into()))?
630 .schema
631 .clone();
632 for b in &self.buffer {
633 if b.schema.column_count() != schema.column_count() {
634 return Err(ColumnarError::InvalidFormat("schema mismatch".into()));
635 }
636 }
637
638 let row_group_size = self.config.row_group_size as usize;
640 let mut row_groups: Vec<RecordBatch> = Vec::new();
641 for batch in self.buffer.drain(..) {
642 let rows = batch.num_rows();
643 let mut offset = 0;
644 while offset < rows {
645 let end = usize::min(offset + row_group_size, rows);
646 let mut cols = Vec::new();
647 for col in &batch.columns {
648 cols.push(slice_column(col, offset, end - offset)?);
649 }
650 let mut bitmaps = Vec::new();
651 for bm in &batch.null_bitmaps {
652 let sliced = bm.as_ref().map(|b| slice_bitmap(b, offset, end - offset));
653 bitmaps.push(sliced);
654 }
655 row_groups.push(RecordBatch::new(batch.schema.clone(), cols, bitmaps));
656 offset = end;
657 }
658 }
659
660 let total_rows: u64 = row_groups.iter().map(|rg| rg.num_rows() as u64).sum();
661
662 let header = SegmentHeader::new(
664 schema.column_count() as u16,
665 total_rows,
666 self.config.row_group_size as u32,
667 self.config.checksum_scope,
668 self.config.compression,
669 );
670
671 let schema_bytes =
672 bincode::serialize(&schema).map_err(|e| ColumnarError::InvalidFormat(e.to_string()))?;
673 let schema_len = schema_bytes.len() as u32;
674
675 let mut data = Vec::new();
677 write_header(&header, &mut data);
678 data.extend_from_slice(&schema_len.to_le_bytes());
679 data.extend_from_slice(&schema_bytes);
680
681 let mut row_group_table_entries = Vec::new();
682 let mut column_descriptors = Vec::new();
683 column_descriptors.resize(
684 schema.column_count(),
685 ColumnDescriptor {
686 column_idx: 0,
687 logical_type: LogicalType::Int64,
688 encoding: EncodingV2::Plain,
689 compression: self.config.compression,
690 nullable: false,
691 fixed_len: None,
692 dictionary_offset: None,
693 data_offset: 0,
694 data_length: 0,
695 },
696 );
697
698 let mut current_offset = data.len() as u64;
699 let mut row_start = 0u64;
700 let mut total_uncompressed = 0u64;
701 let mut queue: std::collections::VecDeque<RecordBatch> = row_groups.into_iter().collect();
702
703 let mut meta_row_groups = Vec::new();
704 while let Some(rg) = queue.pop_front() {
705 let mut rg_buffer = Vec::new();
706 let mut rg_uncompressed_size = 0u64;
707 let mut pending_chunks = Vec::new();
708
709 for (col_idx, col) in rg.columns.iter().enumerate() {
710 let null_bitmap = rg.null_bitmaps.get(col_idx).and_then(|b| b.as_ref());
711 let hints = build_encoding_hints(col, null_bitmap);
712 let encoding = select_encoding(schema.columns[col_idx].logical_type, &hints);
713 let encoder: Box<dyn Encoder> = create_encoder(encoding);
714 let encoded = encoder
715 .encode(col, null_bitmap)
716 .map_err(|e| ColumnarError::InvalidFormat(e.to_string()))?;
717 let uncompressed_len = encoded.len() as u64;
718 rg_uncompressed_size += uncompressed_len;
719
720 let compressed = if let CompressionV2::None = self.config.compression {
721 encoded
722 } else {
723 let compressor = create_compressor(self.config.compression)
724 .map_err(|e| ColumnarError::InvalidFormat(e.to_string()))?;
725 compressor
726 .compress(&encoded)
727 .map_err(|e| ColumnarError::InvalidFormat(e.to_string()))?
728 };
729
730 let chunk_offset = rg_buffer.len() as u64;
731 let chunk_checksum = if self.config.checksum_scope == ChecksumScope::Chunk {
732 let mut hasher = Hasher::new();
733 hasher.update(&compressed);
734 Some(hasher.finalize())
735 } else {
736 None
737 };
738
739 let chunk_len = compressed.len() as u64;
740 rg_buffer.extend_from_slice(&compressed);
741
742 pending_chunks.push((
743 col_idx,
744 encoding,
745 chunk_offset,
746 chunk_len,
747 uncompressed_len,
748 chunk_checksum,
749 null_bitmap.map(|b| b.null_count() as u64).unwrap_or(0),
750 compressed,
751 ));
752 }
753
754 let rg_compressed_size = rg_buffer.len() as u64;
755
756 if self.config.max_row_group_bytes > 0
757 && rg_compressed_size > self.config.max_row_group_bytes
758 {
759 if rg.num_rows() <= 1 {
761 return Err(ColumnarError::RowGroupTooLarge {
762 size: rg_compressed_size,
763 max: self.config.max_row_group_bytes,
764 });
765 }
766 let mid = rg.num_rows() / 2;
767 let mut left_cols = Vec::new();
768 let mut right_cols = Vec::new();
769 for col in &rg.columns {
770 left_cols.push(slice_column(col, 0, mid)?);
771 right_cols.push(slice_column(col, mid, rg.num_rows() - mid)?);
772 }
773 let mut left_bm = Vec::new();
774 let mut right_bm = Vec::new();
775 for bm in &rg.null_bitmaps {
776 match bm {
777 Some(b) => {
778 left_bm.push(Some(slice_bitmap(b, 0, mid)));
779 right_bm.push(Some(slice_bitmap(b, mid, rg.num_rows() - mid)));
780 }
781 None => {
782 left_bm.push(None);
783 right_bm.push(None);
784 }
785 }
786 }
787 queue.push_front(RecordBatch::new(rg.schema.clone(), right_cols, right_bm));
788 queue.push_front(RecordBatch::new(rg.schema.clone(), left_cols, left_bm));
789 continue;
790 }
791
792 let rg_data_offset = current_offset;
793 let mut column_chunk_offsets = Vec::new();
794 let mut rg_column_chunks = Vec::new();
795 let mut written = 0u64;
796 for (
797 col_idx,
798 encoding,
799 chunk_relative,
800 chunk_len,
801 uncompressed_len,
802 chunk_checksum,
803 null_count,
804 compressed,
805 ) in pending_chunks
806 {
807 let chunk_offset_abs = rg_data_offset + chunk_relative;
808 column_chunk_offsets.push(ColumnChunkOffset {
809 column_idx: col_idx as u16,
810 offset: chunk_relative,
811 length: chunk_len,
812 uncompressed_length: uncompressed_len,
813 checksum: chunk_checksum,
814 });
815
816 rg_column_chunks.push(ColumnChunkMeta {
817 column_index: col_idx as u16,
818 encoding,
819 compression: self.config.compression,
820 offset: chunk_offset_abs,
821 compressed_size: chunk_len,
822 uncompressed_size: uncompressed_len,
823 null_count,
824 dictionary_offset: if encoding == EncodingV2::Dictionary {
825 Some(chunk_offset_abs)
826 } else {
827 None
828 },
829 });
830
831 let desc = &mut column_descriptors[col_idx];
833 desc.column_idx = col_idx as u16;
834 desc.logical_type = schema.columns[col_idx].logical_type;
835 desc.encoding = encoding;
836 desc.compression = self.config.compression;
837 desc.nullable = schema.columns[col_idx].nullable;
838 desc.fixed_len = schema.columns[col_idx].fixed_len;
839 if meta_row_groups.is_empty() {
840 desc.data_offset = chunk_offset_abs;
841 }
842 desc.data_length += chunk_len;
843 if encoding == EncodingV2::Dictionary {
844 desc.dictionary_offset = Some(chunk_offset_abs);
845 }
846
847 data.extend_from_slice(&compressed);
848 written += chunk_len;
849 }
850
851 debug_assert_eq!(written, rg_compressed_size);
852
853 let row_group_checksum = None;
856
857 row_group_table_entries.push(RowGroupTableEntry {
858 row_start,
859 row_count: rg.num_rows() as u64,
860 data_offset: rg_data_offset,
861 compressed_size: rg_compressed_size,
862 column_chunk_offsets,
863 checksum: row_group_checksum,
864 });
865
866 meta_row_groups.push(RowGroupMeta {
867 row_start,
868 row_count: rg.num_rows() as u64,
869 compressed_size: rg_compressed_size,
870 column_chunks: rg_column_chunks,
871 checksum: row_group_checksum,
872 });
873
874 current_offset += rg_compressed_size;
875 row_start += rg.num_rows() as u64;
876 total_uncompressed += rg_uncompressed_size;
877 }
878
879 let footer = SegmentFooter {
880 row_group_table: RowGroupTable {
881 entries: row_group_table_entries,
882 },
883 column_descriptors: ColumnDescriptors {
884 columns: column_descriptors,
885 },
886 };
887
888 let footer_payload =
889 bincode::serialize(&footer).map_err(|e| ColumnarError::InvalidFormat(e.to_string()))?;
890 let footer_size = footer_payload.len() as u32;
891 let mut hasher = Hasher::new();
892 hasher.update(&footer_payload);
893 let footer_checksum = hasher.finalize();
894
895 data.extend_from_slice(&footer_payload);
896 data.extend_from_slice(&footer_size.to_le_bytes());
897 data.extend_from_slice(&footer_checksum.to_le_bytes());
898
899 let meta = SegmentMetaV2 {
900 format_version: header.format_version,
901 schema: schema.clone(),
902 num_rows: header.row_count,
903 created_at: 0,
904 uncompressed_size: total_uncompressed,
905 compressed_size: data.len() as u64,
906 row_groups: meta_row_groups,
907 };
908
909 Ok(ColumnSegmentV2 {
910 header,
911 meta,
912 data,
913 row_ids: Vec::new(),
914 })
915 }
916}
917
918#[derive(Debug)]
920pub struct SegmentReaderV2 {
921 header: SegmentHeader,
922 schema: Schema,
923 footer: SegmentFooter,
924 source: Box<dyn SegmentSource>,
925}
926
927impl SegmentReaderV2 {
928 pub fn open(source: Box<dyn SegmentSource>) -> Result<Self> {
930 let total_size = source.total_size();
931 if total_size < (SEGMENT_HEADER_SIZE + 8) as u64 {
932 return Err(ColumnarError::InvalidFormat("segment too small".into()));
933 }
934
935 let trailer = source.read_range(total_size - 8, 8)?;
937 let footer_size = u32::from_le_bytes(trailer[0..4].try_into().unwrap()) as u64;
938 let footer_checksum = u32::from_le_bytes(trailer[4..8].try_into().unwrap());
939
940 if footer_size + 8 > total_size {
941 return Err(ColumnarError::InvalidFormat("invalid footer size".into()));
942 }
943
944 let footer_start = total_size - 8 - footer_size;
945 let footer_bytes = source.read_range(footer_start, footer_size)?;
946 let mut hasher = Hasher::new();
947 hasher.update(&footer_bytes);
948 let computed = hasher.finalize();
949 if computed != footer_checksum {
950 return Err(ColumnarError::ChecksumMismatch);
951 }
952
953 let footer: SegmentFooter = bincode::deserialize(&footer_bytes)
954 .map_err(|e| ColumnarError::InvalidFormat(e.to_string()))?;
955
956 let header_bytes = source.read_range(0, SEGMENT_HEADER_SIZE as u64)?;
958 let header = read_header(&header_bytes)?;
959 if header.format_version != SEGMENT_FORMAT_VERSION_V2 {
960 return Err(ColumnarError::UnsupportedFormatVersion {
961 found: header.format_version,
962 expected: SEGMENT_FORMAT_VERSION_V2,
963 });
964 }
965
966 let schema_len_bytes = source.read_range(SEGMENT_HEADER_SIZE as u64, 4)?;
967 let schema_len = u32::from_le_bytes(schema_len_bytes.try_into().unwrap()) as u64;
968 let schema_bytes = source.read_range(SEGMENT_HEADER_SIZE as u64 + 4, schema_len)?;
969 let schema: Schema = bincode::deserialize(&schema_bytes)
970 .map_err(|e| ColumnarError::InvalidFormat(e.to_string()))?;
971
972 Ok(Self {
973 header,
974 schema,
975 footer,
976 source,
977 })
978 }
979
980 pub fn read_columns(&self, columns: &[usize]) -> Result<Vec<RecordBatch>> {
982 let mut batches = Vec::new();
983 for idx in 0..self.footer.row_group_table.entries.len() {
984 batches.push(self.read_row_group_by_index(columns, idx)?);
985 }
986 Ok(batches)
987 }
988
989 pub fn read_row_group_by_index(
991 &self,
992 columns: &[usize],
993 rg_index: usize,
994 ) -> Result<RecordBatch> {
995 let entry = self
996 .footer
997 .row_group_table
998 .entries
999 .get(rg_index)
1000 .ok_or_else(|| ColumnarError::InvalidFormat("row group index out of bounds".into()))?;
1001
1002 let mut cols = Vec::new();
1003 let mut bitmaps = Vec::new();
1004 for &col_idx in columns {
1005 let desc = self
1006 .footer
1007 .column_descriptors
1008 .columns
1009 .get(col_idx)
1010 .ok_or_else(|| ColumnarError::InvalidFormat("column index out of bounds".into()))?;
1011 let chunk_meta = entry
1012 .column_chunk_offsets
1013 .iter()
1014 .find(|c| c.column_idx as usize == col_idx)
1015 .ok_or_else(|| ColumnarError::InvalidFormat("missing chunk offset".into()))?;
1016 let chunk_bytes = self
1017 .source
1018 .read_range(entry.data_offset + chunk_meta.offset, chunk_meta.length)?;
1019
1020 if self.header.checksum_scope == ChecksumScope::Chunk {
1021 if let Some(expected) = chunk_meta.checksum {
1022 let mut hasher = Hasher::new();
1023 hasher.update(&chunk_bytes);
1024 let computed = hasher.finalize();
1025 if expected != computed {
1026 return Err(ColumnarError::ChecksumMismatch);
1027 }
1028 }
1029 if let Some(expected_rg) = entry.checksum {
1030 let mut hasher = Hasher::new();
1031 let rg_bytes = self
1032 .source
1033 .read_range(entry.data_offset, entry.compressed_size)?;
1034 hasher.update(&rg_bytes);
1035 if hasher.finalize() != expected_rg {
1036 return Err(ColumnarError::ChecksumMismatch);
1037 }
1038 }
1039 }
1040
1041 let decoder: Box<dyn Decoder> = create_decoder(desc.encoding);
1042 let decompressed = if let CompressionV2::None = desc.compression {
1043 chunk_bytes
1044 } else {
1045 let compressor = create_compressor(desc.compression)
1046 .map_err(|e| ColumnarError::InvalidFormat(e.to_string()))?;
1047 compressor
1048 .decompress(&chunk_bytes, chunk_meta.uncompressed_length as usize)
1049 .map_err(|e| ColumnarError::InvalidFormat(e.to_string()))?
1050 };
1051 let (col, bitmap) =
1052 decoder.decode(&decompressed, entry.row_count as usize, desc.logical_type)?;
1053 cols.push(col);
1054 bitmaps.push(bitmap);
1055 }
1056 let projected_schema = Schema {
1057 columns: columns
1058 .iter()
1059 .map(|&i| self.schema.columns[i].clone())
1060 .collect(),
1061 };
1062 Ok(RecordBatch::new(projected_schema, cols, bitmaps))
1063 }
1064
1065 pub fn iter_row_groups(&self) -> RowGroupIter<'_> {
1067 RowGroupIter {
1068 reader: self,
1069 index: 0,
1070 }
1071 }
1072
1073 pub fn row_group_count(&self) -> usize {
1075 self.footer.row_group_table.entries.len()
1076 }
1077}
1078
1079#[derive(Debug)]
1081pub struct RowGroupIter<'a> {
1082 reader: &'a SegmentReaderV2,
1083 index: usize,
1084}
1085
1086impl<'a> Iterator for RowGroupIter<'a> {
1087 type Item = Result<RecordBatch>;
1088
1089 fn next(&mut self) -> Option<Self::Item> {
1090 if self.index >= self.reader.footer.row_group_table.entries.len() {
1091 return None;
1092 }
1093 let idx = self.index;
1094 self.index += 1;
1095 let cols: Vec<usize> = (0..self.reader.schema.column_count()).collect();
1096 Some(self.reader.read_row_group_by_index(&cols, idx))
1097 }
1098}
1099
1100#[cfg(all(test, not(target_arch = "wasm32")))]
1101mod tests {
1102 use super::*;
1103
1104 fn simple_schema() -> Schema {
1105 Schema {
1106 columns: vec![
1107 ColumnSchema {
1108 name: "id".into(),
1109 logical_type: LogicalType::Int64,
1110 nullable: false,
1111 fixed_len: None,
1112 },
1113 ColumnSchema {
1114 name: "val".into(),
1115 logical_type: LogicalType::Int64,
1116 nullable: false,
1117 fixed_len: None,
1118 },
1119 ],
1120 }
1121 }
1122
1123 fn make_batch(rows: &[(i64, i64)]) -> RecordBatch {
1124 let ids: Vec<i64> = rows.iter().map(|(a, _)| *a).collect();
1125 let vals: Vec<i64> = rows.iter().map(|(_, b)| *b).collect();
1126 RecordBatch::new(
1127 simple_schema(),
1128 vec![Column::Int64(ids), Column::Int64(vals)],
1129 vec![None, None],
1130 )
1131 }
1132
1133 fn write_and_read(
1134 config: SegmentConfigV2,
1135 batches: Vec<RecordBatch>,
1136 ) -> Result<SegmentReaderV2> {
1137 let mut writer = SegmentWriterV2::new(config);
1138 for b in batches {
1139 writer.write_batch(b)?;
1140 }
1141 let segment = writer.finish()?;
1142 SegmentReaderV2::open(Box::new(InMemorySegmentSource::new(segment.data)))
1143 }
1144
1145 #[test]
1146 fn test_row_group_boundary() {
1147 let cfg = SegmentConfigV2 {
1148 row_group_size: 2,
1149 ..Default::default()
1150 };
1151 let reader = write_and_read(cfg, vec![make_batch(&[(1, 10), (2, 20), (3, 30)])]).unwrap();
1152 let batches = reader.read_columns(&[0, 1]).unwrap();
1153 assert_eq!(batches.len(), 2);
1154 assert_eq!(batches[0].num_rows(), 2);
1155 assert_eq!(batches[1].num_rows(), 1);
1156 }
1157
1158 #[test]
1159 fn test_16mib_row_group_guard() {
1160 let cfg = SegmentConfigV2 {
1161 row_group_size: 10,
1162 max_row_group_bytes: 16,
1163 ..Default::default()
1164 };
1165 let oversized = RecordBatch::new(
1166 simple_schema(),
1167 vec![Column::Int64(vec![1, 2, 3]), Column::Int64(vec![4, 5, 6])],
1168 vec![None, None],
1169 );
1170 let mut writer = SegmentWriterV2::new(cfg);
1171 writer.write_batch(oversized).unwrap();
1172 let err = writer.finish().unwrap_err();
1173 assert!(matches!(err, ColumnarError::RowGroupTooLarge { .. }));
1174 }
1175
1176 #[test]
1177 fn test_single_batch_too_large() {
1178 let cfg = SegmentConfigV2 {
1179 row_group_size: 100,
1180 max_row_group_bytes: 20,
1181 ..Default::default()
1182 };
1183 let batch = RecordBatch::new(
1184 simple_schema(),
1185 vec![
1186 Column::Int64((0..10).collect()),
1187 Column::Int64((0..10).collect()),
1188 ],
1189 vec![None, None],
1190 );
1191 let mut writer = SegmentWriterV2::new(cfg);
1192 writer.write_batch(batch).unwrap();
1193 let err = writer.finish().unwrap_err();
1194 assert!(matches!(err, ColumnarError::RowGroupTooLarge { .. }));
1195 }
1196
1197 #[test]
1198 fn test_footer_checksum_validation() {
1199 let segment = {
1200 let mut writer = SegmentWriterV2::new(Default::default());
1201 writer.write_batch(make_batch(&[(1, 10)])).unwrap();
1202 writer.finish().unwrap()
1203 };
1204 let mut data = segment.data.clone();
1205 let len = data.len();
1206 data[len - 1] ^= 0xFF; let err = SegmentReaderV2::open(Box::new(InMemorySegmentSource::new(data))).unwrap_err();
1208 assert!(matches!(err, ColumnarError::ChecksumMismatch));
1209 }
1210
1211 #[test]
1212 fn test_multi_column_roundtrip() {
1213 let reader = write_and_read(
1214 Default::default(),
1215 vec![make_batch(&[(1, 10), (2, 20), (3, 30)])],
1216 )
1217 .unwrap();
1218 let batches = reader.read_columns(&[0, 1]).unwrap();
1219 assert_eq!(batches.len(), 1);
1220 if let Column::Int64(ids) = &batches[0].columns[0] {
1221 assert_eq!(ids, &vec![1, 2, 3]);
1222 } else {
1223 panic!("expected int64");
1224 }
1225 }
1226
1227 #[test]
1228 fn test_offset_table_random_access() {
1229 let cfg = SegmentConfigV2 {
1230 row_group_size: 2,
1231 ..Default::default()
1232 };
1233 let reader =
1234 write_and_read(cfg, vec![make_batch(&[(1, 10), (2, 20), (3, 30), (4, 40)])]).unwrap();
1235 let batch = reader.read_row_group_by_index(&[1], 1).unwrap();
1236 assert_eq!(batch.num_rows(), 2);
1237 if let Column::Int64(vals) = &batch.columns[0] {
1238 assert_eq!(vals, &vec![30, 40]);
1239 } else {
1240 panic!("expected int64");
1241 }
1242 }
1243
1244 #[test]
1245 fn test_format_version_rejection() {
1246 let segment = {
1247 let mut writer = SegmentWriterV2::new(Default::default());
1248 writer.write_batch(make_batch(&[(1, 1)])).unwrap();
1249 writer.finish().unwrap()
1250 };
1251 let mut bytes = segment.data.clone();
1252 bytes[4..6].copy_from_slice(&1u16.to_le_bytes());
1254 let err = SegmentReaderV2::open(Box::new(InMemorySegmentSource::new(bytes))).unwrap_err();
1255 assert!(matches!(
1256 err,
1257 ColumnarError::UnsupportedFormatVersion { .. }
1258 ));
1259 }
1260
1261 #[test]
1262 fn test_column_pruning() {
1263 let reader =
1264 write_and_read(Default::default(), vec![make_batch(&[(1, 10), (2, 20)])]).unwrap();
1265 let batches = reader.read_columns(&[1]).unwrap();
1266 assert_eq!(batches[0].columns.len(), 1);
1267 if let Column::Int64(vals) = &batches[0].columns[0] {
1268 assert_eq!(vals, &vec![10, 20]);
1269 } else {
1270 panic!("expected int64");
1271 }
1272 }
1273
1274 #[test]
1275 fn test_chunk_checksum_validation() {
1276 let cfg = SegmentConfigV2 {
1277 checksum_scope: ChecksumScope::Chunk,
1278 ..Default::default()
1279 };
1280 let segment = {
1281 let mut writer = SegmentWriterV2::new(cfg);
1282 writer
1283 .write_batch(make_batch(&[(1, 10), (2, 20), (3, 30)]))
1284 .unwrap();
1285 writer.finish().unwrap()
1286 };
1287 let reader_ok =
1288 SegmentReaderV2::open(Box::new(InMemorySegmentSource::new(segment.data.clone())))
1289 .unwrap();
1290 let entry = &reader_ok.footer.row_group_table.entries[0];
1291 let chunk = &entry.column_chunk_offsets[0];
1292 let mut corrupted = segment.data.clone();
1293 let chunk_pos = (entry.data_offset + chunk.offset) as usize;
1294 corrupted[chunk_pos] ^= 0xAA;
1295 let reader =
1296 SegmentReaderV2::open(Box::new(InMemorySegmentSource::new(corrupted))).unwrap();
1297 let err = reader.read_columns(&[0]).unwrap_err();
1298 assert!(matches!(err, ColumnarError::ChecksumMismatch));
1299 }
1300
1301 #[test]
1302 fn row_id_encode_decode_roundtrip() {
1303 let segment_id = 123u64;
1304 let offset = 456u64;
1305 let encoded = encode_row_id(segment_id, offset).expect("encode");
1306 let (decoded_seg, decoded_off) = decode_row_id(encoded);
1307 assert_eq!(decoded_seg, segment_id);
1308 assert_eq!(decoded_off, offset);
1309 }
1310
1311 #[test]
1312 fn row_id_encode_rejects_overflow() {
1313 let overflow_segment = (1u64 << ROW_ID_SEGMENT_BITS) + 1;
1314 assert!(matches!(
1315 encode_row_id(overflow_segment, 0),
1316 Err(ColumnarError::InvalidFormat(_))
1317 ));
1318
1319 let overflow_offset = (1u64 << ROW_ID_OFFSET_BITS) + 1;
1320 assert!(matches!(
1321 encode_row_id(0, overflow_offset),
1322 Err(ColumnarError::InvalidFormat(_))
1323 ));
1324 }
1325}