use super::super::builtin_routines::PG18_BUILTIN_ROUTINE_GROUPS;
use super::super::helpers::oids::split_schema_name;
use super::super::helpers::rows::{catalog_name, row, str_value};
use super::super::helpers::type_metadata::{catalog_regtype_name, catalog_type_name};
use super::super::pg_proc::user_routine_catalog_oid;
use crate::catalog::{CatalogReadView, RelationNameResolution};
use uqa_core::Value;
use uqa_sql::catalog::languages::SQL_LANGUAGE;
use uqa_sql::registry::registered_names;
use uqa_sql::{ResultRow, SQLError};
#[expect(
clippy::too_many_lines,
reason = "preserves catalog column and OID order"
)]
pub fn build_info_routines(
catalog: &CatalogReadView,
resolution: &RelationNameResolution,
) -> Result<Vec<ResultRow>, SQLError> {
let builtin_owner = catalog.role_is_enabled_for(
resolution.current_user(),
&uqa_sql::catalog::roles::RoleIdentity::BOOTSTRAP,
);
let mut rows: Vec<ResultRow> = PG18_BUILTIN_ROUTINE_GROUPS
.iter()
.flat_map(|group| group.iter())
.map(|routine| {
let regtype_name = catalog_regtype_name(routine.return_type);
row([
("specific_catalog", catalog_name()),
("specific_schema", str_value("pg_catalog")),
(
"specific_name",
str_value(format!("{}_{}", routine.name, routine.oid)),
),
("routine_catalog", catalog_name()),
("routine_schema", str_value("pg_catalog")),
("routine_name", str_value(routine.name)),
(
"routine_type",
if routine.kind == "f" {
str_value("FUNCTION")
} else {
Value::Null
},
),
("module_catalog", Value::Null),
("module_schema", Value::Null),
("module_name", Value::Null),
("udt_catalog", Value::Null),
("udt_schema", Value::Null),
("udt_name", Value::Null),
(
"data_type",
str_value(catalog_type_name(routine.return_type)),
),
(
"type_udt_catalog",
regtype_name.map_or(Value::Null, |_| catalog_name()),
),
(
"type_udt_schema",
regtype_name.map_or(Value::Null, |_| str_value("pg_catalog")),
),
("type_udt_name", regtype_name.map_or(Value::Null, str_value)),
(
"routine_body",
str_value(if routine.language() == i64::from(SQL_LANGUAGE) {
"SQL"
} else {
"EXTERNAL"
}),
),
(
"routine_definition",
if builtin_owner {
str_value(routine.source)
} else {
Value::Null
},
),
("external_name", Value::Null),
(
"external_language",
str_value(if routine.language() == i64::from(SQL_LANGUAGE) {
"SQL"
} else {
"INTERNAL"
}),
),
(
"is_deterministic",
str_value(if routine.volatility == "i" {
"YES"
} else {
"NO"
}),
),
("sql_data_access", str_value("MODIFIES")),
(
"is_null_call",
str_value(if routine.strict { "YES" } else { "NO" }),
),
("schema_level_routine", str_value("YES")),
("max_dynamic_result_sets", Value::Int(0)),
("is_udt_dependent", str_value("NO")),
])
})
.collect();
rows.extend(registered_names().into_iter().map(|name| {
row([
("specific_catalog", catalog_name()),
("specific_schema", str_value("pg_catalog")),
("specific_name", str_value(format!("{name}_0"))),
("routine_catalog", catalog_name()),
("routine_schema", str_value("pg_catalog")),
("routine_name", str_value(name)),
("routine_type", str_value("FUNCTION")),
("module_catalog", Value::Null),
("module_schema", Value::Null),
("module_name", Value::Null),
("udt_catalog", catalog_name()),
("udt_schema", str_value("pg_catalog")),
("udt_name", str_value("text")),
("data_type", str_value("text")),
("routine_body", str_value("EXTERNAL")),
("routine_definition", Value::Null),
("external_name", Value::Null),
("external_language", str_value("rust")),
("is_deterministic", str_value("NO")),
("sql_data_access", str_value("READS SQL DATA")),
("is_null_call", str_value("YES")),
("schema_level_routine", str_value("YES")),
("max_dynamic_result_sets", Value::Int(0)),
("is_udt_dependent", str_value("NO")),
])
}));
for function in catalog.all_sql_functions() {
let def = &function.def;
let (routine_schema, routine_name) = split_schema_name(&def.name)?;
let catalog_oid = user_routine_catalog_oid(&function)?;
let routine_type = if def.is_procedure {
"PROCEDURE"
} else {
"FUNCTION"
};
let (routine_body, external_language) = if def.language == "sql" {
("SQL", "SQL".to_string())
} else {
("EXTERNAL", def.language.to_ascii_uppercase())
};
let definition = match &def.body {
uqa_sql::ast::FunctionBody::Source(source) => str_value(source.clone()),
uqa_sql::ast::FunctionBody::Statements(_) => Value::Null,
};
let data_type = match &def.returns {
uqa_sql::ast::FunctionReturns::Scalar { type_name }
| uqa_sql::ast::FunctionReturns::SetOf { type_name } => str_value(type_name.clone()),
uqa_sql::ast::FunctionReturns::Table => str_value("record"),
uqa_sql::ast::FunctionReturns::None => Value::Null,
};
rows.push(row([
("specific_catalog", catalog_name()),
("specific_schema", str_value(routine_schema.clone())),
(
"specific_name",
str_value(format!("{routine_name}_{catalog_oid}")),
),
("routine_catalog", catalog_name()),
("routine_schema", str_value(routine_schema)),
("routine_name", str_value(routine_name)),
("routine_type", str_value(routine_type)),
("module_catalog", Value::Null),
("module_schema", Value::Null),
("module_name", Value::Null),
("udt_catalog", catalog_name()),
("udt_schema", str_value("pg_catalog")),
("udt_name", data_type.clone()),
("data_type", data_type),
("routine_body", str_value(routine_body)),
("routine_definition", definition),
("external_name", Value::Null),
("external_language", str_value(external_language)),
(
"is_deterministic",
str_value(
if matches!(def.volatility, uqa_sql::ast::FunctionVolatility::Immutable) {
"YES"
} else {
"NO"
},
),
),
("sql_data_access", str_value("MODIFIES SQL DATA")),
(
"is_null_call",
str_value(if def.strict { "YES" } else { "NO" }),
),
("schema_level_routine", str_value("YES")),
("max_dynamic_result_sets", Value::Int(0)),
("is_udt_dependent", str_value("NO")),
]));
}
Ok(rows)
}