Skip to main content

radixdb_core/value/
mod.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Value type for RadixDB - runtime values with type information
16//!
17//! This module provides a unified Value enum that represents SQL values
18//! with full type information and conversion capabilities.
19
20use std::cmp::Ordering;
21use std::fmt;
22use std::hash::{Hash, Hasher};
23use std::sync::Arc;
24
25use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
26use uuid::Uuid;
27
28use super::error::{Error, Result};
29use super::types::{DataType, ExternalTypeRef, LogicalTypeRef};
30use crate::{CompactArc, SmartString};
31
32const EXTERNAL_VALUE_MARKER: u8 = 0xff;
33const EXTERNAL_VALUE_HEADER_BYTES: usize = 1 + 16 + 4;
34pub const MAX_EXTERNAL_VALUE_BYTES: usize = 16 * 1024 * 1024;
35
36/// Borrowed view of the canonical payload carried by an external value.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct ExternalValueRef<'a> {
39    type_ref: ExternalTypeRef,
40    payload: &'a [u8],
41}
42
43impl<'a> ExternalValueRef<'a> {
44    pub const fn type_ref(self) -> ExternalTypeRef {
45        self.type_ref
46    }
47
48    pub const fn payload(self) -> &'a [u8] {
49        self.payload
50    }
51}
52
53/// Timestamp formats supported for parsing
54/// Order matters - more specific formats first
55const TIMESTAMP_FORMATS: &[&str] = &[
56    "%Y-%m-%dT%H:%M:%S%.f%:z", // RFC3339 with fractional seconds
57    "%Y-%m-%dT%H:%M:%S%:z",    // RFC3339
58    "%Y-%m-%dT%H:%M:%S%.fZ",   // RFC3339 UTC with fractional seconds
59    "%Y-%m-%dT%H:%M:%SZ",      // RFC3339 UTC
60    "%Y-%m-%dT%H:%M:%S%.f",    // ISO with fractional seconds, no timezone
61    "%Y-%m-%dT%H:%M:%S",       // ISO without timezone
62    "%Y-%m-%d %H:%M:%S%.f",    // SQL-style with fractional seconds
63    "%Y-%m-%d %H:%M:%S",       // SQL-style
64    "%Y-%m-%d",                // Date only
65    "%Y/%m/%d %H:%M:%S",       // Alternative with slashes
66    "%Y/%m/%d",                // Alternative date only
67    "%m/%d/%Y",                // US format
68    "%d/%m/%Y",                // European format
69];
70
71const TIME_FORMATS: &[&str] = &[
72    "%H:%M:%S%.f", // High precision
73    "%H:%M:%S",    // Standard
74    "%H:%M",       // Hours and minutes only
75];
76
77/// A runtime value with type information
78///
79/// Each variant carries its data directly, avoiding the need for interface
80/// indirection or separate value references.
81///
82/// ## Memory Layout (16 bytes)
83///
84/// Value is exactly 16 bytes due to niche optimization:
85/// - Text(SmartString): 16 bytes with niches in tag byte (values 17-255 unused)
86/// - Extension(CompactArc<[u8]>): 8 bytes (thin pointer), leaving niche bytes free
87/// - Rust stores Value's discriminant in SmartString's niche values
88///
89/// ## Extension Variant
90///
91/// The Extension variant is a catch-all for all complex types (JSON, Vector, Blob, etc.)
92/// It stores a single `CompactArc<[u8]>` (8 bytes) where `byte[0]` is the `DataType` tag
93/// and byte[1..] is the payload. This keeps Value at exactly 7 variants forever —
94/// new types are added by extending DataType (a 1-byte `#[repr(u8)]` enum).
95///
96/// Note: Text uses SmartString for inline storage of strings up to 15 bytes.
97/// Longer strings use `Arc<str>` for O(1) clone and sharing.
98#[derive(Debug, Clone)]
99pub enum Value {
100    /// NULL value with optional type hint
101    Null(DataType),
102
103    /// 64-bit signed integer
104    Integer(i64),
105
106    /// 64-bit floating point
107    Float(f64),
108
109    /// UTF-8 text string (SmartString: inline ≤15 bytes, Arc for larger)
110    Text(SmartString),
111
112    /// Boolean value
113    Boolean(bool),
114
115    /// Timestamp (UTC)
116    Timestamp(DateTime<Utc>),
117
118    /// Extension type: `byte[0]` = `DataType` tag, `byte[1..]` = payload
119    /// - Json: `byte[0]=6`, `byte[1..]`=UTF-8 bytes (access via `as_json()`)
120    /// - Vector: `byte[0]=7`, `byte[1..]`=packed LE f32 bytes (access via `as_vector_f32()`)
121    /// - Future types (Blob, Array, etc.) add DataType variants, not Value variants
122    Extension(CompactArc<[u8]>),
123}
124
125/// Static NULL value for zero-cost reuse
126pub const NULL_VALUE: Value = Value::Null(DataType::Null);
127
128impl Value {
129    // =========================================================================
130    // Constructors
131    // =========================================================================
132
133    /// Create a NULL value with a type hint
134    #[inline]
135    pub fn null(data_type: DataType) -> Self {
136        Value::Null(data_type)
137    }
138
139    /// Create a NULL value with unknown type
140    #[inline(always)]
141    pub fn null_unknown() -> Self {
142        Value::Null(DataType::Null)
143    }
144
145    /// Create an integer value
146    pub fn integer(value: i64) -> Self {
147        Value::Integer(value)
148    }
149
150    /// Create a float value
151    pub fn float(value: f64) -> Self {
152        Value::Float(value)
153    }
154
155    /// Create a text value
156    ///
157    /// Uses SmartString::from_string_shared() for heap strings to enable
158    /// O(1) clone via `Arc<str>`. This allows string sharing between
159    /// Arena, Index, and VersionStore.
160    pub fn text(value: impl Into<String>) -> Self {
161        Value::Text(SmartString::from_string_shared(value.into()))
162    }
163
164    /// Create a text value from `Arc<str>` (zero-copy for heap strings)
165    ///
166    /// Preserves the Arc reference for O(1) clone and sharing.
167    pub fn text_arc(value: Arc<str>) -> Self {
168        Value::Text(SmartString::from(value))
169    }
170
171    /// Create a boolean value
172    pub fn boolean(value: bool) -> Self {
173        Value::Boolean(value)
174    }
175
176    /// Create a timestamp value
177    pub fn timestamp(value: DateTime<Utc>) -> Self {
178        Value::Timestamp(value)
179    }
180
181    /// Create a validated JSON value.
182    pub fn try_json(value: impl Into<String>) -> Result<Self> {
183        let value = value.into();
184        serde_json::from_str::<serde_json::Value>(&value)
185            .map_err(|error| Error::invalid_argument(format!("invalid JSON value: {error}")))?;
186        Ok(Self::json_unchecked(value))
187    }
188
189    /// Build JSON from a document already validated or generated by the engine.
190    fn json_unchecked(value: impl Into<String>) -> Self {
191        let s_bytes = value.into().into_bytes();
192        let mut bytes = Vec::with_capacity(1 + s_bytes.len());
193        bytes.push(DataType::Json as u8);
194        bytes.extend_from_slice(&s_bytes);
195        Value::Extension(CompactArc::from(bytes))
196    }
197
198    #[inline]
199    #[doc(hidden)]
200    pub fn json(value: impl Into<String>) -> Self {
201        Self::json_unchecked(value)
202    }
203
204    /// Create a vector value from f32 data (stored as packed LE f32 bytes in Extension)
205    pub fn vector(data: Vec<f32>) -> Self {
206        let mut bytes = Vec::with_capacity(1 + data.len() * 4);
207        bytes.push(DataType::Vector as u8);
208        for f in &data {
209            bytes.extend_from_slice(&f.to_le_bytes());
210        }
211        Value::Extension(CompactArc::from(bytes))
212    }
213
214    /// Create a vector value from pre-packed little-endian `f32` bytes.
215    pub fn try_vector_from_bytes(raw_f32_bytes: CompactArc<[u8]>) -> Result<Self> {
216        if !raw_f32_bytes
217            .len()
218            .is_multiple_of(std::mem::size_of::<f32>())
219        {
220            return Err(Error::invalid_argument(format!(
221                "VECTOR payload has {} bytes, expected a multiple of 4",
222                raw_f32_bytes.len()
223            )));
224        }
225        Ok(Self::vector_from_bytes_unchecked(raw_f32_bytes))
226    }
227
228    fn vector_from_bytes_unchecked(raw_f32_bytes: CompactArc<[u8]>) -> Self {
229        let mut bytes = Vec::with_capacity(1 + raw_f32_bytes.len());
230        bytes.push(DataType::Vector as u8);
231        bytes.extend_from_slice(&raw_f32_bytes);
232        Value::Extension(CompactArc::from(bytes))
233    }
234
235    /// Create a UUID value from raw 16-byte UUID storage.
236    pub fn uuid(bytes: [u8; 16]) -> Self {
237        let mut data = Vec::with_capacity(17);
238        data.push(DataType::Uuid as u8);
239        data.extend_from_slice(&bytes);
240        Value::Extension(CompactArc::from(data))
241    }
242
243    /// Create a new UUIDv7 value.
244    ///
245    /// UUIDv7 is time-ordered, which makes it much friendlier for primary-key
246    /// B-tree indexes than fully random UUIDv4 values.
247    pub fn uuid_v7() -> Self {
248        Value::uuid(*Uuid::now_v7().as_bytes())
249    }
250
251    /// Create an exact decimal value.
252    ///
253    /// Payload layout:
254    /// - byte 0: [`DataType::Decimal`] tag;
255    /// - bytes 1..17: little-endian `i128` unscaled integer;
256    /// - byte 17: precision;
257    /// - byte 18: scale.
258    pub fn try_decimal(unscaled: i128, precision: u8, scale: u8) -> Result<Self> {
259        validate_decimal_shape(unscaled, precision, scale)?;
260        Ok(Self::decimal_unchecked(unscaled, precision, scale))
261    }
262
263    fn decimal_unchecked(unscaled: i128, precision: u8, scale: u8) -> Self {
264        let mut data = Vec::with_capacity(19);
265        data.push(DataType::Decimal as u8);
266        data.extend_from_slice(&unscaled.to_le_bytes());
267        data.push(precision);
268        data.push(scale);
269        Value::Extension(CompactArc::from(data))
270    }
271
272    #[inline]
273    #[doc(hidden)]
274    pub fn decimal(unscaled: i128, precision: u8, scale: u8) -> Self {
275        Self::decimal_unchecked(unscaled, precision, scale)
276    }
277
278    /// Create a calendar date value from days since Unix epoch.
279    pub fn date(days_since_unix_epoch: i32) -> Self {
280        let mut data = Vec::with_capacity(5);
281        data.push(DataType::Date as u8);
282        data.extend_from_slice(&days_since_unix_epoch.to_le_bytes());
283        Value::Extension(CompactArc::from(data))
284    }
285
286    /// Create a raw byte-string value.
287    pub fn bytes(bytes: Vec<u8>) -> Self {
288        let mut data = Vec::with_capacity(1 + bytes.len());
289        data.push(DataType::Bytes as u8);
290        data.extend_from_slice(&bytes);
291        Value::Extension(CompactArc::from(data))
292    }
293
294    /// Create a typed external value from canonical plugin codec bytes.
295    ///
296    /// Semantic codec validation is performed by the admitted plugin host at
297    /// SQL/wire ingress. This constructor enforces the context-free envelope
298    /// bounds shared by WAL and immutable artifact readers.
299    pub fn try_external(type_ref: ExternalTypeRef, payload: impl AsRef<[u8]>) -> Result<Self> {
300        let payload = payload.as_ref();
301        if payload.len() > MAX_EXTERNAL_VALUE_BYTES {
302            return Err(Error::invalid_argument(format!(
303                "external value payload has {} bytes, limit is {}",
304                payload.len(),
305                MAX_EXTERNAL_VALUE_BYTES
306            )));
307        }
308        let mut data = Vec::with_capacity(EXTERNAL_VALUE_HEADER_BYTES + payload.len());
309        data.push(EXTERNAL_VALUE_MARKER);
310        data.extend_from_slice(&type_ref.type_object_id());
311        data.extend_from_slice(&type_ref.codec_version().to_le_bytes());
312        data.extend_from_slice(payload);
313        Ok(Value::Extension(CompactArc::from(data)))
314    }
315
316    // =========================================================================
317    // Type accessors
318    // =========================================================================
319
320    /// Returns the data type of this value
321    pub fn data_type(&self) -> DataType {
322        match self {
323            Value::Null(dt) => *dt,
324            Value::Integer(_) => DataType::Integer,
325            Value::Float(_) => DataType::Float,
326            Value::Text(_) => DataType::Text,
327            Value::Boolean(_) => DataType::Boolean,
328            Value::Timestamp(_) => DataType::Timestamp,
329            Value::Extension(data) => data
330                .first()
331                .and_then(|&b| DataType::from_u8(b))
332                .unwrap_or(DataType::Null),
333        }
334    }
335
336    /// Return the complete built-in or external logical type identity.
337    pub fn logical_type(&self) -> LogicalTypeRef {
338        self.as_external()
339            .map(|value| LogicalTypeRef::External(value.type_ref()))
340            .unwrap_or_else(|| LogicalTypeRef::Builtin(self.data_type()))
341    }
342
343    /// Return whether this value carries the reserved external-type envelope.
344    ///
345    /// This intentionally checks only the marker. Full envelope validation is
346    /// owned by [`Value::as_external`] and the row/WAL/file admission paths.
347    #[inline]
348    pub fn is_external(&self) -> bool {
349        matches!(self, Value::Extension(data) if data.first() == Some(&EXTERNAL_VALUE_MARKER))
350    }
351
352    /// Borrow the external envelope without exposing its compact storage.
353    pub fn as_external(&self) -> Option<ExternalValueRef<'_>> {
354        let Value::Extension(data) = self else {
355            return None;
356        };
357        if data.first() != Some(&EXTERNAL_VALUE_MARKER) || data.len() < EXTERNAL_VALUE_HEADER_BYTES
358        {
359            return None;
360        }
361        let type_object_id = data[1..17].try_into().ok()?;
362        let codec_version = u32::from_le_bytes(data[17..21].try_into().ok()?);
363        let type_ref = ExternalTypeRef::new(type_object_id, codec_version).ok()?;
364        Some(ExternalValueRef {
365            type_ref,
366            payload: &data[EXTERNAL_VALUE_HEADER_BYTES..],
367        })
368    }
369
370    /// Validate the physical shape of a value before it crosses a row, WAL or
371    /// file-format admission boundary.
372    pub fn validate_shape(&self) -> Result<()> {
373        let Value::Extension(data) = self else {
374            return Ok(());
375        };
376        if data.first() == Some(&EXTERNAL_VALUE_MARKER) {
377            let external = self.as_external().ok_or_else(|| {
378                Error::invalid_argument("external value has an invalid typed envelope")
379            })?;
380            if external.payload().len() > MAX_EXTERNAL_VALUE_BYTES {
381                return Err(Error::invalid_argument(
382                    "external value payload exceeds 16 MiB",
383                ));
384            }
385            return Ok(());
386        }
387        let Some(tag) = data.first().and_then(|tag| DataType::from_u8(*tag)) else {
388            return Err(Error::invalid_argument(
389                "extension value has an unknown or missing tag",
390            ));
391        };
392        Self::validate_extension_payload(tag, &data[1..])
393    }
394
395    /// Validate an untagged extension payload read from a typed physical column.
396    #[doc(hidden)]
397    pub fn validate_extension_payload(tag: DataType, payload: &[u8]) -> Result<()> {
398        match tag {
399            DataType::Json => {
400                let json = std::str::from_utf8(payload).map_err(|error| {
401                    Error::invalid_argument(format!("invalid JSON UTF-8: {error}"))
402                })?;
403                serde_json::from_str::<serde_json::Value>(json).map_err(|error| {
404                    Error::invalid_argument(format!("invalid JSON document: {error}"))
405                })?;
406            }
407            DataType::Vector => {
408                if !payload.len().is_multiple_of(std::mem::size_of::<f32>()) {
409                    return Err(Error::invalid_argument(format!(
410                        "VECTOR payload has {} bytes, expected a multiple of 4",
411                        payload.len()
412                    )));
413                }
414            }
415            DataType::Uuid => {
416                if payload.len() != 16 {
417                    return Err(Error::invalid_argument(
418                        "UUID payload must contain exactly 16 bytes",
419                    ));
420                }
421            }
422            DataType::Decimal => {
423                if payload.len() != 18 {
424                    return Err(Error::invalid_argument(
425                        "DECIMAL payload must contain exactly 18 bytes",
426                    ));
427                }
428                let unscaled = i128::from_le_bytes(
429                    payload[..16]
430                        .try_into()
431                        .map_err(|_| Error::invalid_argument("invalid DECIMAL coefficient"))?,
432                );
433                validate_decimal_shape(unscaled, payload[16], payload[17])?;
434            }
435            DataType::Date => {
436                if payload.len() != 4 {
437                    return Err(Error::invalid_argument(
438                        "DATE payload must contain exactly 4 bytes",
439                    ));
440                }
441            }
442            DataType::Bytes => {}
443            _ => {
444                return Err(Error::invalid_argument(format!(
445                    "data type {tag} is not a valid extension payload tag"
446                )));
447            }
448        }
449        Ok(())
450    }
451
452    /// Returns true if this value is NULL
453    #[inline(always)]
454    pub fn is_null(&self) -> bool {
455        matches!(self, Value::Null(_))
456    }
457
458    // =========================================================================
459    // Value extractors
460    // =========================================================================
461
462    /// Extract as i64, with type coercion
463    ///
464    /// Returns None if:
465    /// - Value is NULL
466    /// - Conversion is not possible
467    pub fn as_int64(&self) -> Option<i64> {
468        match self {
469            Value::Null(_) => None,
470            Value::Integer(v) => Some(*v),
471            Value::Float(v) => checked_float_to_i64(*v),
472            Value::Text(s) => parse_text_to_i64(s),
473            Value::Boolean(b) => Some(if *b { 1 } else { 0 }),
474            Value::Timestamp(t) => t.timestamp_nanos_opt(),
475            Value::Extension(_) => None,
476        }
477    }
478
479    /// Return the exact canonical INTEGER identity of this numeric value.
480    ///
481    /// Unlike [`Value::as_int64`], this never truncates fractional values and
482    /// never accepts a saturating float-to-integer conversion. It follows the
483    /// same cross-domain equality contract as [`Value::compare`], so an
484    /// exactly integral FLOAT or DECIMAL can safely probe an INTEGER index.
485    #[doc(hidden)]
486    pub fn exact_integer_identity(&self) -> Option<i64> {
487        match self {
488            Value::Integer(integer) => Some(*integer),
489            Value::Float(float) if float.is_finite() && float.fract() == 0.0 => {
490                let integer = *float as i64;
491                (Value::Integer(integer) == *self).then_some(integer)
492            }
493            Value::Extension(data) if data.first() == Some(&(DataType::Decimal as u8)) => {
494                let (unscaled, _, scale) = self.as_decimal_parts()?;
495                DecimalIdentity::from_parts(unscaled, scale).exact_i64()
496            }
497            _ => None,
498        }
499    }
500
501    /// Extract as f64, with type coercion
502    pub fn as_float64(&self) -> Option<f64> {
503        match self {
504            Value::Null(_) => None,
505            Value::Integer(v) => Some(*v as f64),
506            Value::Float(v) => Some(*v),
507            Value::Text(s) => s.parse::<f64>().ok(),
508            Value::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
509            Value::Timestamp(_) | Value::Extension(_) => None,
510        }
511    }
512
513    /// Extract as boolean, with type coercion
514    pub fn as_boolean(&self) -> Option<bool> {
515        match self {
516            Value::Null(_) => None,
517            Value::Integer(v) => Some(*v != 0),
518            Value::Float(v) => Some(*v != 0.0),
519            Value::Text(s) => {
520                // OPTIMIZATION: Use eq_ignore_ascii_case to avoid allocation
521                let s_ref: &str = s.as_ref();
522                if s_ref.eq_ignore_ascii_case("true")
523                    || s_ref.eq_ignore_ascii_case("t")
524                    || s_ref.eq_ignore_ascii_case("yes")
525                    || s_ref.eq_ignore_ascii_case("y")
526                    || s_ref == "1"
527                {
528                    Some(true)
529                } else if s_ref.eq_ignore_ascii_case("false")
530                    || s_ref.eq_ignore_ascii_case("f")
531                    || s_ref.eq_ignore_ascii_case("no")
532                    || s_ref.eq_ignore_ascii_case("n")
533                    || s_ref == "0"
534                    || s_ref.is_empty()
535                {
536                    Some(false)
537                } else {
538                    s_ref.parse::<f64>().ok().map(|f| f != 0.0)
539                }
540            }
541            Value::Boolean(b) => Some(*b),
542            Value::Timestamp(_) | Value::Extension(_) => None,
543        }
544    }
545
546    /// Extract as String, with type coercion
547    pub fn as_string(&self) -> Option<String> {
548        match self {
549            Value::Null(_) => None,
550            Value::Integer(v) => Some(v.to_string()),
551            Value::Float(v) => Some(format_float(*v)),
552            Value::Text(s) => Some(s.to_string()),
553            Value::Boolean(b) => Some(if *b { "true" } else { "false" }.to_string()),
554            Value::Timestamp(t) => Some(t.to_rfc3339()),
555            Value::Extension(data) if data.first() == Some(&(DataType::Json as u8)) => {
556                // SAFETY: Json data is always stored as valid UTF-8
557                Some(std::str::from_utf8(&data[1..]).unwrap_or("").to_string())
558            }
559            Value::Extension(data) if data.first() == Some(&(DataType::Vector as u8)) => {
560                Some(format_vector_bytes(&data[1..]))
561            }
562            Value::Extension(data) if data.first() == Some(&(DataType::Uuid as u8)) => {
563                format_uuid_bytes(&data[1..])
564            }
565            Value::Extension(data) if data.first() == Some(&(DataType::Decimal as u8)) => self
566                .as_decimal_parts()
567                .map(|(unscaled, _, scale)| format_decimal_parts(unscaled, scale)),
568            Value::Extension(data) if data.first() == Some(&(DataType::Date as u8)) => self
569                .as_date_days()
570                .and_then(format_date_days_since_unix_epoch),
571            Value::Extension(data) if data.first() == Some(&(DataType::Bytes as u8)) => {
572                Some(format_bytes_hex(&data[1..]))
573            }
574            Value::Extension(data) => {
575                // Generic fallback: try payload as UTF-8
576                if data.len() > 1 {
577                    std::str::from_utf8(&data[1..]).ok().map(|s| s.to_string())
578                } else {
579                    None
580                }
581            }
582        }
583    }
584
585    /// Extract as string reference (avoids clone for Text/Json)
586    pub fn as_str(&self) -> Option<&str> {
587        match self {
588            Value::Text(s) => Some(s.as_str()),
589            Value::Extension(data) if data.first() == Some(&(DataType::Json as u8)) => {
590                let json = std::str::from_utf8(&data[1..]).ok()?;
591                serde_json::from_str::<serde_json::Value>(json).ok()?;
592                Some(json)
593            }
594            _ => None,
595        }
596    }
597
598    /// Extract as `DateTime<Utc>`
599    pub fn as_timestamp(&self) -> Option<DateTime<Utc>> {
600        match self {
601            Value::Null(_) => None,
602            Value::Timestamp(t) => Some(*t),
603            Value::Text(s) => parse_timestamp(s).ok(),
604            Value::Integer(nanos) => {
605                // Interpret as nanoseconds since Unix epoch
606                datetime_from_epoch_nanos(*nanos)
607            }
608            _ => None,
609        }
610    }
611
612    /// Return the exact artifact-backed timestamp representation when it is available.
613    ///
614    /// artifact-backed stores timestamps as signed nanoseconds since the Unix epoch. Chrono
615    /// supports a wider public range, so callers at a persistence boundary must
616    /// reject `None` instead of narrowing or wrapping the value.
617    #[inline]
618    #[doc(hidden)]
619    pub fn artifact_timestamp_nanos(&self) -> Option<i64> {
620        match self {
621            Value::Timestamp(timestamp) => timestamp.timestamp_nanos_opt(),
622            _ => None,
623        }
624    }
625
626    /// Extract as JSON string
627    pub fn as_json(&self) -> Option<&str> {
628        match self {
629            Value::Null(_) => None,
630            // SAFETY: Json data is always stored as valid UTF-8 (tag at [0], payload at [1..])
631            Value::Extension(data) if data.first() == Some(&(DataType::Json as u8)) => {
632                let json = std::str::from_utf8(&data[1..]).ok()?;
633                serde_json::from_str::<serde_json::Value>(json).ok()?;
634                Some(json)
635            }
636            _ => None,
637        }
638    }
639
640    /// Extract vector as `Vec<f32>` (reads packed LE f32 bytes from Extension payload)
641    pub fn as_vector_f32(&self) -> Option<Vec<f32>> {
642        match self {
643            Value::Extension(data) if data.first() == Some(&(DataType::Vector as u8)) => {
644                let payload = &data[1..];
645                if payload.len() % std::mem::size_of::<f32>() != 0 {
646                    return None;
647                }
648                let len = payload.len() / 4;
649                let mut result = Vec::with_capacity(len);
650                for i in 0..len {
651                    let bytes = [
652                        payload[i * 4],
653                        payload[i * 4 + 1],
654                        payload[i * 4 + 2],
655                        payload[i * 4 + 3],
656                    ];
657                    result.push(f32::from_le_bytes(bytes));
658                }
659                Some(result)
660            }
661            _ => None,
662        }
663    }
664
665    /// Extract UUID as raw 16 bytes.
666    pub fn as_uuid_bytes(&self) -> Option<[u8; 16]> {
667        match self {
668            Value::Extension(data)
669                if data.first() == Some(&(DataType::Uuid as u8)) && data.len() == 17 =>
670            {
671                data[1..].try_into().ok()
672            }
673            _ => None,
674        }
675    }
676
677    /// Extract exact decimal parts: `(unscaled, precision, scale)`.
678    pub fn as_decimal_parts(&self) -> Option<(i128, u8, u8)> {
679        match self {
680            Value::Extension(data)
681                if data.first() == Some(&(DataType::Decimal as u8)) && data.len() == 19 =>
682            {
683                let unscaled = i128::from_le_bytes(data[1..17].try_into().ok()?);
684                validate_decimal_shape(unscaled, data[17], data[18]).ok()?;
685                Some((unscaled, data[17], data[18]))
686            }
687            _ => None,
688        }
689    }
690
691    /// Extract calendar date as days since Unix epoch.
692    pub fn as_date_days(&self) -> Option<i32> {
693        match self {
694            Value::Extension(data)
695                if data.first() == Some(&(DataType::Date as u8)) && data.len() == 5 =>
696            {
697                Some(i32::from_le_bytes(data[1..5].try_into().ok()?))
698            }
699            _ => None,
700        }
701    }
702
703    /// Extract raw bytes from a BYTES/BLOB/BINARY value.
704    pub fn as_bytes_value(&self) -> Option<&[u8]> {
705        match self {
706            Value::Extension(data) if data.first() == Some(&(DataType::Bytes as u8)) => {
707                Some(&data[1..])
708            }
709            _ => None,
710        }
711    }
712
713    // =========================================================================
714    // Comparison
715    // =========================================================================
716
717    /// Compare two values for ordering
718    ///
719    /// Returns:
720    /// - Ok(Ordering::Less) if self < other
721    /// - Ok(Ordering::Equal) if self == other
722    /// - Ok(Ordering::Greater) if self > other
723    /// - Err if comparison is not possible
724    pub fn compare(&self, other: &Value) -> Result<Ordering> {
725        // Handle NULL comparisons
726        if self.is_null() || other.is_null() {
727            if self.is_null() && other.is_null() {
728                return Ok(Ordering::Equal);
729            }
730            return Err(Error::NullComparison);
731        }
732
733        // Integer, Float and Decimal share one canonical numeric identity.
734        // Decimal keeps its exact physical payload; only comparison/key
735        // semantics ignore redundant precision and trailing scale zeroes.
736        if let Some(ordering) = compare_canonical_numeric(self, other) {
737            return Ok(ordering);
738        }
739
740        // Same type comparison (most efficient path)
741        if self.data_type() == other.data_type() {
742            return self.compare_same_type(other);
743        }
744
745        // Cross-domain coercion belongs to SQL binding, not Value identity.
746        Err(Error::IncomparableTypes)
747    }
748
749    /// Compare values of the same type
750    fn compare_same_type(&self, other: &Value) -> Result<Ordering> {
751        match (self, other) {
752            (Value::Integer(a), Value::Integer(b)) => Ok(a.cmp(b)),
753            (Value::Float(a), Value::Float(b)) => Ok(compare_floats(*a, *b)),
754            (Value::Text(a), Value::Text(b)) => Ok(a.cmp(b)),
755            (Value::Boolean(a), Value::Boolean(b)) => Ok(a.cmp(b)),
756            (Value::Timestamp(a), Value::Timestamp(b)) => Ok(a.cmp(b)),
757            (Value::Extension(a), Value::Extension(b)) => {
758                // External equality and ordering are explicit plugin
759                // capabilities. Core structural bytes are never a semantic
760                // comparison fallback.
761                if a.first() == Some(&EXTERNAL_VALUE_MARKER)
762                    || b.first() == Some(&EXTERNAL_VALUE_MARKER)
763                {
764                    return Err(Error::IncomparableTypes);
765                }
766                // Extension: tag byte is [0], so same-tag comparison is data equality
767                if a.first() != b.first() {
768                    return Err(Error::IncomparableTypes);
769                }
770                if a.first() == Some(&(DataType::Uuid as u8)) {
771                    return if a.len() == 17 && b.len() == 17 {
772                        Ok(a[1..].cmp(&b[1..]))
773                    } else {
774                        Err(Error::IncomparableTypes)
775                    };
776                }
777                if a.first() == Some(&(DataType::Decimal as u8)) {
778                    return match (self.as_decimal_parts(), other.as_decimal_parts()) {
779                        (
780                            Some((left_unscaled, _, left_scale)),
781                            Some((right_unscaled, _, right_scale)),
782                        ) => Ok(compare_decimal_parts(
783                            left_unscaled,
784                            left_scale,
785                            right_unscaled,
786                            right_scale,
787                        )),
788                        _ => Err(Error::IncomparableTypes),
789                    };
790                }
791                if a.first() == Some(&(DataType::Date as u8)) {
792                    return match (self.as_date_days(), other.as_date_days()) {
793                        (Some(left), Some(right)) => Ok(left.cmp(&right)),
794                        _ => Err(Error::IncomparableTypes),
795                    };
796                }
797                if a.first() == Some(&(DataType::Bytes as u8)) {
798                    return Ok(a[1..].cmp(&b[1..]));
799                }
800                // All extension types: equality only (not orderable)
801                if a == b {
802                    Ok(Ordering::Equal)
803                } else {
804                    Err(Error::IncomparableTypes)
805                }
806            }
807            _ => Err(Error::IncomparableTypes),
808        }
809    }
810
811    // =========================================================================
812    // Construction from typed values
813    // =========================================================================
814
815    /// Create a Value from a typed value with explicit data type
816    pub fn from_typed(value: Option<&dyn std::any::Any>, data_type: DataType) -> Result<Self> {
817        let has_value = value.is_some();
818        let result = match value {
819            None => Value::Null(data_type),
820            Some(v) => {
821                // Try to downcast based on expected type
822                match data_type {
823                    DataType::Integer => {
824                        if let Some(&i) = v.downcast_ref::<i64>() {
825                            Value::Integer(i)
826                        } else if let Some(&i) = v.downcast_ref::<i32>() {
827                            Value::Integer(i as i64)
828                        } else if let Some(s) = v.downcast_ref::<String>() {
829                            s.parse::<i64>()
830                                .map(Value::Integer)
831                                .unwrap_or(Value::Null(data_type))
832                        } else {
833                            Value::Null(data_type)
834                        }
835                    }
836                    DataType::Float => {
837                        if let Some(&f) = v.downcast_ref::<f64>() {
838                            Value::Float(f)
839                        } else if let Some(&i) = v.downcast_ref::<i64>() {
840                            Value::Float(i as f64)
841                        } else if let Some(s) = v.downcast_ref::<String>() {
842                            s.parse::<f64>()
843                                .map(Value::Float)
844                                .unwrap_or(Value::Null(data_type))
845                        } else {
846                            Value::Null(data_type)
847                        }
848                    }
849                    DataType::Text => {
850                        if let Some(s) = v.downcast_ref::<String>() {
851                            Value::Text(SmartString::new(s))
852                        } else if let Some(&s) = v.downcast_ref::<&str>() {
853                            Value::Text(SmartString::from(s))
854                        } else {
855                            Value::Null(data_type)
856                        }
857                    }
858                    DataType::Boolean => {
859                        if let Some(&b) = v.downcast_ref::<bool>() {
860                            Value::Boolean(b)
861                        } else if let Some(&i) = v.downcast_ref::<i64>() {
862                            Value::Boolean(i != 0)
863                        } else {
864                            Value::Null(data_type)
865                        }
866                    }
867                    DataType::Timestamp => {
868                        if let Some(&t) = v.downcast_ref::<DateTime<Utc>>() {
869                            Value::Timestamp(t)
870                        } else if let Some(s) = v.downcast_ref::<String>() {
871                            parse_timestamp(s)
872                                .map(Value::Timestamp)
873                                .unwrap_or(Value::Null(data_type))
874                        } else {
875                            Value::Null(data_type)
876                        }
877                    }
878                    DataType::Json => {
879                        if let Some(s) = v.downcast_ref::<String>() {
880                            // Validate JSON
881                            if serde_json::from_str::<serde_json::Value>(s).is_ok() {
882                                Value::json_unchecked(s)
883                            } else {
884                                Value::Null(data_type)
885                            }
886                        } else {
887                            Value::Null(data_type)
888                        }
889                    }
890                    DataType::Uuid => {
891                        if let Some(&bytes) = v.downcast_ref::<[u8; 16]>() {
892                            Value::uuid(bytes)
893                        } else if let Some(s) = v.downcast_ref::<String>() {
894                            parse_uuid_str(s)
895                                .map(Value::uuid)
896                                .unwrap_or(Value::Null(data_type))
897                        } else if let Some(&s) = v.downcast_ref::<&str>() {
898                            parse_uuid_str(s)
899                                .map(Value::uuid)
900                                .unwrap_or(Value::Null(data_type))
901                        } else {
902                            Value::Null(data_type)
903                        }
904                    }
905                    DataType::Vector => {
906                        if let Some(vec) = v.downcast_ref::<Vec<f32>>() {
907                            Value::vector(vec.clone())
908                        } else {
909                            Value::Null(data_type)
910                        }
911                    }
912                    DataType::Decimal => {
913                        if let Some(&(unscaled, precision, scale)) =
914                            v.downcast_ref::<(i128, u8, u8)>()
915                        {
916                            Value::decimal_unchecked(unscaled, precision, scale)
917                        } else if let Some(&i) = v.downcast_ref::<i64>() {
918                            Value::decimal_unchecked(
919                                i as i128,
920                                decimal_precision_for_unscaled(i),
921                                0,
922                            )
923                        } else if let Some(s) = v.downcast_ref::<String>() {
924                            parse_decimal_str(s)
925                                .map(|(unscaled, precision, scale)| {
926                                    Value::decimal_unchecked(unscaled, precision, scale)
927                                })
928                                .unwrap_or(Value::Null(data_type))
929                        } else if let Some(&s) = v.downcast_ref::<&str>() {
930                            parse_decimal_str(s)
931                                .map(|(unscaled, precision, scale)| {
932                                    Value::decimal_unchecked(unscaled, precision, scale)
933                                })
934                                .unwrap_or(Value::Null(data_type))
935                        } else {
936                            Value::Null(data_type)
937                        }
938                    }
939                    DataType::Date => {
940                        if let Some(&days) = v.downcast_ref::<i32>() {
941                            Value::date(days)
942                        } else if let Some(s) = v.downcast_ref::<String>() {
943                            parse_date_days_since_unix_epoch(s)
944                                .map(Value::date)
945                                .unwrap_or(Value::Null(data_type))
946                        } else if let Some(&s) = v.downcast_ref::<&str>() {
947                            parse_date_days_since_unix_epoch(s)
948                                .map(Value::date)
949                                .unwrap_or(Value::Null(data_type))
950                        } else {
951                            Value::Null(data_type)
952                        }
953                    }
954                    DataType::Bytes => {
955                        if let Some(bytes) = v.downcast_ref::<Vec<u8>>() {
956                            Value::bytes(bytes.clone())
957                        } else if let Some(s) = v.downcast_ref::<String>() {
958                            Value::bytes(s.as_bytes().to_vec())
959                        } else if let Some(&s) = v.downcast_ref::<&str>() {
960                            Value::bytes(s.as_bytes().to_vec())
961                        } else {
962                            Value::Null(data_type)
963                        }
964                    }
965                    DataType::Null => Value::Null(DataType::Null),
966                }
967            }
968        };
969        result.validate_shape()?;
970        if has_value && result.is_null() && data_type != DataType::Null {
971            return Err(Error::type_conversion("typed value", data_type.to_string()));
972        }
973        Ok(result)
974    }
975
976    // =========================================================================
977    // Type coercion
978    // =========================================================================
979
980    /// Coerce this value to the target data type
981    ///
982    /// Type coercion rules:
983    /// - Integer column receiving Float → converts to Integer
984    /// - Float column receiving Integer → converts to Float
985    /// - Text column receiving any type → converts to Text
986    /// - Timestamp column receiving String → parses timestamp
987    /// - JSON column receiving valid JSON string → stores as JSON
988    /// - Boolean column receiving Integer/String → converts to Boolean
989    ///
990    /// Returns the coerced value, or NULL if coercion fails.
991    pub fn coerce_to_type(&self, target_type: DataType) -> Value {
992        // NULL stays NULL (with target type hint)
993        if self.is_null() {
994            return Value::Null(target_type);
995        }
996
997        if self.validate_shape().is_err() {
998            return Value::Null(target_type);
999        }
1000
1001        // Same type - no conversion needed
1002        if self.data_type() == target_type {
1003            return self.clone();
1004        }
1005
1006        match target_type {
1007            DataType::Integer => {
1008                // Convert to INTEGER
1009                match self {
1010                    Value::Integer(v) => Value::Integer(*v),
1011                    Value::Float(v) => checked_float_to_i64(*v)
1012                        .map(Value::Integer)
1013                        .unwrap_or(Value::Null(target_type)),
1014                    Value::Text(s) => parse_text_to_i64(s)
1015                        .map(Value::Integer)
1016                        .unwrap_or(Value::Null(target_type)),
1017                    Value::Boolean(b) => Value::Integer(if *b { 1 } else { 0 }),
1018                    Value::Extension(data) if data.first() == Some(&(DataType::Decimal as u8)) => {
1019                        self.as_decimal_parts()
1020                            .and_then(|(unscaled, _, scale)| {
1021                                decimal_scale_factor(scale)
1022                                    .and_then(|factor| i64::try_from(unscaled / factor).ok())
1023                            })
1024                            .map(Value::Integer)
1025                            .unwrap_or(Value::Null(target_type))
1026                    }
1027                    _ => Value::Null(target_type),
1028                }
1029            }
1030            DataType::Float => {
1031                // Convert to FLOAT
1032                match self {
1033                    Value::Float(v) => Value::Float(*v),
1034                    Value::Integer(v) => Value::Float(*v as f64),
1035                    Value::Text(s) => s
1036                        .parse::<f64>()
1037                        .map(Value::Float)
1038                        .unwrap_or(Value::Null(target_type)),
1039                    Value::Boolean(b) => Value::Float(if *b { 1.0 } else { 0.0 }),
1040                    Value::Extension(data) if data.first() == Some(&(DataType::Decimal as u8)) => {
1041                        self.as_string()
1042                            .and_then(|value| value.parse::<f64>().ok())
1043                            .map(Value::Float)
1044                            .unwrap_or(Value::Null(target_type))
1045                    }
1046                    _ => Value::Null(target_type),
1047                }
1048            }
1049            DataType::Text => {
1050                // Convert to TEXT - everything can become text
1051                match self {
1052                    Value::Text(s) => Value::Text(s.clone()),
1053                    Value::Integer(v) => Value::Text(SmartString::from_string(v.to_string())),
1054                    Value::Float(v) => Value::Text(SmartString::from_string(format_float(*v))),
1055                    Value::Boolean(b) => {
1056                        Value::Text(SmartString::new(if *b { "true" } else { "false" }))
1057                    }
1058                    Value::Timestamp(t) => Value::Text(SmartString::from_string(t.to_rfc3339())),
1059                    Value::Extension(data) if data.first() == Some(&(DataType::Json as u8)) => {
1060                        Value::Text(SmartString::new(
1061                            std::str::from_utf8(&data[1..]).unwrap_or(""),
1062                        ))
1063                    }
1064                    Value::Extension(data) if data.first() == Some(&(DataType::Vector as u8)) => {
1065                        Value::Text(SmartString::from_string(format_vector_bytes(&data[1..])))
1066                    }
1067                    Value::Extension(data) if data.first() == Some(&(DataType::Uuid as u8)) => {
1068                        format_uuid_bytes(&data[1..])
1069                            .map(|s| Value::Text(SmartString::from_string(s)))
1070                            .unwrap_or(Value::Null(target_type))
1071                    }
1072                    Value::Extension(data) if data.first() == Some(&(DataType::Decimal as u8)) => {
1073                        self.as_string()
1074                            .map(|value| Value::Text(SmartString::from_string(value)))
1075                            .unwrap_or(Value::Null(target_type))
1076                    }
1077                    Value::Extension(_) => Value::Null(target_type),
1078                    Value::Null(_) => Value::Null(target_type),
1079                }
1080            }
1081            DataType::Boolean => {
1082                // Convert to BOOLEAN
1083                match self {
1084                    Value::Boolean(b) => Value::Boolean(*b),
1085                    Value::Integer(v) => Value::Boolean(*v != 0),
1086                    Value::Float(v) => Value::Boolean(*v != 0.0),
1087                    Value::Text(s) => {
1088                        // OPTIMIZATION: Use eq_ignore_ascii_case to avoid allocation
1089                        let s_ref: &str = s.as_ref();
1090                        if s_ref.eq_ignore_ascii_case("true")
1091                            || s_ref.eq_ignore_ascii_case("t")
1092                            || s_ref.eq_ignore_ascii_case("yes")
1093                            || s_ref.eq_ignore_ascii_case("y")
1094                            || s_ref == "1"
1095                        {
1096                            Value::Boolean(true)
1097                        } else if s_ref.eq_ignore_ascii_case("false")
1098                            || s_ref.eq_ignore_ascii_case("f")
1099                            || s_ref.eq_ignore_ascii_case("no")
1100                            || s_ref.eq_ignore_ascii_case("n")
1101                            || s_ref == "0"
1102                        {
1103                            Value::Boolean(false)
1104                        } else {
1105                            Value::Null(target_type)
1106                        }
1107                    }
1108                    _ => Value::Null(target_type),
1109                }
1110            }
1111            DataType::Timestamp => {
1112                // Convert to TIMESTAMP
1113                match self {
1114                    Value::Timestamp(t) => Value::Timestamp(*t),
1115                    Value::Extension(data)
1116                        if data.first() == Some(&(DataType::Date as u8)) && data.len() == 5 =>
1117                    {
1118                        self.as_date_days()
1119                            .and_then(|days| {
1120                                NaiveDate::from_ymd_opt(1970, 1, 1)?
1121                                    .checked_add_signed(chrono::Duration::days(i64::from(days)))?
1122                                    .and_hms_opt(0, 0, 0)
1123                            })
1124                            .map(|value| {
1125                                Value::Timestamp(DateTime::<Utc>::from_naive_utc_and_offset(
1126                                    value, Utc,
1127                                ))
1128                            })
1129                            .unwrap_or(Value::Null(target_type))
1130                    }
1131                    Value::Text(s) => parse_timestamp(s)
1132                        .map(Value::Timestamp)
1133                        .unwrap_or(Value::Null(target_type)),
1134                    Value::Integer(nanos) => {
1135                        // Interpret as nanoseconds since Unix epoch
1136                        datetime_from_epoch_nanos(*nanos)
1137                            .map(Value::Timestamp)
1138                            .unwrap_or(Value::Null(target_type))
1139                    }
1140                    _ => Value::Null(target_type),
1141                }
1142            }
1143            DataType::Json => {
1144                // Convert to JSON
1145                match self {
1146                    Value::Extension(data) if data.first() == Some(&(DataType::Json as u8)) => {
1147                        self.clone()
1148                    }
1149                    Value::Text(s) => {
1150                        // Validate JSON
1151                        if serde_json::from_str::<serde_json::Value>(s.as_str()).is_ok() {
1152                            Value::json_unchecked(s.as_str())
1153                        } else {
1154                            Value::Null(target_type)
1155                        }
1156                    }
1157                    // Convert other types to JSON representation
1158                    Value::Integer(v) => Value::json_unchecked(v.to_string()),
1159                    Value::Float(v) if v.is_finite() => Value::json_unchecked(format_float(*v)),
1160                    Value::Boolean(b) => Value::json_unchecked(if *b { "true" } else { "false" }),
1161                    Value::Timestamp(timestamp) => {
1162                        Value::json_unchecked(format!("\"{}\"", timestamp.to_rfc3339()))
1163                    }
1164                    _ => Value::Null(target_type),
1165                }
1166            }
1167            DataType::Vector => match self {
1168                Value::Extension(data) if data.first() == Some(&(DataType::Vector as u8)) => {
1169                    self.clone()
1170                }
1171                Value::Text(s) => {
1172                    if let Some(floats) = parse_vector_str(s.as_str()) {
1173                        Value::vector(floats)
1174                    } else {
1175                        Value::Null(target_type)
1176                    }
1177                }
1178                _ => Value::Null(target_type),
1179            },
1180            DataType::Uuid => match self {
1181                Value::Extension(data) if data.first() == Some(&(DataType::Uuid as u8)) => {
1182                    if data.len() == 17 {
1183                        self.clone()
1184                    } else {
1185                        Value::Null(target_type)
1186                    }
1187                }
1188                Value::Text(s) => parse_uuid_str(s.as_str())
1189                    .map(Value::uuid)
1190                    .unwrap_or(Value::Null(target_type)),
1191                _ => Value::Null(target_type),
1192            },
1193            DataType::Decimal => match self {
1194                Value::Extension(data) if data.first() == Some(&(DataType::Decimal as u8)) => {
1195                    if self.as_decimal_parts().is_some() {
1196                        self.clone()
1197                    } else {
1198                        Value::Null(target_type)
1199                    }
1200                }
1201                Value::Integer(value) => {
1202                    let digits = decimal_precision_for_unscaled(*value);
1203                    Value::decimal_unchecked(*value as i128, digits, 0)
1204                }
1205                Value::Float(value) => parse_decimal_f64(*value)
1206                    .map(|(unscaled, precision, scale)| {
1207                        Value::decimal_unchecked(unscaled, precision, scale)
1208                    })
1209                    .unwrap_or(Value::Null(target_type)),
1210                Value::Text(s) => parse_decimal_str(s.as_str())
1211                    .map(|(unscaled, precision, scale)| {
1212                        Value::decimal_unchecked(unscaled, precision, scale)
1213                    })
1214                    .unwrap_or(Value::Null(target_type)),
1215                _ => Value::Null(target_type),
1216            },
1217            DataType::Date => match self {
1218                Value::Extension(data) if data.first() == Some(&(DataType::Date as u8)) => {
1219                    if data.len() == 5 {
1220                        self.clone()
1221                    } else {
1222                        Value::Null(target_type)
1223                    }
1224                }
1225                Value::Text(s) => parse_date_days_since_unix_epoch(s.as_str())
1226                    .map(Value::date)
1227                    .unwrap_or(Value::Null(target_type)),
1228                Value::Timestamp(timestamp) => {
1229                    let epoch = NaiveDate::from_ymd_opt(1970, 1, 1)
1230                        .expect("Unix epoch is a valid calendar date");
1231                    i32::try_from(
1232                        timestamp
1233                            .date_naive()
1234                            .signed_duration_since(epoch)
1235                            .num_days(),
1236                    )
1237                    .map(Value::date)
1238                    .unwrap_or(Value::Null(target_type))
1239                }
1240                _ => Value::Null(target_type),
1241            },
1242            DataType::Bytes => match self {
1243                Value::Extension(data) if data.first() == Some(&(DataType::Bytes as u8)) => {
1244                    self.clone()
1245                }
1246                Value::Text(s) => Value::bytes(s.as_bytes().to_vec()),
1247                _ => Value::Null(target_type),
1248            },
1249            DataType::Null => Value::Null(DataType::Null),
1250        }
1251    }
1252
1253    /// Checked coercion to the target data type.
1254    ///
1255    /// This is the strict counterpart to [`Value::coerce_to_type`]. It preserves
1256    /// the normal SQL rule that NULL casts to typed NULL, but treats
1257    /// non-NULL-to-NULL conversion as a runtime type error. Use this for explicit
1258    /// SQL `CAST` and other user-visible expression evaluation boundaries where
1259    /// silently producing NULL would hide bad data.
1260    pub fn try_coerce_to_type(&self, target_type: DataType) -> Result<Value> {
1261        self.validate_shape()?;
1262        let coerced = self.coerce_to_type(target_type);
1263        if !self.is_null() && coerced.is_null() && target_type != DataType::Null {
1264            return Err(Error::Type(format!(
1265                "cannot convert value '{}' from {:?} to {:?}",
1266                self,
1267                self.data_type(),
1268                target_type
1269            )));
1270        }
1271        Ok(coerced)
1272    }
1273
1274    /// Coerce value to target type, consuming self
1275    /// OPTIMIZATION: Avoids clone when types already match
1276    #[inline]
1277    pub fn into_coerce_to_type(self, target_type: DataType) -> Value {
1278        // NULL stays NULL (with target type hint)
1279        if self.is_null() {
1280            return Value::Null(target_type);
1281        }
1282
1283        if self.validate_shape().is_err() {
1284            return Value::Null(target_type);
1285        }
1286
1287        // Same type - no conversion needed, return self directly
1288        if self.data_type() == target_type {
1289            return self;
1290        }
1291
1292        match target_type {
1293            DataType::Integer => match &self {
1294                Value::Integer(v) => Value::Integer(*v),
1295                Value::Float(v) => checked_float_to_i64(*v)
1296                    .map(Value::Integer)
1297                    .unwrap_or(Value::Null(target_type)),
1298                Value::Text(s) => parse_text_to_i64(s)
1299                    .map(Value::Integer)
1300                    .unwrap_or(Value::Null(target_type)),
1301                Value::Boolean(b) => Value::Integer(if *b { 1 } else { 0 }),
1302                Value::Extension(ref data) if data.first() == Some(&(DataType::Decimal as u8)) => {
1303                    self.as_decimal_parts()
1304                        .and_then(|(unscaled, _, scale)| {
1305                            decimal_scale_factor(scale)
1306                                .and_then(|factor| i64::try_from(unscaled / factor).ok())
1307                        })
1308                        .map(Value::Integer)
1309                        .unwrap_or(Value::Null(target_type))
1310                }
1311                _ => Value::Null(target_type),
1312            },
1313            DataType::Float => match &self {
1314                Value::Float(v) => Value::Float(*v),
1315                Value::Integer(v) => Value::Float(*v as f64),
1316                Value::Text(s) => s
1317                    .parse::<f64>()
1318                    .map(Value::Float)
1319                    .unwrap_or(Value::Null(target_type)),
1320                Value::Boolean(b) => Value::Float(if *b { 1.0 } else { 0.0 }),
1321                Value::Extension(ref data) if data.first() == Some(&(DataType::Decimal as u8)) => {
1322                    self.as_string()
1323                        .and_then(|value| value.parse::<f64>().ok())
1324                        .map(Value::Float)
1325                        .unwrap_or(Value::Null(target_type))
1326                }
1327                _ => Value::Null(target_type),
1328            },
1329            DataType::Text => match self {
1330                Value::Text(s) => Value::Text(s),
1331                Value::Integer(v) => Value::Text(SmartString::from_string(v.to_string())),
1332                Value::Float(v) => Value::Text(SmartString::from_string(format_float(v))),
1333                Value::Boolean(b) => {
1334                    Value::Text(SmartString::new(if b { "true" } else { "false" }))
1335                }
1336                Value::Timestamp(t) => Value::Text(SmartString::from_string(t.to_rfc3339())),
1337                Value::Extension(data) if data.first() == Some(&(DataType::Json as u8)) => {
1338                    Value::Text(SmartString::new(
1339                        std::str::from_utf8(&data[1..]).unwrap_or(""),
1340                    ))
1341                }
1342                Value::Extension(data) if data.first() == Some(&(DataType::Vector as u8)) => {
1343                    Value::Text(SmartString::from_string(format_vector_bytes(&data[1..])))
1344                }
1345                Value::Extension(data) if data.first() == Some(&(DataType::Uuid as u8)) => {
1346                    format_uuid_bytes(&data[1..])
1347                        .map(|s| Value::Text(SmartString::from_string(s)))
1348                        .unwrap_or(Value::Null(target_type))
1349                }
1350                Value::Extension(ref data) if data.first() == Some(&(DataType::Decimal as u8)) => {
1351                    self.as_string()
1352                        .map(|value| Value::Text(SmartString::from_string(value)))
1353                        .unwrap_or(Value::Null(target_type))
1354                }
1355                Value::Extension(_) | Value::Null(_) => Value::Null(target_type),
1356            },
1357            DataType::Boolean => match &self {
1358                Value::Boolean(b) => Value::Boolean(*b),
1359                Value::Integer(v) => Value::Boolean(*v != 0),
1360                Value::Float(v) => Value::Boolean(*v != 0.0),
1361                Value::Text(s) => {
1362                    // OPTIMIZATION: Use eq_ignore_ascii_case to avoid allocation
1363                    let s_ref: &str = s.as_ref();
1364                    if s_ref.eq_ignore_ascii_case("true")
1365                        || s_ref.eq_ignore_ascii_case("t")
1366                        || s_ref.eq_ignore_ascii_case("yes")
1367                        || s_ref.eq_ignore_ascii_case("y")
1368                        || s_ref == "1"
1369                    {
1370                        Value::Boolean(true)
1371                    } else if s_ref.eq_ignore_ascii_case("false")
1372                        || s_ref.eq_ignore_ascii_case("f")
1373                        || s_ref.eq_ignore_ascii_case("no")
1374                        || s_ref.eq_ignore_ascii_case("n")
1375                        || s_ref == "0"
1376                    {
1377                        Value::Boolean(false)
1378                    } else {
1379                        Value::Null(target_type)
1380                    }
1381                }
1382                _ => Value::Null(target_type),
1383            },
1384            DataType::Timestamp => match self {
1385                Value::Timestamp(t) => Value::Timestamp(t),
1386                Value::Text(s) => parse_timestamp(&s)
1387                    .map(Value::Timestamp)
1388                    .unwrap_or(Value::Null(target_type)),
1389                Value::Integer(nanos) => datetime_from_epoch_nanos(nanos)
1390                    .map(Value::Timestamp)
1391                    .unwrap_or(Value::Null(target_type)),
1392                _ => Value::Null(target_type),
1393            },
1394            DataType::Json => match self {
1395                Value::Extension(ref data) if data.first() == Some(&(DataType::Json as u8)) => self,
1396                Value::Text(s) => {
1397                    if serde_json::from_str::<serde_json::Value>(s.as_str()).is_ok() {
1398                        Value::json_unchecked(s.as_str())
1399                    } else {
1400                        Value::Null(target_type)
1401                    }
1402                }
1403                Value::Integer(v) => Value::json_unchecked(v.to_string()),
1404                Value::Float(v) if v.is_finite() => Value::json_unchecked(format_float(v)),
1405                Value::Boolean(b) => Value::json_unchecked(if b { "true" } else { "false" }),
1406                Value::Timestamp(timestamp) => {
1407                    Value::json_unchecked(format!("\"{}\"", timestamp.to_rfc3339()))
1408                }
1409                _ => Value::Null(target_type),
1410            },
1411            DataType::Vector => match self {
1412                Value::Extension(ref data) if data.first() == Some(&(DataType::Vector as u8)) => {
1413                    self
1414                }
1415                Value::Text(s) => {
1416                    if let Some(floats) = parse_vector_str(s.as_str()) {
1417                        Value::vector(floats)
1418                    } else {
1419                        Value::Null(target_type)
1420                    }
1421                }
1422                _ => Value::Null(target_type),
1423            },
1424            DataType::Uuid => match self {
1425                Value::Extension(ref data) if data.first() == Some(&(DataType::Uuid as u8)) => {
1426                    if data.len() == 17 {
1427                        self
1428                    } else {
1429                        Value::Null(target_type)
1430                    }
1431                }
1432                Value::Text(s) => parse_uuid_str(s.as_str())
1433                    .map(Value::uuid)
1434                    .unwrap_or(Value::Null(target_type)),
1435                _ => Value::Null(target_type),
1436            },
1437            DataType::Decimal => match self {
1438                Value::Extension(ref data) if data.first() == Some(&(DataType::Decimal as u8)) => {
1439                    if self.as_decimal_parts().is_some() {
1440                        self
1441                    } else {
1442                        Value::Null(target_type)
1443                    }
1444                }
1445                Value::Integer(value) => {
1446                    let digits = decimal_precision_for_unscaled(value);
1447                    Value::decimal_unchecked(value as i128, digits, 0)
1448                }
1449                Value::Float(value) => parse_decimal_f64(value)
1450                    .map(|(unscaled, precision, scale)| {
1451                        Value::decimal_unchecked(unscaled, precision, scale)
1452                    })
1453                    .unwrap_or(Value::Null(target_type)),
1454                Value::Text(s) => parse_decimal_str(s.as_str())
1455                    .map(|(unscaled, precision, scale)| {
1456                        Value::decimal_unchecked(unscaled, precision, scale)
1457                    })
1458                    .unwrap_or(Value::Null(target_type)),
1459                _ => Value::Null(target_type),
1460            },
1461            DataType::Date => match self {
1462                Value::Extension(ref data) if data.first() == Some(&(DataType::Date as u8)) => {
1463                    if data.len() == 5 {
1464                        self
1465                    } else {
1466                        Value::Null(target_type)
1467                    }
1468                }
1469                Value::Text(s) => parse_date_days_since_unix_epoch(s.as_str())
1470                    .map(Value::date)
1471                    .unwrap_or(Value::Null(target_type)),
1472                _ => Value::Null(target_type),
1473            },
1474            DataType::Bytes => match self {
1475                Value::Extension(ref data) if data.first() == Some(&(DataType::Bytes as u8)) => {
1476                    self
1477                }
1478                Value::Text(s) => Value::bytes(s.as_bytes().to_vec()),
1479                _ => Value::Null(target_type),
1480            },
1481            DataType::Null => Value::Null(DataType::Null),
1482        }
1483    }
1484}
1485
1486// =========================================================================
1487// Trait implementations
1488// =========================================================================
1489
1490impl Default for Value {
1491    fn default() -> Self {
1492        Value::Null(DataType::Null)
1493    }
1494}
1495
1496impl fmt::Display for Value {
1497    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1498        match self {
1499            Value::Null(_) => write!(f, "NULL"),
1500            Value::Integer(v) => write!(f, "{}", v),
1501            Value::Float(v) => write!(f, "{}", format_float(*v)),
1502            Value::Text(s) => write!(f, "{}", s),
1503            Value::Boolean(b) => write!(f, "{}", if *b { "true" } else { "false" }),
1504            Value::Timestamp(t) => write!(f, "{}", t.to_rfc3339()),
1505            Value::Extension(data) => {
1506                let tag = data.first().copied().unwrap_or(0);
1507                if tag == DataType::Json as u8 {
1508                    write!(f, "{}", std::str::from_utf8(&data[1..]).unwrap_or(""))
1509                } else if tag == DataType::Vector as u8 {
1510                    write!(f, "{}", format_vector_bytes(&data[1..]))
1511                } else if tag == DataType::Uuid as u8 {
1512                    match format_uuid_bytes(&data[1..]) {
1513                        Some(uuid) => write!(f, "{}", uuid),
1514                        None => write!(f, "<invalid-uuid>"),
1515                    }
1516                } else if tag == DataType::Decimal as u8 {
1517                    match self.as_decimal_parts() {
1518                        Some((unscaled, _, scale)) => {
1519                            write!(f, "{}", format_decimal_parts(unscaled, scale))
1520                        }
1521                        None => write!(f, "<invalid-decimal>"),
1522                    }
1523                } else if tag == DataType::Date as u8 {
1524                    match self
1525                        .as_date_days()
1526                        .and_then(format_date_days_since_unix_epoch)
1527                    {
1528                        Some(date) => write!(f, "{}", date),
1529                        None => write!(f, "<invalid-date>"),
1530                    }
1531                } else if tag == DataType::Bytes as u8 {
1532                    write!(f, "{}", format_bytes_hex(&data[1..]))
1533                } else {
1534                    write!(f, "<extension:{}>", tag)
1535                }
1536            }
1537        }
1538    }
1539}
1540
1541impl PartialEq for Value {
1542    #[inline]
1543    fn eq(&self, other: &Self) -> bool {
1544        if let Some(ordering) = compare_canonical_numeric(self, other) {
1545            return ordering == Ordering::Equal;
1546        }
1547
1548        // Single match handles NULL and all type comparisons without redundant is_null() calls
1549        match (self, other) {
1550            // NULL handling: NULL == NULL (SQL equality semantics for grouping)
1551            (Value::Null(_), Value::Null(_)) => true,
1552            // NULL != any non-NULL value
1553            (Value::Null(_), _) | (_, Value::Null(_)) => false,
1554            (Value::Text(a), Value::Text(b)) => a == b,
1555            (Value::Boolean(a), Value::Boolean(b)) => a == b,
1556            (Value::Timestamp(a), Value::Timestamp(b)) => a == b,
1557            (Value::Extension(a), Value::Extension(b)) => a == b,
1558            _ => false,
1559        }
1560    }
1561}
1562
1563impl Eq for Value {}
1564
1565/// Compare an exact i64 with an f64 without converting the integer to f64.
1566///
1567/// NaNs form the existing canonical class ordered after every numeric value.
1568/// `2^63` is the exclusive upper bound because `i64::MAX as f64` rounds to it;
1569/// `-2^63` is exactly representable and therefore remains inclusive.
1570#[inline]
1571fn compare_integer_float(integer: i64, float: f64) -> Ordering {
1572    const I64_EXCLUSIVE_UPPER_F64: f64 = 9_223_372_036_854_775_808.0;
1573    const I64_INCLUSIVE_LOWER_F64: f64 = -9_223_372_036_854_775_808.0;
1574
1575    if float.is_nan() {
1576        return Ordering::Less;
1577    }
1578    if float >= I64_EXCLUSIVE_UPPER_F64 {
1579        return Ordering::Less;
1580    }
1581    if float < I64_INCLUSIVE_LOWER_F64 {
1582        return Ordering::Greater;
1583    }
1584
1585    // The range checks make this saturating cast an exact truncation toward
1586    // zero into i64. Comparing with that integer decides every case except a
1587    // fractional float whose truncation is exactly `integer`.
1588    let truncated = float as i64;
1589    match integer.cmp(&truncated) {
1590        Ordering::Equal => {
1591            let fraction = float.fract();
1592            if fraction == 0.0 {
1593                Ordering::Equal
1594            } else if fraction.is_sign_negative() {
1595                Ordering::Greater
1596            } else {
1597                Ordering::Less
1598            }
1599        }
1600        ordering => ordering,
1601    }
1602}
1603
1604/// WyHash-style 128-bit multiply mixing function.
1605/// Provides excellent avalanche properties - small input changes produce
1606/// completely different outputs. This pre-mixes values before the hasher
1607/// sees them, fixing collision problems with simple hashers like FxHash.
1608#[inline(always)]
1609fn wymix(a: u64, b: u64) -> u64 {
1610    let r = (a as u128).wrapping_mul(b as u128);
1611    (r as u64) ^ ((r >> 64) as u64)
1612}
1613
1614// WyHash prime constants for mixing
1615const WY_P1: u64 = 0xa0761d6478bd642f;
1616const WY_P2: u64 = 0xe7037ed1a0b428db;
1617
1618#[inline(always)]
1619fn integer_hash_word(value: i64) -> u64 {
1620    wymix(1 ^ (value as u64), WY_P1)
1621}
1622
1623#[inline(always)]
1624fn float_hash_word(value: f64) -> u64 {
1625    if value.is_nan() {
1626        return wymix(6 ^ f64::NAN.to_bits(), WY_P1);
1627    }
1628
1629    let integer = value as i64;
1630    if compare_integer_float(integer, value) == Ordering::Equal {
1631        // Exactly integral, in-range floats share the Integer domain,
1632        // including signed zero and -2^63.
1633        integer_hash_word(integer)
1634    } else {
1635        wymix(6 ^ value.to_bits(), WY_P1)
1636    }
1637}
1638
1639impl Hash for Value {
1640    #[inline(always)]
1641    fn hash<H: Hasher>(&self, state: &mut H) {
1642        // Pre-mix strategy: Instead of writing raw values that may have poor
1643        // distribution (causing collisions in simple hashers like FxHash),
1644        // we pre-mix everything using WyHash-style 128-bit multiply mixing.
1645        // This gives ANY hasher well-distributed inputs.
1646        //
1647        // Constraint: Integer(5) == Float(5.0) must have equal hashes.
1648        // We handle this by using the exact integer domain only for integral
1649        // floats that compare equal to a mathematical i64.
1650        match self {
1651            Value::Null(_) => {
1652                // All NULLs hash the same
1653                state.write_u64(0);
1654            }
1655            Value::Integer(v) => {
1656                // Every integer keeps its exact i64 identity.
1657                state.write_u64(integer_hash_word(*v));
1658            }
1659            Value::Float(v) => {
1660                state.write_u64(float_hash_word(*v));
1661            }
1662            Value::Text(s) => {
1663                // Pre-hash string with WyHash-style mixing, write single u64
1664                let bytes = s.as_bytes();
1665                let len = bytes.len();
1666                let mut h = wymix(2 ^ (len as u64), WY_P1);
1667
1668                // Process 8 bytes at a time
1669                let chunks = len / 8;
1670                let ptr = bytes.as_ptr();
1671                for i in 0..chunks {
1672                    // SAFETY: We iterate i from 0..chunks where chunks = len/8.
1673                    // So i*8 is always < len, and we read 8 bytes which is valid
1674                    // since (i+1)*8 <= chunks*8 <= len. read_unaligned handles alignment.
1675                    let chunk = unsafe { (ptr.add(i * 8) as *const u64).read_unaligned() };
1676                    h = wymix(h ^ chunk, WY_P2);
1677                }
1678
1679                // Handle tail bytes (0-7)
1680                let tail_start = chunks * 8;
1681                if tail_start < len {
1682                    let mut tail = 0u64;
1683                    for (j, &b) in bytes[tail_start..].iter().enumerate() {
1684                        tail |= (b as u64) << (j * 8);
1685                    }
1686                    h = wymix(h ^ tail, WY_P1);
1687                }
1688
1689                state.write_u64(h);
1690            }
1691            Value::Boolean(b) => {
1692                // Pre-mixed boolean
1693                state.write_u64(wymix(if *b { 5 } else { 4 }, WY_P1));
1694            }
1695            Value::Timestamp(t) => {
1696                // Hash at full nanosecond precision. Volume segments now store
1697                // timestamps as i64 nanoseconds, so no precision loss occurs.
1698                let nanos = t
1699                    .timestamp_nanos_opt()
1700                    .unwrap_or_else(|| t.timestamp().saturating_mul(1_000_000_000));
1701                state.write_u64(wymix(3 ^ (nanos as u64), WY_P1));
1702            }
1703            Value::Extension(data) => {
1704                if let Some((unscaled, _, scale)) = self.as_decimal_parts() {
1705                    state.write_u64(decimal_hash_word(unscaled, scale));
1706                    return;
1707                }
1708
1709                // Pre-hash extension data with WyHash-style mixing
1710                // Tag byte is included in data, so discriminant is embedded
1711                let bytes: &[u8] = data;
1712                let len = bytes.len();
1713                let mut h = wymix(10 ^ (len as u64), WY_P1);
1714
1715                let chunks = len / 8;
1716                let ptr = bytes.as_ptr();
1717                for i in 0..chunks {
1718                    // SAFETY: We iterate i from 0..chunks where chunks = len/8.
1719                    // So i*8 is always < len, and we read 8 bytes which is valid
1720                    // since (i+1)*8 <= chunks*8 <= len. read_unaligned handles alignment.
1721                    let chunk = unsafe { (ptr.add(i * 8) as *const u64).read_unaligned() };
1722                    h = wymix(h ^ chunk, WY_P2);
1723                }
1724
1725                let tail_start = chunks * 8;
1726                if tail_start < len {
1727                    let mut tail = 0u64;
1728                    for (j, &b) in bytes[tail_start..].iter().enumerate() {
1729                        tail |= (b as u64) << (j * 8);
1730                    }
1731                    h = wymix(h ^ tail, WY_P1);
1732                }
1733
1734                state.write_u64(h);
1735            }
1736        }
1737    }
1738}
1739
1740// Note: PartialOrd intentionally differs from Ord for SQL semantics
1741// - PartialOrd: SQL comparison (NULL returns None, cross-type numeric comparison)
1742// - Ord: BTreeMap ordering (NULLs first, type discriminant ordering)
1743#[allow(clippy::non_canonical_partial_ord_impl)]
1744impl PartialOrd for Value {
1745    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1746        // Use the original compare method for semantic correctness in SQL operations
1747        // This preserves NULL comparison semantics (returning None for NULL comparisons)
1748        // and proper cross-type numeric comparison (Integer vs Float)
1749        self.compare(other).ok()
1750    }
1751}
1752
1753/// Total ordering implementation for Value
1754///
1755/// This is required for using Value as a key in BTreeMap/BTreeSet.
1756/// The ordering is defined as follows:
1757/// 1. NULLs are always ordered first (smallest)
1758/// 2. Numeric types (Integer, Float) are compared by numeric value (consistent with PartialEq)
1759/// 3. Other different data types are ordered by their type discriminant
1760/// 4. Same data types use their natural ordering
1761///
1762/// IMPORTANT: This ordering MUST be consistent with PartialEq. Since Integer(5) == Float(5.0)
1763/// per PartialEq, we must ensure Integer(5).cmp(&Float(5.0)) == Ordering::Equal.
1764/// Violating this contract causes BTreeMap corruption.
1765///
1766/// Note: This differs from SQL NULL semantics where NULL comparisons
1767/// return UNKNOWN. This ordering is only for internal index structure.
1768impl Ord for Value {
1769    fn cmp(&self, other: &Self) -> Ordering {
1770        // Handle NULL comparisons - NULLs are ordered first
1771        match (self.is_null(), other.is_null()) {
1772            (true, true) => return Ordering::Equal,
1773            (true, false) => return Ordering::Less,
1774            (false, true) => return Ordering::Greater,
1775            (false, false) => {} // Continue to value comparison
1776        }
1777
1778        if let Some(ordering) = compare_canonical_numeric(self, other) {
1779            return ordering;
1780        }
1781
1782        // Helper function to get type discriminant for ordering
1783        fn type_discriminant(v: &Value) -> u8 {
1784            match v {
1785                Value::Null(_) => 0,
1786                Value::Boolean(_) => 1,
1787                // Integer, Float and valid Decimal share the numeric domain.
1788                Value::Integer(_) | Value::Float(_) => 2,
1789                Value::Extension(_) if v.as_decimal_parts().is_some() => 2,
1790                Value::Text(_) => 3,
1791                Value::Timestamp(_) => 4,
1792                Value::Extension(_) => 5,
1793            }
1794        }
1795
1796        let self_disc = type_discriminant(self);
1797        let other_disc = type_discriminant(other);
1798
1799        // Different types: order by type discriminant
1800        if self_disc != other_disc {
1801            return self_disc.cmp(&other_disc);
1802        }
1803
1804        // Same type comparison
1805        match (self, other) {
1806            (Value::Integer(a), Value::Integer(b)) => a.cmp(b),
1807            (Value::Float(a), Value::Float(b)) => {
1808                // Handle NaN: NaN is ordered last
1809                match (a.is_nan(), b.is_nan()) {
1810                    (true, true) => Ordering::Equal,
1811                    (true, false) => Ordering::Greater,
1812                    (false, true) => Ordering::Less,
1813                    (false, false) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
1814                }
1815            }
1816            (Value::Text(a), Value::Text(b)) => a.cmp(b),
1817            (Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
1818            (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
1819            (Value::Extension(a), Value::Extension(b)) => a.cmp(b),
1820            _ => Ordering::Equal, // Should not reach here
1821        }
1822    }
1823}
1824
1825// =========================================================================
1826// From implementations for convenient construction
1827// =========================================================================
1828
1829impl From<i64> for Value {
1830    fn from(v: i64) -> Self {
1831        Value::Integer(v)
1832    }
1833}
1834
1835impl From<i32> for Value {
1836    fn from(v: i32) -> Self {
1837        Value::Integer(v as i64)
1838    }
1839}
1840
1841impl From<i16> for Value {
1842    fn from(v: i16) -> Self {
1843        Value::Integer(v as i64)
1844    }
1845}
1846
1847impl From<i8> for Value {
1848    fn from(v: i8) -> Self {
1849        Value::Integer(v as i64)
1850    }
1851}
1852
1853impl From<u32> for Value {
1854    fn from(v: u32) -> Self {
1855        Value::Integer(v as i64)
1856    }
1857}
1858
1859impl From<u16> for Value {
1860    fn from(v: u16) -> Self {
1861        Value::Integer(v as i64)
1862    }
1863}
1864
1865impl From<u8> for Value {
1866    fn from(v: u8) -> Self {
1867        Value::Integer(v as i64)
1868    }
1869}
1870
1871impl From<f64> for Value {
1872    fn from(v: f64) -> Self {
1873        Value::Float(v)
1874    }
1875}
1876
1877impl From<f32> for Value {
1878    fn from(v: f32) -> Self {
1879        Value::Float(v as f64)
1880    }
1881}
1882
1883impl From<String> for Value {
1884    fn from(v: String) -> Self {
1885        Value::Text(SmartString::from_string(v))
1886    }
1887}
1888
1889impl From<&str> for Value {
1890    fn from(v: &str) -> Self {
1891        Value::Text(SmartString::from(v))
1892    }
1893}
1894
1895impl From<Arc<str>> for Value {
1896    fn from(v: Arc<str>) -> Self {
1897        Value::Text(SmartString::from(v.as_ref()))
1898    }
1899}
1900
1901impl From<bool> for Value {
1902    fn from(v: bool) -> Self {
1903        Value::Boolean(v)
1904    }
1905}
1906
1907impl From<DateTime<Utc>> for Value {
1908    fn from(v: DateTime<Utc>) -> Self {
1909        Value::Timestamp(v)
1910    }
1911}
1912
1913impl<T: Into<Value>> From<Option<T>> for Value {
1914    fn from(v: Option<T>) -> Self {
1915        match v {
1916            Some(val) => val.into(),
1917            None => Value::Null(DataType::Null),
1918        }
1919    }
1920}
1921
1922// =========================================================================
1923// Helper functions
1924// =========================================================================
1925
1926/// Parse a timestamp string with multiple format support
1927pub fn parse_timestamp(s: &str) -> Result<DateTime<Utc>> {
1928    let s = s.trim();
1929
1930    // Try each timestamp format
1931    for format in TIMESTAMP_FORMATS {
1932        if let Ok(dt) = DateTime::parse_from_str(s, format) {
1933            return Ok(dt.with_timezone(&Utc));
1934        }
1935        // Try parsing as naive datetime and assume UTC
1936        if let Ok(ndt) = NaiveDateTime::parse_from_str(s, format) {
1937            return Ok(Utc.from_utc_datetime(&ndt));
1938        }
1939    }
1940
1941    // Try date-only formats
1942    if let Ok(date) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
1943        let datetime = date.and_hms_opt(0, 0, 0).unwrap();
1944        return Ok(Utc.from_utc_datetime(&datetime));
1945    }
1946
1947    // Try time-only formats (use today's date)
1948    for format in TIME_FORMATS {
1949        if let Ok(time) = NaiveTime::parse_from_str(s, format) {
1950            let today = Utc::now().date_naive();
1951            let datetime = today.and_time(time);
1952            return Ok(Utc.from_utc_datetime(&datetime));
1953        }
1954    }
1955
1956    Err(Error::parse(format!("invalid timestamp format: {}", s)))
1957}
1958
1959/// Format a float value consistently
1960fn format_float(v: f64) -> String {
1961    // Handle special cases
1962    if v.is_nan() {
1963        return "NaN".to_string();
1964    }
1965    if v.is_infinite() {
1966        return if v.is_sign_positive() {
1967            "Infinity"
1968        } else {
1969            "-Infinity"
1970        }
1971        .to_string();
1972    }
1973
1974    let abs_v = v.abs();
1975
1976    // Use scientific notation for very large or very small numbers
1977    if abs_v != 0.0 && !(1e-4..1e15).contains(&abs_v) {
1978        // Use scientific notation with up to 15 significant digits
1979        let s = format!("{:e}", v);
1980        // Clean up trailing zeros in mantissa
1981        if let Some(e_pos) = s.find('e') {
1982            let (mantissa, exp) = s.split_at(e_pos);
1983            let clean_mantissa = if mantissa.contains('.') {
1984                mantissa
1985                    .trim_end_matches('0')
1986                    .trim_end_matches('.')
1987                    .to_string()
1988            } else {
1989                mantissa.to_string()
1990            };
1991            return format!("{}{}", clean_mantissa, exp);
1992        }
1993        return s;
1994    }
1995
1996    if v.fract() == 0.0 {
1997        // Integer-like float, format without decimal
1998        format!("{:.0}", v)
1999    } else {
2000        // Use standard representation for normal range
2001        let s = format!("{:?}", v);
2002        // Remove trailing zeros after decimal point
2003        if s.contains('.') && !s.contains('e') && !s.contains('E') {
2004            s.trim_end_matches('0').trim_end_matches('.').to_string()
2005        } else {
2006            s
2007        }
2008    }
2009}
2010
2011/// Format packed LE f32 bytes as "[1.0, 2.0, 3.0]" string
2012pub fn format_vector_bytes(data: &[u8]) -> String {
2013    let len = data.len() / 4;
2014    let mut s = String::with_capacity(len * 8 + 2);
2015    s.push('[');
2016    for i in 0..len {
2017        if i > 0 {
2018            s.push_str(", ");
2019        }
2020        let f = f32::from_le_bytes([
2021            data[i * 4],
2022            data[i * 4 + 1],
2023            data[i * 4 + 2],
2024            data[i * 4 + 3],
2025        ]);
2026        use std::fmt::Write;
2027        if f.fract() == 0.0 && f.is_finite() {
2028            let _ = write!(s, "{:.1}", f);
2029        } else {
2030            let _ = write!(s, "{}", f);
2031        }
2032    }
2033    s.push(']');
2034    s
2035}
2036
2037/// Parse a UUID string into raw 16-byte UUID storage.
2038///
2039/// Accepts the standard hyphenated form and the compact 32-hex form supported
2040/// by the `uuid` crate. Short strings are rejected; no implicit zero-padding is
2041/// part of the SQL contract.
2042pub fn parse_uuid_str(s: &str) -> Option<[u8; 16]> {
2043    Uuid::parse_str(s.trim()).ok().map(|uuid| *uuid.as_bytes())
2044}
2045
2046/// Format raw 16-byte UUID storage as canonical lowercase hyphenated text.
2047pub fn format_uuid_bytes(data: &[u8]) -> Option<String> {
2048    let bytes: [u8; 16] = data.try_into().ok()?;
2049    Some(Uuid::from_bytes(bytes).hyphenated().to_string())
2050}
2051
2052pub const MAX_DECIMAL_PRECISION: u8 = 38;
2053
2054/// Validate the declared physical DECIMAL payload.
2055pub fn validate_decimal_shape(unscaled: i128, precision: u8, scale: u8) -> Result<()> {
2056    if !(1..=MAX_DECIMAL_PRECISION).contains(&precision) {
2057        return Err(Error::invalid_argument(format!(
2058            "DECIMAL precision {precision} is outside supported range 1..={MAX_DECIMAL_PRECISION}"
2059        )));
2060    }
2061    if scale > precision {
2062        return Err(Error::invalid_argument(format!(
2063            "DECIMAL scale {scale} exceeds declared precision {precision}"
2064        )));
2065    }
2066    let digits = unscaled.unsigned_abs().to_string().len().max(1);
2067    if digits > usize::from(precision) {
2068        return Err(Error::invalid_argument(format!(
2069            "DECIMAL coefficient has {digits} digits but precision is {precision}"
2070        )));
2071    }
2072    Ok(())
2073}
2074
2075#[inline]
2076fn checked_float_to_i64(value: f64) -> Option<i64> {
2077    const I64_EXCLUSIVE_UPPER_F64: f64 = 9_223_372_036_854_775_808.0;
2078    const I64_INCLUSIVE_LOWER_F64: f64 = -9_223_372_036_854_775_808.0;
2079    (value.is_finite() && (I64_INCLUSIVE_LOWER_F64..I64_EXCLUSIVE_UPPER_F64).contains(&value))
2080        .then_some(value as i64)
2081}
2082
2083#[inline]
2084fn parse_text_to_i64(value: &str) -> Option<i64> {
2085    value
2086        .parse::<i64>()
2087        .ok()
2088        .or_else(|| value.parse::<f64>().ok().and_then(checked_float_to_i64))
2089}
2090
2091#[inline]
2092fn datetime_from_epoch_nanos(nanos: i64) -> Option<DateTime<Utc>> {
2093    DateTime::from_timestamp(
2094        nanos.div_euclid(1_000_000_000),
2095        nanos.rem_euclid(1_000_000_000) as u32,
2096    )
2097}
2098
2099/// Infer precision for an integer decimal payload.
2100fn decimal_precision_for_unscaled(value: i64) -> u8 {
2101    decimal_precision_for_unscaled_i128(value as i128)
2102}
2103
2104fn decimal_precision_for_unscaled_i128(value: i128) -> u8 {
2105    let digits = value.unsigned_abs().to_string().len().max(1);
2106    digits.min(MAX_DECIMAL_PRECISION as usize) as u8
2107}
2108
2109#[inline]
2110fn decimal_scale_factor(scale: u8) -> Option<i128> {
2111    (scale <= MAX_DECIMAL_PRECISION)
2112        .then(|| 10_i128.checked_pow(scale as u32))
2113        .flatten()
2114}
2115
2116/// Canonical numeric identity for an exact base-10 value.
2117///
2118/// `coefficient * 10^exponent` has no trailing coefficient zeroes. Precision
2119/// metadata is deliberately absent: it remains in the physical Decimal bytes,
2120/// but it is not part of numeric equality, hashing or ordering.
2121#[derive(Clone, Debug, Eq, PartialEq)]
2122struct DecimalIdentity {
2123    negative: bool,
2124    coefficient: u128,
2125    exponent: i32,
2126}
2127
2128impl DecimalIdentity {
2129    fn new(negative: bool, mut coefficient: u128, mut exponent: i32) -> Self {
2130        if coefficient == 0 {
2131            return Self {
2132                negative: false,
2133                coefficient: 0,
2134                exponent: 0,
2135            };
2136        }
2137
2138        while coefficient.is_multiple_of(10) {
2139            coefficient /= 10;
2140            exponent += 1;
2141        }
2142
2143        Self {
2144            negative,
2145            coefficient,
2146            exponent,
2147        }
2148    }
2149
2150    fn from_parts(unscaled: i128, scale: u8) -> Self {
2151        Self::new(
2152            unscaled.is_negative(),
2153            unscaled.unsigned_abs(),
2154            -i32::from(scale),
2155        )
2156    }
2157
2158    /// Parse Rust's shortest round-trippable finite FLOAT rendering into the
2159    /// same exact base-10 identity used by Decimal.
2160    fn from_float(value: f64) -> Option<Self> {
2161        if !value.is_finite() {
2162            return None;
2163        }
2164
2165        // LowerExp keeps the significand bounded to the f64 round-trip digit
2166        // count even for values near f64::{MIN_POSITIVE, MAX}.
2167        let rendered = format!("{value:e}");
2168        let (mantissa, exponent) = rendered.split_once('e')?;
2169        let exponent = exponent.parse::<i32>().ok()?;
2170        let (negative, mantissa) = mantissa
2171            .strip_prefix('-')
2172            .map_or((false, mantissa), |unsigned| (true, unsigned));
2173        let (integer, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
2174        let digits = format!("{integer}{fraction}");
2175        let coefficient = digits.parse::<u128>().ok()?;
2176        let fraction_len = i32::try_from(fraction.len()).ok()?;
2177
2178        Some(Self::new(
2179            negative,
2180            coefficient,
2181            exponent.checked_sub(fraction_len)?,
2182        ))
2183    }
2184
2185    fn exact_i64(&self) -> Option<i64> {
2186        if self.coefficient == 0 {
2187            return Some(0);
2188        }
2189        let exponent = u32::try_from(self.exponent).ok()?;
2190        let magnitude = self
2191            .coefficient
2192            .checked_mul(10_u128.checked_pow(exponent)?)?;
2193        if self.negative {
2194            if magnitude == (i64::MAX as u128) + 1 {
2195                Some(i64::MIN)
2196            } else {
2197                i64::try_from(magnitude).ok().map(|value| -value)
2198            }
2199        } else {
2200            i64::try_from(magnitude).ok()
2201        }
2202    }
2203
2204    fn cmp_magnitude(&self, other: &Self) -> Ordering {
2205        debug_assert!(self.coefficient != 0 && other.coefficient != 0);
2206
2207        let left_digits = self.coefficient.to_string();
2208        let right_digits = other.coefficient.to_string();
2209        let left_order = i32::try_from(left_digits.len())
2210            .unwrap_or(i32::MAX)
2211            .saturating_add(self.exponent);
2212        let right_order = i32::try_from(right_digits.len())
2213            .unwrap_or(i32::MAX)
2214            .saturating_add(other.exponent);
2215        match left_order.cmp(&right_order) {
2216            Ordering::Equal => {}
2217            ordering => return ordering,
2218        }
2219
2220        let width = left_digits.len().max(right_digits.len());
2221        for index in 0..width {
2222            let left = left_digits.as_bytes().get(index).copied().unwrap_or(b'0');
2223            let right = right_digits.as_bytes().get(index).copied().unwrap_or(b'0');
2224            match left.cmp(&right) {
2225                Ordering::Equal => {}
2226                ordering => return ordering,
2227            }
2228        }
2229        Ordering::Equal
2230    }
2231
2232    fn cmp_numeric(&self, other: &Self) -> Ordering {
2233        if self == other {
2234            return Ordering::Equal;
2235        }
2236
2237        match (self.coefficient == 0, other.coefficient == 0) {
2238            (true, true) => return Ordering::Equal,
2239            (true, false) => {
2240                return if other.negative {
2241                    Ordering::Greater
2242                } else {
2243                    Ordering::Less
2244                };
2245            }
2246            (false, true) => {
2247                return if self.negative {
2248                    Ordering::Less
2249                } else {
2250                    Ordering::Greater
2251                };
2252            }
2253            (false, false) => {}
2254        }
2255
2256        match self.negative.cmp(&other.negative) {
2257            Ordering::Less => Ordering::Greater,
2258            Ordering::Greater => Ordering::Less,
2259            Ordering::Equal if self.negative => self.cmp_magnitude(other).reverse(),
2260            Ordering::Equal => self.cmp_magnitude(other),
2261        }
2262    }
2263}
2264
2265fn compare_decimal_float(decimal: &DecimalIdentity, float: f64) -> Ordering {
2266    if float.is_nan() || float == f64::INFINITY {
2267        return Ordering::Less;
2268    }
2269    if float == f64::NEG_INFINITY {
2270        return Ordering::Greater;
2271    }
2272
2273    decimal.cmp_numeric(
2274        &DecimalIdentity::from_float(float)
2275            .expect("every finite f64 has a shortest decimal identity"),
2276    )
2277}
2278
2279/// Single owner for structural numeric identity across Integer, Float and
2280/// valid Decimal payloads. `None` means at least one operand is non-numeric.
2281fn compare_canonical_numeric(left: &Value, right: &Value) -> Option<Ordering> {
2282    match (left, right) {
2283        (Value::Integer(left), Value::Integer(right)) => Some(left.cmp(right)),
2284        (Value::Float(left), Value::Float(right)) => Some(compare_floats(*left, *right)),
2285        (Value::Integer(integer), Value::Float(float)) => {
2286            Some(compare_integer_float(*integer, *float))
2287        }
2288        (Value::Float(float), Value::Integer(integer)) => {
2289            Some(compare_integer_float(*integer, *float).reverse())
2290        }
2291        _ => {
2292            let left_decimal = left
2293                .as_decimal_parts()
2294                .map(|(unscaled, _, scale)| DecimalIdentity::from_parts(unscaled, scale));
2295            let right_decimal = right
2296                .as_decimal_parts()
2297                .map(|(unscaled, _, scale)| DecimalIdentity::from_parts(unscaled, scale));
2298
2299            match (left_decimal, right_decimal, left, right) {
2300                (Some(left), Some(right), _, _) => Some(left.cmp_numeric(&right)),
2301                (Some(left), None, _, Value::Integer(right)) => {
2302                    Some(left.cmp_numeric(&DecimalIdentity::from_parts(*right as i128, 0)))
2303                }
2304                (None, Some(right), Value::Integer(left), _) => {
2305                    Some(DecimalIdentity::from_parts(*left as i128, 0).cmp_numeric(&right))
2306                }
2307                (Some(left), None, _, Value::Float(right)) => {
2308                    Some(compare_decimal_float(&left, *right))
2309                }
2310                (None, Some(right), Value::Float(left), _) => {
2311                    Some(compare_decimal_float(&right, *left).reverse())
2312                }
2313                _ => None,
2314            }
2315        }
2316    }
2317}
2318
2319fn decimal_hash_word(unscaled: i128, scale: u8) -> u64 {
2320    let identity = DecimalIdentity::from_parts(unscaled, scale);
2321    if let Some(integer) = identity.exact_i64() {
2322        return integer_hash_word(integer);
2323    }
2324
2325    // A Decimal equals a finite Float only when that Float's shortest
2326    // round-trip decimal identity is exactly the same. Preserve the existing
2327    // Float hash domain for that equality class without changing Float's hot
2328    // hashing path.
2329    if let Ok(float) = format_decimal_parts(unscaled, scale).parse::<f64>() {
2330        if float.is_finite() && DecimalIdentity::from_float(float).as_ref() == Some(&identity) {
2331            return float_hash_word(float);
2332        }
2333    }
2334
2335    let low = identity.coefficient as u64;
2336    let high = (identity.coefficient >> 64) as u64;
2337    let sign = u64::from(identity.negative);
2338    let exponent = identity.exponent as i64 as u64;
2339    let mut hash = wymix(7 ^ low, WY_P1);
2340    hash = wymix(hash ^ high, WY_P2);
2341    hash = wymix(hash ^ exponent, WY_P1);
2342    wymix(hash ^ sign, WY_P2)
2343}
2344
2345/// Convert a finite binary FLOAT through Rust's shortest round-trippable
2346/// decimal representation. This defines the mixed FLOAT/DECIMAL contract
2347/// without importing the binary approximation itself into exact DECIMAL
2348/// storage.
2349fn parse_decimal_f64(value: f64) -> Option<(i128, u8, u8)> {
2350    if !value.is_finite() {
2351        return None;
2352    }
2353
2354    let rendered = value.to_string();
2355    let Some(exponent_offset) = rendered.find(['e', 'E']) else {
2356        return parse_decimal_str(&rendered);
2357    };
2358
2359    let (mantissa, exponent_with_marker) = rendered.split_at(exponent_offset);
2360    let exponent = exponent_with_marker[1..].parse::<i32>().ok()?;
2361    let (mut unscaled, _, mantissa_scale) = parse_decimal_str(mantissa)?;
2362    let resulting_scale = i32::from(mantissa_scale).checked_sub(exponent)?;
2363
2364    let scale = if resulting_scale < 0 {
2365        let power = u8::try_from(resulting_scale.checked_neg()?).ok()?;
2366        unscaled = unscaled.checked_mul(decimal_scale_factor(power)?)?;
2367        0
2368    } else {
2369        u8::try_from(resulting_scale).ok()?
2370    };
2371    if scale > MAX_DECIMAL_PRECISION
2372        || unscaled.unsigned_abs().to_string().len() > MAX_DECIMAL_PRECISION as usize
2373    {
2374        return None;
2375    }
2376
2377    Some((
2378        unscaled,
2379        decimal_precision_for_unscaled_i128(unscaled),
2380        scale,
2381    ))
2382}
2383
2384/// Parse a plain decimal string into exact wire-style decimal parts.
2385///
2386/// This intentionally accepts only ordinary fixed-point forms such as
2387/// `12`, `-12.30` and `.5`. Exponent notation belongs to FLOAT, not exact
2388/// DECIMAL, until the SQL decimal surface is expanded deliberately.
2389pub fn parse_decimal_str(s: &str) -> Option<(i128, u8, u8)> {
2390    let trimmed = s.trim();
2391    if trimmed.is_empty() {
2392        return None;
2393    }
2394
2395    let (negative, body) = match trimmed.as_bytes()[0] {
2396        b'-' => (true, &trimmed[1..]),
2397        b'+' => (false, &trimmed[1..]),
2398        _ => (false, trimmed),
2399    };
2400    if body.is_empty() {
2401        return None;
2402    }
2403
2404    let mut parts = body.split('.');
2405    let int_part = parts.next().unwrap_or("");
2406    let frac_part = parts.next();
2407    if parts.next().is_some() {
2408        return None;
2409    }
2410
2411    let frac = frac_part.unwrap_or("");
2412    if int_part.is_empty() && frac.is_empty() {
2413        return None;
2414    }
2415    if !int_part.bytes().all(|b| b.is_ascii_digit()) || !frac.bytes().all(|b| b.is_ascii_digit()) {
2416        return None;
2417    }
2418
2419    let scale = u8::try_from(frac.len()).ok()?;
2420    if scale > MAX_DECIMAL_PRECISION {
2421        return None;
2422    }
2423
2424    let digits = format!("{int_part}{frac}");
2425    let normalized = digits.trim_start_matches('0');
2426    let precision_len = normalized.len().max(usize::from(scale)).max(1);
2427    if precision_len > MAX_DECIMAL_PRECISION as usize {
2428        return None;
2429    }
2430    let precision = precision_len as u8;
2431
2432    let magnitude = if normalized.is_empty() {
2433        0
2434    } else {
2435        normalized.parse::<i128>().ok()?
2436    };
2437    Some((
2438        if negative { -magnitude } else { magnitude },
2439        precision,
2440        scale,
2441    ))
2442}
2443
2444pub fn format_decimal_parts(unscaled: i128, scale: u8) -> String {
2445    if scale == 0 {
2446        return unscaled.to_string();
2447    }
2448
2449    let negative = unscaled.is_negative();
2450    let mut digits = unscaled.unsigned_abs().to_string();
2451    let scale_len = scale as usize;
2452    if digits.len() <= scale_len {
2453        let mut padded = String::with_capacity(scale_len + 1);
2454        padded.push_str(&"0".repeat(scale_len + 1 - digits.len()));
2455        padded.push_str(&digits);
2456        digits = padded;
2457    }
2458    let split = digits.len() - scale_len;
2459    let mut out = String::with_capacity(digits.len() + 2);
2460    if negative {
2461        out.push('-');
2462    }
2463    out.push_str(&digits[..split]);
2464    out.push('.');
2465    out.push_str(&digits[split..]);
2466    out
2467}
2468
2469#[doc(hidden)]
2470pub fn compare_decimal_parts(
2471    left_unscaled: i128,
2472    left_scale: u8,
2473    right_unscaled: i128,
2474    right_scale: u8,
2475) -> Ordering {
2476    DecimalIdentity::from_parts(left_unscaled, left_scale)
2477        .cmp_numeric(&DecimalIdentity::from_parts(right_unscaled, right_scale))
2478}
2479
2480pub fn parse_date_days_since_unix_epoch(s: &str) -> Option<i32> {
2481    let date = NaiveDate::parse_from_str(s.trim(), "%Y-%m-%d").ok()?;
2482    let epoch = NaiveDate::from_ymd_opt(1970, 1, 1)?;
2483    i32::try_from(date.signed_duration_since(epoch).num_days()).ok()
2484}
2485
2486pub fn format_date_days_since_unix_epoch(days: i32) -> Option<String> {
2487    let epoch = NaiveDate::from_ymd_opt(1970, 1, 1)?;
2488    epoch
2489        .checked_add_signed(chrono::Duration::days(days as i64))
2490        .map(|date| date.format("%Y-%m-%d").to_string())
2491}
2492
2493pub fn format_bytes_hex(data: &[u8]) -> String {
2494    const HEX: &[u8; 16] = b"0123456789abcdef";
2495    let mut out = String::with_capacity(2 + data.len() * 2);
2496    out.push_str("0x");
2497    for byte in data {
2498        out.push(HEX[(byte >> 4) as usize] as char);
2499        out.push(HEX[(byte & 0x0f) as usize] as char);
2500    }
2501    out
2502}
2503
2504/// Parse a vector string in [f32, f32, ...] format
2505pub fn parse_vector_str(s: &str) -> Option<Vec<f32>> {
2506    let s = s.trim();
2507    let inner = s.strip_prefix('[')?.strip_suffix(']')?;
2508    if inner.trim().is_empty() {
2509        return Some(Vec::new());
2510    }
2511    let mut result = Vec::new();
2512    for part in inner.split(',') {
2513        let val: f32 = part.trim().parse().ok()?;
2514        result.push(val);
2515    }
2516    Some(result)
2517}
2518
2519/// Compare two floats with proper NaN handling
2520fn compare_floats(a: f64, b: f64) -> Ordering {
2521    // Handle NaN: treat as greater than all other values for consistency
2522    match (a.is_nan(), b.is_nan()) {
2523        (true, true) => Ordering::Equal,
2524        (true, false) => Ordering::Greater,
2525        (false, true) => Ordering::Less,
2526        (false, false) => a.partial_cmp(&b).unwrap_or(Ordering::Equal),
2527    }
2528}
2529
2530#[cfg(test)]
2531mod tests;