schema_core/common/generic_value.rs
1use std::collections::BTreeMap;
2use std::hash::{Hash, Hasher};
3
4use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
5use rust_decimal::Decimal;
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9/// The canonical value vocabulary every layer trades in — the **middle type**
10/// between a source and a sink.
11///
12/// A source maps its native types *into* these variants; a sink maps them *out*
13/// to its own representation. The set is deliberately fine-grained — numerics are
14/// split by width, temporals are split into date/time/timestamp/timestamptz — so
15/// no semantic information is lost in transit: a `date` arrives at a sink as a
16/// [`Date`](Self::Date), not an opaque string a future sink would have to guess
17/// at. Text-family Postgres types (`text`/`varchar`/`citext`/enum) share the one
18/// [`String`](Self::String) shape because they don't differ *as values*; their
19/// indexing differs, and that lives in the field's `FlussoType`.
20///
21/// Serde is **derived and format-agnostic** on purpose. The derive is externally
22/// tagged (`{"Date":"2024-01-01"}`, `{"BigInt":5}`), so serialize → deserialize
23/// is a lossless identity — a `Date` round-trips back a `Date`, never collapsing
24/// to a string. That lets a queue persist or transport a value in whatever format
25/// it likes and hand it back unchanged: it goes in as a `GenericValue` and comes
26/// out as the same `GenericValue`. Core picks no format (no JSON here); the
27/// concrete encoding is the consumer's choice.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub enum GenericValue {
30 Null,
31 Bool(bool),
32 /// `smallint` / `int2`.
33 SmallInt(i16),
34 /// `integer` / `int4`.
35 Int(i32),
36 /// `bigint` / `int8`.
37 BigInt(i64),
38 /// `real` / `float4`.
39 Float(f32),
40 /// `double precision` / `float8`.
41 Double(f64),
42 /// `numeric` / `decimal` — exact.
43 Decimal(Decimal),
44 /// Any text-family value (`text`/`varchar`/`citext`/enum).
45 String(String),
46 /// `uuid`.
47 Uuid(Uuid),
48 /// `date` — no time, no zone.
49 Date(NaiveDate),
50 /// `time` — no date, no zone.
51 Time(NaiveTime),
52 /// `timestamp` — date + time, no zone.
53 Timestamp(NaiveDateTime),
54 /// `timestamptz` — an instant, normalized to UTC.
55 TimestampTz(DateTime<Utc>),
56 /// `bytea`.
57 Bytes(Vec<u8>),
58 Array(Vec<GenericValue>),
59 Map(BTreeMap<String, GenericValue>),
60}
61
62impl GenericValue {
63 /// Whether this value can stand as a single SQL parameter, key, or literal:
64 /// true for every scalar variant, false for `Null` and the composite
65 /// `Array`/`Map`. The one home for that rule — the Postgres source applies
66 /// it when binding params, building keys, and inlining literals. Written as
67 /// an exhaustive match so a new variant cannot be added without classifying
68 /// it here.
69 pub fn is_bindable_scalar(&self) -> bool {
70 match self {
71 GenericValue::Bool(_)
72 | GenericValue::SmallInt(_)
73 | GenericValue::Int(_)
74 | GenericValue::BigInt(_)
75 | GenericValue::Float(_)
76 | GenericValue::Double(_)
77 | GenericValue::Decimal(_)
78 | GenericValue::String(_)
79 | GenericValue::Uuid(_)
80 | GenericValue::Date(_)
81 | GenericValue::Time(_)
82 | GenericValue::Timestamp(_)
83 | GenericValue::TimestampTz(_)
84 | GenericValue::Bytes(_) => true,
85 GenericValue::Null | GenericValue::Array(_) | GenericValue::Map(_) => false,
86 }
87 }
88}
89
90// `f32`/`f64` are not `Eq`/`Hash`, so the derives won't do; compare and hash the
91// float variants by their bit pattern (a total equivalence — the rare float key
92// is pathological anyway, and this keeps `Eq`/`Hash` consistent). Every other
93// variant defers to its payload's own impls.
94impl PartialEq for GenericValue {
95 fn eq(&self, other: &Self) -> bool {
96 use GenericValue::*;
97 match (self, other) {
98 (Null, Null) => true,
99 (Bool(a), Bool(b)) => a == b,
100 (SmallInt(a), SmallInt(b)) => a == b,
101 (Int(a), Int(b)) => a == b,
102 (BigInt(a), BigInt(b)) => a == b,
103 (Float(a), Float(b)) => a.to_bits() == b.to_bits(),
104 (Double(a), Double(b)) => a.to_bits() == b.to_bits(),
105 (Decimal(a), Decimal(b)) => a == b,
106 (String(a), String(b)) => a == b,
107 (Uuid(a), Uuid(b)) => a == b,
108 (Date(a), Date(b)) => a == b,
109 (Time(a), Time(b)) => a == b,
110 (Timestamp(a), Timestamp(b)) => a == b,
111 (TimestampTz(a), TimestampTz(b)) => a == b,
112 (Bytes(a), Bytes(b)) => a == b,
113 (Array(a), Array(b)) => a == b,
114 (Map(a), Map(b)) => a == b,
115 _ => false,
116 }
117 }
118}
119
120impl Eq for GenericValue {}
121
122impl Hash for GenericValue {
123 fn hash<H: Hasher>(&self, state: &mut H) {
124 use GenericValue::*;
125 std::mem::discriminant(self).hash(state);
126 match self {
127 Null => {}
128 Bool(v) => v.hash(state),
129 SmallInt(v) => v.hash(state),
130 Int(v) => v.hash(state),
131 BigInt(v) => v.hash(state),
132 Float(v) => v.to_bits().hash(state),
133 Double(v) => v.to_bits().hash(state),
134 Decimal(v) => v.hash(state),
135 String(v) => v.hash(state),
136 Uuid(v) => v.hash(state),
137 Date(v) => v.hash(state),
138 Time(v) => v.hash(state),
139 Timestamp(v) => v.hash(state),
140 TimestampTz(v) => v.hash(state),
141 Bytes(v) => v.hash(state),
142 Array(v) => v.hash(state),
143 Map(v) => v.hash(state),
144 }
145 }
146}