cypher_dto/
relationship.rs

1use crate::{FieldSet, NodeEntity, NodeId, StampMode};
2use neo4rs::{Query, Relation, UnboundedRelation};
3
4/// A relationship entity.
5pub trait RelationEntity: FieldSet + TryFrom<Relation> + TryFrom<UnboundedRelation> {
6    type Id: RelationId<T = Self>;
7
8    /// Get the [RelationId] for this entity.
9    ///
10    /// This is less efficient than using self.into(), but is useful when you
11    /// don't want to consume the entity.
12    ///
13    /// The implementation in derive will clone the individual ID fields as
14    /// necessary.
15    fn identifier(&self) -> Self::Id;
16
17    /// Convenience method for `self.into()`.
18    fn into_identifier(self) -> Self::Id {
19        self.into()
20    }
21
22    fn create<S: NodeEntity, E: NodeEntity>(
23        &self,
24        start: RelationBound<S>,
25        end: RelationBound<E>,
26    ) -> Query {
27        let q = format!(
28            r###"
29          {}
30          {}
31          CREATE (s)-[:{}]->(e)
32          "###,
33            start.to_query_clause("s"),
34            end.to_query_clause("e"),
35            Self::to_query_obj(None, StampMode::Create)
36        );
37        // trace!("creating relation: {}", q);
38        let mut q = Query::new(q);
39        q = start.add_params(q, "s");
40        q = end.add_params(q, "e");
41        self.add_values_to_params(q, None, StampMode::Create)
42    }
43
44    /// Use only for relations that have one or more ID fields, otherwise use the other `update_` methods.
45    ///
46    /// This will update all relations of the same type if [FieldSet::field_names()] is empty.
47    ///
48    /// Treats the current values as the desired values and does a merge update (`SET r += ...`).
49    ///
50    /// NOTE: Does not support changing the identifier fields.
51    fn update(&self) -> Query {
52        assert!(!Self::Id::field_names().is_empty());
53        let q = Query::new(format!(
54            "MATCH ()-[r:{}]-()
55             SET r += {{ {} }}",
56            Self::Id::to_query_obj(None, StampMode::Read),
57            Self::to_query_fields(None, StampMode::Update),
58        ));
59        self.add_values_to_params(q, None, StampMode::Update)
60    }
61
62    /// Treats the current values as the desired values and does a merge update (`SET r += ...`).
63    ///
64    /// NOTE: Does not support changing the identifier fields.
65    fn update_from<T: NodeId>(&self, from: &T) -> Query {
66        let mut q = Query::new(format!(
67            "MATCH (n:{})-[r:{}]-()
68             SET r += {{ {} }}",
69            T::to_query_obj(Some("n"), StampMode::Read),
70            Self::Id::to_query_obj(None, StampMode::Read),
71            Self::to_query_fields(None, StampMode::Update),
72        ));
73        q = from.add_values_to_params(q, Some("n"), StampMode::Read);
74        self.add_values_to_params(q, None, StampMode::Update)
75    }
76
77    /// Treats the current values as the desired values and does a merge update (`SET r += ...`).
78    ///
79    /// NOTE: Does not support changing the identifier fields.
80    fn update_between<S: NodeId, E: NodeId>(&self, start: &S, end: &E) -> Query {
81        let mut q = Query::new(format!(
82            "MATCH (s:{})-[r:{}]-(e:{})
83             SET r += {{ {} }}",
84            S::to_query_obj(Some("s"), StampMode::Read),
85            Self::Id::to_query_obj(None, StampMode::Read),
86            E::to_query_obj(Some("e"), StampMode::Read),
87            Self::to_query_fields(None, StampMode::Update),
88        ));
89        q = start.add_values_to_params(q, Some("s"), StampMode::Read);
90        q = end.add_values_to_params(q, Some("e"), StampMode::Read);
91        self.add_values_to_params(q, None, StampMode::Update)
92    }
93}
94
95/// The identifying fields of a [RelationEntity].
96pub trait RelationId:
97    FieldSet + From<Self::T> + TryFrom<Relation> + TryFrom<UnboundedRelation>
98{
99    type T: RelationEntity<Id = Self>;
100
101    /// Use only for relations that have one or more ID fields, otherwise use the other `read_` methods.
102    ///
103    /// This will read all relations of the same type if [FieldSet::field_names()] is empty.
104    fn read(&self) -> Query {
105        assert!(!Self::field_names().is_empty());
106        let q = Query::new(format!(
107            "MATCH [r:{}] RETURN r",
108            Self::to_query_obj(None, StampMode::Read)
109        ));
110        self.add_values_to_params(q, None, StampMode::Read)
111    }
112    /// Reads relationship(s) connected to a specific node.
113    fn read_from<T: NodeId>(&self, from: &T) -> Query {
114        let mut q = Query::new(format!(
115            "MATCH (n:{})-[r:{}]-()
116             RETURN r",
117            T::to_query_obj(Some("n"), StampMode::Read),
118            Self::to_query_obj(None, StampMode::Read)
119        ));
120        q = from.add_values_to_params(q, Some("n"), StampMode::Read);
121        self.add_values_to_params(q, None, StampMode::Read)
122    }
123    /// Reads relationship(s) connected between two specific nodes.
124    fn read_between<S: NodeId, E: NodeId>(&self, start: &S, end: &E) -> Query {
125        let mut q = Query::new(format!(
126            "MATCH (s:{})-[r:{}]-(e:{})
127             RETURN r",
128            S::to_query_obj(Some("s"), StampMode::Read),
129            Self::to_query_obj(None, StampMode::Read),
130            E::to_query_obj(Some("e"), StampMode::Read),
131        ));
132        q = start.add_values_to_params(q, Some("s"), StampMode::Read);
133        q = end.add_values_to_params(q, Some("e"), StampMode::Read);
134        self.add_values_to_params(q, None, StampMode::Read)
135    }
136    /// Use only for relations that have one or more ID fields, otherwise use the other `delete_` methods.
137    ///
138    /// This will delete all relations of the same type if [FieldSet::field_names()] is empty.
139    fn delete(&self) -> Query {
140        assert!(!Self::field_names().is_empty());
141        let q = Query::new(format!(
142            "MATCH [r:{}] DELETE r",
143            Self::to_query_obj(None, StampMode::Read)
144        ));
145        self.add_values_to_params(q, None, StampMode::Read)
146    }
147    /// Deletes relationship(s) connected to a specific node.
148    fn delete_from<T: NodeId>(&self, from: &T) -> Query {
149        let mut q = Query::new(format!(
150            "MATCH (n:{})-[r:{}]-()
151             DELETE r",
152            T::to_query_obj(Some("n"), StampMode::Read),
153            Self::to_query_obj(None, StampMode::Read)
154        ));
155        q = from.add_values_to_params(q, Some("n"), StampMode::Read);
156        self.add_values_to_params(q, None, StampMode::Read)
157    }
158    /// Deletes relationship(s) connected between two specific nodes.
159    fn delete_between<S: NodeId, E: NodeId>(&self, start: &S, end: &E) -> Query {
160        let mut q = Query::new(format!(
161            "MATCH (s:{})-[r:{}]-(e:{})
162             DELETE r",
163            S::to_query_obj(Some("s"), StampMode::Read),
164            Self::to_query_obj(None, StampMode::Read),
165            E::to_query_obj(Some("e"), StampMode::Read),
166        ));
167        q = start.add_values_to_params(q, Some("s"), StampMode::Read);
168        q = end.add_values_to_params(q, Some("e"), StampMode::Read);
169        self.add_values_to_params(q, None, StampMode::Read)
170    }
171}
172
173/// When creating a relationship, the query has to MATCH, CREATE, or MERGE the
174/// start and end nodes.
175pub enum RelationBound<'a, T: NodeEntity> {
176    Create(&'a T),
177    Match(&'a T::Id),
178    // TODO Cannot complete implementation due to add_params_to_query not having enough info.
179    // Merge(T),
180}
181impl<'a, T: NodeEntity> RelationBound<'a, T> {
182    /// Returns a CREATE (node:...) or MATCH (node:...) clause for this variant.
183    pub fn to_query_clause(&self, prefix: &str) -> String {
184        match self {
185            RelationBound::Create(_) => format!(
186                "CREATE ({}:{})",
187                prefix,
188                T::to_query_obj(Some(prefix), StampMode::Create)
189            ),
190            RelationBound::Match(_) => format!(
191                "MATCH ({}:{})",
192                prefix,
193                T::Id::to_query_obj(Some(prefix), StampMode::Read)
194            ),
195            // RelationBound::Merge(_) => {
196            //     format!(
197            //         r###"
198            //         MERGE ({prefix})
199            //         ON CREATE
200            //             SET {prefix} = {}
201            //         ON MATCH
202            //             SET {prefix} += {}
203            //         "###,
204            //         T::as_query_obj(Some(prefix), StampMode::Create),
205            //         T::as_query_obj(Some(prefix), StampMode::Update),
206            //     )
207            // }
208        }
209    }
210    pub fn add_params(&self, q: Query, prefix: &str) -> Query {
211        match self {
212            RelationBound::Create(t) => t.add_values_to_params(q, Some(prefix), StampMode::Create),
213            RelationBound::Match(id) => id.add_values_to_params(q, Some(prefix), StampMode::Read),
214            // TODO cannot know how to add params due to ON CREATE vs ON MATCH.
215            // RelationBound::Merge(t) => {
216            //     t.add_values_to_params(q, Some(prefix) /*not enough info */)
217            // }
218        }
219    }
220}