1use rupg_catalog::{ObjRef, RelKind};
6use rupg_common::{Error, Result, SqlState};
7use rupg_pgcatalog::builtin::{self, Named, Owned};
8use rupg_types::{RegKind, Value};
9
10use crate::{Call, Kernel, Session, bad_value, reg};
11
12pub(crate) const PG_CLASS: u32 = 1259;
14pub(crate) const PG_PROC: u32 = 1255;
16pub(crate) const PG_TYPE: u32 = 1247;
18pub(crate) const PG_CAST: u32 = 2605;
20pub(crate) const PG_COLLATION: u32 = 3456;
22pub(crate) const PG_CONSTRAINT: u32 = 2606;
24pub(crate) const PG_CONVERSION: u32 = 2607;
26pub(crate) const PG_ATTRDEF: u32 = 2604;
28pub(crate) const PG_LANGUAGE: u32 = 2612;
30pub(crate) const PG_LARGEOBJECT: u32 = 2613;
32pub(crate) const PG_OPERATOR: u32 = 2617;
34pub(crate) const PG_OPCLASS: u32 = 2616;
36pub(crate) const PG_OPFAMILY: u32 = 2753;
38pub(crate) const PG_AM: u32 = 2601;
40pub(crate) const PG_AMOP: u32 = 2602;
42pub(crate) const PG_AMPROC: u32 = 2603;
44pub(crate) const PG_REWRITE: u32 = 2618;
46pub(crate) const PG_TRIGGER: u32 = 2620;
48pub(crate) const PG_NAMESPACE: u32 = 2615;
50pub(crate) const PG_TS_PARSER: u32 = 3601;
52pub(crate) const PG_TS_DICT: u32 = 3600;
54pub(crate) const PG_TS_TEMPLATE: u32 = 3764;
56pub(crate) const PG_TS_CONFIG: u32 = 3602;
58pub(crate) const PG_AUTHID: u32 = 1260;
60pub(crate) const PG_DATABASE: u32 = 1262;
62pub(crate) const PG_TABLESPACE: u32 = 1213;
64pub(crate) const PG_EXTENSION: u32 = 3079;
66pub(crate) const PG_STATISTIC_EXT: u32 = 3381;
68
69pub(crate) const NO_OBJECTS: [u32; 13] =
71 [1261, 2328, 1417, 1418, 826, 3466, 6243, 3256, 6104, 6237, 6106, 6100, 3576];
72
73pub(crate) const SYSTEM_COLUMNS: [&str; 6] = ["ctid", "xmin", "cmin", "xmax", "cmax", "tableoid"];
75
76pub(crate) fn oid_arg(args: &[Value], index: usize) -> Result<u32> {
78 args.get(index).and_then(Value::as_oid).ok_or_else(bad_value)
79}
80
81fn describe_object(call: &Call<'_>, args: &[Value]) -> Result<Value> {
83 let (class, oid) = (oid_arg(args, 0)?, oid_arg(args, 1)?);
84 let sub = args
85 .get(2)
86 .and_then(Value::as_i64)
87 .and_then(|n| i32::try_from(n).ok())
88 .ok_or_else(bad_value)?;
89 if class == 0 && oid == 0 {
90 return Ok(Value::Null);
91 }
92 Ok(describe(ObjRef { class, oid, sub }, call.session)?.map_or(Value::Null, Value::Text))
93}
94
95pub fn describe(object: ObjRef, session: &dyn Session) -> Result<Option<String>> {
101 let oid = object.oid;
102 let catalog = session.catalog();
103 let text = match object.class {
104 PG_CLASS if object.sub != 0 => column_text(oid, object.sub, session),
105 PG_CLASS => relation_text(oid, session),
106 PG_PROC => function_text(oid, session),
107 PG_TYPE => reg::name_of(RegKind::Type, oid, session).map(|t| format!("type {t}")),
108 PG_CAST => cast_text(oid, session),
109 PG_COLLATION => {
110 reg::catalog_text(Named::Collation, oid, session).map(|c| format!("collation {c}"))
111 }
112 PG_CONSTRAINT => catalog.and_then(|c| c.constraint(oid)).and_then(|con| {
113 if con.relation == 0 {
114 return Some(format!("constraint {}", con.name));
115 }
116 Some(format!("constraint {} on {}", con.name, relation_text(con.relation, session)?))
117 }),
118 PG_CONVERSION => {
119 named_text(Named::Conversion, oid, session).map(|c| format!("conversion {c}"))
120 }
121 PG_ATTRDEF => catalog
122 .and_then(|c| c.defaults().find(|d| d.oid == oid))
123 .and_then(|d| column_text(d.relation, i32::from(d.column), session))
124 .map(|column| format!("default value for {column}")),
125 PG_LANGUAGE => builtin::owned_by_oid(Owned::Language, oid)
126 .map(|l| l.name)
127 .or_else(|| catalog?.languages().find(|l| l.oid == oid).map(|l| l.name.as_str()))
128 .map(|name| format!("language {name}")),
129 PG_LARGEOBJECT => {
130 builtin::owned_by_oid(Owned::LargeObject, oid).map(|_| format!("large object {oid}"))
131 }
132 PG_OPERATOR => {
133 reg::name_of(RegKind::Operator, oid, session).map(|o| format!("operator {o}"))
134 }
135 PG_OPCLASS => {
136 method_text(Named::Opclass, oid, session).map(|c| format!("operator class {c}"))
137 }
138 PG_OPFAMILY => opfamily_text(oid, session),
139 PG_AM => builtin::access_method_name(oid).map(|name| format!("access method {name}")),
140 PG_AMOP => {
141 amop_parts(oid, session).and_then(|(family, left, right, strategy, operator)| {
142 Some(format!(
143 "operator {strategy} ({}, {}) of {}: {}",
144 reg::type_text(left, None, session).ok()?,
145 reg::type_text(right, None, session).ok()?,
146 opfamily_text(family, session)?,
147 reg::name_of(RegKind::Operator, operator, session)?
148 ))
149 })
150 }
151 PG_AMPROC => amproc_parts(oid, session).and_then(|(family, left, right, number, proc)| {
152 Some(format!(
153 "function {number} ({}, {}) of {}: {}",
154 reg::type_text(left, None, session).ok()?,
155 reg::type_text(right, None, session).ok()?,
156 opfamily_text(family, session)?,
157 procedure_text(proc, session)?
158 ))
159 }),
160 PG_REWRITE => catalog.and_then(|c| match c.view_of_rule(oid) {
161 Some(view) => Some(format!("rule _RETURN on {}", relation_text(view.oid, session)?)),
162 None => {
163 let rule = c.rule(oid)?;
164 Some(format!("rule {} on {}", rule.name, relation_text(rule.relation, session)?))
165 }
166 }),
167 PG_TRIGGER => catalog.and_then(|c| c.trigger(oid)).and_then(|t| {
168 Some(format!("trigger {} on {}", t.name, relation_text(t.relation, session)?))
169 }),
170 PG_NAMESPACE => reg::namespace_name(oid, session).map(|name| format!("schema {name}")),
171 PG_TS_PARSER => ts_text(Named::Parser, "parser", oid, session),
172 PG_TS_DICT => ts_text(Named::Dictionary, "dictionary", oid, session),
173 PG_TS_TEMPLATE => ts_text(Named::Template, "template", oid, session),
174 PG_TS_CONFIG => ts_text(Named::Config, "configuration", oid, session),
175 PG_AUTHID => rupg_pgcatalog::roles::current().name(oid).map(|name| format!("role {name}")),
176 PG_DATABASE => match builtin::named_by_oid(Named::Database, oid) {
177 Some(row) => Some(format!("database {}", row.name)),
178 None => session
179 .databases()
180 .iter()
181 .find(|d| d.oid == oid)
182 .map(|d| format!("database {}", d.name)),
183 },
184 PG_TABLESPACE => {
185 crate::dbsize::tablespace_name(oid, session).map(|name| format!("tablespace {name}"))
186 }
187 PG_EXTENSION => catalog
188 .and_then(|c| c.extensions().find(|e| e.oid == oid))
189 .map(|e| format!("extension {}", e.name)),
190 PG_STATISTIC_EXT => catalog.and_then(|c| c.stat_ext(oid)).and_then(|s| {
191 let name = if reg::stat_ext_visible(oid, session)? {
192 reg::qualified(None, &s.name)
193 } else {
194 reg::qualified(reg::namespace_name(s.namespace, session), &s.name)
195 };
196 Some(format!("statistics object {name}"))
197 }),
198 rupg_catalog::PG_DEFAULT_ACL => catalog.and_then(|c| c.default_acl(oid)).and_then(|d| {
199 let kind = match d.objtype {
200 b'r' => "relations",
201 b'S' => "sequences",
202 b'f' => "functions",
203 b'T' => "types",
204 b'n' => "schemas",
205 _ => "large objects",
206 };
207 let roles = rupg_pgcatalog::roles::current();
208 let role = roles.name(d.role)?;
209 let mut text = format!("default privileges on new {kind} belonging to role {role}");
210 if d.namespace != 0 {
211 text.push_str(&format!(
212 " in schema {}",
213 reg::namespace_name(d.namespace, session)?
214 ));
215 }
216 Some(text)
217 }),
218 class if NO_OBJECTS.contains(&class) => None,
219 class => {
220 return Err(Error::new(
221 SqlState::INTERNAL_ERROR,
222 format!("unsupported object class: {class}"),
223 ));
224 }
225 };
226 Ok(text)
227}
228
229fn relation_text(oid: u32, session: &dyn Session) -> Option<String> {
231 let (code, name, namespace) = match builtin::class_by_oid(oid) {
232 Some(row) => (row.kind, row.name, row.namespace),
233 None => {
234 let rel = session.catalog()?.relation(oid)?;
235 (u8::try_from(rel.kind.code()).ok()?, rel.name.as_str(), rel.namespace)
236 }
237 };
238 let word = match code {
239 b'i' | b'I' => "index",
240 b'S' => "sequence",
241 b't' => "toast table",
242 b'v' => "view",
243 b'm' => "materialized view",
244 b'c' => "composite type",
245 b'f' => "foreign table",
246 _ => "table",
247 };
248 let schema = if reg::class_visible(oid, session)? {
249 None
250 } else {
251 reg::namespace_name(namespace, session)
252 };
253 Some(format!("{word} {}", reg::qualified(schema, name)))
254}
255
256fn column_text(oid: u32, number: i32, session: &dyn Session) -> Option<String> {
258 let name = column_name(oid, number, session)?;
259 Some(format!("column {name} of {}", relation_text(oid, session)?))
260}
261
262pub(crate) fn column_name(oid: u32, number: i32, session: &dyn Session) -> Option<&str> {
264 let number = i16::try_from(number).ok()?;
265 let name = match builtin::class_by_oid(oid) {
266 Some(_) => builtin::attributes(oid).iter().find(|a| a.num == number)?.name,
267 None => {
268 let rel = session.catalog()?.relation(oid)?;
269 if number > 0 {
270 rel.column(number)?.name.as_str()
271 } else if matches!(
272 rel.kind,
273 RelKind::Table
274 | RelKind::Partitioned
275 | RelKind::Toast
276 | RelKind::Sequence
277 | RelKind::MatView
278 ) {
279 SYSTEM_COLUMNS.get(usize::try_from(-i32::from(number) - 1).ok()?)?
280 } else {
281 return None;
282 }
283 }
284 };
285 Some(name)
286}
287
288fn function_text(oid: u32, session: &dyn Session) -> Option<String> {
290 Some(format!("function {}", procedure_text(oid, session)?))
291}
292
293fn procedure_text(oid: u32, session: &dyn Session) -> Option<String> {
295 let catalog = session.catalog();
296 let (name, namespace, args) = match builtin::proc_by_oid(oid) {
297 Some(proc) => (proc.name, proc.namespace, proc.argtypes),
298 None => {
299 let function = catalog?.function(oid)?;
300 (function.name.as_str(), function.namespace, function.argtypes.as_slice())
301 }
302 };
303 let first = reg::path(session).into_iter().find_map(|ns| {
304 builtin::procs_named(name)
305 .find(|p| p.namespace == ns && p.argtypes == args)
306 .map(|p| p.oid)
307 .or_else(|| {
308 catalog?
309 .functions()
310 .find(|f| f.namespace == ns && f.name == name && f.argtypes == args)
311 .map(|f| f.oid)
312 })
313 });
314 let schema = if first == Some(oid) { None } else { reg::namespace_name(namespace, session) };
315 let args: Vec<String> =
316 args.iter().map(|&ty| reg::type_text(ty, None, session)).collect::<Result<_>>().ok()?;
317 Some(format!("{}({})", reg::qualified(schema, name), args.join(",")))
318}
319
320fn cast_text(oid: u32, session: &dyn Session) -> Option<String> {
322 let (source, target) = match builtin::casts().iter().find(|c| c.oid == oid) {
323 Some(cast) => (cast.source, cast.target),
324 None => {
325 let cast = session.catalog()?.casts().find(|c| c.oid == oid)?;
326 (cast.source, cast.target)
327 }
328 };
329 Some(format!(
330 "cast from {} to {}",
331 reg::type_text(source, None, session).ok()?,
332 reg::type_text(target, None, session).ok()?
333 ))
334}
335
336fn named_text(kind: Named, oid: u32, session: &dyn Session) -> Option<String> {
338 let row = builtin::named_by_oid(kind, oid)?;
339 let schema = if reg::visible(kind, row, session) {
340 None
341 } else {
342 reg::namespace_name(row.namespace, session)
343 };
344 Some(reg::qualified(schema, row.name))
345}
346
347fn method_text(kind: Named, oid: u32, session: &dyn Session) -> Option<String> {
349 let (name, namespace, method) = reg::method_row(kind, oid, session)?;
350 let schema = if reg::method_visible(kind, oid, session) {
351 None
352 } else {
353 reg::namespace_name(namespace, session)
354 };
355 Some(format!(
356 "{} for access method {}",
357 reg::qualified(schema, &name),
358 builtin::access_method_name(method)?
359 ))
360}
361
362pub(crate) fn amop_parts(oid: u32, session: &dyn Session) -> Option<(u32, u32, u32, i16, u32)> {
364 if let Some(a) = builtin::amop_by_oid(oid) {
365 return Some((a.family, a.left, a.right, a.strategy, a.operator));
366 }
367 let a = session.catalog()?.amop(oid)?;
368 Some((a.family, a.left, a.right, a.strategy, a.operator))
369}
370
371pub(crate) fn amproc_parts(oid: u32, session: &dyn Session) -> Option<(u32, u32, u32, i16, u32)> {
373 if let Some(a) = builtin::amproc_by_oid(oid) {
374 return Some((a.family, a.left, a.right, a.number, a.proc));
375 }
376 let a = session.catalog()?.amproc(oid)?;
377 Some((a.family, a.left, a.right, a.number, a.function))
378}
379
380fn opfamily_text(oid: u32, session: &dyn Session) -> Option<String> {
382 method_text(Named::Opfamily, oid, session).map(|f| format!("operator family {f}"))
383}
384
385fn ts_text(kind: Named, word: &str, oid: u32, session: &dyn Session) -> Option<String> {
387 reg::catalog_text(kind, oid, session).map(|name| format!("text search {word} {name}"))
388}
389
390pub(crate) fn by_src(src: &str) -> Option<Kernel> {
392 let kernel: Kernel = match src {
393 "pg_describe_object" => describe_object,
394 _ => return None,
395 };
396 Some(kernel)
397}