1use crate::authenticator::Authenticator;
20use crate::componentconfigs::NetworkAndCanonicalEndpoint;
21use crate::diagnosticscomponent::PingSearchReportOptions;
22use crate::error::ErrorKind;
23use crate::httpcomponent::{HttpComponent, HttpComponentState};
24use crate::httpx::client::Client;
25use crate::httpx::request::Auth;
26use crate::mgmtx::node_target::NodeTarget;
27use crate::options::search::SearchOptions;
28use crate::options::search_management::{
29 AllowQueryingOptions, AnalyzeDocumentOptions, DeleteIndexOptions, DisallowQueryingOptions,
30 EnsureIndexOptions, FreezePlanOptions, GetAllIndexesOptions, GetIndexOptions,
31 GetIndexedDocumentsCountOptions, PauseIngestOptions, ResumeIngestOptions, UnfreezePlanOptions,
32 UpsertIndexOptions,
33};
34use crate::results::pingreport::{EndpointPingReport, PingState};
35use crate::results::search::SearchResultStream;
36use crate::retry::{orchestrate_retries, RetryManager, RetryRequest, RetryStrategy};
37use crate::retrybesteffort::ExponentialBackoffCalculator;
38use crate::searchx::document_analysis::DocumentAnalysis;
39use crate::searchx::ensure_index_helper::EnsureIndexHelper;
40use crate::searchx::index::Index;
41use crate::searchx::mgmt_options::{EnsureIndexPollOptions, PingOptions};
42use crate::searchx::search::Search;
43use crate::service_type::ServiceType;
44use crate::tracingcomponent::TracingComponent;
45use crate::{error, httpx};
46use arc_swap::ArcSwap;
47use futures::future::join_all;
48use futures::StreamExt;
49use std::collections::HashMap;
50use std::future::Future;
51use std::ops::Sub;
52use std::sync::Arc;
53use std::time::Duration;
54use tokio::select;
55use tracing::debug;
56
57pub(crate) struct SearchComponent<C: Client> {
58 id: String,
59 http_component: HttpComponent<C>,
60 tracing: Arc<TracingComponent>,
61
62 retry_manager: Arc<RetryManager>,
63
64 state: ArcSwap<SearchComponentState>,
65}
66
67#[derive(Debug)]
68pub(crate) struct SearchComponentState {
69 pub vector_search_enabled: bool,
70}
71
72pub(crate) struct SearchComponentConfig {
73 pub endpoints: HashMap<String, NetworkAndCanonicalEndpoint>,
74 pub authenticator: Authenticator,
75
76 pub vector_search_enabled: bool,
77}
78
79#[derive(Debug)]
80pub(crate) struct SearchComponentOptions {
81 pub id: String,
82 pub user_agent: String,
83}
84
85impl<C: Client + 'static> SearchComponent<C> {
86 pub fn new(
87 retry_manager: Arc<RetryManager>,
88 http_client: Arc<C>,
89 tracing: Arc<TracingComponent>,
90 config: SearchComponentConfig,
91 opts: SearchComponentOptions,
92 ) -> Self {
93 Self {
94 id: opts.id,
95 http_component: HttpComponent::new(
96 ServiceType::SEARCH,
97 opts.user_agent,
98 http_client,
99 HttpComponentState::new(config.endpoints, config.authenticator),
100 ),
101 tracing,
102 retry_manager,
103 state: ArcSwap::new(Arc::new(SearchComponentState {
104 vector_search_enabled: config.vector_search_enabled,
105 })),
106 }
107 }
108
109 pub fn reconfigure(&self, config: SearchComponentConfig) {
110 debug!(
111 "Search component {} updating endpoints to {:?}",
112 self.id,
113 &config.endpoints.keys().collect::<Vec<_>>()
114 );
115
116 self.http_component.reconfigure(HttpComponentState::new(
117 config.endpoints,
118 config.authenticator,
119 ));
120
121 self.state.swap(Arc::new(SearchComponentState {
122 vector_search_enabled: config.vector_search_enabled,
123 }));
124 }
125
126 pub async fn query(&self, opts: SearchOptions) -> error::Result<SearchResultStream> {
127 if (opts.knn.is_some() || opts.knn_operator.is_some())
128 && !self.state.load().vector_search_enabled
129 {
130 return Err(ErrorKind::FeatureNotAvailable {
131 feature: "Vector Search".to_string(),
132 msg: "vector queries are available from Couchbase Server 7.6.0 and above"
133 .to_string(),
134 }
135 .into());
136 }
137 let retry_info = RetryRequest::new("search_query", true);
138
139 let retry = opts.retry_strategy.clone();
140 let endpoint = opts.endpoint.clone();
141 let copts = opts.into();
142
143 orchestrate_retries(self.retry_manager.clone(), retry, retry_info, async || {
144 self.http_component
145 .orchestrate_endpoint(
146 endpoint.clone(),
147 async |client: Arc<C>,
148 endpoint_id: String,
149 endpoint: String,
150 canonical_endpoint: String,
151 auth: Auth| {
152 let res = match (Search::<C> {
153 http_client: client,
154 user_agent: self.http_component.user_agent().to_string(),
155 endpoint: endpoint.clone(),
156 canonical_endpoint,
157 auth,
158
159 vector_search_enabled: self.state.load().vector_search_enabled,
160 tracing: self.tracing.clone(),
161 }
162 .query(&copts)
163 .await)
164 {
165 Ok(r) => r,
166 Err(e) => return Err(ErrorKind::Search(e).into()),
167 };
168
169 Ok(SearchResultStream {
170 inner: res,
171 endpoint,
172 })
173 },
174 )
175 .await
176 })
177 .await
178 }
179
180 pub async fn get_index(&self, opts: &GetIndexOptions<'_>) -> error::Result<Index> {
181 let retry_info = RetryRequest::new("search_get_index", true);
182 let retry = opts.retry_strategy.clone();
183 let endpoint = opts.endpoint;
184 let copts = opts.into();
185
186 self.orchestrate_mgmt_call(
187 retry,
188 retry_info,
189 endpoint.map(|e| e.to_string()),
190 async |search| {
191 search
192 .get_index(&copts)
193 .await
194 .map_err(|e| ErrorKind::Search(e).into())
195 },
196 )
197 .await
198 }
199
200 pub async fn get_all_indexes(
201 &self,
202 opts: &GetAllIndexesOptions<'_>,
203 ) -> error::Result<Vec<Index>> {
204 let retry_info = RetryRequest::new("search_get_all_indexes", true);
205 let retry = opts.retry_strategy.clone();
206 let endpoint = opts.endpoint;
207 let copts = opts.into();
208
209 self.orchestrate_mgmt_call(
210 retry,
211 retry_info,
212 endpoint.map(|e| e.to_string()),
213 async |search| {
214 search
215 .get_all_indexes(&copts)
216 .await
217 .map_err(|e| ErrorKind::Search(e).into())
218 },
219 )
220 .await
221 }
222
223 pub async fn upsert_index(&self, opts: &UpsertIndexOptions<'_>) -> error::Result<()> {
224 let retry_info = RetryRequest::new("search_upsert_index", true);
225 let retry = opts.retry_strategy.clone();
226 let endpoint = opts.endpoint;
227 let copts = opts.into();
228
229 self.orchestrate_no_res_mgmt_call(
230 retry,
231 retry_info,
232 endpoint.map(|e| e.to_string()),
233 async |search| {
234 search
235 .upsert_index(&copts)
236 .await
237 .map_err(|e| ErrorKind::Search(e).into())
238 },
239 )
240 .await
241 }
242
243 pub async fn delete_index(&self, opts: &DeleteIndexOptions<'_>) -> error::Result<()> {
244 let retry_info = RetryRequest::new("search_delete_index", true);
245 let retry = opts.retry_strategy.clone();
246 let endpoint = opts.endpoint;
247 let copts = opts.into();
248
249 self.orchestrate_no_res_mgmt_call(
250 retry,
251 retry_info,
252 endpoint.map(|e| e.to_string()),
253 async |search| {
254 search
255 .delete_index(&copts)
256 .await
257 .map_err(|e| ErrorKind::Search(e).into())
258 },
259 )
260 .await
261 }
262
263 pub async fn analyze_document(
264 &self,
265 opts: &AnalyzeDocumentOptions<'_>,
266 ) -> error::Result<DocumentAnalysis> {
267 let retry_info = RetryRequest::new("search_analyze_document", true);
268 let retry = opts.retry_strategy.clone();
269 let endpoint = opts.endpoint;
270 let copts = opts.into();
271
272 self.orchestrate_mgmt_call(
273 retry,
274 retry_info,
275 endpoint.map(|e| e.to_string()),
276 async |search| {
277 search
278 .analyze_document(&copts)
279 .await
280 .map_err(|e| ErrorKind::Search(e).into())
281 },
282 )
283 .await
284 }
285
286 pub async fn get_indexed_documents_count(
287 &self,
288 opts: &GetIndexedDocumentsCountOptions<'_>,
289 ) -> error::Result<u64> {
290 let retry_info = RetryRequest::new("search_get_indexed_documents_count", true);
291 let retry = opts.retry_strategy.clone();
292 let endpoint = opts.endpoint;
293 let copts = opts.into();
294
295 self.orchestrate_mgmt_call(
296 retry,
297 retry_info,
298 endpoint.map(|e| e.to_string()),
299 async |search| {
300 search
301 .get_indexed_documents_count(&copts)
302 .await
303 .map_err(|e| ErrorKind::Search(e).into())
304 },
305 )
306 .await
307 }
308
309 pub async fn pause_ingest(&self, opts: &PauseIngestOptions<'_>) -> error::Result<()> {
310 let retry_info = RetryRequest::new("search_pause_ingest", true);
311 let retry = opts.retry_strategy.clone();
312 let endpoint = opts.endpoint;
313 let copts = opts.into();
314
315 self.orchestrate_no_res_mgmt_call(
316 retry,
317 retry_info,
318 endpoint.map(|e| e.to_string()),
319 async |search| {
320 search
321 .pause_ingest(&copts)
322 .await
323 .map_err(|e| ErrorKind::Search(e).into())
324 },
325 )
326 .await
327 }
328
329 pub async fn resume_ingest(&self, opts: &ResumeIngestOptions<'_>) -> error::Result<()> {
330 let retry_info = RetryRequest::new("search_resume_ingest", true);
331 let retry = opts.retry_strategy.clone();
332 let endpoint = opts.endpoint;
333 let copts = opts.into();
334
335 self.orchestrate_no_res_mgmt_call(
336 retry,
337 retry_info,
338 endpoint.map(|e| e.to_string()),
339 async |search| {
340 search
341 .resume_ingest(&copts)
342 .await
343 .map_err(|e| ErrorKind::Search(e).into())
344 },
345 )
346 .await
347 }
348
349 pub async fn allow_querying(&self, opts: &AllowQueryingOptions<'_>) -> error::Result<()> {
350 let retry_info = RetryRequest::new("search_allow_querying", true);
351 let retry = opts.retry_strategy.clone();
352 let endpoint = opts.endpoint;
353 let copts = opts.into();
354
355 self.orchestrate_no_res_mgmt_call(
356 retry,
357 retry_info,
358 endpoint.map(|e| e.to_string()),
359 async |search| {
360 search
361 .allow_querying(&copts)
362 .await
363 .map_err(|e| ErrorKind::Search(e).into())
364 },
365 )
366 .await
367 }
368
369 pub async fn disallow_querying(&self, opts: &DisallowQueryingOptions<'_>) -> error::Result<()> {
370 let retry_info = RetryRequest::new("search_disallow_querying", true);
371 let retry = opts.retry_strategy.clone();
372 let endpoint = opts.endpoint;
373 let copts = opts.into();
374
375 self.orchestrate_no_res_mgmt_call(
376 retry,
377 retry_info,
378 endpoint.map(|e| e.to_string()),
379 async |search| {
380 search
381 .disallow_querying(&copts)
382 .await
383 .map_err(|e| ErrorKind::Search(e).into())
384 },
385 )
386 .await
387 }
388
389 pub async fn freeze_plan(&self, opts: &FreezePlanOptions<'_>) -> error::Result<()> {
390 let retry_info = RetryRequest::new("search_freeze_plan", true);
391 let retry = opts.retry_strategy.clone();
392 let endpoint = opts.endpoint;
393 let copts = opts.into();
394
395 self.orchestrate_no_res_mgmt_call(
396 retry,
397 retry_info,
398 endpoint.map(|e| e.to_string()),
399 async |search| {
400 search
401 .freeze_plan(&copts)
402 .await
403 .map_err(|e| ErrorKind::Search(e).into())
404 },
405 )
406 .await
407 }
408
409 pub async fn unfreeze_plan(&self, opts: &UnfreezePlanOptions<'_>) -> error::Result<()> {
410 let retry_info = RetryRequest::new("search_unfreeze_plan", true);
411 let retry = opts.retry_strategy.clone();
412 let endpoint = opts.endpoint;
413 let copts = opts.into();
414
415 self.orchestrate_no_res_mgmt_call(
416 retry,
417 retry_info,
418 endpoint.map(|e| e.to_string()),
419 async |search| {
420 search
421 .unfreeze_plan(&copts)
422 .await
423 .map_err(|e| ErrorKind::Search(e).into())
424 },
425 )
426 .await
427 }
428
429 pub async fn ensure_index(&self, opts: &EnsureIndexOptions<'_>) -> error::Result<()> {
430 let mut helper = EnsureIndexHelper::new(
431 self.http_component.user_agent(),
432 opts.index_name,
433 opts.bucket_name,
434 opts.scope_name,
435 opts.on_behalf_of_info,
436 );
437
438 let backoff = ExponentialBackoffCalculator::new(
439 Duration::from_millis(100),
440 Duration::from_millis(1000),
441 1.5,
442 );
443
444 self.http_component
445 .ensure_resource(backoff, async |client: Arc<C>, targets: Vec<NodeTarget>| {
446 helper
447 .clone()
448 .poll(&EnsureIndexPollOptions {
449 client,
450 targets,
451 desired_state: opts.desired_state,
452 })
453 .await
454 .map_err(error::Error::from)
455 })
456 .await
457 }
458
459 pub async fn ping_all_endpoints(
460 &self,
461 on_behalf_of: Option<&httpx::request::OnBehalfOfInfo>,
462 ) -> error::Result<Vec<error::Result<()>>> {
463 let (client, targets) = self.http_component.get_all_targets::<NodeTarget>(&[])?;
464
465 let copts = PingOptions { on_behalf_of };
466
467 let mut handles = Vec::with_capacity(targets.len());
468 let user_agent = self.http_component.user_agent().to_string();
469 for target in targets {
470 let user_agent = user_agent.clone();
471 let client = Search::<C> {
472 http_client: client.clone(),
473 user_agent,
474 endpoint: target.endpoint,
475 canonical_endpoint: target.canonical_endpoint,
476 auth: target.auth,
477 vector_search_enabled: false,
478 tracing: self.tracing.clone(),
479 };
480
481 let handle = self.ping_one(client, copts.clone());
482
483 handles.push(handle);
484 }
485
486 let results = join_all(handles).await;
487
488 Ok(results)
489 }
490
491 pub async fn create_ping_report(
492 &self,
493 opts: PingSearchReportOptions<'_>,
494 ) -> error::Result<Vec<EndpointPingReport>> {
495 let (client, targets) = self.http_component.get_all_targets::<NodeTarget>(&[])?;
496
497 let copts = PingOptions {
498 on_behalf_of: opts.on_behalf_of,
499 };
500 let timeout = opts.timeout;
501
502 let mut handles = Vec::with_capacity(targets.len());
503 let user_agent = self.http_component.user_agent().to_string();
504 for target in targets {
505 let user_agent = user_agent.clone();
506 let client = Search::<C> {
507 http_client: client.clone(),
508 user_agent,
509 endpoint: target.endpoint,
510 canonical_endpoint: target.canonical_endpoint,
511 auth: target.auth,
512
513 vector_search_enabled: self.state.load().vector_search_enabled,
514 tracing: self.tracing.clone(),
515 };
516
517 let handle = self.create_one_report(client, timeout, copts.clone());
518
519 handles.push(handle);
520 }
521
522 let reports = join_all(handles).await;
523
524 Ok(reports)
525 }
526
527 async fn ping_one(&self, client: Search<C>, opts: PingOptions<'_>) -> error::Result<()> {
528 client.ping(&opts).await.map_err(error::Error::from)
529 }
530
531 async fn create_one_report(
532 &self,
533 client: Search<C>,
534 timeout: Duration,
535 opts: PingOptions<'_>,
536 ) -> EndpointPingReport {
537 let start = std::time::Instant::now();
538 let res = select! {
539 e = tokio::time::sleep(timeout) => {
540 return EndpointPingReport {
541 remote: client.endpoint,
542 error: None,
543 latency: std::time::Instant::now().sub(start),
544 id: None,
545 namespace: None,
546 state: PingState::Timeout,
547 }
548 }
549 r = client.ping(&opts) => r.map_err(error::Error::from),
550 };
551 let end = std::time::Instant::now();
552
553 let (error, state) = match res {
554 Ok(_) => (None, PingState::Ok),
555 Err(e) => (Some(e), PingState::Error),
556 };
557
558 EndpointPingReport {
559 remote: client.endpoint,
560 error,
561 latency: end.sub(start),
562 id: None,
563 namespace: None,
564 state,
565 }
566 }
567
568 async fn orchestrate_mgmt_call<Fut, Resp>(
569 &self,
570 retry_strategy: Arc<dyn RetryStrategy>,
571 retry_info: RetryRequest,
572 endpoint: Option<String>,
573 operation: impl Fn(Search<C>) -> Fut + Send + Sync,
574 ) -> error::Result<Resp>
575 where
576 Resp: Send + Sync,
577 Fut: Future<Output = error::Result<Resp>> + Send,
578 C: Client,
579 {
580 orchestrate_retries(
581 self.retry_manager.clone(),
582 retry_strategy,
583 retry_info,
584 async || {
585 self.http_component
586 .orchestrate_endpoint(
587 endpoint.clone(),
588 async |client: Arc<C>,
589 endpoint_id: String,
590 endpoint: String,
591 canonical_endpoint: String,
592 auth: Auth| {
593 operation(Search::<C> {
594 http_client: client,
595 user_agent: self.http_component.user_agent().to_string(),
596 endpoint,
597 canonical_endpoint,
598 auth,
599
600 vector_search_enabled: self.state.load().vector_search_enabled,
601 tracing: self.tracing.clone(),
602 })
603 .await
604 },
605 )
606 .await
607 },
608 )
609 .await
610 }
611
612 async fn orchestrate_no_res_mgmt_call<Fut>(
613 &self,
614 retry_strategy: Arc<dyn RetryStrategy>,
615 retry_info: RetryRequest,
616 endpoint: Option<String>,
617 operation: impl Fn(Search<C>) -> Fut + Send + Sync,
618 ) -> error::Result<()>
619 where
620 Fut: Future<Output = error::Result<()>> + Send,
621 C: Client,
622 {
623 orchestrate_retries(
624 self.retry_manager.clone(),
625 retry_strategy,
626 retry_info,
627 async || {
628 self.http_component
629 .orchestrate_endpoint(
630 endpoint.clone(),
631 async |client: Arc<C>,
632 endpoint_id: String,
633 endpoint: String,
634 canonical_endpoint: String,
635 auth: Auth| {
636 operation(Search::<C> {
637 http_client: client,
638 user_agent: self.http_component.user_agent().to_string(),
639 endpoint,
640 canonical_endpoint,
641 auth,
642
643 vector_search_enabled: self.state.load().vector_search_enabled,
644 tracing: self.tracing.clone(),
645 })
646 .await
647 },
648 )
649 .await
650 },
651 )
652 .await
653 }
654}