1use std::collections::{HashMap, HashSet};
7use std::sync::Arc;
8
9use crate::api::{RedDBError, RedDBResult};
10use crate::storage::schema::Value;
11use crate::storage::RedDB;
12
13thread_local! {
14 static CURRENT_CONN_ID: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
18
19 static CURRENT_AUTH_IDENTITY: std::cell::RefCell<Option<(String, crate::auth::Role)>> =
27 const { std::cell::RefCell::new(None) };
28
29 static CURRENT_SNAPSHOT: std::cell::RefCell<Option<SnapshotContext>> =
39 const { std::cell::RefCell::new(None) };
40
41 static HAS_SNAPSHOT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
47
48 static CURRENT_TENANT_ID: std::cell::RefCell<Option<String>> =
58 const { std::cell::RefCell::new(None) };
59
60 static CURRENT_CONFIG_RESOLVER: std::cell::RefCell<Option<ConfigResolver>> =
64 const { std::cell::RefCell::new(None) };
65
66 static CURRENT_SECRET_RESOLVER: std::cell::RefCell<Option<SecretResolver>> =
70 const { std::cell::RefCell::new(None) };
71
72 static CURRENT_KV_RESOLVER: std::cell::RefCell<Option<KvResolver>> =
76 const { std::cell::RefCell::new(None) };
77}
78
79#[derive(Clone)]
94pub struct SnapshotContext {
95 pub snapshot: crate::storage::transaction::snapshot::Snapshot,
96 pub manager: Arc<crate::storage::transaction::snapshot::SnapshotManager>,
97 pub own_xids: std::collections::HashSet<crate::storage::transaction::snapshot::Xid>,
98 pub requires_index_fallback: bool,
99 pub serializable_reader: Option<crate::storage::transaction::snapshot::Xid>,
100}
101
102pub fn set_current_connection_id(id: u64) {
111 CURRENT_CONN_ID.with(|c| c.set(id));
112}
113
114pub fn clear_current_connection_id() {
116 CURRENT_CONN_ID.with(|c| c.set(0));
117}
118
119pub fn current_connection_id() -> u64 {
122 CURRENT_CONN_ID.with(|c| c.get())
123}
124
125pub fn set_current_auth_identity(username: String, role: crate::auth::Role) {
129 CURRENT_AUTH_IDENTITY.with(|cell| *cell.borrow_mut() = Some((username, role)));
130}
131
132pub fn clear_current_auth_identity() {
136 CURRENT_AUTH_IDENTITY.with(|cell| *cell.borrow_mut() = None);
137}
138
139pub(crate) fn current_auth_identity() -> Option<(String, crate::auth::Role)> {
142 CURRENT_AUTH_IDENTITY.with(|cell| cell.borrow().clone())
143}
144
145pub fn current_auth_identity_for_audit() -> Option<(String, crate::auth::Role)> {
149 current_auth_identity()
150}
151
152pub fn set_current_tenant(tenant_id: String) {
157 CURRENT_TENANT_ID.with(|cell| *cell.borrow_mut() = Some(tenant_id));
158}
159
160pub fn clear_current_tenant() {
163 CURRENT_TENANT_ID.with(|cell| *cell.borrow_mut() = None);
164}
165
166pub fn current_tenant() -> Option<String> {
177 let inherited = CURRENT_TENANT_ID.with(|cell| cell.borrow().clone());
178 if let Some(over) = current_scope_override() {
179 if over.tenant.is_active() {
180 return over.tenant.resolve(inherited);
181 }
182 }
183 if let Some(tx_local) = current_tx_local_tenant() {
184 return tx_local;
185 }
186 inherited
187}
188
189thread_local! {
190 static TX_LOCAL_TENANT: std::cell::RefCell<Option<Option<String>>> =
199 const { std::cell::RefCell::new(None) };
200}
201
202fn current_tx_local_tenant() -> Option<Option<String>> {
203 TX_LOCAL_TENANT.with(|cell| cell.borrow().clone())
204}
205
206pub(crate) fn parse_set_local_tenant(query: &str) -> RedDBResult<Option<Option<String>>> {
212 let mut tokens = query.split_ascii_whitespace();
213 let Some(w1) = tokens.next() else {
214 return Ok(None);
215 };
216 if !w1.eq_ignore_ascii_case("SET") {
217 return Ok(None);
218 }
219 let Some(w2) = tokens.next() else {
220 return Ok(None);
221 };
222 if !w2.eq_ignore_ascii_case("LOCAL") {
223 return Ok(None);
224 }
225 let Some(w3) = tokens.next() else {
226 return Ok(None);
227 };
228 if !w3.eq_ignore_ascii_case("TENANT") {
229 return Ok(None);
230 }
231 let rest: String = tokens.collect::<Vec<_>>().join(" ");
232 let rest = rest.trim().trim_end_matches(';').trim();
233 let value_str = rest.strip_prefix('=').map(|s| s.trim()).unwrap_or(rest);
234 if value_str.is_empty() {
235 return Err(RedDBError::Query(
236 "SET LOCAL TENANT expects a string literal or NULL".to_string(),
237 ));
238 }
239 if value_str.eq_ignore_ascii_case("NULL") {
240 return Ok(Some(None));
241 }
242 if value_str.starts_with('\'') && value_str.ends_with('\'') && value_str.len() >= 2 {
243 let inner = &value_str[1..value_str.len() - 1];
244 return Ok(Some(Some(inner.to_string())));
245 }
246 Err(RedDBError::Query(format!(
247 "SET LOCAL TENANT expects a string literal or NULL, got `{value_str}`"
248 )))
249}
250
251pub(crate) struct TxLocalTenantGuard;
252
253impl TxLocalTenantGuard {
254 pub fn install(value: Option<Option<String>>) -> Self {
255 TX_LOCAL_TENANT.with(|cell| *cell.borrow_mut() = value);
256 Self
257 }
258}
259
260impl Drop for TxLocalTenantGuard {
261 fn drop(&mut self) {
262 TX_LOCAL_TENANT.with(|cell| *cell.borrow_mut() = None);
263 }
264}
265
266thread_local! {
267 static SCOPE_OVERRIDES: std::cell::RefCell<Vec<crate::runtime::within_clause::ScopeOverride>> =
274 const { std::cell::RefCell::new(Vec::new()) };
275}
276
277pub(crate) fn push_scope_override(over: crate::runtime::within_clause::ScopeOverride) {
278 SCOPE_OVERRIDES.with(|cell| cell.borrow_mut().push(over));
279}
280
281pub(crate) fn pop_scope_override() {
282 SCOPE_OVERRIDES.with(|cell| {
283 cell.borrow_mut().pop();
284 });
285}
286
287pub(crate) fn current_scope_override() -> Option<crate::runtime::within_clause::ScopeOverride> {
288 SCOPE_OVERRIDES.with(|cell| cell.borrow().last().cloned())
289}
290
291pub(crate) fn has_scope_override_active() -> bool {
295 SCOPE_OVERRIDES.with(|cell| !cell.borrow().is_empty())
296}
297
298pub(crate) struct ScopeOverrideGuard;
302
303impl ScopeOverrideGuard {
304 pub fn install(over: crate::runtime::within_clause::ScopeOverride) -> Self {
305 push_scope_override(over);
306 Self
307 }
308}
309
310impl Drop for ScopeOverrideGuard {
311 fn drop(&mut self) {
312 pop_scope_override();
313 }
314}
315
316pub(crate) fn current_user_projected() -> Option<String> {
322 let inherited = current_auth_identity().map(|(u, _)| u);
323 if let Some(over) = current_scope_override() {
324 if over.user.is_active() {
325 return over.user.resolve(inherited);
326 }
327 }
328 inherited
329}
330
331pub(crate) fn current_role_projected() -> Option<String> {
332 let inherited = current_auth_identity().map(|(_, r)| format!("{r:?}").to_lowercase());
333 if let Some(over) = current_scope_override() {
334 if over.role.is_active() {
335 return over.role.resolve(inherited);
336 }
337 }
338 inherited
339}
340
341pub(crate) fn current_secret_value(path: &str) -> Option<String> {
342 let key = path.to_ascii_lowercase();
343 CURRENT_SECRET_RESOLVER.with(|cell| {
344 let mut resolver = cell.borrow_mut();
345 let resolver = resolver.as_mut()?;
346 if resolver.values.is_none() {
347 resolver.values = resolver
348 .store
349 .as_ref()
350 .map(|store| store.vault_kv_snapshot());
351 }
352 let values = resolver.values.as_ref()?;
353 let found = values
354 .get(&key)
355 .map(|value| (key.as_str(), value))
356 .or_else(|| {
357 key.strip_prefix("red.vault/").and_then(|rest| {
358 values.get(rest).map(|value| (rest, value)).or_else(|| {
359 let red_secret_key = format!("red.secret.{rest}");
360 values
361 .get_key_value(&red_secret_key)
362 .map(|(key, value)| (key.as_str(), value))
363 })
364 })
365 })?;
366 if !resolver.can_read(found.0) {
367 return None;
368 }
369 Some(found.1.clone())
370 })
371}
372
373fn secret_value_from_snapshot(values: &HashMap<String, String>, key: &str) -> Option<String> {
374 if key.starts_with("red.secret.") {
375 return None;
376 }
377 values.get(key).cloned()
378}
379
380struct SecretResolver {
381 store: Option<Arc<crate::auth::store::AuthStore>>,
382 values: Option<HashMap<String, String>>,
383 identity: Option<(String, crate::auth::Role, Option<String>)>,
384}
385
386impl SecretResolver {
387 fn can_read(&self, key: &str) -> bool {
388 if key.starts_with("red.secret.") {
391 return false;
392 }
393 let Some(store) = &self.store else {
394 return true;
395 };
396 let Some((username, role, tenant)) = &self.identity else {
397 return true;
398 };
399 let principal = crate::auth::UserId::from_parts(tenant.as_deref(), username);
400 let mut resource =
401 crate::auth::policies::ResourceRef::new("secret".to_string(), key.to_string());
402 if let Some(tenant) = tenant {
403 resource = resource.with_tenant(tenant.clone());
404 }
405 let ctx = crate::auth::policies::EvalContext {
406 principal_tenant: tenant.clone(),
407 current_tenant: tenant.clone(),
408 peer_ip: None,
409 mfa_present: false,
410 now_ms: crate::auth::now_ms(),
411 principal_is_admin_role: *role == crate::auth::Role::Admin,
412 principal_is_platform_scoped: tenant.is_none(),
413 };
414 store.check_policy_authz_with_role(&principal, "secret:read", &resource, &ctx, *role)
415 }
416}
417
418pub(crate) struct SecretStoreGuard {
419 previous: Option<SecretResolver>,
420}
421
422impl SecretStoreGuard {
423 pub(super) fn install(store: Option<Arc<crate::auth::store::AuthStore>>) -> Self {
424 let previous = CURRENT_SECRET_RESOLVER.with(|cell| {
425 cell.replace(Some(SecretResolver {
426 store,
427 values: None,
428 identity: current_auth_identity().map(|(username, role)| {
429 let tenant = current_tenant();
430 (username, role, tenant)
431 }),
432 }))
433 });
434 Self { previous }
435 }
436}
437
438impl Drop for SecretStoreGuard {
439 fn drop(&mut self) {
440 let previous = self.previous.take();
441 CURRENT_SECRET_RESOLVER.with(|cell| {
442 cell.replace(previous);
443 });
444 }
445}
446
447pub(crate) fn current_kv_value(path: &str) -> Option<String> {
455 let key = path.to_ascii_lowercase();
456 CURRENT_KV_RESOLVER.with(|cell| {
457 let mut resolver = cell.borrow_mut();
458 let resolver = resolver.as_mut()?;
459 if resolver.values.is_none() {
460 resolver.values = resolver
461 .store
462 .as_ref()
463 .map(|store| store.plain_kv_snapshot());
464 }
465 let values = resolver.values.as_ref()?;
466 let found = values
470 .get(&key)
471 .map(|value| (key.as_str(), value))
472 .or_else(|| {
473 key.strip_prefix("red.kv/")
474 .and_then(|rest| values.get(rest).map(|value| (rest, value)))
475 })?;
476 if !resolver.can_read(found.0) {
477 return None;
478 }
479 Some(found.1.clone())
480 })
481}
482
483struct KvResolver {
484 store: Option<Arc<crate::auth::store::AuthStore>>,
485 values: Option<HashMap<String, String>>,
486 identity: Option<(String, crate::auth::Role, Option<String>)>,
487}
488
489impl KvResolver {
490 fn can_read(&self, key: &str) -> bool {
491 let Some(store) = &self.store else {
492 return true;
493 };
494 let Some((username, role, tenant)) = &self.identity else {
495 return true;
496 };
497 let principal = crate::auth::UserId::from_parts(tenant.as_deref(), username);
498 let mut resource =
499 crate::auth::policies::ResourceRef::new("kv".to_string(), key.to_string());
500 if let Some(tenant) = tenant {
501 resource = resource.with_tenant(tenant.clone());
502 }
503 let ctx = crate::auth::policies::EvalContext {
504 principal_tenant: tenant.clone(),
505 current_tenant: tenant.clone(),
506 peer_ip: None,
507 mfa_present: false,
508 now_ms: crate::auth::now_ms(),
509 principal_is_admin_role: *role == crate::auth::Role::Admin,
510 principal_is_platform_scoped: tenant.is_none(),
511 };
512 store.check_policy_authz_with_role(&principal, "kv:read", &resource, &ctx, *role)
513 }
514}
515
516pub(crate) struct KvStoreGuard {
517 previous: Option<Box<KvResolver>>,
522}
523
524impl KvStoreGuard {
525 pub(super) fn install(store: Option<Arc<crate::auth::store::AuthStore>>) -> Self {
526 let previous = CURRENT_KV_RESOLVER.with(|cell| {
527 cell.replace(Some(KvResolver {
528 store,
529 values: None,
530 identity: current_auth_identity().map(|(username, role)| {
531 let tenant = current_tenant();
532 (username, role, tenant)
533 }),
534 }))
535 });
536 Self {
537 previous: previous.map(Box::new),
538 }
539 }
540}
541
542impl Drop for KvStoreGuard {
543 fn drop(&mut self) {
544 let previous = self.previous.take().map(|b| *b);
545 CURRENT_KV_RESOLVER.with(|cell| {
546 cell.replace(previous);
547 });
548 }
549}
550
551pub(crate) fn update_current_kv_value(path: &str, value: Option<String>) {
554 let key = path.to_ascii_lowercase();
555 CURRENT_KV_RESOLVER.with(|cell| {
556 if let Some(resolver) = cell.borrow_mut().as_mut() {
557 let Some(values) = resolver.values.as_mut() else {
558 return;
559 };
560 match value {
561 Some(value) => {
562 values.insert(key, value);
563 }
564 None => {
565 values.remove(&key);
566 }
567 }
568 }
569 });
570}
571
572pub(crate) fn current_config_value(path: &str) -> Option<Value> {
573 let key = path.to_ascii_lowercase();
574 CURRENT_CONFIG_RESOLVER.with(|cell| {
575 let mut resolver = cell.borrow_mut();
576 let resolver = resolver.as_mut()?;
577 if resolver.values.is_none() {
578 resolver.values = Some(latest_config_snapshot(&resolver.db));
579 }
580 let values = resolver.values.as_ref()?;
581 values.get(&key).cloned().or_else(|| {
586 key.strip_prefix("red.config/").and_then(|rest| {
587 values
588 .get(rest)
589 .cloned()
590 .or_else(|| values.get(&format!("red.config.{rest}")).cloned())
591 })
592 })
593 })
594}
595
596pub(crate) fn update_current_config_value(path: &str, value: Value) {
597 let key = path.to_ascii_lowercase();
598 CURRENT_CONFIG_RESOLVER.with(|cell| {
599 if let Some(resolver) = cell.borrow_mut().as_mut() {
600 if let Some(values) = resolver.values.as_mut() {
601 values.insert(key, value);
602 }
603 }
604 });
605}
606
607pub(crate) fn update_current_secret_value(path: &str, value: Option<String>) {
608 let key = path.to_ascii_lowercase();
609 CURRENT_SECRET_RESOLVER.with(|cell| {
610 if let Some(resolver) = cell.borrow_mut().as_mut() {
611 let Some(values) = resolver.values.as_mut() else {
612 return;
613 };
614 match value {
615 Some(value) => {
616 values.insert(key, value);
617 }
618 None => {
619 values.remove(&key);
620 }
621 }
622 }
623 });
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 fn with_secret_values<T>(values: HashMap<String, String>, f: impl FnOnce() -> T) -> T {
631 CURRENT_SECRET_RESOLVER.with(|cell| {
632 cell.replace(Some(SecretResolver {
633 store: None,
634 values: Some(values),
635 identity: None,
636 }));
637 });
638 let result = f();
639 CURRENT_SECRET_RESOLVER.with(|cell| {
640 cell.replace(None);
641 });
642 result
643 }
644
645 #[test]
646 fn current_secret_value_does_not_fall_back_to_reserved_red_secret_namespace() {
647 let values = HashMap::from([
648 (
649 "red.secret.aes_key".to_string(),
650 "vault-aes-key".to_string(),
651 ),
652 (
653 "red.secret.ai.anthropic.default.api_key".to_string(),
654 "provider-key".to_string(),
655 ),
656 ("acme.key".to_string(), "user-value".to_string()),
657 ]);
658
659 with_secret_values(values, || {
660 assert_eq!(current_secret_value("red.vault/aes_key"), None);
661 assert_eq!(current_secret_value("red.vault/red.secret.aes_key"), None);
662 assert_eq!(
663 current_secret_value("red.vault/ai.anthropic.default.api_key"),
664 None
665 );
666 assert_eq!(
667 current_secret_value("red.vault/acme.key").as_deref(),
668 Some("user-value")
669 );
670 });
671 }
672
673 fn with_kv_values<T>(values: HashMap<String, String>, f: impl FnOnce() -> T) -> T {
674 CURRENT_KV_RESOLVER.with(|cell| {
675 cell.replace(Some(KvResolver {
676 store: None,
677 values: Some(values),
678 identity: None,
679 }));
680 });
681 let result = f();
682 CURRENT_KV_RESOLVER.with(|cell| {
683 cell.replace(None);
684 });
685 result
686 }
687
688 #[test]
689 fn current_kv_value_resolves_namespaced_and_bare_keys() {
690 let values = HashMap::from([("acme.key".to_string(), "plain-value".to_string())]);
691
692 with_kv_values(values, || {
693 assert_eq!(
695 current_kv_value("red.kv/acme.key").as_deref(),
696 Some("plain-value")
697 );
698 assert_eq!(current_kv_value("acme.key").as_deref(), Some("plain-value"));
700 assert_eq!(current_kv_value("red.kv/missing.key"), None);
702 });
703 }
704
705 #[test]
706 fn update_current_kv_value_reflects_in_resolver() {
707 with_kv_values(HashMap::new(), || {
708 assert_eq!(current_kv_value("red.kv/feature.flag"), None);
709 update_current_kv_value("feature.flag", Some("on".to_string()));
710 assert_eq!(
711 current_kv_value("red.kv/feature.flag").as_deref(),
712 Some("on")
713 );
714 update_current_kv_value("feature.flag", None);
715 assert_eq!(current_kv_value("red.kv/feature.flag"), None);
716 });
717 }
718}
719
720fn latest_config_snapshot(db: &RedDB) -> HashMap<String, Value> {
721 let mut latest: HashMap<String, (u64, Value)> = HashMap::new();
722
723 if let Some(manager) = db.store().get_collection("red_config") {
724 manager.for_each_entity(|entity| {
725 let Some(row) = entity.data.as_row() else {
726 return true;
727 };
728 let Some(Value::Text(key)) = row.get_field("key") else {
729 return true;
730 };
731 let value = row.get_field("value").cloned().unwrap_or(Value::Null);
732 let id = entity.id.raw();
733 let key = key.to_ascii_lowercase();
734 insert_latest_config_value(&mut latest, key.clone(), id, value.clone());
735 if let Some(rest) = key.strip_prefix("red.config.") {
736 insert_latest_config_value(&mut latest, format!("red.config/{rest}"), id, value);
737 }
738 true
739 });
740 }
741
742 if let Some(manager) = db.store().get_collection("red.config") {
743 manager.for_each_entity(|entity| {
744 let Some(row) = entity.data.as_row() else {
745 return true;
746 };
747 if matches!(row.get_field("tombstone"), Some(Value::Boolean(true))) {
748 return true;
749 }
750 let Some(Value::Text(key)) = row.get_field("key") else {
751 return true;
752 };
753 let value = row.get_field("value").cloned().unwrap_or(Value::Null);
754 insert_latest_config_value(
755 &mut latest,
756 format!("red.config/{}", key.to_ascii_lowercase()),
757 entity.id.raw(),
758 value,
759 );
760 true
761 });
762 }
763
764 latest
765 .into_iter()
766 .map(|(key, (_, value))| (key, value))
767 .collect()
768}
769
770fn insert_latest_config_value(
771 latest: &mut HashMap<String, (u64, Value)>,
772 key: String,
773 id: u64,
774 value: Value,
775) {
776 match latest.get(&key) {
777 Some((prev_id, _)) if *prev_id > id => {}
778 _ => {
779 latest.insert(key, (id, value));
780 }
781 }
782}
783
784struct ConfigResolver {
785 db: Arc<RedDB>,
786 values: Option<HashMap<String, Value>>,
787}
788
789pub(crate) struct ConfigSnapshotGuard {
790 previous: Option<ConfigResolver>,
791}
792
793impl ConfigSnapshotGuard {
794 pub(super) fn install(db: Arc<RedDB>) -> Self {
795 let previous = CURRENT_CONFIG_RESOLVER
796 .with(|cell| cell.replace(Some(ConfigResolver { db, values: None })));
797 Self { previous }
798 }
799}
800
801impl Drop for ConfigSnapshotGuard {
802 fn drop(&mut self) {
803 let previous = self.previous.take();
804 CURRENT_CONFIG_RESOLVER.with(|cell| {
805 cell.replace(previous);
806 });
807 }
808}
809
810pub fn set_current_snapshot(ctx: SnapshotContext) {
815 CURRENT_SNAPSHOT.with(|cell| *cell.borrow_mut() = Some(ctx));
816 HAS_SNAPSHOT.with(|c| c.set(true));
817}
818
819pub fn clear_current_snapshot() {
820 CURRENT_SNAPSHOT.with(|cell| *cell.borrow_mut() = None);
821 HAS_SNAPSHOT.with(|c| c.set(false));
822}
823
824pub(crate) struct CurrentSnapshotGuard {
830 previous: Option<SnapshotContext>,
831}
832
833impl CurrentSnapshotGuard {
834 pub(crate) fn install(ctx: SnapshotContext) -> Self {
835 let previous = CURRENT_SNAPSHOT.with(|cell| cell.borrow().clone());
836 set_current_snapshot(ctx);
837 Self { previous }
838 }
839}
840
841impl Drop for CurrentSnapshotGuard {
842 fn drop(&mut self) {
843 let prev = self.previous.take();
844 let has = prev.is_some();
845 CURRENT_SNAPSHOT.with(|cell| *cell.borrow_mut() = prev);
846 HAS_SNAPSHOT.with(|c| c.set(has));
847 }
848}
849
850#[inline]
861pub fn entity_visible_under_current_snapshot(
862 entity: &crate::storage::unified::entity::UnifiedEntity,
863) -> bool {
864 if crate::runtime::ai::moderation::entity_moderation_hidden(entity) {
871 return false;
872 }
873 if !HAS_SNAPSHOT.with(|c| c.get()) {
879 return entity.xmax == 0;
880 }
881 CURRENT_SNAPSHOT.with(|cell| {
882 let guard = cell.borrow();
883 let Some(ctx) = guard.as_ref() else {
884 return true;
885 };
886 let visible = visibility_check(ctx, entity.xmin, entity.xmax);
887 if visible {
888 record_serializable_read(ctx, entity);
889 }
890 visible
891 })
892}
893
894#[inline]
899pub(crate) fn xids_visible_under_current_snapshot(xmin: u64, xmax: u64) -> bool {
900 if !HAS_SNAPSHOT.with(|c| c.get()) {
901 return true;
902 }
903 CURRENT_SNAPSHOT.with(|cell| {
904 let guard = cell.borrow();
905 let Some(ctx) = guard.as_ref() else {
906 return true;
907 };
908 visibility_check(ctx, xmin, xmax)
909 })
910}
911
912pub fn capture_current_snapshot() -> Option<SnapshotContext> {
919 CURRENT_SNAPSHOT.with(|cell| cell.borrow().clone())
920}
921
922pub(crate) fn current_snapshot_requires_index_fallback() -> bool {
927 if !HAS_SNAPSHOT.with(|c| c.get()) {
928 return false;
929 }
930 CURRENT_SNAPSHOT.with(|cell| {
931 cell.borrow()
932 .as_ref()
933 .is_some_and(|ctx| ctx.requires_index_fallback)
934 })
935}
936
937#[derive(Clone, Default)]
952pub struct SnapshotBundle {
953 pub snapshot: Option<SnapshotContext>,
954 pub auth: Option<(String, crate::auth::Role)>,
955 pub tenant: Option<String>,
956}
957
958pub fn snapshot_bundle() -> SnapshotBundle {
961 SnapshotBundle {
962 snapshot: capture_current_snapshot(),
963 auth: current_auth_identity(),
964 tenant: CURRENT_TENANT_ID.with(|cell| cell.borrow().clone()),
965 }
966}
967
968pub fn with_snapshot_bundle<R>(bundle: &SnapshotBundle, f: impl FnOnce() -> R) -> R {
973 struct Guard {
974 prev_snapshot: Option<SnapshotContext>,
975 prev_auth: Option<(String, crate::auth::Role)>,
976 prev_tenant: Option<String>,
977 }
978 impl Drop for Guard {
979 fn drop(&mut self) {
980 let snap = self.prev_snapshot.take();
981 let has = snap.is_some();
982 CURRENT_SNAPSHOT.with(|cell| *cell.borrow_mut() = snap);
983 HAS_SNAPSHOT.with(|c| c.set(has));
984 CURRENT_AUTH_IDENTITY.with(|cell| *cell.borrow_mut() = self.prev_auth.take());
985 CURRENT_TENANT_ID.with(|cell| *cell.borrow_mut() = self.prev_tenant.take());
986 }
987 }
988
989 let _guard = {
990 let prev_snapshot = CURRENT_SNAPSHOT.with(|cell| cell.borrow().clone());
991 let prev_auth = CURRENT_AUTH_IDENTITY.with(|cell| cell.borrow().clone());
992 let prev_tenant = CURRENT_TENANT_ID.with(|cell| cell.borrow().clone());
993
994 match bundle.snapshot.clone() {
995 Some(ctx) => set_current_snapshot(ctx),
996 None => clear_current_snapshot(),
997 }
998 CURRENT_AUTH_IDENTITY.with(|cell| *cell.borrow_mut() = bundle.auth.clone());
999 CURRENT_TENANT_ID.with(|cell| *cell.borrow_mut() = bundle.tenant.clone());
1000
1001 Guard {
1002 prev_snapshot,
1003 prev_auth,
1004 prev_tenant,
1005 }
1006 };
1007 f()
1008}
1009
1010#[inline]
1014pub fn entity_visible_with_context(
1015 ctx: Option<&SnapshotContext>,
1016 entity: &crate::storage::unified::entity::UnifiedEntity,
1017) -> bool {
1018 if crate::runtime::ai::moderation::entity_moderation_hidden(entity) {
1022 return false;
1023 }
1024 match ctx {
1025 Some(ctx) => {
1026 let visible = visibility_check(ctx, entity.xmin, entity.xmax);
1027 if visible {
1028 record_serializable_read(ctx, entity);
1029 }
1030 visible
1031 }
1032 None => true,
1033 }
1034}
1035
1036fn record_serializable_read(
1037 ctx: &SnapshotContext,
1038 entity: &crate::storage::unified::entity::UnifiedEntity,
1039) {
1040 let Some(reader) = ctx.serializable_reader else {
1041 return;
1042 };
1043 if !matches!(
1044 &entity.kind,
1045 crate::storage::unified::entity::EntityKind::TableRow { .. }
1046 ) {
1047 return;
1048 }
1049 ctx.manager
1050 .record_serializable_read(reader, entity.kind.collection(), entity.logical_id());
1051}
1052
1053#[inline]
1054fn visibility_check(ctx: &SnapshotContext, xmin: u64, xmax: u64) -> bool {
1055 if xmin != 0 && ctx.manager.is_aborted(xmin) {
1059 return false;
1060 }
1061 let effective_xmax = if xmax != 0 && ctx.manager.is_aborted(xmax) {
1063 0
1064 } else {
1065 xmax
1066 };
1067 let own_xmin = xmin != 0 && ctx.own_xids.contains(&xmin);
1071 let own_xmax = effective_xmax != 0 && ctx.own_xids.contains(&effective_xmax);
1072 if own_xmax {
1073 return false;
1075 }
1076 if own_xmin {
1077 return true;
1078 }
1079 ctx.snapshot.sees(xmin, effective_xmax)
1080}