Skip to main content

yeti_types/schema/
field.rs

1//! Field definition types: index config, vector/HNSW parameters, and relationships.
2
3// ============================================================================
4// Vector / HNSW configuration types
5// ============================================================================
6
7/// Distance metric for vector similarity search.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum VectorDistance {
10    /// Cosine similarity distance (1 - cosine similarity)
11    #[default]
12    Cosine,
13    /// Euclidean distance (L2 norm)
14    Euclidean,
15    /// Dot-product (inner-product) distance.
16    ///
17    /// Ranks by the inner product of two vectors: a larger inner product
18    /// means greater similarity. The HNSW index treats smaller scores as
19    /// closer, so the distance function returns the negated inner product
20    /// (see `dot_product_distance`). For L2-normalized embeddings this
21    /// ranks identically to [`VectorDistance::Cosine`] while skipping the
22    /// per-vector normalization step.
23    DotProduct,
24}
25
26/// HNSW index configuration parameters.
27#[derive(Debug, Clone, PartialEq)]
28pub struct HnswConfig {
29    /// Maximum number of connections per layer (default: 16)
30    pub m: usize,
31    /// Size of dynamic candidate list during construction (default: 100)
32    pub ef_construction: usize,
33    /// Size of dynamic candidate list during search (default: 50)
34    pub ef_search: usize,
35    /// Normalization factor for level generation: 1 / ln(M)
36    pub ml: f32,
37    /// How aggressively to avoid redundant connections (0.0 - 1.0, default: 0.5)
38    pub optimize_routing: f32,
39    /// Distance metric to use
40    pub distance: VectorDistance,
41}
42
43impl Default for HnswConfig {
44    fn default() -> Self {
45        let m = 16;
46        Self {
47            m,
48            ef_construction: 100,
49            ef_search: 50,
50            #[expect(
51                clippy::cast_precision_loss,
52                reason = "HNSW level normalizer: M is bounded by config (typically 8-64); fits in f32"
53            )]
54            ml: 1.0 / (m as f32).ln(),
55            optimize_routing: 0.5,
56            distance: VectorDistance::default(),
57        }
58    }
59}
60
61// ============================================================================
62// IndexConfig
63// ============================================================================
64
65/// Index configuration for a field.
66#[derive(Debug, Clone, PartialEq)]
67pub enum IndexConfig {
68    /// No index
69    None,
70    /// Standard secondary index (hash + range)
71    Standard,
72    /// Full-text search index (inverted index with tokenization)
73    FullText,
74    /// Vector index using HNSW algorithm
75    Vector {
76        /// HNSW index configuration
77        hnsw_config: HnswConfig,
78        /// Source field to auto-embed (None = manual vectors)
79        source: Option<String>,
80        /// Embedding model identifier (None = use default)
81        model: Option<String>,
82    },
83}
84
85impl IndexConfig {
86    /// Check if any index is configured.
87    #[must_use]
88    pub const fn is_indexed(&self) -> bool {
89        !matches!(self, Self::None)
90    }
91
92    /// Check if this is a vector index.
93    #[must_use]
94    pub const fn is_vector(&self) -> bool {
95        matches!(self, Self::Vector { .. })
96    }
97
98    /// Check if this is a full-text search index.
99    #[must_use]
100    pub const fn is_fulltext(&self) -> bool {
101        matches!(self, Self::FullText)
102    }
103}
104
105/// Composite index definition (multi-field index).
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct CompositeIndexDef {
108    /// Auto-generated name from field names (e.g., "`category_price`")
109    pub name: String,
110    /// Ordered list of field names in the composite key
111    pub fields: Vec<String>,
112}
113
114/// Field definition within a table.
115#[derive(Debug, Clone)]
116#[expect(
117    clippy::struct_excessive_bools,
118    reason = "schema-parser output; each bool maps 1:1 to a documented GraphQL directive (@primary, @createdTime, @updatedTime, @expiresAt). Bitflags would force a parallel directive enum and obscure the parser's pattern-match shape."
119)]
120pub struct FieldDefinition {
121    /// Field name (attribute name)
122    pub name: String,
123    /// GraphQL type (ID, String, Int, Boolean, etc.)
124    pub field_type: String,
125    /// Whether this field is the primary key
126    pub is_primary: bool,
127    /// Index configuration for this field
128    pub index_config: IndexConfig,
129    /// Optional relationship configuration for foreign keys
130    pub relationship: Option<RelationshipDefinition>,
131    /// Auto-assign creation timestamp (@createdTime)
132    pub assign_created_time: bool,
133    /// Auto-assign update timestamp (@updatedTime)
134    pub assign_updated_time: bool,
135    /// TTL expiration field (@expiresAt)
136    pub expires_at: bool,
137    /// Computed field expression from `@computed(from: "expr")`
138    pub computed_from: Option<String>,
139    /// Default value from `@default(value: ...)` — type-validated at parse time
140    pub default_value: Option<serde_json::Value>,
141}
142
143impl Default for FieldDefinition {
144    fn default() -> Self {
145        Self {
146            name: String::new(),
147            field_type: "String".to_owned(),
148            is_primary: false,
149            index_config: IndexConfig::None,
150            relationship: None,
151            assign_created_time: false,
152            assign_updated_time: false,
153            expires_at: false,
154            computed_from: None,
155            default_value: None,
156        }
157    }
158}
159
160/// Relationship configuration (Harper-compatible).
161#[derive(Debug, Clone)]
162pub struct RelationshipDefinition {
163    /// Foreign key attribute in this table (many-to-one)
164    pub from_attribute: Option<String>,
165    /// Foreign key attribute in target table (one-to-many)
166    pub to_attribute: Option<String>,
167}