use super::*;
use crate::catalog::{security::roles::persistence::RoleCatalogSnapshot, sequence::SequenceState};
use std::{
cell::{Cell, RefCell},
collections::BTreeMap,
sync::Arc,
};
use uqa_core::{catalog_sequence::SequencePrivileges, RelationIdentity};
use uqa_sql::{
ast::{RelationPersistence, SequenceDataType},
catalog::{
resolution::RelationResolution,
roles::{identity::RoleBinding, RoleDefinition, RoleReference, RoleReferenceNames},
security::{sequence_inquiry::SequencePrivilegeResolution, BoundSequenceSecurity},
},
};
use uqa_storage::StorageBackendResult;
struct Catalog {
retained: SequenceReadSnapshot,
current: RefCell<SequenceReadSnapshot>,
after_resolution: RefCell<Option<SequenceReadSnapshot>>,
reads: Cell<usize>,
resolutions: Cell<usize>,
}
impl Catalog {
fn new() -> Self {
let mut reader = RoleDefinition::bootstrap();
reader.name = "reader".into();
reader.oid = 20_000;
reader.object_id = [1; 16];
reader.attributes.clear();
let relation = RelationIdentity::new("public", "ids");
let retained = SequenceReadSnapshot {
sequences: Arc::new(BTreeMap::from([(
relation.clone(),
SequenceState::initial(1, 1, SequenceDataType::BigInt),
)])),
object_ids: Arc::new(BTreeMap::from([(relation.clone(), [7; 16])])),
persistence: Arc::new(BTreeMap::from([(
relation.clone(),
RelationPersistence::Permanent,
)])),
security: Arc::new(BTreeMap::from([(
relation,
BoundSequenceSecurity {
role_owner: uqa_core::catalog_role::RoleIdentity::BOOTSTRAP,
acl: Some(vec![uqa_core::catalog_role::BoundAclEntry {
role: Some(reader.identity()),
grantor: uqa_core::catalog_role::RoleIdentity::BOOTSTRAP,
privileges: SequencePrivileges::ALL,
grant_options: SequencePrivileges::default(),
}]),
},
)])),
roles: RoleCatalogSnapshot {
roles: Arc::new(BTreeMap::from([
("uqa".into(), RoleDefinition::bootstrap()),
(reader.name.clone(), reader),
])),
memberships: Arc::new(BTreeMap::new()),
},
};
Self {
current: RefCell::new(retained.clone()),
retained,
after_resolution: RefCell::new(None),
reads: Cell::new(0),
resolutions: Cell::new(0),
}
}
fn value(&self, arguments: &[Value]) -> Result<Value, SQLError> {
sequence_privilege_value(
&SequencePrivilegeInquiry {
names: self,
roles: &self.retained,
security: &self.retained,
resolution: self,
},
self,
arguments,
|_| panic!("unexpected non-sequence lookup"),
)
}
}
impl SequenceSnapshotSource for Catalog {
fn sequence_read_snapshot(&self) -> StorageBackendResult<SequenceReadSnapshot> {
self.reads.set(self.reads.get() + 1);
Ok(self.current.borrow().clone())
}
}
impl RoleReferenceNames for Catalog {
fn outer_role(&self) -> uqa_sql::catalog::roles::RoleReference {
self.current_role()
}
fn current_role(&self) -> RoleReference {
RoleReference::Bound(Arc::new(
RoleBinding::from_definition(&self.retained.roles.roles["reader"]).unwrap(),
))
}
fn session_role(&self) -> RoleReference {
self.current_role()
}
}
impl SequencePrivilegeResolution for Catalog {
fn visible_relation_kind(&self, _: &str) -> Result<RelationResolution, SQLError> {
self.resolutions.set(self.resolutions.get() + 1);
if let Some(next) = self.after_resolution.borrow_mut().take() {
*self.current.borrow_mut() = next;
}
Ok(RelationResolution::Found("public.ids".into(), "sequence"))
}
fn sequence_privilege_oid(
&self,
_: i64,
) -> Result<Option<(String, RelationIdentity)>, SQLError> {
panic!("inquiry must resolve sequence OIDs from its detached snapshot")
}
}
#[test]
fn inquiry_refresh_keeps_selected_role_identity_across_name_resolution() {
for subject in [
None,
Some(Value::Str("reader".into())),
Some(Value::Int(20_000)),
] {
let catalog = Catalog::new();
let mut replaced = catalog.retained.clone();
Arc::make_mut(&mut replaced.roles.roles)
.get_mut("reader")
.unwrap()
.object_id = [2; 16];
Arc::make_mut(&mut replaced.security)
.values_mut()
.next()
.unwrap()
.acl
.as_mut()
.unwrap()[0]
.role = Some(replaced.roles.roles["reader"].identity());
*catalog.after_resolution.borrow_mut() = Some(replaced);
let mut arguments = subject.into_iter().collect::<Vec<_>>();
arguments.extend([Value::Str("ids".into()), Value::Str("USAGE".into())]);
assert_eq!(catalog.value(&arguments).unwrap(), Value::Bool(false));
assert_eq!(catalog.retained.roles.roles["reader"].object_id, [1; 16]);
assert_eq!(catalog.resolutions.get(), 1);
}
}
#[test]
fn inquiry_oid_authority_is_current_without_touching_live_resolution() {
let catalog = Catalog::new();
Arc::make_mut(&mut catalog.current.borrow_mut().security)
.values_mut()
.next()
.unwrap()
.acl = Some(Vec::new());
let oid = sequence_relation_oid([7; 16]);
for subject in [
None,
Some(Value::Str("reader".into())),
Some(Value::Int(20_000)),
] {
let mut arguments = subject.into_iter().collect::<Vec<_>>();
arguments.extend([Value::Int(oid), Value::Str("USAGE".into())]);
assert_eq!(catalog.value(&arguments).unwrap(), Value::Bool(false));
}
assert_eq!(catalog.resolutions.get(), 0);
assert!(!catalog
.retained
.security
.values()
.next()
.unwrap()
.acl
.as_ref()
.unwrap()
.is_empty());
}
#[test]
fn null_and_invalid_implicit_inquiries_do_not_load_catalogs() {
let catalog = Catalog::new();
assert_eq!(
catalog
.value(&[Value::Null, Value::Str("bad".into())])
.unwrap(),
Value::Null
);
assert_eq!(
catalog
.value(&[Value::Int(0), Value::Str("bad".into())])
.unwrap_err()
.sqlstate(),
Some("22023")
);
assert!(matches!(catalog.value(&[]), Err(SQLError::BadArity { .. })));
assert_eq!(catalog.reads.get(), 0);
assert_eq!(catalog.resolutions.get(), 0);
}
#[test]
fn inquiry_binds_a_new_committed_role_before_target_resolution() {
let catalog = Catalog::new();
{
let mut snapshot = catalog.current.borrow_mut();
let mut role = snapshot.roles.roles["reader"].clone();
role.name = "new_reader".into();
role.oid = 20_001;
role.object_id = [2; 16];
let new_reader = role.identity();
Arc::make_mut(&mut snapshot.roles.roles).insert(role.name.clone(), role);
Arc::make_mut(&mut snapshot.security)
.values_mut()
.next()
.unwrap()
.acl
.as_mut()
.unwrap()[0]
.role = Some(new_reader);
}
for subject in [Value::Str("new_reader".into()), Value::Int(20_001)] {
for target in [
Value::Str("ids".into()),
Value::Int(sequence_relation_oid([7; 16])),
] {
assert_eq!(
catalog
.value(&[subject.clone(), target, Value::Str("USAGE".into())])
.unwrap(),
Value::Bool(true)
);
}
}
assert!(!catalog.retained.roles.roles.contains_key("new_reader"));
}
#[test]
fn removed_sequence_authority_does_not_reappear_from_a_retained_statement_catalog() {
let catalog = Catalog::new();
Arc::make_mut(&mut catalog.current.borrow_mut().object_ids).clear();
let oid = sequence_relation_oid([7; 16]);
let arguments = [Value::Int(oid), Value::Str("USAGE".into())];
let inquiry = SequencePrivilegeInquiry {
names: &catalog,
roles: &catalog.retained,
security: &catalog.retained,
resolution: &catalog,
};
for kind in [None, Some("S"), Some("r")] {
let result = sequence_privilege_value(&inquiry, &catalog, &arguments, |requested| {
assert_eq!(requested, oid);
Ok(kind.map(|kind| ("ids".into(), kind.into())))
});
if kind == Some("r") {
assert_eq!(result.unwrap_err().sqlstate(), Some("42809"));
} else {
assert_eq!(result.unwrap(), Value::Null);
}
}
assert_eq!(
catalog
.value(&[Value::Str("ids".into()), Value::Str("USAGE".into())])
.unwrap_err()
.sqlstate(),
Some("42P01")
);
}