Skip to main content

icydb_schema/
expression.rs

1//! Bounded source-level constraint expressions.
2
3use std::collections::BTreeSet;
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    Account, Blob, Date, Decimal, Duration, FieldSourceKey, Float32, Float64,
9    MAX_PROPOSAL_LITERAL_BYTES, MAX_SOURCE_CHECK_INSTRUCTIONS, Principal, ScalarKind,
10    SchemaContractError, Subaccount, Timestamp, TypeSourceKey, U256, Ulid, Unit,
11};
12
13/// One canonical scalar literal carried by a schema proposal.
14#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
15pub enum ScalarLiteral {
16    /// Account identifier.
17    Account(Account),
18    /// Bounded binary value.
19    Blob(#[serde(deserialize_with = "crate::codec::deserialize_literal_blob")] Blob),
20    /// Boolean value.
21    Bool(bool),
22    /// Days since the Unix epoch.
23    Date(Date),
24    /// Canonical fixed-point decimal.
25    Decimal(#[serde(deserialize_with = "crate::codec::deserialize_literal_decimal")] Decimal),
26    /// Millisecond duration.
27    Duration(Duration),
28    /// Source-keyed unit-enum variant.
29    EnumUnit {
30        /// Immutable enum type identity.
31        #[serde(
32            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
33        )]
34        enum_type: TypeSourceKey,
35        /// Immutable unit-variant identity.
36        #[serde(
37            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
38        )]
39        variant: TypeSourceKey,
40    },
41    /// Finite 32-bit float.
42    Float32(Float32),
43    /// Finite 64-bit float.
44    Float64(Float64),
45    /// Signed fixed-width integer.
46    Int(i128),
47    /// Unsigned fixed-width integer.
48    Nat(u128),
49    /// Fixed-width unsigned 256-bit integer.
50    U256(U256),
51    /// Principal value.
52    Principal(Principal),
53    /// Fixed-width subaccount.
54    Subaccount(Subaccount),
55    /// Bounded text value.
56    Text(#[serde(deserialize_with = "crate::codec::deserialize_literal_text")] String),
57    /// Unix-millisecond timestamp.
58    Timestamp(Timestamp),
59    /// Canonical ULID.
60    Ulid(Ulid),
61    /// Explicit unit value.
62    Unit(Unit),
63}
64
65impl ScalarLiteral {
66    /// Return the declared scalar kind represented by this literal.
67    #[must_use]
68    pub const fn kind(&self) -> ScalarKind {
69        match self {
70            Self::Account(_) => ScalarKind::Account,
71            Self::Blob(_) => ScalarKind::Blob,
72            Self::Bool(_) => ScalarKind::Bool,
73            Self::Date(_) => ScalarKind::Date,
74            Self::Decimal(_) => ScalarKind::Decimal,
75            Self::Duration(_) => ScalarKind::Duration,
76            Self::EnumUnit { .. } => ScalarKind::Enum,
77            Self::Float32(_) => ScalarKind::Float32,
78            Self::Float64(_) => ScalarKind::Float64,
79            Self::Int(_) => ScalarKind::Int128,
80            Self::Nat(_) => ScalarKind::Nat128,
81            Self::U256(_) => ScalarKind::U256,
82            Self::Principal(_) => ScalarKind::Principal,
83            Self::Subaccount(_) => ScalarKind::Subaccount,
84            Self::Text(_) => ScalarKind::Text,
85            Self::Timestamp(_) => ScalarKind::Timestamp,
86            Self::Ulid(_) => ScalarKind::Ulid,
87            Self::Unit(_) => ScalarKind::Unit,
88        }
89    }
90
91    pub(crate) const fn validate(&self) -> Result<(), SchemaContractError> {
92        match self {
93            Self::Decimal(value) if value.scale() > crate::decimal::MAX_SUPPORTED_SCALE => {
94                Err(SchemaContractError::InvalidLiteral)
95            }
96            Self::Blob(value) if value.len() > MAX_PROPOSAL_LITERAL_BYTES => {
97                Err(SchemaContractError::InvalidLiteral)
98            }
99            Self::Text(value) if value.len() > MAX_PROPOSAL_LITERAL_BYTES => {
100                Err(SchemaContractError::InvalidLiteral)
101            }
102            _ => Ok(()),
103        }
104    }
105}
106
107/// One instruction in a bounded source-level postfix check expression.
108///
109/// This is an AST transport, not accepted bytecode: field references remain
110/// typed current-name keys and IcyDB still owns accepted binding and compilation.
111#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
112pub enum SourceCheckInstruction {
113    /// Push one field value.
114    Field(
115        #[serde(
116            deserialize_with = "crate::codec::deserialize_key::<_, _, { crate::MAX_SOURCE_KEY_BYTES }>"
117        )]
118        FieldSourceKey,
119    ),
120    /// Push one admitted proposal literal.
121    Literal(ScalarLiteral),
122    /// SQL equality.
123    Equal,
124    /// SQL inequality.
125    NotEqual,
126    /// SQL less-than.
127    LessThan,
128    /// SQL less-than-or-equal.
129    LessThanOrEqual,
130    /// SQL greater-than.
131    GreaterThan,
132    /// SQL greater-than-or-equal.
133    GreaterThanOrEqual,
134    /// SQL three-valued conjunction.
135    And,
136    /// SQL three-valued disjunction.
137    Or,
138    /// SQL three-valued negation.
139    Not,
140    /// Two-valued null test.
141    IsNull,
142    /// Two-valued non-null test.
143    IsNotNull,
144    /// Bounded scalar/collection length.
145    Length,
146}
147
148/// Bounded canonical source expression.
149#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
150pub struct SourceCheckExpr {
151    #[serde(
152        deserialize_with = "crate::codec::deserialize_list::<_, _, { crate::MAX_SOURCE_CHECK_INSTRUCTIONS }>"
153    )]
154    instructions: Vec<SourceCheckInstruction>,
155}
156
157impl SourceCheckExpr {
158    /// Construct and validate one source expression.
159    ///
160    /// # Errors
161    ///
162    /// Returns a typed expression error for empty, oversized, malformed-stack,
163    /// or invalid-literal input.
164    pub fn try_new(instructions: Vec<SourceCheckInstruction>) -> Result<Self, SchemaContractError> {
165        let expression = Self { instructions };
166        expression.validate()?;
167        Ok(expression)
168    }
169
170    /// Borrow canonical postfix instructions.
171    #[must_use]
172    pub fn instructions(&self) -> &[SourceCheckInstruction] {
173        &self.instructions
174    }
175
176    /// Derive referenced field source keys from the expression.
177    #[must_use]
178    pub fn dependencies(&self) -> BTreeSet<FieldSourceKey> {
179        self.instructions
180            .iter()
181            .filter_map(|instruction| match instruction {
182                SourceCheckInstruction::Field(field) => Some(field.clone()),
183                _ => None,
184            })
185            .collect()
186    }
187
188    pub(crate) fn validate(&self) -> Result<(), SchemaContractError> {
189        if self.instructions.is_empty() || self.instructions.len() > MAX_SOURCE_CHECK_INSTRUCTIONS {
190            return Err(SchemaContractError::InvalidExpression);
191        }
192        let mut stack_depth = 0usize;
193        for instruction in &self.instructions {
194            match instruction {
195                SourceCheckInstruction::Field(_) => {
196                    stack_depth = stack_depth
197                        .checked_add(1)
198                        .ok_or(SchemaContractError::InvalidExpression)?;
199                }
200                SourceCheckInstruction::Literal(literal) => {
201                    literal.validate()?;
202                    stack_depth = stack_depth
203                        .checked_add(1)
204                        .ok_or(SchemaContractError::InvalidExpression)?;
205                }
206                SourceCheckInstruction::Not
207                | SourceCheckInstruction::IsNull
208                | SourceCheckInstruction::IsNotNull
209                | SourceCheckInstruction::Length => {
210                    if stack_depth < 1 {
211                        return Err(SchemaContractError::InvalidExpression);
212                    }
213                }
214                SourceCheckInstruction::Equal
215                | SourceCheckInstruction::NotEqual
216                | SourceCheckInstruction::LessThan
217                | SourceCheckInstruction::LessThanOrEqual
218                | SourceCheckInstruction::GreaterThan
219                | SourceCheckInstruction::GreaterThanOrEqual
220                | SourceCheckInstruction::And
221                | SourceCheckInstruction::Or => {
222                    if stack_depth < 2 {
223                        return Err(SchemaContractError::InvalidExpression);
224                    }
225                    stack_depth -= 1;
226                }
227            }
228        }
229        if stack_depth != 1 {
230            return Err(SchemaContractError::InvalidExpression);
231        }
232        Ok(())
233    }
234}