1use std::borrow::Cow;
8
9use rupg_analyze::Env;
10use rupg_catalog::Catalog;
11use rupg_common::{Error, Result, SqlState};
12use rupg_pgcatalog::builtin::{self, Named, NamedRow, OperatorRow, ProcRow};
13use rupg_types::{self as types, Record, RegInput, RegKind, Value, oid, qualified_name_list};
14
15use crate::text::quote_identifier;
16use crate::{Call, Kernel, Session, bad_value, type_error};
17
18const PG_CATALOG: u32 = 11;
20const UTF8: i32 = 6;
22const UTF8_NAME: &str = "UTF8";
24const FUNC_MAX_ARGS: usize = 100;
26
27type Soft<T> = Result<Result<T>>;
29
30pub(crate) struct SessionEnv<'a>(pub(crate) &'a dyn Session);
32
33impl Env for SessionEnv<'_> {
34 fn search_path(&self) -> Vec<u32> {
35 self.0.schemas().iter().filter_map(|name| namespace_oid(name, self.0)).collect()
36 }
37
38 fn namespace(&self, name: &str) -> Option<u32> {
39 namespace_oid(name, self.0)
40 }
41
42 fn database(&self) -> String {
43 self.0.database().to_string()
44 }
45
46 fn input(&self, ty: u32, text: &str, typmod: i32) -> Result<Value> {
47 crate::io::input(ty, text, typmod, self.0)
48 }
49
50 fn catalog(&self) -> Option<&Catalog> {
51 self.0.catalog()
52 }
53
54 fn temp_number(&self) -> Option<i32> {
55 self.0.temp_number()
56 }
57}
58
59pub(crate) fn temp_namespace(session: &dyn Session) -> u32 {
61 session
62 .catalog()
63 .map_or(0, |catalog| rupg_analyze::temp_namespace(&SessionEnv(session), catalog))
64}
65
66pub(crate) fn is_own_temp(oid: u32, session: &dyn Session) -> bool {
68 let Some(n) = session.temp_number() else { return false };
69 namespace_name(oid, session)
70 .is_some_and(|name| name == format!("pg_temp_{n}") || name == format!("pg_toast_temp_{n}"))
71}
72
73fn namespace_oid(name: &str, session: &dyn Session) -> Option<u32> {
75 if name == "pg_temp" {
76 return Some(temp_namespace(session)).filter(|&oid| oid != 0);
77 }
78 builtin::named(Named::Namespace)
79 .iter()
80 .find(|row| row.name == name && !schema_dropped(row.oid, session))
81 .map(|row| row.oid)
82 .or_else(|| session.catalog()?.schema_by_name(name).map(|s| s.oid))
83}
84
85pub(crate) fn namespace_name(oid: u32, session: &dyn Session) -> Option<&str> {
87 builtin::named_by_oid(Named::Namespace, oid)
88 .filter(|row| !schema_dropped(row.oid, session))
89 .map(|row| row.name)
90 .or_else(|| session.catalog()?.schema(oid).map(|s| s.name.as_str()))
91}
92
93fn schema_dropped(oid: u32, session: &dyn Session) -> bool {
95 session.catalog().is_some_and(|catalog| catalog.schema_dropped(oid))
96}
97
98pub(crate) fn path(session: &dyn Session) -> Vec<u32> {
100 let mut path = temp_path(session);
101 let temp = temp_namespace(session);
102 path.retain(|&ns| ns != temp);
103 path
104}
105
106pub(crate) fn stat_ext_visible(oid: u32, session: &dyn Session) -> Option<bool> {
108 let catalog = session.catalog()?;
109 let stat = catalog.stat_ext(oid)?;
110 let first = path(session)
111 .into_iter()
112 .find(|&ns| ns == stat.namespace || catalog.stat_ext_by_name(ns, &stat.name).is_some());
113 Some(first == Some(stat.namespace))
114}
115
116pub(crate) fn temp_path(session: &dyn Session) -> Vec<u32> {
118 let mut path: Vec<u32> =
119 session.schemas().iter().filter_map(|n| namespace_oid(n, session)).collect();
120 if !path.contains(&PG_CATALOG) {
121 path.insert(0, PG_CATALOG);
122 }
123 let temp = temp_namespace(session);
124 if temp != 0 && !path.contains(&temp) {
125 path.insert(0, temp);
126 }
127 path
128}
129
130pub(crate) fn qualified(schema: Option<&str>, name: &str) -> String {
132 match schema {
133 Some(schema) => format!("{}.{}", quote_identifier(schema), quote_identifier(name)),
134 None => quote_identifier(name),
135 }
136}
137
138fn name_list(names: &[Cow<'_, str>]) -> String {
140 names.join(".")
141}
142
143fn soft<T>(state: SqlState, message: impl Into<String>) -> Soft<T> {
145 Ok(Err(Error::new(state, message)))
146}
147
148fn deconstruct<'n>(
154 names: &'n [Cow<'_, str>],
155 session: &dyn Session,
156) -> Result<(Option<&'n str>, &'n str)> {
157 match names {
158 [name] => Ok((None, name)),
159 [schema, name] => Ok((Some(schema), name)),
160 [database, schema, name] => {
161 if database != session.database() {
162 return Err(Error::new(
163 SqlState::FEATURE_NOT_SUPPORTED,
164 format!("cross-database references are not implemented: {}", name_list(names)),
165 ));
166 }
167 Ok((Some(schema), name))
168 }
169 _ => Err(Error::new(
170 SqlState::SYNTAX_ERROR,
171 format!("improper qualified name (too many dotted names): {}", name_list(names)),
172 )),
173 }
174}
175
176fn spaces(schema: Option<&str>, session: &dyn Session) -> Vec<u32> {
178 match schema {
179 Some(schema) => namespace_oid(schema, session).into_iter().collect(),
180 None => path(session),
181 }
182}
183
184fn find(kind: Named, spaces: &[u32], name: &str, method: u32) -> Option<&'static NamedRow> {
186 let rows = builtin::named(kind);
187 spaces.iter().find_map(|&ns| {
188 let here = |encoding: i32| {
189 rows.iter().find(|r| {
190 r.namespace == ns && r.name == name && r.method == method && r.encoding == encoding
191 })
192 };
193 if kind == Named::Collation { here(UTF8).or_else(|| here(-1)) } else { here(-1) }
194 })
195}
196
197pub(crate) fn visible(kind: Named, row: &NamedRow, session: &dyn Session) -> bool {
199 find(kind, &path(session), row.name, row.method).is_some_and(|found| found.oid == row.oid)
200}
201
202pub(crate) fn method_row(
204 kind: Named,
205 oid: u32,
206 session: &dyn Session,
207) -> Option<(String, u32, u32)> {
208 if let Some(row) = builtin::named_by_oid(kind, oid) {
209 return Some((row.name.to_string(), row.namespace, row.method));
210 }
211 let catalog = session.catalog()?;
212 match kind {
213 Named::Opclass => catalog.opclass(oid).map(|c| (c.name.clone(), c.namespace, c.method)),
214 Named::Opfamily => catalog.opfamily(oid).map(|f| (f.name.clone(), f.namespace, f.method)),
215 _ => None,
216 }
217}
218
219pub(crate) fn method_visible(kind: Named, oid: u32, session: &dyn Session) -> bool {
221 let Some((name, _, method)) = method_row(kind, oid, session) else { return false };
222 let catalog = session.catalog();
223 let first = path(session).into_iter().find_map(|ns| {
224 find(kind, &[ns], &name, method).map(|row| row.oid).or_else(|| match kind {
225 Named::Opclass => catalog?
226 .opclasses()
227 .find(|c| c.namespace == ns && c.name == name && c.method == method)
228 .map(|c| c.oid),
229 _ => catalog?
230 .opfamilies()
231 .find(|f| f.namespace == ns && f.name == name && f.method == method)
232 .map(|f| f.oid),
233 })
234 });
235 first == Some(oid)
236}
237
238pub(crate) fn catalog_name(kind: Named, oid: u32, session: &dyn Session) -> Option<(String, u32)> {
240 if let Some(row) = builtin::named_by_oid(kind, oid) {
241 return Some((row.name.to_string(), row.namespace));
242 }
243 let catalog = session.catalog()?;
244 let pair = |name: &str, namespace: u32| (name.to_string(), namespace);
245 match kind {
246 Named::Template => catalog.ts_template(oid).map(|t| pair(&t.name, t.namespace)),
247 Named::Parser => catalog.ts_parser(oid).map(|p| pair(&p.name, p.namespace)),
248 Named::Dictionary => catalog.ts_dictionary(oid).map(|d| pair(&d.name, d.namespace)),
249 Named::Config => catalog.ts_config(oid).map(|c| pair(&c.name, c.namespace)),
250 Named::Collation => catalog.collation(oid).map(|c| pair(&c.name, c.namespace)),
251 _ => None,
252 }
253}
254
255fn catalog_find(kind: Named, spaces: &[u32], name: &str, session: &dyn Session) -> Option<u32> {
257 let catalog = session.catalog();
258 spaces.iter().find_map(|&ns| {
259 find(kind, &[ns], name, 0).map(|row| row.oid).or_else(|| match kind {
260 Named::Template => catalog?.ts_template_by_name(ns, name).map(|t| t.oid),
261 Named::Parser => catalog?.ts_parser_by_name(ns, name).map(|p| p.oid),
262 Named::Dictionary => catalog?.ts_dictionary_by_name(ns, name).map(|d| d.oid),
263 Named::Config => catalog?.ts_config_by_name(ns, name).map(|c| c.oid),
264 Named::Collation => catalog?.collation_by_name(ns, name).map(|c| c.oid),
265 _ => None,
266 })
267 })
268}
269
270pub fn ts_config_setting(text: &str, session: &dyn Session) -> Result<Option<String>> {
276 let Ok(oid) = input(RegKind::Config, text, session)? else { return Ok(None) };
277 let Some((name, namespace)) = catalog_name(Named::Config, oid, session) else {
278 return Ok(None);
279 };
280 Ok(Some(qualified(namespace_name(namespace, session), &name)))
281}
282
283pub(crate) fn ts_parser_oid(text: &str, session: &dyn Session) -> Result<u32> {
289 let names = qualified_name_list(text).map_err(type_error)?;
290 let (schema, name) = deconstruct(&names, session)?;
291 if let Some(schema) = schema
292 && namespace_oid(schema, session).is_none()
293 {
294 return Err(Error::new(
295 SqlState::UNDEFINED_SCHEMA,
296 format!("schema \"{schema}\" does not exist"),
297 ));
298 }
299 catalog_find(Named::Parser, &spaces(schema, session), name, session).ok_or_else(|| {
300 Error::new(
301 SqlState::UNDEFINED_OBJECT,
302 format!("text search parser \"{}\" does not exist", name_list(&names)),
303 )
304 })
305}
306
307pub(crate) fn catalog_visible(kind: Named, oid: u32, session: &dyn Session) -> Option<bool> {
309 let (name, _) = catalog_name(kind, oid, session)?;
310 Some(catalog_find(kind, &path(session), &name, session) == Some(oid))
311}
312
313pub(crate) fn catalog_text(kind: Named, oid: u32, session: &dyn Session) -> Option<String> {
315 let (name, namespace) = catalog_name(kind, oid, session)?;
316 let schema = if catalog_find(kind, &path(session), &name, session) == Some(oid) {
317 None
318 } else {
319 namespace_name(namespace, session)
320 };
321 Some(qualified(schema, &name))
322}
323
324fn find_class(spaces: &[u32], name: &str, session: &dyn Session) -> Option<u32> {
326 spaces.iter().find_map(|&ns| {
327 find(Named::Class, &[ns], name, 0)
328 .map(|row| row.oid)
329 .or_else(|| session.catalog()?.relation_by_name(ns, name).map(|r| r.oid))
330 })
331}
332
333fn find_type(spaces: &[u32], name: &str, session: &dyn Session) -> Option<u32> {
335 spaces.iter().find_map(|&ns| {
336 builtin::type_by_name(ns, name)
337 .map(|row| row.oid)
338 .or_else(|| session.catalog()?.type_by_name(ns, name).map(|t| t.oid))
339 })
340}
341
342pub(crate) fn class_name(oid: u32, session: &dyn Session) -> Option<(&str, u32)> {
344 builtin::named_by_oid(Named::Class, oid)
345 .map(|row| (row.name, row.namespace))
346 .or_else(|| session.catalog()?.relation(oid).map(|r| (r.name.as_str(), r.namespace)))
347}
348
349pub(crate) fn type_name(oid: u32, session: &dyn Session) -> Option<(&str, u32)> {
351 builtin::type_by_oid(oid)
352 .map(|row| (row.name, row.namespace))
353 .or_else(|| session.catalog()?.type_by_oid(oid).map(|t| (t.name.as_str(), t.namespace)))
354 .or_else(|| row_type_relation(oid, session).map(|r| (r.name.as_str(), r.namespace)))
355}
356
357fn row_type_relation(ty: u32, session: &dyn Session) -> Option<&rupg_catalog::Relation> {
359 session.catalog()?.relations().find(|r| ty != 0 && r.row_type == ty)
360}
361
362pub(crate) fn class_visible(oid: u32, session: &dyn Session) -> Option<bool> {
364 let (name, _) = class_name(oid, session)?;
365 Some(find_class(&temp_path(session), name, session) == Some(oid))
366}
367
368pub(crate) fn type_visible(oid: u32, session: &dyn Session) -> Option<bool> {
370 let (name, _) = type_name(oid, session)?;
371 Some(find_type(&temp_path(session), name, session) == Some(oid))
372}
373
374fn functions(spaces: &[u32], name: &str, nargs: Option<usize>) -> Vec<&'static ProcRow> {
376 let mut found: Vec<&'static ProcRow> = Vec::new();
377 for &ns in spaces {
378 for proc in builtin::procs_named(name) {
379 if proc.namespace == ns
380 && nargs.is_none_or(|n| proc.argtypes.len() == n)
381 && !found.iter().any(|f| f.argtypes == proc.argtypes)
382 {
383 found.push(proc);
384 }
385 }
386 }
387 found
388}
389
390fn operators(spaces: &[u32], name: &str) -> Vec<&'static OperatorRow> {
392 let mut found: Vec<&'static OperatorRow> = Vec::new();
393 for &ns in spaces {
394 for op in builtin::operators_named(name) {
395 if op.namespace == ns && !found.iter().any(|f| (f.left, f.right) == (op.left, op.right))
396 {
397 found.push(op);
398 }
399 }
400 }
401 found
402}
403
404pub(crate) fn function_visible(proc: &ProcRow, session: &dyn Session) -> bool {
406 functions(&path(session), proc.name, Some(proc.argtypes.len()))
407 .iter()
408 .find(|f| f.argtypes == proc.argtypes)
409 .is_some_and(|f| f.oid == proc.oid)
410}
411
412pub(crate) fn operator_visible(op: &OperatorRow, session: &dyn Session) -> bool {
414 operators(&path(session), op.name)
415 .iter()
416 .find(|f| (f.left, f.right) == (op.left, op.right))
417 .is_some_and(|f| f.oid == op.oid)
418}
419
420const ARRAY_SUBSCRIPT_HANDLER: u32 = 6179;
422const VARHDRSZ: i32 = 4;
424
425pub(crate) fn type_text(ty: u32, typmod: Option<i32>, session: &dyn Session) -> Result<String> {
431 if ty == 0 {
432 return Ok("-".to_string());
433 }
434 format_type(ty, typmod, false, session)
435}
436
437pub(crate) fn type_text_qualified(ty: u32, session: &dyn Session) -> Option<String> {
439 type_name(ty, session)?;
440 format_type(ty, None, true, session).ok().filter(|text| !text.starts_with("???"))
441}
442
443fn format_type(
445 ty: u32,
446 typmod: Option<i32>,
447 qualify: bool,
448 session: &dyn Session,
449) -> Result<String> {
450 let element = match builtin::type_by_oid(ty) {
452 Some(row) if row.elem != 0 && row.subscript == ARRAY_SUBSCRIPT_HANDLER => {
453 (row.storage != b'p').then_some(row.elem)
454 }
455 Some(_) => None,
456 None => match session.catalog().and_then(|c| c.type_by_oid(ty)) {
457 Some(user) => (user.element != 0).then_some(user.element),
458 None if row_type_relation(ty, session).is_some() => None,
459 None => return Ok("???".to_string()),
460 },
461 };
462 match element {
463 Some(element) if type_name(element, session).is_none() => Ok("???[]".to_string()),
464 Some(element) => Ok(format!("{}[]", base_text(element, typmod, qualify, session)?)),
465 None => base_text(ty, typmod, qualify, session),
466 }
467}
468
469fn base_text(ty: u32, typmod: Option<i32>, qualify: bool, session: &dyn Session) -> Result<String> {
471 let given = typmod.is_some();
472 let with = typmod.filter(|m| *m >= 0);
473 let special = match (ty, with) {
474 (oid::BIT, Some(m)) => Some(format!("bit({m})")),
475 (oid::BIT, None) if !given => Some("bit".to_string()),
476 (oid::BOOL, _) => Some("boolean".to_string()),
477 (oid::BPCHAR, Some(m)) => Some(format!("character{}", length(m))),
478 (oid::BPCHAR, None) if !given => Some("character".to_string()),
479 (oid::FLOAT4, _) => Some("real".to_string()),
480 (oid::FLOAT8, _) => Some("double precision".to_string()),
481 (oid::INT2, _) => Some("smallint".to_string()),
482 (oid::INT4, _) => Some("integer".to_string()),
483 (oid::INT8, _) => Some("bigint".to_string()),
484 (oid::NUMERIC, Some(m)) if m >= VARHDRSZ => {
485 let bits = m - VARHDRSZ;
486 let scale = ((bits & 0x7ff) ^ 1024) - 1024;
487 Some(format!("numeric({},{scale})", (bits >> 16) & 0xffff))
488 }
489 (oid::NUMERIC, _) => Some("numeric".to_string()),
490 (oid::INTERVAL, Some(m)) => Some(format!("interval{}", interval_typmod(m)?)),
491 (oid::INTERVAL, None) => Some("interval".to_string()),
492 (oid::TIME, m) => Some(format!("time{} without time zone", precision(m))),
493 (oid::TIMETZ, m) => Some(format!("time{} with time zone", precision(m))),
494 (oid::TIMESTAMP, m) => Some(format!("timestamp{} without time zone", precision(m))),
495 (oid::TIMESTAMPTZ, m) => Some(format!("timestamp{} with time zone", precision(m))),
496 (oid::VARBIT, m) => Some(format!("bit varying{}", precision(m))),
497 (oid::VARCHAR, m) => Some(format!("character varying{}", m.map_or(String::new(), length))),
498 (oid::JSON, _) => Some("json".to_string()),
499 _ => None,
500 };
501 if let Some(text) = special {
502 return Ok(text);
503 }
504 let Some((name, namespace)) = type_name(ty, session) else { return Ok("???".to_string()) };
505 let schema = if !qualify && type_visible(ty, session) == Some(true) {
506 None
507 } else {
508 namespace_name(namespace, session)
509 };
510 let text = qualified(schema, name);
511 Ok(match with {
513 Some(m) => format!("{text}({m})"),
514 None => text,
515 })
516}
517
518fn precision(typmod: Option<i32>) -> String {
520 typmod.map_or(String::new(), |m| format!("({m})"))
521}
522
523fn interval_typmod(typmod: i32) -> Result<String> {
525 const YEAR: i32 = 1 << 2;
526 const MONTH: i32 = 1 << 1;
527 const DAY: i32 = 1 << 3;
528 const HOUR: i32 = 1 << 10;
529 const MINUTE: i32 = 1 << 11;
530 const SECOND: i32 = 1 << 12;
531 let (precision, range) = types::typmod::interval_precision_range(typmod).unwrap_or_default();
532 let fields = match range {
533 YEAR => " year",
534 MONTH => " month",
535 DAY => " day",
536 HOUR => " hour",
537 MINUTE => " minute",
538 SECOND => " second",
539 r if r == YEAR | MONTH => " year to month",
540 r if r == DAY | HOUR => " day to hour",
541 r if r == DAY | HOUR | MINUTE => " day to minute",
542 r if r == DAY | HOUR | MINUTE | SECOND => " day to second",
543 r if r == HOUR | MINUTE => " hour to minute",
544 r if r == HOUR | MINUTE | SECOND => " hour to second",
545 r if r == MINUTE | SECOND => " minute to second",
546 types::typmod::INTERVAL_FULL_RANGE => "",
547 _ => {
548 return Err(Error::new(
549 SqlState::INTERNAL_ERROR,
550 format!("invalid INTERVAL typmod: 0x{typmod:x}"),
551 ));
552 }
553 };
554 Ok(if precision == types::typmod::INTERVAL_FULL_PRECISION {
555 fields.to_string()
556 } else {
557 format!("{fields}({precision})")
558 })
559}
560
561fn length(typmod: i32) -> String {
563 if typmod > VARHDRSZ { format!("({})", typmod - VARHDRSZ) } else { String::new() }
564}
565
566pub(crate) fn output(kind: RegKind, oid: u32, session: &dyn Session, out: &mut Vec<u8>) {
568 let text = if oid == 0 { None } else { name_of(kind, oid, session) };
569 match text {
570 Some(text) => out.extend_from_slice(text.as_bytes()),
571 None => types::reg_out_oid(kind, oid, out),
572 }
573}
574
575pub(crate) fn name_of(kind: RegKind, oid: u32, session: &dyn Session) -> Option<String> {
577 match kind {
578 RegKind::Class => {
579 let (name, namespace) = class_name(oid, session)?;
580 let schema = if class_visible(oid, session)? {
581 None
582 } else {
583 namespace_name(namespace, session)
584 };
585 Some(qualified(schema, name))
586 }
587 RegKind::Collation => catalog_text(Named::Collation, oid, session),
588 RegKind::Config => catalog_text(Named::Config, oid, session),
589 RegKind::Dictionary => catalog_text(Named::Dictionary, oid, session),
590 RegKind::Namespace => namespace_name(oid, session).map(quote_identifier),
591 RegKind::Role => rupg_pgcatalog::roles::current().name(oid).map(quote_identifier),
592 RegKind::Database => match builtin::named_by_oid(Named::Database, oid) {
593 Some(row) => Some(quote_identifier(row.name)),
594 None => {
595 session.databases().iter().find(|d| d.oid == oid).map(|d| quote_identifier(&d.name))
596 }
597 },
598 RegKind::Type => {
599 type_name(oid, session)?;
600 Some(type_text(oid, None, session).ok()?)
601 }
602 RegKind::Proc => {
603 let proc = builtin::proc_by_oid(oid)?;
604 let only =
605 matches!(functions(&path(session), proc.name, None)[..], [f] if f.oid == oid);
606 let schema = if only { None } else { namespace_name(proc.namespace, session) };
607 Some(qualified(schema, proc.name))
608 }
609 RegKind::Procedure => {
610 let proc = builtin::proc_by_oid(oid)?;
611 let schema = if function_visible(proc, session) {
612 None
613 } else {
614 namespace_name(proc.namespace, session)
615 };
616 let args: Vec<_> = proc
617 .argtypes
618 .iter()
619 .map(|&ty| type_text(ty, None, session).unwrap_or_else(|_| "???".to_string()))
620 .collect();
621 Some(format!("{}({})", qualified(schema, proc.name), args.join(",")))
622 }
623 RegKind::Oper => {
624 let op = builtin::operator_by_oid(oid)?;
625 let only = matches!(operators(&path(session), op.name)[..], [f] if f.oid == oid);
626 match namespace_name(op.namespace, session) {
627 Some(schema) if !only => Some(format!("{}.{}", quote_identifier(schema), op.name)),
628 _ => Some(op.name.to_string()),
629 }
630 }
631 RegKind::Operator => {
632 let op = builtin::operator_by_oid(oid)?;
633 let mut text = String::new();
634 if !operator_visible(op, session)
635 && let Some(schema) = namespace_name(op.namespace, session)
636 {
637 text.push_str("e_identifier(schema));
638 text.push('.');
639 }
640 let arg =
641 |ty: u32| if ty == 0 { Cow::Borrowed("NONE") } else { types::format_type(ty) };
642 Some(format!("{text}{}({},{})", op.name, arg(op.left), arg(op.right)))
643 }
644 }
645}
646
647pub(crate) fn input(kind: RegKind, text: &str, session: &dyn Session) -> Soft<u32> {
653 match types::reg_in(kind, text) {
654 Ok(RegInput::Oid(oid)) => return Ok(Ok(oid)),
655 Ok(RegInput::Name(_)) => {}
656 Err(e) => return Ok(Err(type_error(e))),
657 }
658 match kind {
659 RegKind::Type => {
660 let env = SessionEnv(session);
661 Ok(rupg_analyze::parse_type(text, &env)?.map(|(ty, _)| ty))
662 }
663 RegKind::Procedure => procedure_in(text, session),
664 RegKind::Operator => operator_in(text, session),
665 _ => {
666 let names = match qualified_name_list(text) {
667 Ok(names) => names,
668 Err(e) => return Ok(Err(type_error(e))),
669 };
670 name_in(kind, text, &names, session)
671 }
672 }
673}
674
675fn name_in(kind: RegKind, text: &str, names: &[Cow<'_, str>], session: &dyn Session) -> Soft<u32> {
677 let list = name_list(names);
678 let simple = |named: &[(&str, u32)], state: SqlState, what: &str| match names {
679 [name] => match named.iter().find(|n| n.0 == name) {
680 Some(n) => Ok(Ok(n.1)),
681 None => soft(state, format!("{what} \"{name}\" does not exist")),
682 },
683 _ => soft(SqlState::INVALID_NAME, "invalid name syntax"),
684 };
685 let rows = |named: Named| -> Vec<(&str, u32)> {
686 builtin::named(named).iter().map(|r| (r.name, r.oid)).collect()
687 };
688 let mut schemas = rows(Named::Namespace);
689 schemas.retain(|&(_, oid)| !schema_dropped(oid, session));
690 if let Some(catalog) = session.catalog() {
691 schemas.extend(catalog.schemas().map(|s| (s.name.as_str(), s.oid)));
692 }
693 let catalog_in = |named: Named, state: SqlState, message: String| {
694 let (schema, name) = deconstruct(names, session)?;
695 match catalog_find(named, &spaces(schema, session), name, session) {
696 Some(oid) => Ok(Ok(oid)),
697 None => soft(state, message),
698 }
699 };
700 match kind {
701 RegKind::Class => class_in(names, session, false),
702 RegKind::Namespace => simple(&schemas, SqlState::UNDEFINED_SCHEMA, "schema"),
703 RegKind::Role => {
704 let roles = rupg_pgcatalog::roles::current();
705 simple(&roles.all().collect::<Vec<_>>(), SqlState::UNDEFINED_OBJECT, "role")
706 }
707 RegKind::Database => {
708 let created = session.databases();
709 let mut databases = rows(Named::Database);
710 databases.extend(created.iter().map(|d| (d.name.as_str(), d.oid)));
711 simple(&databases, SqlState::UNDEFINED_OBJECT, "database")
712 }
713 RegKind::Collation => catalog_in(
714 Named::Collation,
715 SqlState::UNDEFINED_OBJECT,
716 format!("collation \"{list}\" for encoding \"{UTF8_NAME}\" does not exist"),
717 ),
718 RegKind::Config => catalog_in(
719 Named::Config,
720 SqlState::UNDEFINED_OBJECT,
721 format!("text search configuration \"{list}\" does not exist"),
722 ),
723 RegKind::Dictionary => catalog_in(
724 Named::Dictionary,
725 SqlState::UNDEFINED_OBJECT,
726 format!("text search dictionary \"{list}\" does not exist"),
727 ),
728 RegKind::Proc => {
729 let (schema, name) = deconstruct(names, session)?;
730 match functions(&spaces(schema, session), name, None)[..] {
731 [] => soft(
732 SqlState::UNDEFINED_FUNCTION,
733 format!("function \"{text}\" does not exist"),
734 ),
735 [proc] => Ok(Ok(proc.oid)),
736 _ => soft(
737 SqlState::AMBIGUOUS_FUNCTION,
738 format!("more than one function named \"{text}\""),
739 ),
740 }
741 }
742 RegKind::Oper => {
743 let (schema, name) = deconstruct(names, session)?;
744 match operators(&spaces(schema, session), name)[..] {
745 [] => {
746 soft(SqlState::UNDEFINED_FUNCTION, format!("operator does not exist: {text}"))
747 }
748 [op] => Ok(Ok(op.oid)),
749 _ => soft(
750 SqlState::AMBIGUOUS_FUNCTION,
751 format!("more than one operator named {text}"),
752 ),
753 }
754 }
755 RegKind::Type | RegKind::Procedure | RegKind::Operator => {
756 Err(Error::internal("a type name read as a list of names"))
757 }
758 }
759}
760
761fn class_in(names: &[Cow<'_, str>], session: &dyn Session, strict: bool) -> Soft<u32> {
763 let (schema, name) = match names {
764 [name] => (None, name),
765 [schema, name] => (Some(schema), name),
766 [database, schema, name] => {
767 if database != session.database() {
768 return Err(Error::new(
769 SqlState::FEATURE_NOT_SUPPORTED,
770 format!(
771 "cross-database references are not implemented: \"{database}.{schema}.{name}\""
772 ),
773 ));
774 }
775 (Some(schema), name)
776 }
777 _ => {
778 return Err(Error::new(
779 SqlState::SYNTAX_ERROR,
780 format!("improper relation name (too many dotted names): {}", name_list(names)),
781 ));
782 }
783 };
784 let spaces = match schema {
785 Some(schema) => match namespace_oid(schema, session) {
786 Some(ns) => vec![ns],
787 None if strict => {
788 return Err(Error::new(
789 SqlState::UNDEFINED_SCHEMA,
790 format!("schema \"{schema}\" does not exist"),
791 ));
792 }
793 None => Vec::new(),
794 },
795 None => temp_path(session),
796 };
797 if let Some(oid) = find_class(&spaces, name, session) {
798 return Ok(Ok(oid));
799 }
800 let message = if strict {
801 match schema {
802 Some(schema) => format!("relation \"{schema}.{name}\" does not exist"),
803 None => format!("relation \"{name}\" does not exist"),
804 }
805 } else {
806 format!("relation \"{}\" does not exist", name_list(names))
807 };
808 let error = Error::new(SqlState::UNDEFINED_TABLE, message);
809 if strict { Err(error) } else { Ok(Err(error)) }
810}
811
812fn is_space(b: u8) -> bool {
814 matches!(b, b' ' | b'\t' | b'\n' | b'\r' | 0x0b | 0x0c)
815}
816
817fn bad_syntax<T>(message: &str) -> Soft<T> {
819 soft(SqlState::INVALID_TEXT_REPRESENTATION, message)
820}
821
822type NameAndArgs<'a> = (Vec<Cow<'a, str>>, Vec<u32>);
824
825fn name_and_args<'a>(
827 text: &'a str,
828 allow_none: bool,
829 session: &dyn Session,
830) -> Soft<NameAndArgs<'a>> {
831 let b = text.as_bytes();
832 let mut in_quote = false;
833 let mut open = None;
834 for (i, &c) in b.iter().enumerate() {
835 if c == b'"' {
836 in_quote = !in_quote;
837 } else if c == b'(' && !in_quote {
838 open = Some(i);
839 break;
840 }
841 }
842 let Some(open) = open else { return bad_syntax("expected a left parenthesis") };
843 let names = match qualified_name_list(&text[..open]) {
844 Ok(names) => names,
845 Err(e) => return Ok(Err(type_error(e))),
846 };
847 let rest = &text[open + 1..];
848 let rb = rest.as_bytes();
849 let close = (1..rb.len()).rev().find(|&i| !is_space(rb[i])).unwrap_or(0);
851 if rb.get(close) != Some(&b')') {
852 return bad_syntax("expected a right parenthesis");
853 }
854 let args = &rest[..close];
855 let ab = args.as_bytes();
856 let env = SessionEnv(session);
857 let mut types = Vec::new();
858 let mut at = 0;
859 let mut had_comma = false;
860 loop {
861 while at < ab.len() && is_space(ab[at]) {
862 at += 1;
863 }
864 if at == ab.len() {
865 if had_comma {
866 return bad_syntax("expected a type name");
867 }
868 break;
869 }
870 let start = at;
871 let mut in_quote = false;
872 let mut depth = 0i32;
873 while at < ab.len() {
874 match ab[at] {
875 b'"' => in_quote = !in_quote,
876 b',' if !in_quote && depth == 0 => break,
877 b'(' | b'[' if !in_quote => depth += 1,
878 b')' | b']' if !in_quote => depth -= 1,
879 _ => {}
880 }
881 at += 1;
882 }
883 if in_quote || depth != 0 {
884 return bad_syntax("improper type name");
885 }
886 let name = args[start..at].trim_end_matches(|c: char| u8::try_from(c).is_ok_and(is_space));
887 had_comma = at < ab.len();
888 if had_comma {
889 at += 1;
890 }
891 let ty = if allow_none && name.eq_ignore_ascii_case("none") {
892 0
893 } else {
894 match rupg_analyze::parse_type(name, &env)? {
895 Ok((ty, _)) => ty,
896 Err(e) => return Ok(Err(e)),
897 }
898 };
899 if types.len() >= FUNC_MAX_ARGS {
900 return soft(SqlState::TOO_MANY_ARGUMENTS, "too many arguments");
901 }
902 types.push(ty);
903 }
904 Ok(Ok((names, types)))
905}
906
907fn procedure_in(text: &str, session: &dyn Session) -> Soft<u32> {
909 let (names, args) = match name_and_args(text, false, session)? {
910 Ok(found) => found,
911 Err(e) => return Ok(Err(e)),
912 };
913 let (schema, name) = deconstruct(&names, session)?;
914 match functions(&spaces(schema, session), name, Some(args.len()))
915 .iter()
916 .find(|f| f.argtypes == args.as_slice())
917 {
918 Some(proc) => Ok(Ok(proc.oid)),
919 None => soft(SqlState::UNDEFINED_FUNCTION, format!("function \"{text}\" does not exist")),
920 }
921}
922
923fn operator_in(text: &str, session: &dyn Session) -> Soft<u32> {
925 let (names, args) = match name_and_args(text, true, session)? {
926 Ok(found) => found,
927 Err(e) => return Ok(Err(e)),
928 };
929 let [left, right] = args[..] else {
930 let error = if args.len() == 1 {
931 Error::new(SqlState::UNDEFINED_PARAMETER, "missing argument")
932 .with_hint("Use NONE to denote the missing argument of a unary operator.")
933 } else {
934 Error::new(SqlState::TOO_MANY_ARGUMENTS, "too many arguments")
935 .with_hint("Provide two argument types for operator.")
936 };
937 return Ok(Err(error));
938 };
939 let (schema, name) = deconstruct(&names, session)?;
940 let found = spaces(schema, session).into_iter().find_map(|ns| {
941 builtin::operators_named(name)
942 .find(|op| op.namespace == ns && op.left == left && op.right == right)
943 });
944 match found {
945 Some(op) => Ok(Ok(op.oid)),
946 None => soft(SqlState::UNDEFINED_FUNCTION, format!("operator does not exist: {text}")),
947 }
948}
949
950pub(crate) fn text_arg(args: &[Value]) -> Result<&str> {
952 args.first().and_then(Value::as_str).ok_or_else(bad_value)
953}
954
955fn to_reg(kind: RegKind, call: &Call<'_>, args: &[Value]) -> Result<Value> {
957 Ok(input(kind, text_arg(args)?, call.session)?.map_or(Value::Null, Value::Oid))
958}
959
960fn to_regtypemod(call: &Call<'_>, args: &[Value]) -> Result<Value> {
962 let env = SessionEnv(call.session);
963 let found = rupg_analyze::parse_type(text_arg(args)?, &env)?;
964 Ok(found.map_or(Value::Null, |(_, typmod)| Value::Int4(typmod)))
965}
966
967fn input_error(call: &Call<'_>, args: &[Value]) -> Result<Option<Error>> {
969 let text = text_arg(args)?;
970 let name = args.get(1).and_then(Value::as_str).ok_or_else(bad_value)?;
971 let env = SessionEnv(call.session);
972 let (ty, typmod) = rupg_analyze::parse_type(name, &env)??;
973 let value = match crate::io::input_soft(ty, text, typmod, call.session)? {
974 Ok(value) => value,
975 Err(error) => return Ok(Some(error)),
976 };
977 let element = builtin::type_by_oid(ty).map_or(0, |row| row.elem);
978 if builtin::domain_by_oid(ty).is_some() || builtin::domain_by_oid(element).is_some() {
979 return call.session.input_checks(ty, &value);
980 }
981 Ok(None)
982}
983
984fn input_is_valid(call: &Call<'_>, args: &[Value]) -> Result<Value> {
986 Ok(Value::Bool(input_error(call, args)?.is_none()))
987}
988
989fn input_error_info(call: &Call<'_>, args: &[Value]) -> Result<Value> {
991 let values = match input_error(call, args)? {
992 None => vec![Value::Null; 4],
993 Some(error) => {
994 let text = |s: Option<&str>| s.map_or(Value::Null, Value::text);
995 vec![
996 Value::text(error.message()),
997 text(error.detail()),
998 text(error.hint()),
999 Value::text(error.state().as_str()),
1000 ]
1001 }
1002 };
1003 Ok(Value::Record(Box::new(Record::new(vec![oid::TEXT; 4], values))))
1004}
1005
1006pub(crate) fn relation_oid(names: &[Cow<'_, str>], session: &dyn Session) -> Result<u32> {
1012 class_in(names, session, true)?
1013}
1014
1015fn text_regclass(call: &Call<'_>, args: &[Value]) -> Result<Value> {
1017 let names = qualified_name_list(text_arg(args)?).map_err(type_error)?;
1018 Ok(Value::Oid(relation_oid(&names, call.session)?))
1019}
1020
1021pub(crate) fn by_src(src: &str) -> Option<Kernel> {
1023 let kernel: Kernel = match src {
1024 "to_regclass" => |c, a| to_reg(RegKind::Class, c, a),
1025 "to_regcollation" => |c, a| to_reg(RegKind::Collation, c, a),
1026 "to_regdatabase" => |c, a| to_reg(RegKind::Database, c, a),
1027 "to_regnamespace" => |c, a| to_reg(RegKind::Namespace, c, a),
1028 "to_regoper" => |c, a| to_reg(RegKind::Oper, c, a),
1029 "to_regoperator" => |c, a| to_reg(RegKind::Operator, c, a),
1030 "to_regproc" => |c, a| to_reg(RegKind::Proc, c, a),
1031 "to_regprocedure" => |c, a| to_reg(RegKind::Procedure, c, a),
1032 "to_regrole" => |c, a| to_reg(RegKind::Role, c, a),
1033 "to_regtype" => |c, a| to_reg(RegKind::Type, c, a),
1034 "to_regtypemod" => to_regtypemod,
1035 "pg_input_is_valid" => input_is_valid,
1036 "pg_input_error_info" => input_error_info,
1037 "text_regclass" => text_regclass,
1038 _ => return None,
1039 };
1040 Some(kernel)
1041}