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