Skip to main content

hedl_core/
document.rs

1// Dweve HEDL - Hierarchical Entity Data Language
2//
3// Copyright (c) 2025 Dweve IP B.V. and individual contributors.
4//
5// SPDX-License-Identifier: Apache-2.0
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the LICENSE file at the
10// root of this repository or at: http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Document structure for parsed HEDL.
19//!
20//! # Memory Optimizations
21//!
22//! This module includes several memory optimizations:
23//! - SmallVec for inline field storage (avoids allocations for ≤4 fields)
24//! - Lazy BTreeMap allocation for children (None until first child added)
25//! - Compact child_count representation (u16 instead of `Option<usize>`)
26//! - Type names can be interned during parsing to reduce duplication
27
28use crate::Value;
29use smallvec::SmallVec;
30use std::collections::BTreeMap;
31
32/// A node in a matrix list.
33///
34/// # Memory Layout Optimizations
35///
36/// Size reduced from ~120 bytes to ~80 bytes per node:
37/// - `fields`: Uses SmallVec to inline up to 4 values (typical case)
38/// - `children`: Lazy allocation - None until first child added (saves 24 bytes for leaf nodes)
39/// - `child_count`: Uses u16 (2 bytes) with 0 = no hint (vs 16 bytes for `Option<usize>`)
40///
41/// For 10,000 nodes: ~400KB saved in struct overhead alone.
42#[derive(Debug, Clone, PartialEq)]
43pub struct Node {
44    /// The type name (from schema).
45    /// During parsing, this can be interned to share memory across all nodes of same type.
46    pub type_name: String,
47    /// The node's ID (first column value).
48    pub id: String,
49    /// Field values (aligned with schema columns).
50    /// SmallVec avoids heap allocation for ≤4 fields (common case: 90%+ of nodes).
51    pub fields: SmallVec<[Value; 4]>,
52    /// Child nodes grouped by type (from NEST relationships).
53    /// Lazy allocation - None until first child added. ~70% of nodes are leaves.
54    pub children: Option<Box<BTreeMap<String, Vec<Node>>>>,
55    /// Count of direct children (for LLM comprehension hints).
56    /// 0 = no hint provided. Max 65,535 children (sufficient for all practical cases).
57    pub child_count: u16,
58}
59
60impl Default for Node {
61    fn default() -> Self {
62        Self {
63            type_name: String::new(),
64            id: String::new(),
65            fields: SmallVec::new(),
66            children: None,
67            child_count: 0,
68        }
69    }
70}
71
72impl Node {
73    /// Create a new node.
74    pub fn new(type_name: impl Into<String>, id: impl Into<String>, fields: Vec<Value>) -> Self {
75        Self {
76            type_name: type_name.into(),
77            id: id.into(),
78            fields: SmallVec::from_vec(fields),
79            children: None,
80            child_count: 0,
81        }
82    }
83
84    /// Get a field value by column index.
85    pub fn get_field(&self, index: usize) -> Option<&Value> {
86        self.fields.get(index)
87    }
88
89    /// Add a child node.
90    /// Uses lazy allocation - children BTreeMap is only created on first add.
91    pub fn add_child(&mut self, child_type: impl Into<String>, child: Node) {
92        let children = self
93            .children
94            .get_or_insert_with(|| Box::new(BTreeMap::new()));
95        children.entry(child_type.into()).or_default().push(child);
96    }
97
98    /// Set the child count hint (for LLM comprehension).
99    /// Saturates at u16::MAX (65,535) if count exceeds.
100    pub fn set_child_count(&mut self, count: usize) {
101        self.child_count = count.min(u16::MAX as usize) as u16;
102    }
103
104    /// Get the child count hint, if provided.
105    pub fn get_child_count(&self) -> Option<usize> {
106        if self.child_count > 0 {
107            Some(self.child_count as usize)
108        } else {
109            None
110        }
111    }
112
113    /// Create a new node with a child count hint.
114    ///
115    /// # Arguments
116    /// - `type_name`: The type name from the schema
117    /// - `fields`: Field values where `fields[0]` MUST be the node ID (a string)
118    /// - `child_count`: Expected number of children (for LLM hints)
119    pub fn with_child_count(
120        type_name: impl Into<String>,
121        id: impl Into<String>,
122        fields: Vec<Value>,
123        child_count: usize,
124    ) -> Self {
125        Self {
126            type_name: type_name.into(),
127            id: id.into(),
128            fields: SmallVec::from_vec(fields),
129            children: None,
130            child_count: child_count.min(u16::MAX as usize) as u16,
131        }
132    }
133
134    /// Get the children map, if any children have been added.
135    pub fn children(&self) -> Option<&BTreeMap<String, Vec<Node>>> {
136        self.children.as_deref()
137    }
138
139    /// Get mutable access to children map, if it exists.
140    pub fn children_mut(&mut self) -> Option<&mut BTreeMap<String, Vec<Node>>> {
141        self.children.as_deref_mut()
142    }
143}
144
145/// A typed matrix list with schema.
146#[derive(Debug, Clone, PartialEq)]
147pub struct MatrixList {
148    /// The type name.
149    pub type_name: String,
150    /// Column names (schema).
151    pub schema: Vec<String>,
152    /// Row data as nodes.
153    pub rows: Vec<Node>,
154    /// Optional count hint for LLM comprehension (e.g., `teams(3):@Team`).
155    pub count_hint: Option<usize>,
156}
157
158impl MatrixList {
159    /// Create a new matrix list.
160    pub fn new(type_name: impl Into<String>, schema: Vec<String>) -> Self {
161        Self {
162            type_name: type_name.into(),
163            schema,
164            rows: Vec::new(),
165            count_hint: None,
166        }
167    }
168
169    /// Create a new matrix list with rows.
170    pub fn with_rows(type_name: impl Into<String>, schema: Vec<String>, rows: Vec<Node>) -> Self {
171        Self {
172            type_name: type_name.into(),
173            schema,
174            rows,
175            count_hint: None,
176        }
177    }
178
179    /// Create a new matrix list with a count hint.
180    pub fn with_count_hint(
181        type_name: impl Into<String>,
182        schema: Vec<String>,
183        count_hint: usize,
184    ) -> Self {
185        Self {
186            type_name: type_name.into(),
187            schema,
188            rows: Vec::new(),
189            count_hint: Some(count_hint),
190        }
191    }
192
193    /// Add a row/node to the list.
194    pub fn add_row(&mut self, node: Node) {
195        self.rows.push(node);
196    }
197
198    /// Get the number of columns.
199    pub fn column_count(&self) -> usize {
200        self.schema.len()
201    }
202}
203
204/// An item in the document body.
205#[derive(Debug, Clone, PartialEq)]
206pub enum Item {
207    /// A scalar value.
208    Scalar(Value),
209    /// A nested object.
210    Object(BTreeMap<String, Item>),
211    /// A matrix list.
212    List(MatrixList),
213}
214
215impl Item {
216    /// Try to get as a scalar value.
217    pub fn as_scalar(&self) -> Option<&Value> {
218        match self {
219            Self::Scalar(v) => Some(v),
220            _ => None,
221        }
222    }
223
224    /// Try to get as an object.
225    pub fn as_object(&self) -> Option<&BTreeMap<String, Item>> {
226        match self {
227            Self::Object(o) => Some(o),
228            _ => None,
229        }
230    }
231
232    /// Try to get as a matrix list.
233    pub fn as_list(&self) -> Option<&MatrixList> {
234        match self {
235            Self::List(l) => Some(l),
236            _ => None,
237        }
238    }
239}
240
241/// A parsed HEDL document.
242#[derive(Debug, Clone, PartialEq)]
243pub struct Document {
244    /// Version (major, minor).
245    pub version: (u32, u32),
246    /// Schema versions for types used in this document (for schema evolution).
247    /// Empty map means no schema versioning is used (legacy behavior).
248    pub schema_versions: BTreeMap<String, crate::schema_version::SchemaVersion>,
249    /// Alias definitions.
250    pub aliases: BTreeMap<String, String>,
251    /// Struct definitions (type -> columns).
252    pub structs: BTreeMap<String, Vec<String>>,
253    /// Nest relationships (parent -> children).
254    /// A parent type can have multiple child types.
255    pub nests: BTreeMap<String, Vec<String>>,
256    /// Root body content.
257    pub root: BTreeMap<String, Item>,
258}
259
260impl Document {
261    /// Create a new empty document.
262    pub fn new(version: (u32, u32)) -> Self {
263        Self {
264            version,
265            schema_versions: BTreeMap::new(),
266            aliases: BTreeMap::new(),
267            structs: BTreeMap::new(),
268            nests: BTreeMap::new(),
269            root: BTreeMap::new(),
270        }
271    }
272
273    /// Get schema version for a type.
274    pub fn get_schema_version(
275        &self,
276        type_name: &str,
277    ) -> Option<crate::schema_version::SchemaVersion> {
278        self.schema_versions.get(type_name).copied()
279    }
280
281    /// Set schema version for a type.
282    pub fn set_schema_version(
283        &mut self,
284        type_name: String,
285        version: crate::schema_version::SchemaVersion,
286    ) {
287        self.schema_versions.insert(type_name, version);
288    }
289
290    /// Get an item from the root by key.
291    pub fn get(&self, key: &str) -> Option<&Item> {
292        self.root.get(key)
293    }
294
295    /// Get a struct schema by type name.
296    pub fn get_schema(&self, type_name: &str) -> Option<&Vec<String>> {
297        self.structs.get(type_name)
298    }
299
300    /// Get the child types for a parent type (from NEST).
301    /// A parent type can have multiple child types (e.g., Customer > Address, Customer > Order).
302    pub fn get_child_types(&self, parent_type: &str) -> Option<&Vec<String>> {
303        self.nests.get(parent_type)
304    }
305
306    /// Expand an alias key to its value.
307    pub fn expand_alias(&self, key: &str) -> Option<&String> {
308        self.aliases.get(key)
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    // ==================== Node tests ====================
317
318    #[test]
319    fn test_node_new() {
320        let node = Node::new("User", "user-1", vec![Value::Int(42)]);
321        assert_eq!(node.type_name, "User");
322        assert_eq!(node.id, "user-1");
323        assert_eq!(node.fields.len(), 1);
324        assert!(node.children.is_none());
325    }
326
327    #[test]
328    fn test_node_get_field() {
329        let node = Node::new(
330            "User",
331            "1",
332            vec![Value::Int(1), Value::String("name".to_string().into())],
333        );
334        assert_eq!(node.get_field(0), Some(&Value::Int(1)));
335        assert_eq!(
336            node.get_field(1),
337            Some(&Value::String("name".to_string().into()))
338        );
339        assert_eq!(node.get_field(2), None);
340    }
341
342    #[test]
343    fn test_node_add_child() {
344        let mut parent = Node::new("User", "1", vec![]);
345        let child = Node::new("Post", "p1", vec![]);
346        parent.add_child("Post", child);
347
348        let children = parent.children().unwrap();
349        assert!(children.contains_key("Post"));
350        assert_eq!(children["Post"].len(), 1);
351    }
352
353    #[test]
354    fn test_node_add_multiple_children_same_type() {
355        let mut parent = Node::new("User", "1", vec![]);
356        parent.add_child("Post", Node::new("Post", "p1", vec![]));
357        parent.add_child("Post", Node::new("Post", "p2", vec![]));
358
359        let children = parent.children().unwrap();
360        assert_eq!(children["Post"].len(), 2);
361    }
362
363    #[test]
364    fn test_node_add_children_different_types() {
365        let mut parent = Node::new("User", "1", vec![]);
366        parent.add_child("Post", Node::new("Post", "p1", vec![]));
367        parent.add_child("Comment", Node::new("Comment", "c1", vec![]));
368
369        let children = parent.children().unwrap();
370        assert_eq!(children.len(), 2);
371        assert!(children.contains_key("Post"));
372        assert!(children.contains_key("Comment"));
373    }
374
375    #[test]
376    fn test_node_equality() {
377        let a = Node::new("User", "1", vec![Value::Int(42)]);
378        let b = Node::new("User", "1", vec![Value::Int(42)]);
379        assert_eq!(a, b);
380    }
381
382    #[test]
383    fn test_node_clone() {
384        let mut original = Node::new("User", "1", vec![Value::Int(42)]);
385        original.add_child("Post", Node::new("Post", "p1", vec![]));
386        let cloned = original.clone();
387        assert_eq!(original, cloned);
388    }
389
390    #[test]
391    fn test_node_debug() {
392        let node = Node::new("User", "1", vec![]);
393        let debug = format!("{:?}", node);
394        assert!(debug.contains("User"));
395        assert!(debug.contains("type_name"));
396    }
397
398    #[test]
399    fn test_node_child_count() {
400        let node = Node::with_child_count("User", "1", vec![], 5);
401        assert_eq!(node.get_child_count(), Some(5));
402    }
403
404    #[test]
405    fn test_node_no_child_count() {
406        let node = Node::new("User", "1", vec![]);
407        assert_eq!(node.get_child_count(), None);
408    }
409
410    #[test]
411    fn test_node_set_child_count() {
412        let mut node = Node::new("User", "1", vec![]);
413        node.set_child_count(10);
414        assert_eq!(node.get_child_count(), Some(10));
415    }
416
417    #[test]
418    fn test_node_child_count_saturation() {
419        let node = Node::with_child_count("User", "1", vec![], 100_000);
420        assert_eq!(node.get_child_count(), Some(65535)); // u16::MAX
421    }
422
423    #[test]
424    fn test_node_lazy_children() {
425        let node = Node::new("User", "1", vec![]);
426        assert!(node.children.is_none()); // Not allocated until first child
427    }
428
429    #[test]
430    fn test_node_children_accessor() {
431        let mut node = Node::new("User", "1", vec![]);
432        assert!(node.children().is_none());
433
434        node.add_child("Post", Node::new("Post", "p1", vec![]));
435        assert!(node.children().is_some());
436        assert!(node.children().unwrap().contains_key("Post"));
437    }
438
439    #[test]
440    fn test_node_children_mut_accessor() {
441        let mut node = Node::new("User", "1", vec![]);
442        node.add_child("Post", Node::new("Post", "p1", vec![]));
443
444        if let Some(children) = node.children_mut() {
445            children.insert("Comment".to_string(), vec![]);
446        }
447
448        assert!(node.children().unwrap().contains_key("Comment"));
449    }
450
451    // ==================== MatrixList tests ====================
452
453    #[test]
454    fn test_matrix_list_new() {
455        let list = MatrixList::new("User", vec!["id".to_string(), "name".to_string()]);
456        assert_eq!(list.type_name, "User");
457        assert_eq!(list.schema, vec!["id", "name"]);
458        assert!(list.rows.is_empty());
459    }
460
461    #[test]
462    fn test_matrix_list_add_row() {
463        let mut list = MatrixList::new("User", vec!["id".to_string()]);
464        list.add_row(Node::new("User", "1", vec![]));
465        assert_eq!(list.rows.len(), 1);
466    }
467
468    #[test]
469    fn test_matrix_list_column_count() {
470        let list = MatrixList::new(
471            "User",
472            vec!["a".to_string(), "b".to_string(), "c".to_string()],
473        );
474        assert_eq!(list.column_count(), 3);
475    }
476
477    #[test]
478    fn test_matrix_list_empty_schema() {
479        let list = MatrixList::new("Empty", vec![]);
480        assert_eq!(list.column_count(), 0);
481    }
482
483    #[test]
484    fn test_matrix_list_equality() {
485        let mut a = MatrixList::new("User", vec!["id".to_string()]);
486        a.add_row(Node::new("User", "1", vec![]));
487        let mut b = MatrixList::new("User", vec!["id".to_string()]);
488        b.add_row(Node::new("User", "1", vec![]));
489        assert_eq!(a, b);
490    }
491
492    #[test]
493    fn test_matrix_list_clone() {
494        let mut list = MatrixList::new("User", vec!["id".to_string()]);
495        list.add_row(Node::new("User", "1", vec![]));
496        let cloned = list.clone();
497        assert_eq!(list, cloned);
498    }
499
500    #[test]
501    fn test_matrix_list_with_rows() {
502        let rows = vec![
503            Node::new("User", "1", vec![]),
504            Node::new("User", "2", vec![]),
505        ];
506        let list = MatrixList::with_rows("User", vec!["id".to_string()], rows);
507        assert_eq!(list.rows.len(), 2);
508    }
509
510    #[test]
511    fn test_matrix_list_with_count_hint() {
512        let list = MatrixList::with_count_hint("User", vec!["id".to_string()], 100);
513        assert_eq!(list.count_hint, Some(100));
514    }
515
516    // ==================== Item tests ====================
517
518    #[test]
519    fn test_item_scalar() {
520        let item = Item::Scalar(Value::Int(42));
521        assert!(item.as_scalar().is_some());
522        assert!(item.as_object().is_none());
523        assert!(item.as_list().is_none());
524    }
525
526    #[test]
527    fn test_item_object() {
528        let mut obj = BTreeMap::new();
529        obj.insert("key".to_string(), Item::Scalar(Value::Int(1)));
530        let item = Item::Object(obj);
531        assert!(item.as_object().is_some());
532        assert!(item.as_scalar().is_none());
533    }
534
535    #[test]
536    fn test_item_list() {
537        let list = MatrixList::new("User", vec!["id".to_string()]);
538        let item = Item::List(list);
539        assert!(item.as_list().is_some());
540        assert!(item.as_scalar().is_none());
541    }
542
543    #[test]
544    fn test_item_as_scalar_returns_value() {
545        let item = Item::Scalar(Value::String("hello".to_string().into()));
546        let value = item.as_scalar().unwrap();
547        assert_eq!(value.as_str(), Some("hello"));
548    }
549
550    #[test]
551    fn test_item_as_object_returns_map() {
552        let mut obj = BTreeMap::new();
553        obj.insert("a".to_string(), Item::Scalar(Value::Int(1)));
554        let item = Item::Object(obj);
555        let map = item.as_object().unwrap();
556        assert!(map.contains_key("a"));
557    }
558
559    #[test]
560    fn test_item_equality() {
561        let a = Item::Scalar(Value::Int(42));
562        let b = Item::Scalar(Value::Int(42));
563        assert_eq!(a, b);
564    }
565
566    #[test]
567    fn test_item_clone() {
568        let item = Item::Scalar(Value::String("test".to_string().into()));
569        let cloned = item.clone();
570        assert_eq!(item, cloned);
571    }
572
573    // ==================== Document tests ====================
574
575    #[test]
576    fn test_document_new() {
577        let doc = Document::new((2, 0));
578        assert_eq!(doc.version, (2, 0));
579        assert!(doc.aliases.is_empty());
580        assert!(doc.structs.is_empty());
581        assert!(doc.nests.is_empty());
582        assert!(doc.root.is_empty());
583    }
584
585    #[test]
586    fn test_document_get() {
587        let mut doc = Document::new((2, 0));
588        doc.root
589            .insert("key".to_string(), Item::Scalar(Value::Int(42)));
590        assert!(doc.get("key").is_some());
591        assert!(doc.get("missing").is_none());
592    }
593
594    #[test]
595    fn test_document_get_schema() {
596        let mut doc = Document::new((2, 0));
597        doc.structs.insert(
598            "User".to_string(),
599            vec!["id".to_string(), "name".to_string()],
600        );
601        let schema = doc.get_schema("User").unwrap();
602        assert_eq!(schema, &vec!["id".to_string(), "name".to_string()]);
603        assert!(doc.get_schema("Missing").is_none());
604    }
605
606    #[test]
607    fn test_document_get_child_types() {
608        let mut doc = Document::new((2, 0));
609        doc.nests
610            .insert("User".to_string(), vec!["Post".to_string()]);
611        assert_eq!(doc.get_child_types("User"), Some(&vec!["Post".to_string()]));
612        assert!(doc.get_child_types("Post").is_none());
613    }
614
615    #[test]
616    fn test_document_expand_alias() {
617        let mut doc = Document::new((2, 0));
618        doc.aliases.insert("active".to_string(), "true".to_string());
619        assert_eq!(doc.expand_alias("active"), Some(&"true".to_string()));
620        assert!(doc.expand_alias("missing").is_none());
621    }
622
623    #[test]
624    fn test_document_equality() {
625        let a = Document::new((2, 0));
626        let b = Document::new((2, 0));
627        assert_eq!(a, b);
628    }
629
630    #[test]
631    fn test_document_clone() {
632        let mut doc = Document::new((2, 0));
633        doc.aliases.insert("key".to_string(), "value".to_string());
634        let cloned = doc.clone();
635        assert_eq!(doc, cloned);
636    }
637
638    #[test]
639    fn test_document_debug() {
640        let doc = Document::new((2, 0));
641        let debug = format!("{:?}", doc);
642        assert!(debug.contains("version"));
643        assert!(debug.contains("aliases"));
644    }
645
646    // ==================== Edge cases ====================
647
648    #[test]
649    fn test_node_empty_fields() {
650        let node = Node::new("Type", "id", vec![]);
651        assert!(node.fields.is_empty());
652        assert!(node.get_field(0).is_none());
653    }
654
655    #[test]
656    fn test_node_unicode_id() {
657        let node = Node::new("User", "日本語", vec![]);
658        assert_eq!(node.id, "日本語");
659    }
660
661    #[test]
662    fn test_document_version_zero() {
663        let doc = Document::new((0, 0));
664        assert_eq!(doc.version, (0, 0));
665    }
666
667    #[test]
668    fn test_document_large_version() {
669        let doc = Document::new((999, 999));
670        assert_eq!(doc.version, (999, 999));
671    }
672
673    #[test]
674    fn test_nested_items() {
675        let mut inner = BTreeMap::new();
676        inner.insert("nested".to_string(), Item::Scalar(Value::Int(42)));
677
678        let mut outer = BTreeMap::new();
679        outer.insert("inner".to_string(), Item::Object(inner));
680
681        let item = Item::Object(outer);
682        let obj = item.as_object().unwrap();
683        let inner_item = obj.get("inner").unwrap();
684        let inner_obj = inner_item.as_object().unwrap();
685        assert!(inner_obj.contains_key("nested"));
686    }
687
688    #[test]
689    fn test_deeply_nested_nodes() {
690        let mut root = Node::new("A", "a", vec![]);
691        let mut child = Node::new("B", "b", vec![]);
692        child.add_child("C", Node::new("C", "c", vec![]));
693        root.add_child("B", child);
694
695        let children = root.children().unwrap();
696        assert_eq!(children["B"].len(), 1);
697        let b_children = children["B"][0].children().unwrap();
698        assert_eq!(b_children["C"].len(), 1);
699    }
700
701    #[test]
702    fn test_node_default() {
703        let node = Node::default();
704        assert_eq!(node.type_name, "");
705        assert!(node.fields.is_empty());
706        assert!(node.children.is_none());
707        assert_eq!(node.child_count, 0);
708    }
709
710    // ==================== Document schema_versions tests ====================
711
712    #[test]
713    fn test_document_schema_versions_empty() {
714        let doc = Document::new((2, 0));
715        assert!(doc.schema_versions.is_empty());
716    }
717
718    #[test]
719    fn test_document_set_schema_version() {
720        use crate::schema_version::SchemaVersion;
721        let mut doc = Document::new((2, 0));
722        doc.set_schema_version("User".to_string(), SchemaVersion::new(1, 2, 3));
723        assert_eq!(doc.schema_versions.len(), 1);
724        assert!(doc.schema_versions.contains_key("User"));
725    }
726
727    #[test]
728    fn test_document_get_schema_version() {
729        use crate::schema_version::SchemaVersion;
730        let mut doc = Document::new((2, 0));
731        let version = SchemaVersion::new(2, 0, 0);
732        doc.set_schema_version("Post".to_string(), version);
733        assert_eq!(doc.get_schema_version("Post"), Some(version));
734        assert_eq!(doc.get_schema_version("User"), None);
735    }
736
737    #[test]
738    fn test_document_multiple_schema_versions() {
739        use crate::schema_version::SchemaVersion;
740        let mut doc = Document::new((2, 0));
741        doc.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
742        doc.set_schema_version("Post".to_string(), SchemaVersion::new(2, 1, 0));
743        doc.set_schema_version("Comment".to_string(), SchemaVersion::new(1, 5, 2));
744
745        assert_eq!(doc.schema_versions.len(), 3);
746        assert_eq!(
747            doc.get_schema_version("User"),
748            Some(SchemaVersion::new(1, 0, 0))
749        );
750        assert_eq!(
751            doc.get_schema_version("Post"),
752            Some(SchemaVersion::new(2, 1, 0))
753        );
754        assert_eq!(
755            doc.get_schema_version("Comment"),
756            Some(SchemaVersion::new(1, 5, 2))
757        );
758    }
759
760    #[test]
761    fn test_document_replace_schema_version() {
762        use crate::schema_version::SchemaVersion;
763        let mut doc = Document::new((2, 0));
764        doc.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
765        doc.set_schema_version("User".to_string(), SchemaVersion::new(2, 0, 0));
766
767        assert_eq!(doc.schema_versions.len(), 1);
768        assert_eq!(
769            doc.get_schema_version("User"),
770            Some(SchemaVersion::new(2, 0, 0))
771        );
772    }
773
774    #[test]
775    fn test_document_schema_version_with_clone() {
776        use crate::schema_version::SchemaVersion;
777        let mut doc = Document::new((2, 0));
778        doc.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
779        let cloned = doc.clone();
780
781        assert_eq!(cloned.schema_versions.len(), 1);
782        assert_eq!(
783            cloned.get_schema_version("User"),
784            Some(SchemaVersion::new(1, 0, 0))
785        );
786    }
787
788    #[test]
789    fn test_document_schema_version_equality() {
790        use crate::schema_version::SchemaVersion;
791        let mut a = Document::new((2, 0));
792        a.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
793
794        let mut b = Document::new((2, 0));
795        b.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
796
797        assert_eq!(a, b);
798    }
799
800    #[test]
801    fn test_document_schema_version_inequality() {
802        use crate::schema_version::SchemaVersion;
803        let mut a = Document::new((2, 0));
804        a.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
805
806        let mut b = Document::new((2, 0));
807        b.set_schema_version("User".to_string(), SchemaVersion::new(2, 0, 0));
808
809        assert_ne!(a, b);
810    }
811}