Skip to main content

prax_schema/ast/
attribute.rs

1//! Attribute definitions for the Prax schema AST.
2
3use serde::{Deserialize, Serialize};
4use smol_str::SmolStr;
5
6use super::{Ident, Span};
7
8/// An attribute argument value.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub enum AttributeValue {
11    /// A string literal.
12    String(String),
13    /// An integer literal.
14    Int(i64),
15    /// A float literal.
16    Float(f64),
17    /// A boolean literal.
18    Boolean(bool),
19    /// An identifier/constant reference (e.g., enum value).
20    Ident(SmolStr),
21    /// A function call (e.g., `now()`, `uuid()`).
22    Function(SmolStr, Vec<AttributeValue>),
23    /// An array of values.
24    Array(Vec<AttributeValue>),
25    /// A field reference (e.g., `[field_name]`).
26    FieldRef(SmolStr),
27    /// A list of field references (e.g., `[field1, field2]`).
28    FieldRefList(Vec<SmolStr>),
29}
30
31impl AttributeValue {
32    /// Try to get the value as a string.
33    pub fn as_string(&self) -> Option<&str> {
34        match self {
35            Self::String(s) => Some(s),
36            _ => None,
37        }
38    }
39
40    /// Try to get the value as an integer.
41    pub fn as_int(&self) -> Option<i64> {
42        match self {
43            Self::Int(i) => Some(*i),
44            _ => None,
45        }
46    }
47
48    /// Try to get the value as a float.
49    pub fn as_float(&self) -> Option<f64> {
50        match self {
51            Self::Float(f) => Some(*f),
52            Self::Int(i) => Some(*i as f64),
53            _ => None,
54        }
55    }
56
57    /// Try to get the value as a boolean.
58    pub fn as_bool(&self) -> Option<bool> {
59        match self {
60            Self::Boolean(b) => Some(*b),
61            _ => None,
62        }
63    }
64
65    /// Try to get the value as an identifier.
66    pub fn as_ident(&self) -> Option<&str> {
67        match self {
68            Self::Ident(s) => Some(s),
69            _ => None,
70        }
71    }
72}
73
74/// An attribute argument (named or positional).
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
76pub struct AttributeArg {
77    /// Argument name (None for positional arguments).
78    pub name: Option<Ident>,
79    /// Argument value.
80    pub value: AttributeValue,
81    /// Source location.
82    pub span: Span,
83}
84
85impl AttributeArg {
86    /// Create a positional argument.
87    pub fn positional(value: AttributeValue, span: Span) -> Self {
88        Self {
89            name: None,
90            value,
91            span,
92        }
93    }
94
95    /// Create a named argument.
96    pub fn named(name: Ident, value: AttributeValue, span: Span) -> Self {
97        Self {
98            name: Some(name),
99            value,
100            span,
101        }
102    }
103
104    /// Check if this is a positional argument.
105    pub fn is_positional(&self) -> bool {
106        self.name.is_none()
107    }
108}
109
110/// An attribute applied to a field, model, or enum.
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112pub struct Attribute {
113    /// Attribute name (without `@` prefix).
114    pub name: Ident,
115    /// Attribute arguments.
116    pub args: Vec<AttributeArg>,
117    /// Source location (including `@`).
118    pub span: Span,
119}
120
121impl Attribute {
122    /// Create a new attribute.
123    pub fn new(name: Ident, args: Vec<AttributeArg>, span: Span) -> Self {
124        Self { name, args, span }
125    }
126
127    /// Create an attribute with no arguments.
128    pub fn simple(name: Ident, span: Span) -> Self {
129        Self {
130            name,
131            args: vec![],
132            span,
133        }
134    }
135
136    /// Get the attribute name as a string.
137    pub fn name(&self) -> &str {
138        self.name.as_str()
139    }
140
141    /// Check if this attribute has the given name.
142    pub fn is(&self, name: &str) -> bool {
143        self.name.as_str() == name
144    }
145
146    /// Get the first positional argument.
147    pub fn first_arg(&self) -> Option<&AttributeValue> {
148        self.args.first().map(|a| &a.value)
149    }
150
151    /// Get a named argument by name.
152    pub fn get_arg(&self, name: &str) -> Option<&AttributeValue> {
153        self.args
154            .iter()
155            .find(|a| a.name.as_ref().map(|n| n.as_str()) == Some(name))
156            .map(|a| &a.value)
157    }
158
159    /// First positional string-literal argument, or `None`.
160    pub fn first_string_arg(&self) -> Option<&str> {
161        self.args.first().and_then(|a| a.value.as_string())
162    }
163
164    /// First positional identifier argument, or `None`.
165    /// Also accepts a `String` variant so dotted-path args (stored as strings)
166    /// are returned for the plain-ident case when there is no dot.
167    pub fn first_ident_arg(&self) -> Option<&str> {
168        self.args.first().and_then(|a| match &a.value {
169            AttributeValue::Ident(s) => Some(s.as_str()),
170            AttributeValue::String(s) if !s.contains('.') => Some(s.as_str()),
171            _ => None,
172        })
173    }
174
175    /// First positional argument as a dotted path (`"a"` or `"a.b"`).
176    /// Returns `None` if the first arg is neither an ident nor a string.
177    pub fn first_path_arg(&self) -> Option<String> {
178        self.args.first().and_then(|a| match &a.value {
179            AttributeValue::Ident(s) => Some(s.as_str().to_string()),
180            AttributeValue::String(s) => Some(s.clone()),
181            _ => None,
182        })
183    }
184
185    /// Check if this is a field-level attribute.
186    pub fn is_field_attribute(&self) -> bool {
187        matches!(
188            self.name(),
189            "id" | "auto"
190                | "unique"
191                | "index"
192                | "default"
193                | "updated_at"
194                | "omit"
195                | "map"
196                | "db"
197                | "relation"
198                | "generated"
199                | "stored"
200                | "virtual"
201                | "count"
202                | "sum"
203                | "avg"
204                | "min"
205                | "max"
206        )
207    }
208
209    /// Check if this is a model-level attribute (prefixed with `@@`).
210    pub fn is_model_attribute(&self) -> bool {
211        matches!(
212            self.name(),
213            "map" | "index" | "unique" | "id" | "search" | "sql"
214        )
215    }
216}
217
218/// Common field attributes.
219#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
220pub struct FieldAttributes {
221    /// This field is the primary key.
222    pub is_id: bool,
223    /// This field auto-increments (for integer IDs).
224    pub is_auto: bool,
225    /// This field has a unique constraint.
226    pub is_unique: bool,
227    /// This field is indexed.
228    pub is_indexed: bool,
229    /// This field is updated automatically on record update.
230    pub is_updated_at: bool,
231    /// This field should be omitted from default selections.
232    pub is_omit: bool,
233    /// Default value expression.
234    pub default: Option<AttributeValue>,
235    /// Database column name mapping.
236    pub map: Option<String>,
237    /// Native database type (e.g., `@db.VarChar(255)`).
238    pub native_type: Option<NativeType>,
239    /// Relation attributes.
240    pub relation: Option<RelationAttribute>,
241    /// `@generated("...")` attribute, if present.
242    pub generated: Option<GeneratedAttribute>,
243    /// Relation-aggregate attribute (`@count`/`@sum`/etc.), if present.
244    pub aggregate: Option<AggregateAttribute>,
245}
246
247/// Native database type specification.
248#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
249pub struct NativeType {
250    /// Type name (e.g., "VarChar", "Text", "Decimal").
251    pub name: SmolStr,
252    /// Type arguments (e.g., length, precision, scale).
253    pub args: Vec<AttributeValue>,
254}
255
256impl NativeType {
257    /// Create a new native type.
258    pub fn new(name: impl Into<SmolStr>, args: Vec<AttributeValue>) -> Self {
259        Self {
260            name: name.into(),
261            args,
262        }
263    }
264}
265
266/// Relation attribute details.
267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
268pub struct RelationAttribute {
269    /// Relation name (for disambiguation).
270    pub name: Option<String>,
271    /// Fields on this model that reference the other model.
272    pub fields: Vec<SmolStr>,
273    /// Fields on the other model being referenced.
274    pub references: Vec<SmolStr>,
275    /// On delete action.
276    pub on_delete: Option<ReferentialAction>,
277    /// On update action.
278    pub on_update: Option<ReferentialAction>,
279    /// Custom foreign key constraint name in the database.
280    pub map: Option<String>,
281}
282
283/// Aggregate kind for relation-aggregate virtual fields.
284#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
285pub enum AggregateKind {
286    Count,
287    Sum,
288    Avg,
289    Min,
290    Max,
291}
292
293/// `@generated("expr") @stored|@virtual` attribute.
294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
295pub struct GeneratedAttribute {
296    /// SQL expression text. Emitted verbatim into DDL — no dialect translation.
297    pub expression: String,
298    /// True if STORED, false if VIRTUAL. Default is STORED.
299    pub stored: bool,
300}
301
302/// `@count(rel)` / `@sum(rel.field)` / etc.
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
304pub struct AggregateAttribute {
305    pub kind: AggregateKind,
306    /// Outgoing relation name on the parent model.
307    pub relation: SmolStr,
308    /// Target field on the related model. Required for non-Count.
309    pub field: Option<SmolStr>,
310}
311
312/// Referential actions for relations.
313#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
314pub enum ReferentialAction {
315    /// Cascade the operation.
316    Cascade,
317    /// Restrict the operation (error if references exist).
318    Restrict,
319    /// No action (deferred check).
320    NoAction,
321    /// Set to null.
322    SetNull,
323    /// Set to default value.
324    SetDefault,
325}
326
327impl ReferentialAction {
328    /// Parse from string.
329    #[allow(clippy::should_implement_trait)]
330    pub fn from_str(s: &str) -> Option<Self> {
331        match s {
332            "Cascade" => Some(Self::Cascade),
333            "Restrict" => Some(Self::Restrict),
334            "NoAction" => Some(Self::NoAction),
335            "SetNull" => Some(Self::SetNull),
336            "SetDefault" => Some(Self::SetDefault),
337            _ => None,
338        }
339    }
340
341    /// Get the action name.
342    pub fn as_str(&self) -> &'static str {
343        match self {
344            Self::Cascade => "CASCADE",
345            Self::Restrict => "RESTRICT",
346            Self::NoAction => "NO ACTION",
347            Self::SetNull => "SET NULL",
348            Self::SetDefault => "SET DEFAULT",
349        }
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    // ==================== AttributeValue Tests ====================
358
359    #[test]
360    fn test_attribute_value_string() {
361        let val = AttributeValue::String("hello".into());
362        assert_eq!(val.as_string(), Some("hello"));
363        assert_eq!(val.as_int(), None);
364        assert_eq!(val.as_bool(), None);
365        assert_eq!(val.as_ident(), None);
366    }
367
368    #[test]
369    fn test_attribute_value_int() {
370        let val = AttributeValue::Int(42);
371        assert_eq!(val.as_int(), Some(42));
372        assert_eq!(val.as_string(), None);
373        assert_eq!(val.as_bool(), None);
374    }
375
376    #[test]
377    fn test_attribute_value_int_negative() {
378        let val = AttributeValue::Int(-100);
379        assert_eq!(val.as_int(), Some(-100));
380    }
381
382    #[test]
383    #[allow(clippy::approx_constant)]
384    fn test_attribute_value_float() {
385        let val = AttributeValue::Float(3.14);
386        assert_eq!(val.as_int(), None);
387        assert_eq!(val.as_string(), None);
388    }
389
390    #[test]
391    fn test_attribute_value_boolean_true() {
392        let val = AttributeValue::Boolean(true);
393        assert_eq!(val.as_bool(), Some(true));
394        assert_eq!(val.as_int(), None);
395    }
396
397    #[test]
398    fn test_attribute_value_boolean_false() {
399        let val = AttributeValue::Boolean(false);
400        assert_eq!(val.as_bool(), Some(false));
401    }
402
403    #[test]
404    fn test_attribute_value_ident() {
405        let val = AttributeValue::Ident("MyEnum".into());
406        assert_eq!(val.as_ident(), Some("MyEnum"));
407        assert_eq!(val.as_string(), None);
408    }
409
410    #[test]
411    fn test_attribute_value_function() {
412        let val = AttributeValue::Function("now".into(), vec![]);
413        assert_eq!(val.as_string(), None);
414        assert_eq!(val.as_int(), None);
415
416        // Check it's the right variant
417        if let AttributeValue::Function(name, args) = val {
418            assert_eq!(name.as_str(), "now");
419            assert!(args.is_empty());
420        } else {
421            panic!("Expected Function variant");
422        }
423    }
424
425    #[test]
426    fn test_attribute_value_function_with_args() {
427        let val = AttributeValue::Function("uuid".into(), vec![AttributeValue::Int(4)]);
428
429        if let AttributeValue::Function(name, args) = val {
430            assert_eq!(name.as_str(), "uuid");
431            assert_eq!(args.len(), 1);
432        } else {
433            panic!("Expected Function variant");
434        }
435    }
436
437    #[test]
438    fn test_attribute_value_array() {
439        let val = AttributeValue::Array(vec![
440            AttributeValue::String("a".into()),
441            AttributeValue::String("b".into()),
442        ]);
443
444        if let AttributeValue::Array(items) = val {
445            assert_eq!(items.len(), 2);
446        } else {
447            panic!("Expected Array variant");
448        }
449    }
450
451    #[test]
452    fn test_attribute_value_field_ref() {
453        let val = AttributeValue::FieldRef("user_id".into());
454        assert_eq!(val.as_ident(), None);
455
456        if let AttributeValue::FieldRef(name) = val {
457            assert_eq!(name.as_str(), "user_id");
458        } else {
459            panic!("Expected FieldRef variant");
460        }
461    }
462
463    #[test]
464    fn test_attribute_value_field_ref_list() {
465        let val = AttributeValue::FieldRefList(vec!["id".into(), "name".into()]);
466
467        if let AttributeValue::FieldRefList(fields) = val {
468            assert_eq!(fields.len(), 2);
469            assert_eq!(fields[0].as_str(), "id");
470            assert_eq!(fields[1].as_str(), "name");
471        } else {
472            panic!("Expected FieldRefList variant");
473        }
474    }
475
476    #[test]
477    fn test_attribute_value_equality() {
478        let val1 = AttributeValue::Int(42);
479        let val2 = AttributeValue::Int(42);
480        let val3 = AttributeValue::Int(43);
481
482        assert_eq!(val1, val2);
483        assert_ne!(val1, val3);
484    }
485
486    // ==================== AttributeArg Tests ====================
487
488    #[test]
489    fn test_attribute_arg_positional() {
490        let arg = AttributeArg::positional(AttributeValue::Int(42), Span::new(0, 2));
491
492        assert!(arg.is_positional());
493        assert!(arg.name.is_none());
494        assert_eq!(arg.value.as_int(), Some(42));
495    }
496
497    #[test]
498    fn test_attribute_arg_named() {
499        let arg = AttributeArg::named(
500            Ident::new("length", Span::new(0, 6)),
501            AttributeValue::Int(255),
502            Span::new(0, 10),
503        );
504
505        assert!(!arg.is_positional());
506        assert!(arg.name.is_some());
507        assert_eq!(arg.name.as_ref().unwrap().as_str(), "length");
508        assert_eq!(arg.value.as_int(), Some(255));
509    }
510
511    #[test]
512    fn test_attribute_arg_equality() {
513        let arg1 = AttributeArg::positional(AttributeValue::Int(42), Span::new(0, 2));
514        let arg2 = AttributeArg::positional(AttributeValue::Int(42), Span::new(0, 2));
515        let arg3 = AttributeArg::positional(AttributeValue::Int(43), Span::new(0, 2));
516
517        assert_eq!(arg1, arg2);
518        assert_ne!(arg1, arg3);
519    }
520
521    // ==================== Attribute Tests ====================
522
523    #[test]
524    fn test_attribute_new() {
525        let attr = Attribute::new(
526            Ident::new("default", Span::new(0, 7)),
527            vec![AttributeArg::positional(
528                AttributeValue::Int(0),
529                Span::new(8, 9),
530            )],
531            Span::new(0, 10),
532        );
533
534        assert_eq!(attr.name(), "default");
535        assert_eq!(attr.args.len(), 1);
536    }
537
538    #[test]
539    fn test_attribute_simple() {
540        let attr = Attribute::simple(Ident::new("id", Span::new(0, 2)), Span::new(0, 3));
541
542        assert_eq!(attr.name(), "id");
543        assert!(attr.args.is_empty());
544    }
545
546    #[test]
547    fn test_attribute_is() {
548        let attr = Attribute::simple(Ident::new("unique", Span::new(0, 6)), Span::new(0, 7));
549
550        assert!(attr.is("unique"));
551        assert!(!attr.is("id"));
552        assert!(!attr.is("UNIQUE")); // case sensitive
553    }
554
555    #[test]
556    fn test_attribute_first_arg() {
557        let attr = Attribute::new(
558            Ident::new("default", Span::new(0, 7)),
559            vec![
560                AttributeArg::positional(AttributeValue::Int(42), Span::new(8, 10)),
561                AttributeArg::positional(AttributeValue::String("extra".into()), Span::new(12, 19)),
562            ],
563            Span::new(0, 20),
564        );
565
566        assert_eq!(attr.first_arg().unwrap().as_int(), Some(42));
567    }
568
569    #[test]
570    fn test_attribute_first_arg_none() {
571        let attr = Attribute::simple(Ident::new("id", Span::new(0, 2)), Span::new(0, 3));
572        assert!(attr.first_arg().is_none());
573    }
574
575    #[test]
576    fn test_attribute_get_arg() {
577        let attr = Attribute::new(
578            Ident::new("relation", Span::new(0, 8)),
579            vec![
580                AttributeArg::named(
581                    Ident::new("fields", Span::new(9, 15)),
582                    AttributeValue::FieldRefList(vec!["user_id".into()]),
583                    Span::new(9, 30),
584                ),
585                AttributeArg::named(
586                    Ident::new("references", Span::new(32, 42)),
587                    AttributeValue::FieldRefList(vec!["id".into()]),
588                    Span::new(32, 50),
589                ),
590            ],
591            Span::new(0, 51),
592        );
593
594        let fields = attr.get_arg("fields").unwrap();
595        if let AttributeValue::FieldRefList(f) = fields {
596            assert_eq!(f[0].as_str(), "user_id");
597        } else {
598            panic!("Expected FieldRefList");
599        }
600
601        assert!(attr.get_arg("onDelete").is_none());
602    }
603
604    #[test]
605    fn test_attribute_is_field_attribute() {
606        let id_attr = Attribute::simple(Ident::new("id", Span::new(0, 2)), Span::new(0, 3));
607        let auto_attr = Attribute::simple(Ident::new("auto", Span::new(0, 4)), Span::new(0, 5));
608        let unique_attr = Attribute::simple(Ident::new("unique", Span::new(0, 6)), Span::new(0, 7));
609        let index_attr = Attribute::simple(Ident::new("index", Span::new(0, 5)), Span::new(0, 6));
610        let default_attr =
611            Attribute::simple(Ident::new("default", Span::new(0, 7)), Span::new(0, 8));
612        let updated_at_attr =
613            Attribute::simple(Ident::new("updated_at", Span::new(0, 10)), Span::new(0, 11));
614        let omit_attr = Attribute::simple(Ident::new("omit", Span::new(0, 4)), Span::new(0, 5));
615        let map_attr = Attribute::simple(Ident::new("map", Span::new(0, 3)), Span::new(0, 4));
616        let db_attr = Attribute::simple(Ident::new("db", Span::new(0, 2)), Span::new(0, 3));
617        let relation_attr =
618            Attribute::simple(Ident::new("relation", Span::new(0, 8)), Span::new(0, 9));
619        let unknown_attr =
620            Attribute::simple(Ident::new("unknown", Span::new(0, 7)), Span::new(0, 8));
621
622        assert!(id_attr.is_field_attribute());
623        assert!(auto_attr.is_field_attribute());
624        assert!(unique_attr.is_field_attribute());
625        assert!(index_attr.is_field_attribute());
626        assert!(default_attr.is_field_attribute());
627        assert!(updated_at_attr.is_field_attribute());
628        assert!(omit_attr.is_field_attribute());
629        assert!(map_attr.is_field_attribute());
630        assert!(db_attr.is_field_attribute());
631        assert!(relation_attr.is_field_attribute());
632        assert!(!unknown_attr.is_field_attribute());
633    }
634
635    #[test]
636    fn test_attribute_is_model_attribute() {
637        let map_attr = Attribute::simple(Ident::new("map", Span::new(0, 3)), Span::new(0, 4));
638        let index_attr = Attribute::simple(Ident::new("index", Span::new(0, 5)), Span::new(0, 6));
639        let unique_attr = Attribute::simple(Ident::new("unique", Span::new(0, 6)), Span::new(0, 7));
640        let id_attr = Attribute::simple(Ident::new("id", Span::new(0, 2)), Span::new(0, 3));
641        let search_attr = Attribute::simple(Ident::new("search", Span::new(0, 6)), Span::new(0, 7));
642        let sql_attr = Attribute::simple(Ident::new("sql", Span::new(0, 3)), Span::new(0, 4));
643        let unknown_attr =
644            Attribute::simple(Ident::new("unknown", Span::new(0, 7)), Span::new(0, 8));
645
646        assert!(map_attr.is_model_attribute());
647        assert!(index_attr.is_model_attribute());
648        assert!(unique_attr.is_model_attribute());
649        assert!(id_attr.is_model_attribute());
650        assert!(search_attr.is_model_attribute());
651        assert!(sql_attr.is_model_attribute());
652        assert!(!unknown_attr.is_model_attribute());
653    }
654
655    // ==================== FieldAttributes Tests ====================
656
657    #[test]
658    fn test_field_attributes_default() {
659        let attrs = FieldAttributes::default();
660
661        assert!(!attrs.is_id);
662        assert!(!attrs.is_auto);
663        assert!(!attrs.is_unique);
664        assert!(!attrs.is_indexed);
665        assert!(!attrs.is_updated_at);
666        assert!(!attrs.is_omit);
667        assert!(attrs.default.is_none());
668        assert!(attrs.map.is_none());
669        assert!(attrs.native_type.is_none());
670        assert!(attrs.relation.is_none());
671    }
672
673    #[test]
674    fn test_field_attributes_with_values() {
675        let attrs = FieldAttributes {
676            is_id: true,
677            is_auto: true,
678            is_unique: false,
679            is_indexed: false,
680            is_updated_at: false,
681            is_omit: false,
682            default: Some(AttributeValue::Function("auto".into(), vec![])),
683            map: Some("user_id".to_string()),
684            native_type: None,
685            relation: None,
686            generated: None,
687            aggregate: None,
688        };
689
690        assert!(attrs.is_id);
691        assert!(attrs.is_auto);
692        assert!(attrs.default.is_some());
693        assert_eq!(attrs.map, Some("user_id".to_string()));
694    }
695
696    // ==================== NativeType Tests ====================
697
698    #[test]
699    fn test_native_type_new() {
700        let nt = NativeType::new("VarChar", vec![AttributeValue::Int(255)]);
701
702        assert_eq!(nt.name.as_str(), "VarChar");
703        assert_eq!(nt.args.len(), 1);
704        assert_eq!(nt.args[0].as_int(), Some(255));
705    }
706
707    #[test]
708    fn test_native_type_no_args() {
709        let nt = NativeType::new("Text", vec![]);
710
711        assert_eq!(nt.name.as_str(), "Text");
712        assert!(nt.args.is_empty());
713    }
714
715    #[test]
716    fn test_native_type_multiple_args() {
717        let nt = NativeType::new(
718            "Decimal",
719            vec![AttributeValue::Int(10), AttributeValue::Int(2)],
720        );
721
722        assert_eq!(nt.name.as_str(), "Decimal");
723        assert_eq!(nt.args.len(), 2);
724    }
725
726    #[test]
727    fn test_native_type_equality() {
728        let nt1 = NativeType::new("VarChar", vec![AttributeValue::Int(255)]);
729        let nt2 = NativeType::new("VarChar", vec![AttributeValue::Int(255)]);
730        let nt3 = NativeType::new("VarChar", vec![AttributeValue::Int(100)]);
731
732        assert_eq!(nt1, nt2);
733        assert_ne!(nt1, nt3);
734    }
735
736    // ==================== RelationAttribute Tests ====================
737
738    #[test]
739    fn test_relation_attribute() {
740        let rel = RelationAttribute {
741            name: Some("UserPosts".to_string()),
742            fields: vec!["author_id".into()],
743            references: vec!["id".into()],
744            on_delete: Some(ReferentialAction::Cascade),
745            on_update: Some(ReferentialAction::Cascade),
746            map: Some("fk_post_author".to_string()),
747        };
748
749        assert_eq!(rel.name, Some("UserPosts".to_string()));
750        assert_eq!(rel.fields[0].as_str(), "author_id");
751        assert_eq!(rel.references[0].as_str(), "id");
752        assert_eq!(rel.on_delete, Some(ReferentialAction::Cascade));
753        assert_eq!(rel.map, Some("fk_post_author".to_string()));
754    }
755
756    #[test]
757    fn test_relation_attribute_minimal() {
758        let rel = RelationAttribute {
759            name: None,
760            fields: vec![],
761            references: vec![],
762            on_delete: None,
763            on_update: None,
764            map: None,
765        };
766
767        assert!(rel.name.is_none());
768        assert!(rel.fields.is_empty());
769        assert!(rel.map.is_none());
770    }
771
772    // ==================== ReferentialAction Tests ====================
773
774    #[test]
775    fn test_referential_action_from_str_cascade() {
776        assert_eq!(
777            ReferentialAction::from_str("Cascade"),
778            Some(ReferentialAction::Cascade)
779        );
780    }
781
782    #[test]
783    fn test_referential_action_from_str_restrict() {
784        assert_eq!(
785            ReferentialAction::from_str("Restrict"),
786            Some(ReferentialAction::Restrict)
787        );
788    }
789
790    #[test]
791    fn test_referential_action_from_str_no_action() {
792        assert_eq!(
793            ReferentialAction::from_str("NoAction"),
794            Some(ReferentialAction::NoAction)
795        );
796    }
797
798    #[test]
799    fn test_referential_action_from_str_set_null() {
800        assert_eq!(
801            ReferentialAction::from_str("SetNull"),
802            Some(ReferentialAction::SetNull)
803        );
804    }
805
806    #[test]
807    fn test_referential_action_from_str_set_default() {
808        assert_eq!(
809            ReferentialAction::from_str("SetDefault"),
810            Some(ReferentialAction::SetDefault)
811        );
812    }
813
814    #[test]
815    fn test_referential_action_from_str_unknown() {
816        assert_eq!(ReferentialAction::from_str("Unknown"), None);
817        assert_eq!(ReferentialAction::from_str("cascade"), None); // case sensitive
818        assert_eq!(ReferentialAction::from_str(""), None);
819    }
820
821    #[test]
822    fn test_referential_action_as_str() {
823        assert_eq!(ReferentialAction::Cascade.as_str(), "CASCADE");
824        assert_eq!(ReferentialAction::Restrict.as_str(), "RESTRICT");
825        assert_eq!(ReferentialAction::NoAction.as_str(), "NO ACTION");
826        assert_eq!(ReferentialAction::SetNull.as_str(), "SET NULL");
827        assert_eq!(ReferentialAction::SetDefault.as_str(), "SET DEFAULT");
828    }
829
830    #[test]
831    fn test_referential_action_equality() {
832        assert_eq!(ReferentialAction::Cascade, ReferentialAction::Cascade);
833        assert_ne!(ReferentialAction::Cascade, ReferentialAction::Restrict);
834    }
835
836    #[test]
837    fn test_referential_action_copy() {
838        let action = ReferentialAction::Cascade;
839        let copy = action;
840        assert_eq!(action, copy);
841    }
842}