Skip to main content

chroma_types/
api_types.rs

1use crate::collection_configuration::InternalCollectionConfiguration;
2use crate::collection_configuration::InternalUpdateCollectionConfiguration;
3use crate::error::QueryConversionError;
4use crate::operator::GetResult;
5use crate::operator::Key;
6use crate::operator::KnnBatchResult;
7use crate::operator::KnnProjectionRecord;
8use crate::operator::ProjectionRecord;
9use crate::operator::SearchResult;
10use crate::operators_generated::{
11    FUNCTION_RECORD_COUNTER_ID, FUNCTION_RECORD_COUNTER_NAME, FUNCTION_STATISTICS_ID,
12    FUNCTION_STATISTICS_NAME,
13};
14use crate::plan::PlanToProtoError;
15use crate::plan::ReadLevel;
16use crate::plan::SearchPayload;
17use crate::validators::{
18    validate_metadata_vec, validate_name, validate_non_empty_collection_update_metadata,
19    validate_optional_metadata, validate_schema, validate_update_metadata_vec,
20};
21use crate::AttachedFunction;
22use crate::AttachedFunctionUuid;
23use crate::Collection;
24use crate::CollectionConfigurationToInternalConfigurationError;
25use crate::CollectionConversionError;
26use crate::CollectionUuid;
27use crate::DatabaseName;
28use crate::DistributedSpannParametersFromSegmentError;
29use crate::EmbeddingsPayload;
30use crate::HnswParametersFromSegmentError;
31use crate::Metadata;
32use crate::RawWhereFields;
33use crate::Schema;
34use crate::SchemaError;
35use crate::SegmentConversionError;
36use crate::SegmentScopeConversionError;
37use crate::UpdateEmbeddingsPayload;
38use crate::UpdateMetadata;
39use crate::Where;
40use crate::WhereValidationError;
41use chroma_error::ChromaValidationError;
42use chroma_error::{ChromaError, ErrorCodes};
43use serde::Deserialize;
44use serde::Serialize;
45use std::time::SystemTimeError;
46use thiserror::Error;
47use tonic::Status;
48use uuid::Uuid;
49use validator::Validate;
50use validator::ValidationError;
51
52#[cfg(feature = "pyo3")]
53use pyo3::types::PyAnyMethods;
54
55#[derive(Debug, Error)]
56pub enum GetSegmentsError {
57    #[error("Could not parse segment")]
58    SegmentConversion(#[from] SegmentConversionError),
59    #[error("Unknown segment scope")]
60    UnknownScope(#[from] SegmentScopeConversionError),
61    #[error(transparent)]
62    Internal(#[from] Box<dyn ChromaError>),
63}
64
65impl ChromaError for GetSegmentsError {
66    fn code(&self) -> ErrorCodes {
67        match self {
68            GetSegmentsError::SegmentConversion(_) => ErrorCodes::Internal,
69            GetSegmentsError::UnknownScope(_) => ErrorCodes::Internal,
70            GetSegmentsError::Internal(err) => err.code(),
71        }
72    }
73}
74
75#[derive(Debug, Error)]
76pub enum GetCollectionWithSegmentsError {
77    #[error("Failed to convert proto collection")]
78    CollectionConversionError(#[from] CollectionConversionError),
79    #[error("Duplicate segment")]
80    DuplicateSegment,
81    #[error("Missing field: [{0}]")]
82    Field(String),
83    #[error("Failed to convert proto segment")]
84    SegmentConversionError(#[from] SegmentConversionError),
85    #[error("Failed to get segments")]
86    GetSegmentsError(#[from] GetSegmentsError),
87    #[error("Grpc error: {0}")]
88    Grpc(#[from] Status),
89    #[error("Collection [{0}] does not exist.")]
90    NotFound(String),
91    #[error(transparent)]
92    Internal(#[from] Box<dyn ChromaError>),
93}
94
95impl ChromaError for GetCollectionWithSegmentsError {
96    fn code(&self) -> ErrorCodes {
97        match self {
98            GetCollectionWithSegmentsError::CollectionConversionError(
99                collection_conversion_error,
100            ) => collection_conversion_error.code(),
101            GetCollectionWithSegmentsError::DuplicateSegment => ErrorCodes::Internal,
102            GetCollectionWithSegmentsError::Field(_) => ErrorCodes::FailedPrecondition,
103            GetCollectionWithSegmentsError::SegmentConversionError(segment_conversion_error) => {
104                segment_conversion_error.code()
105            }
106            GetCollectionWithSegmentsError::Grpc(status) => status.code().into(),
107            GetCollectionWithSegmentsError::GetSegmentsError(get_segments_error) => {
108                get_segments_error.code()
109            }
110            GetCollectionWithSegmentsError::NotFound(_) => ErrorCodes::NotFound,
111            GetCollectionWithSegmentsError::Internal(err) => err.code(),
112        }
113    }
114
115    fn should_trace_error(&self) -> bool {
116        if let Self::Grpc(status) = self {
117            status.code() != ErrorCodes::NotFound.into()
118        } else {
119            true
120        }
121    }
122}
123
124#[derive(Debug, Error)]
125pub enum BatchGetCollectionVersionFilePathsError {
126    #[error("Grpc error: {0}")]
127    Grpc(#[from] Status),
128    #[error("Could not parse UUID from string {1}: {0}")]
129    Uuid(uuid::Error, String),
130}
131
132impl ChromaError for BatchGetCollectionVersionFilePathsError {
133    fn code(&self) -> ErrorCodes {
134        match self {
135            BatchGetCollectionVersionFilePathsError::Grpc(status) => status.code().into(),
136            BatchGetCollectionVersionFilePathsError::Uuid(_, _) => ErrorCodes::InvalidArgument,
137        }
138    }
139}
140
141#[derive(Debug, Error)]
142pub enum BatchGetCollectionSoftDeleteStatusError {
143    #[error("Grpc error: {0}")]
144    Grpc(#[from] Status),
145    #[error("Could not parse UUID from string {1}: {0}")]
146    Uuid(uuid::Error, String),
147}
148
149impl ChromaError for BatchGetCollectionSoftDeleteStatusError {
150    fn code(&self) -> ErrorCodes {
151        match self {
152            BatchGetCollectionSoftDeleteStatusError::Grpc(status) => status.code().into(),
153            BatchGetCollectionSoftDeleteStatusError::Uuid(_, _) => ErrorCodes::InvalidArgument,
154        }
155    }
156}
157
158#[derive(Serialize)]
159#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
160pub struct ResetResponse {}
161
162#[derive(Debug, Error)]
163pub enum ResetError {
164    #[error(transparent)]
165    Cache(Box<dyn ChromaError>),
166    #[error(transparent)]
167    Internal(#[from] Box<dyn ChromaError>),
168    #[error("Reset is disabled by config")]
169    NotAllowed,
170}
171
172impl ChromaError for ResetError {
173    fn code(&self) -> ErrorCodes {
174        match self {
175            ResetError::Cache(err) => err.code(),
176            ResetError::Internal(err) => err.code(),
177            ResetError::NotAllowed => ErrorCodes::PermissionDenied,
178        }
179    }
180}
181
182#[derive(Serialize)]
183#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
184pub struct ChecklistResponse {
185    pub max_batch_size: u32,
186    pub supports_base64_encoding: bool,
187}
188
189#[derive(Debug, Error)]
190#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
191pub enum HeartbeatError {
192    #[error("system time error: {0}")]
193    CouldNotGetTime(String),
194}
195
196impl From<SystemTimeError> for HeartbeatError {
197    fn from(err: SystemTimeError) -> Self {
198        HeartbeatError::CouldNotGetTime(err.to_string())
199    }
200}
201
202impl ChromaError for HeartbeatError {
203    fn code(&self) -> ErrorCodes {
204        ErrorCodes::Internal
205    }
206}
207
208#[non_exhaustive]
209#[derive(Serialize, Validate, Deserialize)]
210#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
211pub struct CreateTenantRequest {
212    #[validate(length(min = 3))]
213    pub name: String,
214}
215
216impl CreateTenantRequest {
217    pub fn try_new(name: String) -> Result<Self, ChromaValidationError> {
218        let request = Self { name };
219        request.validate().map_err(ChromaValidationError::from)?;
220        Ok(request)
221    }
222}
223
224#[derive(Serialize, Deserialize)]
225#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
226pub struct CreateTenantResponse {}
227
228#[derive(Debug, Error)]
229pub enum CreateTenantError {
230    #[error("Tenant [{0}] already exists")]
231    AlreadyExists(String),
232    #[error(transparent)]
233    Internal(#[from] Box<dyn ChromaError>),
234}
235
236impl ChromaError for CreateTenantError {
237    fn code(&self) -> ErrorCodes {
238        match self {
239            CreateTenantError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
240            CreateTenantError::Internal(err) => err.code(),
241        }
242    }
243}
244
245#[non_exhaustive]
246#[derive(Validate, Serialize)]
247#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
248pub struct GetTenantRequest {
249    pub name: String,
250}
251
252impl GetTenantRequest {
253    pub fn try_new(name: String) -> Result<Self, ChromaValidationError> {
254        let request = Self { name };
255        request.validate().map_err(ChromaValidationError::from)?;
256        Ok(request)
257    }
258}
259
260#[derive(Serialize)]
261#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
262#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
263pub struct GetTenantResponse {
264    pub name: String,
265    pub resource_name: Option<String>,
266}
267
268#[cfg(feature = "pyo3")]
269#[pyo3::pymethods]
270impl GetTenantResponse {
271    #[getter]
272    pub fn name(&self) -> &String {
273        &self.name
274    }
275
276    #[getter]
277    pub fn resource_name(&self) -> Option<String> {
278        self.resource_name.clone()
279    }
280}
281
282#[derive(Debug, Error)]
283pub enum GetTenantError {
284    #[error(transparent)]
285    Internal(#[from] Box<dyn ChromaError>),
286    #[error("Tenant [{0}] not found")]
287    NotFound(String),
288}
289
290impl ChromaError for GetTenantError {
291    fn code(&self) -> ErrorCodes {
292        match self {
293            GetTenantError::Internal(err) => err.code(),
294            GetTenantError::NotFound(_) => ErrorCodes::NotFound,
295        }
296    }
297}
298
299#[non_exhaustive]
300#[derive(Validate, Serialize)]
301#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
302pub struct UpdateTenantRequest {
303    pub tenant_id: String,
304    pub resource_name: String,
305}
306
307impl UpdateTenantRequest {
308    pub fn try_new(
309        tenant_id: String,
310        resource_name: String,
311    ) -> Result<Self, ChromaValidationError> {
312        let request = Self {
313            tenant_id,
314            resource_name,
315        };
316        request.validate().map_err(ChromaValidationError::from)?;
317        Ok(request)
318    }
319}
320
321#[derive(Serialize)]
322#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
323#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
324pub struct UpdateTenantResponse {}
325
326#[cfg(feature = "pyo3")]
327#[pyo3::pymethods]
328impl UpdateTenantResponse {}
329
330#[derive(Error, Debug)]
331pub enum UpdateTenantError {
332    #[error("Failed to set resource name")]
333    FailedToSetResourceName(#[from] tonic::Status),
334    #[error(transparent)]
335    Internal(#[from] Box<dyn ChromaError>),
336    #[error("Tenant [{0}] not found")]
337    NotFound(String),
338}
339
340impl ChromaError for UpdateTenantError {
341    fn code(&self) -> ErrorCodes {
342        match self {
343            UpdateTenantError::FailedToSetResourceName(_) => ErrorCodes::AlreadyExists,
344            UpdateTenantError::Internal(err) => err.code(),
345            UpdateTenantError::NotFound(_) => ErrorCodes::NotFound,
346        }
347    }
348}
349
350#[non_exhaustive]
351#[derive(Validate, Serialize)]
352#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
353pub struct CreateDatabaseRequest {
354    pub database_id: Uuid,
355    pub tenant_id: String,
356    pub database_name: DatabaseName,
357}
358
359impl CreateDatabaseRequest {
360    pub fn try_new(
361        tenant_id: String,
362        database_name: DatabaseName,
363    ) -> Result<Self, ChromaValidationError> {
364        let database_id = Uuid::new_v4();
365        let request = Self {
366            database_id,
367            tenant_id,
368            database_name,
369        };
370        request.validate().map_err(ChromaValidationError::from)?;
371        Ok(request)
372    }
373}
374
375#[derive(Error, Debug)]
376pub enum ClientResolutionError {
377    #[error("Not supported")]
378    McmrNotSupported,
379    #[error("Database not found")]
380    DatabaseNotFound,
381}
382
383impl ChromaError for ClientResolutionError {
384    fn code(&self) -> ErrorCodes {
385        match self {
386            ClientResolutionError::McmrNotSupported => ErrorCodes::InvalidArgument,
387            ClientResolutionError::DatabaseNotFound => ErrorCodes::NotFound,
388        }
389    }
390}
391
392#[derive(Serialize)]
393#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
394pub struct CreateDatabaseResponse {}
395
396#[derive(Error, Debug)]
397pub enum CreateDatabaseError {
398    #[error("Database [{0}] already exists")]
399    AlreadyExists(String),
400    #[error(transparent)]
401    Internal(#[from] Box<dyn ChromaError>),
402    #[error("Client resolution error: {0}")]
403    ClientResolutionError(#[from] ClientResolutionError),
404}
405
406impl ChromaError for CreateDatabaseError {
407    fn code(&self) -> ErrorCodes {
408        match self {
409            CreateDatabaseError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
410            CreateDatabaseError::Internal(status) => status.code(),
411            CreateDatabaseError::ClientResolutionError(e) => e.code(),
412        }
413    }
414}
415
416#[derive(Serialize, Deserialize, Debug, Clone, Default)]
417#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
418#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
419pub struct Database {
420    pub id: Uuid,
421    pub name: String,
422    pub tenant: String,
423}
424
425#[cfg(feature = "pyo3")]
426#[pyo3::pymethods]
427impl Database {
428    #[getter]
429    fn id<'py>(&self, py: pyo3::Python<'py>) -> pyo3::PyResult<pyo3::Bound<'py, pyo3::PyAny>> {
430        let res = pyo3::prelude::PyModule::import(py, "uuid")?
431            .getattr("UUID")?
432            .call1((self.id.to_string(),))?;
433        Ok(res)
434    }
435
436    #[getter]
437    pub fn name(&self) -> &str {
438        &self.name
439    }
440
441    #[getter]
442    pub fn tenant(&self) -> &str {
443        &self.tenant
444    }
445}
446
447impl From<Database> for crate::chroma_proto::Database {
448    fn from(d: Database) -> Self {
449        crate::chroma_proto::Database {
450            id: d.id.to_string(),
451            name: d.name,
452            tenant: d.tenant,
453        }
454    }
455}
456
457#[non_exhaustive]
458#[derive(Validate, Serialize)]
459#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
460pub struct ListDatabasesRequest {
461    pub tenant_id: String,
462    pub limit: Option<u32>,
463    pub offset: u32,
464}
465
466impl ListDatabasesRequest {
467    pub fn try_new(
468        tenant_id: String,
469        limit: Option<u32>,
470        offset: u32,
471    ) -> Result<Self, ChromaValidationError> {
472        let request = Self {
473            tenant_id,
474            limit,
475            offset,
476        };
477        request.validate().map_err(ChromaValidationError::from)?;
478        Ok(request)
479    }
480}
481
482pub type ListDatabasesResponse = Vec<Database>;
483
484#[derive(Debug, Error)]
485pub enum ListDatabasesError {
486    #[error(transparent)]
487    Internal(#[from] Box<dyn ChromaError>),
488    #[error("Invalid database id [{0}]")]
489    InvalidID(String),
490}
491
492impl ChromaError for ListDatabasesError {
493    fn code(&self) -> ErrorCodes {
494        match self {
495            ListDatabasesError::Internal(status) => status.code(),
496            ListDatabasesError::InvalidID(_) => ErrorCodes::InvalidArgument,
497        }
498    }
499}
500
501#[non_exhaustive]
502#[derive(Validate, Serialize)]
503#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
504pub struct GetDatabaseRequest {
505    pub tenant_id: String,
506    pub database_name: DatabaseName,
507}
508
509impl GetDatabaseRequest {
510    pub fn try_new(
511        tenant_id: String,
512        database_name: DatabaseName,
513    ) -> Result<Self, ChromaValidationError> {
514        let request = Self {
515            tenant_id,
516            database_name,
517        };
518        request.validate().map_err(ChromaValidationError::from)?;
519        Ok(request)
520    }
521}
522
523pub type GetDatabaseResponse = Database;
524
525#[derive(Error, Debug)]
526pub enum GetDatabaseError {
527    #[error(transparent)]
528    Internal(#[from] Box<dyn ChromaError>),
529    #[error("Invalid database id [{0}]")]
530    InvalidID(String),
531    #[error("Database [{0}] not found. Are you sure it exists?")]
532    NotFound(String),
533    #[error("Client resolution error: {0}")]
534    ClientResolutionError(#[from] ClientResolutionError),
535}
536
537impl ChromaError for GetDatabaseError {
538    fn code(&self) -> ErrorCodes {
539        match self {
540            GetDatabaseError::Internal(err) => err.code(),
541            GetDatabaseError::InvalidID(_) => ErrorCodes::InvalidArgument,
542            GetDatabaseError::NotFound(_) => ErrorCodes::NotFound,
543            GetDatabaseError::ClientResolutionError(e) => e.code(),
544        }
545    }
546}
547
548#[non_exhaustive]
549#[derive(Validate, Serialize)]
550#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
551pub struct DeleteDatabaseRequest {
552    pub tenant_id: String,
553    pub database_name: String,
554}
555
556impl DeleteDatabaseRequest {
557    pub fn try_new(
558        tenant_id: String,
559        database_name: String,
560    ) -> Result<Self, ChromaValidationError> {
561        let request = Self {
562            tenant_id,
563            database_name,
564        };
565        request.validate().map_err(ChromaValidationError::from)?;
566        Ok(request)
567    }
568}
569
570#[derive(Serialize)]
571#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
572pub struct DeleteDatabaseResponse {}
573
574#[derive(Debug, Error)]
575pub enum DeleteDatabaseError {
576    #[error(transparent)]
577    Internal(#[from] Box<dyn ChromaError>),
578    #[error("Invalid database id [{0}]")]
579    InvalidID(String),
580    #[error("Database [{0}] not found")]
581    NotFound(String),
582}
583
584impl ChromaError for DeleteDatabaseError {
585    fn code(&self) -> ErrorCodes {
586        match self {
587            DeleteDatabaseError::Internal(err) => err.code(),
588            DeleteDatabaseError::InvalidID(_) => ErrorCodes::InvalidArgument,
589            DeleteDatabaseError::NotFound(_) => ErrorCodes::NotFound,
590        }
591    }
592}
593
594#[derive(Debug, Error)]
595pub enum FinishDatabaseDeletionError {
596    #[error(transparent)]
597    Internal(#[from] Box<dyn ChromaError>),
598}
599
600impl ChromaError for FinishDatabaseDeletionError {
601    fn code(&self) -> ErrorCodes {
602        match self {
603            FinishDatabaseDeletionError::Internal(err) => err.code(),
604        }
605    }
606}
607
608#[non_exhaustive]
609#[derive(Validate, Debug, Serialize)]
610#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
611pub struct ListCollectionsRequest {
612    pub tenant_id: String,
613    pub database_name: DatabaseName,
614    pub limit: Option<u32>,
615    pub offset: u32,
616}
617
618impl ListCollectionsRequest {
619    pub fn try_new(
620        tenant_id: String,
621        database_name: DatabaseName,
622        limit: Option<u32>,
623        offset: u32,
624    ) -> Result<Self, ChromaValidationError> {
625        let request = Self {
626            tenant_id,
627            database_name,
628            limit,
629            offset,
630        };
631        request.validate().map_err(ChromaValidationError::from)?;
632        Ok(request)
633    }
634}
635
636pub type ListCollectionsResponse = Vec<Collection>;
637
638#[non_exhaustive]
639#[derive(Validate, Serialize)]
640#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
641pub struct CountCollectionsRequest {
642    pub tenant_id: String,
643    pub database_name: DatabaseName,
644}
645
646impl CountCollectionsRequest {
647    pub fn try_new(
648        tenant_id: String,
649        database_name: DatabaseName,
650    ) -> Result<Self, ChromaValidationError> {
651        let request = Self {
652            tenant_id,
653            database_name,
654        };
655        request.validate().map_err(ChromaValidationError::from)?;
656        Ok(request)
657    }
658}
659
660pub type CountCollectionsResponse = u32;
661
662#[non_exhaustive]
663#[derive(Validate, Clone, Serialize)]
664#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
665pub struct GetCollectionRequest {
666    pub tenant_id: String,
667    pub database_name: DatabaseName,
668    pub collection_name: String,
669}
670
671impl GetCollectionRequest {
672    pub fn try_new(
673        tenant_id: String,
674        database_name: DatabaseName,
675        collection_name: String,
676    ) -> Result<Self, ChromaValidationError> {
677        let request = Self {
678            tenant_id,
679            database_name,
680            collection_name,
681        };
682        request.validate().map_err(ChromaValidationError::from)?;
683        Ok(request)
684    }
685}
686
687pub type GetCollectionResponse = Collection;
688
689#[derive(Debug, Error)]
690pub enum GetCollectionError {
691    #[error("Failed to reconcile schema: {0}")]
692    InvalidSchema(#[from] SchemaError),
693    #[error(transparent)]
694    Internal(#[from] Box<dyn ChromaError>),
695    #[error("Collection [{0}] does not exist")]
696    NotFound(String),
697}
698
699impl ChromaError for GetCollectionError {
700    fn code(&self) -> ErrorCodes {
701        match self {
702            GetCollectionError::InvalidSchema(e) => e.code(),
703            GetCollectionError::Internal(err) => err.code(),
704            GetCollectionError::NotFound(_) => ErrorCodes::NotFound,
705        }
706    }
707}
708
709#[non_exhaustive]
710#[derive(Clone, Debug, Validate, Serialize)]
711#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
712pub struct CreateCollectionRequest {
713    pub tenant_id: String,
714    pub database_name: DatabaseName,
715    #[validate(custom(function = "validate_name"))]
716    pub name: String,
717    #[validate(custom(function = "validate_optional_metadata"))]
718    pub metadata: Option<Metadata>,
719    pub configuration: Option<InternalCollectionConfiguration>,
720    #[validate(custom(function = "validate_schema"))]
721    pub schema: Option<Schema>,
722    pub get_or_create: bool,
723}
724
725impl CreateCollectionRequest {
726    pub fn try_new(
727        tenant_id: String,
728        database_name: DatabaseName,
729        name: String,
730        metadata: Option<Metadata>,
731        configuration: Option<InternalCollectionConfiguration>,
732        schema: Option<Schema>,
733        get_or_create: bool,
734    ) -> Result<Self, ChromaValidationError> {
735        let request = Self {
736            tenant_id,
737            database_name,
738            name,
739            metadata,
740            configuration,
741            schema,
742            get_or_create,
743        };
744        request.validate().map_err(ChromaValidationError::from)?;
745        Ok(request)
746    }
747}
748
749pub type CreateCollectionResponse = Collection;
750
751#[derive(Debug, Error)]
752pub enum CreateCollectionError {
753    #[error("Invalid HNSW parameters: {0}")]
754    InvalidHnswParameters(#[from] HnswParametersFromSegmentError),
755    #[error("Could not parse config: {0}")]
756    InvalidConfig(#[from] CollectionConfigurationToInternalConfigurationError),
757    #[error("Invalid Spann parameters: {0}")]
758    InvalidSpannParameters(#[from] DistributedSpannParametersFromSegmentError),
759    #[error("Collection [{0}] already exists")]
760    AlreadyExists(String),
761    #[error("Database [{0}] does not exist")]
762    DatabaseNotFound(String),
763    #[error("Could not fetch collections: {0}")]
764    Get(#[from] GetCollectionsError),
765    #[error("Could not deserialize configuration: {0}")]
766    Configuration(serde_json::Error),
767    #[error("Could not serialize schema: {0}")]
768    Schema(#[source] SchemaError),
769    #[error(transparent)]
770    Internal(#[from] Box<dyn ChromaError>),
771    #[error("The operation was aborted, {0}")]
772    Aborted(String),
773    #[error("SPANN is still in development. Not allowed to created spann indexes")]
774    SpannNotImplemented,
775    #[error("HNSW is not supported on this platform")]
776    HnswNotSupported,
777    #[error("Failed to parse db id")]
778    DatabaseIdParseError,
779    #[error("Failed to reconcile schema: {0}")]
780    InvalidSchema(#[source] SchemaError),
781}
782
783impl ChromaError for CreateCollectionError {
784    fn code(&self) -> ErrorCodes {
785        match self {
786            CreateCollectionError::InvalidHnswParameters(_) => ErrorCodes::InvalidArgument,
787            CreateCollectionError::InvalidConfig(_) => ErrorCodes::InvalidArgument,
788            CreateCollectionError::InvalidSpannParameters(_) => ErrorCodes::InvalidArgument,
789            CreateCollectionError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
790            CreateCollectionError::DatabaseNotFound(_) => ErrorCodes::InvalidArgument,
791            CreateCollectionError::Get(err) => err.code(),
792            CreateCollectionError::Configuration(_) => ErrorCodes::Internal,
793            CreateCollectionError::Internal(err) => err.code(),
794            CreateCollectionError::Aborted(_) => ErrorCodes::Aborted,
795            CreateCollectionError::SpannNotImplemented => ErrorCodes::InvalidArgument,
796            CreateCollectionError::HnswNotSupported => ErrorCodes::InvalidArgument,
797            CreateCollectionError::DatabaseIdParseError => ErrorCodes::Internal,
798            CreateCollectionError::InvalidSchema(e) => e.code(),
799            CreateCollectionError::Schema(e) => e.code(),
800        }
801    }
802}
803
804#[derive(Debug, Error)]
805pub enum CountCollectionsError {
806    #[error("Internal error in getting count")]
807    Internal,
808}
809
810impl ChromaError for CountCollectionsError {
811    fn code(&self) -> ErrorCodes {
812        match self {
813            CountCollectionsError::Internal => ErrorCodes::Internal,
814        }
815    }
816}
817
818#[derive(Debug, Error)]
819pub enum GetCollectionsError {
820    #[error("Failed to reconcile schema: {0}")]
821    InvalidSchema(#[from] SchemaError),
822    #[error(transparent)]
823    Internal(#[from] Box<dyn ChromaError>),
824    #[error("Could not deserialize configuration")]
825    Configuration(#[source] serde_json::Error),
826    #[error("Could not deserialize collection ID")]
827    CollectionId(#[from] uuid::Error),
828    #[error("Could not deserialize database ID")]
829    DatabaseId,
830    #[error("Could not deserialize schema")]
831    Schema(#[source] serde_json::Error),
832}
833
834impl ChromaError for GetCollectionsError {
835    fn code(&self) -> ErrorCodes {
836        match self {
837            GetCollectionsError::InvalidSchema(e) => e.code(),
838            GetCollectionsError::Internal(err) => err.code(),
839            GetCollectionsError::Configuration(_) => ErrorCodes::Internal,
840            GetCollectionsError::CollectionId(_) => ErrorCodes::Internal,
841            GetCollectionsError::DatabaseId => ErrorCodes::Internal,
842            GetCollectionsError::Schema(_) => ErrorCodes::Internal,
843        }
844    }
845}
846
847#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
848#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
849pub struct ChromaResourceName {
850    pub tenant_resource_name: String,
851    pub database_name: String,
852    pub collection_name: String,
853}
854#[non_exhaustive]
855#[derive(Clone, Serialize)]
856#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
857pub struct GetCollectionByCrnRequest {
858    pub parsed_crn: ChromaResourceName,
859}
860
861impl GetCollectionByCrnRequest {
862    pub fn try_new(crn: String) -> Result<Self, ChromaValidationError> {
863        let parsed_crn = parse_and_validate_crn(&crn)?;
864        Ok(Self { parsed_crn })
865    }
866}
867
868fn parse_and_validate_crn(crn: &str) -> Result<ChromaResourceName, ChromaValidationError> {
869    let mut parts = crn.splitn(4, ':');
870    if let (Some(p1), Some(p2), Some(p3), None) =
871        (parts.next(), parts.next(), parts.next(), parts.next())
872    {
873        if !p1.is_empty() && !p2.is_empty() && !p3.is_empty() {
874            return Ok(ChromaResourceName {
875                tenant_resource_name: p1.to_string(),
876                database_name: p2.to_string(),
877                collection_name: p3.to_string(),
878            });
879        }
880    }
881    let mut err = ValidationError::new("invalid_crn_format");
882    err.message = Some(
883        "CRN must be in the format <tenant_resource_name>:<database_name>:<collection_name> with non-empty parts"
884            .into(),
885    );
886    Err(ChromaValidationError::from(("crn", err)))
887}
888
889pub type GetCollectionByCrnResponse = Collection;
890
891#[derive(Debug, Error)]
892pub enum GetCollectionByCrnError {
893    #[error("Failed to reconcile schema: {0}")]
894    InvalidSchema(#[from] SchemaError),
895    #[error(transparent)]
896    Internal(#[from] Box<dyn ChromaError>),
897    #[error("Collection [{0}] does not exist")]
898    NotFound(String),
899}
900
901impl ChromaError for GetCollectionByCrnError {
902    fn code(&self) -> ErrorCodes {
903        match self {
904            GetCollectionByCrnError::InvalidSchema(e) => e.code(),
905            GetCollectionByCrnError::Internal(err) => err.code(),
906            GetCollectionByCrnError::NotFound(_) => ErrorCodes::NotFound,
907        }
908    }
909}
910
911#[derive(Clone, Deserialize, Serialize, Debug)]
912#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
913pub enum CollectionMetadataUpdate {
914    ResetMetadata,
915    UpdateMetadata(UpdateMetadata),
916}
917
918#[non_exhaustive]
919#[derive(Clone, Validate, Debug, Serialize)]
920#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
921pub struct UpdateCollectionRequest {
922    pub database_name: Option<DatabaseName>,
923    pub collection_id: CollectionUuid,
924    #[validate(custom(function = "validate_name"))]
925    pub new_name: Option<String>,
926    #[validate(custom(function = "validate_non_empty_collection_update_metadata"))]
927    pub new_metadata: Option<CollectionMetadataUpdate>,
928    pub new_configuration: Option<InternalUpdateCollectionConfiguration>,
929}
930
931impl UpdateCollectionRequest {
932    pub fn try_new(
933        database_name: Option<DatabaseName>,
934        collection_id: CollectionUuid,
935        new_name: Option<String>,
936        new_metadata: Option<CollectionMetadataUpdate>,
937        new_configuration: Option<InternalUpdateCollectionConfiguration>,
938    ) -> Result<Self, ChromaValidationError> {
939        let request = Self {
940            database_name,
941            collection_id,
942            new_name,
943            new_metadata,
944            new_configuration,
945        };
946        request.validate().map_err(ChromaValidationError::from)?;
947        Ok(request)
948    }
949}
950
951#[derive(Serialize)]
952#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
953pub struct UpdateCollectionResponse {}
954
955#[derive(Error, Debug)]
956pub enum UpdateCollectionError {
957    #[error("Collection [{0}] does not exist")]
958    NotFound(String),
959    #[error("Metadata reset unsupported")]
960    MetadataResetUnsupported,
961    #[error("Could not serialize configuration")]
962    Configuration(#[source] serde_json::Error),
963    #[error(transparent)]
964    Internal(#[from] Box<dyn ChromaError>),
965    #[error("Could not parse config: {0}")]
966    InvalidConfig(#[from] CollectionConfigurationToInternalConfigurationError),
967    #[error("SPANN is still in development. Not allowed to created spann indexes")]
968    SpannNotImplemented,
969    #[error("Could not serialize schema: {0}")]
970    Schema(#[source] serde_json::Error),
971}
972
973impl ChromaError for UpdateCollectionError {
974    fn code(&self) -> ErrorCodes {
975        match self {
976            UpdateCollectionError::NotFound(_) => ErrorCodes::NotFound,
977            UpdateCollectionError::MetadataResetUnsupported => ErrorCodes::InvalidArgument,
978            UpdateCollectionError::Configuration(_) => ErrorCodes::Internal,
979            UpdateCollectionError::Internal(err) => err.code(),
980            UpdateCollectionError::InvalidConfig(_) => ErrorCodes::InvalidArgument,
981            UpdateCollectionError::SpannNotImplemented => ErrorCodes::InvalidArgument,
982            UpdateCollectionError::Schema(_) => ErrorCodes::Internal,
983        }
984    }
985}
986
987#[non_exhaustive]
988#[derive(Clone, Validate, Serialize)]
989#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
990pub struct DeleteCollectionRequest {
991    pub tenant_id: String,
992    pub database_name: String,
993    pub collection_name: String,
994}
995
996impl DeleteCollectionRequest {
997    pub fn try_new(
998        tenant_id: String,
999        database_name: String,
1000        collection_name: String,
1001    ) -> Result<Self, ChromaValidationError> {
1002        let request = Self {
1003            tenant_id,
1004            database_name,
1005            collection_name,
1006        };
1007        request.validate().map_err(ChromaValidationError::from)?;
1008        Ok(request)
1009    }
1010}
1011
1012#[derive(Serialize)]
1013#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1014pub struct DeleteCollectionResponse {}
1015
1016#[derive(Error, Debug)]
1017pub enum DeleteCollectionError {
1018    #[error("Collection [{0}] does not exist")]
1019    NotFound(String),
1020    #[error(transparent)]
1021    Validation(#[from] ChromaValidationError),
1022    #[error(transparent)]
1023    Get(#[from] GetCollectionError),
1024    #[error(transparent)]
1025    Internal(#[from] Box<dyn ChromaError>),
1026}
1027
1028impl ChromaError for DeleteCollectionError {
1029    fn code(&self) -> ErrorCodes {
1030        match self {
1031            DeleteCollectionError::Validation(err) => err.code(),
1032            DeleteCollectionError::NotFound(_) => ErrorCodes::NotFound,
1033            DeleteCollectionError::Get(err) => err.code(),
1034            DeleteCollectionError::Internal(err) => err.code(),
1035        }
1036    }
1037}
1038
1039#[derive(Serialize, Deserialize, Debug)]
1040#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1041pub struct IndexStatusResponse {
1042    pub op_indexing_progress: f32,
1043    pub num_unindexed_ops: u64,
1044    pub num_indexed_ops: u64,
1045    pub total_ops: u64,
1046}
1047
1048#[derive(Error, Debug)]
1049pub enum IndexStatusError {
1050    #[error("Collection [{0}] does not exist")]
1051    NotFound(String),
1052    #[error(transparent)]
1053    Internal(#[from] Box<dyn ChromaError>),
1054}
1055
1056impl From<GetCollectionError> for IndexStatusError {
1057    fn from(err: GetCollectionError) -> Self {
1058        match err {
1059            GetCollectionError::NotFound(msg) => IndexStatusError::NotFound(msg),
1060            other => IndexStatusError::Internal(Box::new(other)),
1061        }
1062    }
1063}
1064
1065impl ChromaError for IndexStatusError {
1066    fn code(&self) -> ErrorCodes {
1067        match self {
1068            IndexStatusError::NotFound(_) => ErrorCodes::NotFound,
1069            IndexStatusError::Internal(err) => err.code(),
1070        }
1071    }
1072}
1073
1074#[non_exhaustive]
1075#[derive(Clone, Validate, Serialize)]
1076#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1077pub struct ForkCollectionRequest {
1078    pub tenant_id: String,
1079    pub database_name: String,
1080    pub source_collection_id: CollectionUuid,
1081    pub target_collection_name: String,
1082}
1083
1084impl ForkCollectionRequest {
1085    pub fn try_new(
1086        tenant_id: String,
1087        database_name: String,
1088        source_collection_id: CollectionUuid,
1089        target_collection_name: String,
1090    ) -> Result<Self, ChromaValidationError> {
1091        let request = Self {
1092            tenant_id,
1093            database_name,
1094            source_collection_id,
1095            target_collection_name,
1096        };
1097        request.validate().map_err(ChromaValidationError::from)?;
1098        Ok(request)
1099    }
1100}
1101
1102pub type ForkCollectionResponse = Collection;
1103
1104#[derive(Clone, Debug)]
1105pub struct ForkLogsResponse {
1106    pub compaction_offset: u64,
1107    pub enumeration_offset: u64,
1108}
1109
1110#[derive(Error, Debug)]
1111pub enum ForkCollectionError {
1112    #[error("Collection [{0}] already exists")]
1113    AlreadyExists(String),
1114    #[error("Failed to convert proto collection")]
1115    CollectionConversionError(#[from] CollectionConversionError),
1116    #[error("Duplicate segment")]
1117    DuplicateSegment,
1118    #[error("Missing field: [{0}]")]
1119    Field(String),
1120    #[error("Invalid argument: {0}")]
1121    InvalidArgument(String),
1122    #[error("Collection forking is unsupported for local chroma")]
1123    Local,
1124    #[error(transparent)]
1125    Internal(#[from] Box<dyn ChromaError>),
1126    #[error("Collection [{0}] does not exist")]
1127    NotFound(String),
1128    #[error("Failed to convert proto segment")]
1129    SegmentConversionError(#[from] SegmentConversionError),
1130    #[error("Failed to reconcile schema: {0}")]
1131    InvalidSchema(#[from] SchemaError),
1132}
1133
1134impl ChromaError for ForkCollectionError {
1135    fn code(&self) -> ErrorCodes {
1136        match self {
1137            ForkCollectionError::NotFound(_) => ErrorCodes::NotFound,
1138            ForkCollectionError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
1139            ForkCollectionError::CollectionConversionError(e) => e.code(),
1140            ForkCollectionError::DuplicateSegment => ErrorCodes::Internal,
1141            ForkCollectionError::Field(_) => ErrorCodes::FailedPrecondition,
1142            ForkCollectionError::InvalidArgument(_) => ErrorCodes::InvalidArgument,
1143            ForkCollectionError::Local => ErrorCodes::Unimplemented,
1144            ForkCollectionError::Internal(e) => e.code(),
1145            ForkCollectionError::SegmentConversionError(e) => e.code(),
1146            ForkCollectionError::InvalidSchema(e) => e.code(),
1147        }
1148    }
1149}
1150
1151#[derive(Debug, Error)]
1152pub enum CountForksError {
1153    #[error("Collection [{0}] does not exist")]
1154    NotFound(String),
1155    #[error(transparent)]
1156    Internal(#[from] Box<dyn ChromaError>),
1157    #[error("Count forks is unsupported for local chroma")]
1158    Local,
1159}
1160
1161impl ChromaError for CountForksError {
1162    fn code(&self) -> ErrorCodes {
1163        match self {
1164            CountForksError::NotFound(_) => ErrorCodes::NotFound,
1165            CountForksError::Internal(chroma_error) => chroma_error.code(),
1166            CountForksError::Local => ErrorCodes::Unimplemented,
1167        }
1168    }
1169}
1170
1171#[derive(Debug, Error)]
1172pub enum ListAttachedFunctionsError {
1173    #[error("Collection [{0}] does not exist")]
1174    NotFound(String),
1175    #[error(transparent)]
1176    Internal(#[from] Box<dyn ChromaError>),
1177    #[error("List attached functions is not implemented")]
1178    NotImplemented,
1179}
1180
1181impl ChromaError for ListAttachedFunctionsError {
1182    fn code(&self) -> ErrorCodes {
1183        match self {
1184            ListAttachedFunctionsError::NotFound(_) => ErrorCodes::NotFound,
1185            ListAttachedFunctionsError::Internal(chroma_error) => chroma_error.code(),
1186            ListAttachedFunctionsError::NotImplemented => ErrorCodes::Unimplemented,
1187        }
1188    }
1189}
1190
1191#[derive(Debug, Error)]
1192pub enum GetCollectionSizeError {
1193    #[error(transparent)]
1194    Internal(#[from] Box<dyn ChromaError>),
1195    #[error("Collection [{0}] does not exist")]
1196    NotFound(String),
1197}
1198
1199impl ChromaError for GetCollectionSizeError {
1200    fn code(&self) -> ErrorCodes {
1201        match self {
1202            GetCollectionSizeError::Internal(err) => err.code(),
1203            GetCollectionSizeError::NotFound(_) => ErrorCodes::NotFound,
1204        }
1205    }
1206}
1207
1208#[derive(Error, Debug)]
1209pub enum ListCollectionVersionsError {
1210    #[error(transparent)]
1211    Internal(#[from] Box<dyn ChromaError>),
1212    #[error("Collection [{0}] does not exist")]
1213    NotFound(String),
1214}
1215
1216impl ChromaError for ListCollectionVersionsError {
1217    fn code(&self) -> ErrorCodes {
1218        match self {
1219            ListCollectionVersionsError::Internal(err) => err.code(),
1220            ListCollectionVersionsError::NotFound(_) => ErrorCodes::NotFound,
1221        }
1222    }
1223}
1224
1225////////////////////////// Metadata Key Constants //////////////////////////
1226
1227pub const CHROMA_KEY: &str = "chroma:";
1228pub const CHROMA_DOCUMENT_KEY: &str = "chroma:document";
1229pub const CHROMA_URI_KEY: &str = "chroma:uri";
1230
1231////////////////////////// AddCollectionRecords //////////////////////////
1232
1233/// Payload for adding records to a collection.
1234///
1235/// Records are added in batches. All arrays must have the same length, with each index
1236/// representing a single record. For example, `ids[0]`, `embeddings[0]`, `documents[0]`, etc.
1237/// all belong to the same record.
1238#[derive(Serialize, Deserialize, Debug, Clone)]
1239#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1240pub struct AddCollectionRecordsPayload {
1241    /// Unique identifiers for each record.
1242    pub ids: Vec<String>,
1243    /// Embeddings for each record. Can contain the raw f32 arrays or base64 encoded strings.
1244    pub embeddings: EmbeddingsPayload,
1245    pub documents: Option<Vec<Option<String>>>,
1246    pub uris: Option<Vec<Option<String>>>,
1247    pub metadatas: Option<Vec<Option<Metadata>>>,
1248}
1249
1250impl AddCollectionRecordsPayload {
1251    pub fn new(
1252        ids: Vec<String>,
1253        embeddings: Vec<Vec<f32>>,
1254        documents: Option<Vec<Option<String>>>,
1255        uris: Option<Vec<Option<String>>>,
1256        metadatas: Option<Vec<Option<Metadata>>>,
1257    ) -> Self {
1258        Self {
1259            ids,
1260            embeddings: EmbeddingsPayload::JsonArrays(embeddings),
1261            documents,
1262            uris,
1263            metadatas,
1264        }
1265    }
1266}
1267
1268#[non_exhaustive]
1269#[derive(Debug, Clone, Validate, Serialize)]
1270#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1271pub struct AddCollectionRecordsRequest {
1272    pub tenant_id: String,
1273    pub database_name: String,
1274    pub collection_id: CollectionUuid,
1275    pub ids: Vec<String>,
1276    #[validate(custom(function = "validate_embeddings"))]
1277    pub embeddings: Vec<Vec<f32>>,
1278    pub documents: Option<Vec<Option<String>>>,
1279    pub uris: Option<Vec<Option<String>>>,
1280    #[validate(custom(function = "validate_metadata_vec"))]
1281    pub metadatas: Option<Vec<Option<Metadata>>>,
1282}
1283
1284impl AddCollectionRecordsRequest {
1285    #[allow(clippy::too_many_arguments)]
1286    pub fn try_new(
1287        tenant_id: String,
1288        database_name: String,
1289        collection_id: CollectionUuid,
1290        ids: Vec<String>,
1291        embeddings: Vec<Vec<f32>>,
1292        documents: Option<Vec<Option<String>>>,
1293        uris: Option<Vec<Option<String>>>,
1294        metadatas: Option<Vec<Option<Metadata>>>,
1295    ) -> Result<Self, ChromaValidationError> {
1296        let request = Self {
1297            tenant_id,
1298            database_name,
1299            collection_id,
1300            ids,
1301            embeddings,
1302            documents,
1303            uris,
1304            metadatas,
1305        };
1306        request.validate().map_err(ChromaValidationError::from)?;
1307        Ok(request)
1308    }
1309
1310    pub fn into_payload(self) -> AddCollectionRecordsPayload {
1311        AddCollectionRecordsPayload {
1312            ids: self.ids,
1313            embeddings: EmbeddingsPayload::JsonArrays(self.embeddings),
1314            documents: self.documents,
1315            uris: self.uris,
1316            metadatas: self.metadatas,
1317        }
1318    }
1319}
1320
1321fn validate_embeddings(embeddings: &[Vec<f32>]) -> Result<(), ValidationError> {
1322    if embeddings.iter().any(|e| e.is_empty()) {
1323        return Err(ValidationError::new("embedding_minimum_dimensions")
1324            .with_message("Each embedding must have at least 1 dimension".into()));
1325    }
1326    Ok(())
1327}
1328
1329#[derive(Serialize, Default, Deserialize)]
1330#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1331pub struct AddCollectionRecordsResponse {}
1332
1333#[derive(Error, Debug)]
1334pub enum AddCollectionRecordsError {
1335    #[error("Failed to get collection: {0}")]
1336    Collection(#[from] GetCollectionError),
1337    #[error("Backoff and retry")]
1338    Backoff,
1339    #[error("Invalid database name")]
1340    InvalidDatabaseName,
1341    #[error(transparent)]
1342    Other(#[from] Box<dyn ChromaError>),
1343}
1344
1345impl ChromaError for AddCollectionRecordsError {
1346    fn code(&self) -> ErrorCodes {
1347        match self {
1348            AddCollectionRecordsError::Collection(err) => err.code(),
1349            AddCollectionRecordsError::Backoff => ErrorCodes::ResourceExhausted,
1350            AddCollectionRecordsError::InvalidDatabaseName => ErrorCodes::InvalidArgument,
1351            AddCollectionRecordsError::Other(err) => err.code(),
1352        }
1353    }
1354}
1355
1356////////////////////////// UpdateCollectionRecords //////////////////////////
1357
1358/// Payload for updating existing records in a collection.
1359///
1360/// Records are added in batches. All arrays must have the same length, with each index
1361/// representing a single record. For example, `ids[0]`, `embeddings[0]`, `documents[0]`, etc.
1362/// all belong to the same record.
1363#[derive(Deserialize, Debug, Clone, Serialize)]
1364#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1365pub struct UpdateCollectionRecordsPayload {
1366    pub ids: Vec<String>,
1367    /// Updated embeddings for each record. Can contain the raw f32 arrays or base64 encoded strings.
1368    pub embeddings: Option<UpdateEmbeddingsPayload>,
1369    pub documents: Option<Vec<Option<String>>>,
1370    pub uris: Option<Vec<Option<String>>>,
1371    pub metadatas: Option<Vec<Option<UpdateMetadata>>>,
1372}
1373
1374#[non_exhaustive]
1375#[derive(Debug, Clone, Validate, Serialize)]
1376#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1377pub struct UpdateCollectionRecordsRequest {
1378    pub tenant_id: String,
1379    pub database_name: String,
1380    pub collection_id: CollectionUuid,
1381    pub ids: Vec<String>,
1382    pub embeddings: Option<Vec<Option<Vec<f32>>>>,
1383    pub documents: Option<Vec<Option<String>>>,
1384    pub uris: Option<Vec<Option<String>>>,
1385    #[validate(custom(function = "validate_update_metadata_vec"))]
1386    pub metadatas: Option<Vec<Option<UpdateMetadata>>>,
1387}
1388
1389impl UpdateCollectionRecordsRequest {
1390    #[allow(clippy::too_many_arguments)]
1391    pub fn try_new(
1392        tenant_id: String,
1393        database_name: String,
1394        collection_id: CollectionUuid,
1395        ids: Vec<String>,
1396        embeddings: Option<Vec<Option<Vec<f32>>>>,
1397        documents: Option<Vec<Option<String>>>,
1398        uris: Option<Vec<Option<String>>>,
1399        metadatas: Option<Vec<Option<UpdateMetadata>>>,
1400    ) -> Result<Self, ChromaValidationError> {
1401        let request = Self {
1402            tenant_id,
1403            database_name,
1404            collection_id,
1405            ids,
1406            embeddings,
1407            documents,
1408            uris,
1409            metadatas,
1410        };
1411        request.validate().map_err(ChromaValidationError::from)?;
1412        Ok(request)
1413    }
1414
1415    pub fn into_payload(self) -> UpdateCollectionRecordsPayload {
1416        UpdateCollectionRecordsPayload {
1417            ids: self.ids,
1418            embeddings: self.embeddings.map(UpdateEmbeddingsPayload::JsonArrays),
1419            documents: self.documents,
1420            uris: self.uris,
1421            metadatas: self.metadatas,
1422        }
1423    }
1424}
1425
1426#[derive(Serialize, Deserialize)]
1427#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1428pub struct UpdateCollectionRecordsResponse {}
1429
1430#[derive(Error, Debug)]
1431pub enum UpdateCollectionRecordsError {
1432    #[error("Backoff and retry")]
1433    Backoff,
1434    #[error("Invalid database name")]
1435    InvalidDatabaseName,
1436    #[error(transparent)]
1437    Other(#[from] Box<dyn ChromaError>),
1438}
1439
1440impl ChromaError for UpdateCollectionRecordsError {
1441    fn code(&self) -> ErrorCodes {
1442        match self {
1443            UpdateCollectionRecordsError::Backoff => ErrorCodes::ResourceExhausted,
1444            UpdateCollectionRecordsError::InvalidDatabaseName => ErrorCodes::InvalidArgument,
1445            UpdateCollectionRecordsError::Other(err) => err.code(),
1446        }
1447    }
1448}
1449
1450////////////////////////// UpsertCollectionRecords //////////////////////////
1451
1452/// Payload for upserting records in a collection.
1453///
1454/// Upsert creates records if they don't exist, or updates them if they do.
1455/// Records are added in batches. All arrays must have the same length, with each index
1456/// representing a single record. For example, `ids[0]`, `embeddings[0]`, `documents[0]`, etc.
1457/// all belong to the same record.
1458#[derive(Deserialize, Debug, Clone, Serialize)]
1459#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1460pub struct UpsertCollectionRecordsPayload {
1461    pub ids: Vec<String>,
1462    /// Embeddings for each record. Can contain the raw f32 arrays or base64 encoded strings.
1463    pub embeddings: EmbeddingsPayload,
1464    pub documents: Option<Vec<Option<String>>>,
1465    pub uris: Option<Vec<Option<String>>>,
1466    pub metadatas: Option<Vec<Option<UpdateMetadata>>>,
1467}
1468
1469#[non_exhaustive]
1470#[derive(Debug, Clone, Validate, Serialize)]
1471#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1472pub struct UpsertCollectionRecordsRequest {
1473    pub tenant_id: String,
1474    pub database_name: String,
1475    pub collection_id: CollectionUuid,
1476    pub ids: Vec<String>,
1477    #[validate(custom(function = "validate_embeddings"))]
1478    pub embeddings: Vec<Vec<f32>>,
1479    pub documents: Option<Vec<Option<String>>>,
1480    pub uris: Option<Vec<Option<String>>>,
1481    #[validate(custom(function = "validate_update_metadata_vec"))]
1482    pub metadatas: Option<Vec<Option<UpdateMetadata>>>,
1483}
1484
1485impl UpsertCollectionRecordsRequest {
1486    #[allow(clippy::too_many_arguments)]
1487    pub fn try_new(
1488        tenant_id: String,
1489        database_name: String,
1490        collection_id: CollectionUuid,
1491        ids: Vec<String>,
1492        embeddings: Vec<Vec<f32>>,
1493        documents: Option<Vec<Option<String>>>,
1494        uris: Option<Vec<Option<String>>>,
1495        metadatas: Option<Vec<Option<UpdateMetadata>>>,
1496    ) -> Result<Self, ChromaValidationError> {
1497        let request = Self {
1498            tenant_id,
1499            database_name,
1500            collection_id,
1501            ids,
1502            embeddings,
1503            documents,
1504            uris,
1505            metadatas,
1506        };
1507        request.validate().map_err(ChromaValidationError::from)?;
1508        Ok(request)
1509    }
1510
1511    pub fn into_payload(self) -> UpsertCollectionRecordsPayload {
1512        UpsertCollectionRecordsPayload {
1513            ids: self.ids.clone(),
1514            embeddings: EmbeddingsPayload::JsonArrays(self.embeddings.clone()),
1515            documents: self.documents.clone(),
1516            uris: self.uris.clone(),
1517            metadatas: self.metadatas.clone(),
1518        }
1519    }
1520}
1521
1522#[derive(Serialize, Deserialize)]
1523#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1524pub struct UpsertCollectionRecordsResponse {}
1525
1526#[derive(Error, Debug)]
1527pub enum UpsertCollectionRecordsError {
1528    #[error("Backoff and retry")]
1529    Backoff,
1530    #[error("Invalid database name")]
1531    InvalidDatabaseName,
1532    #[error(transparent)]
1533    Other(#[from] Box<dyn ChromaError>),
1534}
1535
1536impl ChromaError for UpsertCollectionRecordsError {
1537    fn code(&self) -> ErrorCodes {
1538        match self {
1539            UpsertCollectionRecordsError::Backoff => ErrorCodes::ResourceExhausted,
1540            UpsertCollectionRecordsError::InvalidDatabaseName => ErrorCodes::InvalidArgument,
1541            UpsertCollectionRecordsError::Other(err) => err.code(),
1542        }
1543    }
1544}
1545
1546////////////////////////// DeleteCollectionRecords //////////////////////////
1547
1548/// Payload for deleting records from a collection.
1549///
1550/// Records can be deleted by their IDs or by a metadata filter. At least one of `ids` or `where`
1551/// must be provided.
1552#[derive(Deserialize, Debug, Clone, Serialize)]
1553#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1554pub struct DeleteCollectionRecordsPayload {
1555    pub ids: Option<Vec<String>>,
1556    #[serde(flatten)]
1557    pub where_fields: RawWhereFields,
1558}
1559
1560#[non_exhaustive]
1561#[derive(Debug, Clone, Validate, Serialize)]
1562#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1563pub struct DeleteCollectionRecordsRequest {
1564    pub tenant_id: String,
1565    pub database_name: String,
1566    pub collection_id: CollectionUuid,
1567    pub ids: Option<Vec<String>>,
1568    pub r#where: Option<Where>,
1569}
1570
1571impl DeleteCollectionRecordsRequest {
1572    pub fn try_new(
1573        tenant_id: String,
1574        database_name: String,
1575        collection_id: CollectionUuid,
1576        ids: Option<Vec<String>>,
1577        r#where: Option<Where>,
1578    ) -> Result<Self, ChromaValidationError> {
1579        if ids.as_ref().map(|ids| ids.is_empty()).unwrap_or(false) && r#where.is_none() {
1580            return Err(ChromaValidationError::from((
1581                ("ids, where"),
1582                ValidationError::new("filter")
1583                    .with_message("Either ids or where must be specified".into()),
1584            )));
1585        }
1586
1587        let request = Self {
1588            tenant_id,
1589            database_name,
1590            collection_id,
1591            ids,
1592            r#where,
1593        };
1594        request.validate().map_err(ChromaValidationError::from)?;
1595        Ok(request)
1596    }
1597
1598    pub fn into_payload(self) -> Result<DeleteCollectionRecordsPayload, WhereError> {
1599        let where_fields = if let Some(r#where) = self.r#where.as_ref() {
1600            RawWhereFields::from_json_str(Some(&serde_json::to_string(r#where)?), None)?
1601        } else {
1602            RawWhereFields::default()
1603        };
1604        Ok(DeleteCollectionRecordsPayload {
1605            ids: self.ids.clone(),
1606            where_fields,
1607        })
1608    }
1609}
1610
1611#[derive(Serialize, Deserialize)]
1612#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1613pub struct DeleteCollectionRecordsResponse {}
1614
1615#[derive(Error, Debug)]
1616pub enum DeleteCollectionRecordsError {
1617    #[error("Failed to resolve records for deletion: {0}")]
1618    Get(#[from] ExecutorError),
1619    #[error("Backoff and retry")]
1620    Backoff,
1621    #[error("Invalid database name")]
1622    InvalidDatabaseName,
1623    #[error(transparent)]
1624    Internal(#[from] Box<dyn ChromaError>),
1625}
1626
1627impl ChromaError for DeleteCollectionRecordsError {
1628    fn code(&self) -> ErrorCodes {
1629        match self {
1630            DeleteCollectionRecordsError::Get(err) => err.code(),
1631            DeleteCollectionRecordsError::Backoff => ErrorCodes::ResourceExhausted,
1632            DeleteCollectionRecordsError::InvalidDatabaseName => ErrorCodes::InvalidArgument,
1633            DeleteCollectionRecordsError::Internal(err) => err.code(),
1634        }
1635    }
1636}
1637
1638////////////////////////// Include //////////////////////////
1639
1640#[derive(Error, Debug)]
1641#[error("Invalid include value: {0}")]
1642pub struct IncludeParsingError(String);
1643
1644impl ChromaError for IncludeParsingError {
1645    fn code(&self) -> ErrorCodes {
1646        ErrorCodes::InvalidArgument
1647    }
1648}
1649
1650/// Use this enum to specify which fields should be returned when retrieving records.
1651#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1652#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1653pub enum Include {
1654    #[serde(rename = "distances")]
1655    Distance,
1656    #[serde(rename = "documents")]
1657    Document,
1658    #[serde(rename = "embeddings")]
1659    Embedding,
1660    #[serde(rename = "metadatas")]
1661    Metadata,
1662    #[serde(rename = "uris")]
1663    Uri,
1664}
1665
1666impl TryFrom<&str> for Include {
1667    type Error = IncludeParsingError;
1668
1669    fn try_from(value: &str) -> Result<Self, Self::Error> {
1670        match value {
1671            "distances" => Ok(Include::Distance),
1672            "documents" => Ok(Include::Document),
1673            "embeddings" => Ok(Include::Embedding),
1674            "metadatas" => Ok(Include::Metadata),
1675            "uris" => Ok(Include::Uri),
1676            _ => Err(IncludeParsingError(value.to_string())),
1677        }
1678    }
1679}
1680
1681#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
1682#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1683#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
1684pub struct IncludeList(pub Vec<Include>);
1685
1686impl IncludeList {
1687    pub fn empty() -> Self {
1688        Self(Vec::new())
1689    }
1690
1691    pub fn default_query() -> Self {
1692        Self(vec![
1693            Include::Document,
1694            Include::Metadata,
1695            Include::Distance,
1696        ])
1697    }
1698    pub fn default_get() -> Self {
1699        Self(vec![Include::Document, Include::Metadata])
1700    }
1701    pub fn all() -> Self {
1702        Self(vec![
1703            Include::Document,
1704            Include::Metadata,
1705            Include::Distance,
1706            Include::Embedding,
1707            Include::Uri,
1708        ])
1709    }
1710}
1711
1712impl TryFrom<Vec<String>> for IncludeList {
1713    type Error = IncludeParsingError;
1714
1715    fn try_from(value: Vec<String>) -> Result<Self, Self::Error> {
1716        let mut includes = Vec::new();
1717        for v in value {
1718            // "data" is only used by single node Chroma
1719            if v == "data" {
1720                includes.push(Include::Metadata);
1721                continue;
1722            }
1723
1724            includes.push(Include::try_from(v.as_str())?);
1725        }
1726        Ok(IncludeList(includes))
1727    }
1728}
1729
1730////////////////////////// Count //////////////////////////
1731
1732#[non_exhaustive]
1733#[derive(Clone, Deserialize, Serialize, Validate)]
1734pub struct CountRequest {
1735    pub tenant_id: String,
1736    pub database_name: String,
1737    pub collection_id: CollectionUuid,
1738}
1739
1740impl CountRequest {
1741    pub fn try_new(
1742        tenant_id: String,
1743        database_name: String,
1744        collection_id: CollectionUuid,
1745    ) -> Result<Self, ChromaValidationError> {
1746        let request = Self {
1747            tenant_id,
1748            database_name,
1749            collection_id,
1750        };
1751        request.validate().map_err(ChromaValidationError::from)?;
1752        Ok(request)
1753    }
1754}
1755
1756pub type CountResponse = u32;
1757
1758//////////////////////// Payload Err ////////////////////
1759
1760#[derive(Debug, thiserror::Error)]
1761pub enum WhereError {
1762    #[error("serialization: {0}")]
1763    Serialization(#[from] serde_json::Error),
1764    #[error("validation: {0}")]
1765    Validation(#[from] WhereValidationError),
1766}
1767
1768////////////////////////// Get //////////////////////////
1769
1770/// Records can be retrieved by their IDs or by a metadata filter. At least one of `ids` or `where`
1771/// must be provided. Use `include` to specify which fields to return in the response.
1772#[derive(Debug, Clone, Deserialize, Serialize)]
1773#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1774pub struct GetRequestPayload {
1775    pub ids: Option<Vec<String>>,
1776    #[serde(flatten)]
1777    pub where_fields: RawWhereFields,
1778    pub limit: Option<u32>,
1779    pub offset: Option<u32>,
1780    #[serde(default = "IncludeList::default_get")]
1781    pub include: IncludeList,
1782}
1783
1784#[non_exhaustive]
1785#[derive(Debug, Clone, Validate, Serialize)]
1786#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1787pub struct GetRequest {
1788    pub tenant_id: String,
1789    pub database_name: String,
1790    pub collection_id: CollectionUuid,
1791    pub ids: Option<Vec<String>>,
1792    pub r#where: Option<Where>,
1793    pub limit: Option<u32>,
1794    pub offset: u32,
1795    pub include: IncludeList,
1796}
1797
1798impl GetRequest {
1799    #[allow(clippy::too_many_arguments)]
1800    pub fn try_new(
1801        tenant_id: String,
1802        database_name: String,
1803        collection_id: CollectionUuid,
1804        ids: Option<Vec<String>>,
1805        r#where: Option<Where>,
1806        limit: Option<u32>,
1807        offset: u32,
1808        include: IncludeList,
1809    ) -> Result<Self, ChromaValidationError> {
1810        let request = Self {
1811            tenant_id,
1812            database_name,
1813            collection_id,
1814            ids,
1815            r#where,
1816            limit,
1817            offset,
1818            include,
1819        };
1820        request.validate().map_err(ChromaValidationError::from)?;
1821        Ok(request)
1822    }
1823
1824    pub fn into_payload(self) -> Result<GetRequestPayload, WhereError> {
1825        let where_fields = if let Some(r#where) = self.r#where.as_ref() {
1826            RawWhereFields::from_json_str(Some(&serde_json::to_string(r#where)?), None)?
1827        } else {
1828            RawWhereFields::default()
1829        };
1830        Ok(GetRequestPayload {
1831            ids: self.ids,
1832            where_fields,
1833            limit: self.limit,
1834            offset: Some(self.offset),
1835            include: self.include,
1836        })
1837    }
1838}
1839
1840/// All arrays have the same length, with each index representing a single record.
1841/// Only fields specified in the request's `include` parameter are populated.
1842#[derive(Clone, Deserialize, Serialize, Debug, Default)]
1843#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1844#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
1845pub struct GetResponse {
1846    pub ids: Vec<String>,
1847    pub embeddings: Option<Vec<Vec<f32>>>,
1848    pub documents: Option<Vec<Option<String>>>,
1849    pub uris: Option<Vec<Option<String>>>,
1850    pub metadatas: Option<Vec<Option<Metadata>>>,
1851    /// List of fields that were included in this response.
1852    pub include: Vec<Include>,
1853}
1854
1855impl GetResponse {
1856    pub fn sort_by_ids(&mut self) {
1857        let mut indices: Vec<usize> = (0..self.ids.len()).collect();
1858        indices.sort_by(|&a, &b| self.ids[a].cmp(&self.ids[b]));
1859
1860        let sorted_ids = indices.iter().map(|&i| self.ids[i].clone()).collect();
1861        self.ids = sorted_ids;
1862
1863        if let Some(ref mut embeddings) = self.embeddings {
1864            let sorted_embeddings = indices.iter().map(|&i| embeddings[i].clone()).collect();
1865            *embeddings = sorted_embeddings;
1866        }
1867
1868        if let Some(ref mut documents) = self.documents {
1869            let sorted_docs = indices.iter().map(|&i| documents[i].clone()).collect();
1870            *documents = sorted_docs;
1871        }
1872
1873        if let Some(ref mut uris) = self.uris {
1874            let sorted_uris = indices.iter().map(|&i| uris[i].clone()).collect();
1875            *uris = sorted_uris;
1876        }
1877
1878        if let Some(ref mut metadatas) = self.metadatas {
1879            let sorted_metas = indices.iter().map(|&i| metadatas[i].clone()).collect();
1880            *metadatas = sorted_metas;
1881        }
1882    }
1883}
1884
1885#[cfg(feature = "pyo3")]
1886#[pyo3::pymethods]
1887impl GetResponse {
1888    #[getter]
1889    pub fn ids(&self) -> &Vec<String> {
1890        &self.ids
1891    }
1892
1893    #[getter]
1894    pub fn embeddings(&self) -> Option<Vec<Vec<f32>>> {
1895        self.embeddings.clone()
1896    }
1897
1898    #[getter]
1899    pub fn documents(&self) -> Option<Vec<Option<String>>> {
1900        self.documents.clone()
1901    }
1902
1903    #[getter]
1904    pub fn uris(&self) -> Option<Vec<Option<String>>> {
1905        self.uris.clone()
1906    }
1907
1908    #[getter]
1909    pub fn metadatas(&self) -> Option<Vec<Option<Metadata>>> {
1910        self.metadatas.clone()
1911    }
1912}
1913
1914impl From<(GetResult, IncludeList)> for GetResponse {
1915    fn from((result, IncludeList(include_vec)): (GetResult, IncludeList)) -> Self {
1916        let mut res = Self {
1917            ids: Vec::new(),
1918            embeddings: include_vec
1919                .contains(&Include::Embedding)
1920                .then_some(Vec::new()),
1921            documents: include_vec
1922                .contains(&Include::Document)
1923                .then_some(Vec::new()),
1924            uris: include_vec.contains(&Include::Uri).then_some(Vec::new()),
1925            metadatas: include_vec
1926                .contains(&Include::Metadata)
1927                .then_some(Vec::new()),
1928            include: include_vec,
1929        };
1930        for ProjectionRecord {
1931            id,
1932            document,
1933            embedding,
1934            mut metadata,
1935        } in result.result.records
1936        {
1937            res.ids.push(id);
1938            if let (Some(emb), Some(embeddings)) = (embedding, res.embeddings.as_mut()) {
1939                embeddings.push(emb);
1940            }
1941            if let Some(documents) = res.documents.as_mut() {
1942                documents.push(document);
1943            }
1944            let uri = metadata.as_mut().and_then(|meta| {
1945                meta.remove(CHROMA_URI_KEY).and_then(|v| {
1946                    if let crate::MetadataValue::Str(uri) = v {
1947                        Some(uri)
1948                    } else {
1949                        None
1950                    }
1951                })
1952            });
1953            if let Some(uris) = res.uris.as_mut() {
1954                uris.push(uri);
1955            }
1956
1957            let metadata = metadata.map(|m| {
1958                m.into_iter()
1959                    .filter(|(k, _)| !k.starts_with(CHROMA_KEY))
1960                    .collect()
1961            });
1962            if let Some(metadatas) = res.metadatas.as_mut() {
1963                metadatas.push(metadata);
1964            }
1965        }
1966        res
1967    }
1968}
1969
1970////////////////////////// Query //////////////////////////
1971
1972#[derive(Deserialize, Debug, Clone, Serialize)]
1973#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1974pub struct QueryRequestPayload {
1975    pub ids: Option<Vec<String>>,
1976    #[serde(flatten)]
1977    pub where_fields: RawWhereFields,
1978    pub query_embeddings: Vec<Vec<f32>>,
1979    pub n_results: Option<u32>,
1980    #[serde(default = "IncludeList::default_query")]
1981    pub include: IncludeList,
1982}
1983
1984#[non_exhaustive]
1985#[derive(Debug, Clone, Validate, Serialize)]
1986#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1987pub struct QueryRequest {
1988    pub tenant_id: String,
1989    pub database_name: String,
1990    pub collection_id: CollectionUuid,
1991    pub ids: Option<Vec<String>>,
1992    pub r#where: Option<Where>,
1993    pub embeddings: Vec<Vec<f32>>,
1994    pub n_results: u32,
1995    pub include: IncludeList,
1996}
1997
1998impl QueryRequest {
1999    #[allow(clippy::too_many_arguments)]
2000    pub fn try_new(
2001        tenant_id: String,
2002        database_name: String,
2003        collection_id: CollectionUuid,
2004        ids: Option<Vec<String>>,
2005        r#where: Option<Where>,
2006        embeddings: Vec<Vec<f32>>,
2007        n_results: u32,
2008        include: IncludeList,
2009    ) -> Result<Self, ChromaValidationError> {
2010        let request = Self {
2011            tenant_id,
2012            database_name,
2013            collection_id,
2014            ids,
2015            r#where,
2016            embeddings,
2017            n_results,
2018            include,
2019        };
2020        request.validate().map_err(ChromaValidationError::from)?;
2021        Ok(request)
2022    }
2023
2024    pub fn into_payload(self) -> Result<QueryRequestPayload, WhereError> {
2025        let where_fields = if let Some(r#where) = self.r#where.as_ref() {
2026            RawWhereFields::from_json_str(Some(&serde_json::to_string(r#where)?), None)?
2027        } else {
2028            RawWhereFields::default()
2029        };
2030        Ok(QueryRequestPayload {
2031            ids: self.ids,
2032            where_fields,
2033            query_embeddings: self.embeddings,
2034            n_results: Some(self.n_results),
2035            include: self.include,
2036        })
2037    }
2038}
2039
2040#[derive(Clone, Deserialize, Serialize, Debug)]
2041#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2042#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
2043pub struct QueryResponse {
2044    pub ids: Vec<Vec<String>>,
2045    pub embeddings: Option<Vec<Vec<Option<Vec<f32>>>>>,
2046    pub documents: Option<Vec<Vec<Option<String>>>>,
2047    pub uris: Option<Vec<Vec<Option<String>>>>,
2048    pub metadatas: Option<Vec<Vec<Option<Metadata>>>>,
2049    pub distances: Option<Vec<Vec<Option<f32>>>>,
2050    pub include: Vec<Include>,
2051}
2052
2053impl QueryResponse {
2054    pub fn sort_by_ids(&mut self) {
2055        fn reorder<T: Clone>(v: &mut [T], indices: &[usize]) {
2056            let old = v.to_owned();
2057            for (new_pos, &i) in indices.iter().enumerate() {
2058                v[new_pos] = old[i].clone();
2059            }
2060        }
2061
2062        for i in 0..self.ids.len() {
2063            let mut indices: Vec<usize> = (0..self.ids[i].len()).collect();
2064
2065            indices.sort_unstable_by(|&a, &b| self.ids[i][a].cmp(&self.ids[i][b]));
2066
2067            reorder(&mut self.ids[i], &indices);
2068
2069            if let Some(embeddings) = &mut self.embeddings {
2070                reorder(&mut embeddings[i], &indices);
2071            }
2072
2073            if let Some(documents) = &mut self.documents {
2074                reorder(&mut documents[i], &indices);
2075            }
2076
2077            if let Some(uris) = &mut self.uris {
2078                reorder(&mut uris[i], &indices);
2079            }
2080
2081            if let Some(metadatas) = &mut self.metadatas {
2082                reorder(&mut metadatas[i], &indices);
2083            }
2084
2085            if let Some(distances) = &mut self.distances {
2086                reorder(&mut distances[i], &indices);
2087            }
2088        }
2089    }
2090}
2091
2092#[cfg(feature = "pyo3")]
2093#[pyo3::pymethods]
2094impl QueryResponse {
2095    #[getter]
2096    pub fn ids(&self) -> &Vec<Vec<String>> {
2097        &self.ids
2098    }
2099
2100    #[getter]
2101    pub fn embeddings(&self) -> Option<Vec<Vec<Option<Vec<f32>>>>> {
2102        self.embeddings.clone()
2103    }
2104
2105    #[getter]
2106    pub fn documents(&self) -> Option<Vec<Vec<Option<String>>>> {
2107        self.documents.clone()
2108    }
2109
2110    #[getter]
2111    pub fn uris(&self) -> Option<Vec<Vec<Option<String>>>> {
2112        self.uris.clone()
2113    }
2114
2115    #[getter]
2116    pub fn metadatas(&self) -> Option<Vec<Vec<Option<Metadata>>>> {
2117        self.metadatas.clone()
2118    }
2119
2120    #[getter]
2121    pub fn distances(&self) -> Option<Vec<Vec<Option<f32>>>> {
2122        self.distances.clone()
2123    }
2124}
2125
2126impl From<(KnnBatchResult, IncludeList)> for QueryResponse {
2127    fn from((result, IncludeList(include_vec)): (KnnBatchResult, IncludeList)) -> Self {
2128        let mut res = Self {
2129            ids: Vec::new(),
2130            embeddings: include_vec
2131                .contains(&Include::Embedding)
2132                .then_some(Vec::new()),
2133            documents: include_vec
2134                .contains(&Include::Document)
2135                .then_some(Vec::new()),
2136            uris: include_vec.contains(&Include::Uri).then_some(Vec::new()),
2137            metadatas: include_vec
2138                .contains(&Include::Metadata)
2139                .then_some(Vec::new()),
2140            distances: include_vec
2141                .contains(&Include::Distance)
2142                .then_some(Vec::new()),
2143            include: include_vec,
2144        };
2145        for query_result in result.results {
2146            let mut ids = Vec::new();
2147            let mut embeddings = Vec::new();
2148            let mut documents = Vec::new();
2149            let mut uris = Vec::new();
2150            let mut metadatas = Vec::new();
2151            let mut distances = Vec::new();
2152            for KnnProjectionRecord {
2153                record:
2154                    ProjectionRecord {
2155                        id,
2156                        document,
2157                        embedding,
2158                        mut metadata,
2159                    },
2160                distance,
2161            } in query_result.records
2162            {
2163                ids.push(id);
2164                embeddings.push(embedding);
2165                documents.push(document);
2166
2167                let uri = metadata.as_mut().and_then(|meta| {
2168                    meta.remove(CHROMA_URI_KEY).and_then(|v| {
2169                        if let crate::MetadataValue::Str(uri) = v {
2170                            Some(uri)
2171                        } else {
2172                            None
2173                        }
2174                    })
2175                });
2176                uris.push(uri);
2177
2178                let metadata = metadata.map(|m| {
2179                    m.into_iter()
2180                        .filter(|(k, _)| !k.starts_with(CHROMA_KEY))
2181                        .collect()
2182                });
2183                metadatas.push(metadata);
2184
2185                distances.push(distance);
2186            }
2187            res.ids.push(ids);
2188
2189            if let Some(res_embs) = res.embeddings.as_mut() {
2190                res_embs.push(embeddings);
2191            }
2192            if let Some(res_docs) = res.documents.as_mut() {
2193                res_docs.push(documents);
2194            }
2195            if let Some(res_uri) = res.uris.as_mut() {
2196                res_uri.push(uris);
2197            }
2198            if let Some(res_metas) = res.metadatas.as_mut() {
2199                res_metas.push(metadatas);
2200            }
2201            if let Some(res_dists) = res.distances.as_mut() {
2202                res_dists.push(distances);
2203            }
2204        }
2205        res
2206    }
2207}
2208
2209#[derive(Debug, Clone, Deserialize, Serialize)]
2210#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2211pub struct SearchRequestPayload {
2212    pub searches: Vec<SearchPayload>,
2213    /// Specifies whether to include unindexed data in the search results.
2214    #[serde(default)]
2215    pub read_level: ReadLevel,
2216}
2217
2218#[non_exhaustive]
2219#[derive(Clone, Debug, Serialize, Validate)]
2220#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2221pub struct SearchRequest {
2222    pub tenant_id: String,
2223    pub database_name: String,
2224    pub collection_id: CollectionUuid,
2225    #[validate(nested)]
2226    pub searches: Vec<SearchPayload>,
2227    /// Specifies the read level for consistency vs performance tradeoffs.
2228    pub read_level: ReadLevel,
2229}
2230
2231impl SearchRequest {
2232    pub fn try_new(
2233        tenant_id: String,
2234        database_name: String,
2235        collection_id: CollectionUuid,
2236        searches: Vec<SearchPayload>,
2237        read_level: ReadLevel,
2238    ) -> Result<Self, ChromaValidationError> {
2239        let request = Self {
2240            tenant_id,
2241            database_name,
2242            collection_id,
2243            searches,
2244            read_level,
2245        };
2246        request.validate().map_err(ChromaValidationError::from)?;
2247        Ok(request)
2248    }
2249
2250    pub fn into_payload(self) -> SearchRequestPayload {
2251        SearchRequestPayload {
2252            searches: self.searches,
2253            read_level: self.read_level,
2254        }
2255    }
2256}
2257
2258#[derive(Clone, Deserialize, Serialize, Debug)]
2259#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2260pub struct SearchResponse {
2261    pub ids: Vec<Vec<String>>,
2262    pub documents: Vec<Option<Vec<Option<String>>>>,
2263    pub embeddings: Vec<Option<Vec<Option<Vec<f32>>>>>,
2264    pub metadatas: Vec<Option<Vec<Option<Metadata>>>>,
2265    pub scores: Vec<Option<Vec<Option<f32>>>>,
2266    pub select: Vec<Vec<Key>>,
2267}
2268
2269impl From<(SearchResult, Vec<SearchPayload>)> for SearchResponse {
2270    fn from((result, payloads): (SearchResult, Vec<SearchPayload>)) -> Self {
2271        let num_payloads = payloads.len();
2272        let mut res = Self {
2273            ids: Vec::with_capacity(num_payloads),
2274            documents: Vec::with_capacity(num_payloads),
2275            embeddings: Vec::with_capacity(num_payloads),
2276            metadatas: Vec::with_capacity(num_payloads),
2277            scores: Vec::with_capacity(num_payloads),
2278            select: Vec::with_capacity(num_payloads),
2279        };
2280
2281        for (payload_result, payload) in result.results.into_iter().zip(payloads) {
2282            // Get the sorted keys for this payload
2283            let mut payload_select = Vec::from_iter(payload.select.keys.iter().cloned());
2284            payload_select.sort();
2285
2286            let num_records = payload_result.records.len();
2287            let mut ids = Vec::with_capacity(num_records);
2288            let mut documents = Vec::with_capacity(num_records);
2289            let mut embeddings = Vec::with_capacity(num_records);
2290            let mut metadatas = Vec::with_capacity(num_records);
2291            let mut scores = Vec::with_capacity(num_records);
2292
2293            for record in payload_result.records {
2294                ids.push(record.id);
2295                documents.push(record.document);
2296                embeddings.push(record.embedding);
2297                metadatas.push(record.metadata);
2298                scores.push(record.score);
2299            }
2300
2301            res.ids.push(ids);
2302            res.select.push(payload_select.clone());
2303
2304            // Push documents if requested by this payload, otherwise None
2305            res.documents.push(
2306                payload_select
2307                    .binary_search(&Key::Document)
2308                    .is_ok()
2309                    .then_some(documents),
2310            );
2311
2312            // Push embeddings if requested by this payload, otherwise None
2313            res.embeddings.push(
2314                payload_select
2315                    .binary_search(&Key::Embedding)
2316                    .is_ok()
2317                    .then_some(embeddings),
2318            );
2319
2320            // Push metadatas if requested by this payload, otherwise None
2321            // Include if either Key::Metadata is present or any Key::MetadataField(_)
2322            let has_metadata = payload_select.binary_search(&Key::Metadata).is_ok()
2323                || payload_select
2324                    .last()
2325                    .is_some_and(|field| matches!(field, Key::MetadataField(_)));
2326            res.metadatas.push(has_metadata.then_some(metadatas));
2327
2328            // Push scores if requested by this payload, otherwise None
2329            res.scores.push(
2330                payload_select
2331                    .binary_search(&Key::Score)
2332                    .is_ok()
2333                    .then_some(scores),
2334            );
2335        }
2336
2337        res
2338    }
2339}
2340
2341#[derive(Error, Debug)]
2342pub enum QueryError {
2343    #[error("Error executing plan: {0}")]
2344    Executor(#[from] ExecutorError),
2345    #[error(transparent)]
2346    Other(#[from] Box<dyn ChromaError>),
2347}
2348
2349impl ChromaError for QueryError {
2350    fn code(&self) -> ErrorCodes {
2351        match self {
2352            QueryError::Executor(e) => e.code(),
2353            QueryError::Other(err) => err.code(),
2354        }
2355    }
2356}
2357
2358#[derive(Serialize)]
2359#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2360pub struct HealthCheckResponse {
2361    pub is_executor_ready: bool,
2362    pub is_log_client_ready: bool,
2363}
2364
2365impl HealthCheckResponse {
2366    pub fn get_status_code(&self) -> tonic::Code {
2367        if self.is_executor_ready && self.is_log_client_ready {
2368            tonic::Code::Ok
2369        } else {
2370            tonic::Code::Unavailable
2371        }
2372    }
2373}
2374
2375#[derive(Debug, Error)]
2376pub enum ExecutorError {
2377    #[error("Error converting: {0}")]
2378    Conversion(#[from] QueryConversionError),
2379    #[error("Error converting plan to proto: {0}")]
2380    PlanToProto(#[from] PlanToProtoError),
2381    #[error(transparent)]
2382    Grpc(#[from] Status),
2383    #[error("Inconsistent data")]
2384    InconsistentData,
2385    #[error("Collection is missing HNSW configuration")]
2386    CollectionMissingHnswConfiguration,
2387    #[error("Internal error: {0}")]
2388    Internal(Box<dyn ChromaError>),
2389    #[error("Error sending backfill request to compactor: {0}")]
2390    BackfillError(Box<dyn ChromaError>),
2391    #[error("Not implemented: {0}")]
2392    NotImplemented(String),
2393}
2394
2395impl ChromaError for ExecutorError {
2396    fn code(&self) -> ErrorCodes {
2397        match self {
2398            ExecutorError::Conversion(_) => ErrorCodes::InvalidArgument,
2399            ExecutorError::PlanToProto(_) => ErrorCodes::Internal,
2400            ExecutorError::Grpc(e) => e.code().into(),
2401            ExecutorError::InconsistentData => ErrorCodes::Internal,
2402            ExecutorError::CollectionMissingHnswConfiguration => ErrorCodes::Internal,
2403            ExecutorError::Internal(e) => e.code(),
2404            ExecutorError::BackfillError(e) => e.code(),
2405            ExecutorError::NotImplemented(_) => ErrorCodes::Unimplemented,
2406        }
2407    }
2408}
2409
2410//////////////////////////  Attached Function Operations //////////////////////////
2411
2412#[non_exhaustive]
2413#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
2414#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2415pub struct AttachFunctionRequest {
2416    #[validate(length(min = 1))]
2417    pub name: String,
2418    pub function_id: String,
2419    pub output_collection: String,
2420    #[serde(default = "default_empty_json_object")]
2421    pub params: serde_json::Value,
2422}
2423
2424fn default_empty_json_object() -> serde_json::Value {
2425    serde_json::json!({})
2426}
2427
2428impl AttachFunctionRequest {
2429    pub fn try_new(
2430        name: String,
2431        function_id: String,
2432        output_collection: String,
2433        params: serde_json::Value,
2434    ) -> Result<Self, ChromaValidationError> {
2435        let request = Self {
2436            name,
2437            function_id,
2438            output_collection,
2439            params,
2440        };
2441        request.validate().map_err(ChromaValidationError::from)?;
2442        Ok(request)
2443    }
2444}
2445
2446#[derive(Clone, Debug, Serialize)]
2447#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2448pub struct AttachedFunctionInfo {
2449    /// Unique identifier for the attached function.
2450    pub id: String,
2451    /// Human-readable name for the attached function instance.
2452    pub name: String,
2453    /// Name of the function (e.g., "record_counter", "statistics").
2454    pub function_name: String,
2455}
2456
2457#[derive(Clone, Debug, Serialize)]
2458#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2459pub struct AttachFunctionResponse {
2460    pub attached_function: AttachedFunctionInfo,
2461    /// True if newly created, false if already existed (idempotent request).
2462    pub created: bool,
2463}
2464
2465/// API response struct for attached function with function_name instead of function_id
2466#[derive(Clone, Debug, Serialize)]
2467#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2468pub struct AttachedFunctionApiResponse {
2469    /// Unique identifier for the attached function
2470    pub id: AttachedFunctionUuid,
2471    /// Human-readable name for the attached function instance
2472    pub name: String,
2473    /// Name of the function (e.g., "record_counter", "statistics")
2474    pub function_name: String,
2475    /// Source collection that triggers the attached function
2476    pub input_collection_id: CollectionUuid,
2477    /// Name of target collection where attached function output is stored
2478    #[serde(rename = "output_collection")]
2479    pub output_collection_name: String,
2480    /// ID of the output collection (lazily filled in after creation)
2481    pub output_collection_id: Option<CollectionUuid>,
2482    /// Optional JSON parameters for the function
2483    pub params: Option<String>,
2484    /// Tenant name this attached function belongs to
2485    pub tenant_id: String,
2486    /// Database name this attached function belongs to
2487    pub database_id: String,
2488    /// Completion offset: the WAL position up to which the attached function has processed records
2489    pub completion_offset: u64,
2490    /// Minimum number of new records required before the attached function runs again
2491    pub min_records_for_invocation: u64,
2492}
2493
2494impl AttachedFunctionApiResponse {
2495    /// Convert an AttachedFunction to the API response format, mapping function_id UUID to function_name
2496    pub fn from_attached_function(af: AttachedFunction) -> Result<Self, GetAttachedFunctionError> {
2497        let function_name = match af.function_id {
2498            id if id == FUNCTION_RECORD_COUNTER_ID => FUNCTION_RECORD_COUNTER_NAME.to_string(),
2499            id if id == FUNCTION_STATISTICS_ID => FUNCTION_STATISTICS_NAME.to_string(),
2500            _ => {
2501                return Err(GetAttachedFunctionError::UnknownFunctionId(af.function_id));
2502            }
2503        };
2504
2505        Ok(Self {
2506            id: af.id,
2507            name: af.name,
2508            function_name,
2509            input_collection_id: af.input_collection_id,
2510            output_collection_name: af.output_collection_name,
2511            output_collection_id: af.output_collection_id,
2512            params: af.params,
2513            tenant_id: af.tenant_id,
2514            database_id: af.database_id,
2515            completion_offset: af.completion_offset,
2516            min_records_for_invocation: af.min_records_for_invocation,
2517        })
2518    }
2519}
2520
2521#[derive(Clone, Debug, Serialize)]
2522#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2523pub struct GetAttachedFunctionResponse {
2524    pub attached_function: AttachedFunctionApiResponse,
2525}
2526
2527#[derive(Error, Debug)]
2528pub enum AttachFunctionError {
2529    #[error("{0}")]
2530    AlreadyExists(String),
2531    #[error("{0}")]
2532    CollectionAlreadyHasFunction(String),
2533    #[error("Failed to get collection and segments")]
2534    GetCollectionError(#[from] GetCollectionError),
2535    #[error("Input collection [{0}] does not exist")]
2536    InputCollectionNotFound(String),
2537    #[error("Output collection [{0}] already exists")]
2538    OutputCollectionExists(String),
2539    #[error("{0}")]
2540    InvalidArgument(String),
2541    #[error("{0}")]
2542    FunctionNotFound(String),
2543    #[error(transparent)]
2544    Validation(#[from] ChromaValidationError),
2545    #[error(transparent)]
2546    FinishCreate(#[from] crate::FinishCreateAttachedFunctionError),
2547    #[error(transparent)]
2548    Internal(#[from] Box<dyn ChromaError>),
2549}
2550
2551impl ChromaError for AttachFunctionError {
2552    fn code(&self) -> ErrorCodes {
2553        match self {
2554            AttachFunctionError::AlreadyExists(_) => ErrorCodes::AlreadyExists,
2555            AttachFunctionError::CollectionAlreadyHasFunction(_) => ErrorCodes::FailedPrecondition,
2556            AttachFunctionError::GetCollectionError(err) => err.code(),
2557            AttachFunctionError::InputCollectionNotFound(_) => ErrorCodes::NotFound,
2558            AttachFunctionError::OutputCollectionExists(_) => ErrorCodes::AlreadyExists,
2559            AttachFunctionError::InvalidArgument(_) => ErrorCodes::InvalidArgument,
2560            AttachFunctionError::FunctionNotFound(_) => ErrorCodes::NotFound,
2561            AttachFunctionError::Validation(err) => err.code(),
2562            AttachFunctionError::FinishCreate(err) => err.code(),
2563            AttachFunctionError::Internal(err) => err.code(),
2564        }
2565    }
2566}
2567
2568#[derive(Error, Debug)]
2569pub enum GetAttachedFunctionError {
2570    #[error("Attached Function not found")]
2571    NotFound(String),
2572    #[error("Unknown function ID [{0}]. Function may not be registered in the system.")]
2573    UnknownFunctionId(Uuid),
2574    #[error(transparent)]
2575    Internal(#[from] Box<dyn ChromaError>),
2576}
2577
2578impl ChromaError for GetAttachedFunctionError {
2579    fn code(&self) -> ErrorCodes {
2580        match self {
2581            GetAttachedFunctionError::NotFound(_) => ErrorCodes::NotFound,
2582            GetAttachedFunctionError::UnknownFunctionId(_) => ErrorCodes::Internal,
2583            GetAttachedFunctionError::Internal(err) => err.code(),
2584        }
2585    }
2586}
2587
2588#[non_exhaustive]
2589#[derive(Clone, Debug, Deserialize, Validate, Serialize)]
2590#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2591pub struct DetachFunctionRequest {
2592    /// Whether to delete the output collection as well when detaching the function.
2593    #[serde(default)]
2594    pub delete_output: bool,
2595}
2596
2597impl DetachFunctionRequest {
2598    pub fn try_new(delete_output: bool) -> Result<Self, ChromaValidationError> {
2599        let request = Self { delete_output };
2600        request.validate().map_err(ChromaValidationError::from)?;
2601        Ok(request)
2602    }
2603}
2604
2605#[derive(Clone, Debug, Serialize)]
2606#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2607pub struct DetachFunctionResponse {
2608    pub success: bool,
2609}
2610
2611#[derive(Error, Debug)]
2612pub enum DetachFunctionError {
2613    #[error(" Attached Function with ID [{0}] does not exist")]
2614    NotFound(String),
2615    #[error(transparent)]
2616    Validation(#[from] ChromaValidationError),
2617    #[error(transparent)]
2618    Internal(#[from] Box<dyn ChromaError>),
2619}
2620
2621impl ChromaError for DetachFunctionError {
2622    fn code(&self) -> ErrorCodes {
2623        match self {
2624            DetachFunctionError::NotFound(_) => ErrorCodes::NotFound,
2625            DetachFunctionError::Validation(err) => err.code(),
2626            DetachFunctionError::Internal(err) => err.code(),
2627        }
2628    }
2629}
2630
2631#[cfg(test)]
2632mod test {
2633    use super::*;
2634    use crate::{MetadataValue, SparseVector, UpdateMetadataValue};
2635    use std::collections::HashMap;
2636
2637    #[test]
2638    fn test_create_database_min_length() {
2639        // DatabaseName requires at least 3 characters
2640        assert!(DatabaseName::new("a").is_none());
2641        assert!(DatabaseName::new("ab").is_none());
2642        assert!(DatabaseName::new("abc").is_some());
2643    }
2644
2645    #[test]
2646    fn test_create_tenant_min_length() {
2647        let request = CreateTenantRequest::try_new("a".to_string());
2648        assert!(request.is_err());
2649    }
2650
2651    #[test]
2652    fn test_add_request_validates_sparse_vectors() {
2653        let mut metadata = HashMap::new();
2654        // Add unsorted sparse vector - should fail validation
2655        metadata.insert(
2656            "sparse".to_string(),
2657            MetadataValue::SparseVector(
2658                SparseVector::new(vec![3, 1, 2], vec![0.3, 0.1, 0.2]).unwrap(),
2659            ),
2660        );
2661
2662        let result = AddCollectionRecordsRequest::try_new(
2663            "tenant".to_string(),
2664            "database".to_string(),
2665            CollectionUuid(uuid::Uuid::new_v4()),
2666            vec!["id1".to_string()],
2667            vec![vec![0.1, 0.2]],
2668            None,
2669            None,
2670            Some(vec![Some(metadata)]),
2671        );
2672
2673        // Should fail because sparse vector is not sorted
2674        assert!(result.is_err());
2675    }
2676
2677    #[test]
2678    fn test_update_request_validates_sparse_vectors() {
2679        let mut metadata = HashMap::new();
2680        // Add unsorted sparse vector - should fail validation
2681        metadata.insert(
2682            "sparse".to_string(),
2683            UpdateMetadataValue::SparseVector(
2684                SparseVector::new(vec![3, 1, 2], vec![0.3, 0.1, 0.2]).unwrap(),
2685            ),
2686        );
2687
2688        let result = UpdateCollectionRecordsRequest::try_new(
2689            "tenant".to_string(),
2690            "database".to_string(),
2691            CollectionUuid(uuid::Uuid::new_v4()),
2692            vec!["id1".to_string()],
2693            None,
2694            None,
2695            None,
2696            Some(vec![Some(metadata)]),
2697        );
2698
2699        // Should fail because sparse vector is not sorted
2700        assert!(result.is_err());
2701    }
2702
2703    #[test]
2704    fn test_upsert_request_validates_sparse_vectors() {
2705        let mut metadata = HashMap::new();
2706        // Add unsorted sparse vector - should fail validation
2707        metadata.insert(
2708            "sparse".to_string(),
2709            UpdateMetadataValue::SparseVector(
2710                SparseVector::new(vec![3, 1, 2], vec![0.3, 0.1, 0.2]).unwrap(),
2711            ),
2712        );
2713
2714        let result = UpsertCollectionRecordsRequest::try_new(
2715            "tenant".to_string(),
2716            "database".to_string(),
2717            CollectionUuid(uuid::Uuid::new_v4()),
2718            vec!["id1".to_string()],
2719            vec![vec![0.1, 0.2]],
2720            None,
2721            None,
2722            Some(vec![Some(metadata)]),
2723        );
2724
2725        // Should fail because sparse vector is not sorted
2726        assert!(result.is_err());
2727    }
2728}