Skip to main content

cqlite_core/storage/write_engine/
mutation.rs

1//! Mutation types for CQL write operations
2//!
3//! Represents INSERT, UPDATE, DELETE operations as structured mutations.
4//! Supports cell-level operations with timestamps and TTL.
5//!
6//! This module implements the core data types for M5 write support:
7//! - `Mutation`: Represents a write operation (INSERT, UPDATE, DELETE)
8//! - `DecoratedKey`: Token + raw key bytes for partition ordering
9//! - `PartitionKey`: Multi-column partition key with schema-aware encoding
10//! - `ClusteringKey`: Multi-column clustering key with ASC/DESC ordering
11//! - `CellOperation`: Cell-level write/delete operations
12
13use crate::error::{Error, Result};
14use crate::schema::{ClusteringOrder, TableSchema};
15use crate::types::{ComparatorType, Value};
16use std::cmp::Ordering;
17
18/// Table identifier (keyspace + table name)
19#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
20pub struct TableId {
21    /// Keyspace name
22    pub keyspace: String,
23    /// Table name
24    pub table: String,
25}
26
27impl TableId {
28    /// Create a new table identifier
29    pub fn new(keyspace: impl Into<String>, table: impl Into<String>) -> Self {
30        Self {
31            keyspace: keyspace.into(),
32            table: table.into(),
33        }
34    }
35
36    /// Get the fully qualified table name (keyspace.table)
37    pub fn qualified_name(&self) -> String {
38        format!("{}.{}", self.keyspace, self.table)
39    }
40}
41
42impl std::fmt::Display for TableId {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        write!(f, "{}.{}", self.keyspace, self.table)
45    }
46}
47
48/// A mutation represents a write operation (INSERT, UPDATE, DELETE)
49///
50/// This is the fundamental unit of write operations in CQLite, corresponding to
51/// a single CQL INSERT/UPDATE/DELETE statement. Each mutation targets a specific
52/// row (identified by partition key + optional clustering key) and contains
53/// one or more cell operations.
54///
55/// # Tombstone Support (M5.2)
56///
57/// Mutations can represent various deletion types:
58/// - Cell tombstone: `CellOperation::Delete` for single column
59/// - Row tombstone: `CellOperation::DeleteRow` for entire row
60/// - Range tombstone: `range_tombstones` field for clustering key ranges
61/// - Partition tombstone: `partition_tombstone` field for entire partition
62#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
63pub struct Mutation {
64    /// Target table
65    pub table: TableId,
66    /// Partition key values
67    pub partition_key: PartitionKey,
68    /// Clustering key values (None for tables without clustering keys)
69    pub clustering_key: Option<ClusteringKey>,
70    /// Cell-level operations (writes or deletes)
71    pub operations: Vec<CellOperation>,
72    /// Timestamp in microseconds since Unix epoch
73    pub timestamp_micros: i64,
74    /// Time-to-live in seconds applied to all cells in this mutation (None = no expiration).
75    ///
76    /// This is set by `USING TTL` in CQL statements and applies uniformly to all
77    /// `Write` operations. For per-column TTL, use `CellOperation::WriteWithTtl`
78    /// in the operations list instead.
79    pub ttl_seconds: Option<u32>,
80    /// Partition tombstone (deletes entire partition)
81    pub partition_tombstone: Option<PartitionTombstone>,
82    /// Range tombstones (delete clustering key ranges within partition)
83    pub range_tombstones: Vec<RangeTombstone>,
84    /// Explicit local deletion time (seconds since Unix epoch) for the row and
85    /// cell tombstones (`DeleteRow`, `Delete`) produced by this mutation.
86    ///
87    /// `None` (the default) preserves historical behavior: the writer derives
88    /// the local deletion time from `timestamp_micros` (`timestamp_micros /
89    /// 1_000_000`). When `Some(ldt)`, the writer emits exactly `ldt` as the
90    /// `localDeletionTime` of every row/cell tombstone in this mutation, and
91    /// feeds it into the Statistics.db min/max `localDeletionTime` aggregates.
92    ///
93    /// Partition and range tombstones carry their own `local_deletion_time`
94    /// inside [`PartitionTombstone`] / [`RangeTombstone`] and are unaffected by
95    /// this field. (Issue #764)
96    #[serde(default)]
97    pub local_deletion_time: Option<i32>,
98    /// Row-level deletion that COEXISTS with the live cells produced by this
99    /// mutation's `operations` (issue #932).
100    ///
101    /// `Some((deletion_time_micros, local_deletion_time_secs))` carries a row
102    /// tombstone whose `deletion_time` is DECOUPLED from `timestamp_micros` — it
103    /// is older than the row's surviving cells (whose own writetimes drive
104    /// `timestamp_micros`). The writer emits a `HAS_DELETION` row carrying BOTH
105    /// the deletion AND the surviving cells, so older cells of OTHER columns in
106    /// SSTables not part of a partial compaction stay shadowed (they cannot
107    /// resurrect). This is distinct from a `CellOperation::DeleteRow`, whose
108    /// deletion time IS the mutation timestamp; `row_tombstone` is used only on
109    /// the compaction merge→mutation path where the two diverge.
110    ///
111    /// `None` (the default) preserves historical behavior: a row deletion, when
112    /// present, rides as `CellOperation::DeleteRow` at `timestamp_micros`.
113    #[serde(default)]
114    pub row_tombstone: Option<(i64, i32)>,
115    /// Per-column write timestamps (microseconds) for the simple-cell
116    /// `Write`/`WriteWithTtl` operations of this mutation (issue #1018).
117    ///
118    /// `CellOperation::Write`/`WriteWithTtl` carry no per-cell timestamp — every
119    /// such cell historically inherits the row's single `timestamp_micros`. On
120    /// the compaction merge→mutation path a reconciled row can hold sibling cells
121    /// at DIFFERENT writetimes (e.g. live `name`@100 alongside a `score` cell
122    /// tombstone@300). Promoting every live cell to the row's MAX writetime would
123    /// rewrite `name`'s writetime to 300, losing fidelity. This side-channel maps
124    /// a regular column name → that cell's OWN write timestamp so the writer emits
125    /// it verbatim (clearing `USE_ROW_TIMESTAMP` when it differs from the row
126    /// marker).
127    ///
128    /// `None` / absent column (the default) preserves historical behavior: the
129    /// cell inherits `timestamp_micros`. Populated only by
130    /// `merge_entry_to_mutation`; the WAL / CQL-INSERT paths leave it unset (their
131    /// cells genuinely share one writetime). `#[serde(default)]` keeps backward
132    /// compatibility with mutations serialized before this field existed.
133    #[serde(default)]
134    pub cell_write_timestamps: Option<std::collections::HashMap<String, i64>>,
135}
136
137impl Mutation {
138    /// Create a new mutation
139    pub fn new(
140        table: TableId,
141        partition_key: PartitionKey,
142        clustering_key: Option<ClusteringKey>,
143        operations: Vec<CellOperation>,
144        timestamp_micros: i64,
145        ttl_seconds: Option<u32>,
146    ) -> Self {
147        Self {
148            table,
149            partition_key,
150            clustering_key,
151            operations,
152            timestamp_micros,
153            ttl_seconds,
154            partition_tombstone: None,
155            range_tombstones: Vec::new(),
156            local_deletion_time: None,
157            row_tombstone: None,
158            cell_write_timestamps: None,
159        }
160    }
161
162    /// Per-cell write timestamp (microseconds) for `column`, falling back to the
163    /// mutation's row `timestamp_micros` when no per-cell override was supplied
164    /// (issue #1018). Used by the writer to decide whether a simple cell keeps
165    /// `USE_ROW_TIMESTAMP` or carries its own explicit delta.
166    pub fn cell_write_timestamp(&self, column: &str) -> i64 {
167        self.cell_write_timestamps
168            .as_ref()
169            .and_then(|m| m.get(column).copied())
170            .unwrap_or(self.timestamp_micros)
171    }
172
173    /// Attach a row-level deletion that coexists with this mutation's live cells
174    /// (issue #932).
175    ///
176    /// `deletion_time` is in microseconds (`markedForDeleteAt`); `ldt` is the
177    /// `localDeletionTime` in GC-clock seconds. The writer emits a `HAS_DELETION`
178    /// row carrying both the deletion and the surviving cells, decoupling the
179    /// deletion time from `timestamp_micros` (the row's liveness writetime).
180    #[must_use]
181    pub fn with_row_tombstone(mut self, deletion_time: i64, ldt: i32) -> Self {
182        self.row_tombstone = Some((deletion_time, ldt));
183        self
184    }
185
186    /// Set an explicit local deletion time (seconds since Unix epoch) for the
187    /// row/cell tombstones produced by this mutation (Issue #764).
188    ///
189    /// When unset, the writer derives the local deletion time from
190    /// `timestamp_micros` exactly as it did historically.
191    pub fn with_local_deletion_time(mut self, local_deletion_time: i32) -> Self {
192        self.local_deletion_time = Some(local_deletion_time);
193        self
194    }
195
196    /// Local deletion time (seconds since Unix epoch) to stamp on the row/cell
197    /// tombstones of this mutation, falling back to the timestamp-derived value
198    /// when no explicit value was supplied (Issue #764).
199    pub fn effective_local_deletion_time(&self) -> i32 {
200        self.local_deletion_time
201            .unwrap_or((self.timestamp_micros / 1_000_000) as i32)
202    }
203
204    /// Get the decorated key for this mutation (token + raw bytes)
205    pub fn decorated_key(&self, schema: &TableSchema) -> Result<DecoratedKey> {
206        self.partition_key.to_decorated_key(schema)
207    }
208}
209
210/// Partition tombstone for deleting entire partition
211///
212/// Stored in the partition header and shadows all rows in the partition
213/// when the partition deletion time is greater than the row timestamps.
214#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
215pub struct PartitionTombstone {
216    /// Deletion timestamp in microseconds since Unix epoch
217    pub deletion_time: i64,
218    /// Local deletion time in seconds since Unix epoch
219    pub local_deletion_time: i32,
220}
221
222/// Range tombstone for deleting a range of clustering keys
223///
224/// Stored as markers within the partition data and shadows all rows
225/// in the specified clustering key range.
226#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
227pub struct RangeTombstone {
228    /// Start bound (inclusive or exclusive)
229    pub start: ClusteringBound,
230    /// End bound (inclusive or exclusive)
231    pub end: ClusteringBound,
232    /// Deletion timestamp in microseconds since Unix epoch
233    pub deletion_time: i64,
234    /// Local deletion time in seconds since Unix epoch
235    pub local_deletion_time: i32,
236}
237
238/// Clustering key bound for range tombstones
239///
240/// Defines the boundary of a range deletion. Can be inclusive or exclusive,
241/// or represent the minimum/maximum possible clustering key.
242#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
243pub enum ClusteringBound {
244    /// Inclusive bound (clustering key is part of the deletion range)
245    Inclusive(ClusteringKey),
246    /// Exclusive bound (clustering key is NOT part of the deletion range)
247    Exclusive(ClusteringKey),
248    /// Before all clustering keys (start of partition)
249    Bottom,
250    /// After all clustering keys (end of partition)
251    Top,
252}
253
254/// Operations that can be applied to individual cells within a row.
255///
256/// # Per-Cell TTL
257///
258/// Per-cell TTL is supported via the `WriteWithTtl` variant when using
259/// the JSON mutation format directly. CQL syntax (`USING TTL`) applies
260/// TTL uniformly to all cells in a statement. To set different TTLs
261/// per column, submit separate mutations or use JSON mutations with
262/// `WriteWithTtl`:
263///
264/// ```json
265/// {"WriteWithTtl": {"column": "session_token", "value": {"Text": "abc"}, "ttl_seconds": 3600}}
266/// ```
267#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
268pub enum CellOperation {
269    /// Write a value to a column
270    Write {
271        /// Column name
272        column: String,
273        /// Column value
274        value: Value,
275    },
276    /// Write a value to a column with TTL (expiring cell).
277    ///
278    /// The cell will expire after `ttl_seconds` seconds. This is the only
279    /// way to set per-column TTL — CQL `USING TTL` applies to all cells
280    /// in a statement. Use JSON mutations to set different TTLs per column.
281    WriteWithTtl {
282        /// Column name
283        column: String,
284        /// Column value
285        value: Value,
286        /// Time-to-live in seconds
287        ttl_seconds: u32,
288    },
289    /// Delete a specific column
290    Delete {
291        /// Column name
292        column: String,
293        /// Optional per-cell `localDeletionTime` (seconds since the Unix epoch,
294        /// the on-disk GC clock) for this cell tombstone (#921 finding 2).
295        ///
296        /// When `Some`, the writer stamps the emitted cell tombstone with this
297        /// LDT VERBATIM rather than deriving one from the enclosing mutation's
298        /// timestamp / [`Mutation::local_deletion_time`]. The compaction
299        /// merge→rewrite path sets it from the SOURCE cell tombstone's own LDT
300        /// (`TombstoneInfo::local_deletion_time`) so a within-grace cell
301        /// tombstone that survives a compaction keeps its original GC clock and
302        /// is not purged too early / kept too long in a LATER compaction.
303        ///
304        /// `None` preserves the historical behavior (the writer derives the LDT
305        /// from the mutation), so the WAL / CQL-DELETE paths that build a
306        /// `Delete` without a surfaced source LDT are unchanged.
307        ///
308        /// `#[serde(default)]` keeps backward compatibility with `Delete`
309        /// values serialized before this field existed (e.g. older WAL records).
310        ///
311        /// [`Mutation::local_deletion_time`]: Mutation::local_deletion_time
312        #[serde(default)]
313        local_deletion_time: Option<i32>,
314    },
315    /// Delete entire row (row tombstone)
316    DeleteRow,
317    /// Write (or tombstone) a single element of a non-frozen complex column
318    /// (a list/set member or a map entry), carrying its OWN write metadata and
319    /// its preserved source cell path (epic #899, Phase B).
320    ///
321    /// Unlike [`Write`](Self::Write)/[`WriteWithTtl`](Self::WriteWithTtl) — which
322    /// describe a whole-column value at one row timestamp — this op preserves the
323    /// per-element granularity of Cassandra's multi-cell on-disk layout so the
324    /// compaction writer can emit two elements of the same column at DIFFERENT
325    /// timestamps (explicit deltas, not one promoted row timestamp), round-trip a
326    /// LIST element's 16-byte TimeUUID cell path byte-for-byte, and emit an
327    /// element-level tombstone (`value == None`, IS_DELETED).
328    ///
329    /// PHASE B SCOPE: this variant is plumbing + writer capability only. The real
330    /// compaction pipeline (`merge_entry_to_mutation`) does NOT yet emit it — that
331    /// flip (and differential byte-parity verification) is Phase C. In Phase B it
332    /// is exercised by unit tests that hand-construct mutations.
333    WriteComplexElement {
334        /// Complex column name.
335        column: String,
336        /// Raw cell-path bytes identifying this element (the serialized element
337        /// for a SET, the serialized key for a MAP, the 16-byte TimeUUID for a
338        /// LIST). Preserved byte-for-byte from the source SSTable — never
339        /// regenerated.
340        cell_path: Vec<u8>,
341        /// Decoded element value. `None` means an element-level tombstone
342        /// (IS_DELETED) or an empty-value element (e.g. SET members store the
343        /// element in the path with an empty value). Use `is_deleted` — NOT the
344        /// shape of this field — to distinguish the two.
345        value: Option<Value>,
346        /// Per-element write timestamp in microseconds. When equal to the row
347        /// liveness timestamp the writer keeps `USE_ROW_TIMESTAMP` (0x08);
348        /// otherwise it clears the flag and writes an explicit unsigned delta.
349        timestamp_micros: i64,
350        /// TTL in seconds when the element is expiring (`None` otherwise).
351        ttl_seconds: Option<u32>,
352        /// `localDeletionTime` in seconds for an expiring / deleted element
353        /// (`None` when not applicable). Far-future values in `[2^31, 2^32)` are
354        /// carried as the wrapping `as u32 as i32` representation.
355        local_deletion_time: Option<i32>,
356        /// Authoritative per-element IS_DELETED (0x01) flag, carried verbatim
357        /// from the reader's [`ComplexElement::is_deleted`] (the source of
358        /// truth). The writer emits `CELL_IS_DELETED` iff this is `true`.
359        ///
360        /// This is NOT re-derived from `value`/`ttl_seconds`/`local_deletion_time`
361        /// (no-heuristics mandate, CLAUDE.md): an expiring SET member legitimately
362        /// has `value == None`, `ttl_seconds == Some`, `local_deletion_time ==
363        /// Some` while `is_deleted == false` — re-deriving would misclassify it as
364        /// a tombstone (roborev #885, Finding 2).
365        ///
366        /// [`ComplexElement::is_deleted`]: crate::storage::sstable::reader::compaction_row::ComplexElement::is_deleted
367        is_deleted: bool,
368    },
369    /// A per-column complex deletion marker (the `markedForDeleteAt` +
370    /// `localDeletionTime` tombstone Cassandra writes ahead of a multi-cell
371    /// column's elements) covering elements written at or before
372    /// `marked_for_delete_at` (epic #899, Phase B).
373    ///
374    /// When present for a complex column, the writer emits a REAL complex
375    /// deletion marker (replacing the hardcoded `DeletionTime.LIVE` sentinel)
376    /// followed by the surviving per-element cells.
377    ///
378    /// PHASE B SCOPE: plumbing + writer capability only; not yet emitted by
379    /// `merge_entry_to_mutation` (Phase C).
380    ComplexDeletion {
381        /// Complex column name.
382        column: String,
383        /// `markedForDeleteAt` in microseconds.
384        marked_for_delete_at: i64,
385        /// `localDeletionTime` in seconds since the Unix epoch.
386        local_deletion_time: i32,
387    },
388}
389
390/// Partition key with multi-column support
391///
392/// Stores the partition key as a list of (column name, value) pairs.
393/// The order must match the schema's partition key definition.
394#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
395pub struct PartitionKey {
396    /// Column name and value pairs (in schema order)
397    pub columns: Vec<(String, Value)>,
398}
399
400impl PartitionKey {
401    /// Create a new partition key
402    pub fn new(columns: Vec<(String, Value)>) -> Self {
403        Self { columns }
404    }
405
406    /// Create a partition key from a single column
407    pub fn single(column: impl Into<String>, value: Value) -> Self {
408        Self {
409            columns: vec![(column.into(), value)],
410        }
411    }
412
413    /// Serialize partition key to bytes according to Cassandra's on-disk encoding.
414    ///
415    /// Single-component keys are written as raw value bytes.
416    /// Multi-component keys use `[len][value][0x00]` per component, including a
417    /// trailing `0x00` after the final component.
418    pub fn to_bytes(&self, schema: &TableSchema) -> Result<Vec<u8>> {
419        if self.columns.is_empty() {
420            return Err(Error::InvalidInput("Empty partition key".to_string()));
421        }
422
423        // Validate column count matches schema
424        if self.columns.len() != schema.partition_keys.len() {
425            return Err(Error::InvalidInput(format!(
426                "Partition key column count mismatch: expected {}, got {}",
427                schema.partition_keys.len(),
428                self.columns.len()
429            )));
430        }
431
432        // Single-component key: no length prefix. Return the serialized value
433        // directly to avoid allocating (and copying into) an intermediate Vec.
434        if self.columns.len() == 1 {
435            return self.serialize_value(&self.columns[0].1, &schema.partition_keys[0]);
436        }
437
438        let mut result = Vec::new();
439
440        // Multi-component partition keys use a `0x00` end-of-component marker
441        // after every component, including the last one.
442        for (i, (_, value)) in self.columns.iter().enumerate() {
443            let value_bytes = self.serialize_value(value, &schema.partition_keys[i])?;
444            let len = value_bytes.len();
445            if len > u16::MAX as usize {
446                return Err(Error::InvalidInput(format!(
447                    "Partition key component too large: {} bytes",
448                    len
449                )));
450            }
451            // 2-byte big-endian length prefix
452            result.extend_from_slice(&(len as u16).to_be_bytes());
453            result.extend_from_slice(&value_bytes);
454            result.push(0x00);
455        }
456
457        Ok(result)
458    }
459
460    /// Convert to DecoratedKey (token + raw bytes)
461    pub fn to_decorated_key(&self, schema: &TableSchema) -> Result<DecoratedKey> {
462        let key_bytes = self.to_bytes(schema)?;
463        let token = calculate_murmur3_token(&key_bytes)?;
464        Ok(DecoratedKey::new(token, key_bytes))
465    }
466
467    /// Deserialize partition key from raw bytes (inverse of `to_bytes`)
468    ///
469    /// Single-component keys are raw value bytes.
470    /// Multi-component keys use `[len:u16 BE][value bytes][0x00]` per component.
471    pub fn from_bytes(data: &[u8], schema: &TableSchema) -> Result<Self> {
472        if schema.partition_keys.is_empty() {
473            return Err(Error::InvalidInput(
474                "Schema has no partition keys".to_string(),
475            ));
476        }
477
478        if data.is_empty() {
479            return Err(Error::InvalidInput("Empty partition key bytes".to_string()));
480        }
481
482        // Delegate to the canonical codec shared with the read/scan path so the
483        // two never diverge (Issue #586).
484        let columns =
485            crate::storage::partition_key_codec::decode_partition_key_columns(data, schema)?;
486
487        Ok(PartitionKey { columns })
488    }
489
490    /// Serialize a single value to bytes according to its CQL type
491    fn serialize_value(
492        &self,
493        value: &Value,
494        key_column: &crate::schema::KeyColumn,
495    ) -> Result<Vec<u8>> {
496        // Get comparator type from schema
497        let comparator = ComparatorType::from_data_type(&key_column.data_type)?;
498
499        serialize_value_bytes(value, &comparator)
500    }
501}
502
503/// Clustering key with multi-column support
504///
505/// Stores the clustering key as a list of (column name, value) pairs.
506/// The order must match the schema's clustering key definition.
507#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
508pub struct ClusteringKey {
509    /// Column name and value pairs (in schema order)
510    pub columns: Vec<(String, Value)>,
511}
512
513impl ClusteringKey {
514    /// Create a new clustering key
515    pub fn new(columns: Vec<(String, Value)>) -> Self {
516        Self { columns }
517    }
518
519    /// Create a clustering key from a single column
520    pub fn single(column: impl Into<String>, value: Value) -> Self {
521        Self {
522            columns: vec![(column.into(), value)],
523        }
524    }
525
526    /// Compare two clustering keys according to schema-defined ordering
527    ///
528    /// Each clustering column can be ASC or DESC. This method requires
529    /// schema information to determine the correct ordering.
530    pub fn compare(&self, other: &Self, schema: &TableSchema) -> Result<Ordering> {
531        // A clustering key may have *fewer* stored components than the schema
532        // defines (an absent trailing component). Cassandra treats an absent
533        // component exactly like an explicit NULL: it sorts first regardless of
534        // ASC/DESC. We therefore iterate over the schema clustering positions
535        // and read each side positionally via `.get(i)`, rather than zipping the
536        // two `columns` vectors (which would stop at the shorter key and report
537        // a falsely-Equal ordering for a genuinely absent trailing component).
538        if self.columns.len() > schema.clustering_keys.len()
539            || other.columns.len() > schema.clustering_keys.len()
540        {
541            return Err(Error::Schema(format!(
542                "Clustering key has more columns than schema: {} / {} > {}",
543                self.columns.len(),
544                other.columns.len(),
545                schema.clustering_keys.len()
546            )));
547        }
548
549        for (i, cluster_col) in schema.clustering_keys.iter().enumerate() {
550            // Absent component (shorter key) is equivalent to an explicit NULL.
551            let a_val = self.columns.get(i).map(|(_, v)| v).unwrap_or(&Value::Null);
552            let b_val = other.columns.get(i).map(|(_, v)| v).unwrap_or(&Value::Null);
553
554            // Cassandra (ref 587612cd): clustering reversal (DESC) only reverses
555            // the comparison of two *present* values. An absent/NULL component
556            // sorts first regardless of ASC/DESC, so the reversal must never flip
557            // null/empty-component ordering. Empty-vs-valued comparisons are
558            // routed through the type (compare_values) so they reverse correctly
559            // on DESC columns, but an absent or NULL component is not a typed
560            // value and is handled positionally here.
561            let final_ordering = match (a_val, b_val) {
562                // Two absent/NULL components are equal in either direction.
563                (Value::Null, Value::Null) => Ordering::Equal,
564                // Exactly one side absent/NULL: NULL sorts first (Less)
565                // regardless of ASC/DESC. Do not apply the DESC reversal.
566                (Value::Null, _) => Ordering::Less,
567                (_, Value::Null) => Ordering::Greater,
568                // Both present: compare through the type, then apply DESC
569                // reversal so empty-vs-valued ordering matches Cassandra.
570                (_, _) => {
571                    let ordering = compare_values(a_val, b_val)?;
572                    if cluster_col.order == ClusteringOrder::Desc {
573                        ordering.reverse()
574                    } else {
575                        ordering
576                    }
577                }
578            };
579
580            if final_ordering != Ordering::Equal {
581                return Ok(final_ordering);
582            }
583        }
584
585        Ok(Ordering::Equal)
586    }
587}
588
589impl Ord for ClusteringKey {
590    fn cmp(&self, other: &Self) -> Ordering {
591        // Fallback comparison without schema: lexicographic by value
592        // This is used for BTreeMap ordering in memtable.
593        // Schema-aware comparison should use `compare()` method.
594        for ((_, a_val), (_, b_val)) in self.columns.iter().zip(other.columns.iter()) {
595            let ordering = compare_values(a_val, b_val).unwrap_or_else(|_| {
596                // Type mismatch fallback: deterministic ordering via Debug representation.
597                // This only matters for BTreeMap key placement in memtable, not persistence.
598                // Heterogeneous SSTables (e.g. Frozen(List) vs List) must not crash the process.
599                format!("{a_val:?}").cmp(&format!("{b_val:?}"))
600            });
601            if ordering != Ordering::Equal {
602                return ordering;
603            }
604        }
605        self.columns.len().cmp(&other.columns.len())
606    }
607}
608
609impl PartialOrd for ClusteringKey {
610    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
611        Some(self.cmp(other))
612    }
613}
614
615impl Eq for ClusteringKey {}
616
617/// Decorated key: Murmur3 token + raw partition key bytes
618///
619/// This is the fundamental ordering key in Cassandra SSTables. Partitions are
620/// ordered first by token (i64), then by raw key bytes for collision resolution.
621///
622/// # Hash Collision Handling
623///
624/// While Murmur3 hash collisions are extremely rare in practice, the ordering
625/// implementation handles them correctly:
626/// 1. Primary ordering: by token (Murmur3 hash value)
627/// 2. Secondary ordering: by raw partition key bytes (for hash collisions)
628///
629/// This ensures deterministic, stable ordering even when two different partition
630/// keys produce the same token value.
631#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
632pub struct DecoratedKey {
633    /// Murmur3 hash token (i64)
634    pub token: i64,
635    /// Raw partition key bytes
636    pub key: Vec<u8>,
637}
638
639impl DecoratedKey {
640    /// Create a new decorated key
641    pub fn new(token: i64, key: Vec<u8>) -> Self {
642        Self { token, key }
643    }
644
645    /// Create a decorated key from raw partition key bytes
646    pub fn from_key_bytes(key_bytes: Vec<u8>) -> Result<Self> {
647        let token = calculate_murmur3_token(&key_bytes)?;
648        Ok(Self::new(token, key_bytes))
649    }
650}
651
652impl Ord for DecoratedKey {
653    fn cmp(&self, other: &Self) -> Ordering {
654        // Primary ordering: by token
655        match self.token.cmp(&other.token) {
656            Ordering::Equal => {
657                // Secondary ordering: by raw key bytes (for hash collisions)
658                self.key.cmp(&other.key)
659            }
660            other => other,
661        }
662    }
663}
664
665impl PartialOrd for DecoratedKey {
666    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
667        Some(self.cmp(other))
668    }
669}
670
671/// Calculate Murmur3 token from partition key bytes
672///
673/// Uses Cassandra's Murmur3Partitioner algorithm:
674/// 1. Compute Cassandra's `MurmurHash.hash3_x64_128`
675/// 2. Take `h1` as the signed token
676/// 3. Apply `normalize`: map `i64::MIN` → `i64::MAX` (Cassandra excludes MIN_VALUE)
677fn calculate_murmur3_token(key_bytes: &[u8]) -> Result<i64> {
678    if key_bytes.is_empty() {
679        return Ok(i64::MIN);
680    }
681
682    Ok(crate::util::cassandra_murmur3::cassandra_murmur3_token(
683        key_bytes,
684    ))
685}
686
687/// Serialize a Value to bytes according to its CQL type
688///
689/// This is used for partition key encoding and follows Cassandra's
690/// type-specific serialization rules.
691fn serialize_value_bytes(value: &Value, comparator: &ComparatorType) -> Result<Vec<u8>> {
692    match (value, comparator) {
693        (Value::Null, _) => Ok(Vec::new()),
694
695        (Value::Boolean(b), ComparatorType::Boolean) => Ok(vec![if *b { 1 } else { 0 }]),
696
697        (Value::TinyInt(n), ComparatorType::TinyInt) => Ok(vec![*n as u8]),
698
699        (Value::SmallInt(n), ComparatorType::SmallInt) => Ok(n.to_be_bytes().to_vec()),
700
701        (Value::Integer(n), ComparatorType::Int) => Ok(n.to_be_bytes().to_vec()),
702
703        (Value::BigInt(n), ComparatorType::BigInt) => Ok(n.to_be_bytes().to_vec()),
704
705        (Value::Counter(n), ComparatorType::Counter) => Ok(n.to_be_bytes().to_vec()),
706
707        (Value::Float32(f), ComparatorType::Float32) => Ok(f.to_bits().to_be_bytes().to_vec()),
708
709        (Value::Float(f), ComparatorType::Float) => Ok(f.to_bits().to_be_bytes().to_vec()),
710
711        (Value::Text(s), ComparatorType::Text) => Ok(s.as_bytes().to_vec()),
712
713        (Value::Blob(bytes), ComparatorType::Blob) => Ok(bytes.clone()),
714
715        (Value::Timestamp(millis), ComparatorType::Timestamp) => Ok(millis.to_be_bytes().to_vec()),
716
717        (Value::Date(days), ComparatorType::Date) => {
718            // Cassandra DATE: stored as unsigned int with Integer.MIN_VALUE offset
719            let stored = days.wrapping_sub(i32::MIN) as u32;
720            Ok(stored.to_be_bytes().to_vec())
721        }
722
723        (Value::Uuid(bytes), ComparatorType::Uuid) => Ok(bytes.to_vec()),
724
725        // Time and Inet are mapped to Custom types in ComparatorType
726        (Value::Time(nanos), ComparatorType::Custom(name)) if name == "time" => {
727            Ok(nanos.to_be_bytes().to_vec())
728        }
729
730        (Value::Inet(bytes), ComparatorType::Custom(name)) if name == "inet" => Ok(bytes.clone()),
731
732        (Value::Varint(bytes), ComparatorType::Varint) => Ok(bytes.clone()),
733
734        (Value::Decimal { scale, unscaled }, ComparatorType::Decimal) => {
735            // Decimal: [scale (4B BE i32)][unscaled bytes]
736            let mut result = Vec::new();
737            result.extend_from_slice(&scale.to_be_bytes());
738            result.extend_from_slice(unscaled);
739            Ok(result)
740        }
741
742        (
743            Value::Duration {
744                months,
745                days,
746                nanos,
747            },
748            ComparatorType::Duration,
749        ) => {
750            // Duration: [months (4B)][days (4B)][nanos (8B)]
751            let mut result = Vec::new();
752            result.extend_from_slice(&months.to_be_bytes());
753            result.extend_from_slice(&days.to_be_bytes());
754            result.extend_from_slice(&nanos.to_be_bytes());
755            Ok(result)
756        }
757
758        _ => Err(Error::InvalidInput(format!(
759            "Type mismatch: value {:?} does not match comparator {:?}",
760            value, comparator
761        ))),
762    }
763}
764
765/// Compare two values for ordering
766fn compare_values(a: &Value, b: &Value) -> Result<Ordering> {
767    use Value::*;
768
769    match (a, b) {
770        (Null, Null) => Ok(Ordering::Equal),
771        (Null, _) => Ok(Ordering::Less),
772        (_, Null) => Ok(Ordering::Greater),
773
774        (Boolean(a), Boolean(b)) => Ok(a.cmp(b)),
775        (TinyInt(a), TinyInt(b)) => Ok(a.cmp(b)),
776        (SmallInt(a), SmallInt(b)) => Ok(a.cmp(b)),
777        (Integer(a), Integer(b)) => Ok(a.cmp(b)),
778        (BigInt(a), BigInt(b)) => Ok(a.cmp(b)),
779        (Counter(a), Counter(b)) => Ok(a.cmp(b)),
780        (Float32(a), Float32(b)) => Ok(a.partial_cmp(b).unwrap_or(Ordering::Equal)),
781        (Float(a), Float(b)) => Ok(a.partial_cmp(b).unwrap_or(Ordering::Equal)),
782        (Text(a), Text(b)) => Ok(a.cmp(b)),
783        (Blob(a), Blob(b)) => Ok(a.cmp(b)),
784        (Timestamp(a), Timestamp(b)) => Ok(a.cmp(b)),
785        (Date(a), Date(b)) => Ok(a.cmp(b)),
786        (Time(a), Time(b)) => Ok(a.cmp(b)),
787        (Uuid(a), Uuid(b)) => Ok(a.cmp(b)),
788        (Inet(a), Inet(b)) => Ok(a.cmp(b)),
789
790        // Collection types (element-wise lexicographic comparison)
791        (List(a), List(b)) | (Set(a), Set(b)) => {
792            for (elem_a, elem_b) in a.iter().zip(b.iter()) {
793                let ord = compare_values(elem_a, elem_b)?;
794                if ord != Ordering::Equal {
795                    return Ok(ord);
796                }
797            }
798            Ok(a.len().cmp(&b.len()))
799        }
800        (Map(a), Map(b)) => {
801            for ((ka, va), (kb, vb)) in a.iter().zip(b.iter()) {
802                let key_ord = compare_values(ka, kb)?;
803                if key_ord != Ordering::Equal {
804                    return Ok(key_ord);
805                }
806                let val_ord = compare_values(va, vb)?;
807                if val_ord != Ordering::Equal {
808                    return Ok(val_ord);
809                }
810            }
811            Ok(a.len().cmp(&b.len()))
812        }
813        (Tuple(a), Tuple(b)) => {
814            for (fa, fb) in a.iter().zip(b.iter()) {
815                let ord = compare_values(fa, fb)?;
816                if ord != Ordering::Equal {
817                    return Ok(ord);
818                }
819            }
820            Ok(a.len().cmp(&b.len()))
821        }
822
823        // Frozen wrapper: compare inner values
824        (Frozen(a), Frozen(b)) => compare_values(a, b),
825
826        _ => Err(Error::InvalidInput(format!(
827            "Cannot compare values of different types: {:?} vs {:?}",
828            a, b
829        ))),
830    }
831}
832
833#[cfg(test)]
834mod tests {
835    use super::*;
836    use crate::schema::{ClusteringColumn, ClusteringOrder, KeyColumn};
837    use std::collections::HashMap;
838
839    fn create_test_schema(
840        partition_cols: Vec<(&str, &str)>,
841        clustering_cols: Vec<(&str, &str, ClusteringOrder)>,
842    ) -> TableSchema {
843        TableSchema {
844            keyspace: "test_ks".to_string(),
845            table: "test_table".to_string(),
846            partition_keys: partition_cols
847                .into_iter()
848                .enumerate()
849                .map(|(i, (name, data_type))| KeyColumn {
850                    name: name.to_string(),
851                    data_type: data_type.to_string(),
852                    position: i,
853                })
854                .collect(),
855            clustering_keys: clustering_cols
856                .into_iter()
857                .enumerate()
858                .map(|(i, (name, data_type, order))| ClusteringColumn {
859                    name: name.to_string(),
860                    data_type: data_type.to_string(),
861                    position: i,
862                    order,
863                })
864                .collect(),
865            columns: vec![],
866            comments: HashMap::new(),
867            dropped_columns: HashMap::new(),
868        }
869    }
870
871    #[test]
872    fn test_table_id() {
873        let table_id = TableId::new("my_keyspace", "my_table");
874        assert_eq!(table_id.keyspace, "my_keyspace");
875        assert_eq!(table_id.table, "my_table");
876        assert_eq!(table_id.qualified_name(), "my_keyspace.my_table");
877        assert_eq!(table_id.to_string(), "my_keyspace.my_table");
878    }
879
880    #[test]
881    fn test_partition_key_single_int() {
882        let schema = create_test_schema(vec![("id", "int")], vec![]);
883        let pk = PartitionKey::single("id", Value::Integer(42));
884
885        let bytes = pk.to_bytes(&schema).unwrap();
886        // Single component: no length prefix, just 4-byte big-endian int
887        assert_eq!(bytes, vec![0x00, 0x00, 0x00, 0x2A]);
888    }
889
890    #[test]
891    fn test_partition_key_multi_component() {
892        let schema = create_test_schema(vec![("id", "int"), ("name", "text")], vec![]);
893        let pk = PartitionKey::new(vec![
894            ("id".to_string(), Value::Integer(42)),
895            ("name".to_string(), Value::Text("hello".to_string())),
896        ]);
897
898        let bytes = pk.to_bytes(&schema).unwrap();
899        // Multi-component partition key format:
900        // [len1(2B)][val1][0x00][len2(2B)][val2][0x00]
901        let expected = vec![
902            0x00, 0x04, // len1 = 4
903            0x00, 0x00, 0x00, 0x2A, // int = 42
904            0x00, // end-of-component after component 1
905            0x00, 0x05, // len2 = 5
906            b'h', b'e', b'l', b'l', b'o', // text = "hello"
907            0x00, // end-of-component after component 2
908        ];
909        assert_eq!(bytes, expected);
910    }
911
912    #[test]
913    fn test_partition_key_three_components() {
914        // Issue #438: Verify 3-component composite keys (e.g., tick_data table)
915        let schema = create_test_schema(
916            vec![("symbol", "text"), ("exchange", "text"), ("bucket", "int")],
917            vec![],
918        );
919        let pk = PartitionKey::new(vec![
920            ("symbol".to_string(), Value::Text("AAPL".to_string())),
921            ("exchange".to_string(), Value::Text("NYSE".to_string())),
922            ("bucket".to_string(), Value::Integer(100)),
923        ]);
924
925        let bytes = pk.to_bytes(&schema).unwrap();
926        // Composite partition key format: end-of-component byte after every component
927        let expected = vec![
928            0x00, 0x04, // len1 = 4
929            b'A', b'A', b'P', b'L', // "AAPL"
930            0x00, // end-of-component
931            0x00, 0x04, // len2 = 4
932            b'N', b'Y', b'S', b'E', // "NYSE"
933            0x00, // end-of-component
934            0x00, 0x04, // len3 = 4
935            0x00, 0x00, 0x00, 0x64, // int = 100
936            0x00, // end-of-component
937        ];
938        assert_eq!(bytes, expected);
939    }
940
941    #[test]
942    fn test_decorated_key_ordering() {
943        let dk1 = DecoratedKey::new(100, vec![1, 2, 3]);
944        let dk2 = DecoratedKey::new(200, vec![1, 2, 3]);
945        let dk3 = DecoratedKey::new(100, vec![1, 2, 4]);
946
947        // Order by token first
948        assert!(dk1 < dk2);
949        assert!(dk2 > dk1);
950
951        // Equal tokens: order by key bytes
952        assert!(dk1 < dk3);
953        assert!(dk3 > dk1);
954
955        // Equal tokens and keys
956        let dk4 = DecoratedKey::new(100, vec![1, 2, 3]);
957        assert_eq!(dk1, dk4);
958    }
959
960    #[test]
961    fn test_murmur3_token_empty_key() {
962        let token = calculate_murmur3_token(&[]).unwrap();
963        assert_eq!(token, i64::MIN);
964    }
965
966    #[test]
967    fn test_murmur3_token_deterministic() {
968        let key_bytes = b"test_key";
969        let token1 = calculate_murmur3_token(key_bytes).unwrap();
970        let token2 = calculate_murmur3_token(key_bytes).unwrap();
971        assert_eq!(token1, token2, "Token calculation should be deterministic");
972    }
973
974    #[test]
975    fn test_murmur3_token_different_keys() {
976        let token1 = calculate_murmur3_token(b"key1").unwrap();
977        let token2 = calculate_murmur3_token(b"key2").unwrap();
978        assert_ne!(
979            token1, token2,
980            "Different keys should produce different tokens"
981        );
982    }
983
984    #[test]
985    fn test_murmur3_token_matches_cassandra_for_composite_uuid_key() {
986        // Verified against Cassandra 5.0:
987        // SELECT token(tenant_id, user_id) FROM issue438_probe.multi_pk_raw;
988        let key_bytes = vec![
989            0x00, 0x10, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00,
990            0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x10, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x40,
991            0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00,
992        ];
993
994        let token = calculate_murmur3_token(&key_bytes).unwrap();
995        assert_eq!(token, -5_116_541_970_184_546_410);
996    }
997
998    #[test]
999    fn test_decorated_key_from_bytes() {
1000        let key_bytes = vec![0x00, 0x00, 0x00, 0x2A]; // int = 42
1001        let dk = DecoratedKey::from_key_bytes(key_bytes.clone()).unwrap();
1002
1003        assert_eq!(dk.key, key_bytes);
1004        // Token should be calculated consistently
1005        let expected_token = calculate_murmur3_token(&key_bytes).unwrap();
1006        assert_eq!(dk.token, expected_token);
1007    }
1008
1009    #[test]
1010    fn test_clustering_key_ordering() {
1011        let schema = create_test_schema(
1012            vec![("id", "int")],
1013            vec![("ts", "timestamp", ClusteringOrder::Asc)],
1014        );
1015
1016        let ck1 = ClusteringKey::single("ts", Value::Timestamp(1000));
1017        let ck2 = ClusteringKey::single("ts", Value::Timestamp(2000));
1018
1019        let ordering = ck1.compare(&ck2, &schema).unwrap();
1020        assert_eq!(ordering, Ordering::Less);
1021    }
1022
1023    #[test]
1024    fn test_clustering_key_desc_ordering() {
1025        let schema = create_test_schema(
1026            vec![("id", "int")],
1027            vec![("ts", "timestamp", ClusteringOrder::Desc)],
1028        );
1029
1030        let ck1 = ClusteringKey::single("ts", Value::Timestamp(1000));
1031        let ck2 = ClusteringKey::single("ts", Value::Timestamp(2000));
1032
1033        let ordering = ck1.compare(&ck2, &schema).unwrap();
1034        // DESC ordering reverses the comparison
1035        assert_eq!(ordering, Ordering::Greater);
1036    }
1037
1038    // --- Issue #849: clustering reversal must not flip null/empty ordering ---
1039
1040    #[test]
1041    fn test_clustering_null_sorts_first_asc() {
1042        // An absent (NULL) trailing component must sort before any valued
1043        // component on an ASC clustering column.
1044        let schema = create_test_schema(
1045            vec![("id", "int")],
1046            vec![("ts", "timestamp", ClusteringOrder::Asc)],
1047        );
1048
1049        let null_ck = ClusteringKey::single("ts", Value::Null);
1050        let valued_ck = ClusteringKey::single("ts", Value::Timestamp(1000));
1051
1052        assert_eq!(
1053            null_ck.compare(&valued_ck, &schema).unwrap(),
1054            Ordering::Less,
1055            "NULL must sort before a value on ASC"
1056        );
1057        assert_eq!(
1058            valued_ck.compare(&null_ck, &schema).unwrap(),
1059            Ordering::Greater,
1060            "value must sort after NULL on ASC"
1061        );
1062    }
1063
1064    #[test]
1065    fn test_clustering_null_sorts_first_desc() {
1066        // The blanket `ordering.reverse()` previously flipped this so that NULL
1067        // sorted *after* a value on DESC columns. Cassandra sorts NULL first
1068        // regardless of ASC/DESC (ref 587612cd).
1069        let schema = create_test_schema(
1070            vec![("id", "int")],
1071            vec![("ts", "timestamp", ClusteringOrder::Desc)],
1072        );
1073
1074        let null_ck = ClusteringKey::single("ts", Value::Null);
1075        let valued_ck = ClusteringKey::single("ts", Value::Timestamp(1000));
1076
1077        assert_eq!(
1078            null_ck.compare(&valued_ck, &schema).unwrap(),
1079            Ordering::Less,
1080            "NULL must sort before a value even on DESC"
1081        );
1082        assert_eq!(
1083            valued_ck.compare(&null_ck, &schema).unwrap(),
1084            Ordering::Greater,
1085            "value must sort after NULL even on DESC"
1086        );
1087
1088        // Two NULLs compare equal regardless of order.
1089        let null_ck2 = ClusteringKey::single("ts", Value::Null);
1090        assert_eq!(
1091            null_ck.compare(&null_ck2, &schema).unwrap(),
1092            Ordering::Equal
1093        );
1094    }
1095
1096    #[test]
1097    fn test_clustering_empty_vs_valued_asc() {
1098        // An empty (zero-length) text component routes through the type:
1099        // empty < non-empty on ASC.
1100        let schema = create_test_schema(
1101            vec![("id", "int")],
1102            vec![("c", "text", ClusteringOrder::Asc)],
1103        );
1104
1105        let empty_ck = ClusteringKey::single("c", Value::Text(String::new()));
1106        let valued_ck = ClusteringKey::single("c", Value::Text("a".to_string()));
1107
1108        assert_eq!(
1109            empty_ck.compare(&valued_ck, &schema).unwrap(),
1110            Ordering::Less,
1111            "empty text must sort before non-empty on ASC"
1112        );
1113    }
1114
1115    #[test]
1116    fn test_clustering_empty_vs_valued_desc() {
1117        // On a DESC column the type-routed comparison is reversed for two
1118        // present values: empty > non-empty.
1119        let schema = create_test_schema(
1120            vec![("id", "int")],
1121            vec![("c", "text", ClusteringOrder::Desc)],
1122        );
1123
1124        let empty_ck = ClusteringKey::single("c", Value::Text(String::new()));
1125        let valued_ck = ClusteringKey::single("c", Value::Text("a".to_string()));
1126
1127        assert_eq!(
1128            empty_ck.compare(&valued_ck, &schema).unwrap(),
1129            Ordering::Greater,
1130            "empty text must sort after non-empty on DESC (type-routed reversal)"
1131        );
1132        assert_eq!(
1133            valued_ck.compare(&empty_ck, &schema).unwrap(),
1134            Ordering::Less
1135        );
1136    }
1137
1138    #[test]
1139    fn test_clustering_null_first_on_second_desc_component() {
1140        // Multi-component: first component equal, NULL on the second (DESC)
1141        // component must still sort first.
1142        let schema = create_test_schema(
1143            vec![("id", "int")],
1144            vec![
1145                ("a", "int", ClusteringOrder::Asc),
1146                ("b", "timestamp", ClusteringOrder::Desc),
1147            ],
1148        );
1149
1150        let null_b = ClusteringKey::new(vec![
1151            ("a".to_string(), Value::Integer(1)),
1152            ("b".to_string(), Value::Null),
1153        ]);
1154        let valued_b = ClusteringKey::new(vec![
1155            ("a".to_string(), Value::Integer(1)),
1156            ("b".to_string(), Value::Timestamp(5000)),
1157        ]);
1158
1159        assert_eq!(
1160            null_b.compare(&valued_b, &schema).unwrap(),
1161            Ordering::Less,
1162            "NULL on a DESC sub-component still sorts first"
1163        );
1164    }
1165
1166    #[test]
1167    fn test_clustering_absent_trailing_desc_component_sorts_first() {
1168        // Review (#849): an *absent* trailing component (shorter key) must be
1169        // treated identically to an explicit NULL. (a=1) vs (a=1, b=5000) where
1170        // b is DESC: the short key (absent b) must sort FIRST regardless of the
1171        // DESC order on b. Previously the zip stopped at the shorter key and
1172        // returned Equal.
1173        let schema = create_test_schema(
1174            vec![("id", "int")],
1175            vec![
1176                ("a", "int", ClusteringOrder::Asc),
1177                ("b", "timestamp", ClusteringOrder::Desc),
1178            ],
1179        );
1180
1181        // Short key omits the trailing DESC component `b` entirely.
1182        let absent_b = ClusteringKey::new(vec![("a".to_string(), Value::Integer(1))]);
1183        let valued_b = ClusteringKey::new(vec![
1184            ("a".to_string(), Value::Integer(1)),
1185            ("b".to_string(), Value::Timestamp(5000)),
1186        ]);
1187
1188        assert_eq!(
1189            absent_b.compare(&valued_b, &schema).unwrap(),
1190            Ordering::Less,
1191            "absent trailing DESC component must sort first (null-first), not Equal"
1192        );
1193        assert_eq!(
1194            valued_b.compare(&absent_b, &schema).unwrap(),
1195            Ordering::Greater,
1196            "present value must sort after an absent trailing component"
1197        );
1198
1199        // An absent trailing component must compare equal to an explicit NULL
1200        // for the same position.
1201        let explicit_null_b = ClusteringKey::new(vec![
1202            ("a".to_string(), Value::Integer(1)),
1203            ("b".to_string(), Value::Null),
1204        ]);
1205        assert_eq!(
1206            absent_b.compare(&explicit_null_b, &schema).unwrap(),
1207            Ordering::Equal,
1208            "absent trailing component == explicit NULL at the same position"
1209        );
1210        assert_eq!(
1211            explicit_null_b.compare(&absent_b, &schema).unwrap(),
1212            Ordering::Equal,
1213        );
1214    }
1215
1216    #[test]
1217    fn test_clustering_absent_trailing_asc_component_sorts_first() {
1218        // Same null-first rule on an ASC trailing component: the shorter key
1219        // (absent component) sorts before any present value.
1220        let schema = create_test_schema(
1221            vec![("id", "int")],
1222            vec![
1223                ("a", "int", ClusteringOrder::Asc),
1224                ("b", "int", ClusteringOrder::Asc),
1225            ],
1226        );
1227
1228        let absent_b = ClusteringKey::new(vec![("a".to_string(), Value::Integer(7))]);
1229        let valued_b = ClusteringKey::new(vec![
1230            ("a".to_string(), Value::Integer(7)),
1231            ("b".to_string(), Value::Integer(0)),
1232        ]);
1233
1234        assert_eq!(
1235            absent_b.compare(&valued_b, &schema).unwrap(),
1236            Ordering::Less,
1237            "absent trailing ASC component must sort first"
1238        );
1239        assert_eq!(
1240            valued_b.compare(&absent_b, &schema).unwrap(),
1241            Ordering::Greater,
1242        );
1243    }
1244
1245    #[test]
1246    fn test_clustering_both_absent_trailing_component_equal() {
1247        // Two keys that both omit the trailing component compare Equal.
1248        let schema = create_test_schema(
1249            vec![("id", "int")],
1250            vec![
1251                ("a", "int", ClusteringOrder::Asc),
1252                ("b", "timestamp", ClusteringOrder::Desc),
1253            ],
1254        );
1255
1256        let a1 = ClusteringKey::new(vec![("a".to_string(), Value::Integer(3))]);
1257        let a2 = ClusteringKey::new(vec![("a".to_string(), Value::Integer(3))]);
1258        assert_eq!(a1.compare(&a2, &schema).unwrap(), Ordering::Equal);
1259    }
1260
1261    #[test]
1262    fn test_mutation_creation() {
1263        let table_id = TableId::new("ks", "table");
1264        let pk = PartitionKey::single("id", Value::Integer(1));
1265        let ops = vec![CellOperation::Write {
1266            column: "name".to_string(),
1267            value: Value::Text("Alice".to_string()),
1268        }];
1269
1270        let mutation = Mutation::new(table_id.clone(), pk, None, ops, 1234567890, None);
1271
1272        assert_eq!(mutation.table.keyspace, "ks");
1273        assert_eq!(mutation.table.table, "table");
1274        assert_eq!(mutation.timestamp_micros, 1234567890);
1275        assert_eq!(mutation.ttl_seconds, None);
1276        assert_eq!(mutation.operations.len(), 1);
1277    }
1278
1279    #[test]
1280    fn test_cell_operation_write() {
1281        let op = CellOperation::Write {
1282            column: "age".to_string(),
1283            value: Value::Integer(30),
1284        };
1285
1286        match op {
1287            CellOperation::Write { column, value } => {
1288                assert_eq!(column, "age");
1289                assert_eq!(value, Value::Integer(30));
1290            }
1291            _ => panic!("Expected Write operation"),
1292        }
1293    }
1294
1295    #[test]
1296    fn test_cell_operation_delete() {
1297        let op = CellOperation::Delete {
1298            column: "name".to_string(),
1299            local_deletion_time: None,
1300        };
1301
1302        match op {
1303            CellOperation::Delete { column, .. } => {
1304                assert_eq!(column, "name");
1305            }
1306            _ => panic!("Expected Delete operation"),
1307        }
1308    }
1309
1310    #[test]
1311    fn test_cell_operation_delete_row() {
1312        let op = CellOperation::DeleteRow;
1313        assert!(matches!(op, CellOperation::DeleteRow));
1314    }
1315
1316    #[test]
1317    fn test_serialize_value_types() {
1318        // Boolean
1319        let bytes = serialize_value_bytes(&Value::Boolean(true), &ComparatorType::Boolean).unwrap();
1320        assert_eq!(bytes, vec![1]);
1321
1322        // Integer
1323        let bytes = serialize_value_bytes(&Value::Integer(42), &ComparatorType::Int).unwrap();
1324        assert_eq!(bytes, vec![0x00, 0x00, 0x00, 0x2A]);
1325
1326        // Text
1327        let bytes = serialize_value_bytes(&Value::Text("hello".to_string()), &ComparatorType::Text)
1328            .unwrap();
1329        assert_eq!(bytes, b"hello");
1330
1331        // UUID
1332        let uuid_bytes = [
1333            0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB,
1334            0xCD, 0xEF,
1335        ];
1336        let bytes = serialize_value_bytes(&Value::Uuid(uuid_bytes), &ComparatorType::Uuid).unwrap();
1337        assert_eq!(bytes, uuid_bytes);
1338    }
1339
1340    #[test]
1341    fn test_compare_values() {
1342        assert_eq!(
1343            compare_values(&Value::Integer(1), &Value::Integer(2)).unwrap(),
1344            Ordering::Less
1345        );
1346        assert_eq!(
1347            compare_values(&Value::Integer(2), &Value::Integer(1)).unwrap(),
1348            Ordering::Greater
1349        );
1350        assert_eq!(
1351            compare_values(&Value::Integer(1), &Value::Integer(1)).unwrap(),
1352            Ordering::Equal
1353        );
1354
1355        // Null comparison
1356        assert_eq!(
1357            compare_values(&Value::Null, &Value::Integer(1)).unwrap(),
1358            Ordering::Less
1359        );
1360        assert_eq!(
1361            compare_values(&Value::Integer(1), &Value::Null).unwrap(),
1362            Ordering::Greater
1363        );
1364    }
1365
1366    #[test]
1367    fn test_partition_key_to_decorated_key() {
1368        let schema = create_test_schema(vec![("id", "int")], vec![]);
1369        let pk = PartitionKey::single("id", Value::Integer(42));
1370
1371        let dk = pk.to_decorated_key(&schema).unwrap();
1372        assert_eq!(dk.key, vec![0x00, 0x00, 0x00, 0x2A]);
1373
1374        // Token should match direct calculation
1375        let expected_token = calculate_murmur3_token(&dk.key).unwrap();
1376        assert_eq!(dk.token, expected_token);
1377    }
1378
1379    #[test]
1380    fn test_murmur3_token_cassandra_compatibility() {
1381        // Test known token values from Cassandra to validate our implementation
1382        // These values were generated by Cassandra 5.0 Murmur3Partitioner
1383
1384        // Test case 1: int value 1
1385        let key1 = vec![0x00, 0x00, 0x00, 0x01];
1386        let token1 = calculate_murmur3_token(&key1).unwrap();
1387        // Cassandra produces deterministic tokens for the same input
1388        // The exact value depends on Murmur3 algorithm implementation
1389        assert_ne!(token1, 0, "Token should not be zero for non-zero input");
1390
1391        // Test case 2: int value 100
1392        let key2 = vec![0x00, 0x00, 0x00, 0x64];
1393        let token2 = calculate_murmur3_token(&key2).unwrap();
1394        assert_ne!(
1395            token2, token1,
1396            "Different keys should produce different tokens"
1397        );
1398
1399        // Test case 3: text value "test"
1400        let key3 = b"test";
1401        let token3 = calculate_murmur3_token(key3).unwrap();
1402        assert_ne!(token3, token1);
1403        assert_ne!(token3, token2);
1404
1405        // Test consistency: same key should always produce same token
1406        let token1_repeat = calculate_murmur3_token(&key1).unwrap();
1407        assert_eq!(token1, token1_repeat, "Tokens must be deterministic");
1408    }
1409
1410    #[test]
1411    fn test_decorated_key_btree_ordering() {
1412        // Verify that DecoratedKey ordering is correct for use in BTreeMap
1413        use std::collections::BTreeMap;
1414
1415        let mut map = BTreeMap::new();
1416
1417        // Insert keys in non-sorted order
1418        let dk3 = DecoratedKey::new(300, vec![3]);
1419        let dk1 = DecoratedKey::new(100, vec![1]);
1420        let dk2 = DecoratedKey::new(200, vec![2]);
1421
1422        map.insert(dk3.clone(), "value3");
1423        map.insert(dk1.clone(), "value1");
1424        map.insert(dk2.clone(), "value2");
1425
1426        // Verify BTreeMap orders by token
1427        let keys: Vec<_> = map.keys().collect();
1428        assert_eq!(keys[0].token, 100);
1429        assert_eq!(keys[1].token, 200);
1430        assert_eq!(keys[2].token, 300);
1431    }
1432
1433    #[test]
1434    fn test_decorated_key_hash_collision_handling() {
1435        // Test Issue #406: Explicit hash collision scenario
1436        // When two different keys produce the same token (extremely rare but possible),
1437        // they should be ordered by raw key bytes to ensure deterministic ordering.
1438
1439        let token = 12345_i64; // Shared token value (simulated collision)
1440
1441        let dk1 = DecoratedKey::new(token, vec![0x00, 0x01, 0x02]); // Key A
1442        let dk2 = DecoratedKey::new(token, vec![0x00, 0x01, 0x03]); // Key B (differs in last byte)
1443        let dk3 = DecoratedKey::new(token, vec![0x00, 0x01, 0x02]); // Key C (identical to A)
1444
1445        // Equal tokens: order by key bytes
1446        assert!(dk1 < dk2, "Keys with same token should order by bytes");
1447        assert!(dk2 > dk1, "Key comparison should be consistent");
1448        assert_eq!(
1449            dk1.cmp(&dk3),
1450            Ordering::Equal,
1451            "Identical keys should be equal"
1452        );
1453
1454        // Verify ordering is stable in BTreeMap
1455        use std::collections::BTreeMap;
1456        let mut map = BTreeMap::new();
1457
1458        map.insert(dk2.clone(), "value2");
1459        map.insert(dk1.clone(), "value1");
1460        map.insert(dk3.clone(), "value3"); // Overwrites dk1 (same key)
1461
1462        // Should have 2 entries (dk1/dk3 are same key)
1463        assert_eq!(map.len(), 2);
1464
1465        // Verify ordering by raw bytes
1466        let keys: Vec<_> = map.keys().collect();
1467        assert_eq!(keys[0].key, vec![0x00, 0x01, 0x02]); // dk1/dk3
1468        assert_eq!(keys[1].key, vec![0x00, 0x01, 0x03]); // dk2
1469    }
1470
1471    #[test]
1472    fn test_clustering_key_ord_valid_comparison() {
1473        // Test Issue #409: Valid comparisons work correctly
1474        let ck1 = ClusteringKey::single("ts", Value::Timestamp(1000));
1475        let ck2 = ClusteringKey::single("ts", Value::Timestamp(2000));
1476        let ck3 = ClusteringKey::single("ts", Value::Timestamp(1000));
1477
1478        // Basic ordering
1479        assert_eq!(ck1.cmp(&ck2), Ordering::Less);
1480        assert_eq!(ck2.cmp(&ck1), Ordering::Greater);
1481        assert_eq!(ck1.cmp(&ck3), Ordering::Equal);
1482
1483        // Multi-column clustering key
1484        let ck_multi1 = ClusteringKey::new(vec![
1485            ("year".to_string(), Value::Integer(2024)),
1486            ("month".to_string(), Value::SmallInt(1)),
1487        ]);
1488        let ck_multi2 = ClusteringKey::new(vec![
1489            ("year".to_string(), Value::Integer(2024)),
1490            ("month".to_string(), Value::SmallInt(2)),
1491        ]);
1492
1493        assert_eq!(ck_multi1.cmp(&ck_multi2), Ordering::Less);
1494    }
1495
1496    #[test]
1497    fn test_clustering_key_ord_type_mismatch_is_total_and_does_not_panic() {
1498        // Issue #458/#465: `Ord for ClusteringKey` MUST NOT panic on a type mismatch.
1499        // Heterogeneous SSTables can produce mismatched clustering value types, and a
1500        // panic in `cmp` would crash the memtable BTreeMap. Instead, the implementation
1501        // falls back to a deterministic ordering. This test confirms it is panic-free,
1502        // total (antisymmetric), and deterministic.
1503        let ck1 = ClusteringKey::single("ts", Value::Timestamp(1000));
1504        let ck2 = ClusteringKey::single("ts", Value::Integer(2000)); // Different type
1505
1506        // Must not panic.
1507        let ord_12 = ck1.cmp(&ck2);
1508        let ord_21 = ck2.cmp(&ck1);
1509
1510        // Determinism: repeated comparisons return the same result.
1511        assert_eq!(ord_12, ck1.cmp(&ck2), "comparison must be deterministic");
1512
1513        // Antisymmetry / totality: a<b implies b>a (and the mismatch is never reported
1514        // as Equal, since the underlying values differ).
1515        assert_ne!(
1516            ord_12,
1517            Ordering::Equal,
1518            "mismatched types must not compare Equal"
1519        );
1520        assert_eq!(
1521            ord_12.reverse(),
1522            ord_21,
1523            "ordering must be antisymmetric (a.cmp(b) == b.cmp(a).reverse())"
1524        );
1525
1526        // Reflexivity: a key compares Equal to itself even across the fallback path.
1527        assert_eq!(ck1.cmp(&ck1), Ordering::Equal);
1528
1529        // It must remain usable as a BTreeMap key without panicking.
1530        use std::collections::BTreeMap;
1531        let mut map = BTreeMap::new();
1532        map.insert(ck1.clone(), "a");
1533        map.insert(ck2.clone(), "b");
1534        assert_eq!(map.len(), 2, "both distinct keys should be retained");
1535    }
1536
1537    #[test]
1538    fn test_clustering_key_ord_btree_ordering() {
1539        // Test Issue #409: Verify ClusteringKey works correctly in BTreeMap
1540        use std::collections::BTreeMap;
1541
1542        let mut map = BTreeMap::new();
1543
1544        let ck3 = ClusteringKey::single("ts", Value::Timestamp(3000));
1545        let ck1 = ClusteringKey::single("ts", Value::Timestamp(1000));
1546        let ck2 = ClusteringKey::single("ts", Value::Timestamp(2000));
1547
1548        // Insert in non-sorted order
1549        map.insert(ck3.clone(), "value3");
1550        map.insert(ck1.clone(), "value1");
1551        map.insert(ck2.clone(), "value2");
1552
1553        // Verify BTreeMap orders correctly
1554        let values: Vec<_> = map.values().copied().collect();
1555        assert_eq!(values, vec!["value1", "value2", "value3"]);
1556    }
1557
1558    #[test]
1559    fn test_compare_frozen_list_values() {
1560        // Issue #437: Frozen collection clustering keys must be comparable
1561        let list_a = Value::Frozen(Box::new(Value::List(vec![
1562            Value::Text("a".to_string()),
1563            Value::Text("b".to_string()),
1564        ])));
1565        let list_b = Value::Frozen(Box::new(Value::List(vec![
1566            Value::Text("a".to_string()),
1567            Value::Text("c".to_string()),
1568        ])));
1569        let list_c = Value::Frozen(Box::new(Value::List(vec![
1570            Value::Text("a".to_string()),
1571            Value::Text("b".to_string()),
1572            Value::Text("c".to_string()),
1573        ])));
1574
1575        // Same elements: equal
1576        assert_eq!(compare_values(&list_a, &list_a).unwrap(), Ordering::Equal);
1577        // Different second element: a < c
1578        assert_eq!(compare_values(&list_a, &list_b).unwrap(), Ordering::Less);
1579        assert_eq!(compare_values(&list_b, &list_a).unwrap(), Ordering::Greater);
1580        // Prefix match, shorter < longer
1581        assert_eq!(compare_values(&list_a, &list_c).unwrap(), Ordering::Less);
1582        assert_eq!(compare_values(&list_c, &list_a).unwrap(), Ordering::Greater);
1583    }
1584
1585    #[test]
1586    fn test_frozen_list_clustering_key_btree_ordering() {
1587        // Issue #437: Frozen list clustering keys must sort correctly in BTreeMap
1588        use std::collections::BTreeMap;
1589
1590        let mut map = BTreeMap::new();
1591
1592        // Create clustering keys with frozen lists of varying sizes (mimics test data generator)
1593        let ck_2elem = ClusteringKey::single(
1594            "tags",
1595            Value::Frozen(Box::new(Value::List(vec![
1596                Value::Text("ck_0_0".to_string()),
1597                Value::Text("ck_0_1".to_string()),
1598            ]))),
1599        );
1600        let ck_3elem = ClusteringKey::single(
1601            "tags",
1602            Value::Frozen(Box::new(Value::List(vec![
1603                Value::Text("ck_1_0".to_string()),
1604                Value::Text("ck_1_1".to_string()),
1605                Value::Text("ck_1_2".to_string()),
1606            ]))),
1607        );
1608        let ck_4elem = ClusteringKey::single(
1609            "tags",
1610            Value::Frozen(Box::new(Value::List(vec![
1611                Value::Text("ck_2_0".to_string()),
1612                Value::Text("ck_2_1".to_string()),
1613                Value::Text("ck_2_2".to_string()),
1614                Value::Text("ck_2_3".to_string()),
1615            ]))),
1616        );
1617
1618        // Insert in non-sorted order
1619        map.insert(ck_4elem.clone(), "4elem");
1620        map.insert(ck_2elem.clone(), "2elem");
1621        map.insert(ck_3elem.clone(), "3elem");
1622
1623        // All three should be distinct keys (no deduplication)
1624        assert_eq!(map.len(), 3, "All frozen list CKs should be distinct");
1625
1626        // Verify ordering: ck_0_* < ck_1_* < ck_2_* (lexicographic by first element)
1627        let values: Vec<_> = map.values().copied().collect();
1628        assert_eq!(values, vec!["2elem", "3elem", "4elem"]);
1629    }
1630
1631    #[test]
1632    fn test_partition_key_from_bytes_single_int() {
1633        let schema = TableSchema {
1634            keyspace: "ks".to_string(),
1635            table: "tbl".to_string(),
1636            partition_keys: vec![KeyColumn {
1637                name: "id".to_string(),
1638                data_type: "int".to_string(),
1639                position: 0,
1640            }],
1641            clustering_keys: vec![],
1642            columns: vec![],
1643            comments: HashMap::new(),
1644            dropped_columns: HashMap::new(),
1645        };
1646
1647        let original = PartitionKey::single("id", Value::Integer(42));
1648        let bytes = original.to_bytes(&schema).unwrap();
1649        let decoded = PartitionKey::from_bytes(&bytes, &schema).unwrap();
1650        assert_eq!(original, decoded);
1651    }
1652
1653    #[test]
1654    fn test_partition_key_from_bytes_single_uuid() {
1655        let schema = TableSchema {
1656            keyspace: "ks".to_string(),
1657            table: "tbl".to_string(),
1658            partition_keys: vec![KeyColumn {
1659                name: "id".to_string(),
1660                data_type: "uuid".to_string(),
1661                position: 0,
1662            }],
1663            clustering_keys: vec![],
1664            columns: vec![],
1665            comments: HashMap::new(),
1666            dropped_columns: HashMap::new(),
1667        };
1668
1669        let uuid_bytes = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
1670        let original = PartitionKey::single("id", Value::Uuid(uuid_bytes));
1671        let bytes = original.to_bytes(&schema).unwrap();
1672        let decoded = PartitionKey::from_bytes(&bytes, &schema).unwrap();
1673        assert_eq!(original, decoded);
1674    }
1675
1676    #[test]
1677    fn test_partition_key_from_bytes_single_text() {
1678        let schema = TableSchema {
1679            keyspace: "ks".to_string(),
1680            table: "tbl".to_string(),
1681            partition_keys: vec![KeyColumn {
1682                name: "name".to_string(),
1683                data_type: "text".to_string(),
1684                position: 0,
1685            }],
1686            clustering_keys: vec![],
1687            columns: vec![],
1688            comments: HashMap::new(),
1689            dropped_columns: HashMap::new(),
1690        };
1691
1692        let original = PartitionKey::single("name", Value::Text("hello".to_string()));
1693        let bytes = original.to_bytes(&schema).unwrap();
1694        let decoded = PartitionKey::from_bytes(&bytes, &schema).unwrap();
1695        assert_eq!(original, decoded);
1696    }
1697
1698    #[test]
1699    fn test_partition_key_from_bytes_multi_component() {
1700        let schema = TableSchema {
1701            keyspace: "ks".to_string(),
1702            table: "tbl".to_string(),
1703            partition_keys: vec![
1704                KeyColumn {
1705                    name: "tenant".to_string(),
1706                    data_type: "text".to_string(),
1707                    position: 0,
1708                },
1709                KeyColumn {
1710                    name: "id".to_string(),
1711                    data_type: "int".to_string(),
1712                    position: 1,
1713                },
1714            ],
1715            clustering_keys: vec![],
1716            columns: vec![],
1717            comments: HashMap::new(),
1718            dropped_columns: HashMap::new(),
1719        };
1720
1721        let original = PartitionKey::new(vec![
1722            ("tenant".to_string(), Value::Text("acme".to_string())),
1723            ("id".to_string(), Value::Integer(99)),
1724        ]);
1725        let bytes = original.to_bytes(&schema).unwrap();
1726        let decoded = PartitionKey::from_bytes(&bytes, &schema).unwrap();
1727        assert_eq!(original, decoded);
1728    }
1729
1730    #[test]
1731    fn test_partition_key_from_bytes_empty_errors() {
1732        let schema = TableSchema {
1733            keyspace: "ks".to_string(),
1734            table: "tbl".to_string(),
1735            partition_keys: vec![KeyColumn {
1736                name: "id".to_string(),
1737                data_type: "int".to_string(),
1738                position: 0,
1739            }],
1740            clustering_keys: vec![],
1741            columns: vec![],
1742            comments: HashMap::new(),
1743            dropped_columns: HashMap::new(),
1744        };
1745
1746        assert!(PartitionKey::from_bytes(&[], &schema).is_err());
1747    }
1748
1749    #[test]
1750    fn test_clustering_key_cmp_type_mismatch_does_not_panic() {
1751        // Two ClusteringKeys whose sole column has mismatched value types.
1752        // This can happen with heterogeneous SSTables (e.g. Frozen(List) vs List).
1753        // The Ord impl must not panic; it must produce a deterministic result.
1754        let key_a = ClusteringKey {
1755            columns: vec![("col".to_string(), Value::Integer(1))],
1756        };
1757        let key_b = ClusteringKey {
1758            columns: vec![("col".to_string(), Value::Text("1".to_string()))],
1759        };
1760
1761        // Must not panic.
1762        let first = key_a.cmp(&key_b);
1763        // Must be deterministic: same result on repeated calls.
1764        let second = key_a.cmp(&key_b);
1765        assert_eq!(first, second);
1766
1767        // Reflexive: a key compared against itself is Equal.
1768        assert_eq!(key_a.cmp(&key_a), Ordering::Equal);
1769    }
1770}