arete_server/view/
registry.rs1use crate::row_scope::{ScopeField, DEFAULT_MAX_SCOPE_PARTITIONS};
2use crate::sorted_cache::{SortOrder, SortedViewCache};
3use crate::view::ViewSpec;
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::RwLock;
7
8#[derive(Clone)]
9pub struct ViewIndex {
10 by_export: HashMap<String, Vec<ViewSpec>>,
11 by_id: HashMap<String, ViewSpec>,
12 sorted_caches: Arc<RwLock<HashMap<String, SortedViewCache>>>,
13 derived_by_source: HashMap<String, Vec<String>>,
15 entity_scopes: HashMap<String, ScopeField>,
17 max_scope_partitions: usize,
19}
20
21impl ViewIndex {
22 pub fn new() -> Self {
23 Self {
24 by_export: HashMap::new(),
25 by_id: HashMap::new(),
26 sorted_caches: Arc::new(RwLock::new(HashMap::new())),
27 derived_by_source: HashMap::new(),
28 entity_scopes: HashMap::new(),
29 max_scope_partitions: DEFAULT_MAX_SCOPE_PARTITIONS,
30 }
31 }
32
33 pub fn set_entity_scope(&mut self, export: impl Into<String>, field: ScopeField) {
40 self.entity_scopes.insert(export.into(), field);
41 self.partition_scoped_caches();
42 }
43
44 pub fn set_max_scope_partitions(&mut self, max_partitions: usize) {
48 self.max_scope_partitions = max_partitions.max(1);
49 self.partition_scoped_caches();
50 }
51
52 pub fn max_scope_partitions(&self) -> usize {
53 self.max_scope_partitions
54 }
55
56 pub fn scope_field_for_view(&self, view_id: &str) -> Option<&ScopeField> {
59 if self.entity_scopes.is_empty() {
60 return None;
61 }
62 let mut current = view_id;
63 for _ in 0..=self.by_id.len() {
66 let Some(spec) = self.by_id.get(current) else {
67 let entity = current.split('/').next().unwrap_or(current);
69 return self.entity_scopes.get(entity);
70 };
71 if let Some(field) = self.entity_scopes.get(&spec.export) {
72 return Some(field);
73 }
74 match spec.source_view.as_deref() {
75 Some(source) if source != current => current = source,
76 _ => return None,
77 }
78 }
79 None
80 }
81
82 pub(crate) fn validate_scope_projection(&self, spec: &ViewSpec) -> anyhow::Result<()> {
84 if let (Some(scope), Some(fields)) =
85 (self.scope_field_for_view(&spec.id), &spec.projection.fields)
86 {
87 if !scope
88 .path()
89 .first()
90 .is_some_and(|root| fields.contains(root))
91 {
92 anyhow::bail!(
93 "View {} projection must retain its row scope field",
94 spec.id
95 );
96 }
97 }
98 Ok(())
99 }
100
101 pub(crate) fn validate_scope_projections(&self) -> anyhow::Result<()> {
102 for spec in self.by_id.values() {
103 self.validate_scope_projection(spec)?;
104 }
105 Ok(())
106 }
107
108 pub fn has_scoped_entities(&self) -> bool {
110 !self.entity_scopes.is_empty()
111 }
112
113 fn partition_scoped_caches(&mut self) {
115 let scoped: Vec<(String, ScopeField)> = self
116 .by_id
117 .keys()
118 .filter_map(|id| {
119 self.scope_field_for_view(id)
120 .map(|field| (id.clone(), field.clone()))
121 })
122 .collect();
123 if scoped.is_empty() {
124 return;
125 }
126 let max_partitions = self.max_scope_partitions;
127 let caches = Arc::get_mut(&mut self.sorted_caches)
128 .expect("Cannot partition sorted caches: Arc is shared");
129 let caches = caches.get_mut();
130 for (view_id, field) in scoped {
131 if let Some(cache) = caches.get_mut(&view_id) {
132 cache.partition_by_scope(field, max_partitions);
133 }
134 }
135 }
136
137 pub fn add_spec(&mut self, spec: ViewSpec) {
138 if let Some(ref source) = spec.source_view {
139 self.derived_by_source
140 .entry(source.clone())
141 .or_default()
142 .push(spec.id.clone());
143 }
144
145 if let Some(ref pipeline) = spec.pipeline {
146 if let Some(ref sort_config) = pipeline.sort {
147 self.init_sorted_cache_sync(
148 &spec.id,
149 sort_config.field_path.clone(),
150 sort_config.order.into(),
151 );
152 }
153 }
154
155 if !spec.is_derived() {
159 self.by_export
160 .entry(spec.export.clone())
161 .or_default()
162 .push(spec.clone());
163 }
164 let partition = self.has_scoped_entities();
165 self.by_id.insert(spec.id.clone(), spec);
166 if partition {
167 self.partition_scoped_caches();
168 }
169 }
170
171 pub fn by_export(&self, entity: &str) -> &[ViewSpec] {
172 self.by_export
173 .get(entity)
174 .map(|v| v.as_slice())
175 .unwrap_or(&[])
176 }
177
178 pub fn get_view(&self, id: &str) -> Option<&ViewSpec> {
179 self.by_id.get(id)
180 }
181
182 pub(crate) fn snapshot_specs(&self) -> Vec<&ViewSpec> {
184 let mut specs = self.by_id.values().collect::<Vec<_>>();
185 specs.sort_by(|left, right| left.id.cmp(&right.id));
186 specs
187 }
188
189 pub fn get_derived_views(&self) -> Vec<&ViewSpec> {
190 self.by_id.values().filter(|s| s.is_derived()).collect()
191 }
192
193 pub fn get_derived_views_for_source(&self, source_view_id: &str) -> Vec<&ViewSpec> {
194 self.derived_by_source
195 .get(source_view_id)
196 .map(|ids| ids.iter().filter_map(|id| self.by_id.get(id)).collect())
197 .unwrap_or_default()
198 }
199
200 pub fn sorted_caches(&self) -> Arc<RwLock<HashMap<String, SortedViewCache>>> {
201 self.sorted_caches.clone()
202 }
203
204 pub async fn init_sorted_cache(
205 &self,
206 view_id: &str,
207 sort_field: Vec<String>,
208 order: SortOrder,
209 ) {
210 let mut caches = self.sorted_caches.write().await;
211 if !caches.contains_key(view_id) {
212 caches.insert(
213 view_id.to_string(),
214 SortedViewCache::new(view_id.to_string(), sort_field, order),
215 );
216 }
217 }
218
219 fn init_sorted_cache_sync(&mut self, view_id: &str, sort_field: Vec<String>, order: SortOrder) {
220 let cache = SortedViewCache::new(view_id.to_string(), sort_field, order);
221 let caches = Arc::get_mut(&mut self.sorted_caches)
222 .expect("Cannot initialize sorted cache: Arc is shared");
223 let caches = caches.get_mut();
224 if !caches.contains_key(view_id) {
225 caches.insert(view_id.to_string(), cache);
226 }
227 }
228}
229
230impl Default for ViewIndex {
231 fn default() -> Self {
232 Self::new()
233 }
234}