Insert a row with upsert-on-duplicate semantics. Returns WAL records to persist. Validates schema. If the PK already exists, the prior row is tombstoned via the segment's delete bitmap (a single positional delete) before the new row is appended to the memtable. The PK index is rebound to the new row location. This matches the ClickHouse / Iceberg "sparse PK + positional delete" model and keeps `S
(&mut self, values: &[Value])
| 28 | /// want `ON CONFLICT DO NOTHING` semantics should use |
| 29 | /// [`Self::insert_if_absent`]. |
| 30 | pub fn insert(&mut self, values: &[Value]) -> Result<MutationResult, ColumnarError> { |
| 31 | let pk_bytes = self.extract_pk_bytes(values)?; |
| 32 | let mut wal_records = Vec::with_capacity(2); |
| 33 | |
| 34 | // Bitemporal collections preserve every version of a PK: each |
| 35 | // write appends a new row with a distinct `_ts_system` stamp and |
| 36 | // the prior row stays visible to `AS OF` queries. Skipping the |
| 37 | // upsert-tombstone here keeps compaction lossless without |
| 38 | // needing a separate "version-aware" delete bitmap. The PK |
| 39 | // index is still rebound below so current-state reads see the |
| 40 | // latest version. |
| 41 | let bitemporal = self.schema.is_bitemporal(); |
| 42 | |
| 43 | // If a prior row exists for this PK, tombstone it in place so |
| 44 | // subsequent scans skip the stale row. The PK index is rebound |
| 45 | // below to the freshly-appended row. |
| 46 | if !bitemporal && let Some(prior) = self.pk_index.get(&pk_bytes).copied() { |
| 47 | let bitmap = self.delete_bitmaps.entry(prior.segment_id).or_default(); |
| 48 | bitmap.mark_deleted(prior.row_index); |
| 49 | wal_records.push(ColumnarWalRecord::DeleteRows { |
| 50 | collection: self.collection.clone(), |
| 51 | segment_id: prior.segment_id, |
| 52 | row_indices: vec![prior.row_index], |
| 53 | }); |
| 54 | } |
| 55 | |
| 56 | let row_data = encode_row_for_wal(values)?; |
| 57 | wal_records.push(ColumnarWalRecord::InsertRow { |
| 58 | collection: self.collection.clone(), |
| 59 | row_data, |
| 60 | }); |
| 61 | |
| 62 | self.memtable.append_row(values)?; |
| 63 | let location = RowLocation { |
| 64 | segment_id: self.memtable_segment_id, |
| 65 | row_index: self.memtable_row_counter, |
| 66 | }; |
| 67 | self.pk_index.upsert(pk_bytes, location); |
| 68 | self.memtable_surrogates.push(None); |
| 69 | self.memtable_row_counter += 1; |
| 70 | |
| 71 | Ok(MutationResult { wal_records }) |
| 72 | } |
| 73 | |
| 74 | /// Insert with a stable cross-engine surrogate identity. |
| 75 | /// |