Encode a drained memtable into a segment byte buffer. `schema` is the column schema, `columns` are the drained column data, `row_count` is the total number of rows. When `kek` is `Some`, the assembled plaintext segment is wrapped in an AES-256-GCM encrypted `SEGC` envelope before being returned. When `None`, the raw `NDBS` segment bytes are returned.
(
&self,
schema: &ColumnarSchema,
columns: &[ColumnData],
row_count: usize,
kek: Option<&nodedb_wal::crypto::WalEncryptionKey>,
)
| 63 | /// AES-256-GCM encrypted `SEGC` envelope before being returned. When |
| 64 | /// `None`, the raw `NDBS` segment bytes are returned. |
| 65 | pub fn write_segment( |
| 66 | &self, |
| 67 | schema: &ColumnarSchema, |
| 68 | columns: &[ColumnData], |
| 69 | row_count: usize, |
| 70 | kek: Option<&nodedb_wal::crypto::WalEncryptionKey>, |
| 71 | ) -> Result<Vec<u8>, ColumnarError> { |
| 72 | if row_count == 0 { |
| 73 | return Err(ColumnarError::EmptyMemtable); |
| 74 | } |
| 75 | if columns.len() != schema.columns.len() { |
| 76 | return Err(ColumnarError::SchemaMismatch { |
| 77 | expected: schema.columns.len(), |
| 78 | got: columns.len(), |
| 79 | }); |
| 80 | } |
| 81 | |
| 82 | let mut buf = Vec::new(); |
| 83 | |
| 84 | // 1. Write header. |
| 85 | buf.extend_from_slice(&SegmentHeader::current().to_bytes()); |
| 86 | |
| 87 | // 2. Encode each column's blocks. |
| 88 | let _metas_guard = self |
| 89 | .governor |
| 90 | .as_ref() |
| 91 | .map(|g| { |
| 92 | g.reserve( |
| 93 | EngineId::Columnar, |
| 94 | columns.len() * std::mem::size_of::<ColumnMeta>(), |
| 95 | ) |
| 96 | }) |
| 97 | .transpose()?; |
| 98 | let mut column_metas = Vec::with_capacity(columns.len()); |
| 99 | |
| 100 | for (i, (col_def, col_data)) in schema.columns.iter().zip(columns.iter()).enumerate() { |
| 101 | let col_start = buf.len() as u64; |
| 102 | |
| 103 | // Select codec for this column type. |
| 104 | let codec = select_codec_for_profile(&col_def.column_type, self.profile_tag); |
| 105 | |
| 106 | // Encode blocks. |
| 107 | let block_stats = encode_column_blocks( |
| 108 | &mut buf, |
| 109 | col_data, |
| 110 | &col_def.column_type, |
| 111 | codec, |
| 112 | row_count, |
| 113 | self.governor.as_ref(), |
| 114 | )?; |
| 115 | |
| 116 | let col_end = buf.len() as u64; |
| 117 | |
| 118 | // For DictEncoded columns, the codec stored in meta is DeltaFastLanesLz4 (IDs), |
| 119 | // and the dictionary strings are stored in the meta for reader reconstruction. |
| 120 | let (effective_codec, dictionary) = match col_data { |
| 121 | ColumnData::DictEncoded { dictionary, .. } => ( |
| 122 | ResolvedColumnCodec::DeltaFastLanesLz4, |