1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
//! TypeDB relation trait with default AST generation methods.
use type_bridge_core_lib::ast::{Clause, Constraint, FetchItem, Pattern, RolePlayer, Statement};
use serde::Serialize;
use crate::_entity::OwnedAttributeInfo;
use crate::error::Result;
use crate::filter::Filter;
use crate::value::AttributeValue;
/// Static metadata about a role in a relation type.
#[derive(Debug, Clone, Serialize)]
pub struct RoleInfo {
/// The role name within the relation (e.g. `"employee"`, `"employer"`).
pub role_name: &'static str,
/// The entity type that plays this role (e.g. `"person"`, `"company"`).
pub player_type_name: &'static str,
/// Optional `@doc("...")` documentation annotation on the role (TypeDB 3.12+).
pub doc: Option<&'static str>,
/// `@meta("key", "value")` annotations on the role (TypeDB 3.12+),
/// as key/value pairs. TypeDB allows one value per key per subject.
pub meta: &'static [(&'static str, &'static str)],
}
/// A reference to a role player for use in relation insert/match operations.
///
/// Identifies an entity that plays a specific role in a relation.
/// The entity can be identified by IID (preferred) or by a @key attribute.
#[derive(Debug, Clone, Serialize)]
pub struct RolePlayerRef {
/// The role name this entity plays.
pub role: &'static str,
/// The entity type name of the player.
pub entity_type_name: &'static str,
/// IID-based identification (preferred when available).
pub iid: Option<String>,
/// Key-attribute-based identification (fallback).
pub key: Option<(&'static str, AttributeValue)>,
}
impl RolePlayerRef {
/// Build a match pattern for this role player.
///
/// Produces `$var isa entity_type, iid 0x...` or
/// `$var isa entity_type, has key_attr value`.
fn to_match_pattern(&self, var: &str) -> Pattern {
let mut constraints = Vec::new();
if let Some(ref iid) = self.iid {
constraints.push(Constraint::Iid(iid.clone()));
} else if let Some((attr_name, ref value)) = self.key {
constraints.push(Constraint::Has {
attr_name: attr_name.to_string(),
value: value.to_ast_value(),
});
}
Pattern::Entity {
variable: var.to_string(),
type_name: self.entity_type_name.to_string(),
constraints,
is_strict: false,
}
}
}
/// Trait for TypeDB relation types.
///
/// Implement this for each relation struct to enable CRUD operations via
/// [`RelationManager`](crate::_manager::RelationManager).
///
/// # Required methods
///
/// - [`TYPE_NAME`](Self::TYPE_NAME): The TypeDB relation type name
/// - [`owned_attributes`](Self::owned_attributes): Static attribute metadata
/// - [`role_info`](Self::role_info): Static role metadata
/// - [`iid`](Self::iid) / [`set_iid`](Self::set_iid): Internal identifier access
/// - [`to_attribute_values`](Self::to_attribute_values): Serialize to attribute pairs
/// - [`to_role_player_refs`](Self::to_role_player_refs): Produce role player references
/// - [`from_document`](Self::from_document): Deserialize from JSON
///
/// # Default methods
///
/// All query-building methods have default implementations that construct
/// AST nodes from the required methods above.
pub trait TypeBridgeRelation: Sized + Send + Sync + 'static {
/// The TypeDB relation type name (e.g. `"employment"`, `"friendship"`).
const TYPE_NAME: &'static str;
/// Whether this relation type is abstract.
const IS_ABSTRACT: bool = false;
/// The parent type name if this relation extends another relation type (`sub` in TypeQL).
const PARENT_TYPE: Option<&'static str> = None;
/// Optional `@doc("...")` documentation annotation on the type (TypeDB 3.12+).
const DOC: Option<&'static str> = None;
/// `@meta("key", "value")` annotations on the type (TypeDB 3.12+), as
/// key/value pairs. TypeDB allows one value per key per subject.
const META: &'static [(&'static str, &'static str)] = &[];
/// Static metadata for all owned attributes in declaration order.
fn owned_attributes() -> &'static [OwnedAttributeInfo];
/// Static metadata for all roles in this relation.
fn role_info() -> &'static [RoleInfo];
/// Get the IID (internal identifier) assigned after insert/fetch.
fn iid(&self) -> Option<&str>;
/// Set the IID (called by the manager after insert or fetch).
fn set_iid(&mut self, iid: String);
/// Convert this relation's own attributes to `(attr_name, value)` pairs.
fn to_attribute_values(&self) -> Vec<(&'static str, AttributeValue)>;
/// Produce role player references for insertion.
fn to_role_player_refs(&self) -> Vec<RolePlayerRef>;
/// Hydrate from a flattened JSON attribute map.
fn from_document(doc: &serde_json::Map<String, serde_json::Value>) -> Result<Self>;
// ------------------------------------------------------------------
// Provided methods — default implementations using the above
// ------------------------------------------------------------------
/// Build AST clauses for inserting this relation.
///
/// Produces match clauses for each role player, then an insert clause
/// with the relation type, links, and attributes:
/// ```text
/// match $rp0 isa person, has name "Alice"; $rp1 isa company, has name "Acme";
/// insert $r isa employment, links (employee: $rp0, employer: $rp1), has position "Engineer";
/// ```
fn to_insert_clauses(&self, var: &str) -> Vec<Clause> {
let role_refs = self.to_role_player_refs();
// Build match patterns for role players
let match_patterns: Vec<Pattern> = role_refs
.iter()
.enumerate()
.map(|(i, rp)| rp.to_match_pattern(&format!("$rp{i}")))
.collect();
// Build role player bindings
let role_players: Vec<RolePlayer> = role_refs
.iter()
.enumerate()
.map(|(i, rp)| RolePlayer {
role: rp.role.to_string(),
player_var: format!("$rp{i}"),
})
.collect();
// Build inline attribute statements
let attributes: Vec<Statement> = self
.to_attribute_values()
.into_iter()
.map(|(attr_name, value)| Statement::Has {
subject_var: var.to_string(),
attr_name: attr_name.to_string(),
value: value.to_ast_value(),
})
.collect();
let mut clauses = Vec::new();
if !match_patterns.is_empty() {
clauses.push(Clause::Match(match_patterns));
}
clauses.push(Clause::Insert(vec![Statement::Relation {
variable: var.to_string(),
type_name: Self::TYPE_NAME.to_string(),
role_players,
include_variable: true,
attributes,
}]));
clauses
}
/// Build insert + fetch-IID clauses.
fn to_insert_with_iid_fetch(&self, var: &str) -> Vec<Clause> {
let mut clauses = self.to_insert_clauses(var);
clauses.push(Clause::Fetch(vec![FetchItem::Function {
key: "iid".to_string(),
func_name: "iid".to_string(),
var: var.to_string(),
}]));
clauses
}
/// Build identification constraints for matching.
fn identification_constraints(&self) -> Vec<Constraint> {
if let Some(iid) = self.iid() {
return vec![Constraint::Iid(iid.to_string())];
}
// For relations, use role players for identification if no IID.
// This is less common — relations are typically identified by IID.
vec![]
}
/// Build a match pattern for this relation.
///
/// If the relation has an IID, matches by IID. Otherwise matches
/// by type with role player constraints.
fn to_match_pattern(&self, var: &str) -> Vec<Pattern> {
if let Some(iid) = self.iid() {
return vec![Pattern::Relation {
variable: var.to_string(),
type_name: Self::TYPE_NAME.to_string(),
role_players: vec![],
constraints: vec![Constraint::Iid(iid.to_string())],
}];
}
// Match via role players
let role_refs = self.to_role_player_refs();
let mut patterns = Vec::new();
// Add match patterns for each role player
for (i, rp) in role_refs.iter().enumerate() {
patterns.push(rp.to_match_pattern(&format!("$rp{i}")));
}
// Build relation pattern with role players
let rp_bindings: Vec<RolePlayer> = role_refs
.iter()
.enumerate()
.map(|(i, rp)| RolePlayer {
role: rp.role.to_string(),
player_var: format!("$rp{i}"),
})
.collect();
let attr_constraints: Vec<Constraint> = self
.to_attribute_values()
.into_iter()
.map(|(attr_name, value)| Constraint::Has {
attr_name: attr_name.to_string(),
value: value.to_ast_value(),
})
.collect();
patterns.push(Pattern::Relation {
variable: var.to_string(),
type_name: Self::TYPE_NAME.to_string(),
role_players: rp_bindings,
constraints: attr_constraints,
});
patterns
}
/// Build a polymorphic fetch query for relations.
fn build_polymorphic_fetch(var: &str, type_name: &str, filters: &[Filter]) -> Vec<Clause> {
let constraints: Vec<Constraint> = filters
.iter()
.map(|f| Constraint::Has {
attr_name: f.attr_name.clone(),
value: f.value.to_ast_value(),
})
.collect();
let match_patterns = vec![
Pattern::Relation {
variable: var.to_string(),
type_name: "$t".to_string(),
role_players: vec![],
constraints,
},
Pattern::SubType {
variable: "$t".to_string(),
parent_type: type_name.to_string(),
},
];
let fetch_items = vec![
FetchItem::Function {
key: "_iid".to_string(),
func_name: "iid".to_string(),
var: var.to_string(),
},
FetchItem::Function {
key: "_type".to_string(),
func_name: "label".to_string(),
var: "$t".to_string(),
},
FetchItem::NestedWildcard {
key: "attributes".to_string(),
var: var.to_string(),
},
];
vec![Clause::Match(match_patterns), Clause::Fetch(fetch_items)]
}
}