1use kyu_common::InternalId;
2use smol_str::SmolStr;
3
4use crate::interval::Interval;
5use crate::logical_type::LogicalType;
6
7#[derive(Clone, Debug)]
13pub enum TypedValue {
14 Null,
15 Bool(bool),
16 Int8(i8),
17 Int16(i16),
18 Int32(i32),
19 Int64(i64),
20 Int128(i128),
21 UInt8(u8),
22 UInt16(u16),
23 UInt32(u32),
24 UInt64(u64),
25 Float(f32),
26 Double(f64),
27 Date(i64),
28 Timestamp(i64),
29 TimestampSec(i64),
30 TimestampMs(i64),
31 TimestampNs(i64),
32 TimestampTz(i64),
33 Interval(Interval),
34 String(SmolStr),
35 Blob(Vec<u8>),
36 Uuid(SmolStr),
37 InternalId(InternalId),
38 Serial(i64),
39 List(Vec<TypedValue>),
40 Array(Vec<TypedValue>),
41 Struct(Vec<(SmolStr, TypedValue)>),
42 Map(Vec<(TypedValue, TypedValue)>),
43}
44
45impl PartialEq for TypedValue {
47 fn eq(&self, other: &Self) -> bool {
48 match (self, other) {
49 (Self::Null, Self::Null) => true,
50 (Self::Bool(a), Self::Bool(b)) => a == b,
51 (Self::Int8(a), Self::Int8(b)) => a == b,
52 (Self::Int16(a), Self::Int16(b)) => a == b,
53 (Self::Int32(a), Self::Int32(b)) => a == b,
54 (Self::Int64(a), Self::Int64(b)) => a == b,
55 (Self::Int128(a), Self::Int128(b)) => a == b,
56 (Self::UInt8(a), Self::UInt8(b)) => a == b,
57 (Self::UInt16(a), Self::UInt16(b)) => a == b,
58 (Self::UInt32(a), Self::UInt32(b)) => a == b,
59 (Self::UInt64(a), Self::UInt64(b)) => a == b,
60 (Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
61 (Self::Double(a), Self::Double(b)) => a.to_bits() == b.to_bits(),
62 (Self::Date(a), Self::Date(b)) => a == b,
63 (Self::Timestamp(a), Self::Timestamp(b)) => a == b,
64 (Self::TimestampSec(a), Self::TimestampSec(b)) => a == b,
65 (Self::TimestampMs(a), Self::TimestampMs(b)) => a == b,
66 (Self::TimestampNs(a), Self::TimestampNs(b)) => a == b,
67 (Self::TimestampTz(a), Self::TimestampTz(b)) => a == b,
68 (Self::Interval(a), Self::Interval(b)) => a == b,
69 (Self::String(a), Self::String(b)) => a == b,
70 (Self::Blob(a), Self::Blob(b)) => a == b,
71 (Self::Uuid(a), Self::Uuid(b)) => a == b,
72 (Self::InternalId(a), Self::InternalId(b)) => a == b,
73 (Self::Serial(a), Self::Serial(b)) => a == b,
74 (Self::List(a), Self::List(b)) => a == b,
75 (Self::Array(a), Self::Array(b)) => a == b,
76 (Self::Struct(a), Self::Struct(b)) => a == b,
77 (Self::Map(a), Self::Map(b)) => a == b,
78 _ => false,
79 }
80 }
81}
82
83impl Eq for TypedValue {}
84
85impl std::hash::Hash for TypedValue {
86 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
87 std::mem::discriminant(self).hash(state);
88 match self {
89 Self::Null => {}
90 Self::Bool(v) => v.hash(state),
91 Self::Int8(v) => v.hash(state),
92 Self::Int16(v) => v.hash(state),
93 Self::Int32(v) => v.hash(state),
94 Self::Int64(v) => v.hash(state),
95 Self::Int128(v) => v.hash(state),
96 Self::UInt8(v) => v.hash(state),
97 Self::UInt16(v) => v.hash(state),
98 Self::UInt32(v) => v.hash(state),
99 Self::UInt64(v) => v.hash(state),
100 Self::Float(v) => v.to_bits().hash(state),
101 Self::Double(v) => v.to_bits().hash(state),
102 Self::Date(v) => v.hash(state),
103 Self::Timestamp(v) => v.hash(state),
104 Self::TimestampSec(v) => v.hash(state),
105 Self::TimestampMs(v) => v.hash(state),
106 Self::TimestampNs(v) => v.hash(state),
107 Self::TimestampTz(v) => v.hash(state),
108 Self::Interval(v) => v.hash(state),
109 Self::String(v) => v.hash(state),
110 Self::Blob(v) => v.hash(state),
111 Self::Uuid(v) => v.hash(state),
112 Self::InternalId(v) => v.hash(state),
113 Self::Serial(v) => v.hash(state),
114 Self::List(v) => v.hash(state),
115 Self::Array(v) => v.hash(state),
116 Self::Struct(v) => v.hash(state),
117 Self::Map(v) => v.hash(state),
118 }
119 }
120}
121
122impl TypedValue {
123 pub fn logical_type(&self) -> LogicalType {
125 match self {
126 Self::Null => LogicalType::Any,
127 Self::Bool(_) => LogicalType::Bool,
128 Self::Int8(_) => LogicalType::Int8,
129 Self::Int16(_) => LogicalType::Int16,
130 Self::Int32(_) => LogicalType::Int32,
131 Self::Int64(_) => LogicalType::Int64,
132 Self::Int128(_) => LogicalType::Int128,
133 Self::UInt8(_) => LogicalType::UInt8,
134 Self::UInt16(_) => LogicalType::UInt16,
135 Self::UInt32(_) => LogicalType::UInt32,
136 Self::UInt64(_) => LogicalType::UInt64,
137 Self::Float(_) => LogicalType::Float,
138 Self::Double(_) => LogicalType::Double,
139 Self::Date(_) => LogicalType::Date,
140 Self::Timestamp(_) => LogicalType::Timestamp,
141 Self::TimestampSec(_) => LogicalType::TimestampSec,
142 Self::TimestampMs(_) => LogicalType::TimestampMs,
143 Self::TimestampNs(_) => LogicalType::TimestampNs,
144 Self::TimestampTz(_) => LogicalType::TimestampTz,
145 Self::Interval(_) => LogicalType::Interval,
146 Self::String(_) => LogicalType::String,
147 Self::Blob(_) => LogicalType::Blob,
148 Self::Uuid(_) => LogicalType::Uuid,
149 Self::InternalId(_) => LogicalType::InternalId,
150 Self::Serial(_) => LogicalType::Serial,
151 Self::List(_) => LogicalType::List(Box::new(LogicalType::Any)),
152 Self::Array(_) => LogicalType::List(Box::new(LogicalType::Any)),
153 Self::Struct(_) => LogicalType::Any,
154 Self::Map(_) => LogicalType::Map {
155 key: Box::new(LogicalType::Any),
156 value: Box::new(LogicalType::Any),
157 },
158 }
159 }
160
161 pub fn is_null(&self) -> bool {
162 matches!(self, Self::Null)
163 }
164
165 pub fn as_bool(&self) -> Option<bool> {
167 match self {
168 Self::Bool(v) => Some(*v),
169 _ => None,
170 }
171 }
172
173 pub fn as_i64(&self) -> Option<i64> {
175 match self {
176 Self::Int8(v) => Some(*v as i64),
177 Self::Int16(v) => Some(*v as i64),
178 Self::Int32(v) => Some(*v as i64),
179 Self::Int64(v) | Self::Date(v) | Self::Timestamp(v) | Self::Serial(v) => Some(*v),
180 Self::UInt8(v) => Some(*v as i64),
181 Self::UInt16(v) => Some(*v as i64),
182 Self::UInt32(v) => Some(*v as i64),
183 _ => None,
184 }
185 }
186
187 pub fn as_f64(&self) -> Option<f64> {
189 match self {
190 Self::Float(v) => Some(*v as f64),
191 Self::Double(v) => Some(*v),
192 _ => None,
193 }
194 }
195
196 pub fn as_str(&self) -> Option<&str> {
198 match self {
199 Self::String(s) | Self::Uuid(s) => Some(s.as_str()),
200 _ => None,
201 }
202 }
203
204 pub fn as_internal_id(&self) -> Option<InternalId> {
206 match self {
207 Self::InternalId(id) => Some(*id),
208 _ => None,
209 }
210 }
211}
212
213impl std::fmt::Display for TypedValue {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 match self {
216 Self::Null => write!(f, "NULL"),
217 Self::Bool(v) => write!(f, "{v}"),
218 Self::Int8(v) => write!(f, "{v}"),
219 Self::Int16(v) => write!(f, "{v}"),
220 Self::Int32(v) => write!(f, "{v}"),
221 Self::Int64(v) | Self::Serial(v) => write!(f, "{v}"),
222 Self::Int128(v) => write!(f, "{v}"),
223 Self::UInt8(v) => write!(f, "{v}"),
224 Self::UInt16(v) => write!(f, "{v}"),
225 Self::UInt32(v) => write!(f, "{v}"),
226 Self::UInt64(v) => write!(f, "{v}"),
227 Self::Float(v) => write!(f, "{v}"),
228 Self::Double(v) => write!(f, "{v}"),
229 Self::Date(v) => write!(f, "{v}"),
230 Self::Timestamp(v)
231 | Self::TimestampSec(v)
232 | Self::TimestampMs(v)
233 | Self::TimestampNs(v)
234 | Self::TimestampTz(v) => write!(f, "{v}"),
235 Self::Interval(iv) => write!(f, "{iv}"),
236 Self::String(s) | Self::Uuid(s) => write!(f, "{s}"),
237 Self::Blob(b) => write!(f, "\\x{}", hex_encode(b)),
238 Self::InternalId(id) => write!(f, "{id}"),
239 Self::List(items) | Self::Array(items) => {
240 write!(f, "[")?;
241 for (i, item) in items.iter().enumerate() {
242 if i > 0 {
243 write!(f, ",")?;
244 }
245 write!(f, "{item}")?;
246 }
247 write!(f, "]")
248 }
249 Self::Struct(fields) => {
250 write!(f, "{{")?;
251 for (i, (name, val)) in fields.iter().enumerate() {
252 if i > 0 {
253 write!(f, ",")?;
254 }
255 write!(f, "{name}: {val}")?;
256 }
257 write!(f, "}}")
258 }
259 Self::Map(entries) => {
260 write!(f, "{{")?;
261 for (i, (k, v)) in entries.iter().enumerate() {
262 if i > 0 {
263 write!(f, ",")?;
264 }
265 write!(f, "{k}={v}")?;
266 }
267 write!(f, "}}")
268 }
269 }
270 }
271}
272
273fn hex_encode(bytes: &[u8]) -> String {
274 bytes.iter().map(|b| format!("{b:02x}")).collect()
275}
276
277impl From<serde_json::Value> for TypedValue {
282 fn from(v: serde_json::Value) -> Self {
283 match v {
284 serde_json::Value::Null => TypedValue::Null,
285 serde_json::Value::Bool(b) => TypedValue::Bool(b),
286 serde_json::Value::Number(n) => {
287 if let Some(i) = n.as_i64() {
288 TypedValue::Int64(i)
289 } else if let Some(u) = n.as_u64() {
290 TypedValue::UInt64(u)
291 } else if let Some(f) = n.as_f64() {
292 TypedValue::Double(f)
293 } else {
294 TypedValue::Null
295 }
296 }
297 serde_json::Value::String(s) => TypedValue::String(SmolStr::new(s)),
298 serde_json::Value::Array(arr) => {
299 TypedValue::List(arr.into_iter().map(TypedValue::from).collect())
300 }
301 serde_json::Value::Object(map) => TypedValue::Struct(
302 map.into_iter()
303 .map(|(k, v)| (SmolStr::new(k), TypedValue::from(v)))
304 .collect(),
305 ),
306 }
307 }
308}
309
310impl From<TypedValue> for serde_json::Value {
311 fn from(v: TypedValue) -> Self {
312 match v {
313 TypedValue::Null => serde_json::Value::Null,
314 TypedValue::Bool(b) => serde_json::Value::Bool(b),
315 TypedValue::Int8(n) => serde_json::Value::from(n),
316 TypedValue::Int16(n) => serde_json::Value::from(n),
317 TypedValue::Int32(n) => serde_json::Value::from(n),
318 TypedValue::Int64(n)
319 | TypedValue::Date(n)
320 | TypedValue::Timestamp(n)
321 | TypedValue::TimestampSec(n)
322 | TypedValue::TimestampMs(n)
323 | TypedValue::TimestampNs(n)
324 | TypedValue::TimestampTz(n)
325 | TypedValue::Serial(n) => serde_json::Value::from(n),
326 TypedValue::Int128(n) => serde_json::Value::from(n.to_string()),
327 TypedValue::UInt8(n) => serde_json::Value::from(n),
328 TypedValue::UInt16(n) => serde_json::Value::from(n),
329 TypedValue::UInt32(n) => serde_json::Value::from(n),
330 TypedValue::UInt64(n) => serde_json::Value::from(n),
331 TypedValue::Float(f) => serde_json::Number::from_f64(f as f64)
332 .map(serde_json::Value::Number)
333 .unwrap_or(serde_json::Value::Null),
334 TypedValue::Double(f) => serde_json::Number::from_f64(f)
335 .map(serde_json::Value::Number)
336 .unwrap_or(serde_json::Value::Null),
337 TypedValue::Interval(iv) => serde_json::Value::from(iv.to_string()),
338 TypedValue::String(s) | TypedValue::Uuid(s) => serde_json::Value::String(s.to_string()),
339 TypedValue::Blob(b) => serde_json::Value::String(format!("\\x{}", hex_encode(&b))),
340 TypedValue::InternalId(id) => serde_json::Value::from(id.to_string()),
341 TypedValue::List(items) | TypedValue::Array(items) => {
342 serde_json::Value::Array(items.into_iter().map(serde_json::Value::from).collect())
343 }
344 TypedValue::Struct(fields) => {
345 let map: serde_json::Map<String, serde_json::Value> = fields
346 .into_iter()
347 .map(|(k, v)| (k.to_string(), serde_json::Value::from(v)))
348 .collect();
349 serde_json::Value::Object(map)
350 }
351 TypedValue::Map(entries) => {
352 let map: serde_json::Map<String, serde_json::Value> = entries
353 .into_iter()
354 .map(|(k, v)| (k.to_string(), serde_json::Value::from(v)))
355 .collect();
356 serde_json::Value::Object(map)
357 }
358 }
359 }
360}
361
362pub fn json_object_to_map(
366 value: serde_json::Value,
367) -> std::collections::HashMap<SmolStr, TypedValue> {
368 match value {
369 serde_json::Value::Object(map) => map
370 .into_iter()
371 .map(|(k, v)| (SmolStr::new(k), TypedValue::from(v)))
372 .collect(),
373 _ => std::collections::HashMap::new(),
374 }
375}
376
377pub fn json_str_to_map(
381 s: &str,
382) -> Result<std::collections::HashMap<SmolStr, TypedValue>, serde_json::Error> {
383 let value: serde_json::Value = serde_json::from_str(s)?;
384 Ok(json_object_to_map(value))
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn null() {
393 let v = TypedValue::Null;
394 assert!(v.is_null());
395 assert_eq!(v.to_string(), "NULL");
396 }
397
398 #[test]
399 fn bool_round_trip() {
400 let v = TypedValue::Bool(true);
401 assert_eq!(v.as_bool(), Some(true));
402 assert!(!v.is_null());
403 }
404
405 #[test]
406 fn integer_round_trips() {
407 assert_eq!(TypedValue::Int8(42).as_i64(), Some(42));
408 assert_eq!(TypedValue::Int16(-1000).as_i64(), Some(-1000));
409 assert_eq!(TypedValue::Int32(100_000).as_i64(), Some(100_000));
410 assert_eq!(TypedValue::Int64(i64::MAX).as_i64(), Some(i64::MAX));
411 assert_eq!(TypedValue::UInt8(255).as_i64(), Some(255));
412 assert_eq!(TypedValue::UInt16(65535).as_i64(), Some(65535));
413 assert_eq!(TypedValue::UInt32(u32::MAX).as_i64(), Some(u32::MAX as i64));
414 }
415
416 #[test]
417 fn float_round_trips() {
418 let f = TypedValue::Float(3.14);
419 assert!((f.as_f64().unwrap() - 3.14f32 as f64).abs() < 1e-6);
420
421 let d = TypedValue::Double(2.718281828);
422 assert!((d.as_f64().unwrap() - 2.718281828).abs() < 1e-9);
423 }
424
425 #[test]
426 fn string_round_trip() {
427 let v = TypedValue::String(SmolStr::new("hello"));
428 assert_eq!(v.as_str(), Some("hello"));
429 assert_eq!(v.to_string(), "hello");
430 }
431
432 #[test]
433 fn internal_id_round_trip() {
434 let id = InternalId::new(1, 42);
435 let v = TypedValue::InternalId(id);
436 assert_eq!(v.as_internal_id(), Some(id));
437 }
438
439 #[test]
440 fn list_display() {
441 let v = TypedValue::List(vec![
442 TypedValue::Int64(1),
443 TypedValue::Int64(2),
444 TypedValue::Int64(3),
445 ]);
446 assert_eq!(v.to_string(), "[1,2,3]");
447 }
448
449 #[test]
450 fn struct_display() {
451 let v = TypedValue::Struct(vec![
452 (
453 SmolStr::new("name"),
454 TypedValue::String(SmolStr::new("Alice")),
455 ),
456 (SmolStr::new("age"), TypedValue::Int64(30)),
457 ]);
458 assert_eq!(v.to_string(), "{name: Alice,age: 30}");
459 }
460
461 #[test]
462 fn logical_type_inference() {
463 assert_eq!(TypedValue::Null.logical_type(), LogicalType::Any);
464 assert_eq!(TypedValue::Bool(true).logical_type(), LogicalType::Bool);
465 assert_eq!(TypedValue::Int8(1).logical_type(), LogicalType::Int8);
466 assert_eq!(TypedValue::Int16(1).logical_type(), LogicalType::Int16);
467 assert_eq!(TypedValue::Int32(1).logical_type(), LogicalType::Int32);
468 assert_eq!(TypedValue::Int64(42).logical_type(), LogicalType::Int64);
469 assert_eq!(TypedValue::Float(1.0).logical_type(), LogicalType::Float);
470 assert_eq!(TypedValue::Double(3.14).logical_type(), LogicalType::Double);
471 assert_eq!(
472 TypedValue::String(SmolStr::new("hi")).logical_type(),
473 LogicalType::String
474 );
475 assert_eq!(TypedValue::Serial(1).logical_type(), LogicalType::Serial);
476 }
477
478 #[test]
479 fn wrong_type_returns_none() {
480 let v = TypedValue::String(SmolStr::new("hello"));
481 assert_eq!(v.as_bool(), None);
482 assert_eq!(v.as_i64(), None);
483 assert_eq!(v.as_f64(), None);
484 }
485
486 #[test]
487 fn clone_and_eq() {
488 let a = TypedValue::List(vec![TypedValue::Int64(1)]);
489 let b = a.clone();
490 assert_eq!(a, b);
491 }
492
493 #[test]
496 fn json_null() {
497 let v: TypedValue = serde_json::Value::Null.into();
498 assert_eq!(v, TypedValue::Null);
499 }
500
501 #[test]
502 fn json_bool() {
503 let v: TypedValue = serde_json::json!(true).into();
504 assert_eq!(v, TypedValue::Bool(true));
505 }
506
507 #[test]
508 fn json_integer() {
509 let v: TypedValue = serde_json::json!(42).into();
510 assert_eq!(v, TypedValue::Int64(42));
511 }
512
513 #[test]
514 fn json_negative_integer() {
515 let v: TypedValue = serde_json::json!(-10).into();
516 assert_eq!(v, TypedValue::Int64(-10));
517 }
518
519 #[test]
520 fn json_float() {
521 let v: TypedValue = serde_json::json!(3.14).into();
522 assert_eq!(v, TypedValue::Double(3.14));
523 }
524
525 #[test]
526 fn json_string() {
527 let v: TypedValue = serde_json::json!("hello").into();
528 assert_eq!(v, TypedValue::String(SmolStr::new("hello")));
529 }
530
531 #[test]
532 fn json_array() {
533 let v: TypedValue = serde_json::json!([1, "two", true]).into();
534 assert_eq!(
535 v,
536 TypedValue::List(vec![
537 TypedValue::Int64(1),
538 TypedValue::String(SmolStr::new("two")),
539 TypedValue::Bool(true),
540 ])
541 );
542 }
543
544 #[test]
545 fn json_object() {
546 let v: TypedValue = serde_json::json!({"name": "Alice", "age": 30}).into();
547 if let TypedValue::Struct(fields) = &v {
548 assert_eq!(fields.len(), 2);
549 } else {
550 panic!("expected Struct");
551 }
552 }
553
554 #[test]
555 fn json_roundtrip() {
556 let original = serde_json::json!({"x": 42, "y": "hello", "z": [1, 2]});
557 let typed: TypedValue = original.clone().into();
558 let back: serde_json::Value = typed.into();
559 assert_eq!(original, back);
560 }
561
562 #[test]
563 fn json_object_to_map_flat() {
564 let map = super::json_object_to_map(serde_json::json!({
565 "DATA_DIR": "/data",
566 "PORT": 8080,
567 "DEBUG": true
568 }));
569 assert_eq!(map.len(), 3);
570 assert_eq!(
571 map.get("DATA_DIR"),
572 Some(&TypedValue::String(SmolStr::new("/data")))
573 );
574 assert_eq!(map.get("PORT"), Some(&TypedValue::Int64(8080)));
575 assert_eq!(map.get("DEBUG"), Some(&TypedValue::Bool(true)));
576 }
577
578 #[test]
579 fn json_str_to_map_valid() {
580 let map = super::json_str_to_map(r#"{"key": "value", "num": 42}"#).unwrap();
581 assert_eq!(map.len(), 2);
582 assert_eq!(
583 map.get("key"),
584 Some(&TypedValue::String(SmolStr::new("value")))
585 );
586 assert_eq!(map.get("num"), Some(&TypedValue::Int64(42)));
587 }
588
589 #[test]
590 fn json_str_to_map_invalid() {
591 assert!(super::json_str_to_map("not json").is_err());
592 }
593
594 #[test]
595 fn json_str_to_map_non_object() {
596 let map = super::json_str_to_map("[1,2,3]").unwrap();
598 assert!(map.is_empty());
599 }
600}