1mod composite;
11mod files;
12#[cfg(test)]
13pub(crate) mod flaky;
14pub mod loose;
15mod midx;
16mod packs;
17
18pub use composite::CompositeStore;
19pub use files::FilesSource;
20pub use loose::LooseStore;
21pub use midx::{MidxObjects, MidxObjectsStatus};
22pub(crate) use packs::touch_pack_mtime;
23pub use packs::{PackFilter, PackedObjects};
24
25use std::io::{self, Cursor, Read};
26use std::ops::ControlFlow;
27use std::sync::{Arc, RwLock};
28
29use std::path::Path;
30
31use crate::error::{Error, Result};
32use crate::hash;
33use crate::objects::{
34 oid_hash_map, HashAlgo, Object, ObjectId, ObjectIdPrefix, ObjectInfo, ObjectKind, OidHashMap,
35};
36use crate::odb::WriteOptions;
37use crate::pack;
38
39type MemoryObjectEntry = (ObjectKind, Arc<[u8]>);
40
41#[derive(Debug)]
43pub(crate) enum BoundedRead {
44 Object(Object),
46 Larger,
48}
49
50#[derive(Debug)]
52pub(crate) enum LocalSizedRead {
53 Packed(Option<u64>),
56 Loose(BoundedRead),
58 Missing,
60}
61
62pub struct ObjectStream<'a> {
66 pub kind: ObjectKind,
68 pub size: u64,
70 pub reader: Box<dyn Read + Send + 'a>,
72}
73
74pub trait ObjectStore: Send + Sync + std::fmt::Debug {
85 fn hash_algo(&self) -> HashAlgo;
87
88 fn read(&self, oid: &ObjectId) -> Result<Option<Object>>;
94
95 fn read_into(&self, oid: &ObjectId, buf: &mut Vec<u8>) -> Result<Option<ObjectKind>> {
107 let Some(obj) = self.read(oid)? else {
108 return Ok(None);
109 };
110 buf.clear();
111 buf.extend_from_slice(&obj.data);
112 Ok(Some(obj.kind))
113 }
114
115 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>>;
121
122 fn contains(&self, oid: &ObjectId) -> Result<bool> {
128 Ok(self.read_info(oid)?.is_some())
129 }
130
131 fn contains_many(&self, oids: &[ObjectId]) -> Result<Vec<bool>> {
144 oids.iter().map(|oid| self.contains(oid)).collect()
145 }
146
147 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
155 let Some(obj) = self.read(oid)? else {
156 return Ok(None);
157 };
158 let size = u64::try_from(obj.data.len()).map_err(|_| {
159 Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
160 })?;
161 Ok(Some(ObjectStream {
162 kind: obj.kind,
163 size,
164 reader: Box::new(Cursor::new(obj.data)),
165 }))
166 }
167
168 fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()>;
174
175 fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
193 let prefix = ObjectIdPrefix::from_hex(prefix, self.hash_algo())?;
194 let mut count = 0usize;
195 self.for_each_object(&mut |oid| {
196 if prefix.matches(oid) {
197 out.push(*oid);
198 count += 1;
199 if limit != 0 && count >= limit {
200 return ControlFlow::Break(());
201 }
202 }
203 ControlFlow::Continue(())
204 })
205 }
206
207 fn refresh(&self) -> Result<bool> {
215 Ok(false)
216 }
217}
218
219pub trait WritableObjectStore: ObjectStore {
221 fn write(&self, kind: ObjectKind, data: &[u8], options: WriteOptions) -> Result<ObjectId>;
230
231 fn write_stream(
239 &self,
240 kind: ObjectKind,
241 size: u64,
242 reader: &mut dyn Read,
243 options: WriteOptions,
244 ) -> Result<ObjectId> {
245 let size_usize = usize::try_from(size).map_err(|_| {
246 Error::CorruptObject(format!("object size {size} exceeds address space"))
247 })?;
248 let mut buf = vec![0u8; size_usize];
249 reader.read_exact(&mut buf).map_err(Error::Io)?;
250 self.write(kind, &buf, options)
251 }
252
253 fn freshen(&self, oid: &ObjectId) -> Result<bool>;
261}
262
263macro_rules! forward_object_store {
268 ($pointer:ty) => {
269 impl<T: ObjectStore + ?Sized> ObjectStore for $pointer {
270 fn hash_algo(&self) -> HashAlgo {
271 (**self).hash_algo()
272 }
273
274 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
275 (**self).read(oid)
276 }
277
278 fn read_into(&self, oid: &ObjectId, buf: &mut Vec<u8>) -> Result<Option<ObjectKind>> {
279 (**self).read_into(oid, buf)
280 }
281
282 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
283 (**self).read_info(oid)
284 }
285
286 fn contains(&self, oid: &ObjectId) -> Result<bool> {
287 (**self).contains(oid)
288 }
289
290 fn contains_many(&self, oids: &[ObjectId]) -> Result<Vec<bool>> {
291 (**self).contains_many(oids)
292 }
293
294 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
295 (**self).open_stream(oid)
296 }
297
298 fn for_each_object(
299 &self,
300 f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>,
301 ) -> Result<()> {
302 (**self).for_each_object(f)
303 }
304
305 fn lookup_prefix(
306 &self,
307 prefix: &str,
308 limit: usize,
309 out: &mut Vec<ObjectId>,
310 ) -> Result<()> {
311 (**self).lookup_prefix(prefix, limit, out)
312 }
313
314 fn refresh(&self) -> Result<bool> {
315 (**self).refresh()
316 }
317 }
318
319 impl<T: WritableObjectStore + ?Sized> WritableObjectStore for $pointer {
320 fn write(
321 &self,
322 kind: ObjectKind,
323 data: &[u8],
324 options: WriteOptions,
325 ) -> Result<ObjectId> {
326 (**self).write(kind, data, options)
327 }
328
329 fn write_stream(
330 &self,
331 kind: ObjectKind,
332 size: u64,
333 reader: &mut dyn Read,
334 options: WriteOptions,
335 ) -> Result<ObjectId> {
336 (**self).write_stream(kind, size, reader, options)
337 }
338
339 fn freshen(&self, oid: &ObjectId) -> Result<bool> {
340 (**self).freshen(oid)
341 }
342 }
343 };
344}
345
346forward_object_store!(Arc<T>);
347forward_object_store!(Box<T>);
348forward_object_store!(&T);
349
350pub(crate) fn local_object_materialized(objects_dir: &Path, oid: &ObjectId) -> bool {
352 if local_object_materialized_without_rescan(objects_dir, oid) {
353 return true;
354 }
355 if pack::reprepare_pack_directory_on_miss(objects_dir).ok() == Some(true) {
356 return object_in_local_packs(objects_dir, oid);
357 }
358 false
359}
360
361pub(crate) fn local_object_materialized_without_rescan(objects_dir: &Path, oid: &ObjectId) -> bool {
364 object_in_local_packs(objects_dir, oid) || oid.loose_path_in(objects_dir).is_file()
365}
366
367fn object_in_local_packs(objects_dir: &Path, oid: &ObjectId) -> bool {
368 object_in_non_promisor_pack(objects_dir, oid)
369}
370
371pub(crate) fn object_in_non_promisor_pack(objects_dir: &Path, oid: &ObjectId) -> bool {
373 let Ok(indexes) = pack::read_local_pack_indexes_cached(objects_dir) else {
374 return false;
375 };
376 for idx in &indexes {
377 if idx.is_promisor {
378 continue;
379 }
380 if idx.contains(oid).unwrap_or(false) {
381 return true;
382 }
383 }
384 false
385}
386
387pub(crate) fn local_object_stored_without_rescan(objects_dir: &Path, oid: &ObjectId) -> bool {
392 let Ok(indexes) = pack::read_local_pack_indexes_cached(objects_dir) else {
393 return oid.loose_path_in(objects_dir).is_file();
394 };
395 for idx in &indexes {
396 if idx.contains(oid).unwrap_or(false) {
397 return true;
398 }
399 }
400 oid.loose_path_in(objects_dir).is_file()
401}
402
403pub(crate) fn local_object_stored(objects_dir: &Path, oid: &ObjectId) -> bool {
405 if local_object_stored_without_rescan(objects_dir, oid) {
406 return true;
407 }
408 if pack::reprepare_pack_directory_on_miss(objects_dir).ok() == Some(true) {
409 return local_object_stored_without_rescan(objects_dir, oid);
410 }
411 false
412}
413
414#[derive(Debug)]
418pub struct MemoryStore {
419 algo: HashAlgo,
420 objects: RwLock<OidHashMap<MemoryObjectEntry>>,
421}
422
423impl MemoryStore {
424 #[must_use]
426 pub fn new(algo: HashAlgo) -> Self {
427 Self {
428 algo,
429 objects: RwLock::new(oid_hash_map()),
430 }
431 }
432
433 pub(crate) fn insert_object(&self, oid: ObjectId, kind: ObjectKind, data: Arc<[u8]>) {
435 if let Ok(mut guard) = self.objects.write() {
436 guard.entry(oid).or_insert((kind, data));
437 }
438 }
439}
440
441impl ObjectStore for MemoryStore {
442 fn hash_algo(&self) -> HashAlgo {
443 self.algo
444 }
445
446 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
447 let guard = self
448 .objects
449 .read()
450 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
451 Ok(guard.get(oid).map(|(kind, data)| Object {
452 kind: *kind,
453 data: data.to_vec(),
454 }))
455 }
456
457 fn read_into(&self, oid: &ObjectId, buf: &mut Vec<u8>) -> Result<Option<ObjectKind>> {
458 let guard = self
459 .objects
460 .read()
461 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
462 Ok(guard.get(oid).map(|(kind, data)| {
463 buf.clear();
464 buf.extend_from_slice(data);
465 *kind
466 }))
467 }
468
469 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
470 let guard = self
471 .objects
472 .read()
473 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
474 Ok(guard.get(oid).map(|(kind, data)| ObjectInfo {
475 kind: *kind,
476 size: u64::try_from(data.len()).unwrap_or(u64::MAX),
477 }))
478 }
479
480 fn contains(&self, oid: &ObjectId) -> Result<bool> {
481 let guard = self
482 .objects
483 .read()
484 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
485 Ok(guard.contains_key(oid))
486 }
487
488 fn contains_many(&self, oids: &[ObjectId]) -> Result<Vec<bool>> {
489 let guard = self
490 .objects
491 .read()
492 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
493 Ok(oids.iter().map(|oid| guard.contains_key(oid)).collect())
494 }
495
496 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
497 let guard = self
498 .objects
499 .read()
500 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
501 let Some((kind, data)) = guard.get(oid) else {
502 return Ok(None);
503 };
504 let size = u64::try_from(data.len()).map_err(|_| {
505 Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
506 })?;
507 Ok(Some(ObjectStream {
508 kind: *kind,
509 size,
510 reader: Box::new(Cursor::new(Arc::clone(data))),
511 }))
512 }
513
514 fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()> {
515 let guard = self
516 .objects
517 .read()
518 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
519 for oid in guard.keys() {
520 if f(oid).is_break() {
521 break;
522 }
523 }
524 Ok(())
525 }
526
527 fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
528 let prefix = ObjectIdPrefix::from_hex(prefix, self.hash_algo())?;
529 let guard = self
530 .objects
531 .read()
532 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
533 let matches = guard.keys().filter(|oid| prefix.matches(oid)).copied();
534 if limit == 0 {
535 out.extend(matches);
536 } else {
537 out.extend(matches.take(limit));
538 }
539 Ok(())
540 }
541}
542
543impl WritableObjectStore for MemoryStore {
544 fn write(&self, kind: ObjectKind, data: &[u8], _options: WriteOptions) -> Result<ObjectId> {
545 let oid = hash::hash_object(self.algo, kind, data)?;
546 let mut guard = self
547 .objects
548 .write()
549 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
550 guard
551 .entry(oid)
552 .or_insert_with(|| (kind, Arc::from(data.to_owned())));
553 Ok(oid)
554 }
555
556 fn freshen(&self, oid: &ObjectId) -> Result<bool> {
557 let guard = self
558 .objects
559 .read()
560 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
561 Ok(guard.contains_key(oid))
562 }
563}
564
565pub(crate) fn resolve_pending(
570 oids: &[ObjectId],
571 present: &mut [bool],
572 pending: &mut Vec<usize>,
573 probe: impl FnOnce(&[ObjectId]) -> Result<Vec<bool>>,
574) -> Result<()> {
575 if pending.is_empty() {
576 return Ok(());
577 }
578 let batch: Vec<ObjectId> = pending.iter().map(|&i| oids[i]).collect();
579 let mut found = probe(&batch)?.into_iter();
580 pending.retain(|&i| {
581 let hit = found.next().unwrap_or(false);
582 present[i] |= hit;
583 !hit
584 });
585 Ok(())
586}
587
588pub(crate) fn for_each_propagate_break(
590 store: &dyn ObjectStore,
591 f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>,
592) -> Result<bool> {
593 let mut stop = false;
594 store.for_each_object(&mut |oid| {
595 if stop {
596 return ControlFlow::Break(());
597 }
598 match f(oid) {
599 ControlFlow::Break(()) => {
600 stop = true;
601 ControlFlow::Break(())
602 }
603 ControlFlow::Continue(()) => ControlFlow::Continue(()),
604 }
605 })?;
606 Ok(stop)
607}
608
609#[cfg(test)]
610mod tests {
611 use super::*;
612 use std::io::Read;
613
614 #[test]
615 fn read_hit_and_miss() {
616 let store = MemoryStore::new(HashAlgo::Sha1);
617 let data = b"hello";
618 let oid = store
619 .write(ObjectKind::Blob, data, WriteOptions::default())
620 .unwrap();
621 let obj = store.read(&oid).unwrap().expect("hit");
622 assert_eq!(obj.kind, ObjectKind::Blob);
623 assert_eq!(obj.data, data);
624 assert!(store.read(&ObjectId::zero()).unwrap().is_none());
625 let mut buf = b"stale bytes".to_vec();
626 assert_eq!(
627 store.read_into(&oid, &mut buf).unwrap(),
628 Some(ObjectKind::Blob)
629 );
630 assert_eq!(buf, data);
631 assert_eq!(store.read_into(&ObjectId::zero(), &mut buf).unwrap(), None);
632 assert_eq!(buf, data, "a miss leaves the buffer alone");
633 assert!(store.read_info(&ObjectId::zero()).unwrap().is_none());
634 assert!(!store.contains(&ObjectId::zero()).unwrap());
635 assert!(store.contains(&oid).unwrap());
636 assert_eq!(
637 store.contains_many(&[oid, ObjectId::zero(), oid]).unwrap(),
638 vec![true, false, true]
639 );
640 assert!(store.contains_many(&[]).unwrap().is_empty());
641 }
642
643 #[test]
644 fn resolve_pending_marks_hits_and_keeps_misses() {
645 let ids: Vec<ObjectId> = (1u8..=3)
646 .map(|b| ObjectId::from_bytes(&[b; 20]).unwrap())
647 .collect();
648 let mut present = vec![false; 3];
649 let mut pending = vec![0, 2];
650 resolve_pending(&ids, &mut present, &mut pending, |batch| {
651 assert_eq!(batch, [ids[0], ids[2]]);
652 Ok(vec![false, true])
653 })
654 .unwrap();
655 assert_eq!(present, [false, false, true]);
656 assert_eq!(pending, [0]);
657
658 resolve_pending(&ids, &mut present, &mut pending, |_| Ok(Vec::new())).unwrap();
659 assert_eq!(pending, [0], "a short answer counts as absent");
660
661 pending.clear();
662 resolve_pending(&ids, &mut present, &mut pending, |_| {
663 panic!("no probe when nothing is pending")
664 })
665 .unwrap();
666 }
667
668 #[test]
669 fn stream_four_mib_blob_in_sixty_four_kib_chunks() {
670 let store = MemoryStore::new(HashAlgo::Sha1);
671 let payload = vec![0xABu8; 4 * 1024 * 1024];
672 let oid = store
673 .write(ObjectKind::Blob, &payload, WriteOptions::default())
674 .unwrap();
675 let mut stream = store.open_stream(&oid).unwrap().expect("stream");
676 assert_eq!(stream.kind, ObjectKind::Blob);
677 assert_eq!(stream.size, payload.len() as u64);
678 let mut out = Vec::with_capacity(payload.len());
679 let mut chunk = [0u8; 64 * 1024];
680 loop {
681 let n = stream.reader.read(&mut chunk).unwrap();
682 if n == 0 {
683 break;
684 }
685 out.extend_from_slice(&chunk[..n]);
686 }
687 assert_eq!(out, payload);
688 }
689
690 #[test]
691 fn for_each_object_stops_on_break() {
692 let store = MemoryStore::new(HashAlgo::Sha1);
693 for i in 0u8..5 {
694 store
695 .write(ObjectKind::Blob, &[i], WriteOptions::default())
696 .unwrap();
697 }
698 let mut seen = 0usize;
699 store
700 .for_each_object(&mut |_| {
701 seen += 1;
702 ControlFlow::Break(())
703 })
704 .unwrap();
705 assert_eq!(seen, 1);
706 }
707
708 #[test]
709 fn lookup_prefix_unique_ambiguous_and_none() {
710 let store = MemoryStore::new(HashAlgo::Sha1);
711 let a = store
712 .write(ObjectKind::Blob, b"a", WriteOptions::default())
713 .unwrap();
714 let b = store
715 .write(ObjectKind::Blob, b"b", WriteOptions::default())
716 .unwrap();
717 let hex_a = a.to_hex();
718 let unique_prefix = &hex_a[..12];
719 let mut out = Vec::new();
720 store.lookup_prefix(unique_prefix, 0, &mut out).unwrap();
721 assert_eq!(out, vec![a]);
722
723 let mut ambiguous = Vec::new();
724 store.lookup_prefix("", 2, &mut ambiguous).unwrap();
725 assert_eq!(ambiguous.len(), 2);
726 assert!(ambiguous.contains(&a));
727 assert!(ambiguous.contains(&b));
728
729 let mut none = Vec::new();
730 store
731 .lookup_prefix("ffffffffffffffffffffffffffffffffffffffff", 0, &mut none)
732 .unwrap();
733 assert!(none.is_empty());
734 }
735
736 #[test]
737 fn lookup_prefix_odd_even_empty_and_full_length() {
738 let store = MemoryStore::new(HashAlgo::Sha1);
739 let ids: Vec<ObjectId> = [
740 "abcdef0123000000000000000000000000000000",
741 "abcdef0124000000000000000000000000000000",
742 "abd0000000000000000000000000000000000000",
743 ]
744 .iter()
745 .map(|hex| ObjectId::from_hex(hex).unwrap())
746 .collect();
747 for id in &ids {
748 store.insert_object(*id, ObjectKind::Blob, Arc::from(Vec::new()));
749 }
750 let lookup = |prefix: &str, limit: usize| {
751 let mut out = Vec::new();
752 store.lookup_prefix(prefix, limit, &mut out).unwrap();
753 out.sort();
754 out
755 };
756 assert_eq!(lookup("", 0), ids);
757 assert_eq!(lookup("ab", 1).len(), 1);
758 assert_eq!(lookup("abc", 0), ids[..2]);
759 assert_eq!(lookup("abcdef012", 0), ids[..2]);
760 assert_eq!(lookup("ABCDEF0124", 0), ids[1..2]);
761 assert_eq!(lookup(&ids[2].to_hex(), 0), ids[2..]);
762 assert!(lookup("abe", 0).is_empty());
763 let mut out = Vec::new();
764 assert!(matches!(
765 store.lookup_prefix("xyz", 0, &mut out),
766 Err(Error::InvalidObjectId(_))
767 ));
768 }
769
770 #[test]
771 fn write_idempotent() {
772 let store = MemoryStore::new(HashAlgo::Sha1);
773 let data = b"same";
774 let oid1 = store
775 .write(ObjectKind::Blob, data, WriteOptions::default())
776 .unwrap();
777 let oid2 = store
778 .write(ObjectKind::Blob, data, WriteOptions::default())
779 .unwrap();
780 assert_eq!(oid1, oid2);
781 let guard = store.objects.read().unwrap();
782 assert_eq!(guard.len(), 1);
783 }
784
785 #[test]
786 fn write_stream_default_and_freshen() {
787 let store = MemoryStore::new(HashAlgo::Sha1);
788 let payload = b"streamed";
789 let mut cursor = Cursor::new(*payload);
790 let oid = store
791 .write_stream(
792 ObjectKind::Blob,
793 payload.len() as u64,
794 &mut cursor,
795 WriteOptions::default(),
796 )
797 .unwrap();
798 assert_eq!(
799 store.read(&oid).unwrap().expect("written").data,
800 payload.as_slice()
801 );
802 assert!(store.freshen(&oid).unwrap());
803 assert!(!store.freshen(&ObjectId::zero()).unwrap());
804 }
805
806 #[test]
807 fn refresh_default_false() {
808 let store = MemoryStore::new(HashAlgo::Sha1);
809 assert!(!store.refresh().unwrap());
810 }
811
812 #[test]
813 fn trait_defaults_via_blanket_test_store() {
814 struct DefaultsOnly {
815 inner: MemoryStore,
816 }
817
818 impl std::fmt::Debug for DefaultsOnly {
819 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
820 self.inner.fmt(f)
821 }
822 }
823
824 impl ObjectStore for DefaultsOnly {
825 fn hash_algo(&self) -> HashAlgo {
826 self.inner.hash_algo()
827 }
828
829 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
830 self.inner.read(oid)
831 }
832
833 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
834 self.inner.read_info(oid)
835 }
836
837 fn for_each_object(
838 &self,
839 f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>,
840 ) -> Result<()> {
841 self.inner.for_each_object(f)
842 }
843 }
844
845 let store = DefaultsOnly {
846 inner: MemoryStore::new(HashAlgo::Sha1),
847 };
848 let oid = WritableObjectStore::write(
849 &store.inner,
850 ObjectKind::Blob,
851 b"x",
852 WriteOptions::default(),
853 )
854 .unwrap();
855 assert!(ObjectStore::contains(&store, &oid).unwrap());
856 assert_eq!(
857 ObjectStore::contains_many(&store, &[ObjectId::zero(), oid]).unwrap(),
858 vec![false, true]
859 );
860 assert!(ObjectStore::open_stream(&store, &oid).unwrap().is_some());
861 let mut prefixed = Vec::new();
862 ObjectStore::lookup_prefix(&store, &oid.to_hex()[..6], 0, &mut prefixed).unwrap();
863 assert_eq!(prefixed, vec![oid]);
864 }
865}