Skip to main content

dakera_client/
types.rs

1//! Types for the Dakera client SDK
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6// ============================================================================
7// Retry & Timeout Configuration
8// ============================================================================
9
10/// Configuration for request retry behavior with exponential backoff.
11#[derive(Debug, Clone)]
12pub struct RetryConfig {
13    /// Maximum number of retry attempts (default: 3).
14    pub max_retries: u32,
15    /// Base delay before the first retry (default: 100ms).
16    pub base_delay: std::time::Duration,
17    /// Maximum delay between retries (default: 60s).
18    pub max_delay: std::time::Duration,
19    /// Whether to add random jitter to backoff delay (default: true).
20    pub jitter: bool,
21}
22
23impl Default for RetryConfig {
24    fn default() -> Self {
25        Self {
26            max_retries: 3,
27            base_delay: std::time::Duration::from_millis(100),
28            max_delay: std::time::Duration::from_secs(60),
29            jitter: true,
30        }
31    }
32}
33
34// ============================================================================
35// OPS-1: Rate-Limit Headers
36// ============================================================================
37
38/// Rate-limit and quota headers present on every API response (OPS-1).
39///
40/// Fields are `None` when the server does not include the header (e.g.
41/// non-namespaced endpoints where quota does not apply).
42#[derive(Debug, Clone, Default)]
43pub struct RateLimitHeaders {
44    /// `X-RateLimit-Limit` — max requests allowed in the current window.
45    pub limit: Option<u64>,
46    /// `X-RateLimit-Remaining` — requests left in the current window.
47    pub remaining: Option<u64>,
48    /// `X-RateLimit-Reset` — Unix timestamp (seconds) when the window resets.
49    pub reset: Option<u64>,
50    /// `X-Quota-Used` — namespace vectors / storage consumed.
51    pub quota_used: Option<u64>,
52    /// `X-Quota-Limit` — namespace quota ceiling.
53    pub quota_limit: Option<u64>,
54}
55
56impl RateLimitHeaders {
57    /// Parse rate-limit headers from a `reqwest::Response`.
58    pub fn from_response(response: &reqwest::Response) -> Self {
59        let headers = response.headers();
60        fn parse(h: &reqwest::header::HeaderMap, name: &str) -> Option<u64> {
61            h.get(name)
62                .and_then(|v| v.to_str().ok())
63                .and_then(|s| s.parse().ok())
64        }
65        Self {
66            limit: parse(headers, "X-RateLimit-Limit"),
67            remaining: parse(headers, "X-RateLimit-Remaining"),
68            reset: parse(headers, "X-RateLimit-Reset"),
69            quota_used: parse(headers, "X-Quota-Used"),
70            quota_limit: parse(headers, "X-Quota-Limit"),
71        }
72    }
73}
74
75// ============================================================================
76// Health & Status Types
77// ============================================================================
78
79/// Health check response
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct HealthResponse {
82    /// Overall health status
83    pub healthy: bool,
84    /// Service version
85    pub version: Option<String>,
86    /// Uptime in seconds
87    pub uptime_seconds: Option<u64>,
88}
89
90/// Readiness check response
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ReadinessResponse {
93    /// Is the service ready to accept requests
94    pub ready: bool,
95    /// Component status details
96    pub components: Option<HashMap<String, bool>>,
97}
98
99// ============================================================================
100// Namespace Types
101// ============================================================================
102
103/// Namespace information
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct NamespaceInfo {
106    /// Namespace name
107    #[serde(alias = "namespace")]
108    pub name: String,
109    /// Number of vectors in the namespace
110    pub vector_count: u64,
111    /// Vector dimensions
112    #[serde(alias = "dimension")]
113    pub dimensions: Option<u32>,
114    /// Index type used
115    pub index_type: Option<String>,
116}
117
118/// List namespaces response
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct ListNamespacesResponse {
121    /// List of namespace names
122    pub namespaces: Vec<String>,
123}
124
125// ============================================================================
126// Vector Types
127// ============================================================================
128
129/// A vector with ID and optional metadata
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct Vector {
132    /// Unique vector identifier
133    pub id: String,
134    /// Vector values (embeddings)
135    pub values: Vec<f32>,
136    /// Optional metadata
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub metadata: Option<HashMap<String, serde_json::Value>>,
139}
140
141impl Vector {
142    /// Create a new vector with just ID and values
143    pub fn new(id: impl Into<String>, values: Vec<f32>) -> Self {
144        Self {
145            id: id.into(),
146            values,
147            metadata: None,
148        }
149    }
150
151    /// Create a new vector with metadata
152    pub fn with_metadata(
153        id: impl Into<String>,
154        values: Vec<f32>,
155        metadata: HashMap<String, serde_json::Value>,
156    ) -> Self {
157        Self {
158            id: id.into(),
159            values,
160            metadata: Some(metadata),
161        }
162    }
163}
164
165/// Upsert vectors request
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct UpsertRequest {
168    /// Vectors to upsert
169    pub vectors: Vec<Vector>,
170}
171
172impl UpsertRequest {
173    /// Create a new upsert request with a single vector
174    pub fn single(vector: Vector) -> Self {
175        Self {
176            vectors: vec![vector],
177        }
178    }
179
180    /// Create a new upsert request with multiple vectors
181    pub fn batch(vectors: Vec<Vector>) -> Self {
182        Self { vectors }
183    }
184}
185
186/// Upsert response
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct UpsertResponse {
189    /// Number of vectors upserted
190    pub upserted_count: u64,
191}
192
193/// Column-based upsert request (Turbopuffer-inspired)
194///
195/// This format is more efficient for bulk upserts as it avoids repeating
196/// field names for each vector. All arrays must have equal length.
197///
198/// # Example
199///
200/// ```rust
201/// use dakera_client::ColumnUpsertRequest;
202/// use std::collections::HashMap;
203///
204/// let request = ColumnUpsertRequest::new(
205///     vec!["id1".to_string(), "id2".to_string()],
206///     vec![vec![0.1, 0.2, 0.3], vec![0.4, 0.5, 0.6]],
207/// );
208/// ```
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct ColumnUpsertRequest {
211    /// Array of vector IDs (required)
212    pub ids: Vec<String>,
213    /// Array of vectors (required for vector namespaces)
214    pub vectors: Vec<Vec<f32>>,
215    /// Additional attributes as columns (optional)
216    /// Each key is an attribute name, value is array of attribute values
217    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
218    pub attributes: HashMap<String, Vec<serde_json::Value>>,
219    /// TTL in seconds for all vectors (optional)
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub ttl_seconds: Option<u64>,
222    /// Expected dimension (optional, for validation)
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub dimension: Option<usize>,
225}
226
227impl ColumnUpsertRequest {
228    /// Create a new column upsert request
229    pub fn new(ids: Vec<String>, vectors: Vec<Vec<f32>>) -> Self {
230        Self {
231            ids,
232            vectors,
233            attributes: HashMap::new(),
234            ttl_seconds: None,
235            dimension: None,
236        }
237    }
238
239    /// Add an attribute column
240    pub fn with_attribute(
241        mut self,
242        name: impl Into<String>,
243        values: Vec<serde_json::Value>,
244    ) -> Self {
245        self.attributes.insert(name.into(), values);
246        self
247    }
248
249    /// Set TTL for all vectors
250    pub fn with_ttl(mut self, seconds: u64) -> Self {
251        self.ttl_seconds = Some(seconds);
252        self
253    }
254
255    /// Set expected dimension for validation
256    pub fn with_dimension(mut self, dim: usize) -> Self {
257        self.dimension = Some(dim);
258        self
259    }
260
261    /// Get the number of vectors in this request
262    pub fn len(&self) -> usize {
263        self.ids.len()
264    }
265
266    /// Check if the request is empty
267    pub fn is_empty(&self) -> bool {
268        self.ids.is_empty()
269    }
270}
271
272/// Delete vectors request
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct DeleteRequest {
275    /// Vector IDs to delete
276    pub ids: Vec<String>,
277}
278
279impl DeleteRequest {
280    /// Create a delete request for a single ID
281    pub fn single(id: impl Into<String>) -> Self {
282        Self {
283            ids: vec![id.into()],
284        }
285    }
286
287    /// Create a delete request for multiple IDs
288    pub fn batch(ids: Vec<String>) -> Self {
289        Self { ids }
290    }
291}
292
293/// Delete response
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct DeleteResponse {
296    /// Number of vectors deleted
297    pub deleted_count: u64,
298}
299
300// ============================================================================
301// Query Types
302// ============================================================================
303
304/// Read consistency level for queries (Turbopuffer-inspired)
305#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
306#[serde(rename_all = "snake_case")]
307pub enum ReadConsistency {
308    /// Always read from primary/leader node - guarantees latest data
309    Strong,
310    /// Read from any replica - may return slightly stale data but faster
311    #[default]
312    Eventual,
313    /// Read from replicas within staleness bounds
314    #[serde(rename = "bounded_staleness")]
315    BoundedStaleness,
316}
317
318/// Configuration for bounded staleness reads
319#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
320pub struct StalenessConfig {
321    /// Maximum acceptable staleness in milliseconds
322    #[serde(default = "default_max_staleness_ms")]
323    pub max_staleness_ms: u64,
324}
325
326fn default_max_staleness_ms() -> u64 {
327    5000 // 5 seconds default
328}
329
330impl StalenessConfig {
331    /// Create a new staleness config with specified max staleness
332    pub fn new(max_staleness_ms: u64) -> Self {
333        Self { max_staleness_ms }
334    }
335}
336
337/// Distance metric for similarity search
338#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
339#[serde(rename_all = "snake_case")]
340pub enum DistanceMetric {
341    /// Cosine similarity (default)
342    #[default]
343    Cosine,
344    /// Euclidean distance
345    Euclidean,
346    /// Dot product
347    DotProduct,
348}
349
350/// Query request for vector similarity search
351#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct QueryRequest {
353    /// Query vector
354    pub vector: Vec<f32>,
355    /// Number of results to return
356    pub top_k: u32,
357    /// Distance metric to use
358    #[serde(default)]
359    pub distance_metric: DistanceMetric,
360    /// Optional filter expression
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub filter: Option<serde_json::Value>,
363    /// Whether to include metadata in results
364    #[serde(default = "default_true")]
365    pub include_metadata: bool,
366    /// Whether to include vector values in results
367    #[serde(default)]
368    pub include_vectors: bool,
369    /// Read consistency level
370    #[serde(default)]
371    pub consistency: ReadConsistency,
372    /// Staleness configuration for bounded staleness reads
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub staleness_config: Option<StalenessConfig>,
375}
376
377fn default_true() -> bool {
378    true
379}
380
381impl QueryRequest {
382    /// Create a new query request
383    pub fn new(vector: Vec<f32>, top_k: u32) -> Self {
384        Self {
385            vector,
386            top_k,
387            distance_metric: DistanceMetric::default(),
388            filter: None,
389            include_metadata: true,
390            include_vectors: false,
391            consistency: ReadConsistency::default(),
392            staleness_config: None,
393        }
394    }
395
396    /// Add a filter to the query
397    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
398        self.filter = Some(filter);
399        self
400    }
401
402    /// Set whether to include metadata
403    pub fn include_metadata(mut self, include: bool) -> Self {
404        self.include_metadata = include;
405        self
406    }
407
408    /// Set whether to include vector values
409    pub fn include_vectors(mut self, include: bool) -> Self {
410        self.include_vectors = include;
411        self
412    }
413
414    /// Set distance metric
415    pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
416        self.distance_metric = metric;
417        self
418    }
419
420    /// Set read consistency level
421    pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
422        self.consistency = consistency;
423        self
424    }
425
426    /// Set bounded staleness with max staleness in ms
427    pub fn with_bounded_staleness(mut self, max_staleness_ms: u64) -> Self {
428        self.consistency = ReadConsistency::BoundedStaleness;
429        self.staleness_config = Some(StalenessConfig::new(max_staleness_ms));
430        self
431    }
432
433    /// Use strong consistency (always read from primary)
434    pub fn with_strong_consistency(mut self) -> Self {
435        self.consistency = ReadConsistency::Strong;
436        self
437    }
438}
439
440/// A match result from a query
441#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct Match {
443    /// Vector ID
444    pub id: String,
445    /// Similarity score
446    pub score: f32,
447    /// Optional metadata
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub metadata: Option<HashMap<String, serde_json::Value>>,
450}
451
452/// Query response
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct QueryResponse {
455    /// Matched vectors
456    pub matches: Vec<Match>,
457}
458
459// ============================================================================
460// Full-Text Search Types
461// ============================================================================
462
463/// A document for full-text indexing
464#[derive(Debug, Clone, Serialize, Deserialize)]
465pub struct Document {
466    /// Document ID
467    pub id: String,
468    /// Document text content
469    pub text: String,
470    /// Optional metadata
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub metadata: Option<HashMap<String, serde_json::Value>>,
473}
474
475impl Document {
476    /// Create a new document
477    pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
478        Self {
479            id: id.into(),
480            text: text.into(),
481            metadata: None,
482        }
483    }
484
485    /// Create a new document with metadata
486    pub fn with_metadata(
487        id: impl Into<String>,
488        text: impl Into<String>,
489        metadata: HashMap<String, serde_json::Value>,
490    ) -> Self {
491        Self {
492            id: id.into(),
493            text: text.into(),
494            metadata: Some(metadata),
495        }
496    }
497}
498
499/// Index documents request
500#[derive(Debug, Clone, Serialize, Deserialize)]
501pub struct IndexDocumentsRequest {
502    /// Documents to index
503    pub documents: Vec<Document>,
504}
505
506/// Index documents response
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct IndexDocumentsResponse {
509    /// Number of documents indexed
510    pub indexed_count: u64,
511}
512
513/// Full-text search request
514#[derive(Debug, Clone, Serialize, Deserialize)]
515pub struct FullTextSearchRequest {
516    /// Search query
517    pub query: String,
518    /// Maximum number of results
519    pub top_k: u32,
520    /// Optional filter
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub filter: Option<serde_json::Value>,
523}
524
525impl FullTextSearchRequest {
526    /// Create a new full-text search request
527    pub fn new(query: impl Into<String>, top_k: u32) -> Self {
528        Self {
529            query: query.into(),
530            top_k,
531            filter: None,
532        }
533    }
534
535    /// Add a filter to the search
536    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
537        self.filter = Some(filter);
538        self
539    }
540}
541
542/// Full-text search result
543#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct FullTextMatch {
545    /// Document ID
546    pub id: String,
547    /// BM25 score
548    pub score: f32,
549    /// Document text
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub text: Option<String>,
552    /// Optional metadata
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub metadata: Option<HashMap<String, serde_json::Value>>,
555}
556
557/// Full-text search response
558#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct FullTextSearchResponse {
560    /// Matched documents
561    pub matches: Vec<FullTextMatch>,
562}
563
564/// Full-text index statistics
565#[derive(Debug, Clone, Serialize, Deserialize)]
566pub struct FullTextStats {
567    /// Number of documents indexed
568    pub document_count: u64,
569    /// Number of unique terms
570    pub term_count: u64,
571}
572
573// ============================================================================
574// Hybrid Search Types
575// ============================================================================
576
577/// Hybrid search request combining vector and full-text search.
578///
579/// When `vector` is `None` the server falls back to BM25-only full-text search.
580/// When provided, results are blended with vector similarity according to `vector_weight`.
581#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct HybridSearchRequest {
583    /// Optional query vector. Omit for BM25-only search.
584    #[serde(skip_serializing_if = "Option::is_none")]
585    pub vector: Option<Vec<f32>>,
586    /// Text query
587    pub text: String,
588    /// Number of results to return
589    pub top_k: u32,
590    /// Weight for vector search (0.0-1.0)
591    #[serde(default = "default_vector_weight")]
592    pub vector_weight: f32,
593    /// Optional filter
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub filter: Option<serde_json::Value>,
596}
597
598fn default_vector_weight() -> f32 {
599    0.5
600}
601
602impl HybridSearchRequest {
603    /// Create a new hybrid search request with a query vector (hybrid mode).
604    pub fn new(vector: Vec<f32>, text: impl Into<String>, top_k: u32) -> Self {
605        Self {
606            vector: Some(vector),
607            text: text.into(),
608            top_k,
609            vector_weight: 0.5,
610            filter: None,
611        }
612    }
613
614    /// Create a BM25-only full-text search request (no vector required).
615    pub fn text_only(text: impl Into<String>, top_k: u32) -> Self {
616        Self {
617            vector: None,
618            text: text.into(),
619            top_k,
620            vector_weight: 0.5,
621            filter: None,
622        }
623    }
624
625    /// Set the vector weight (text weight is 1.0 - vector_weight)
626    pub fn with_vector_weight(mut self, weight: f32) -> Self {
627        self.vector_weight = weight.clamp(0.0, 1.0);
628        self
629    }
630
631    /// Add a filter to the search
632    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
633        self.filter = Some(filter);
634        self
635    }
636}
637
638/// Hybrid search response
639#[derive(Debug, Clone, Serialize, Deserialize)]
640pub struct HybridSearchResponse {
641    /// Matched results
642    pub matches: Vec<Match>,
643}
644
645// ============================================================================
646// Operations Types
647// ============================================================================
648
649/// System diagnostics
650#[derive(Debug, Clone, Serialize, Deserialize)]
651pub struct SystemDiagnostics {
652    /// System information
653    pub system: SystemInfo,
654    /// Resource usage
655    pub resources: ResourceUsage,
656    /// Component health
657    pub components: ComponentHealth,
658    /// Number of active jobs
659    pub active_jobs: u64,
660}
661
662/// System information
663#[derive(Debug, Clone, Serialize, Deserialize)]
664pub struct SystemInfo {
665    /// Dakera version
666    pub version: String,
667    /// Rust version
668    pub rust_version: String,
669    /// Uptime in seconds
670    pub uptime_seconds: u64,
671    /// Process ID
672    pub pid: u32,
673}
674
675/// Resource usage metrics
676#[derive(Debug, Clone, Serialize, Deserialize)]
677pub struct ResourceUsage {
678    /// Memory usage in bytes
679    pub memory_bytes: u64,
680    /// Thread count
681    pub thread_count: u64,
682    /// Open file descriptors
683    pub open_fds: u64,
684    /// CPU usage percentage
685    pub cpu_percent: Option<f64>,
686}
687
688/// Component health status
689#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct ComponentHealth {
691    /// Storage health
692    pub storage: HealthStatus,
693    /// Search engine health
694    pub search_engine: HealthStatus,
695    /// Cache health
696    pub cache: HealthStatus,
697    /// gRPC health
698    pub grpc: HealthStatus,
699}
700
701/// Health status for a component
702#[derive(Debug, Clone, Serialize, Deserialize)]
703pub struct HealthStatus {
704    /// Is the component healthy
705    pub healthy: bool,
706    /// Status message
707    pub message: String,
708    /// Last check timestamp
709    pub last_check: u64,
710}
711
712/// Background job information
713#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct JobInfo {
715    /// Job ID
716    pub id: String,
717    /// Job type
718    pub job_type: String,
719    /// Current status
720    pub status: String,
721    /// Creation timestamp
722    pub created_at: u64,
723    /// Start timestamp
724    pub started_at: Option<u64>,
725    /// Completion timestamp
726    pub completed_at: Option<u64>,
727    /// Progress percentage
728    pub progress: u8,
729    /// Status message
730    pub message: Option<String>,
731}
732
733/// Compaction request
734#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct CompactionRequest {
736    /// Namespace to compact (None = all)
737    #[serde(skip_serializing_if = "Option::is_none")]
738    pub namespace: Option<String>,
739    /// Force compaction
740    #[serde(default)]
741    pub force: bool,
742}
743
744/// Compaction response
745#[derive(Debug, Clone, Serialize, Deserialize)]
746pub struct CompactionResponse {
747    /// Job ID for tracking
748    pub job_id: String,
749    /// Status message
750    pub message: String,
751}
752
753// ============================================================================
754// Cache Warming Types (Turbopuffer-inspired)
755// ============================================================================
756
757/// Priority level for cache warming operations
758#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
759#[serde(rename_all = "snake_case")]
760pub enum WarmingPriority {
761    /// Highest priority - warm immediately, preempt other operations
762    Critical,
763    /// High priority - warm soon
764    High,
765    /// Normal priority (default)
766    #[default]
767    Normal,
768    /// Low priority - warm when resources available
769    Low,
770    /// Background priority - warm during idle time only
771    Background,
772}
773
774/// Target cache tier for warming
775#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
776#[serde(rename_all = "snake_case")]
777pub enum WarmingTargetTier {
778    /// L1 in-memory cache (Moka) - fastest, limited size
779    L1,
780    /// L2 local disk cache (RocksDB) - larger, persistent
781    #[default]
782    L2,
783    /// Both L1 and L2 caches
784    Both,
785}
786
787/// Access pattern hint for cache optimization
788#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
789#[serde(rename_all = "snake_case")]
790pub enum AccessPatternHint {
791    /// Random access pattern
792    #[default]
793    Random,
794    /// Sequential access pattern
795    Sequential,
796    /// Temporal locality (recently accessed items accessed again)
797    Temporal,
798    /// Spatial locality (nearby items accessed together)
799    Spatial,
800}
801
802/// Cache warming request with priority hints
803#[derive(Debug, Clone, Serialize, Deserialize)]
804pub struct WarmCacheRequest {
805    /// Namespace to warm
806    pub namespace: String,
807    /// Specific vector IDs to warm (None = all)
808    #[serde(skip_serializing_if = "Option::is_none")]
809    pub vector_ids: Option<Vec<String>>,
810    /// Warming priority level
811    #[serde(default)]
812    pub priority: WarmingPriority,
813    /// Target cache tier
814    #[serde(default)]
815    pub target_tier: WarmingTargetTier,
816    /// Run warming in background (non-blocking)
817    #[serde(default)]
818    pub background: bool,
819    /// TTL hint in seconds
820    #[serde(skip_serializing_if = "Option::is_none")]
821    pub ttl_hint_seconds: Option<u64>,
822    /// Access pattern hint for optimization
823    #[serde(default)]
824    pub access_pattern: AccessPatternHint,
825    /// Maximum vectors to warm
826    #[serde(skip_serializing_if = "Option::is_none")]
827    pub max_vectors: Option<usize>,
828}
829
830impl WarmCacheRequest {
831    /// Create a new cache warming request for a namespace
832    pub fn new(namespace: impl Into<String>) -> Self {
833        Self {
834            namespace: namespace.into(),
835            vector_ids: None,
836            priority: WarmingPriority::default(),
837            target_tier: WarmingTargetTier::default(),
838            background: false,
839            ttl_hint_seconds: None,
840            access_pattern: AccessPatternHint::default(),
841            max_vectors: None,
842        }
843    }
844
845    /// Warm specific vector IDs
846    pub fn with_vector_ids(mut self, ids: Vec<String>) -> Self {
847        self.vector_ids = Some(ids);
848        self
849    }
850
851    /// Set warming priority
852    pub fn with_priority(mut self, priority: WarmingPriority) -> Self {
853        self.priority = priority;
854        self
855    }
856
857    /// Set target cache tier
858    pub fn with_target_tier(mut self, tier: WarmingTargetTier) -> Self {
859        self.target_tier = tier;
860        self
861    }
862
863    /// Run warming in background
864    pub fn in_background(mut self) -> Self {
865        self.background = true;
866        self
867    }
868
869    /// Set TTL hint
870    pub fn with_ttl(mut self, seconds: u64) -> Self {
871        self.ttl_hint_seconds = Some(seconds);
872        self
873    }
874
875    /// Set access pattern hint
876    pub fn with_access_pattern(mut self, pattern: AccessPatternHint) -> Self {
877        self.access_pattern = pattern;
878        self
879    }
880
881    /// Limit number of vectors to warm
882    pub fn with_max_vectors(mut self, max: usize) -> Self {
883        self.max_vectors = Some(max);
884        self
885    }
886}
887
888/// Cache warming response
889#[derive(Debug, Clone, Serialize, Deserialize)]
890pub struct WarmCacheResponse {
891    /// Operation success
892    pub success: bool,
893    /// Number of entries warmed
894    pub entries_warmed: u64,
895    /// Number of entries already warm (skipped)
896    pub entries_skipped: u64,
897    /// Job ID for tracking background operations
898    #[serde(skip_serializing_if = "Option::is_none")]
899    pub job_id: Option<String>,
900    /// Status message
901    pub message: String,
902    /// Estimated completion time for background jobs (ISO 8601)
903    #[serde(skip_serializing_if = "Option::is_none")]
904    pub estimated_completion: Option<String>,
905    /// Target tier that was warmed
906    pub target_tier: WarmingTargetTier,
907    /// Priority that was used
908    pub priority: WarmingPriority,
909    /// Bytes warmed (approximate)
910    #[serde(skip_serializing_if = "Option::is_none")]
911    pub bytes_warmed: Option<u64>,
912}
913
914// ============================================================================
915// Export Types (Turbopuffer-inspired)
916// ============================================================================
917
918/// Request to export vectors from a namespace with pagination
919#[derive(Debug, Clone, Serialize, Deserialize)]
920pub struct ExportRequest {
921    /// Maximum number of vectors to return per page (default: 1000, max: 10000)
922    #[serde(default = "default_export_top_k")]
923    pub top_k: usize,
924    /// Cursor for pagination - the last vector ID from previous page
925    #[serde(skip_serializing_if = "Option::is_none")]
926    pub cursor: Option<String>,
927    /// Whether to include vector values in the response (default: true)
928    #[serde(default = "default_true")]
929    pub include_vectors: bool,
930    /// Whether to include metadata in the response (default: true)
931    #[serde(default = "default_true")]
932    pub include_metadata: bool,
933}
934
935fn default_export_top_k() -> usize {
936    1000
937}
938
939impl Default for ExportRequest {
940    fn default() -> Self {
941        Self {
942            top_k: 1000,
943            cursor: None,
944            include_vectors: true,
945            include_metadata: true,
946        }
947    }
948}
949
950impl ExportRequest {
951    /// Create a new export request with default settings
952    pub fn new() -> Self {
953        Self::default()
954    }
955
956    /// Set the maximum number of vectors to return per page
957    pub fn with_top_k(mut self, top_k: usize) -> Self {
958        self.top_k = top_k;
959        self
960    }
961
962    /// Set the pagination cursor
963    pub fn with_cursor(mut self, cursor: impl Into<String>) -> Self {
964        self.cursor = Some(cursor.into());
965        self
966    }
967
968    /// Set whether to include vector values
969    pub fn include_vectors(mut self, include: bool) -> Self {
970        self.include_vectors = include;
971        self
972    }
973
974    /// Set whether to include metadata
975    pub fn include_metadata(mut self, include: bool) -> Self {
976        self.include_metadata = include;
977        self
978    }
979}
980
981/// A single exported vector record
982#[derive(Debug, Clone, Serialize, Deserialize)]
983pub struct ExportedVector {
984    /// Vector ID
985    pub id: String,
986    /// Vector values (optional based on include_vectors)
987    #[serde(skip_serializing_if = "Option::is_none")]
988    pub values: Option<Vec<f32>>,
989    /// Metadata (optional based on include_metadata)
990    #[serde(skip_serializing_if = "Option::is_none")]
991    pub metadata: Option<serde_json::Value>,
992    /// TTL in seconds if set
993    #[serde(skip_serializing_if = "Option::is_none")]
994    pub ttl_seconds: Option<u64>,
995}
996
997/// Response from export operation
998#[derive(Debug, Clone, Serialize, Deserialize)]
999pub struct ExportResponse {
1000    /// Exported vectors for this page
1001    pub vectors: Vec<ExportedVector>,
1002    /// Cursor for next page (None if this is the last page)
1003    #[serde(skip_serializing_if = "Option::is_none")]
1004    pub next_cursor: Option<String>,
1005    /// Total vectors in namespace (for progress tracking)
1006    pub total_count: usize,
1007    /// Number of vectors returned in this page
1008    pub returned_count: usize,
1009}
1010
1011// ============================================================================
1012// Batch Query Types
1013// ============================================================================
1014
1015/// A single query within a batch request
1016#[derive(Debug, Clone, Serialize, Deserialize)]
1017pub struct BatchQueryItem {
1018    /// Unique identifier for this query within the batch
1019    #[serde(skip_serializing_if = "Option::is_none")]
1020    pub id: Option<String>,
1021    /// The query vector
1022    pub vector: Vec<f32>,
1023    /// Number of results to return
1024    #[serde(default = "default_batch_top_k")]
1025    pub top_k: u32,
1026    /// Optional filter expression
1027    #[serde(skip_serializing_if = "Option::is_none")]
1028    pub filter: Option<serde_json::Value>,
1029    /// Whether to include metadata in results
1030    #[serde(default)]
1031    pub include_metadata: bool,
1032    /// Read consistency level
1033    #[serde(default)]
1034    pub consistency: ReadConsistency,
1035    /// Staleness configuration for bounded staleness reads
1036    #[serde(skip_serializing_if = "Option::is_none")]
1037    pub staleness_config: Option<StalenessConfig>,
1038}
1039
1040fn default_batch_top_k() -> u32 {
1041    10
1042}
1043
1044impl BatchQueryItem {
1045    /// Create a new batch query item
1046    pub fn new(vector: Vec<f32>, top_k: u32) -> Self {
1047        Self {
1048            id: None,
1049            vector,
1050            top_k,
1051            filter: None,
1052            include_metadata: true,
1053            consistency: ReadConsistency::default(),
1054            staleness_config: None,
1055        }
1056    }
1057
1058    /// Set a unique identifier for this query
1059    pub fn with_id(mut self, id: impl Into<String>) -> Self {
1060        self.id = Some(id.into());
1061        self
1062    }
1063
1064    /// Add a filter to the query
1065    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1066        self.filter = Some(filter);
1067        self
1068    }
1069
1070    /// Set whether to include metadata
1071    pub fn include_metadata(mut self, include: bool) -> Self {
1072        self.include_metadata = include;
1073        self
1074    }
1075
1076    /// Set read consistency level
1077    pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
1078        self.consistency = consistency;
1079        self
1080    }
1081
1082    /// Set bounded staleness with max staleness in ms
1083    pub fn with_bounded_staleness(mut self, max_staleness_ms: u64) -> Self {
1084        self.consistency = ReadConsistency::BoundedStaleness;
1085        self.staleness_config = Some(StalenessConfig::new(max_staleness_ms));
1086        self
1087    }
1088}
1089
1090/// Batch query request - execute multiple queries in parallel
1091#[derive(Debug, Clone, Serialize, Deserialize)]
1092pub struct BatchQueryRequest {
1093    /// List of queries to execute
1094    pub queries: Vec<BatchQueryItem>,
1095}
1096
1097impl BatchQueryRequest {
1098    /// Create a new batch query request
1099    pub fn new(queries: Vec<BatchQueryItem>) -> Self {
1100        Self { queries }
1101    }
1102
1103    /// Create a batch query request from a single query
1104    pub fn single(query: BatchQueryItem) -> Self {
1105        Self {
1106            queries: vec![query],
1107        }
1108    }
1109}
1110
1111/// Results for a single query within a batch
1112#[derive(Debug, Clone, Serialize, Deserialize)]
1113pub struct BatchQueryResult {
1114    /// The query identifier (if provided in request)
1115    #[serde(skip_serializing_if = "Option::is_none")]
1116    pub id: Option<String>,
1117    /// Query results (empty if an error occurred)
1118    pub results: Vec<Match>,
1119    /// Query execution time in milliseconds
1120    pub latency_ms: f64,
1121    /// Error message if this individual query failed
1122    #[serde(skip_serializing_if = "Option::is_none")]
1123    pub error: Option<String>,
1124}
1125
1126/// Batch query response
1127#[derive(Debug, Clone, Serialize, Deserialize)]
1128pub struct BatchQueryResponse {
1129    /// Results for each query in the batch
1130    pub results: Vec<BatchQueryResult>,
1131    /// Total execution time in milliseconds
1132    pub total_latency_ms: f64,
1133    /// Number of queries executed
1134    pub query_count: usize,
1135}
1136
1137// ============================================================================
1138// Multi-Vector Search Types
1139// ============================================================================
1140
1141/// Request for multi-vector search with positive and negative vectors
1142#[derive(Debug, Clone, Serialize, Deserialize)]
1143pub struct MultiVectorSearchRequest {
1144    /// Positive vectors to search towards (required, at least one)
1145    pub positive_vectors: Vec<Vec<f32>>,
1146    /// Weights for positive vectors (optional, defaults to equal weights)
1147    #[serde(skip_serializing_if = "Option::is_none")]
1148    pub positive_weights: Option<Vec<f32>>,
1149    /// Negative vectors to search away from (optional)
1150    #[serde(skip_serializing_if = "Option::is_none")]
1151    pub negative_vectors: Option<Vec<Vec<f32>>>,
1152    /// Weights for negative vectors (optional, defaults to equal weights)
1153    #[serde(skip_serializing_if = "Option::is_none")]
1154    pub negative_weights: Option<Vec<f32>>,
1155    /// Number of results to return
1156    #[serde(default = "default_multi_vector_top_k")]
1157    pub top_k: u32,
1158    /// Distance metric to use
1159    #[serde(default)]
1160    pub distance_metric: DistanceMetric,
1161    /// Minimum score threshold
1162    #[serde(skip_serializing_if = "Option::is_none")]
1163    pub score_threshold: Option<f32>,
1164    /// Enable MMR (Maximal Marginal Relevance) for diversity
1165    #[serde(default)]
1166    pub enable_mmr: bool,
1167    /// Lambda parameter for MMR (0 = max diversity, 1 = max relevance)
1168    #[serde(default = "default_mmr_lambda")]
1169    pub mmr_lambda: f32,
1170    /// Include metadata in results
1171    #[serde(default = "default_true")]
1172    pub include_metadata: bool,
1173    /// Include vectors in results
1174    #[serde(default)]
1175    pub include_vectors: bool,
1176    /// Optional metadata filter
1177    #[serde(skip_serializing_if = "Option::is_none")]
1178    pub filter: Option<serde_json::Value>,
1179    /// Read consistency level
1180    #[serde(default)]
1181    pub consistency: ReadConsistency,
1182    /// Staleness configuration for bounded staleness reads
1183    #[serde(skip_serializing_if = "Option::is_none")]
1184    pub staleness_config: Option<StalenessConfig>,
1185}
1186
1187fn default_multi_vector_top_k() -> u32 {
1188    10
1189}
1190
1191fn default_mmr_lambda() -> f32 {
1192    0.5
1193}
1194
1195impl MultiVectorSearchRequest {
1196    /// Create a new multi-vector search request with positive vectors
1197    pub fn new(positive_vectors: Vec<Vec<f32>>) -> Self {
1198        Self {
1199            positive_vectors,
1200            positive_weights: None,
1201            negative_vectors: None,
1202            negative_weights: None,
1203            top_k: 10,
1204            distance_metric: DistanceMetric::default(),
1205            score_threshold: None,
1206            enable_mmr: false,
1207            mmr_lambda: 0.5,
1208            include_metadata: true,
1209            include_vectors: false,
1210            filter: None,
1211            consistency: ReadConsistency::default(),
1212            staleness_config: None,
1213        }
1214    }
1215
1216    /// Set the number of results to return
1217    pub fn with_top_k(mut self, top_k: u32) -> Self {
1218        self.top_k = top_k;
1219        self
1220    }
1221
1222    /// Add weights for positive vectors
1223    pub fn with_positive_weights(mut self, weights: Vec<f32>) -> Self {
1224        self.positive_weights = Some(weights);
1225        self
1226    }
1227
1228    /// Add negative vectors to search away from
1229    pub fn with_negative_vectors(mut self, vectors: Vec<Vec<f32>>) -> Self {
1230        self.negative_vectors = Some(vectors);
1231        self
1232    }
1233
1234    /// Add weights for negative vectors
1235    pub fn with_negative_weights(mut self, weights: Vec<f32>) -> Self {
1236        self.negative_weights = Some(weights);
1237        self
1238    }
1239
1240    /// Set distance metric
1241    pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
1242        self.distance_metric = metric;
1243        self
1244    }
1245
1246    /// Set minimum score threshold
1247    pub fn with_score_threshold(mut self, threshold: f32) -> Self {
1248        self.score_threshold = Some(threshold);
1249        self
1250    }
1251
1252    /// Enable MMR for diversity
1253    pub fn with_mmr(mut self, lambda: f32) -> Self {
1254        self.enable_mmr = true;
1255        self.mmr_lambda = lambda.clamp(0.0, 1.0);
1256        self
1257    }
1258
1259    /// Set whether to include metadata
1260    pub fn include_metadata(mut self, include: bool) -> Self {
1261        self.include_metadata = include;
1262        self
1263    }
1264
1265    /// Set whether to include vectors
1266    pub fn include_vectors(mut self, include: bool) -> Self {
1267        self.include_vectors = include;
1268        self
1269    }
1270
1271    /// Add a filter
1272    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1273        self.filter = Some(filter);
1274        self
1275    }
1276
1277    /// Set read consistency level
1278    pub fn with_consistency(mut self, consistency: ReadConsistency) -> Self {
1279        self.consistency = consistency;
1280        self
1281    }
1282}
1283
1284/// Single result from multi-vector search
1285#[derive(Debug, Clone, Serialize, Deserialize)]
1286pub struct MultiVectorSearchResult {
1287    /// Vector ID
1288    pub id: String,
1289    /// Similarity score
1290    pub score: f32,
1291    /// MMR score (if MMR enabled)
1292    #[serde(skip_serializing_if = "Option::is_none")]
1293    pub mmr_score: Option<f32>,
1294    /// Original rank before reranking
1295    #[serde(skip_serializing_if = "Option::is_none")]
1296    pub original_rank: Option<usize>,
1297    /// Optional metadata
1298    #[serde(skip_serializing_if = "Option::is_none")]
1299    pub metadata: Option<HashMap<String, serde_json::Value>>,
1300    /// Optional vector values
1301    #[serde(skip_serializing_if = "Option::is_none")]
1302    pub vector: Option<Vec<f32>>,
1303}
1304
1305/// Response from multi-vector search
1306#[derive(Debug, Clone, Serialize, Deserialize)]
1307pub struct MultiVectorSearchResponse {
1308    /// Search results
1309    pub results: Vec<MultiVectorSearchResult>,
1310    /// The computed query vector (weighted combination of positive - negative)
1311    #[serde(skip_serializing_if = "Option::is_none")]
1312    pub computed_query_vector: Option<Vec<f32>>,
1313}
1314
1315// ============================================================================
1316// Aggregation Types (Turbopuffer-inspired)
1317// ============================================================================
1318
1319/// Aggregate function for computing values across documents
1320#[derive(Debug, Clone, Serialize, Deserialize)]
1321#[serde(untagged)]
1322pub enum AggregateFunction {
1323    /// Count matching documents
1324    Count,
1325    /// Sum numeric attribute values
1326    Sum { field: String },
1327    /// Average numeric attribute values
1328    Avg { field: String },
1329    /// Minimum numeric attribute value
1330    Min { field: String },
1331    /// Maximum numeric attribute value
1332    Max { field: String },
1333}
1334
1335/// Request for aggregation query (Turbopuffer-inspired)
1336#[derive(Debug, Clone, Serialize, Deserialize)]
1337pub struct AggregationRequest {
1338    /// Named aggregations to compute
1339    /// Example: {"my_count": ["Count"], "total_score": ["Sum", "score"]}
1340    pub aggregate_by: HashMap<String, serde_json::Value>,
1341    /// Fields to group results by (optional)
1342    /// Example: ["category", "status"]
1343    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1344    pub group_by: Vec<String>,
1345    /// Filter to apply before aggregation
1346    #[serde(skip_serializing_if = "Option::is_none")]
1347    pub filter: Option<serde_json::Value>,
1348    /// Maximum number of groups to return (default: 100)
1349    #[serde(default = "default_agg_limit")]
1350    pub limit: usize,
1351}
1352
1353fn default_agg_limit() -> usize {
1354    100
1355}
1356
1357impl AggregationRequest {
1358    /// Create a new aggregation request with a single aggregation
1359    pub fn new() -> Self {
1360        Self {
1361            aggregate_by: HashMap::new(),
1362            group_by: Vec::new(),
1363            filter: None,
1364            limit: 100,
1365        }
1366    }
1367
1368    /// Add a count aggregation
1369    pub fn with_count(mut self, name: impl Into<String>) -> Self {
1370        self.aggregate_by
1371            .insert(name.into(), serde_json::json!(["Count"]));
1372        self
1373    }
1374
1375    /// Add a sum aggregation
1376    pub fn with_sum(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1377        self.aggregate_by
1378            .insert(name.into(), serde_json::json!(["Sum", field.into()]));
1379        self
1380    }
1381
1382    /// Add an average aggregation
1383    pub fn with_avg(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1384        self.aggregate_by
1385            .insert(name.into(), serde_json::json!(["Avg", field.into()]));
1386        self
1387    }
1388
1389    /// Add a min aggregation
1390    pub fn with_min(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1391        self.aggregate_by
1392            .insert(name.into(), serde_json::json!(["Min", field.into()]));
1393        self
1394    }
1395
1396    /// Add a max aggregation
1397    pub fn with_max(mut self, name: impl Into<String>, field: impl Into<String>) -> Self {
1398        self.aggregate_by
1399            .insert(name.into(), serde_json::json!(["Max", field.into()]));
1400        self
1401    }
1402
1403    /// Set group by fields
1404    pub fn group_by(mut self, fields: Vec<String>) -> Self {
1405        self.group_by = fields;
1406        self
1407    }
1408
1409    /// Add a single group by field
1410    pub fn with_group_by(mut self, field: impl Into<String>) -> Self {
1411        self.group_by.push(field.into());
1412        self
1413    }
1414
1415    /// Set filter for aggregation
1416    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1417        self.filter = Some(filter);
1418        self
1419    }
1420
1421    /// Set maximum number of groups to return
1422    pub fn with_limit(mut self, limit: usize) -> Self {
1423        self.limit = limit;
1424        self
1425    }
1426}
1427
1428impl Default for AggregationRequest {
1429    fn default() -> Self {
1430        Self::new()
1431    }
1432}
1433
1434/// Response for aggregation query
1435#[derive(Debug, Clone, Serialize, Deserialize)]
1436pub struct AggregationResponse {
1437    /// Aggregation results (without grouping)
1438    #[serde(skip_serializing_if = "Option::is_none")]
1439    pub aggregations: Option<HashMap<String, serde_json::Value>>,
1440    /// Grouped aggregation results (with group_by)
1441    #[serde(skip_serializing_if = "Option::is_none")]
1442    pub aggregation_groups: Option<Vec<AggregationGroup>>,
1443}
1444
1445/// Single group in aggregation results
1446#[derive(Debug, Clone, Serialize, Deserialize)]
1447pub struct AggregationGroup {
1448    /// Group key values (flattened into object)
1449    #[serde(flatten)]
1450    pub group_key: HashMap<String, serde_json::Value>,
1451    /// Aggregation results for this group
1452    #[serde(flatten)]
1453    pub aggregations: HashMap<String, serde_json::Value>,
1454}
1455
1456// ============================================================================
1457// Unified Query Types (Turbopuffer-inspired)
1458// ============================================================================
1459
1460/// Vector search method for unified query
1461#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1462pub enum VectorSearchMethod {
1463    /// Approximate Nearest Neighbor (fast, default)
1464    #[default]
1465    ANN,
1466    /// Exact k-Nearest Neighbor (exhaustive, requires filters)
1467    #[serde(rename = "kNN")]
1468    KNN,
1469}
1470
1471/// Sort direction for attribute ordering
1472#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1473#[serde(rename_all = "lowercase")]
1474pub enum SortDirection {
1475    /// Ascending order
1476    Asc,
1477    /// Descending order
1478    #[default]
1479    Desc,
1480}
1481
1482/// Ranking function for unified query API
1483/// Supports vector search (ANN/kNN), full-text BM25, and attribute ordering
1484#[derive(Debug, Clone, Serialize, Deserialize)]
1485#[serde(untagged)]
1486pub enum RankBy {
1487    /// Vector search: uses field, method, and query_vector
1488    VectorSearch {
1489        field: String,
1490        method: VectorSearchMethod,
1491        query_vector: Vec<f32>,
1492    },
1493    /// Full-text BM25 search
1494    FullTextSearch {
1495        field: String,
1496        method: String, // Always "BM25"
1497        query: String,
1498    },
1499    /// Attribute ordering
1500    AttributeOrder {
1501        field: String,
1502        direction: SortDirection,
1503    },
1504    /// Sum of multiple ranking functions
1505    Sum(Vec<RankBy>),
1506    /// Max of multiple ranking functions
1507    Max(Vec<RankBy>),
1508    /// Product with weight
1509    Product { weight: f32, ranking: Box<RankBy> },
1510}
1511
1512impl RankBy {
1513    /// Create a vector search ranking using ANN
1514    pub fn vector_ann(field: impl Into<String>, query_vector: Vec<f32>) -> Self {
1515        RankBy::VectorSearch {
1516            field: field.into(),
1517            method: VectorSearchMethod::ANN,
1518            query_vector,
1519        }
1520    }
1521
1522    /// Create a vector search ranking using ANN on the default "vector" field
1523    pub fn ann(query_vector: Vec<f32>) -> Self {
1524        Self::vector_ann("vector", query_vector)
1525    }
1526
1527    /// Create a vector search ranking using exact kNN
1528    pub fn vector_knn(field: impl Into<String>, query_vector: Vec<f32>) -> Self {
1529        RankBy::VectorSearch {
1530            field: field.into(),
1531            method: VectorSearchMethod::KNN,
1532            query_vector,
1533        }
1534    }
1535
1536    /// Create a vector search ranking using kNN on the default "vector" field
1537    pub fn knn(query_vector: Vec<f32>) -> Self {
1538        Self::vector_knn("vector", query_vector)
1539    }
1540
1541    /// Create a BM25 full-text search ranking
1542    pub fn bm25(field: impl Into<String>, query: impl Into<String>) -> Self {
1543        RankBy::FullTextSearch {
1544            field: field.into(),
1545            method: "BM25".to_string(),
1546            query: query.into(),
1547        }
1548    }
1549
1550    /// Create an attribute ordering ranking (ascending)
1551    pub fn asc(field: impl Into<String>) -> Self {
1552        RankBy::AttributeOrder {
1553            field: field.into(),
1554            direction: SortDirection::Asc,
1555        }
1556    }
1557
1558    /// Create an attribute ordering ranking (descending)
1559    pub fn desc(field: impl Into<String>) -> Self {
1560        RankBy::AttributeOrder {
1561            field: field.into(),
1562            direction: SortDirection::Desc,
1563        }
1564    }
1565
1566    /// Sum multiple ranking functions together
1567    pub fn sum(rankings: Vec<RankBy>) -> Self {
1568        RankBy::Sum(rankings)
1569    }
1570
1571    /// Take the max of multiple ranking functions
1572    pub fn max(rankings: Vec<RankBy>) -> Self {
1573        RankBy::Max(rankings)
1574    }
1575
1576    /// Apply a weight multiplier to a ranking function
1577    pub fn product(weight: f32, ranking: RankBy) -> Self {
1578        RankBy::Product {
1579            weight,
1580            ranking: Box::new(ranking),
1581        }
1582    }
1583}
1584
1585/// Unified query request with flexible ranking options (Turbopuffer-inspired)
1586///
1587/// # Example
1588///
1589/// ```rust
1590/// use dakera_client::UnifiedQueryRequest;
1591///
1592/// // Vector ANN search
1593/// let request = UnifiedQueryRequest::vector_search(vec![0.1, 0.2, 0.3], 10);
1594///
1595/// // Full-text BM25 search
1596/// let request = UnifiedQueryRequest::fulltext_search("content", "hello world", 10);
1597///
1598/// // Custom rank_by with filters
1599/// let request = UnifiedQueryRequest::vector_search(vec![0.1, 0.2, 0.3], 10)
1600///     .with_filter(serde_json::json!({"category": {"$eq": "science"}}));
1601/// ```
1602#[derive(Debug, Clone, Serialize, Deserialize)]
1603pub struct UnifiedQueryRequest {
1604    /// How to rank documents (required)
1605    pub rank_by: serde_json::Value,
1606    /// Number of results to return
1607    #[serde(default = "default_unified_top_k")]
1608    pub top_k: usize,
1609    /// Optional metadata filter
1610    #[serde(skip_serializing_if = "Option::is_none")]
1611    pub filter: Option<serde_json::Value>,
1612    /// Include metadata in results
1613    #[serde(default = "default_true")]
1614    pub include_metadata: bool,
1615    /// Include vectors in results
1616    #[serde(default)]
1617    pub include_vectors: bool,
1618    /// Distance metric for vector search (default: cosine)
1619    #[serde(default)]
1620    pub distance_metric: DistanceMetric,
1621}
1622
1623fn default_unified_top_k() -> usize {
1624    10
1625}
1626
1627impl UnifiedQueryRequest {
1628    /// Create a new unified query request with vector ANN search
1629    pub fn vector_search(query_vector: Vec<f32>, top_k: usize) -> Self {
1630        Self {
1631            rank_by: serde_json::json!(["ANN", query_vector]),
1632            top_k,
1633            filter: None,
1634            include_metadata: true,
1635            include_vectors: false,
1636            distance_metric: DistanceMetric::default(),
1637        }
1638    }
1639
1640    /// Create a new unified query request with vector kNN search
1641    pub fn vector_knn_search(query_vector: Vec<f32>, top_k: usize) -> Self {
1642        Self {
1643            rank_by: serde_json::json!(["kNN", query_vector]),
1644            top_k,
1645            filter: None,
1646            include_metadata: true,
1647            include_vectors: false,
1648            distance_metric: DistanceMetric::default(),
1649        }
1650    }
1651
1652    /// Create a new unified query request with full-text BM25 search
1653    pub fn fulltext_search(
1654        field: impl Into<String>,
1655        query: impl Into<String>,
1656        top_k: usize,
1657    ) -> Self {
1658        Self {
1659            rank_by: serde_json::json!([field.into(), "BM25", query.into()]),
1660            top_k,
1661            filter: None,
1662            include_metadata: true,
1663            include_vectors: false,
1664            distance_metric: DistanceMetric::default(),
1665        }
1666    }
1667
1668    /// Create a new unified query request with attribute ordering
1669    pub fn attribute_order(
1670        field: impl Into<String>,
1671        direction: SortDirection,
1672        top_k: usize,
1673    ) -> Self {
1674        let dir = match direction {
1675            SortDirection::Asc => "asc",
1676            SortDirection::Desc => "desc",
1677        };
1678        Self {
1679            rank_by: serde_json::json!([field.into(), dir]),
1680            top_k,
1681            filter: None,
1682            include_metadata: true,
1683            include_vectors: false,
1684            distance_metric: DistanceMetric::default(),
1685        }
1686    }
1687
1688    /// Create a unified query with a raw rank_by JSON value
1689    pub fn with_rank_by(rank_by: serde_json::Value, top_k: usize) -> Self {
1690        Self {
1691            rank_by,
1692            top_k,
1693            filter: None,
1694            include_metadata: true,
1695            include_vectors: false,
1696            distance_metric: DistanceMetric::default(),
1697        }
1698    }
1699
1700    /// Add a filter to the query
1701    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1702        self.filter = Some(filter);
1703        self
1704    }
1705
1706    /// Set whether to include metadata
1707    pub fn include_metadata(mut self, include: bool) -> Self {
1708        self.include_metadata = include;
1709        self
1710    }
1711
1712    /// Set whether to include vector values
1713    pub fn include_vectors(mut self, include: bool) -> Self {
1714        self.include_vectors = include;
1715        self
1716    }
1717
1718    /// Set the distance metric
1719    pub fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
1720        self.distance_metric = metric;
1721        self
1722    }
1723
1724    /// Set the number of results to return
1725    pub fn with_top_k(mut self, top_k: usize) -> Self {
1726        self.top_k = top_k;
1727        self
1728    }
1729}
1730
1731/// Single result from unified query
1732#[derive(Debug, Clone, Serialize, Deserialize)]
1733pub struct UnifiedSearchResult {
1734    /// Vector/document ID
1735    pub id: String,
1736    /// Ranking score (distance for vector search, BM25 score for text)
1737    /// Named $dist for Turbopuffer compatibility
1738    #[serde(rename = "$dist", skip_serializing_if = "Option::is_none")]
1739    pub dist: Option<f32>,
1740    /// Metadata if requested
1741    #[serde(skip_serializing_if = "Option::is_none")]
1742    pub metadata: Option<serde_json::Value>,
1743    /// Vector values if requested
1744    #[serde(skip_serializing_if = "Option::is_none")]
1745    pub vector: Option<Vec<f32>>,
1746}
1747
1748/// Unified query response
1749#[derive(Debug, Clone, Serialize, Deserialize)]
1750pub struct UnifiedQueryResponse {
1751    /// Search results ordered by rank_by score
1752    pub results: Vec<UnifiedSearchResult>,
1753    /// Cursor for pagination (if more results available)
1754    #[serde(skip_serializing_if = "Option::is_none")]
1755    pub next_cursor: Option<String>,
1756}
1757
1758// ============================================================================
1759// Query Explain Types
1760// ============================================================================
1761
1762fn default_explain_top_k() -> usize {
1763    10
1764}
1765
1766/// Query type for explain
1767#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1768#[serde(rename_all = "snake_case")]
1769#[derive(Default)]
1770pub enum ExplainQueryType {
1771    /// Vector similarity search
1772    #[default]
1773    VectorSearch,
1774    /// Full-text search
1775    FullTextSearch,
1776    /// Hybrid search combining vector and text
1777    HybridSearch,
1778    /// Multi-vector search with positive/negative vectors
1779    MultiVector,
1780    /// Batch query execution
1781    BatchQuery,
1782}
1783
1784/// Query explain request
1785#[derive(Debug, Clone, Serialize, Deserialize)]
1786pub struct QueryExplainRequest {
1787    /// Type of query to explain
1788    #[serde(default)]
1789    pub query_type: ExplainQueryType,
1790    /// Query vector (for vector searches)
1791    #[serde(skip_serializing_if = "Option::is_none")]
1792    pub vector: Option<Vec<f32>>,
1793    /// Number of results to return
1794    #[serde(default = "default_explain_top_k")]
1795    pub top_k: usize,
1796    /// Optional metadata filter
1797    #[serde(skip_serializing_if = "Option::is_none")]
1798    pub filter: Option<serde_json::Value>,
1799    /// Optional text query for hybrid/fulltext search
1800    #[serde(skip_serializing_if = "Option::is_none")]
1801    pub text_query: Option<String>,
1802    /// Distance metric
1803    #[serde(default = "default_distance_metric")]
1804    pub distance_metric: String,
1805    /// Whether to actually execute the query for actual stats
1806    #[serde(default)]
1807    pub execute: bool,
1808    /// Include verbose output
1809    #[serde(default)]
1810    pub verbose: bool,
1811}
1812
1813fn default_distance_metric() -> String {
1814    "cosine".to_string()
1815}
1816
1817impl QueryExplainRequest {
1818    /// Create a new explain request for a vector search
1819    pub fn vector_search(vector: Vec<f32>, top_k: usize) -> Self {
1820        Self {
1821            query_type: ExplainQueryType::VectorSearch,
1822            vector: Some(vector),
1823            top_k,
1824            filter: None,
1825            text_query: None,
1826            distance_metric: "cosine".to_string(),
1827            execute: false,
1828            verbose: false,
1829        }
1830    }
1831
1832    /// Create a new explain request for a full-text search
1833    pub fn fulltext_search(text_query: impl Into<String>, top_k: usize) -> Self {
1834        Self {
1835            query_type: ExplainQueryType::FullTextSearch,
1836            vector: None,
1837            top_k,
1838            filter: None,
1839            text_query: Some(text_query.into()),
1840            distance_metric: "bm25".to_string(),
1841            execute: false,
1842            verbose: false,
1843        }
1844    }
1845
1846    /// Create a new explain request for a hybrid search
1847    pub fn hybrid_search(vector: Vec<f32>, text_query: impl Into<String>, top_k: usize) -> Self {
1848        Self {
1849            query_type: ExplainQueryType::HybridSearch,
1850            vector: Some(vector),
1851            top_k,
1852            filter: None,
1853            text_query: Some(text_query.into()),
1854            distance_metric: "hybrid".to_string(),
1855            execute: false,
1856            verbose: false,
1857        }
1858    }
1859
1860    /// Add a filter to the explain request
1861    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
1862        self.filter = Some(filter);
1863        self
1864    }
1865
1866    /// Set the distance metric
1867    pub fn with_distance_metric(mut self, metric: impl Into<String>) -> Self {
1868        self.distance_metric = metric.into();
1869        self
1870    }
1871
1872    /// Execute the query to get actual stats
1873    pub fn with_execution(mut self) -> Self {
1874        self.execute = true;
1875        self
1876    }
1877
1878    /// Enable verbose output
1879    pub fn with_verbose(mut self) -> Self {
1880        self.verbose = true;
1881        self
1882    }
1883}
1884
1885/// A stage in query execution
1886#[derive(Debug, Clone, Serialize, Deserialize)]
1887pub struct ExecutionStage {
1888    /// Stage name
1889    pub name: String,
1890    /// Stage description
1891    pub description: String,
1892    /// Stage order (1-based)
1893    pub order: u32,
1894    /// Estimated input rows
1895    pub estimated_input: u64,
1896    /// Estimated output rows
1897    pub estimated_output: u64,
1898    /// Estimated cost for this stage
1899    pub estimated_cost: f64,
1900    /// Stage-specific details
1901    #[serde(default)]
1902    pub details: HashMap<String, serde_json::Value>,
1903}
1904
1905/// Cost estimation
1906#[derive(Debug, Clone, Serialize, Deserialize)]
1907pub struct CostEstimate {
1908    /// Total estimated cost (abstract units)
1909    pub total_cost: f64,
1910    /// Estimated execution time in milliseconds
1911    pub estimated_time_ms: u64,
1912    /// Estimated memory usage in bytes
1913    pub estimated_memory_bytes: u64,
1914    /// Estimated I/O operations
1915    pub estimated_io_ops: u64,
1916    /// Cost breakdown by component
1917    #[serde(default)]
1918    pub cost_breakdown: HashMap<String, f64>,
1919    /// Confidence level (0.0-1.0)
1920    pub confidence: f64,
1921}
1922
1923/// Actual execution statistics (when execute=true)
1924#[derive(Debug, Clone, Serialize, Deserialize)]
1925pub struct ActualStats {
1926    /// Actual execution time in milliseconds
1927    pub execution_time_ms: u64,
1928    /// Actual results returned
1929    pub results_returned: usize,
1930    /// Vectors scanned
1931    pub vectors_scanned: u64,
1932    /// Vectors after filter
1933    pub vectors_after_filter: u64,
1934    /// Index lookups performed
1935    pub index_lookups: u64,
1936    /// Cache hits
1937    pub cache_hits: u64,
1938    /// Cache misses
1939    pub cache_misses: u64,
1940    /// Memory used in bytes
1941    pub memory_used_bytes: u64,
1942}
1943
1944/// Performance recommendation
1945#[derive(Debug, Clone, Serialize, Deserialize)]
1946pub struct Recommendation {
1947    /// Recommendation type
1948    pub recommendation_type: String,
1949    /// Priority (high, medium, low)
1950    pub priority: String,
1951    /// Recommendation description
1952    pub description: String,
1953    /// Expected improvement
1954    pub expected_improvement: String,
1955    /// How to implement
1956    pub implementation: String,
1957}
1958
1959/// Index selection details
1960#[derive(Debug, Clone, Serialize, Deserialize)]
1961pub struct IndexSelection {
1962    /// Index type that will be used
1963    pub index_type: String,
1964    /// Why this index was selected
1965    pub selection_reason: String,
1966    /// Alternative indexes considered
1967    #[serde(default)]
1968    pub alternatives_considered: Vec<IndexAlternative>,
1969    /// Index configuration
1970    #[serde(default)]
1971    pub index_config: HashMap<String, serde_json::Value>,
1972    /// Index statistics
1973    pub index_stats: IndexStatistics,
1974}
1975
1976/// Alternative index that was considered
1977#[derive(Debug, Clone, Serialize, Deserialize)]
1978pub struct IndexAlternative {
1979    /// Index type
1980    pub index_type: String,
1981    /// Why it wasn't selected
1982    pub rejection_reason: String,
1983    /// Estimated cost if this index was used
1984    pub estimated_cost: f64,
1985}
1986
1987/// Index statistics
1988#[derive(Debug, Clone, Serialize, Deserialize)]
1989pub struct IndexStatistics {
1990    /// Total vectors in index
1991    pub vector_count: u64,
1992    /// Vector dimension
1993    pub dimension: usize,
1994    /// Index memory usage (estimated)
1995    pub memory_bytes: u64,
1996    /// Index build time (if available)
1997    #[serde(skip_serializing_if = "Option::is_none")]
1998    pub build_time_ms: Option<u64>,
1999    /// Last updated timestamp
2000    #[serde(skip_serializing_if = "Option::is_none")]
2001    pub last_updated: Option<u64>,
2002}
2003
2004/// Query parameters for reference
2005#[derive(Debug, Clone, Serialize, Deserialize)]
2006pub struct QueryParams {
2007    /// Number of results requested
2008    pub top_k: usize,
2009    /// Whether a filter was applied
2010    pub has_filter: bool,
2011    /// Filter complexity level
2012    pub filter_complexity: String,
2013    /// Vector dimension (if applicable)
2014    #[serde(skip_serializing_if = "Option::is_none")]
2015    pub vector_dimension: Option<usize>,
2016    /// Distance metric used
2017    pub distance_metric: String,
2018    /// Text query length (if applicable)
2019    #[serde(skip_serializing_if = "Option::is_none")]
2020    pub text_query_length: Option<usize>,
2021}
2022
2023/// Query explain response - detailed execution plan
2024#[derive(Debug, Clone, Serialize, Deserialize)]
2025pub struct QueryExplainResponse {
2026    /// Query type being explained
2027    pub query_type: ExplainQueryType,
2028    /// Namespace being queried
2029    pub namespace: String,
2030    /// Index selection information
2031    pub index_selection: IndexSelection,
2032    /// Query execution stages
2033    pub stages: Vec<ExecutionStage>,
2034    /// Cost estimates
2035    pub cost_estimate: CostEstimate,
2036    /// Actual execution stats (if execute=true)
2037    #[serde(skip_serializing_if = "Option::is_none")]
2038    pub actual_stats: Option<ActualStats>,
2039    /// Performance recommendations
2040    #[serde(default)]
2041    pub recommendations: Vec<Recommendation>,
2042    /// Query plan summary
2043    pub summary: String,
2044    /// Raw query parameters
2045    pub query_params: QueryParams,
2046}
2047
2048// ============================================================================
2049// Text Auto-Embedding Types
2050// ============================================================================
2051
2052/// Supported embedding models for text-based operations.
2053#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
2054#[serde(rename_all = "kebab-case")]
2055pub enum EmbeddingModel {
2056    /// MiniLM-L6 — Fast, good quality (384 dimensions)
2057    #[default]
2058    Minilm,
2059    /// BGE-small — Balanced performance (384 dimensions)
2060    BgeSmall,
2061    /// E5-small — High quality (384 dimensions)
2062    E5Small,
2063}
2064
2065/// A text document to upsert with automatic embedding generation.
2066#[derive(Debug, Clone, Serialize, Deserialize)]
2067pub struct TextDocument {
2068    /// Unique identifier for the document.
2069    pub id: String,
2070    /// Raw text content to be embedded.
2071    pub text: String,
2072    /// Optional metadata for the document.
2073    #[serde(skip_serializing_if = "Option::is_none")]
2074    pub metadata: Option<HashMap<String, serde_json::Value>>,
2075    /// Optional TTL in seconds.
2076    #[serde(skip_serializing_if = "Option::is_none")]
2077    pub ttl_seconds: Option<u64>,
2078}
2079
2080impl TextDocument {
2081    /// Create a new text document with the given ID and text.
2082    pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
2083        Self {
2084            id: id.into(),
2085            text: text.into(),
2086            metadata: None,
2087            ttl_seconds: None,
2088        }
2089    }
2090
2091    /// Add metadata to this document.
2092    pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
2093        self.metadata = Some(metadata);
2094        self
2095    }
2096
2097    /// Set a TTL on this document.
2098    pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
2099        self.ttl_seconds = Some(ttl_seconds);
2100        self
2101    }
2102}
2103
2104/// Request to upsert text documents with automatic embedding.
2105#[derive(Debug, Clone, Serialize, Deserialize)]
2106pub struct UpsertTextRequest {
2107    /// Documents to upsert.
2108    pub documents: Vec<TextDocument>,
2109    /// Embedding model to use (default: minilm).
2110    #[serde(skip_serializing_if = "Option::is_none")]
2111    pub model: Option<EmbeddingModel>,
2112}
2113
2114impl UpsertTextRequest {
2115    /// Create a new upsert-text request.
2116    pub fn new(documents: Vec<TextDocument>) -> Self {
2117        Self {
2118            documents,
2119            model: None,
2120        }
2121    }
2122
2123    /// Set the embedding model.
2124    pub fn with_model(mut self, model: EmbeddingModel) -> Self {
2125        self.model = Some(model);
2126        self
2127    }
2128}
2129
2130/// Response from a text upsert operation.
2131#[derive(Debug, Clone, Serialize, Deserialize)]
2132pub struct TextUpsertResponse {
2133    /// Number of documents upserted.
2134    pub upserted_count: u64,
2135    /// Approximate number of tokens processed.
2136    pub tokens_processed: u64,
2137    /// Embedding model used.
2138    pub model: EmbeddingModel,
2139    /// Time spent generating embeddings in milliseconds.
2140    pub embedding_time_ms: u64,
2141}
2142
2143/// A single text search result.
2144#[derive(Debug, Clone, Serialize, Deserialize)]
2145pub struct TextSearchResult {
2146    /// Document ID.
2147    pub id: String,
2148    /// Similarity score.
2149    pub score: f32,
2150    /// Original text (if `include_text` was true).
2151    #[serde(skip_serializing_if = "Option::is_none")]
2152    pub text: Option<String>,
2153    /// Document metadata.
2154    #[serde(skip_serializing_if = "Option::is_none")]
2155    pub metadata: Option<HashMap<String, serde_json::Value>>,
2156    /// Vector values (if `include_vectors` was true).
2157    #[serde(skip_serializing_if = "Option::is_none")]
2158    pub vector: Option<Vec<f32>>,
2159}
2160
2161/// Request to query using natural language text with automatic embedding.
2162#[derive(Debug, Clone, Serialize, Deserialize)]
2163pub struct QueryTextRequest {
2164    /// Query text.
2165    pub text: String,
2166    /// Number of results to return.
2167    pub top_k: u32,
2168    /// Optional metadata filter.
2169    #[serde(skip_serializing_if = "Option::is_none")]
2170    pub filter: Option<serde_json::Value>,
2171    /// Whether to include the original text in results.
2172    pub include_text: bool,
2173    /// Whether to include vectors in results.
2174    pub include_vectors: bool,
2175    /// Embedding model to use (default: minilm).
2176    #[serde(skip_serializing_if = "Option::is_none")]
2177    pub model: Option<EmbeddingModel>,
2178}
2179
2180impl QueryTextRequest {
2181    /// Create a new text query request.
2182    pub fn new(text: impl Into<String>, top_k: u32) -> Self {
2183        Self {
2184            text: text.into(),
2185            top_k,
2186            filter: None,
2187            include_text: true,
2188            include_vectors: false,
2189            model: None,
2190        }
2191    }
2192
2193    /// Add a metadata filter.
2194    pub fn with_filter(mut self, filter: serde_json::Value) -> Self {
2195        self.filter = Some(filter);
2196        self
2197    }
2198
2199    /// Set whether to include the original text in results.
2200    pub fn include_text(mut self, include: bool) -> Self {
2201        self.include_text = include;
2202        self
2203    }
2204
2205    /// Set whether to include vectors in results.
2206    pub fn include_vectors(mut self, include: bool) -> Self {
2207        self.include_vectors = include;
2208        self
2209    }
2210
2211    /// Set the embedding model.
2212    pub fn with_model(mut self, model: EmbeddingModel) -> Self {
2213        self.model = Some(model);
2214        self
2215    }
2216}
2217
2218/// Response from a text query operation.
2219#[derive(Debug, Clone, Serialize, Deserialize)]
2220pub struct TextQueryResponse {
2221    /// Search results.
2222    pub results: Vec<TextSearchResult>,
2223    /// Embedding model used.
2224    pub model: EmbeddingModel,
2225    /// Time spent generating the query embedding in milliseconds.
2226    pub embedding_time_ms: u64,
2227    /// Time spent searching in milliseconds.
2228    pub search_time_ms: u64,
2229}
2230
2231/// Request to execute multiple text queries with automatic embedding in a single call.
2232#[derive(Debug, Clone, Serialize, Deserialize)]
2233pub struct BatchQueryTextRequest {
2234    /// Text queries.
2235    pub queries: Vec<String>,
2236    /// Number of results per query.
2237    pub top_k: u32,
2238    /// Optional metadata filter applied to all queries.
2239    #[serde(skip_serializing_if = "Option::is_none")]
2240    pub filter: Option<serde_json::Value>,
2241    /// Whether to include vectors in results.
2242    pub include_vectors: bool,
2243    /// Embedding model to use (default: minilm).
2244    #[serde(skip_serializing_if = "Option::is_none")]
2245    pub model: Option<EmbeddingModel>,
2246}
2247
2248impl BatchQueryTextRequest {
2249    /// Create a new batch text query request.
2250    pub fn new(queries: Vec<String>, top_k: u32) -> Self {
2251        Self {
2252            queries,
2253            top_k,
2254            filter: None,
2255            include_vectors: false,
2256            model: None,
2257        }
2258    }
2259}
2260
2261/// Response from a batch text query operation.
2262#[derive(Debug, Clone, Serialize, Deserialize)]
2263pub struct BatchQueryTextResponse {
2264    /// Results for each query (in the same order as the request).
2265    pub results: Vec<Vec<TextSearchResult>>,
2266    /// Embedding model used.
2267    pub model: EmbeddingModel,
2268    /// Time spent generating all embeddings in milliseconds.
2269    pub embedding_time_ms: u64,
2270    /// Time spent on all searches in milliseconds.
2271    pub search_time_ms: u64,
2272}
2273
2274// ============================================================================
2275// Fetch by ID Types
2276// ============================================================================
2277
2278/// Request to fetch vectors by their IDs.
2279#[derive(Debug, Clone, Serialize, Deserialize)]
2280pub struct FetchRequest {
2281    /// IDs of vectors to fetch.
2282    pub ids: Vec<String>,
2283    /// Whether to include vector values.
2284    pub include_values: bool,
2285    /// Whether to include metadata.
2286    pub include_metadata: bool,
2287}
2288
2289impl FetchRequest {
2290    /// Create a new fetch request.
2291    pub fn new(ids: Vec<String>) -> Self {
2292        Self {
2293            ids,
2294            include_values: true,
2295            include_metadata: true,
2296        }
2297    }
2298}
2299
2300/// Response from a fetch-by-ID operation.
2301#[derive(Debug, Clone, Serialize, Deserialize)]
2302pub struct FetchResponse {
2303    /// Fetched vectors.
2304    pub vectors: Vec<Vector>,
2305}
2306
2307// ============================================================================
2308// Namespace Management Types
2309// ============================================================================
2310
2311/// Request to create a new namespace.
2312#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2313pub struct CreateNamespaceRequest {
2314    /// Vector dimensions (inferred from first upsert if omitted).
2315    #[serde(skip_serializing_if = "Option::is_none")]
2316    pub dimensions: Option<u32>,
2317    /// Index type (e.g. "hnsw", "flat").
2318    #[serde(skip_serializing_if = "Option::is_none")]
2319    pub index_type: Option<String>,
2320    /// Arbitrary namespace metadata.
2321    #[serde(skip_serializing_if = "Option::is_none")]
2322    pub metadata: Option<HashMap<String, serde_json::Value>>,
2323}
2324
2325impl CreateNamespaceRequest {
2326    /// Create a minimal request (server picks sensible defaults).
2327    pub fn new() -> Self {
2328        Self::default()
2329    }
2330
2331    /// Set the vector dimensions.
2332    pub fn with_dimensions(mut self, dimensions: u32) -> Self {
2333        self.dimensions = Some(dimensions);
2334        self
2335    }
2336
2337    /// Set the index type.
2338    pub fn with_index_type(mut self, index_type: impl Into<String>) -> Self {
2339        self.index_type = Some(index_type.into());
2340        self
2341    }
2342}
2343
2344/// Request body for `PUT /v1/namespaces/:namespace` — upsert semantics (v0.6.0).
2345///
2346/// Creates the namespace if it does not exist, or updates its configuration
2347/// if it already exists.  Requires `Scope::Write`.
2348#[derive(Debug, Clone, Serialize, Deserialize)]
2349pub struct ConfigureNamespaceRequest {
2350    /// Vector dimension.  Required on first creation; must match on subsequent calls.
2351    pub dimension: usize,
2352    /// Distance metric (defaults to cosine when omitted).
2353    #[serde(skip_serializing_if = "Option::is_none")]
2354    pub distance: Option<DistanceMetric>,
2355}
2356
2357impl ConfigureNamespaceRequest {
2358    /// Create a new configure-namespace request with the given dimension.
2359    pub fn new(dimension: usize) -> Self {
2360        Self {
2361            dimension,
2362            distance: None,
2363        }
2364    }
2365
2366    /// Set the distance metric.
2367    pub fn with_distance(mut self, distance: DistanceMetric) -> Self {
2368        self.distance = Some(distance);
2369        self
2370    }
2371}
2372
2373/// Response from `PUT /v1/namespaces/:namespace`.
2374#[derive(Debug, Clone, Serialize, Deserialize)]
2375pub struct ConfigureNamespaceResponse {
2376    /// Namespace name.
2377    pub namespace: String,
2378    /// Vector dimension.
2379    pub dimension: usize,
2380    /// Distance metric in use.
2381    pub distance: DistanceMetric,
2382    /// `true` if the namespace was newly created; `false` if it already existed.
2383    pub created: bool,
2384}