dsq_shared/
value.rs

1//! Value types for DSQ data processing
2//!
3//! This module provides the core Value enum that represents all data types
4//! that can be processed by DSQ, including JSON-like values and Polars DataFrames.
5
6use chrono::{DateTime, Duration, NaiveDate};
7use num_traits::identities::Zero;
8use polars::prelude::*;
9#[cfg(not(target_arch = "wasm32"))]
10use rayon::iter::IntoParallelIterator;
11#[cfg(not(target_arch = "wasm32"))]
12use rayon::iter::ParallelIterator;
13use serde::ser::{SerializeMap, SerializeSeq};
14use serde_json::{Number as JsonNumber, Value as JsonValue};
15use std::collections::HashMap;
16
17#[derive(Clone)]
18pub enum Value {
19    /// Null value
20    Null,
21    /// Boolean value
22    Bool(bool),
23    /// Integer value (i64)
24    Int(i64),
25    /// Big integer value (arbitrary precision)
26    BigInt(num_bigint::BigInt),
27    /// Float value (f64)
28    Float(f64),
29    /// String value
30    String(String),
31    /// Array of values
32    Array(Vec<Value>),
33    /// Object (key-value pairs)
34    Object(HashMap<String, Value>),
35    /// Polars `DataFrame`
36    DataFrame(polars::prelude::DataFrame),
37    /// Polars `LazyFrame` (for lazy evaluation)
38    LazyFrame(Box<polars::prelude::LazyFrame>),
39    /// Polars Series (column)
40    Series(polars::prelude::Series),
41}
42
43impl Value {
44    /// Create a new null value
45    #[must_use]
46    pub fn null() -> Self {
47        Value::Null
48    }
49
50    /// Create a new boolean value
51    #[must_use]
52    pub fn bool(b: bool) -> Self {
53        Value::Bool(b)
54    }
55
56    /// Create a new integer value
57    #[must_use]
58    pub fn int(i: i64) -> Self {
59        Value::Int(i)
60    }
61
62    /// Create a new big integer value
63    #[must_use]
64    pub fn bigint(i: num_bigint::BigInt) -> Self {
65        Value::BigInt(i)
66    }
67
68    /// Create a new float value
69    #[must_use]
70    pub fn float(f: f64) -> Self {
71        Value::Float(f)
72    }
73
74    /// Create a new string value
75    pub fn string(s: impl Into<String>) -> Self {
76        Value::String(s.into())
77    }
78
79    /// Create a new array value
80    #[must_use]
81    pub fn array(arr: Vec<Value>) -> Self {
82        Value::Array(arr)
83    }
84
85    /// Create a new object value
86    #[must_use]
87    pub fn object(obj: HashMap<String, Value>) -> Self {
88        Value::Object(obj)
89    }
90
91    /// Create a new `DataFrame` value
92    #[must_use]
93    pub fn dataframe(df: polars::prelude::DataFrame) -> Self {
94        Value::DataFrame(df)
95    }
96
97    /// Create a new `LazyFrame` value
98    #[must_use]
99    pub fn lazy_frame(lf: polars::prelude::LazyFrame) -> Self {
100        Value::LazyFrame(Box::new(lf))
101    }
102
103    /// Create a new Series value
104    #[must_use]
105    pub fn series(s: polars::prelude::Series) -> Self {
106        Value::Series(s)
107    }
108}
109
110impl std::fmt::Debug for Value {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        match self {
113            Value::Null => write!(f, "Null"),
114            Value::Bool(b) => f.debug_tuple("Bool").field(b).finish(),
115            Value::Int(i) => f.debug_tuple("Int").field(i).finish(),
116            Value::BigInt(bi) => f.debug_tuple("BigInt").field(bi).finish(),
117            Value::Float(fl) => f.debug_tuple("Float").field(fl).finish(),
118            Value::String(s) => f.debug_tuple("String").field(s).finish(),
119            Value::Array(arr) => f.debug_tuple("Array").field(arr).finish(),
120            Value::Object(obj) => f.debug_tuple("Object").field(obj).finish(),
121            Value::DataFrame(df) => f
122                .debug_tuple("DataFrame")
123                .field(&format!("{}x{} DataFrame", df.height(), df.width()))
124                .finish(),
125            Value::LazyFrame(_) => f.debug_tuple("LazyFrame").field(&"<LazyFrame>").finish(),
126            Value::Series(s) => f
127                .debug_tuple("Series")
128                .field(&format!("{} Series[{}]", s.len(), s.dtype()))
129                .finish(),
130        }
131    }
132}
133
134impl Value {
135    /// Check if value is null
136    #[must_use]
137    pub fn is_null(&self) -> bool {
138        matches!(self, Value::Null)
139    }
140
141    /// Check if value is a `DataFrame`
142    #[must_use]
143    pub fn is_dataframe(&self) -> bool {
144        matches!(self, Value::DataFrame(_))
145    }
146
147    /// Check if value is a `LazyFrame`
148    #[must_use]
149    pub fn is_lazy_frame(&self) -> bool {
150        matches!(self, Value::LazyFrame(_))
151    }
152
153    /// Check if value is a Series
154    #[must_use]
155    pub fn is_series(&self) -> bool {
156        matches!(self, Value::Series(_))
157    }
158
159    /// Get the type name of this value
160    #[must_use]
161    pub fn type_name(&self) -> &'static str {
162        match self {
163            Value::Null => "null",
164            Value::Bool(_) => "boolean",
165            Value::Int(_) => "integer",
166            Value::BigInt(_) => "biginteger",
167            Value::Float(_) => "float",
168            Value::String(_) => "string",
169            Value::Array(_) => "array",
170            Value::Object(_) => "object",
171            Value::DataFrame(_) => "dataframe",
172            Value::LazyFrame(_) => "lazyframe",
173            Value::Series(_) => "series",
174        }
175    }
176
177    /// Convert to JSON value (for jaq compatibility)
178    pub fn to_json(&self) -> crate::Result<JsonValue> {
179        match self {
180            Value::Null => Ok(JsonValue::Null),
181            Value::Bool(b) => Ok(JsonValue::Bool(*b)),
182            Value::Int(i) => Ok(JsonValue::Number(JsonNumber::from(*i))),
183            Value::BigInt(bi) => Ok(JsonValue::String(bi.to_string())),
184            Value::Float(f) => JsonNumber::from_f64(*f)
185                .map(JsonValue::Number)
186                .ok_or_else(|| crate::error::operation_error(format!("Invalid float: {f}"))),
187            Value::String(s) => Ok(JsonValue::String(s.clone())),
188            Value::Array(arr) => {
189                let json_arr: crate::Result<Vec<JsonValue>> =
190                    arr.iter().map(Value::to_json).collect();
191                Ok(JsonValue::Array(json_arr?))
192            }
193            Value::Object(obj) => {
194                let json_obj: crate::Result<serde_json::Map<String, JsonValue>> = obj
195                    .iter()
196                    .map(|(k, v)| v.to_json().map(|json_v| (k.clone(), json_v)))
197                    .collect();
198                Ok(JsonValue::Object(json_obj?))
199            }
200            Value::DataFrame(df) => {
201                // Convert DataFrame to array of objects
202                Self::dataframe_to_json_array(df)
203            }
204            Value::LazyFrame(lf) => {
205                // Collect LazyFrame first, then convert
206                let df = lf.clone().collect().map_err(|e| {
207                    crate::error::operation_error(format!("LazyFrame collect error: {e}"))
208                })?;
209                Self::dataframe_to_json_array(&df)
210            }
211            Value::Series(s) => {
212                // Convert Series to array
213                Self::series_to_json_array(s)
214            }
215        }
216    }
217
218    /// Helper to convert `DataFrame` to JSON array
219    #[cfg(not(target_arch = "wasm32"))]
220    fn dataframe_to_json_array(df: &polars::prelude::DataFrame) -> crate::Result<JsonValue> {
221        let height = df.height();
222        let columns = df.get_column_names();
223        let num_cols = columns.len();
224
225        // Cache column references to avoid repeated lookups
226        let series_vec: crate::Result<Vec<_>> = columns
227            .iter()
228            .map(|col_name| {
229                df.column(col_name)
230                    .map_err(|e| crate::error::operation_error(format!("Column access error: {e}")))
231            })
232            .collect();
233        let series_vec = series_vec?;
234
235        // Use parallel processing for large datasets (>10k rows) when not on wasm
236        let rows: crate::Result<Vec<_>> = {
237            #[cfg(not(target_arch = "wasm32"))]
238            {
239                if height > 10_000 {
240                    (0..height)
241                        .into_par_iter()
242                        .map(|row_idx| {
243                            let mut row_obj = serde_json::Map::with_capacity(num_cols);
244
245                            for (col_idx, col_name) in columns.iter().enumerate() {
246                                let series = &series_vec[col_idx];
247                                let value = Self::series_value_to_json(
248                                    series.as_materialized_series(),
249                                    row_idx,
250                                )?;
251                                row_obj.insert((*col_name).to_string(), value);
252                            }
253
254                            Ok(JsonValue::Object(row_obj))
255                        })
256                        .collect()
257                } else {
258                    (0..height)
259                        .map(|row_idx| {
260                            let mut row_obj = serde_json::Map::with_capacity(num_cols);
261
262                            for (col_idx, col_name) in columns.iter().enumerate() {
263                                let series = &series_vec[col_idx];
264                                let value = Self::series_value_to_json(
265                                    series.as_materialized_series(),
266                                    row_idx,
267                                )?;
268                                row_obj.insert((*col_name).to_string(), value);
269                            }
270
271                            Ok(JsonValue::Object(row_obj))
272                        })
273                        .collect()
274                }
275            }
276            #[cfg(target_arch = "wasm32")]
277            {
278                (0..height)
279                    .map(|row_idx| {
280                        let mut row_obj = serde_json::Map::with_capacity(num_cols);
281
282                        for (col_idx, col_name) in columns.iter().enumerate() {
283                            let series = &series_vec[col_idx];
284                            let value = Self::series_value_to_json(
285                                series.as_materialized_series(),
286                                row_idx,
287                            )?;
288                            row_obj.insert((*col_name).to_string(), value);
289                        }
290
291                        Ok(JsonValue::Object(row_obj))
292                    })
293                    .collect()
294            }
295        };
296
297        Ok(JsonValue::Array(rows?))
298    }
299
300    /// Helper to convert `DataFrame` to JSON array (WASM fallback)
301    #[cfg(target_arch = "wasm32")]
302    fn dataframe_to_json_array(df: &polars::prelude::DataFrame) -> crate::Result<JsonValue> {
303        let height = df.height();
304        let columns = df.get_column_names();
305        let num_cols = columns.len();
306
307        // Cache column references to avoid repeated lookups
308        let series_vec: crate::Result<Vec<_>> = columns
309            .iter()
310            .map(|col_name| {
311                df.column(col_name)
312                    .map_err(|e| crate::error::operation_error(format!("Column access error: {e}")))
313            })
314            .collect();
315        let series_vec = series_vec?;
316
317        let rows: crate::Result<Vec<_>> = (0..height)
318            .map(|row_idx| {
319                let mut row_obj = serde_json::Map::with_capacity(num_cols);
320
321                for (col_idx, col_name) in columns.iter().enumerate() {
322                    let series = &series_vec[col_idx];
323                    let value =
324                        Self::series_value_to_json(series.as_materialized_series(), row_idx)?;
325                    row_obj.insert((*col_name).to_string(), value);
326                }
327
328                Ok(JsonValue::Object(row_obj))
329            })
330            .collect();
331
332        Ok(JsonValue::Array(rows?))
333    }
334
335    /// Helper to convert Series to JSON array
336    fn series_to_json_array(series: &polars::prelude::Series) -> crate::Result<JsonValue> {
337        let len = series.len();
338        let mut values = Vec::with_capacity(len);
339
340        for i in 0..len {
341            let value = Self::series_value_to_json(series, i)?;
342            values.push(value);
343        }
344
345        Ok(JsonValue::Array(values))
346    }
347
348    /// Helper to convert a single Series value to JSON
349    fn series_value_to_json(
350        series: &polars::prelude::Series,
351        index: usize,
352    ) -> crate::Result<JsonValue> {
353        use polars::prelude::DataType;
354
355        if series.is_null().get(index).unwrap_or(false) {
356            return Ok(JsonValue::Null);
357        }
358
359        match series.dtype() {
360            DataType::Boolean => {
361                let val = series
362                    .bool()
363                    .map_err(|e| {
364                        crate::error::operation_error(format!("Boolean access error: {e}"))
365                    })?
366                    .get(index);
367                Ok(JsonValue::Bool(val.unwrap_or(false)))
368            }
369            DataType::Int8 | DataType::Int16 | DataType::Int32 | DataType::Int64 => {
370                let val = series
371                    .i64()
372                    .map_err(|e| crate::error::operation_error(format!("Int access error: {e}")))?
373                    .get(index);
374                Ok(JsonValue::Number(JsonNumber::from(val.unwrap_or(0))))
375            }
376            DataType::UInt8 | DataType::UInt16 | DataType::UInt32 | DataType::UInt64 => {
377                let val = series
378                    .u64()
379                    .map_err(|e| crate::error::operation_error(format!("UInt access error: {e}")))?
380                    .get(index);
381                Ok(JsonValue::Number(JsonNumber::from(val.unwrap_or(0))))
382            }
383            DataType::Float32 | DataType::Float64 => {
384                let val = series
385                    .f64()
386                    .map_err(|e| crate::error::operation_error(format!("Float access error: {e}")))?
387                    .get(index);
388                JsonNumber::from_f64(val.unwrap_or(0.0))
389                    .map(JsonValue::Number)
390                    .ok_or_else(|| crate::error::operation_error("Invalid float value"))
391            }
392            DataType::String => {
393                let val = series
394                    .str()
395                    .map_err(|e| {
396                        crate::error::operation_error(format!("String access error: {e}"))
397                    })?
398                    .get(index);
399                Ok(JsonValue::String(val.unwrap_or("").to_string()))
400            }
401            DataType::Binary => {
402                let val = series
403                    .binary()
404                    .map_err(|e| {
405                        crate::error::operation_error(format!("Binary access error: {e}"))
406                    })?
407                    .get(index);
408                // Convert binary to base64 string for JSON representation
409                if let Some(bytes) = val {
410                    use base64::{engine::general_purpose, Engine as _};
411                    Ok(JsonValue::String(general_purpose::STANDARD.encode(bytes)))
412                } else {
413                    Ok(JsonValue::Null)
414                }
415            }
416            DataType::Date => {
417                let val = series
418                    .date()
419                    .map_err(|e| crate::error::operation_error(format!("Date access error: {e}")))?
420                    .phys
421                    .get(index);
422                if let Some(days) = val {
423                    let epoch = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
424                    let actual_date = epoch + chrono::Duration::days(i64::from(days));
425                    Ok(JsonValue::String(
426                        actual_date.format("%Y-%m-%d").to_string(),
427                    ))
428                } else {
429                    Ok(JsonValue::Null)
430                }
431            }
432            DataType::Datetime(_, _) => {
433                let val = series
434                    .datetime()
435                    .map_err(|e| {
436                        crate::error::operation_error(format!("Datetime access error: {e}"))
437                    })?
438                    .phys
439                    .get(index);
440                if let Some(ns) = val {
441                    let secs = ns / 1_000_000_000;
442                    #[allow(clippy::cast_sign_loss)]
443                    let nsecs = (ns % 1_000_000_000) as u32;
444                    let dt = chrono::DateTime::from_timestamp(secs, nsecs)
445                        .unwrap()
446                        .naive_utc();
447                    Ok(JsonValue::String(
448                        dt.format("%Y-%m-%d %H:%M:%S%.f").to_string(),
449                    ))
450                } else {
451                    Ok(JsonValue::Null)
452                }
453            }
454            _ => Err(crate::error::operation_error(format!(
455                "Unsupported series type: {:?}",
456                series.dtype()
457            ))),
458        }
459    }
460
461    /// Convert from JSON value
462    pub fn from_json(json: JsonValue) -> Self {
463        match json {
464            JsonValue::Null => Value::Null,
465            JsonValue::Bool(b) => Value::Bool(b),
466            JsonValue::Number(n) => {
467                if let Some(i) = n.as_i64() {
468                    Value::Int(i)
469                } else if let Some(f) = n.as_f64() {
470                    Value::Float(f)
471                } else {
472                    Value::Null // Fallback for invalid numbers
473                }
474            }
475            JsonValue::String(s) => {
476                // Try to parse as BigInt if it's a large number
477                if let Ok(bi) = s.parse::<num_bigint::BigInt>() {
478                    return Value::BigInt(bi);
479                }
480                Value::String(s)
481            }
482            JsonValue::Array(arr) => {
483                let values = arr.into_iter().map(Value::from_json).collect();
484                Value::Array(values)
485            }
486            JsonValue::Object(obj) => {
487                let map = obj
488                    .into_iter()
489                    .map(|(k, v)| (k, Value::from_json(v)))
490                    .collect();
491                Value::Object(map)
492            }
493        }
494    }
495
496    /// Convert to `DataFrame` if possible
497    pub fn to_dataframe(&self) -> crate::Result<polars::prelude::DataFrame> {
498        match self {
499            Value::DataFrame(df) => Ok(df.clone()),
500            Value::LazyFrame(lf) => lf.clone().collect().map_err(|e| {
501                crate::error::operation_error(format!("LazyFrame collect error: {e}"))
502            }),
503            Value::Array(arr) => {
504                // Try to convert array of objects to DataFrame
505                if arr.is_empty() {
506                    return Ok(DataFrame::empty());
507                }
508
509                // Check if all elements are objects with the same keys
510                let Value::Object(first_obj) = &arr[0] else {
511                    return Err(crate::error::operation_error(
512                        "Cannot convert array to DataFrame: not all elements are objects",
513                    ));
514                };
515
516                let columns: Vec<String> = first_obj.keys().cloned().collect();
517                let num_rows = arr.len();
518                let num_cols = columns.len();
519
520                // Directly allocate column vectors without HashMap intermediate
521                // This reduces memory allocations and improves cache locality
522                let mut column_data: Vec<Vec<AnyValue>> = Vec::with_capacity(num_cols);
523                for _ in 0..num_cols {
524                    column_data.push(Vec::with_capacity(num_rows));
525                }
526
527                // Single-pass row processing - columnar collection
528                for value in arr {
529                    match value {
530                        Value::Object(obj) => {
531                            for (col_idx, col) in columns.iter().enumerate() {
532                                let val = obj.get(col).unwrap_or(&Value::Null);
533                                let any_val = Self::value_to_any_value(val)?;
534                                // Direct indexing - we know col_idx is valid
535                                column_data[col_idx].push(any_val);
536                            }
537                        }
538                        _ => {
539                            return Err(crate::error::operation_error(
540                                "Cannot convert array to DataFrame: not all elements are objects",
541                            ));
542                        }
543                    }
544                }
545
546                // Create Series directly from column vectors
547                let mut series_vec = Vec::with_capacity(num_cols);
548                for (col_idx, col_name) in columns.into_iter().enumerate() {
549                    let values = std::mem::take(&mut column_data[col_idx]);
550                    let series = Series::new(col_name.into(), values);
551                    series_vec.push(series.into());
552                }
553
554                DataFrame::new(series_vec).map_err(|e| {
555                    crate::error::operation_error(format!("DataFrame creation error: {e}"))
556                })
557            }
558            _ => Err(crate::error::operation_error(format!(
559                "Cannot convert {} to DataFrame",
560                self.type_name()
561            ))),
562        }
563    }
564
565    /// Helper to convert Value to `AnyValue` for Polars
566    fn value_to_any_value(value: &Value) -> crate::Result<polars::prelude::AnyValue<'_>> {
567        match value {
568            Value::Null => Ok(AnyValue::Null),
569            Value::Bool(b) => Ok(AnyValue::Boolean(*b)),
570            Value::Int(i) => Ok(AnyValue::Int64(*i)),
571            Value::BigInt(_) => Err(crate::error::operation_error(
572                "Cannot convert BigInt to AnyValue",
573            )),
574            Value::Float(f) => Ok(AnyValue::Float64(*f)),
575            Value::String(s) => Ok(AnyValue::String(s)),
576            _ => Err(crate::error::operation_error(format!(
577                "Cannot convert {} to AnyValue",
578                value.type_name()
579            ))),
580        }
581    }
582
583    /// Get length for array-like values
584    #[must_use]
585    pub fn len(&self) -> Option<usize> {
586        match self {
587            Value::Array(arr) => Some(arr.len()),
588            Value::String(s) => Some(s.len()),
589            Value::DataFrame(df) => Some(df.height()),
590            Value::Series(s) => Some(s.len()),
591            _ => None,
592        }
593    }
594
595    /// Check if value is empty
596    #[must_use]
597    pub fn is_empty(&self) -> bool {
598        self.len() == Some(0)
599    }
600
601    /// Index into array-like values
602    pub fn index(&self, idx: i64) -> crate::Result<Value> {
603        match self {
604            Value::Array(arr) => {
605                #[allow(clippy::cast_possible_wrap)]
606                let len = arr.len() as i64;
607                let index = if idx < 0 { len + idx } else { idx };
608
609                if index >= 0 && index < len {
610                    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
611                    Ok(arr[index as usize].clone())
612                } else {
613                    Ok(Value::Null)
614                }
615            }
616            Value::String(s) => {
617                // Optimize string indexing by avoiding full char collection when possible
618                // For ASCII strings, we can use byte indexing directly
619                let byte_len = s.len();
620
621                // Fast path for empty strings
622                if byte_len == 0 {
623                    return Ok(Value::Null);
624                }
625
626                // Check if string is ASCII for fast path
627                if s.is_ascii() {
628                    let bytes = s.as_bytes();
629                    #[allow(clippy::cast_possible_wrap)]
630                    let len = bytes.len() as i64;
631                    let index = if idx < 0 { len + idx } else { idx };
632
633                    if index >= 0 && index < len {
634                        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
635                        let ch = bytes[index as usize] as char;
636                        Ok(Value::String(ch.to_string()))
637                    } else {
638                        Ok(Value::Null)
639                    }
640                } else {
641                    // Slow path for non-ASCII: need proper UTF-8 char handling
642                    // Use iterator to avoid collecting all chars if we just need one
643                    #[allow(clippy::cast_possible_wrap)]
644                    let char_count = s.chars().count() as i64;
645                    let index = if idx < 0 { char_count + idx } else { idx };
646
647                    if index >= 0 && index < char_count {
648                        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
649                        if let Some(ch) = s.chars().nth(index as usize) {
650                            Ok(Value::String(ch.to_string()))
651                        } else {
652                            Ok(Value::Null)
653                        }
654                    } else {
655                        Ok(Value::Null)
656                    }
657                }
658            }
659            Value::DataFrame(df) => {
660                #[allow(clippy::cast_possible_wrap)]
661                let len = df.height() as i64;
662                let index = if idx < 0 { len + idx } else { idx };
663
664                if index >= 0 && index < len {
665                    // Return a row as an object
666                    let mut row_obj = HashMap::new();
667                    for col_name in df.get_column_names() {
668                        let series = df.column(col_name).map_err(|e| {
669                            crate::error::operation_error(format!("Column access error: {e}"))
670                        })?;
671                        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
672                        let value = Self::series_value_to_json(
673                            series.as_materialized_series(),
674                            index as usize,
675                        )?;
676                        row_obj.insert(col_name.to_string(), Value::from_json(value));
677                    }
678                    Ok(Value::Object(row_obj))
679                } else {
680                    Ok(Value::Null)
681                }
682            }
683            _ => Err(crate::error::operation_error(format!(
684                "Cannot index into {}",
685                self.type_name()
686            ))),
687        }
688    }
689
690    /// Get field from object-like values
691    pub fn field(&self, key: &str) -> crate::Result<Value> {
692        match self {
693            Value::Null => Ok(Value::Null),
694            Value::Object(obj) => Ok(obj.get(key).cloned().unwrap_or(Value::Null)),
695            Value::Array(arr) => {
696                let mut result = Vec::new();
697                for item in arr {
698                    result.push(item.field(key)?);
699                }
700                Ok(Value::Array(result))
701            }
702            Value::DataFrame(df) => {
703                // Return the column as a Series
704                match df.column(key) {
705                    Ok(series) => Ok(Value::Series(series.as_materialized_series().clone())),
706                    Err(_) => Ok(Value::Null),
707                }
708            }
709            _ => Err(crate::error::operation_error(format!(
710                "Cannot access field '{}' on {}",
711                key,
712                self.type_name()
713            ))),
714        }
715    }
716
717    /// Get nested field path from object-like values
718    pub fn field_path(&self, fields: &[&str]) -> crate::Result<Value> {
719        let mut result = self.clone();
720        for &field in fields {
721            result = result.field(field)?;
722        }
723        Ok(result)
724    }
725}
726
727impl PartialEq<&Value> for Value {
728    fn eq(&self, other: &&Value) -> bool {
729        self == *other
730    }
731}
732
733impl PartialEq<Value> for &Value {
734    fn eq(&self, other: &Value) -> bool {
735        *self == other
736    }
737}
738
739impl PartialEq for Value {
740    fn eq(&self, other: &Self) -> bool {
741        match (self, other) {
742            (Value::Null, Value::Null) => true,
743            (Value::Bool(a), Value::Bool(b)) => a == b,
744            (Value::Int(a), Value::Int(b)) => a == b,
745            (Value::BigInt(a), Value::BigInt(b)) => a == b,
746            (Value::Float(a), Value::Float(b)) => a == b,
747            (Value::String(a), Value::String(b)) => a == b,
748            (Value::Array(a), Value::Array(b)) => a == b,
749            (Value::Object(a), Value::Object(b)) => a == b,
750            // For DataFrames, no comparison implemented
751            (Value::DataFrame(_), Value::DataFrame(_)) => false,
752            // Series comparison (content comparison not implemented)
753            (Value::Series(_), Value::Series(_)) => false,
754            // Cross-type numeric comparisons
755            #[allow(clippy::cast_precision_loss)]
756            (Value::Int(a), Value::Float(b)) => *a as f64 == *b,
757            #[allow(clippy::cast_precision_loss)]
758            (Value::Float(a), Value::Int(b)) => *a == *b as f64,
759            #[cfg(not(target_arch = "wasm32"))]
760            (Value::Int(a), Value::BigInt(b)) => num_bigint::BigInt::from(*a) == *b,
761            #[cfg(not(target_arch = "wasm32"))]
762            (Value::BigInt(a), Value::Int(b)) => *a == num_bigint::BigInt::from(*b),
763            // Note: BigInt vs Float comparison is not implemented for precision reasons
764            _ => false,
765        }
766    }
767}
768
769impl std::fmt::Display for Value {
770    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
771        match self {
772            Value::Null => write!(f, "null"),
773            Value::Bool(b) => write!(f, "{b}"),
774            Value::Int(i) => write!(f, "{i}"),
775            Value::BigInt(bi) => write!(f, "{bi}"),
776            Value::Float(fl) => write!(f, "{fl}"),
777            Value::String(s) => write!(f, "\"{s}\""),
778            Value::Array(arr) => {
779                write!(f, "[")?;
780                for (i, item) in arr.iter().enumerate() {
781                    if i > 0 {
782                        write!(f, ", ")?;
783                    }
784                    write!(f, "{item}")?;
785                }
786                write!(f, "]")
787            }
788            Value::Object(obj) => {
789                write!(f, "{{")?;
790                for (i, (key, value)) in obj.iter().enumerate() {
791                    if i > 0 {
792                        write!(f, ", ")?;
793                    }
794                    write!(f, "\"{key}\": {value}")?;
795                }
796                write!(f, "}}")
797            }
798            Value::DataFrame(df) => {
799                write!(
800                    f,
801                    "DataFrame({} rows × {} columns)",
802                    df.height(),
803                    df.width()
804                )
805            }
806            Value::LazyFrame(_) => write!(f, "LazyFrame"),
807            Value::Series(s) => write!(f, "Series[{}]({} values)", s.dtype(), s.len()),
808        }
809    }
810}
811
812impl serde::Serialize for Value {
813    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
814    where
815        S: serde::Serializer,
816    {
817        match self {
818            Value::Null => serializer.serialize_none(),
819            Value::Bool(b) => serializer.serialize_bool(*b),
820            Value::Int(i) => serializer.serialize_i64(*i),
821            Value::BigInt(bi) => bi.serialize(serializer),
822            Value::Float(f) => serializer.serialize_f64(*f),
823            Value::String(s) => serializer.serialize_str(s),
824            Value::Array(arr) => {
825                let mut seq = serializer.serialize_seq(Some(arr.len()))?;
826                for item in arr {
827                    seq.serialize_element(item)?;
828                }
829                seq.end()
830            }
831            Value::Object(obj) => {
832                let mut map = serializer.serialize_map(Some(obj.len()))?;
833                for (key, value) in obj {
834                    map.serialize_entry(key, value)?;
835                }
836                map.end()
837            }
838            Value::DataFrame(df) => {
839                // Serialize DataFrame as an object with metadata
840                let mut map = serializer.serialize_map(Some(3))?;
841                map.serialize_entry("type", "DataFrame")?;
842                map.serialize_entry("shape", &vec![df.height(), df.width()])?;
843                map.serialize_entry("columns", &df.get_column_names())?;
844                map.end()
845            }
846            Value::LazyFrame(_) => {
847                let mut map = serializer.serialize_map(Some(1))?;
848                map.serialize_entry("type", "LazyFrame")?;
849                map.end()
850            }
851            Value::Series(s) => {
852                // Serialize Series as an object with metadata
853                let mut map = serializer.serialize_map(Some(3))?;
854                map.serialize_entry("type", "Series")?;
855                map.serialize_entry("name", s.name())?;
856                map.serialize_entry("dtype", &s.dtype().to_string())?;
857                map.end()
858            }
859        }
860    }
861}
862
863/// Convert `AnyValue` to `Value`
864#[must_use]
865#[allow(clippy::needless_pass_by_value)]
866pub fn value_from_any_value(av: polars::prelude::AnyValue<'_>) -> Option<Value> {
867    match av {
868        AnyValue::Null => Some(Value::Null),
869        AnyValue::Boolean(b) => Some(Value::Bool(b)),
870        AnyValue::String(s) => Some(Value::String(s.to_string())),
871        AnyValue::Int8(i) => Some(Value::Int(i64::from(i))),
872        AnyValue::Int16(i) => Some(Value::Int(i64::from(i))),
873        AnyValue::Int32(i) => Some(Value::Int(i64::from(i))),
874        AnyValue::Int64(i) => Some(Value::Int(i)),
875        AnyValue::UInt8(i) => Some(Value::Int(i64::from(i))),
876        AnyValue::UInt16(i) => Some(Value::Int(i64::from(i))),
877        AnyValue::UInt32(i) => Some(Value::Int(i64::from(i))),
878        #[allow(clippy::cast_possible_wrap)]
879        AnyValue::UInt64(i) => Some(Value::Int(i as i64)),
880        AnyValue::Float32(f) => Some(Value::Float(f64::from(f))),
881        AnyValue::Float64(f) => Some(Value::Float(f)),
882        _ => None,
883    }
884}
885
886/// Convert a `DataFrame` row to a `Value::Object`
887pub fn df_row_to_value(df: &polars::prelude::DataFrame, row_idx: usize) -> crate::Result<Value> {
888    let mut obj = HashMap::new();
889
890    for col_name in df.get_column_names() {
891        let series = df
892            .column(col_name)
893            .map_err(|e| crate::error::operation_error(format!("Failed to get column: {e}")))?;
894        let value = series_value_at(series.as_materialized_series(), row_idx)?;
895        obj.insert(col_name.to_string(), value);
896    }
897
898    Ok(Value::Object(obj))
899}
900
901/// Helper to get value at index from Series
902fn series_value_at(series: &polars::prelude::Series, idx: usize) -> crate::Result<Value> {
903    if idx >= series.len() {
904        return Ok(Value::Null);
905    }
906
907    if series.is_null().get(idx).unwrap_or(false) {
908        return Ok(Value::Null);
909    }
910
911    match series.dtype() {
912        DataType::Boolean => {
913            let val = series
914                .bool()
915                .map_err(|e| crate::error::operation_error(format!("Boolean access error: {e}")))?
916                .get(idx);
917            Ok(Value::Bool(val.unwrap_or(false)))
918        }
919        DataType::Int8 | DataType::Int16 | DataType::Int32 | DataType::Int64 => {
920            let val = series
921                .i64()
922                .map_err(|e| crate::error::operation_error(format!("Int access error: {e}")))?
923                .get(idx);
924            Ok(Value::Int(val.unwrap_or(0)))
925        }
926        DataType::UInt8 | DataType::UInt16 | DataType::UInt32 | DataType::UInt64 => {
927            let val = series
928                .u64()
929                .map_err(|e| crate::error::operation_error(format!("UInt access error: {e}")))?
930                .get(idx);
931            #[allow(clippy::cast_possible_wrap)]
932            Ok(Value::Int(val.unwrap_or(0) as i64))
933        }
934        DataType::Float32 | DataType::Float64 => {
935            let val = series
936                .f64()
937                .map_err(|e| crate::error::operation_error(format!("Float access error: {e}")))?
938                .get(idx);
939            Ok(Value::Float(val.unwrap_or(0.0)))
940        }
941        DataType::String => {
942            let val = series
943                .str()
944                .map_err(|e| crate::error::operation_error(format!("String access error: {e}")))?
945                .get(idx);
946            Ok(Value::String(val.unwrap_or("").to_string()))
947        }
948        DataType::Date => {
949            let val = series
950                .date()
951                .map_err(|e| crate::error::operation_error(format!("Date access error: {e}")))?
952                .phys
953                .get(idx);
954            if let Some(days) = val {
955                let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
956                let actual_date = epoch + Duration::days(i64::from(days));
957                Ok(Value::String(actual_date.format("%Y-%m-%d").to_string()))
958            } else {
959                Ok(Value::Null)
960            }
961        }
962        DataType::Datetime(_, _) => {
963            let val = series
964                .datetime()
965                .map_err(|e| crate::error::operation_error(format!("Datetime access error: {e}")))?
966                .phys
967                .get(idx);
968            if let Some(ns) = val {
969                let secs = ns / 1_000_000_000;
970                #[allow(clippy::cast_sign_loss)]
971                let nsecs = (ns % 1_000_000_000) as u32;
972                let dt = DateTime::from_timestamp(secs, nsecs).unwrap().naive_utc();
973                Ok(Value::String(dt.format("%Y-%m-%d %H:%M:%S%.f").to_string()))
974            } else {
975                Ok(Value::Null)
976            }
977        }
978        _ => Ok(Value::Null), // For unsupported types, return null
979    }
980}
981
982/// Check if a value is truthy (non-null, non-empty, non-zero)
983#[must_use]
984pub fn is_truthy(v: &Value) -> bool {
985    match v {
986        Value::Null => false,
987        Value::Bool(b) => *b,
988        Value::Int(i) => *i != 0,
989        Value::BigInt(bi) => !bi.is_zero(),
990        Value::Float(f) => *f != 0.0 && !f.is_nan(),
991        Value::String(s) => !s.is_empty(),
992        Value::Array(a) => !a.is_empty(),
993        Value::Object(o) => !o.is_empty(),
994        Value::DataFrame(df) => df.height() > 0,
995        Value::Series(s) => !s.is_empty(),
996        Value::LazyFrame(_) => true, // Assume lazy frames are truthy if present
997    }
998}
999
1000#[cfg(test)]
1001mod tests {
1002    use super::*;
1003    use num_bigint::BigInt;
1004
1005    use serde_json::json;
1006
1007    #[test]
1008    fn test_value_creation() {
1009        assert_eq!(Value::null(), Value::Null);
1010        assert_eq!(Value::bool(true), Value::Bool(true));
1011        assert_eq!(Value::bool(false), Value::Bool(false));
1012        assert_eq!(Value::int(42), Value::Int(42));
1013        assert_eq!(Value::int(-1), Value::Int(-1));
1014        assert_eq!(
1015            Value::bigint(BigInt::from(123456789012345678901234567890i128)),
1016            Value::BigInt(BigInt::from(123456789012345678901234567890i128))
1017        );
1018        assert_eq!(
1019            Value::float(std::f64::consts::PI),
1020            Value::Float(std::f64::consts::PI)
1021        );
1022        assert_eq!(Value::float(-2.5), Value::Float(-2.5));
1023        assert_eq!(Value::string("hello"), Value::String("hello".to_string()));
1024        assert_eq!(Value::string(""), Value::String("".to_string()));
1025
1026        let arr = vec![Value::int(1), Value::int(2)];
1027        assert_eq!(Value::array(arr.clone()), Value::Array(arr));
1028
1029        let obj = HashMap::from([("key".to_string(), Value::string("value"))]);
1030        assert_eq!(Value::object(obj.clone()), Value::Object(obj));
1031
1032        let df = DataFrame::new(vec![Series::new("a".into(), vec![1, 2, 3]).into()]).unwrap();
1033        let df_val = Value::dataframe(df.clone());
1034        assert!(df_val.is_dataframe());
1035
1036        let lf = df.clone().lazy();
1037        let lf_val = Value::lazy_frame(lf.clone());
1038        assert!(lf_val.is_lazy_frame());
1039
1040        let series = Series::new("test".into(), vec![1, 2, 3]);
1041        let series_val = Value::series(series.clone());
1042        assert!(series_val.is_series());
1043    }
1044
1045    #[test]
1046    fn test_value_is_methods() {
1047        let df = DataFrame::new(vec![Series::new("a".into(), vec![1, 2, 3]).into()]).unwrap();
1048        let lf = df.clone().lazy();
1049        let series = Series::new("test".into(), vec![1, 2, 3]);
1050
1051        assert!(Value::Null.is_null());
1052        assert!(!Value::int(1).is_null());
1053
1054        assert!(Value::dataframe(df.clone()).is_dataframe());
1055        assert!(!Value::int(1).is_dataframe());
1056
1057        assert!(Value::lazy_frame(lf.clone()).is_lazy_frame());
1058        assert!(!Value::int(1).is_lazy_frame());
1059
1060        assert!(Value::series(series.clone()).is_series());
1061        assert!(!Value::int(1).is_series());
1062
1063        assert!(is_truthy(&Value::Bool(true)));
1064        assert!(!is_truthy(&Value::Bool(false)));
1065        assert!(is_truthy(&Value::int(1)));
1066        assert!(!is_truthy(&Value::int(0)));
1067        assert!(is_truthy(&Value::string("hello")));
1068        assert!(!is_truthy(&Value::string("")));
1069        assert!(is_truthy(&Value::array(vec![Value::int(1)])));
1070        assert!(!is_truthy(&Value::array(vec![])));
1071        assert!(is_truthy(&Value::object(HashMap::from([(
1072            "k".to_string(),
1073            Value::int(1)
1074        )]))));
1075        assert!(!is_truthy(&Value::object(HashMap::new())));
1076        assert!(is_truthy(&Value::dataframe(df)));
1077        assert!(is_truthy(&Value::series(series)));
1078        assert!(is_truthy(&Value::lazy_frame(lf)));
1079    }
1080
1081    #[test]
1082    fn test_value_len_and_empty() {
1083        let df = DataFrame::new(vec![Series::new("a".into(), vec![1, 2, 3]).into()]).unwrap();
1084        let empty_df = DataFrame::empty();
1085        let series = Series::new("test".into(), vec![1, 2, 3]);
1086        let empty_series = Series::new("empty".into(), Vec::<i32>::new());
1087        let lf = df.clone().lazy();
1088
1089        assert_eq!(Value::Null.len(), None);
1090        assert_eq!(Value::int(1).len(), None);
1091        assert_eq!(Value::string("hello").len(), Some(5));
1092        assert_eq!(Value::string("").len(), Some(0));
1093        assert_eq!(
1094            Value::array(vec![Value::int(1), Value::int(2)]).len(),
1095            Some(2)
1096        );
1097        assert_eq!(Value::array(vec![]).len(), Some(0));
1098        assert_eq!(Value::dataframe(df.clone()).len(), Some(3));
1099        assert_eq!(Value::dataframe(empty_df.clone()).len(), Some(0));
1100        assert_eq!(Value::series(series.clone()).len(), Some(3));
1101        assert_eq!(Value::series(empty_series.clone()).len(), Some(0));
1102        assert_eq!(Value::lazy_frame(lf.clone()).len(), None);
1103
1104        assert!(Value::string("").is_empty());
1105        assert!(Value::array(vec![]).is_empty());
1106        assert!(Value::dataframe(empty_df).is_empty());
1107        assert!(Value::series(empty_series).is_empty());
1108        assert!(!Value::string("a").is_empty());
1109        assert!(!Value::array(vec![Value::int(1)]).is_empty());
1110        assert!(!Value::dataframe(df).is_empty());
1111        assert!(!Value::series(series).is_empty());
1112        assert!(!Value::lazy_frame(lf).is_empty());
1113        assert!(!Value::Null.is_empty()); // None len means not empty in this context?
1114    }
1115
1116    #[test]
1117    fn test_json_conversion_primitives() {
1118        // Null
1119        let json = Value::Null.to_json().unwrap();
1120        assert_eq!(json, JsonValue::Null);
1121        assert_eq!(Value::from_json(json), Value::Null);
1122
1123        // Bool
1124        let json = Value::bool(true).to_json().unwrap();
1125        assert_eq!(json, JsonValue::Bool(true));
1126        assert_eq!(Value::from_json(json), Value::bool(true));
1127
1128        // Int
1129        let json = Value::int(42).to_json().unwrap();
1130        assert_eq!(json, JsonValue::Number(JsonNumber::from(42)));
1131        assert_eq!(Value::from_json(json), Value::int(42));
1132
1133        // Float
1134        let json = Value::float(std::f64::consts::PI).to_json().unwrap();
1135        assert_eq!(
1136            json,
1137            JsonValue::Number(JsonNumber::from_f64(std::f64::consts::PI).unwrap())
1138        );
1139        assert_eq!(Value::from_json(json), Value::float(std::f64::consts::PI));
1140
1141        // String
1142        let json = Value::string("hello").to_json().unwrap();
1143        assert_eq!(json, JsonValue::String("hello".to_string()));
1144        assert_eq!(Value::from_json(json), Value::string("hello"));
1145
1146        // BigInt
1147        let big = BigInt::from(12345678901234567890u64);
1148        let json = Value::bigint(big.clone()).to_json().unwrap();
1149        assert_eq!(json, JsonValue::String(big.to_string()));
1150        assert_eq!(Value::from_json(json), Value::bigint(big));
1151    }
1152
1153    #[test]
1154    fn test_json_conversion_complex() {
1155        // Array
1156        let arr = Value::array(vec![Value::int(1), Value::string("two"), Value::bool(true)]);
1157        let json = arr.to_json().unwrap();
1158        let expected = json!([1, "two", true]);
1159        assert_eq!(json, expected);
1160        assert_eq!(Value::from_json(json), arr);
1161
1162        // Object
1163        let obj = Value::object(HashMap::from([
1164            ("name".to_string(), Value::string("Alice")),
1165            ("age".to_string(), Value::int(30)),
1166            ("active".to_string(), Value::bool(true)),
1167        ]));
1168        let json = obj.to_json().unwrap();
1169        let expected = json!({
1170            "name": "Alice",
1171            "age": 30,
1172            "active": true
1173        });
1174        assert_eq!(json, expected);
1175        assert_eq!(Value::from_json(json), obj);
1176
1177        // Nested structures
1178        let nested = Value::object(HashMap::from([
1179            (
1180                "data".to_string(),
1181                Value::array(vec![Value::int(1), Value::int(2)]),
1182            ),
1183            (
1184                "meta".to_string(),
1185                Value::object(HashMap::from([("count".to_string(), Value::int(2))])),
1186            ),
1187        ]));
1188        let json = nested.to_json().unwrap();
1189        let expected = json!({
1190            "data": [1, 2],
1191            "meta": {"count": 2}
1192        });
1193        assert_eq!(json, expected);
1194        assert_eq!(Value::from_json(json), nested);
1195    }
1196
1197    #[test]
1198    fn test_json_conversion_polars() {
1199        // DataFrame
1200        let df = DataFrame::new(vec![
1201            Series::new("name".into(), vec!["Alice", "Bob"]).into(),
1202            Series::new("age".into(), vec![30i64, 25i64]).into(),
1203        ])
1204        .unwrap();
1205        let value = Value::dataframe(df.clone());
1206        let json = value.to_json().unwrap();
1207        let expected = json!([
1208            {"name": "Alice", "age": 30},
1209            {"name": "Bob", "age": 25}
1210        ]);
1211        assert_eq!(json, expected);
1212
1213        // LazyFrame
1214        let lf = df.lazy();
1215        let value = Value::lazy_frame(lf);
1216        let json = value.to_json().unwrap();
1217        assert_eq!(json, expected); // Same as DataFrame after collect
1218
1219        // Series
1220        let series = Series::new("ages".into(), vec![30i64, 25i64]);
1221        let value = Value::series(series);
1222        let json = value.to_json().unwrap();
1223        let expected_series = json!([30, 25]);
1224        assert_eq!(json, expected_series);
1225    }
1226
1227    #[test]
1228    fn test_json_conversion_edge_cases() {
1229        // Invalid float (NaN)
1230        let nan_val = Value::float(f64::NAN);
1231        assert!(nan_val.to_json().is_err());
1232
1233        // Very large number as string -> BigInt
1234        let json = JsonValue::String("999999999999999999999999999999".to_string());
1235        let value = Value::from_json(json);
1236        match value {
1237            Value::BigInt(_) => {}
1238            _ => panic!("Expected BigInt"),
1239        }
1240
1241        // Invalid number string -> String
1242        let json = JsonValue::String("not_a_number".to_string());
1243        let value = Value::from_json(json);
1244        assert_eq!(value, Value::string("not_a_number"));
1245    }
1246
1247    #[test]
1248    fn test_indexing_array() {
1249        let arr = Value::array(vec![Value::int(1), Value::int(2), Value::int(3)]);
1250
1251        assert_eq!(arr.index(0).unwrap(), Value::int(1));
1252        assert_eq!(arr.index(1).unwrap(), Value::int(2));
1253        assert_eq!(arr.index(2).unwrap(), Value::int(3));
1254        assert_eq!(arr.index(-1).unwrap(), Value::int(3));
1255        assert_eq!(arr.index(-2).unwrap(), Value::int(2));
1256        assert_eq!(arr.index(-3).unwrap(), Value::int(1));
1257        assert_eq!(arr.index(10).unwrap(), Value::Null);
1258        assert_eq!(arr.index(-10).unwrap(), Value::Null);
1259    }
1260
1261    #[test]
1262    fn test_indexing_string() {
1263        let s = Value::string("hello");
1264
1265        assert_eq!(s.index(0).unwrap(), Value::string("h"));
1266        assert_eq!(s.index(1).unwrap(), Value::string("e"));
1267        assert_eq!(s.index(4).unwrap(), Value::string("o"));
1268        assert_eq!(s.index(-1).unwrap(), Value::string("o"));
1269        assert_eq!(s.index(-2).unwrap(), Value::string("l"));
1270        assert_eq!(s.index(10).unwrap(), Value::Null);
1271        assert_eq!(s.index(-10).unwrap(), Value::Null);
1272    }
1273
1274    #[test]
1275    fn test_indexing_dataframe() {
1276        let df = DataFrame::new(vec![
1277            Series::new("name".into(), vec!["Alice", "Bob"]).into(),
1278            Series::new("age".into(), vec![30i64, 25i64]).into(),
1279        ])
1280        .unwrap();
1281        let val = Value::dataframe(df);
1282
1283        let row = val.index(0).unwrap();
1284        match row {
1285            Value::Object(obj) => {
1286                assert_eq!(obj.get("name"), Some(&Value::string("Alice")));
1287                assert_eq!(obj.get("age"), Some(&Value::int(30)));
1288            }
1289            _ => panic!("Expected object"),
1290        }
1291
1292        let row1 = val.index(1).unwrap();
1293        match row1 {
1294            Value::Object(obj) => {
1295                assert_eq!(obj.get("name"), Some(&Value::string("Bob")));
1296                assert_eq!(obj.get("age"), Some(&Value::int(25)));
1297            }
1298            _ => panic!("Expected object"),
1299        }
1300
1301        let null_row = val.index(10).unwrap();
1302        assert_eq!(null_row, Value::Null);
1303    }
1304
1305    #[test]
1306    fn test_indexing_invalid() {
1307        let obj = Value::object(HashMap::new());
1308        assert!(obj.index(0).is_err());
1309
1310        let null_val = Value::Null;
1311        assert!(null_val.index(0).is_err());
1312    }
1313
1314    #[test]
1315    fn test_field_access() {
1316        let obj = Value::object(HashMap::from([
1317            ("name".to_string(), Value::string("Bob")),
1318            ("age".to_string(), Value::int(25)),
1319            (
1320                "nested".to_string(),
1321                Value::object(HashMap::from([("inner".to_string(), Value::bool(true))])),
1322            ),
1323        ]));
1324
1325        assert_eq!(obj.field("name").unwrap(), Value::string("Bob"));
1326        assert_eq!(obj.field("age").unwrap(), Value::int(25));
1327        assert_eq!(obj.field("missing").unwrap(), Value::Null);
1328
1329        // Nested field access
1330        assert_eq!(
1331            obj.field_path(&["nested", "inner"]).unwrap(),
1332            Value::bool(true)
1333        );
1334        assert_eq!(obj.field_path(&["nested", "missing"]).unwrap(), Value::Null);
1335        assert_eq!(obj.field_path(&["missing", "field"]).unwrap(), Value::Null);
1336    }
1337
1338    #[test]
1339    fn test_field_access_array() {
1340        let arr = Value::array(vec![
1341            Value::object(HashMap::from([(
1342                "name".to_string(),
1343                Value::string("Alice"),
1344            )])),
1345            Value::object(HashMap::from([("name".to_string(), Value::string("Bob"))])),
1346        ]);
1347
1348        let result = arr.field("name").unwrap();
1349        match result {
1350            Value::Array(names) => {
1351                assert_eq!(names.len(), 2);
1352                assert_eq!(names[0], Value::string("Alice"));
1353                assert_eq!(names[1], Value::string("Bob"));
1354            }
1355            _ => panic!("Expected array"),
1356        }
1357    }
1358
1359    #[test]
1360    fn test_field_access_dataframe() {
1361        let df = DataFrame::new(vec![
1362            Series::new("name".into(), vec!["Alice", "Bob"]).into(),
1363            Series::new("age".into(), vec![30i64, 25i64]).into(),
1364        ])
1365        .unwrap();
1366        let val = Value::dataframe(df);
1367
1368        let name_series = val.field("name").unwrap();
1369        match name_series {
1370            Value::Series(s) => {
1371                assert_eq!(s.name(), "name");
1372            }
1373            _ => panic!("Expected series"),
1374        }
1375
1376        let missing = val.field("missing").unwrap();
1377        assert_eq!(missing, Value::Null);
1378    }
1379
1380    #[test]
1381    fn test_field_access_null() {
1382        let null_val = Value::Null;
1383        assert_eq!(null_val.field("any").unwrap(), Value::Null);
1384    }
1385
1386    #[test]
1387    fn test_type_names() {
1388        assert_eq!(Value::Null.type_name(), "null");
1389        assert_eq!(Value::Bool(true).type_name(), "boolean");
1390        assert_eq!(Value::Int(42).type_name(), "integer");
1391        assert_eq!(Value::BigInt(BigInt::from(42)).type_name(), "biginteger");
1392        assert_eq!(Value::Float(std::f64::consts::PI).type_name(), "float");
1393        assert_eq!(Value::String("test".to_string()).type_name(), "string");
1394        assert_eq!(Value::Array(vec![]).type_name(), "array");
1395        assert_eq!(Value::Object(HashMap::new()).type_name(), "object");
1396    }
1397
1398    #[test]
1399    fn test_equality() {
1400        // Same types
1401        assert_eq!(Value::int(42), Value::int(42));
1402        assert_eq!(
1403            Value::float(std::f64::consts::PI),
1404            Value::float(std::f64::consts::PI)
1405        );
1406        assert_eq!(
1407            Value::bigint(BigInt::from(42)),
1408            Value::bigint(BigInt::from(42))
1409        );
1410        assert_eq!(Value::string("hello"), Value::string("hello"));
1411        assert_eq!(Value::bool(true), Value::bool(true));
1412        assert_eq!(Value::Null, Value::Null);
1413
1414        // Cross-type numeric
1415        assert_eq!(Value::int(42), Value::float(42.0));
1416        assert_eq!(Value::float(42.0), Value::int(42));
1417        assert_eq!(Value::int(42), Value::bigint(BigInt::from(42)));
1418        assert_eq!(Value::bigint(BigInt::from(42)), Value::int(42));
1419
1420        // Arrays and objects
1421        let arr1 = Value::array(vec![Value::int(1), Value::int(2)]);
1422        let arr2 = Value::array(vec![Value::int(1), Value::int(2)]);
1423        assert_eq!(arr1, arr2);
1424
1425        let obj1 = Value::object(HashMap::from([("a".to_string(), Value::int(1))]));
1426        let obj2 = Value::object(HashMap::from([("a".to_string(), Value::int(1))]));
1427        assert_eq!(obj1, obj2);
1428
1429        // Inequalities
1430        assert_ne!(Value::int(1), Value::int(2));
1431        assert_ne!(Value::int(1), Value::float(1.1));
1432        assert_ne!(Value::string("a"), Value::string("b"));
1433        assert_ne!(Value::bool(true), Value::bool(false));
1434
1435        // DataFrame/Series comparison (always false)
1436        let df = DataFrame::new(vec![Series::new("a".into(), vec![1, 2, 3]).into()]).unwrap();
1437        assert_ne!(Value::dataframe(df.clone()), Value::dataframe(df));
1438        let series = Series::new("test".into(), vec![1, 2, 3]);
1439        assert_ne!(Value::series(series.clone()), Value::series(series));
1440
1441        // BigInt vs Float (not implemented, so false)
1442        assert_ne!(Value::bigint(BigInt::from(42)), Value::float(42.0));
1443    }
1444
1445    #[test]
1446    fn test_serde() {
1447        // Primitive
1448        let val = Value::int(42);
1449        let json = serde_json::to_string(&val).unwrap();
1450        assert_eq!(json, "42");
1451
1452        // Array
1453        let arr = Value::array(vec![Value::int(1), Value::string("two")]);
1454        let json = serde_json::to_string(&arr).unwrap();
1455        assert_eq!(json, "[1,\"two\"]");
1456
1457        // Object
1458        let obj = Value::object(HashMap::from([
1459            ("a".to_string(), Value::int(1)),
1460            ("b".to_string(), Value::string("x")),
1461        ]));
1462        let json = serde_json::to_string(&obj).unwrap();
1463        // Order may vary, so check contains
1464        assert!(json.contains("\"a\":1"));
1465        assert!(json.contains("\"b\":\"x\""));
1466
1467        // DataFrame
1468        let df = DataFrame::new(vec![Series::new("name".into(), vec!["Alice"]).into()]).unwrap();
1469        let val = Value::dataframe(df);
1470        let json = serde_json::to_string(&val).unwrap();
1471        assert!(json.contains("\"type\":\"DataFrame\""));
1472        assert!(json.contains("\"shape\":[1,1]"));
1473        assert!(json.contains("\"columns\":[\"name\"]"));
1474    }
1475
1476    #[test]
1477    fn test_debug() {
1478        assert!(format!("{:?}", Value::Null).contains("Null"));
1479        assert!(format!("{:?}", Value::int(42)).contains("Int(42)"));
1480        assert!(format!("{:?}", Value::string("hello")).contains("String(\"hello\")"));
1481        assert!(format!("{:?}", Value::array(vec![Value::int(1)])).contains("Array([Int(1)]"));
1482        assert!(format!("{:?}", Value::object(HashMap::new())).contains("Object({})"));
1483        assert!(format!("{:?}", Value::dataframe(DataFrame::empty())).contains("DataFrame"));
1484        assert!(
1485            format!("{:?}", Value::series(Series::new("test".into(), vec![1]))).contains("Series")
1486        );
1487    }
1488
1489    #[test]
1490    #[allow(clippy::approx_constant)]
1491    fn test_display() {
1492        assert_eq!(format!("{}", Value::Null), "null");
1493        assert_eq!(format!("{}", Value::bool(true)), "true");
1494        assert_eq!(format!("{}", Value::int(42)), "42");
1495        assert_eq!(format!("{}", Value::bigint(BigInt::from(123))), "123");
1496        assert_eq!(format!("{}", Value::float(3.14)), "3.14");
1497        assert_eq!(format!("{}", Value::string("hello")), "\"hello\"");
1498
1499        let arr = Value::array(vec![Value::int(1), Value::string("two")]);
1500        assert_eq!(format!("{}", arr), "[1, \"two\"]");
1501
1502        let obj = Value::object(HashMap::from([
1503            ("a".to_string(), Value::int(1)),
1504            ("b".to_string(), Value::string("x")),
1505        ]));
1506        let display = format!("{}", obj);
1507        assert!(display.contains("\"a\": 1"));
1508        assert!(display.contains("\"b\": \"x\""));
1509    }
1510
1511    #[test]
1512    fn test_to_dataframe() {
1513        // Array of objects
1514        let data = Value::array(vec![
1515            Value::object(HashMap::from([
1516                ("name".to_string(), Value::string("Alice")),
1517                ("age".to_string(), Value::int(30)),
1518            ])),
1519            Value::object(HashMap::from([
1520                ("name".to_string(), Value::string("Bob")),
1521                ("age".to_string(), Value::int(25)),
1522            ])),
1523        ]);
1524
1525        let df = data.to_dataframe().unwrap();
1526        assert_eq!(df.height(), 2);
1527        assert_eq!(df.width(), 2);
1528        assert!(df
1529            .get_column_names()
1530            .contains(&&polars::datatypes::PlSmallStr::from("name")));
1531        assert!(df
1532            .get_column_names()
1533            .contains(&&polars::datatypes::PlSmallStr::from("age")));
1534
1535        // Empty array
1536        let empty = Value::array(vec![]);
1537        let df = empty.to_dataframe().unwrap();
1538        assert_eq!(df.height(), 0);
1539
1540        // Invalid array (mixed types)
1541        let invalid = Value::array(vec![Value::int(1), Value::string("not object")]);
1542        assert!(invalid.to_dataframe().is_err());
1543
1544        // Array with BigInt (unsupported in AnyValue)
1545        let data = Value::array(vec![Value::object(HashMap::from([
1546            ("name".to_string(), Value::string("Alice")),
1547            ("big".to_string(), Value::bigint(BigInt::from(123))),
1548        ]))]);
1549        assert!(data.to_dataframe().is_err());
1550
1551        // DataFrame input
1552        let df = DataFrame::new(vec![
1553            Series::new("name".into(), vec!["Alice", "Bob"]).into(),
1554            Series::new("age".into(), vec![30i64, 25i64]).into(),
1555        ])
1556        .unwrap();
1557        let val = Value::dataframe(df.clone());
1558        let df2 = val.to_dataframe().unwrap();
1559        assert_eq!(df2.height(), 2);
1560        assert_eq!(df2.width(), 2);
1561
1562        // LazyFrame input
1563        let lf = df.lazy();
1564        let val = Value::lazy_frame(lf);
1565        let df3 = val.to_dataframe().unwrap();
1566        assert_eq!(df3.height(), 2);
1567        assert_eq!(df3.width(), 2);
1568    }
1569
1570    #[test]
1571    #[allow(clippy::approx_constant)]
1572    fn test_value_from_any_value() {
1573        use polars::datatypes::AnyValue;
1574
1575        assert_eq!(value_from_any_value(AnyValue::Null), Some(Value::Null));
1576        assert_eq!(
1577            value_from_any_value(AnyValue::Boolean(true)),
1578            Some(Value::bool(true))
1579        );
1580        assert_eq!(
1581            value_from_any_value(AnyValue::Int64(42)),
1582            Some(Value::int(42))
1583        );
1584        assert_eq!(
1585            value_from_any_value(AnyValue::Float64(3.14)),
1586            Some(Value::float(3.14))
1587        );
1588        assert_eq!(
1589            value_from_any_value(AnyValue::String("hello")),
1590            Some(Value::string("hello"))
1591        );
1592
1593        // Unsupported types return None
1594        assert_eq!(value_from_any_value(AnyValue::Date(0)), None);
1595    }
1596
1597    #[test]
1598    fn test_df_row_to_value() {
1599        let df = DataFrame::new(vec![
1600            Series::new("name".into(), vec!["Alice", "Bob"]).into(),
1601            Series::new("age".into(), vec![30i64, 25i64]).into(),
1602        ])
1603        .unwrap();
1604
1605        let row = df_row_to_value(&df, 0).unwrap();
1606        match row {
1607            Value::Object(obj) => {
1608                assert_eq!(obj.get("name"), Some(&Value::string("Alice")));
1609                assert_eq!(obj.get("age"), Some(&Value::int(30)));
1610            }
1611            _ => panic!("Expected object"),
1612        }
1613
1614        // Out of bounds
1615        let out_of_bounds = df_row_to_value(&df, 10).unwrap();
1616        match out_of_bounds {
1617            Value::Object(obj) => {
1618                assert_eq!(obj.get("name"), Some(&Value::Null));
1619                assert_eq!(obj.get("age"), Some(&Value::Null));
1620            }
1621            _ => panic!("Expected object"),
1622        }
1623    }
1624
1625    #[test]
1626    fn test_series_value_at() {
1627        let series = Series::new("test".into(), vec![Some(1i64), None, Some(3i64)]);
1628
1629        assert_eq!(series_value_at(&series, 0).unwrap(), Value::int(1));
1630        assert_eq!(series_value_at(&series, 1).unwrap(), Value::Null);
1631        assert_eq!(series_value_at(&series, 2).unwrap(), Value::int(3));
1632
1633        // Out of bounds
1634        assert_eq!(series_value_at(&series, 10).unwrap(), Value::Null);
1635    }
1636}