Skip to main content

cratestack_sql/values/
sql_value.rs

1use cratestack_core::Value;
2
3use super::decimal_like::DecimalLike;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum SqlValue {
7    Bool(bool),
8    Int(i64),
9    Float(f64),
10    String(String),
11    Bytes(Vec<u8>),
12    Uuid(uuid::Uuid),
13    DateTime(chrono::DateTime<chrono::Utc>),
14    Json(Value),
15    /// Holds whichever concrete decimal type the originating schema chose
16    /// (cratestack#505 Direction 2 — `rust_decimal::Decimal`,
17    /// `bigdecimal::BigDecimal`, or any other [`DecimalLike`]
18    /// implementer), boxed rather than a fixed concrete type so two
19    /// schemas that chose different backends can share this one compiled
20    /// `SqlValue` without a Cargo-feature union collision. Unconditional —
21    /// no `#[cfg]` gate, since this variant no longer names a concrete
22    /// backend type at all.
23    Decimal(Box<dyn DecimalLike>),
24    /// A `Vector(n)` field's value (see `docs/design/extensions.md`
25    /// §6). Defined unconditionally — no `pgvector` dependency is
26    /// needed to hold a `Vec<f32>` — but only ever constructed by
27    /// generated code gated on the `pgvector` Cargo feature (#161's
28    /// compile-time check), and only ever bound to a real column by
29    /// `cratestack-sqlx`'s own `pgvector`-gated encode path.
30    Vector(Vec<f32>),
31    /// A `Geography`/`Geometry` field's value as EWKB bytes (see
32    /// `docs/design/extensions.md` §6b and cratestack#842). Defined
33    /// unconditionally — no PostGIS dependency is needed to hold a
34    /// `Vec<u8>` — but only ever constructed by generated code gated on
35    /// the `postgis` Cargo feature, and bound to a real column by
36    /// `cratestack-sqlx`'s own `postgis`-gated encode path.
37    ///
38    /// A distinct variant rather than reusing [`SqlValue::Bytes`] so
39    /// the encode boundary can tell "these bytes are a geometry" from
40    /// "these bytes are a bytea column", which matters for the
41    /// `NULL` arm's type annotation.
42    #[cfg(feature = "postgis")]
43    Spatial(Vec<u8>),
44    NullBool,
45    NullInt,
46    NullFloat,
47    NullString,
48    NullBytes,
49    NullUuid,
50    NullDateTime,
51    NullJson,
52    NullDecimal,
53    NullVector,
54    #[cfg(feature = "postgis")]
55    NullSpatial,
56}
57
58#[derive(Debug, Clone, PartialEq)]
59pub enum FilterValue {
60    None,
61    Single(SqlValue),
62    Many(Vec<SqlValue>),
63}
64
65#[derive(Debug, Clone, PartialEq)]
66pub struct SqlColumnValue {
67    pub column: &'static str,
68    pub value: SqlValue,
69}
70
71/// Detect the first duplicate value in a list of `SqlValue`s, used for
72/// batch_upsert input deduplication. Linear-scan with `PartialEq` rather
73/// than the hashed variant in `cratestack-core` because `SqlValue::Float`
74/// and `SqlValue::Decimal` don't admit a sound `Hash` impl.
75///
76/// At the documented batch cap (≤ 1000 items) the O(N²) cost is on the
77/// order of a million `PartialEq` comparisons, which dominates nothing
78/// next to a single round-trip to Postgres. Returns `(first_index,
79/// duplicate_index)` on collision, matching `cratestack_core::find_duplicate_position`.
80pub fn find_duplicate_sql_value(values: &[SqlValue]) -> Option<(usize, usize)> {
81    for (index, value) in values.iter().enumerate() {
82        if let Some(earlier) = values[..index].iter().position(|prior| prior == value) {
83            return Some((earlier, index));
84        }
85    }
86    None
87}