1use rupg_catalog::RelKind;
4use rupg_common::{Error, Result, SqlState};
5use rupg_pgcatalog::builtin::{self, Named};
6use rupg_sql::nodes::{LockStmt, Node, RangeVar};
7
8use super::relkind_detail;
9use super::view::parse_bool;
10use crate::expr::{Expr, ExprKind};
11use crate::{Analyzer, Env, Params, Query};
12
13pub type Allowed<'a> = dyn FnMut(u32, u32) -> Result<bool> + 'a;
15
16pub type Take<'a> = dyn FnMut(u32) -> Result<bool> + 'a;
18
19pub fn lock_table(
25 stmt: &LockStmt,
26 env: &dyn Env,
27 user: u32,
28 allowed: &mut Allowed<'_>,
29 take: &mut Take<'_>,
30) -> Result<()> {
31 let params = Params::default();
32 let an = Analyzer::new(env, ¶ms);
33 let mut locker = Locker { an, allowed, take, ancestors: Vec::new() };
34 for node in stmt.relations.iter().flatten() {
35 let Node::RangeVar(rv) = node else { continue };
36 let relid = lookup(&locker.an, rv)?;
37 let name = rv.relname.as_deref().unwrap_or_default();
38 let kind = relkind(env, relid);
39 if !matches!(kind, b'r' | b'p' | b'v') {
40 return Err(Error::new(
41 SqlState::WRONG_OBJECT_TYPE,
42 format!("cannot lock relation \"{name}\""),
43 )
44 .with_detail(relkind_detail(kind)));
45 }
46 if !(locker.allowed)(relid, user)? {
47 return Err(denied(kind, name));
48 }
49 if !(locker.take)(relid)? {
50 let name = match rv.schemaname.as_deref() {
51 Some(schema) => format!("{schema}.{name}"),
52 None => name.to_owned(),
53 };
54 return Err(not_free(&name));
55 }
56 if kind == b'v' {
57 locker.view(relid, user)?;
58 } else if rv.inh {
59 locker.children(relid)?;
60 }
61 }
62 Ok(())
63}
64
65pub(super) fn lookup(an: &Analyzer<'_>, rv: &RangeVar) -> Result<u32> {
67 let found = an.lookup_relation(rv).map_err(|error| error.with_position(None))?;
68 if let Some(relid) = found {
69 return Ok(relid);
70 }
71 let name = rv.relname.as_deref().unwrap_or_default();
72 Err(match rv.schemaname.as_deref() {
73 Some(schema) if an.schema(schema).is_none() => {
74 Error::new(SqlState::UNDEFINED_SCHEMA, format!("schema \"{schema}\" does not exist"))
75 }
76 Some(schema) => Error::new(
77 SqlState::UNDEFINED_TABLE,
78 format!("relation \"{schema}.{name}\" does not exist"),
79 ),
80 None => {
81 Error::new(SqlState::UNDEFINED_TABLE, format!("relation \"{name}\" does not exist"))
82 }
83 })
84}
85
86struct Locker<'a, 'f> {
88 an: Analyzer<'a>,
89 allowed: &'f mut Allowed<'f>,
90 take: &'f mut Take<'f>,
91 ancestors: Vec<u32>,
93}
94
95impl Locker<'_, '_> {
96 fn children(&mut self, relid: u32) -> Result<()> {
98 let children = self.an.env.catalog().map(|c| c.all_inheritors(relid)).unwrap_or_default();
99 for child in children {
100 if !(self.take)(child)? {
101 return Err(not_free(&class_name(self.an.env, child)));
102 }
103 }
104 Ok(())
105 }
106
107 fn view(&mut self, relid: u32, user: u32) -> Result<()> {
109 let Some(query) = self.an.view_query(relid)? else { return Ok(()) };
110 self.view_query(relid, &query, user)
111 }
112
113 fn view_query(&mut self, relid: u32, query: &Query, user: u32) -> Result<()> {
115 let rel = self.an.env.catalog().and_then(|c| c.relation(relid));
116 let invoker = rel.is_some_and(|r| {
117 r.options
118 .iter()
119 .filter_map(|o| o.strip_prefix("security_invoker="))
120 .next_back()
121 .and_then(parse_bool)
122 .unwrap_or(false)
123 });
124 let check_as = match rel {
125 Some(rel) if !invoker => rel.owner,
126 _ => user,
127 };
128 self.ancestors.push(relid);
129 let result = self.walk(query, check_as, user);
130 self.ancestors.pop();
131 result
132 }
133
134 fn walk(&mut self, query: &Query, check_as: u32, user: u32) -> Result<()> {
136 for relation in &query.relations {
137 if relation.oid == 0 || relation.cte.is_some() {
138 continue;
139 }
140 let kind = relkind(self.an.env, relation.oid);
141 if !matches!(kind, b'r' | b'p' | b'v') || self.ancestors.contains(&relation.oid) {
142 continue;
143 }
144 if !(self.allowed)(relation.oid, check_as)? {
145 return Err(denied(kind, &class_name(self.an.env, relation.oid)));
146 }
147 if !(self.take)(relation.oid)? {
148 return Err(not_free(&class_name(self.an.env, relation.oid)));
149 }
150 if kind == b'v' {
151 if let Some(sub) = relation.subquery.as_deref() {
152 self.view_query(relation.oid, sub, user)?;
153 }
154 } else if !relation.only {
155 self.children(relation.oid)?;
156 }
157 }
158 for expr in query.exprs() {
159 self.walk_expr(expr, check_as, user)?;
160 }
161 for cte in &query.ctes {
162 self.walk(&cte.query, check_as, user)?;
163 }
164 for relation in &query.relations {
165 if relation.oid == 0
166 && relation.cte.is_none()
167 && let Some(sub) = relation.subquery.as_deref()
168 {
169 self.walk(sub, check_as, user)?;
170 }
171 }
172 Ok(())
173 }
174
175 fn walk_expr(&mut self, expr: &Expr, check_as: u32, user: u32) -> Result<()> {
177 if let ExprKind::SubLink(sub) = &expr.kind {
178 self.walk(&sub.query, check_as, user)?;
179 }
180 for child in expr.children() {
181 self.walk_expr(child, check_as, user)?;
182 }
183 Ok(())
184 }
185}
186
187pub(crate) fn relkind(env: &dyn Env, relid: u32) -> u8 {
189 match env.catalog().and_then(|c| c.relation(relid)) {
190 Some(rel) => match rel.kind {
191 RelKind::Table => b'r',
192 RelKind::Index => b'i',
193 RelKind::Sequence => b'S',
194 RelKind::Toast => b't',
195 RelKind::View => b'v',
196 RelKind::Composite => b'c',
197 RelKind::Partitioned => b'p',
198 RelKind::PartitionedIndex => b'I',
199 RelKind::MatView => b'm',
200 },
201 None => builtin::class_by_oid(relid).map_or(0, |row| row.kind),
202 }
203}
204
205pub(super) fn class_name(env: &dyn Env, relid: u32) -> String {
207 match env.catalog().and_then(|c| c.relation(relid)) {
208 Some(rel) => rel.name.clone(),
209 None => builtin::named_by_oid(Named::Class, relid)
210 .map_or_else(String::new, |n| n.name.to_string()),
211 }
212}
213
214fn denied(kind: u8, name: &str) -> Error {
216 let object = if kind == b'v' { "view" } else { "table" };
217 Error::new(SqlState::INSUFFICIENT_PRIVILEGE, format!("permission denied for {object} {name}"))
218}
219
220fn not_free(name: &str) -> Error {
222 Error::new(
223 SqlState::LOCK_NOT_AVAILABLE,
224 format!("could not obtain lock on relation \"{name}\""),
225 )
226}