1mod text_search;
4pub(in crate::serve) use text_search::{Words, not_configured};
5
6use std::cell::Cell;
7use std::io::{self, Write};
8use std::num::{NonZeroU32, NonZeroU64, NonZeroUsize};
9use std::sync::{Arc, Condvar, Mutex, TryLockError};
10use std::time::{Duration, Instant};
11
12use haematite::db::ReadOnlyDatabase;
13use serde_json::value::RawValue;
14use serde_json::{Value, json};
15
16use super::embed::job::Bounds;
17use super::embed::settings::Restart;
18use super::embedding::{Driver, Guard, Worker};
19use super::protocol::{
20 BranchPage, Change, CollectionPage, CountsRebuild, DocumentPage, DraftsRun, EdgePage,
21 EmbedRewalk, GraphPage, MutationContext, Operation, PROTOCOL, ResponseFailure, ReviewPage,
22 Selection, StoreRead,
23};
24use super::query::Seal;
25use super::query::admission::Slots;
26use super::query::probe::Prepared;
27use super::releaser::{self, Releaser};
28use super::storage::{
29 Captured, Catalogue, DiscardPasses, Failure, Held, Storage, edges, graph, reads,
30};
31
32use super::access::{self, Access, Source, Standalone};
33use super::changes::{self, ChangesRequest};
34use super::pass::{self, Gate};
35use super::placement::{self, Placer};
36
37thread_local! {
38 static WRITER_DEADLINE: Cell<Option<(Instant, Duration)>> = const { Cell::new(None) };
39}
40
41struct RequestWait(Option<(Instant, Duration)>);
42impl RequestWait {
43 fn begin(timeout: Duration) -> Result<Self, Failure> {
44 if timeout.is_zero() {
45 return Err(Failure::new(
46 "invalid_params",
47 "writer timeout must be positive",
48 ));
49 }
50 let end = Instant::now().checked_add(timeout).ok_or_else(|| {
51 Failure::new(
52 "invalid_params",
53 "writer timeout exceeds the monotonic clock",
54 )
55 })?;
56 Ok(Self(WRITER_DEADLINE.replace(Some((end, timeout)))))
57 }
58}
59impl Drop for RequestWait {
60 fn drop(&mut self) {
61 WRITER_DEADLINE.set(self.0);
62 }
63}
64
65#[derive(Default)]
66struct WriterAvailable {
67 active: Mutex<bool>,
68 changed: Condvar,
69}
70
71pub struct Service {
72 access_source: Arc<dyn Source>,
73 lys: Option<Arc<Gate>>,
76 storage: Arc<Mutex<Storage>>,
77 writer_available: Arc<WriterAvailable>,
78 reader: ReadOnlyDatabase,
79 catalogue: Catalogue,
80 discard_passes: DiscardPasses,
82 pub max_page: NonZeroU32,
83 pub append_prune_limits: super::storage::AppendPruneLimits,
84 pub query: Option<Prepared>,
85 query_slots: Slots,
87 query_seal: Seal,
89 pub changes: changes::Settings,
91 releaser: Option<Releaser>,
93 embedding: Option<Driver>,
95 pub embed_search_bound: Option<NonZeroU64>,
97 placer: Option<Placer>,
100}
101
102impl Service {
103 pub fn new(storage: Storage, max_page: NonZeroU32) -> Result<Self, Failure> {
107 Self::with_query(storage, max_page, None)
108 }
109
110 pub fn with_query(
116 storage: Storage,
117 max_page: NonZeroU32,
118 query: Option<Prepared>,
119 ) -> Result<Self, Failure> {
120 Self::with_access_source(storage, max_page, query, Arc::new(Standalone))
121 }
122
123 pub(in crate::serve) fn with_access_source(
124 storage: Storage,
125 max_page: NonZeroU32,
126 query: Option<Prepared>,
127 access_source: Arc<dyn Source>,
128 ) -> Result<Self, Failure> {
129 let reader = storage.reader()?;
130 let catalogue = storage.catalogue();
131 let discard_passes = storage.discard_passes();
132 let running = running_limit(query.as_ref().map(Prepared::settings))?;
133 let embed_search_bound = query
134 .as_ref()
135 .map(|prepared| prepared.settings().scan_records);
136 Ok(Self {
137 access_source,
138 lys: None,
139 storage: Arc::new(Mutex::new(storage)),
140 writer_available: Arc::default(),
141 reader,
142 catalogue,
143 discard_passes,
144 max_page,
145 append_prune_limits: super::storage::AppendPruneLimits::default(),
146 query,
147 query_slots: Slots::new(running),
148 query_seal: Seal::new()?,
149 changes: changes::Settings::default(),
150 releaser: None,
151 embedding: None,
152 embed_search_bound,
153 placer: None,
154 })
155 }
156
157 #[must_use]
161 pub fn with_lys(mut self, gate: Gate) -> Self {
162 if let Some(placer) = &self.placer {
163 placer.tell(gate.made());
164 }
165 self.lys = Some(Arc::new(gate));
166 self
167 }
168
169 pub fn with_placer(mut self, placer: Placer) -> Result<Self, Failure> {
177 if let Some(gate) = &self.lys {
178 placer.tell(gate.made());
179 }
180 placer.existing(placement::held(&self.catalogue, &self.reader)?);
181 self.placer = Some(placer);
182 Ok(self)
183 }
184
185 #[must_use]
189 pub const fn decides(&self) -> bool {
190 self.lys.is_some()
191 }
192
193 pub fn present(&self, token: &str) -> Result<Value, Failure> {
200 let Some(gate) = &self.lys else {
201 return Err(Failure::new(
202 "invalid_operation",
203 "this service is not served through Lys, so it takes no pass",
204 ));
205 };
206 gate.present(token, pass::now())
207 }
208
209 pub fn run_drafts(&self, token: Option<&str>, run: &DraftsRun) -> Result<Value, Failure> {
217 let Some(gate) = &self.lys else {
218 return Err(Failure::new(
219 "invalid_operation",
220 "this service is not served through Lys, so it runs no drafts",
221 ));
222 };
223 gate.run_drafts(self, token, run.after.as_deref())
224 }
225
226 pub(in crate::serve) fn run_drafts_waiting(
228 &self,
229 token: Option<&str>,
230 run: &DraftsRun,
231 timeout: Duration,
232 ) -> Result<Value, Failure> {
233 let _wait = RequestWait::begin(timeout)?;
234 self.run_drafts(token, run)
235 }
236
237 pub fn follow(
248 &self,
249 branch: &str,
250 expires: Option<u64>,
251 clock: impl Fn() -> std::time::SystemTime,
252 send: impl FnMut(&Value) -> io::Result<()>,
253 ) -> io::Result<Failure> {
254 super::follow::follow(&self.catalogue, branch, expires, clock, send)
255 }
256
257 pub(in crate::serve) fn follow_socket(
258 &self,
259 peer: std::os::fd::OwnedFd,
260 branch: &str,
261 expires: Option<u64>,
262 send: impl FnMut(&Value) -> io::Result<()>,
263 ) -> io::Result<Failure> {
264 use std::sync::atomic::{AtomicBool, Ordering};
265 let cancelled = Arc::new(AtomicBool::new(false));
266 let closed = Arc::clone(&cancelled);
267 let catalogue = self.catalogue.clone();
268 let _monitor = super::follow::Monitor::start(peer, move || {
269 closed.store(true, Ordering::Release);
270 catalogue.wake_followers();
271 })?;
272 super::follow::follow_cancellable(
273 &self.catalogue,
274 branch,
275 expires,
276 std::time::SystemTime::now,
277 &cancelled,
278 send,
279 )
280 }
281
282 pub fn authorize(
291 &self,
292 token: Option<&str>,
293 operation: &Operation,
294 words: Option<&str>,
295 ) -> Result<(), Failure> {
296 let Some(gate) = &self.lys else {
297 return Ok(());
298 };
299 let absent = |kind: &str, name: &str| match kind {
300 "branch" => self.catalogue.head(name).is_ok_and(|head| head.is_none()),
301 "collection" => self.collection_absent(operation, name),
302 _ => false,
303 };
304 gate.authorize_with(token, operation, words, pass::now(), &absent)
305 .map(drop)
306 }
307
308 fn collection_absent(&self, operation: &Operation, collection: &str) -> bool {
312 let Operation::Mutate(mutation) = operation else {
313 return false;
314 };
315 self.catalogue
316 .capture_held(&Selection {
317 branch: mutation.branch.clone(),
318 version: None,
319 })
320 .and_then(|at| reads::collection(&self.reader, &at, collection))
321 .is_ok_and(|found| found.is_none())
322 }
323
324 pub fn start_release(&mut self, settings: releaser::Settings) -> Result<(), Failure> {
331 self.releaser = Some(Releaser::start(Arc::clone(&self.storage), settings)?);
332 Ok(())
333 }
334
335 pub fn start_embedding(
342 &mut self,
343 worker: Arc<dyn Worker>,
344 bounds: Bounds,
345 writer_wait: std::time::Duration,
346 ) -> Result<(), Failure> {
347 self.start_embedding_with(worker, bounds, writer_wait, Restart::default())
348 }
349
350 pub fn start_embedding_with(
357 &mut self,
358 worker: Arc<dyn Worker>,
359 bounds: Bounds,
360 writer_wait: std::time::Duration,
361 restart: Restart,
362 ) -> Result<(), Failure> {
363 self.start_embedding_apart(worker, None, bounds, writer_wait, restart)
364 }
365
366 pub fn start_embedding_apart(
375 &mut self,
376 worker: Arc<dyn Worker>,
377 questions: Option<Arc<dyn Worker>>,
378 bounds: Bounds,
379 writer_wait: std::time::Duration,
380 restart: Restart,
381 ) -> Result<(), Failure> {
382 let storage = Arc::clone(&self.storage);
383 let driver = Driver::start_apart(storage, worker, questions, bounds, writer_wait, restart)?;
384 self.embedding = Some(driver);
385 Ok(())
386 }
387
388 #[must_use]
390 pub const fn embedding(&self) -> Option<&Driver> {
391 self.embedding.as_ref()
392 }
393
394 #[cfg(test)]
395 pub(in crate::serve) fn start_embedding_steps(
396 &mut self,
397 worker: Arc<dyn Worker>,
398 bounds: Bounds,
399 restart: Restart,
400 ) -> Result<(), Failure> {
401 self.embedding = Some(Driver::for_steps(&self.storage, worker, bounds, restart)?);
402 Ok(())
403 }
404
405 #[cfg(test)]
406 pub(in crate::serve) fn embedding_step_at(
407 &self,
408 bounds: Bounds,
409 now: std::time::Instant,
410 ) -> Result<bool, String> {
411 self.embedding
412 .as_ref()
413 .ok_or("embedding driver missing")?
414 .step_at(&self.storage, bounds, now)
415 }
416
417 #[must_use]
419 pub const fn releaser(&self) -> Option<&Releaser> {
420 self.releaser.as_ref()
421 }
422
423 pub(crate) fn activate_reclaim(&self) -> Result<(), Failure> {
424 self.admission()?.activate_reclaim()
425 }
426
427 #[cfg(test)]
433 pub fn quiesce_reclaim(&self) -> Result<(), Failure> {
434 self.admission()?.quiesce_reclaim()
435 }
436
437 #[cfg(test)]
442 pub fn reclaim_status(&self) -> Result<haematite::db::ReclaimStatus, Failure> {
443 Ok(self.admission()?.reclaim_status())
444 }
445
446 fn releasing(&self) -> bool {
447 self.releaser.as_ref().is_some_and(Releaser::is_releasing)
448 }
449
450 fn admission(&self) -> Result<Guard<'_, Storage>, Failure> {
451 let Some((end, timeout)) = WRITER_DEADLINE.get() else {
452 return self.admission_now();
453 };
454 let started = Instant::now();
455 loop {
456 if Instant::now() >= end {
457 return Err(Failure::new(
458 "busy",
459 format!(
460 "writer lock wait reached timeout_ms={} after {} ms",
461 timeout.as_millis(),
462 started.elapsed().as_millis()
463 ),
464 ));
465 }
466 match self.admission_now() {
467 Ok(guard) => {
468 if Instant::now() < end {
469 return Ok(guard);
470 }
471 drop(guard);
472 return Err(Failure::new(
473 "busy",
474 format!(
475 "writer lock wait reached timeout_ms={} after {} ms",
476 timeout.as_millis(),
477 started.elapsed().as_millis()
478 ),
479 ));
480 }
481 Err(failure) if failure.code == "busy" => {
482 self.wait_writer(end, started, timeout)?;
483 }
484 Err(failure) => return Err(failure),
485 }
486 }
487 }
488
489 fn wait_writer(
490 &self,
491 end: Instant,
492 started: Instant,
493 timeout: Duration,
494 ) -> Result<(), Failure> {
495 let unavailable = || {
496 Failure::new(
497 "writer_unavailable",
498 "writer wait state requires process recovery",
499 )
500 };
501 let busy = || {
502 Failure::new(
503 "busy",
504 format!(
505 "writer lock wait reached timeout_ms={} after {} ms",
506 timeout.as_millis(),
507 started.elapsed().as_millis()
508 ),
509 )
510 };
511 if Instant::now() >= end {
512 return Err(busy());
513 }
514 let waited = {
515 let mut active = self
516 .writer_available
517 .active
518 .lock()
519 .map_err(|_| unavailable())?;
520 if !*active {
521 *active = true;
522 #[cfg(test)]
523 self.writer_available.changed.notify_all();
524 let storage = Arc::clone(&self.storage);
525 let ready = Arc::clone(&self.writer_available);
526 if let Err(error) = std::thread::Builder::new()
527 .name("haematite-writer-ready".to_owned())
528 .spawn(move || {
529 match storage.lock() {
531 Ok(guard) => drop(guard),
532 Err(error) => eprintln!("haematite writer_unavailable: {error}"),
533 }
534 match ready.active.lock() {
535 Ok(mut active) => {
536 *active = false;
537 ready.changed.notify_all();
538 }
539 Err(error) => {
540 eprintln!("haematite writer_wait_unavailable: {error}");
541 ready.changed.notify_all();
542 }
543 }
544 })
545 {
546 *active = false;
547 self.writer_available.changed.notify_all();
548 return Err(Failure::new(
549 "writer_unavailable",
550 format!("writer wait thread could not start: {error}"),
551 ));
552 }
553 }
554 let left = end
555 .checked_duration_since(Instant::now())
556 .filter(|left| !left.is_zero())
557 .ok_or_else(busy)?;
558 self.writer_available
559 .changed
560 .wait_timeout_while(active, left, |active| *active)
561 .map_err(|_| unavailable())
562 }?;
563 let timed_out = waited.1.timed_out();
564 drop(waited);
565 if timed_out {
566 return Err(busy());
567 }
568 Ok(())
569 }
570
571 fn admission_now(&self) -> Result<Guard<'_, Storage>, Failure> {
572 if let Some(driver) = &self.embedding {
573 return match WRITER_DEADLINE.get() {
574 Some((end, _)) => driver.admission().foreground_until(&self.storage, end),
575 None => driver
576 .admission()
577 .foreground(&self.storage, self.releasing()),
578 };
579 }
580 let unavailable = || {
581 Failure::new(
582 "writer_unavailable",
583 "writer state requires process recovery",
584 )
585 };
586 match self.storage.try_lock() {
587 Ok(guard) => Ok(Guard::plain(guard)),
588 Err(TryLockError::WouldBlock)
589 if self.releasing() && WRITER_DEADLINE.get().is_none() =>
590 {
591 self.storage
592 .lock()
593 .map(Guard::plain)
594 .map_err(|_| unavailable())
595 }
596 Err(TryLockError::WouldBlock) => Err(Failure::new(
597 "busy",
598 "another mutation is being admitted; retry this request identity",
599 )),
600 Err(TryLockError::Poisoned(_)) => Err(unavailable()),
601 }
602 }
603
604 pub fn dispatch(&self, operation: Operation) -> Result<Value, ResponseFailure> {
612 let mutation = MutationContext::from_operation(&operation);
613 let result = access::request(self.access_source.as_ref())
614 .and_then(|access| self.dispatch_with_entry_budget(&access, operation, None))
615 .and_then(Answer::into_value);
616 result.map_err(|failure| ResponseFailure::new(failure, mutation))
617 }
618
619 pub fn dispatch_waiting(
625 &self,
626 operation: Operation,
627 timeout: std::time::Duration,
628 ) -> Result<Value, ResponseFailure> {
629 let mutation = MutationContext::from_operation(&operation);
630 let _wait = RequestWait::begin(timeout)
631 .map_err(|failure| ResponseFailure::new(failure, mutation))?;
632 self.dispatch(operation)
633 }
634
635 pub fn dispatch_text_waiting(
641 &self,
642 operation: Operation,
643 limit: NonZeroUsize,
644 timeout: Duration,
645 ) -> Result<Box<RawValue>, ResponseFailure> {
646 let mutation = MutationContext::from_operation(&operation);
647 let _wait = RequestWait::begin(timeout)
648 .map_err(|failure| ResponseFailure::new(failure, mutation))?;
649 self.dispatch_text(operation, limit)
650 }
651
652 pub fn dispatch_text(
662 &self,
663 operation: Operation,
664 response_byte_limit: NonZeroUsize,
665 ) -> Result<Box<RawValue>, ResponseFailure> {
666 let mutation = MutationContext::from_operation(&operation);
667 let access = access::request(self.access_source.as_ref())
668 .map_err(|failure| ResponseFailure::new(failure, mutation.clone()))?;
669 let text = self
670 .dispatch_with_entry_budget(&access, operation, Some(response_byte_limit))
671 .and_then(Answer::into_text)
672 .map_err(|failure| ResponseFailure::new(failure, mutation.clone()))?;
673 if text.get().len() > response_byte_limit.get() {
674 return Err(ResponseFailure::after_dispatch(
675 Failure::new(
676 "response_limit",
677 "response exceeds the configured byte budget; use a smaller page",
678 ),
679 mutation,
680 ));
681 }
682 Ok(text)
683 }
684
685 pub fn dispatch_bounded(
693 &self,
694 operation: Operation,
695 response_byte_limit: NonZeroUsize,
696 ) -> Result<Value, ResponseFailure> {
697 let mutation = MutationContext::from_operation(&operation);
698 let access = access::request(self.access_source.as_ref())
699 .map_err(|failure| ResponseFailure::new(failure, mutation.clone()))?;
700 let result = self
701 .dispatch_with_entry_budget(&access, operation, Some(response_byte_limit))
702 .and_then(Answer::into_value)
703 .map_err(|failure| ResponseFailure::new(failure, mutation.clone()))?;
704 if !fits_serialized(&result, response_byte_limit)
705 .map_err(|failure| ResponseFailure::after_dispatch(failure, mutation.clone()))?
706 {
707 return Err(ResponseFailure::after_dispatch(
708 Failure::new(
709 "response_limit",
710 "response exceeds the configured byte budget; use a smaller page",
711 ),
712 mutation,
713 ));
714 }
715 Ok(result)
716 }
717
718 fn dispatch_with_entry_budget(
719 &self,
720 access: &Access,
721 operation: Operation,
722 response_byte_limit: Option<NonZeroUsize>,
723 ) -> Result<Answer, Failure> {
724 match operation {
725 Operation::Mutate(request) => {
726 if matches!(&request.change, Change::Discard(_)) && !access.can_read_store() {
727 return Err(Failure::new(
728 "discard_access_refused",
729 "whole store authority is required",
730 ));
731 }
732 if matches!(&request.change, Change::Merge(_)) && !access.can_read_store() {
734 return Err(Failure::new(
735 "merge_access_refused",
736 "whole store authority is required",
737 ));
738 }
739 let mut writer = self.admission()?;
740 let receipt = writer.mutate_with_append_prune_limits(
741 &request,
742 self.max_page,
743 self.append_prune_limits,
744 )?;
745 let discarded = matches!(&request.change, Change::Discard(_));
747 let release_due = self.releaser.as_ref().is_some_and(|releaser| {
748 discarded || releaser.due(writer.advanced_since_release(&request.branch))
749 });
750 drop(writer);
751 if release_due && let Some(releaser) = &self.releaser {
752 if discarded {
753 releaser.discarded(&request.branch);
754 } else {
755 releaser.wake(&request.branch);
756 }
757 }
758 if let Some(driver) = &self.embedding {
759 wake_embedding(driver, &request.branch, &request.change);
760 }
761 if let Some(gate) = &self.lys
764 && let Some(child) = placement::made_by(&request.change)
765 {
766 gate.placed(
767 child.kind.lys(),
768 &child.name,
769 pass::now(),
770 self.placer.is_some(),
771 );
772 }
773 if let Some(placer) = &self.placer
776 && let Some(child) = placement::made_by(&request.change)
777 {
778 placer.committed(child);
779 }
780 serde_json::to_value(receipt)
781 .map(Answer::Value)
782 .map_err(Failure::storage)
783 }
784 Operation::Receipt(query) => serde_json::to_value(self.admission()?.receipt(&query)?)
785 .map(Answer::Value)
786 .map_err(Failure::storage),
787 Operation::CountsRebuild(request) => {
788 self.counts_rebuild(access, &request).map(Answer::Value)
789 }
790 Operation::EmbedRewalk(request) => {
791 self.embed_rewalk(access, &request).map(Answer::Value)
792 }
793 Operation::QueryRun(request) => self
794 .query_run(access, &request, response_byte_limit)
795 .map(Answer::Text),
796 Operation::ChangesList(request) => self
797 .changes_list(access, &request, response_byte_limit)
798 .map(Answer::Text),
799 read => self
800 .dispatch_read(access, read, response_byte_limit)
801 .map(Answer::Value),
802 }
803 }
804
805 fn dispatch_read(
806 &self,
807 access: &Access,
808 operation: Operation,
809 response_byte_limit: Option<NonZeroUsize>,
810 ) -> Result<Value, Failure> {
811 match operation {
812 Operation::Hello(agreement) => {
813 agreement.validate(agreement.max_frame)?;
814 Ok(
815 json!({"protocol": PROTOCOL, "capabilities": [super::protocol::RECEIPT_CAPABILITY], "max_page": self.max_page}),
816 )
817 }
818 Operation::QueryCheck(request) => Ok(super::query::check::check(&request)),
819 Operation::QueryWords(request) => super::query::check::words(&request),
820 Operation::Branches(page) => self.branch_page(&page),
821 Operation::Collections(page) => {
822 self.collection_page(access, &page, response_byte_limit)
823 }
824 Operation::Get(read) => {
825 let at = self.capture(&read.selection, false)?;
826 let document = reads::document(
827 access,
828 &self.reader,
829 &at,
830 &read.collection,
831 &read.document_id,
832 )?;
833 Ok(json!({"selection":view(&at), "document":document}))
834 }
835 Operation::List(page) => self.document_page(access, &page, response_byte_limit),
836 Operation::Review(page) => self.branch_review(&page),
837 Operation::ReviewCounts(page) => self.branch_review_counts(&page),
838 Operation::MergePrepare(page) => self.branch_merge_prepare(&page),
839 Operation::MergeDangling(page) => self.branch_merge_dangling(&page),
840 Operation::History(page) => self.history_page(&page),
841 Operation::EdgesOut(page) => self.edge_page(access, &page, false, response_byte_limit),
842 Operation::EdgesIn(page) => self.edge_page(access, &page, true, response_byte_limit),
843 Operation::GraphExport(page) => self.graph_export(access, &page, response_byte_limit),
844 Operation::VectorGet(read) => {
845 let at = self.capture(&read.selection, false)?;
846 let vector = super::storage::vectors::get(
847 access,
848 &self.reader,
849 &at,
850 &read.collection,
851 &read.field,
852 &read.document_id,
853 )?;
854 Ok(json!({"selection":view(&at), "vector":vector}))
855 }
856 Operation::VectorList(page) => {
857 let limit = self.limit(page.limit)?;
858 let at = self.capture(&page.selection, page.after.is_some())?;
859 let (vectors, next_after) = super::storage::vectors::list(
860 access,
861 &self.reader,
862 &at,
863 &page.collection,
864 &page.field,
865 page.after.as_deref(),
866 (limit, response_limit(response_byte_limit)?),
867 )?;
868 Ok(json!({"selection":view(&at), "vectors":vectors, "next_after":next_after}))
869 }
870 Operation::VectorSearch(search) => {
871 let k = self.limit(search.k)?;
872 let at = self.capture(&search.selection, false)?;
873 let (hits, scanned) =
874 super::storage::vectors::search(access, &self.reader, &at, &search, k)?;
875 Ok(json!({"selection":view(&at), "hits":hits, "scanned":scanned}))
876 }
877 Operation::VectorSearchText(search) => self.search_text(access, &search),
878 Operation::StoreStats(read) => self.store_stats(access, &read),
879 Operation::CountsAudit(selection) => self.counts_audit(access, &selection),
880 Operation::VectorFields(read) => self.vector_fields(access, &read),
881 Operation::Relations(read) => {
882 let at = self.capture(&read.selection, false)?;
883 let relations = super::storage::relations::list(
884 access,
885 &self.reader,
886 &at,
887 response_limit(response_byte_limit)?,
888 )?;
889 Ok(json!({"selection":view(&at), "relations":relations}))
890 }
891 Operation::Mutate(_)
892 | Operation::Receipt(_)
893 | Operation::CountsRebuild(_)
894 | Operation::EmbedRewalk(_) => Err(Failure::new(
895 "invalid_operation",
896 "mutation must use writer admission",
897 )),
898 Operation::QueryRun(_) | Operation::ChangesList(_) => Err(Failure::new(
899 "invalid_operation",
900 "query.run and changes.list are answered as text",
901 )),
902 Operation::Present(_) | Operation::DraftsRun(_) | Operation::Follow(_) => {
903 Err(Failure::new(
904 "invalid_operation",
905 "session.pass, drafts.run and head.follow are answered by the connection \
906 that asks them, with its pass",
907 ))
908 }
909 }
910 }
911
912 fn history_page(&self, page: &super::protocol::HistoryPage) -> Result<Value, Failure> {
913 let limit = self.limit(page.limit)?;
914 let rows = self
915 .catalogue
916 .history(&page.branch, page.after, limit.get() + 1)?;
917 let more = rows.len() > limit.get();
918 let rows: Vec<_> = rows.into_iter().take(limit.get()).collect();
919 let next = more.then(|| rows.last().map(|row| row.version)).flatten();
920 Ok(json!({"entries":rows.iter().map(view).collect::<Vec<_>>(), "next_after":next}))
921 }
922
923 fn branch_page(&self, page: &BranchPage) -> Result<Value, Failure> {
924 let limit = self.limit(page.limit)?;
925 let rows = self
926 .catalogue
927 .branches(page.after.as_deref(), limit.get() + 1)?;
928 let more = rows.len() > limit.get();
929 let rows: Vec<_> = rows.into_iter().take(limit.get()).collect();
930 let next = more
931 .then(|| rows.last().map(|row| row.branch.clone()))
932 .flatten();
933 Ok(json!({"entries": rows.iter().map(view).collect::<Vec<_>>(), "next_after": next}))
934 }
935
936 fn counts_rebuild(&self, access: &Access, request: &CountsRebuild) -> Result<Value, Failure> {
937 if !access.can_read_store() {
938 return Err(Failure::new(
939 "counts_access_refused",
940 "whole store authority is required",
941 ));
942 }
943 let mut writer = self.admission()?;
944 serde_json::to_value(writer.rebuild_counts(request)?).map_err(Failure::storage)
945 }
946
947 fn embed_rewalk(&self, access: &Access, request: &EmbedRewalk) -> Result<Value, Failure> {
949 if !access.can_read_store() {
950 return Err(Failure::new(
951 "embed_rewalk_access_refused",
952 "whole store authority is required",
953 ));
954 }
955 let Some(driver) = &self.embedding else {
956 return Err(Failure::new(
957 "embed_not_configured",
958 "this service embeds no field",
959 ));
960 };
961 let (branch, collection, field) = (&request.branch, &request.collection, &request.field);
962 let through = self
963 .admission()?
964 .embed_rewalk_longer(branch, collection, field)?;
965 driver.declared(branch, collection, field);
966 Ok(
967 json!({"branch": branch, "collection": collection, "field": field,
968 "through": through}),
969 )
970 }
971
972 fn counts_audit(&self, access: &Access, selection: &Selection) -> Result<Value, Failure> {
973 let at = self.capture(selection, false)?;
974 let mut answer = serde_json::to_value(Storage::audit_counts(access, &self.reader, &at)?)
975 .map_err(Failure::storage)?;
976 answer
977 .as_object_mut()
978 .ok_or_else(|| Failure::new("answer_shape", "audit answer must be an object"))?
979 .insert("selection".to_owned(), view(&at));
980 Ok(answer)
981 }
982
983 fn changes_list(
985 &self,
986 access: &Access,
987 request: &ChangesRequest,
988 response_byte_limit: Option<NonZeroUsize>,
989 ) -> Result<Box<RawValue>, Failure> {
990 let context = changes::Context {
991 access,
992 reader: &self.reader,
993 catalogue: &self.catalogue,
994 settings: &self.changes,
995 seal: &self.query_seal,
996 };
997 changes::list(&context, request, response_byte_limit)
998 }
999
1000 fn branch_review(&self, page: &ReviewPage) -> Result<Value, Failure> {
1001 let result = super::storage::review::page(
1002 &self.reader,
1003 &self.catalogue,
1004 page,
1005 self.limit(page.limit)?,
1006 )?;
1007 Ok(
1009 json!({"branch": result.selection.branch, "selection": view(&result.selection),
1010 "from": result.from, "diff": result.diff, "next_after": result.next_after,
1011 "changes": null, "conflicts": null, "dangling": null, "base_moved": result.base_moved}),
1012 )
1013 }
1014
1015 fn branch_review_counts(&self, page: &ReviewPage) -> Result<Value, Failure> {
1016 let result = super::storage::review::counts::page(
1017 &self.reader,
1018 &self.catalogue,
1019 page,
1020 self.limit(page.limit)?,
1021 )?;
1022 Ok(json!({
1023 "branch": result.selection.branch, "selection": view(&result.selection),
1024 "from": result.from, "parent": view(&result.parent), "partial": result.partial,
1025 "base_moved": result.base_moved, "done": result.next_after.is_none(),
1026 "next_after": result.next_after,
1027 }))
1028 }
1029
1030 fn branch_merge_prepare(&self, page: &ReviewPage) -> Result<Value, Failure> {
1031 let result = super::storage::review::prepare::page(
1032 &self.reader,
1033 &self.catalogue,
1034 page,
1035 self.limit(page.limit)?,
1036 )?;
1037 Ok(json!({
1038 "branch": result.selection.branch, "selection": view(&result.selection),
1039 "from": result.from, "parent": view(&result.parent), "lines": result.lines,
1040 "done": result.next_after.is_none(), "next_after": result.next_after,
1041 }))
1042 }
1043
1044 fn branch_merge_dangling(&self, page: &ReviewPage) -> Result<Value, Failure> {
1045 let result = super::storage::review::dangling::page(
1046 &self.reader,
1047 &self.catalogue,
1048 page,
1049 self.limit(page.limit)?,
1050 )?;
1051 Ok(json!({
1052 "branch": result.selection.branch, "selection": view(&result.selection),
1053 "from": result.from, "parent": view(&result.parent), "lines": result.lines,
1054 "done": result.next_after.is_none(), "next_after": result.next_after,
1055 }))
1056 }
1057
1058 fn collection_page(
1059 &self,
1060 access: &Access,
1061 page: &CollectionPage,
1062 response_byte_limit: Option<NonZeroUsize>,
1063 ) -> Result<Value, Failure> {
1064 let limit = self.limit(page.limit)?;
1065 let at = self.capture(&page.selection, page.after.is_some())?;
1066 let result = if let Some(byte_limit) = response_byte_limit {
1067 reads::collections_bounded(
1068 access,
1069 &self.reader,
1070 &at,
1071 page.after.as_deref(),
1072 limit,
1073 byte_limit,
1074 )?
1075 } else {
1076 let result = reads::collections(&self.reader, &at, page.after.as_deref(), limit)?;
1077 reads::Page {
1078 branch: result.branch,
1079 version: result.version,
1080 entries: reads::render_collections(access, &self.reader, &at, result.entries)?,
1081 next_after: result.next_after,
1082 }
1083 };
1084 Ok(json!({
1085 "selection": view(&at), "entries":result.entries, "next_after":result.next_after,
1086 }))
1087 }
1088
1089 fn document_page(
1090 &self,
1091 access: &Access,
1092 page: &DocumentPage,
1093 response_byte_limit: Option<NonZeroUsize>,
1094 ) -> Result<Value, Failure> {
1095 reads::validate_document_content(page.content.as_deref())?;
1096 reads::validate_document_prefix(page.prefix.as_deref(), page.after.as_deref())?;
1097 let limit = self.limit(page.limit)?;
1098 let at = self.capture(&page.selection, page.after.is_some())?;
1099 let result =
1100 reads::list_documents(access, &self.reader, &at, page, limit, response_byte_limit)?;
1101 Ok(json!({"selection":view(&at), "entries":result.entries, "next_after":result.next_after}))
1102 }
1103
1104 fn store_stats(&self, access: &Access, read: &StoreRead) -> Result<Value, Failure> {
1105 let at = self.capture(&read.selection, false)?;
1106 let stats = reads::stats(access, &self.reader, &at)?;
1107 let mut answer = json!({
1108 "selection":view(&at), "collections":stats.collections,
1109 "embedded_fields":stats.embedded_fields,
1110 "embedded_fields_reason":stats.embedded_fields_reason,
1111 "live_bytes":null, "history_bytes":null, "reclaimable_bytes":null,
1112 });
1113 stats.documents.insert(&mut answer, "documents")?;
1114 stats.edges.insert(&mut answer, "edges")?;
1115 stats.vectors.insert(&mut answer, "vectors")?;
1116 stats
1117 .collection_count
1118 .insert(&mut answer, "collection_count")?;
1119 stats.relation_count.insert(&mut answer, "relation_count")?;
1120 if let Some(object) = answer.as_object_mut() {
1121 let release = self
1122 .releaser
1123 .as_ref()
1124 .map_or_else(|| json!({"state":"not_started"}), Releaser::status);
1125 object.insert("release".to_owned(), release);
1126 object.insert("discard_pass".to_owned(), self.discard_passes.status());
1127 let embedding = self
1128 .embedding
1129 .as_ref()
1130 .map_or_else(|| json!({"state":"not_started"}), Driver::status);
1131 object.insert("embedding".to_owned(), embedding);
1132 }
1133 Ok(answer)
1134 }
1135
1136 fn vector_fields(&self, access: &Access, read: &StoreRead) -> Result<Value, Failure> {
1137 let at = self.capture(&read.selection, false)?;
1138 let mut listing = super::storage::vectors::fields(access, &self.reader, &at)?;
1139 if let Some(driver) = &self.embedding {
1141 listing.statused(|collection, field| super::storage::vectors::FieldState {
1142 status: driver.field_status(&at.branch, collection, field),
1143 longer_than_model: driver.longer_than_model(&at.branch, collection, field),
1144 embed_input_empty: driver.embed_input_empty(&at.branch, collection, field),
1145 windows: driver.windows(&at.branch, collection, field),
1146 failure: driver.field_failure(&at.branch, collection, field),
1147 });
1148 }
1149 Ok(json!({"selection":view(&at), "fields":listing.fields,
1150 "embedded_fields":listing.embedded_fields}))
1151 }
1152
1153 fn edge_page(
1154 &self,
1155 access: &Access,
1156 page: &EdgePage,
1157 incoming: bool,
1158 response_byte_limit: Option<NonZeroUsize>,
1159 ) -> Result<Value, Failure> {
1160 let limit = self.limit(page.limit)?;
1161 let at = self.capture(&page.selection, page.after.is_some())?;
1162 let query = if incoming {
1163 edges::visible_incoming
1164 } else {
1165 edges::visible_out
1166 };
1167 let result = query(
1168 &self.reader,
1169 &at,
1170 &edges::VisibleEdges {
1171 access,
1172 node: &page.node,
1173 relation: page.relation.as_deref(),
1174 after: page.after.as_deref(),
1175 limits: reads::PageLimits {
1176 rows: limit,
1177 bytes: response_limit(response_byte_limit)?,
1178 },
1179 },
1180 )?;
1181 Ok(json!({"selection":view(&at), "edges":result.entries, "next_after":result.next_after}))
1182 }
1183
1184 fn graph_export(
1185 &self,
1186 access: &Access,
1187 page: &GraphPage,
1188 response_byte_limit: Option<NonZeroUsize>,
1189 ) -> Result<Value, Failure> {
1190 let limit = self.limit(page.limit)?;
1191 let at = self.capture(&page.selection, page.after.is_some())?;
1192 if graph::is_filtered(page) {
1193 let result = graph::export(
1194 &self.reader,
1195 &at,
1196 access,
1197 page,
1198 reads::PageLimits {
1199 rows: limit,
1200 bytes: response_limit(response_byte_limit)?,
1201 },
1202 self.max_page,
1203 )?;
1204 return Ok(json!({"selection":view(&at), "edges":result.edges,
1205 "nodes":result.nodes, "next_after":result.next_after,
1206 "examination_ended":result.examination_ended}));
1207 }
1208 let result = edges::visible_export(
1209 &self.reader,
1210 &at,
1211 access,
1212 page.after.as_deref(),
1213 reads::PageLimits {
1214 rows: limit,
1215 bytes: response_limit(response_byte_limit)?,
1216 },
1217 )?;
1218 Ok(json!({"selection":view(&at), "edges":result.entries, "next_after":result.next_after}))
1219 }
1220
1221 fn capture(&self, selection: &Selection, continuation: bool) -> Result<Held, Failure> {
1225 if continuation && selection.version.is_none() {
1226 return Err(Failure::new(
1227 "version_required",
1228 "continuation requires the captured version",
1229 ));
1230 }
1231 self.catalogue.capture_held(selection)
1232 }
1233
1234 fn limit(&self, limit: NonZeroU32) -> Result<NonZeroUsize, Failure> {
1235 if limit > self.max_page {
1236 return Err(Failure::new(
1237 "page_limit",
1238 format!("maximum page size is {}", self.max_page),
1239 ));
1240 }
1241 let value = usize::try_from(limit.get()).map_err(Failure::storage)?;
1242 NonZeroUsize::new(value)
1243 .filter(|limit| limit.get() < usize::MAX)
1244 .ok_or_else(|| Failure::new("page_limit", "page must allow one lookahead entry"))
1245 }
1246}
1247
1248fn response_limit(limit: Option<NonZeroUsize>) -> Result<NonZeroUsize, Failure> {
1249 let bytes = limit.map_or(usize::MAX, NonZeroUsize::get);
1250 NonZeroUsize::new(bytes)
1251 .ok_or_else(|| Failure::new("response_limit", "response byte limit is zero"))
1252}
1253
1254fn running_limit(settings: Option<&super::query::Settings>) -> Result<NonZeroUsize, Failure> {
1255 let running = settings.map_or(1, |settings| settings.max_running.get());
1256 NonZeroUsize::new(running)
1257 .ok_or_else(|| Failure::new("query_limit", "query running limit is zero"))
1258}
1259
1260fn view(at: &Captured) -> Value {
1261 json!({"branch":at.branch, "version":at.version,
1262 "roots":at.shards.iter().map(|shard| json!({"shard":shard.shard_id, "root":shard.head.to_string()})).collect::<Vec<_>>()})
1263}
1264
1265enum Answer {
1268 Value(Value),
1269 Text(Box<RawValue>),
1270}
1271
1272impl Answer {
1273 fn into_value(self) -> Result<Value, Failure> {
1275 match self {
1276 Self::Value(value) => Ok(value),
1277 Self::Text(text) => serde_json::from_str(text.get()).map_err(Failure::storage),
1278 }
1279 }
1280
1281 fn into_text(self) -> Result<Box<RawValue>, Failure> {
1283 match self {
1284 Self::Value(value) => serde_json::value::to_raw_value(&value).map_err(Failure::storage),
1285 Self::Text(text) => Ok(text),
1286 }
1287 }
1288}
1289
1290pub(super) fn fits_serialized(value: &Value, limit: NonZeroUsize) -> Result<bool, Failure> {
1291 let mut output = CappedWriter {
1292 remaining: limit.get(),
1293 exceeded: false,
1294 };
1295 match serde_json::to_writer(&mut output, value) {
1296 Ok(()) => Ok(true),
1297 Err(_) if output.exceeded => Ok(false),
1298 Err(error) => Err(Failure::storage(error)),
1299 }
1300}
1301
1302struct CappedWriter {
1303 remaining: usize,
1304 exceeded: bool,
1305}
1306
1307impl Write for CappedWriter {
1308 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
1309 if bytes.len() > self.remaining {
1310 self.exceeded = true;
1311 return Err(io::Error::other("serialized response exceeds byte limit"));
1312 }
1313 self.remaining -= bytes.len();
1314 Ok(bytes.len())
1315 }
1316
1317 fn flush(&mut self) -> io::Result<()> {
1318 Ok(())
1319 }
1320}
1321
1322fn wake_embedding(driver: &Driver, branch: &str, change: &Change) {
1325 match change {
1326 Change::Put(write) => driver.wrote(branch, &write.collection),
1327 Change::AppendPrune(write) => driver.wrote(branch, &write.collection),
1328 Change::Delete(delete) => driver.wrote(branch, &delete.collection),
1329 Change::DeleteMany(delete) => driver.wrote(branch, &delete.collection),
1330 Change::PutMany(import) => driver.wrote(branch, &import.collection),
1331 Change::VectorDeclare(declare) => {
1332 driver.declared(branch, &declare.collection, &declare.field);
1333 }
1334 _ => {}
1335 }
1336}
1337
1338#[cfg(test)]
1339#[path = "service_tests.rs"]
1340mod tests;
1341
1342#[cfg(test)]
1343#[path = "service_merge_prepare_tests.rs"]
1344mod merge_prepare_tests;
1345
1346#[cfg(test)]
1347#[path = "service_relationship_access_tests.rs"]
1348mod relationship_access_tests;
1349
1350#[cfg(test)]
1351#[path = "service_access_tests.rs"]
1352mod access_tests;
1353
1354#[cfg(test)]
1355#[path = "service_mutation_outcome_tests.rs"]
1356mod mutation_outcome_tests;
1357
1358#[cfg(test)]
1359#[path = "service_held_tests.rs"]
1360mod held_tests;
1361
1362#[cfg(test)]
1363mod count_access_tests {
1364 use super::Service;
1365 use crate::serve::access::TestSource;
1366 use crate::serve::protocol::{CountsRebuild, Operation, Selection};
1367 use crate::serve::storage::Storage;
1368 use haematite::db::DatabaseConfig;
1369 use haematite::store::NodeCacheBudget;
1370 use std::error::Error;
1371 use std::num::NonZeroU32;
1372 use std::sync::Arc;
1373
1374 #[test]
1375 fn whole_store_authority_is_required_without_disclosing_or_rebuilding()
1376 -> Result<(), Box<dyn Error>> {
1377 let directory = tempfile::tempdir()?;
1378 let storage = Storage::create(
1379 DatabaseConfig {
1380 data_dir: directory.path().join("database"),
1381 shard_count: 1,
1382 distributed: None,
1383 executor_threads: Some(1),
1384 node_cache_budget: Some(NodeCacheBudget::Unlimited),
1385 retention: haematite::Retention::KeepAll,
1386 },
1387 NodeCacheBudget::Unlimited,
1388 fixture_long_wait_interval(),
1389 )?;
1390 let before = storage
1391 .capture(&Selection {
1392 branch: "main".to_owned(),
1393 version: None,
1394 })?
1395 .version;
1396 let source = Arc::new(TestSource::denying(&[("records", "one")]));
1397 let service = Service::with_access_source(storage, NonZeroU32::MIN, None, source)?;
1398 let error = service
1399 .dispatch(Operation::CountsRebuild(CountsRebuild {
1400 branch: "main".to_owned(),
1401 request_id: "withheld".to_owned(),
1402 }))
1403 .err()
1404 .ok_or("restricted rebuild was admitted")?;
1405 let error = serde_json::to_value(error)?;
1406 assert_eq!(error["code"], "counts_access_refused");
1407 assert_eq!(error["outcome"], "not_applied");
1408 let answer = service.dispatch(Operation::CountsAudit(Selection {
1409 branch: "main".to_owned(),
1410 version: None,
1411 }))?;
1412 assert_eq!(answer["result"], "cannot_tell");
1413 assert_eq!(answer["reason"], "withheld");
1414 assert!(answer.get("buckets").is_none());
1415 assert_eq!(
1416 answer["selection"]["version"]["sequence"],
1417 before.sequence.to_string()
1418 );
1419 Ok(())
1420 }
1421
1422 const fn fixture_long_wait_interval() -> std::time::Duration {
1423 std::time::Duration::from_secs(1)
1424 }
1425}
1426
1427#[cfg(test)]
1428#[path = "service_writer_wait_tests.rs"]
1429mod writer_wait_tests;