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
//! TypeDB entity trait with default AST generation methods.
use type_bridge_core_lib::ast::{Clause, Constraint, FetchItem, Pattern, Statement};
use serde::{Deserialize, Serialize};
use crate::_attribute::ValueType;
use crate::error::Result;
use crate::filter::Filter;
use crate::value::AttributeValue;
/// Ownership annotation on an attribute (mirrors TypeDB `@key`, `@unique`, `@card`, `@distinct`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Annotation {
/// `@key` — unique identifier attribute.
Key,
/// `@unique` — unique but not identifier.
Unique,
/// `@card(min, max)` — cardinality constraint. `None` max = unbounded.
Card(u32, Option<u32>),
/// `@distinct` — unique values within a list attribute ownership.
///
/// Only valid on ordered ownerships (`owns name[]`). Projected create and
/// hydration paths enforce canonical member uniqueness before provider I/O
/// or before returning hydrated output, respectively.
Distinct,
}
/// Metadata about one owned attribute on an entity type.
#[derive(Debug, Clone, Serialize)]
pub struct OwnedAttributeInfo {
/// TypeDB attribute type name (e.g. `"name"`, `"age"`).
pub attr_name: &'static str,
/// TypeDB value type.
pub value_type: ValueType,
/// Ownership annotations (e.g. `@key`, `@unique`, `@card`).
pub annotations: &'static [Annotation],
/// Optional `@doc("...")` documentation annotation on the ownership (TypeDB 3.12+).
pub doc: Option<&'static str>,
/// `@meta("key", "value")` annotations on the ownership (TypeDB 3.12+),
/// as key/value pairs. TypeDB allows one value per key per subject.
pub meta: &'static [(&'static str, &'static str)],
}
impl OwnedAttributeInfo {
/// Whether this attribute has a `@key` annotation.
pub fn is_key(&self) -> bool {
self.annotations
.iter()
.any(|a| matches!(a, Annotation::Key))
}
/// Whether this attribute has a `@unique` annotation.
pub fn is_unique(&self) -> bool {
self.annotations
.iter()
.any(|a| matches!(a, Annotation::Unique))
}
/// Get the cardinality constraint, if any.
pub fn cardinality(&self) -> Option<(u32, Option<u32>)> {
self.annotations.iter().find_map(|a| match a {
Annotation::Card(min, max) => Some((*min, *max)),
_ => None,
})
}
}
/// Trait for TypeDB entity types.
///
/// Implement this for each entity struct to enable CRUD operations via
/// [`EntityManager`](crate::_manager::EntityManager). Phase 1 uses manual
/// implementations; derive macros will be added in a later phase.
///
/// # Required methods
///
/// - [`TYPE_NAME`](Self::TYPE_NAME): The TypeDB type name
/// - [`owned_attributes`](Self::owned_attributes): Static attribute metadata
/// - [`iid`](Self::iid) / [`set_iid`](Self::set_iid): Internal identifier access
/// - [`to_attribute_values`](Self::to_attribute_values): Serialize to attribute pairs
/// - [`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 TypeBridgeEntity: Sized + Send + Sync + 'static {
/// The TypeDB entity type name (e.g. `"person"`, `"company"`).
const TYPE_NAME: &'static str;
/// Whether this entity type is abstract (cannot be directly instantiated in TypeDB).
const IS_ABSTRACT: bool = false;
/// The parent type name if this entity extends another entity 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];
/// 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 entity to a list of `(attr_name, value)` pairs.
///
/// Used to build INSERT statements. Optional attributes should be
/// omitted from the returned list when `None`.
fn to_attribute_values(&self) -> Vec<(&'static str, AttributeValue)>;
/// Hydrate from a flattened JSON attribute map.
///
/// The map contains `attr_name -> json_value` pairs extracted from
/// a TypeDB fetch result. Used by the hydration layer to construct
/// typed Rust structs from query results.
fn from_document(doc: &serde_json::Map<String, serde_json::Value>) -> Result<Self>;
// ------------------------------------------------------------------
// Provided methods — default implementations using the above
// ------------------------------------------------------------------
/// Build AST insert clauses for this entity.
///
/// Produces: `insert $var isa TYPE, has attr1 val1, has attr2 val2;`
fn to_insert_clauses(&self, var: &str) -> Vec<Clause> {
let mut statements = vec![Statement::Isa {
variable: var.to_string(),
type_name: Self::TYPE_NAME.to_string(),
}];
for (attr_name, value) in self.to_attribute_values() {
statements.push(Statement::Has {
subject_var: var.to_string(),
attr_name: attr_name.to_string(),
value: value.to_ast_value(),
});
}
vec![Clause::Insert(statements)]
}
/// Build insert + fetch-IID clauses.
///
/// Produces:
/// ```text
/// insert $var isa person, has name "Alice", has age 30;
/// fetch { "iid": iid($var) };
/// ```
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 (IID preferred, then @key attrs).
fn identification_constraints(&self) -> Vec<Constraint> {
if let Some(iid) = self.iid() {
return vec![Constraint::Iid(iid.to_string())];
}
let key_attrs: Vec<&'static str> = Self::owned_attributes()
.iter()
.filter(|a| a.is_key())
.map(|a| a.attr_name)
.collect();
self.to_attribute_values()
.into_iter()
.filter(|(name, _)| key_attrs.contains(name))
.map(|(attr_name, value)| Constraint::Has {
attr_name: attr_name.to_string(),
value: value.to_ast_value(),
})
.collect()
}
/// Build a match pattern for this entity (for delete, get_one, etc.).
fn to_match_pattern(&self, var: &str) -> Pattern {
Pattern::Entity {
variable: var.to_string(),
type_name: Self::TYPE_NAME.to_string(),
constraints: self.identification_constraints(),
is_strict: false,
}
}
/// Build a polymorphic fetch query with `isa!` type variable resolution.
///
/// Produces:
/// ```text
/// match $var isa! $t, has name "Alice"; $t sub person;
/// fetch { "_iid": iid($var), "_type": label($t), "attributes": { $var.* } };
/// ```
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::Entity {
variable: var.to_string(),
type_name: "$t".to_string(),
constraints,
is_strict: true,
},
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)]
}
}