Encode a row of values into a Binary Tuple. `values` must have exactly `schema.len()` entries. A `Value::Null` is allowed only if the corresponding column is nullable.
(&self, values: &[Value])
| 75 | /// `values` must have exactly `schema.len()` entries. A `Value::Null` is |
| 76 | /// allowed only if the corresponding column is nullable. |
| 77 | pub fn encode(&self, values: &[Value]) -> Result<Vec<u8>, StrictError> { |
| 78 | let n_cols = self.schema.columns.len(); |
| 79 | if values.len() != n_cols { |
| 80 | return Err(StrictError::ValueCountMismatch { |
| 81 | expected: n_cols, |
| 82 | got: values.len(), |
| 83 | }); |
| 84 | } |
| 85 | |
| 86 | // Pre-size: header + fixed + offset_table. Variable data appended later. |
| 87 | let offset_table_size = (self.var_indices.len() + 1) * 4; |
| 88 | let base_size = self.header_size + self.fixed_section_size + offset_table_size; |
| 89 | let mut buf = vec![0u8; base_size]; |
| 90 | |
| 91 | // 1. Magic, format version, schema version. |
| 92 | buf[0..4].copy_from_slice(&MAGIC.to_le_bytes()); |
| 93 | buf[4] = FORMAT_VERSION; |
| 94 | buf[5..9].copy_from_slice(&self.schema.version.to_le_bytes()); |
| 95 | |
| 96 | // 2. Null bitmap + fixed fields + type validation. |
| 97 | let bitmap_start = 9; |
| 98 | let fixed_start = self.header_size; |
| 99 | |
| 100 | for (i, (col, val)) in self.schema.columns.iter().zip(values.iter()).enumerate() { |
| 101 | let is_null = matches!(val, Value::Null); |
| 102 | |
| 103 | if is_null { |
| 104 | if !col.nullable { |
| 105 | return Err(StrictError::NullViolation(col.name.clone())); |
| 106 | } |
| 107 | // Set null bit: byte = i / 8, bit = i % 8. |
| 108 | buf[bitmap_start + i / 8] |= 1 << (i % 8); |
| 109 | // Fixed fields remain zeroed; no variable data emitted. |
| 110 | continue; |
| 111 | } |
| 112 | |
| 113 | // Type check (with coercion). |
| 114 | if !col.column_type.accepts(val) { |
| 115 | return Err(StrictError::TypeMismatch { |
| 116 | column: col.name.clone(), |
| 117 | expected: col.column_type, |
| 118 | }); |
| 119 | } |
| 120 | |
| 121 | // Write fixed-size value. |
| 122 | if let Some(offset) = self.fixed_offsets[i] { |
| 123 | let dst = fixed_start + offset; |
| 124 | encode_fixed(&mut buf[dst..], &col.column_type, val); |
| 125 | } |
| 126 | // Variable-length values are handled in the offset table pass below. |
| 127 | } |
| 128 | |
| 129 | // 3. Variable-length fields: build offset table + variable data. |
| 130 | let offset_table_start = self.header_size + self.fixed_section_size; |
| 131 | let mut var_data: Vec<u8> = Vec::new(); |
| 132 | |
| 133 | for (var_idx, &col_idx) in self.var_indices.iter().enumerate() { |
| 134 | // Write current offset. |