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