Skip to main content

qubit_value/multi_values/
multi_values_identity.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Equality and hashing for [`super::MultiValues`].
9
10use std::hash::Hash;
11use std::hash::Hasher;
12
13#[cfg(feature = "json")]
14use qubit_budget::MeasuredBudgetError;
15#[cfg(feature = "json")]
16use qubit_budget::ResourceQuantity;
17#[cfg(feature = "json")]
18use qubit_budget::json::JsonValueBudget;
19
20use super::MultiValuesRepr;
21use super::multi_values::MultiValues;
22use crate::identity::canonical_f32_bits;
23use crate::identity::canonical_f64_bits;
24#[cfg(feature = "big-decimal")]
25use crate::identity::hash_big_decimal;
26#[cfg(feature = "json")]
27use crate::identity::hash_json;
28use crate::identity::hash_string_map;
29#[cfg(feature = "json")]
30use crate::identity::json_eq;
31
32/// Compares ordered payloads using the identity rule for their element type.
33macro_rules! payloads_eq {
34    (Float32, $left:expr, $right:expr) => {
35        $left.len() == $right.len()
36            && $left
37                .iter()
38                .zip($right)
39                .all(|(left, right)| canonical_f32_bits(*left) == canonical_f32_bits(*right))
40    };
41    (Float64, $left:expr, $right:expr) => {
42        $left.len() == $right.len()
43            && $left
44                .iter()
45                .zip($right)
46                .all(|(left, right)| canonical_f64_bits(*left) == canonical_f64_bits(*right))
47    };
48    (Json, $left:expr, $right:expr) => {
49        $left.len() == $right.len() && $left.iter().zip($right).all(|(left, right)| json_eq(left, right))
50    };
51    ($variant:ident, $left:expr, $right:expr) => {
52        $left == $right
53    };
54}
55
56/// Hashes ordered payloads using the identity rule for their element type.
57macro_rules! hash_payloads {
58    (Float32, $values:expr, $state:expr) => {{
59        $values.len().hash($state);
60        for value in $values {
61            canonical_f32_bits(*value).hash($state);
62        }
63    }};
64    (Float64, $values:expr, $state:expr) => {{
65        $values.len().hash($state);
66        for value in $values {
67            canonical_f64_bits(*value).hash($state);
68        }
69    }};
70    (BigDecimal, $values:expr, $state:expr) => {{
71        $values.len().hash($state);
72        for value in $values {
73            hash_big_decimal(value, $state);
74        }
75    }};
76    (StringMap, $values:expr, $state:expr) => {{
77        $values.len().hash($state);
78        for value in $values {
79            hash_string_map(value, $state);
80        }
81    }};
82    (Json, $values:expr, $state:expr) => {{
83        $values.len().hash($state);
84        for value in $values {
85            hash_json(value, $state);
86        }
87    }};
88    ($variant:ident, $values:expr, $state:expr) => {
89        $values.hash($state)
90    };
91}
92
93/// Keeps JSON transaction handling in the caller while dispatching other types.
94#[cfg(feature = "json")]
95macro_rules! budgeted_hash_payload {
96    (Json, $value:expr, $state:expr) => {{
97        let _ = $value;
98        unreachable!("JSON payload hashing is handled by MultiValues::hash_with_json_budget")
99    }};
100    ($variant:ident, $value:expr, $state:expr) => {
101        hash_payloads!($variant, $value, $state)
102    };
103}
104
105/// Generates the non-JSON payload dispatch from the storage type table.
106#[cfg(feature = "json")]
107macro_rules! budgeted_payload_match {
108    ($repr:expr, $state:expr; $(([$($cfg:meta),*], $variant:ident, $type:ty, $data_type:expr, $materialization:ident, $json_class:ident, $number_projection:ident, $value_doc:literal, $multi_doc:literal $(, $_wire:tt)*)),+ $(,)?) => {
109        match $repr {
110            MultiValuesRepr::Unset(data_type) => data_type.hash($state),
111            $($(#[$cfg])* MultiValuesRepr::$variant(value) => {
112                budgeted_hash_payload!($variant, value, $state)
113            },)+
114        }
115    };
116}
117
118/// Hashes one multi-value payload while applying a budget to JSON elements.
119///
120/// # Type Parameters
121///
122/// * `H` - Hasher receiving the semantic collection identity.
123/// * `R` - Resource identifier used by the JSON budget.
124/// * `Q` - Quantity type used by the JSON budget.
125///
126/// # Parameters
127///
128/// * `repr` - Private collection representation whose payload is hashed.
129/// * `state` - Destination hasher.
130/// * `_budget` - Budget reserved for JSON element accounting by the caller.
131///
132/// # Returns
133///
134/// `Ok(())` after the complete collection payload identity is hashed.
135///
136/// # Errors
137///
138/// This helper currently returns no error for non-JSON payloads; JSON elements
139/// are preflighted by
140/// [`MultiValues::hash_with_json_budget`](crate::MultiValues::hash_with_json_budget).
141#[cfg(feature = "json")]
142pub(crate) fn hash_multi_values_payload_with_json_budget<H, R, Q>(
143    repr: &MultiValuesRepr,
144    state: &mut H,
145    _budget: &mut JsonValueBudget<R, Q>,
146) -> Result<(), MeasuredBudgetError<R, Q>>
147where
148    H: Hasher,
149    R: Clone,
150    Q: ResourceQuantity,
151{
152    for_each_value_type!(budgeted_payload_match, repr, state);
153    Ok(())
154}
155
156/// Implements lawful equality and hashing for the complete value-type table.
157macro_rules! impl_multi_values_identity {
158    (
159        ;
160        $(([$($cfg:meta),*], $variant:ident, $type:ty, $data_type:expr, $materialization:ident, $json_class:ident, $number_projection:ident, $value_doc:literal, $multi_doc:literal $(, $_wire:tt)*)),+ $(,)?
161    ) => {
162        impl PartialEq for MultiValues {
163            fn eq(&self, other: &Self) -> bool {
164                match (&self.repr, &other.repr) {
165                    (MultiValuesRepr::Unset(left), MultiValuesRepr::Unset(right)) => left == right,
166                    $($(#[$cfg])*
167                    (MultiValuesRepr::$variant(left), MultiValuesRepr::$variant(right)) => {
168                        payloads_eq!($variant, left, right)
169                    },)+
170                    _ => false,
171                }
172            }
173        }
174
175        impl Eq for MultiValues {}
176
177        impl Hash for MultiValues {
178            fn hash<H: Hasher>(&self, state: &mut H) {
179                std::mem::discriminant(&self.repr).hash(state);
180                match &self.repr {
181                    MultiValuesRepr::Unset(data_type) => data_type.hash(state),
182                    $($(#[$cfg])*
183                    MultiValuesRepr::$variant(values) => {
184                        hash_payloads!($variant, values, state)
185                    },)+
186                }
187            }
188        }
189    };
190}
191
192for_each_value_type!(impl_multi_values_identity);