1use std::collections::HashMap;
9use std::sync::Arc;
10
11use arc_swap::ArcSwap;
12use dashmap::DashMap;
13use parking_lot::{Mutex, RwLock};
14use smol_str::SmolStr;
15
16use crate::capability::CapabilitySet;
17use crate::errors::PluginError;
18use crate::plugin::PluginId;
19use crate::qname::QName;
20use crate::traits::aggregate::{AggSignature, AggregatePluginFn};
21use crate::traits::algorithm::AlgorithmProvider;
22use crate::traits::background::BackgroundJobProvider;
23use crate::traits::catalog::{CatalogProvider, ReplacementScanProvider};
24use crate::traits::cdc::CdcOutputProvider;
25use crate::traits::collation::CollationProvider;
26use crate::traits::connector::{AuthProvider, AuthzPolicy};
27use crate::traits::crdt::{CrdtKind, CrdtKindProvider};
28use crate::traits::hook::SessionHook;
29use crate::traits::index::{IndexHandle, IndexKind, IndexKindProvider};
30use crate::traits::locy::{
31 GenSignature, LocyAggregate, LocyGenerator, LocyPredicate, PredSignature,
32};
33use crate::traits::operator::OptimizerRuleProvider;
34use crate::traits::procedure::{ProcedurePlugin, ProcedureSignature};
35use crate::traits::scalar::{FnSignature, ScalarPluginFn};
36use crate::traits::trigger::TriggerPlugin;
37use crate::traits::types::LogicalTypeProvider;
38use crate::traits::window::{WindowPluginFn, WindowSignature};
39
40pub struct ScalarEntry {
42 pub plugin: PluginId,
44 pub signature: FnSignature,
46 pub function: Arc<dyn ScalarPluginFn>,
48}
49
50impl std::fmt::Debug for ScalarEntry {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("ScalarEntry")
53 .field("plugin", &self.plugin)
54 .field("signature", &self.signature)
55 .finish_non_exhaustive()
56 }
57}
58
59pub struct AggregateEntry {
61 pub plugin: PluginId,
63 pub signature: AggSignature,
65 pub aggregate: Arc<dyn AggregatePluginFn>,
67}
68
69impl std::fmt::Debug for AggregateEntry {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.debug_struct("AggregateEntry")
72 .field("plugin", &self.plugin)
73 .field("signature", &self.signature)
74 .finish_non_exhaustive()
75 }
76}
77
78pub struct WindowEntry {
80 pub plugin: PluginId,
82 pub signature: WindowSignature,
84 pub window: Arc<dyn WindowPluginFn>,
86}
87
88impl std::fmt::Debug for WindowEntry {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 f.debug_struct("WindowEntry")
91 .field("plugin", &self.plugin)
92 .field("signature", &self.signature)
93 .finish_non_exhaustive()
94 }
95}
96
97pub struct AlgorithmEntry {
103 pub plugin: PluginId,
105 pub effective_caps: CapabilitySet,
107 pub provider: Arc<dyn AlgorithmProvider>,
109}
110
111impl std::fmt::Debug for AlgorithmEntry {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 f.debug_struct("AlgorithmEntry")
114 .field("plugin", &self.plugin)
115 .field("effective_caps", &self.effective_caps)
116 .finish_non_exhaustive()
117 }
118}
119
120pub struct ProcedureEntry {
122 pub plugin: PluginId,
124 pub signature: ProcedureSignature,
126 pub procedure: Arc<dyn ProcedurePlugin>,
128}
129
130impl std::fmt::Debug for ProcedureEntry {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.debug_struct("ProcedureEntry")
133 .field("plugin", &self.plugin)
134 .field("signature", &self.signature)
135 .finish_non_exhaustive()
136 }
137}
138
139pub struct LocyAggregateEntry {
141 pub plugin: PluginId,
143 pub aggregate: Arc<dyn LocyAggregate>,
145}
146
147impl std::fmt::Debug for LocyAggregateEntry {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 f.debug_struct("LocyAggregateEntry")
150 .field("plugin", &self.plugin)
151 .finish_non_exhaustive()
152 }
153}
154
155pub struct LocyPredicateEntry {
157 pub plugin: PluginId,
159 pub signature: PredSignature,
161 pub predicate: Arc<dyn LocyPredicate>,
163}
164
165impl std::fmt::Debug for LocyPredicateEntry {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 f.debug_struct("LocyPredicateEntry")
168 .field("plugin", &self.plugin)
169 .field("signature", &self.signature)
170 .finish_non_exhaustive()
171 }
172}
173
174pub struct LocyGeneratorEntry {
176 pub plugin: PluginId,
178 pub signature: GenSignature,
180 pub generator: Arc<dyn LocyGenerator>,
182}
183
184impl std::fmt::Debug for LocyGeneratorEntry {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 f.debug_struct("LocyGeneratorEntry")
187 .field("plugin", &self.plugin)
188 .field("signature", &self.signature)
189 .finish_non_exhaustive()
190 }
191}
192
193#[derive(Clone)]
208pub struct IndexHandleEntry {
209 pub kind: IndexKind,
212 pub handle: Arc<dyn IndexHandle>,
214}
215
216impl std::fmt::Debug for IndexHandleEntry {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 f.debug_struct("IndexHandleEntry")
219 .field("kind", &self.kind)
220 .finish_non_exhaustive()
221 }
222}
223
224#[derive(Clone)]
232pub struct VirtualEntry {
233 pub name: SmolStr,
235 pub table: Arc<dyn crate::traits::catalog::CatalogTable>,
237}
238
239impl std::fmt::Debug for VirtualEntry {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 f.debug_struct("VirtualEntry")
242 .field("name", &self.name)
243 .finish_non_exhaustive()
244 }
245}
246
247trait VirtualId:
252 Copy + Eq + Ord + std::hash::Hash + std::fmt::Debug + std::fmt::LowerHex + 'static
253{
254 const START: Self;
256 const SENTINEL: Self;
259 const KIND_LABEL: &'static str;
262
263 fn next(self) -> Self;
266}
267
268impl VirtualId for u16 {
269 const START: Self = uni_common::core::schema::VIRTUAL_LABEL_ID_START;
270 const SENTINEL: Self = uni_common::core::schema::VIRTUAL_LABEL_ID_SENTINEL;
271 const KIND_LABEL: &'static str = "label";
272
273 fn next(self) -> Self {
274 self.saturating_add(1)
275 }
276}
277
278impl VirtualId for u32 {
279 const START: Self = uni_common::core::edge_type::VIRTUAL_EDGE_TYPE_ID_START;
280 const SENTINEL: Self = uni_common::core::edge_type::VIRTUAL_EDGE_TYPE_ID_SENTINEL;
281 const KIND_LABEL: &'static str = "edge-type";
282
283 fn next(self) -> Self {
284 self.saturating_add(1)
285 }
286}
287
288#[derive(Debug)]
293struct VirtualIdSpace<Id: VirtualId> {
294 name_to_id: HashMap<SmolStr, Id>,
295 id_to_entry: HashMap<Id, VirtualEntry>,
296 next_id: Id,
297}
298
299impl<Id: VirtualId> Default for VirtualIdSpace<Id> {
300 fn default() -> Self {
301 Self {
302 name_to_id: HashMap::new(),
303 id_to_entry: HashMap::new(),
304 next_id: Id::START,
305 }
306 }
307}
308
309impl<Id: VirtualId> VirtualIdSpace<Id> {
310 fn register(
314 &mut self,
315 name: SmolStr,
316 table: Arc<dyn crate::traits::catalog::CatalogTable>,
317 ) -> Result<Id, PluginError> {
318 if let Some(&id) = self.name_to_id.get(&name) {
319 self.id_to_entry.insert(
320 id,
321 VirtualEntry {
322 name: name.clone(),
323 table,
324 },
325 );
326 return Ok(id);
327 }
328 if self.next_id >= Id::SENTINEL {
329 return Err(PluginError::Internal(format!(
330 "virtual {}-ID space exhausted ({} slots taken; sentinel {:#x})",
331 Id::KIND_LABEL,
332 self.id_to_entry.len(),
333 Id::SENTINEL,
334 )));
335 }
336 let id = self.next_id;
337 self.next_id = self.next_id.next();
338 self.name_to_id.insert(name.clone(), id);
339 self.id_to_entry.insert(id, VirtualEntry { name, table });
340 Ok(id)
341 }
342}
343
344#[derive(Default, Debug)]
351pub(crate) struct PluginRecord {
352 pub(crate) scalars: Vec<QName>,
353 pub(crate) aggregates: Vec<QName>,
354 pub(crate) windows: Vec<QName>,
355 pub(crate) procedures: Vec<(QName, usize)>,
360 pub(crate) locy_aggregates: Vec<QName>,
361 pub(crate) locy_predicates: Vec<QName>,
362 pub(crate) locy_generators: Vec<QName>,
363 pub(crate) algorithms: Vec<QName>,
364 pub(crate) index_kinds: Vec<IndexKind>,
365 pub(crate) label_storages: Vec<SmolStr>,
366 pub(crate) crdt_kinds: Vec<CrdtKind>,
367 pub(crate) logical_types: Vec<SmolStr>,
371 pub(crate) collations: Vec<SmolStr>,
373 pub(crate) cdc_outputs: Vec<SmolStr>,
375 pub(crate) catalogs: Vec<SmolStr>,
377 pub(crate) hook_count: usize,
378 pub(crate) auth_count: usize,
379 pub(crate) authz_count: usize,
380 pub(crate) trigger_count: usize,
381 pub(crate) replacement_scan_count: usize,
382 pub(crate) optimizer_rule_count: usize,
383 pub(crate) background_job_count: usize,
384}
385
386impl PluginRecord {
387 fn merge(&mut self, other: PluginRecord) {
396 self.scalars.extend(other.scalars);
397 self.aggregates.extend(other.aggregates);
398 self.windows.extend(other.windows);
399 self.procedures.extend(other.procedures);
400 self.locy_aggregates.extend(other.locy_aggregates);
401 self.locy_predicates.extend(other.locy_predicates);
402 self.locy_generators.extend(other.locy_generators);
403 self.algorithms.extend(other.algorithms);
404 self.index_kinds.extend(other.index_kinds);
405 self.label_storages.extend(other.label_storages);
406 self.crdt_kinds.extend(other.crdt_kinds);
407 self.logical_types.extend(other.logical_types);
408 self.collations.extend(other.collations);
409 self.cdc_outputs.extend(other.cdc_outputs);
410 self.catalogs.extend(other.catalogs);
411 self.hook_count += other.hook_count;
412 self.auth_count += other.auth_count;
413 self.authz_count += other.authz_count;
414 self.trigger_count += other.trigger_count;
415 self.replacement_scan_count += other.replacement_scan_count;
416 self.optimizer_rule_count += other.optimizer_rule_count;
417 self.background_job_count += other.background_job_count;
418 }
419}
420
421#[derive(Clone, Debug, Default)]
428pub struct PluginRecordSnapshot {
429 pub scalars: Vec<QName>,
431 pub aggregates: Vec<QName>,
433 pub windows: Vec<QName>,
435 pub procedures: Vec<(QName, usize)>,
437 pub locy_aggregates: Vec<QName>,
439 pub locy_predicates: Vec<QName>,
441 pub locy_generators: Vec<QName>,
443 pub algorithms: Vec<QName>,
445 pub index_kinds: Vec<IndexKind>,
447 pub label_storages: Vec<SmolStr>,
449 pub crdt_kinds: Vec<CrdtKind>,
451 pub logical_types: Vec<SmolStr>,
453 pub collations: Vec<SmolStr>,
455 pub cdc_outputs: Vec<SmolStr>,
457 pub catalogs: Vec<SmolStr>,
459 pub hook_count: usize,
461 pub auth_count: usize,
463 pub authz_count: usize,
465 pub trigger_count: usize,
467 pub replacement_scan_count: usize,
469 pub optimizer_rule_count: usize,
471 pub background_job_count: usize,
473}
474
475impl From<&PluginRecord> for PluginRecordSnapshot {
476 fn from(r: &PluginRecord) -> Self {
480 Self {
481 scalars: r.scalars.clone(),
482 aggregates: r.aggregates.clone(),
483 windows: r.windows.clone(),
484 procedures: r.procedures.clone(),
485 locy_aggregates: r.locy_aggregates.clone(),
486 locy_predicates: r.locy_predicates.clone(),
487 locy_generators: r.locy_generators.clone(),
488 algorithms: r.algorithms.clone(),
489 index_kinds: r.index_kinds.clone(),
490 label_storages: r.label_storages.clone(),
491 crdt_kinds: r.crdt_kinds.clone(),
492 logical_types: r.logical_types.clone(),
493 collations: r.collations.clone(),
494 cdc_outputs: r.cdc_outputs.clone(),
495 catalogs: r.catalogs.clone(),
496 hook_count: r.hook_count,
497 auth_count: r.auth_count,
498 authz_count: r.authz_count,
499 trigger_count: r.trigger_count,
500 replacement_scan_count: r.replacement_scan_count,
501 optimizer_rule_count: r.optimizer_rule_count,
502 background_job_count: r.background_job_count,
503 }
504 }
505}
506
507#[derive(Default)]
513pub struct PluginRegistry {
514 pub(crate) scalars: DashMap<QName, Arc<ScalarEntry>>,
515 pub(crate) aggregates: DashMap<QName, Arc<AggregateEntry>>,
516 pub(crate) windows: DashMap<QName, Arc<WindowEntry>>,
517 pub(crate) procedures: DashMap<QName, Vec<Arc<ProcedureEntry>>>,
524 pub(crate) locy_aggregates: DashMap<QName, Arc<LocyAggregateEntry>>,
525 pub(crate) locy_predicates: DashMap<QName, Arc<LocyPredicateEntry>>,
526 pub(crate) locy_generators: DashMap<QName, Arc<LocyGeneratorEntry>>,
527 pub(crate) optimizer_rules:
528 ArcSwap<Vec<crate::surfaces::AppendEntry<dyn OptimizerRuleProvider>>>,
529 pub(crate) algorithms: DashMap<QName, Arc<AlgorithmEntry>>,
530 pub(crate) index_kinds: DashMap<IndexKind, Arc<dyn IndexKindProvider>>,
531 index_handles: DashMap<SmolStr, IndexHandleEntry>,
532 pub(crate) label_storages: DashMap<SmolStr, Arc<dyn crate::traits::storage::Storage>>,
538 pub(crate) crdt_kinds: DashMap<CrdtKind, Arc<dyn CrdtKindProvider>>,
539 pub(crate) hooks: ArcSwap<Vec<crate::surfaces::AppendEntry<dyn SessionHook>>>,
540 pub(crate) logical_types: DashMap<SmolStr, Arc<dyn LogicalTypeProvider>>,
541 pub(crate) auth_providers: ArcSwap<Vec<crate::surfaces::AppendEntry<dyn AuthProvider>>>,
542 pub(crate) authz_policies: ArcSwap<Vec<crate::surfaces::AppendEntry<dyn AuthzPolicy>>>,
543 pub(crate) triggers: ArcSwap<Vec<crate::surfaces::AppendEntry<dyn TriggerPlugin>>>,
544 pub(crate) collations: DashMap<SmolStr, Arc<dyn CollationProvider>>,
545 pub(crate) cdc_outputs: DashMap<SmolStr, Arc<dyn CdcOutputProvider>>,
546 pub(crate) catalogs: DashMap<SmolStr, Arc<dyn CatalogProvider>>,
547 pub(crate) replacement_scans:
548 ArcSwap<Vec<crate::surfaces::AppendEntry<dyn ReplacementScanProvider>>>,
549 pub(crate) background_jobs:
550 ArcSwap<Vec<crate::surfaces::AppendEntry<dyn BackgroundJobProvider>>>,
551 virtual_labels: Mutex<VirtualIdSpace<u16>>,
557 virtual_edge_types: Mutex<VirtualIdSpace<u32>>,
561 per_plugin: RwLock<dashmap::DashMap<PluginId, PluginRecord>>,
562}
563
564impl std::fmt::Debug for PluginRegistry {
565 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
566 f.debug_struct("PluginRegistry")
567 .field("scalar_fns", &self.scalars.len())
568 .field("aggregates", &self.aggregates.len())
569 .field("procedures", &self.procedures.len())
570 .field("locy_aggregates", &self.locy_aggregates.len())
571 .field("algorithms", &self.algorithms.len())
572 .field("index_kinds", &self.index_kinds.len())
573 .field("hooks", &self.hooks.load().len())
574 .field("plugins", &self.per_plugin.read().len())
575 .finish()
576 }
577}
578
579impl PluginRegistry {
580 #[must_use]
582 pub fn new() -> Self {
583 Self::default()
584 }
585
586 #[must_use]
588 pub fn scalar_fn(&self, q: &QName) -> Option<Arc<ScalarEntry>> {
589 self.scalars.get(q).map(|e| Arc::clone(e.value()))
590 }
591
592 #[must_use]
606 pub fn iter_scalars(&self) -> Vec<(QName, Arc<ScalarEntry>)> {
607 self.scalars
608 .iter()
609 .map(|kv| (kv.key().clone(), Arc::clone(kv.value())))
610 .collect()
611 }
612
613 #[must_use]
615 pub fn iter_locy_predicates(&self) -> Vec<(QName, Arc<LocyPredicateEntry>)> {
616 self.locy_predicates
617 .iter()
618 .map(|kv| (kv.key().clone(), Arc::clone(kv.value())))
619 .collect()
620 }
621
622 #[must_use]
624 pub fn iter_algorithms(&self) -> Vec<(QName, Arc<dyn AlgorithmProvider>)> {
625 self.algorithms
626 .iter()
627 .map(|kv| (kv.key().clone(), Arc::clone(&kv.value().provider)))
628 .collect()
629 }
630
631 #[must_use]
633 pub fn iter_index_kinds(&self) -> Vec<(IndexKind, Arc<dyn IndexKindProvider>)> {
634 self.index_kinds
635 .iter()
636 .map(|kv| (kv.key().clone(), Arc::clone(kv.value())))
637 .collect()
638 }
639
640 #[must_use]
645 pub fn catalogs(&self) -> Vec<Arc<dyn CatalogProvider>> {
646 self.catalogs
647 .iter()
648 .map(|kv| Arc::clone(kv.value()))
649 .collect()
650 }
651
652 #[must_use]
654 pub fn aggregate(&self, q: &QName) -> Option<Arc<AggregateEntry>> {
655 self.aggregates.get(q).map(|e| Arc::clone(e.value()))
656 }
657
658 #[must_use]
660 pub fn window(&self, q: &QName) -> Option<Arc<WindowEntry>> {
661 self.windows.get(q).map(|e| Arc::clone(e.value()))
662 }
663
664 #[must_use]
671 pub fn procedure(&self, q: &QName) -> Option<Arc<ProcedureEntry>> {
672 self.procedures
673 .get(q)
674 .and_then(|e| e.value().first().map(Arc::clone))
675 }
676
677 #[must_use]
688 pub fn procedure_with_arity(&self, q: &QName, arity: usize) -> Option<Arc<ProcedureEntry>> {
689 self.procedures.get(q).and_then(|e| {
690 e.value()
691 .iter()
692 .find(|entry| entry.signature.args.len() == arity)
693 .map(Arc::clone)
694 })
695 }
696
697 #[must_use]
703 pub fn procedure_overloads(&self, q: &QName) -> Vec<Arc<ProcedureEntry>> {
704 self.procedures
705 .get(q)
706 .map(|e| e.value().iter().map(Arc::clone).collect())
707 .unwrap_or_default()
708 }
709
710 #[must_use]
712 pub fn locy_aggregate(&self, q: &QName) -> Option<Arc<LocyAggregateEntry>> {
713 self.locy_aggregates.get(q).map(|e| Arc::clone(e.value()))
714 }
715
716 #[must_use]
718 pub fn locy_predicate(&self, q: &QName) -> Option<Arc<LocyPredicateEntry>> {
719 self.locy_predicates.get(q).map(|e| Arc::clone(e.value()))
720 }
721
722 #[must_use]
724 pub fn locy_generator(&self, q: &QName) -> Option<Arc<LocyGeneratorEntry>> {
725 self.locy_generators.get(q).map(|e| Arc::clone(e.value()))
726 }
727
728 #[must_use]
734 pub fn lookup_label_storage(
735 &self,
736 label: &str,
737 ) -> Option<Arc<dyn crate::traits::storage::Storage>> {
738 self.label_storages
739 .get(&SmolStr::new(label))
740 .map(|e| Arc::clone(e.value()))
741 }
742
743 #[must_use]
745 pub fn index_kind(&self, k: &IndexKind) -> Option<Arc<dyn IndexKindProvider>> {
746 self.index_kinds.get(k).map(|e| Arc::clone(e.value()))
747 }
748
749 pub fn register_index_handle(
758 &self,
759 name: impl Into<SmolStr>,
760 kind: IndexKind,
761 handle: Arc<dyn IndexHandle>,
762 ) {
763 self.index_handles
764 .insert(name.into(), IndexHandleEntry { kind, handle });
765 }
766
767 #[must_use]
770 pub fn index_handle(&self, name: &str) -> Option<IndexHandleEntry> {
771 self.index_handles
772 .get(&SmolStr::new(name))
773 .map(|e| e.value().clone())
774 }
775
776 pub fn deregister_index_handle(&self, name: &str) -> Option<IndexHandleEntry> {
779 self.index_handles
780 .remove(&SmolStr::new(name))
781 .map(|(_, v)| v)
782 }
783
784 pub fn register_virtual_label(
796 &self,
797 name: impl Into<SmolStr>,
798 table: Arc<dyn crate::traits::catalog::CatalogTable>,
799 ) -> Result<u16, PluginError> {
800 self.virtual_labels.lock().register(name.into(), table)
801 }
802
803 #[must_use]
807 pub fn virtual_label_by_name(&self, name: &str) -> Option<u16> {
808 let inner = self.virtual_labels.lock();
809 inner.name_to_id.get(&SmolStr::new(name)).copied()
810 }
811
812 #[must_use]
815 pub fn virtual_label_by_id(&self, id: u16) -> Option<VirtualEntry> {
816 self.virtual_labels.lock().id_to_entry.get(&id).cloned()
817 }
818
819 pub fn register_virtual_edge_type(
823 &self,
824 name: impl Into<SmolStr>,
825 table: Arc<dyn crate::traits::catalog::CatalogTable>,
826 ) -> Result<u32, PluginError> {
827 self.virtual_edge_types.lock().register(name.into(), table)
828 }
829
830 #[must_use]
832 pub fn virtual_edge_type_by_id(&self, id: u32) -> Option<VirtualEntry> {
833 self.virtual_edge_types.lock().id_to_entry.get(&id).cloned()
834 }
835
836 #[must_use]
838 pub fn algorithm(&self, q: &QName) -> Option<Arc<dyn AlgorithmProvider>> {
839 self.algorithms
840 .get(q)
841 .map(|e| Arc::clone(&e.value().provider))
842 }
843
844 #[must_use]
850 pub fn algorithm_entry(&self, q: &QName) -> Option<Arc<AlgorithmEntry>> {
851 self.algorithms.get(q).map(|e| Arc::clone(e.value()))
852 }
853
854 #[must_use]
856 pub fn crdt_kind(&self, k: &CrdtKind) -> Option<Arc<dyn CrdtKindProvider>> {
857 self.crdt_kinds.get(k).map(|e| Arc::clone(e.value()))
858 }
859
860 #[must_use]
862 pub fn logical_type(&self, name: &SmolStr) -> Option<Arc<dyn LogicalTypeProvider>> {
863 self.logical_types.get(name).map(|e| Arc::clone(e.value()))
864 }
865
866 #[must_use]
868 pub fn hooks(&self) -> Arc<Vec<Arc<dyn SessionHook>>> {
869 Self::project_append(&self.hooks)
870 }
871
872 #[must_use]
874 pub fn optimizer_rules(&self) -> Arc<Vec<Arc<dyn OptimizerRuleProvider>>> {
875 Self::project_append(&self.optimizer_rules)
876 }
877
878 #[must_use]
880 pub fn triggers(&self) -> Arc<Vec<Arc<dyn TriggerPlugin>>> {
881 Self::project_append(&self.triggers)
882 }
883
884 #[must_use]
889 pub fn cdc_outputs_snapshot(&self) -> Vec<(SmolStr, Arc<dyn CdcOutputProvider>)> {
890 self.cdc_outputs
891 .iter()
892 .map(|e| (e.key().clone(), Arc::clone(e.value())))
893 .collect()
894 }
895
896 #[must_use]
902 pub fn cdc_outputs_is_empty(&self) -> bool {
903 self.cdc_outputs.is_empty()
904 }
905
906 #[must_use]
908 pub fn auth_providers(&self) -> Arc<Vec<Arc<dyn AuthProvider>>> {
909 Self::project_append(&self.auth_providers)
910 }
911
912 #[must_use]
914 pub fn authz_policies(&self) -> Arc<Vec<Arc<dyn AuthzPolicy>>> {
915 Self::project_append(&self.authz_policies)
916 }
917
918 #[must_use]
920 pub fn replacement_scans(&self) -> Arc<Vec<Arc<dyn ReplacementScanProvider>>> {
921 Self::project_append(&self.replacement_scans)
922 }
923
924 pub(crate) fn apply_pending(
939 &self,
940 plugin_id: &PluginId,
941 pending: Vec<Box<dyn crate::surfaces::DynPendingRegistration>>,
942 ) -> Result<(), PluginError> {
943 let mut seen: std::collections::HashSet<QName> = std::collections::HashSet::new();
948 for reg in &pending {
949 reg.preflight(self)?;
950 if let Some(qname) = reg.dedup_key()
951 && !seen.insert(qname.clone())
952 {
953 return Err(PluginError::DuplicateRegistration(qname));
954 }
955 }
956
957 let mut record = PluginRecord::default();
958 for reg in pending {
959 reg.apply(self, plugin_id.clone(), &mut record);
960 }
961
962 self.per_plugin
965 .read()
966 .entry(plugin_id.clone())
967 .or_default()
968 .merge(record);
969
970 Ok(())
971 }
972
973 #[must_use]
975 pub fn background_jobs(&self) -> Arc<Vec<Arc<dyn BackgroundJobProvider>>> {
976 Self::project_append(&self.background_jobs)
977 }
978
979 fn project_append<P: ?Sized>(
991 slot: &ArcSwap<Vec<crate::surfaces::AppendEntry<P>>>,
992 ) -> Arc<Vec<Arc<P>>> {
993 let snap = slot.load();
994 let v: Vec<Arc<P>> = snap.iter().map(|e| Arc::clone(&e.provider)).collect();
995 Arc::new(v)
996 }
997
998 #[must_use]
1009 pub fn iter_for_plugin(&self, plugin: &PluginId) -> Option<PluginRecordSnapshot> {
1010 let guard = self.per_plugin.read();
1011 guard.get(plugin).map(|r| PluginRecordSnapshot::from(&*r))
1012 }
1013
1014 pub fn remove_named_unique(&self, plugin: &PluginId, qname: &QName) -> bool {
1025 use crate::surfaces::{AggregateSurface, NamedUniqueOps, ScalarSurface};
1026 let mut removed = false;
1027 if let Some(mut rec) = self.per_plugin.read().get_mut(plugin) {
1028 if let Some(pos) = rec.scalars.iter().position(|q| q == qname) {
1029 rec.scalars.remove(pos);
1030 <ScalarSurface as NamedUniqueOps>::remove(self, qname);
1031 removed = true;
1032 }
1033 if let Some(pos) = rec.aggregates.iter().position(|q| q == qname) {
1034 rec.aggregates.remove(pos);
1035 <AggregateSurface as NamedUniqueOps>::remove(self, qname);
1036 removed = true;
1037 }
1038 }
1039 removed
1040 }
1041
1042 pub fn remove_plugin(&self, plugin: &PluginId) {
1050 use crate::surfaces::{
1051 AggregateSurface, AlgorithmSurface, AppendOps, AuthSurface, AuthzSurface,
1052 BackgroundJobSurface, CatalogSurface, CdcSurface, CollationSurface, CrdtSurface,
1053 Discriminator, HookSurface, IndexKindSurface, KeyedUniqueOps, LabelStorageSurface,
1054 LocyAggregateSurface, LocyGeneratorSurface, LocyPredicateSurface, LogicalTypeSurface,
1055 NamedUniqueOps, OptimizerRuleSurface, ProcedureSurface, ReplacementScanSurface,
1056 ScalarSurface, TriggerSurface, VersionedOps, WindowSurface,
1057 };
1058
1059 let record = self.per_plugin.read().remove(plugin).map(|(_, r)| r);
1060 let Some(record) = record else { return };
1061
1062 for q in record.scalars {
1063 <ScalarSurface as NamedUniqueOps>::remove(self, &q);
1064 }
1065 for q in record.aggregates {
1066 <AggregateSurface as NamedUniqueOps>::remove(self, &q);
1067 }
1068 for q in record.windows {
1069 <WindowSurface as NamedUniqueOps>::remove(self, &q);
1070 }
1071 for (q, arity) in record.procedures {
1072 <ProcedureSurface as VersionedOps>::remove(self, &q, Discriminator::Arity(arity));
1073 }
1074 for q in record.locy_aggregates {
1075 <LocyAggregateSurface as NamedUniqueOps>::remove(self, &q);
1076 }
1077 for q in record.locy_predicates {
1078 <LocyPredicateSurface as NamedUniqueOps>::remove(self, &q);
1079 }
1080 for q in record.locy_generators {
1081 <LocyGeneratorSurface as NamedUniqueOps>::remove(self, &q);
1082 }
1083 for q in record.algorithms {
1084 <AlgorithmSurface as NamedUniqueOps>::remove(self, &q);
1085 }
1086 for k in record.index_kinds {
1087 <IndexKindSurface as KeyedUniqueOps>::remove(self, &k);
1088 }
1089 for l in record.label_storages {
1090 <LabelStorageSurface as KeyedUniqueOps>::remove(self, &l);
1091 }
1092 for k in record.crdt_kinds {
1093 <CrdtSurface as KeyedUniqueOps>::remove(self, &k);
1094 }
1095 for k in record.logical_types {
1096 <LogicalTypeSurface as KeyedUniqueOps>::remove(self, &k);
1097 }
1098 for k in record.collations {
1099 <CollationSurface as KeyedUniqueOps>::remove(self, &k);
1100 }
1101 for k in record.cdc_outputs {
1102 <CdcSurface as KeyedUniqueOps>::remove(self, &k);
1103 }
1104 for k in record.catalogs {
1105 <CatalogSurface as KeyedUniqueOps>::remove(self, &k);
1106 }
1107
1108 <OptimizerRuleSurface as AppendOps>::remove_plugin(self, plugin);
1109 <HookSurface as AppendOps>::remove_plugin(self, plugin);
1110 <AuthSurface as AppendOps>::remove_plugin(self, plugin);
1111 <AuthzSurface as AppendOps>::remove_plugin(self, plugin);
1112 <TriggerSurface as AppendOps>::remove_plugin(self, plugin);
1113 <ReplacementScanSurface as AppendOps>::remove_plugin(self, plugin);
1114 <BackgroundJobSurface as AppendOps>::remove_plugin(self, plugin);
1115 }
1116}
1117
1118#[cfg(test)]
1119mod tests {
1120 use super::*;
1121
1122 #[test]
1123 fn registry_default_is_empty() {
1124 let r = PluginRegistry::new();
1125 assert!(r.scalar_fn(&QName::builtin("anything")).is_none());
1126 assert!(r.procedure(&QName::builtin("anything")).is_none());
1127 assert_eq!(r.hooks().len(), 0);
1128 }
1129
1130 #[test]
1131 fn debug_smoke() {
1132 let r = PluginRegistry::new();
1133 let s = format!("{r:?}");
1134 assert!(s.contains("PluginRegistry"));
1135 }
1136}