use alloc::string::{String, ToString};
use alloc::vec::Vec;
use spg_sql::ast::{Expr, SelectItem, SelectStatement};
use spg_storage::{Catalog, ColumnSchema, DataType, Row, TableSchema, Value};
use crate::{Engine, EngineError};
pub(crate) fn pg_unique_conname(
t: &spg_storage::Table,
uc: &spg_storage::UniquenessConstraint,
tname: &str,
) -> String {
if let Some(n) = &uc.name {
return n.clone();
}
if uc.is_primary_key {
return alloc::format!("{tname}_pkey");
}
let cols = uc
.columns
.iter()
.map(|&p| {
t.schema()
.columns
.get(p)
.map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
})
.collect::<Vec<_>>()
.join("_");
alloc::format!("{tname}_{cols}_key")
}
pub(crate) fn pg_fk_conname(
t: &spg_storage::Table,
fk: &spg_storage::ForeignKeyConstraint,
tname: &str,
) -> String {
if let Some(n) = &fk.name {
return n.clone();
}
let cols = fk
.local_columns
.iter()
.map(|&p| {
t.schema()
.columns
.get(p)
.map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
})
.collect::<Vec<_>>()
.join("_");
alloc::format!("{tname}_{cols}_fkey")
}
fn referenced_columns(t: &spg_storage::Table, check: &str) -> Vec<String> {
let bytes = check.as_bytes();
let mut found: Vec<String> = Vec::new();
let mut in_str = false;
let mut i = 0;
while i < bytes.len() {
let c = bytes[i];
if in_str {
if c == b'\'' {
in_str = false;
}
i += 1;
continue;
}
if c == b'\'' {
in_str = true;
i += 1;
continue;
}
if c.is_ascii_alphabetic() || c == b'_' {
let start = i;
while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
i += 1;
}
let ident = &check[start..i];
if let Some(col) = t
.schema()
.columns
.iter()
.find(|c| c.name.eq_ignore_ascii_case(ident))
&& !found.iter().any(|f| f.eq_ignore_ascii_case(&col.name))
{
found.push(col.name.clone());
}
} else {
i += 1;
}
}
found
}
pub(crate) fn pg_check_connames(
t: &spg_storage::Table,
tname: &str,
checks: &[spg_storage::CheckConstraint],
) -> Vec<String> {
let mut seen: alloc::collections::BTreeMap<String, usize> = alloc::collections::BTreeMap::new();
let mut out = Vec::with_capacity(checks.len());
for chk in checks {
if let Some(n) = &chk.name {
out.push(n.clone());
continue;
}
let cols = referenced_columns(t, &chk.expr);
let base = if cols.len() == 1 {
alloc::format!("{tname}_{}_check", cols[0])
} else {
alloc::format!("{tname}_check")
};
let count = seen.entry(base.clone()).or_insert(0);
out.push(if *count == 0 {
base.clone()
} else {
alloc::format!("{base}{count}")
});
*count += 1;
}
out
}
pub(crate) fn mysql_data_type_text(
ty: DataType,
width: Option<spg_storage::MysqlIntWidth>,
) -> alloc::string::String {
if let Some(base) = crate::show::mysql_int_base_name(ty, width) {
return alloc::string::String::from(base);
}
let s = match ty {
DataType::Float => "double",
DataType::Real => "float",
DataType::Numeric { .. } => "decimal",
DataType::Bool => "tinyint",
DataType::Text => "text",
DataType::Varchar(_) => "varchar",
DataType::Char(_) => "char",
DataType::Date => "date",
DataType::Time => "time",
DataType::Timestamp | DataType::Timestamptz => "datetime",
DataType::Bytes => "blob",
DataType::Json | DataType::Jsonb => "json",
other => return pg_data_type_text(other).to_ascii_lowercase(),
};
alloc::string::String::from(s)
}
pub(crate) fn pg_data_type_text(ty: DataType) -> alloc::string::String {
if let DataType::Range(k) = ty {
return alloc::string::String::from(k.keyword());
}
if let DataType::Multirange(k) = ty {
let base = k.keyword();
return alloc::string::String::from(base).replace("range", "multirange");
}
let s = match ty {
DataType::Int => "integer",
DataType::BigInt => "bigint",
DataType::Xid => "xid",
DataType::Xid8 => "xid8",
DataType::SmallInt => "smallint",
DataType::Float => "double precision",
DataType::Real => "real",
DataType::Numeric { .. } => "numeric",
DataType::Bool => "boolean",
DataType::Text => "text",
DataType::Varchar(_) => "character varying",
DataType::Char(_) => "character",
DataType::Char1 => "\"char\"",
DataType::Date => "date",
DataType::Time => "time without time zone",
DataType::Timestamp => "timestamp without time zone",
DataType::Timestamptz => "timestamp with time zone",
DataType::Interval => "interval",
DataType::Json => "json",
DataType::Jsonb => "jsonb",
DataType::Bytes => "bytea",
DataType::Uuid => "uuid",
DataType::Money => "money",
DataType::Inet => "inet",
DataType::Cidr => "cidr",
DataType::Macaddr => "macaddr",
DataType::Macaddr8 => "macaddr8",
DataType::PgLsn => "pg_lsn",
DataType::Bit(_) => "bit",
DataType::BitVarying(_) => "bit varying",
DataType::Xml => "xml",
DataType::Point => "point",
DataType::Lseg => "lseg",
DataType::Path => "path",
DataType::PgBox => "box",
DataType::Polygon => "polygon",
DataType::Line => "line",
DataType::Circle => "circle",
DataType::TsVector => "tsvector",
DataType::TsQuery => "tsquery",
DataType::TextArray
| DataType::IntArray
| DataType::BigIntArray
| DataType::SmallIntArray
| DataType::FloatArray
| DataType::NumericArray
| DataType::BoolArray
| DataType::DateArray
| DataType::TimestampArray
| DataType::TimestamptzArray
| DataType::IntervalArray
| DataType::UuidArray
| DataType::JsonArray
| DataType::JsonbArray
| DataType::BytesArray
| DataType::VarcharArray
| DataType::CharArray
| DataType::MoneyArray => "ARRAY",
DataType::Vector { .. } => "USER-DEFINED",
_ => "USER-DEFINED",
};
alloc::string::String::from(s)
}
pub(crate) fn synth_information_schema_columns(
cat: &Catalog,
mysql: bool,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("table_catalog", DataType::Text, false),
ColumnSchema::new("table_schema", DataType::Text, false),
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("column_name", DataType::Text, false),
ColumnSchema::new("ordinal_position", DataType::Int, false),
ColumnSchema::new("is_nullable", DataType::Text, false),
ColumnSchema::new("data_type", DataType::Text, false),
ColumnSchema::new("column_default", DataType::Text, true),
ColumnSchema::new("character_maximum_length", DataType::Int, true),
ColumnSchema::new("numeric_precision", DataType::Int, true),
ColumnSchema::new("numeric_precision_radix", DataType::Int, true),
ColumnSchema::new("numeric_scale", DataType::Int, true),
ColumnSchema::new("collation_name", DataType::Text, true),
ColumnSchema::new("udt_name", DataType::Text, false),
ColumnSchema::new("is_identity", DataType::Text, false),
ColumnSchema::new("identity_generation", DataType::Text, true),
ColumnSchema::new("datetime_precision", DataType::Int, true),
ColumnSchema::new("is_updatable", DataType::Text, false),
ColumnSchema::new("is_generated", DataType::Text, false),
ColumnSchema::new("generation_expression", DataType::Text, true),
];
let mut schema = schema;
if mysql {
schema.push(ColumnSchema::new("column_type", DataType::Text, false));
}
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
if cat.materialized_views().contains_key(&tname) {
continue;
}
let Some(t) = cat.get(&tname) else { continue };
for (i, col) in t.schema().columns.iter().enumerate() {
#[allow(clippy::cast_possible_wrap)]
let ordinal = (i + 1) as i32;
let mut row = info_column_row(&tname, ordinal, col, None, mysql);
if mysql {
row.values
.push(Value::text(crate::show::render_mysql_type(col)));
}
rows.push(row);
}
}
for (vname, _) in cat.views_all() {
let Some(vname) = cat.listed_name(vname) else {
continue;
};
let cols = crate::describe::describe_view_columns(cat, vname);
let updatable = crate::dml::view_is_auto_updatable(cat, vname);
let simple = crate::dml::view_simple_column_names(cat, vname);
for (i, col) in cols.iter().enumerate() {
#[allow(clippy::cast_possible_wrap)]
let ordinal = (i + 1) as i32;
let writable = updatable && simple.iter().any(|n| n == &col.name);
let mut row = info_column_row(vname, ordinal, col, Some(writable), mysql);
if mysql {
row.values
.push(Value::text(crate::show::render_mysql_type(col)));
}
rows.push(row);
}
}
(schema, rows)
}
fn info_column_row(
rel: &str,
ordinal: i32,
col: &ColumnSchema,
view_updatable: Option<bool>,
mysql: bool,
) -> Row<'static> {
let default_text: Value<'static> = if let Some(txt) = &col.default_text {
Value::text(txt.clone())
} else if col.auto_increment {
Value::text(alloc::format!(
"nextval('{rel}_{}_seq'::regclass)",
col.name
))
} else {
Value::Null
};
let (num_prec, num_scale): (Value<'static>, Value<'static>) = match col.ty {
DataType::SmallInt => (Value::Int(16), Value::Int(0)),
DataType::Int => (Value::Int(32), Value::Int(0)),
DataType::BigInt => (Value::Int(64), Value::Int(0)),
DataType::Float => (Value::Int(53), Value::Null),
DataType::Real => (Value::Int(24), Value::Null),
DataType::Numeric { precision: 0, .. } => (Value::Null, Value::Null),
DataType::Numeric { precision, scale } => (
Value::Int(i32::from(precision)),
Value::Int(if scale < 0 {
2048 + i32::from(scale)
} else {
i32::from(scale)
}),
),
_ => (Value::Null, Value::Null),
};
let udt: &str = match col.ty {
DataType::SmallInt => "int2",
DataType::Int => "int4",
DataType::BigInt => "int8",
DataType::Float => "float8",
DataType::Real => "float4",
DataType::Bool => "bool",
DataType::Text => "text",
DataType::Bytes => "bytea",
DataType::Json => "json",
DataType::Jsonb => "jsonb",
DataType::Uuid => "uuid",
DataType::Date => "date",
DataType::Timestamp => "timestamp",
DataType::Timestamptz => "timestamptz",
DataType::Time => "time",
DataType::Interval => "interval",
DataType::Numeric { .. } => "numeric",
DataType::Varchar(_) => "varchar",
DataType::Char(_) => "bpchar",
DataType::Char1 => "char",
DataType::TextArray => "_text",
DataType::IntArray => "_int4",
DataType::BigIntArray => "_int8",
DataType::SmallIntArray => "_int2",
DataType::FloatArray => "_float8",
DataType::NumericArray => "_numeric",
DataType::BoolArray => "_bool",
DataType::DateArray => "_date",
DataType::TimestampArray => "_timestamp",
DataType::TimestamptzArray => "_timestamptz",
DataType::UuidArray => "_uuid",
DataType::BytesArray => "_bytea",
DataType::JsonArray => "_json",
_ => "text",
};
let dt_prec: Value<'static> = match col.ty {
DataType::Date => Value::Int(0),
DataType::Time | DataType::Timestamp | DataType::Timestamptz | DataType::Interval => {
Value::Int(6)
}
_ => Value::Null,
};
Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(rel.to_string()),
Value::text(col.name.clone()),
Value::Int(ordinal),
Value::text::<&str>(if view_updatable.is_some() || col.nullable {
"YES"
} else {
"NO"
}),
Value::text(if mysql {
mysql_data_type_text(col.ty, col.mysql_int_width)
} else {
pg_data_type_text(col.ty)
}),
default_text,
match col.ty {
DataType::Varchar(n) | DataType::Char(n) => {
i32::try_from(n).map(Value::Int).unwrap_or(Value::Null)
}
_ => Value::Null,
},
num_prec.clone(),
match col.ty {
DataType::Numeric { .. } => Value::Int(10),
_ if matches!(num_prec, Value::Null) => Value::Null,
_ => Value::Int(2),
},
num_scale,
match col.collation_name.as_deref() {
Some(n) => Value::text::<&str>(n),
None => Value::Null,
},
Value::text::<&str>(udt),
Value::text::<&str>(if col.identity_always { "YES" } else { "NO" }),
if col.identity_always {
Value::text::<&str>("ALWAYS")
} else {
Value::Null
},
dt_prec,
Value::text::<&str>(match view_updatable {
Some(false) => "NO",
_ => "YES",
}),
Value::text::<&str>(if col.generated_stored_expr.is_some() {
"ALWAYS"
} else {
"NEVER"
}),
match &col.generated_stored_expr {
Some(src) => Value::text(src.clone()),
None => Value::Null,
},
])
}
pub(crate) fn synth_pg_prepared_statements(
prepared: &alloc::collections::BTreeMap<String, crate::PreparedSqlStatement>,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("name", DataType::Text, false),
ColumnSchema::new("statement", DataType::Text, false),
ColumnSchema::new("parameter_types", DataType::TextArray, false),
ColumnSchema::new("from_sql", DataType::Bool, false),
];
let rows: Vec<Row<'static>> = prepared
.iter()
.map(|(name, p)| {
Row::new(alloc::vec![
Value::text(name.clone()),
Value::text(p.source.clone()),
Value::TextArray(
p.param_types
.iter()
.map(|t| {
Some(
crate::conversions::type_name_to_data_type(t)
.map_or_else(|| t.clone(), pg_data_type_text),
)
})
.collect(),
),
Value::Bool(true),
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_information_schema_tables(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("table_catalog", DataType::Text, false),
ColumnSchema::new("table_schema", DataType::Text, false),
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("table_type", DataType::Text, false),
ColumnSchema::new("self_referencing_column_name", DataType::Text, true),
ColumnSchema::new("reference_generation", DataType::Text, true),
ColumnSchema::new("user_defined_type_catalog", DataType::Text, true),
ColumnSchema::new("user_defined_type_schema", DataType::Text, true),
ColumnSchema::new("user_defined_type_name", DataType::Text, true),
ColumnSchema::new("is_insertable_into", DataType::Text, false),
ColumnSchema::new("is_typed", DataType::Text, false),
ColumnSchema::new("commit_action", DataType::Text, true),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
if cat.materialized_views().contains_key(&tname) {
continue;
}
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(tname.clone()),
Value::text("BASE TABLE"),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::text("YES"),
Value::text("NO"),
Value::Null,
]));
}
for (name, _) in cat.views_all() {
let Some(name) = cat.listed_name(name) else {
continue;
};
let insertable = crate::dml::view_is_auto_updatable(cat, name);
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(name.to_string()),
Value::text("VIEW"),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::text::<&str>(if insertable { "YES" } else { "NO" }),
Value::text("NO"),
Value::Null,
]));
}
(schema, rows)
}
pub(crate) fn synth_information_schema_schemata(
_cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("catalog_name", DataType::Text, false),
ColumnSchema::new("schema_name", DataType::Text, false),
ColumnSchema::new("schema_owner", DataType::Text, false),
ColumnSchema::new("default_character_set_catalog", DataType::Text, true),
ColumnSchema::new("default_character_set_schema", DataType::Text, true),
ColumnSchema::new("default_character_set_name", DataType::Text, true),
ColumnSchema::new("sql_path", DataType::Text, true),
];
let rows: Vec<Row<'static>> = alloc::vec![
Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text("admin"),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
]),
Row::new(alloc::vec![
Value::text("spg"),
Value::text("pg_catalog"),
Value::text("admin"),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
]),
Row::new(alloc::vec![
Value::text("spg"),
Value::text("information_schema"),
Value::text("admin"),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
]),
];
(schema, rows)
}
pub(crate) fn synth_information_schema_views(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("table_catalog", DataType::Text, false),
ColumnSchema::new("table_schema", DataType::Text, false),
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("view_definition", DataType::Text, true),
ColumnSchema::new("check_option", DataType::Text, false),
ColumnSchema::new("is_updatable", DataType::Text, false),
ColumnSchema::new("is_insertable_into", DataType::Text, false),
ColumnSchema::new("is_trigger_updatable", DataType::Text, false),
ColumnSchema::new("is_trigger_deletable", DataType::Text, false),
ColumnSchema::new("is_trigger_insertable_into", DataType::Text, false),
];
let rows: Vec<Row<'static>> = cat
.views_all()
.values()
.filter_map(|v| cat.listed_name(&v.name).map(|n| (n.to_string(), v)))
.map(|(vname, v)| {
Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(vname),
Value::text(crate::eval::functions::pg_viewdef_render(&v.body, false)),
Value::text(match v.check_option {
1 => "LOCAL",
2 => "CASCADED",
_ => "NONE",
}),
Value::text::<&str>(if crate::dml::view_is_auto_updatable(cat, &v.name) {
"YES"
} else {
"NO"
}),
Value::text::<&str>(if crate::dml::view_is_auto_updatable(cat, &v.name) {
"YES"
} else {
"NO"
}),
Value::text("NO"),
Value::text("NO"),
Value::text("NO"),
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_stat_progress_vacuum(
_cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("pid", DataType::Int, false),
ColumnSchema::new("datid", DataType::BigInt, false),
ColumnSchema::new("datname", DataType::Text, false),
ColumnSchema::new("relid", DataType::BigInt, false),
ColumnSchema::new("phase", DataType::Text, false),
ColumnSchema::new("heap_blks_total", DataType::BigInt, false),
ColumnSchema::new("heap_blks_scanned", DataType::BigInt, false),
ColumnSchema::new("heap_blks_vacuumed", DataType::BigInt, false),
ColumnSchema::new("index_vacuum_count", DataType::BigInt, false),
ColumnSchema::new("max_dead_tuples", DataType::BigInt, false),
ColumnSchema::new("num_dead_tuples", DataType::BigInt, false),
];
let rows: Vec<Row<'static>> = Vec::new();
(schema, rows)
}
pub(crate) fn synth_pg_stat_progress_create_index(
_cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("pid", DataType::Int, false),
ColumnSchema::new("datid", DataType::BigInt, false),
ColumnSchema::new("datname", DataType::Text, false),
ColumnSchema::new("relid", DataType::BigInt, false),
ColumnSchema::new("index_relid", DataType::BigInt, false),
ColumnSchema::new("command", DataType::Text, false),
ColumnSchema::new("phase", DataType::Text, false),
ColumnSchema::new("lockers_total", DataType::BigInt, false),
ColumnSchema::new("lockers_done", DataType::BigInt, false),
ColumnSchema::new("current_locker_pid", DataType::Int, false),
ColumnSchema::new("blocks_total", DataType::BigInt, false),
ColumnSchema::new("blocks_done", DataType::BigInt, false),
ColumnSchema::new("tuples_total", DataType::BigInt, false),
ColumnSchema::new("tuples_done", DataType::BigInt, false),
ColumnSchema::new("partitions_total", DataType::BigInt, false),
ColumnSchema::new("partitions_done", DataType::BigInt, false),
];
let rows: Vec<Row<'static>> = Vec::new();
(schema, rows)
}
pub(crate) fn synth_pg_stat_progress_analyze(
_cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("pid", DataType::Int, false),
ColumnSchema::new("datid", DataType::BigInt, false),
ColumnSchema::new("datname", DataType::Text, false),
ColumnSchema::new("relid", DataType::BigInt, false),
ColumnSchema::new("phase", DataType::Text, false),
ColumnSchema::new("sample_blks_total", DataType::BigInt, false),
ColumnSchema::new("sample_blks_scanned", DataType::BigInt, false),
ColumnSchema::new("ext_stats_total", DataType::BigInt, false),
ColumnSchema::new("ext_stats_computed", DataType::BigInt, false),
ColumnSchema::new("child_tables_total", DataType::BigInt, false),
ColumnSchema::new("child_tables_done", DataType::BigInt, false),
ColumnSchema::new("current_child_table_relid", DataType::BigInt, false),
];
let rows: Vec<Row<'static>> = Vec::new();
(schema, rows)
}
pub(crate) fn synth_pg_ts_config_map(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
use crate::fts::{TokenType, TsDict};
let schema = alloc::vec![
ColumnSchema::new("mapcfg", DataType::BigInt, false),
ColumnSchema::new("maptokentype", DataType::Int, false),
ColumnSchema::new("mapseqno", DataType::Int, false),
ColumnSchema::new("mapdict", DataType::BigInt, false),
];
const TYPES: &[TokenType] = &[
TokenType::AsciiWord,
TokenType::Word,
TokenType::NumWord,
TokenType::Email,
TokenType::Url,
TokenType::Host,
TokenType::SFloat,
TokenType::Version,
TokenType::HwordNumPart,
TokenType::HwordPart,
TokenType::HwordAsciiPart,
TokenType::Blank,
TokenType::Tag,
TokenType::Protocol,
TokenType::NumHword,
TokenType::AsciiHword,
TokenType::Hword,
TokenType::UrlPath,
TokenType::File,
TokenType::Float,
TokenType::Int,
TokenType::Uint,
TokenType::Entity,
];
let mut rows = Vec::new();
for (cfg_oid, english) in [(3748i64, false), (13248i64, true)] {
for t in TYPES {
let Some(dict) = t.dictionary(english) else {
continue;
};
rows.push(Row::new(alloc::vec![
Value::BigInt(cfg_oid),
Value::Int(*t as i32),
Value::Int(1),
Value::BigInt(match dict {
TsDict::Simple => 3765,
TsDict::EnglishStem => 13247,
}),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_pg_ts_config(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("cfgname", DataType::Name, false),
ColumnSchema::new("cfgnamespace", DataType::BigInt, false),
ColumnSchema::new("cfgowner", DataType::BigInt, false),
ColumnSchema::new("cfgparser", DataType::BigInt, false),
];
let rows = alloc::vec![
Row::new(alloc::vec![
Value::BigInt(3748),
Value::text("simple"),
Value::BigInt(11),
Value::BigInt(10),
Value::BigInt(3722),
]),
Row::new(alloc::vec![
Value::BigInt(13248),
Value::text("english"),
Value::BigInt(11),
Value::BigInt(10),
Value::BigInt(3722),
]),
];
(schema, rows)
}
pub(crate) fn synth_pg_ts_dict(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("dictname", DataType::Name, false),
ColumnSchema::new("dictnamespace", DataType::BigInt, false),
ColumnSchema::new("dictowner", DataType::BigInt, false),
ColumnSchema::new("dicttemplate", DataType::BigInt, false),
ColumnSchema::new("dictinitoption", DataType::Text, true),
];
let rows = alloc::vec![
Row::new(alloc::vec![
Value::BigInt(3765),
Value::text("simple"),
Value::BigInt(11),
Value::BigInt(10),
Value::BigInt(3727),
Value::Null,
]),
Row::new(alloc::vec![
Value::BigInt(13247),
Value::text("english_stem"),
Value::BigInt(11),
Value::BigInt(10),
Value::BigInt(13234),
Value::text("language = 'english', stopwords = 'english'"),
]),
];
(schema, rows)
}
pub(crate) fn synth_pg_ts_parser(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("prsname", DataType::Name, false),
ColumnSchema::new("prsnamespace", DataType::BigInt, false),
ColumnSchema::new("prsstart", DataType::BigInt, false),
ColumnSchema::new("prstoken", DataType::BigInt, false),
ColumnSchema::new("prsend", DataType::BigInt, false),
ColumnSchema::new("prsheadline", DataType::BigInt, false),
ColumnSchema::new("prslextype", DataType::BigInt, false),
];
let rows = alloc::vec![Row::new(alloc::vec![
Value::BigInt(3722),
Value::text("default"),
Value::BigInt(11),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
])];
(schema, rows)
}
pub(crate) fn synth_pg_ts_template(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("tmplname", DataType::Name, false),
ColumnSchema::new("tmplnamespace", DataType::BigInt, false),
ColumnSchema::new("tmplinit", DataType::BigInt, false),
ColumnSchema::new("tmpllexize", DataType::BigInt, false),
];
let rows = alloc::vec![
Row::new(alloc::vec![
Value::BigInt(3727),
Value::text("simple"),
Value::BigInt(11),
Value::BigInt(0),
Value::BigInt(0),
]),
Row::new(alloc::vec![
Value::BigInt(13234),
Value::text("snowball"),
Value::BigInt(11),
Value::BigInt(0),
Value::BigInt(0),
]),
];
(schema, rows)
}
pub(crate) fn synth_pg_inherits(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
use spg_storage::PartitionRole;
let schema = alloc::vec![
ColumnSchema::new("inhrelid", DataType::BigInt, false),
ColumnSchema::new("inhparent", DataType::BigInt, false),
ColumnSchema::new("inhseqno", DataType::Int, false),
ColumnSchema::new("inhdetachpending", DataType::Bool, false),
];
let mut by_name: alloc::collections::BTreeMap<String, i64> =
alloc::collections::BTreeMap::new();
let mut oid: i64 = 16384;
for tname in cat.visible_table_names() {
by_name.insert(tname.clone(), oid);
oid = oid.saturating_add(1);
}
let mut rows: Vec<Row<'static>> = Vec::new();
for cname in cat.visible_table_names() {
let Some(c) = cat.get(&cname) else { continue };
if let Some(PartitionRole::Inherits { parent_names }) = &c.schema().partition_role {
let Some(&child_oid) = by_name.get(&cname) else {
continue;
};
for (i, pname) in parent_names.iter().enumerate() {
let Some(&parent_oid) = by_name.get(pname) else {
continue;
};
rows.push(Row::new(alloc::vec![
Value::BigInt(child_oid),
Value::BigInt(parent_oid),
Value::Int(i as i32 + 1),
Value::Bool(false),
]));
}
continue;
}
let parent_name = match &c.schema().partition_role {
Some(PartitionRole::Range { parent_name, .. })
| Some(PartitionRole::List { parent_name, .. })
| Some(PartitionRole::Hash { parent_name, .. })
| Some(PartitionRole::Default { parent_name }) => parent_name.clone(),
_ => continue,
};
let Some(&child_oid) = by_name.get(&cname) else {
continue;
};
let Some(&parent_oid) = by_name.get(&parent_name) else {
continue;
};
rows.push(Row::new(alloc::vec![
Value::BigInt(child_oid),
Value::BigInt(parent_oid),
Value::Int(1),
Value::Bool(false),
]));
}
(schema, rows)
}
pub(crate) fn synth_pg_depend(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("classid", DataType::BigInt, false),
ColumnSchema::new("objid", DataType::BigInt, false),
ColumnSchema::new("objsubid", DataType::Int, false),
ColumnSchema::new("refclassid", DataType::BigInt, false),
ColumnSchema::new("refobjid", DataType::BigInt, false),
ColumnSchema::new("refobjsubid", DataType::Int, false),
ColumnSchema::new("deptype", DataType::Text, false),
];
let rows: Vec<Row<'static>> = Vec::new();
(schema, rows)
}
pub(crate) fn synth_pg_attrdef(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("adrelid", DataType::BigInt, false),
ColumnSchema::new("adnum", DataType::SmallInt, false),
ColumnSchema::new("adbin", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut table_oid: i64 = 16384;
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else {
table_oid = table_oid.saturating_add(1);
continue;
};
for (i, col) in t.schema().columns.iter().enumerate() {
let Some(txt) = &col.default_text else {
continue;
};
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let adnum = (i + 1) as i16;
let row_oid = table_oid
.saturating_mul(1000)
.saturating_add(i64::from(adnum));
rows.push(Row::new(alloc::vec![
Value::BigInt(row_oid),
Value::BigInt(table_oid),
Value::SmallInt(adnum),
Value::text(txt.clone()),
]));
}
table_oid = table_oid.saturating_add(1);
}
(schema, rows)
}
pub(crate) fn synth_pg_policy(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("polname", DataType::Text, false),
ColumnSchema::new("polrelid", DataType::BigInt, false),
ColumnSchema::new("polcmd", DataType::Text, false),
ColumnSchema::new("polpermissive", DataType::Bool, false),
ColumnSchema::new("polroles", DataType::Text, false),
ColumnSchema::new("polqual", DataType::Text, true),
ColumnSchema::new("polwithcheck", DataType::Text, true),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut table_oid: i64 = 16384;
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else {
table_oid = table_oid.saturating_add(1);
continue;
};
for (i, p) in t.schema().policies.iter().enumerate() {
#[allow(clippy::cast_possible_wrap)]
let row_oid = table_oid.saturating_mul(1000).saturating_add(i as i64 + 1);
let roles = if p.roles.is_empty() {
alloc::string::String::from("{0}")
} else {
alloc::format!("{{{}}}", p.roles.join(","))
};
rows.push(Row::new(alloc::vec![
Value::BigInt(row_oid),
Value::text(p.name.clone()),
Value::BigInt(table_oid),
Value::text(alloc::string::String::from(p.cmd.as_pg_char())),
Value::Bool(p.permissive),
Value::text(roles),
p.using_expr.clone().map_or(Value::Null, Value::text),
p.with_check_expr.clone().map_or(Value::Null, Value::text),
]));
}
table_oid = table_oid.saturating_add(1);
}
(schema, rows)
}
pub(crate) fn synth_pg_policies(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("tablename", DataType::Text, false),
ColumnSchema::new("policyname", DataType::Text, false),
ColumnSchema::new("permissive", DataType::Text, false),
ColumnSchema::new("roles", DataType::Text, false),
ColumnSchema::new("cmd", DataType::Text, false),
ColumnSchema::new("qual", DataType::Text, true),
ColumnSchema::new("with_check", DataType::Text, true),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
for p in &t.schema().policies {
let roles = if p.roles.is_empty() {
alloc::string::String::from("{public}")
} else {
alloc::format!("{{{}}}", p.roles.join(","))
};
rows.push(Row::new(alloc::vec![
Value::text("public"),
Value::text(tname.clone()),
Value::text(p.name.clone()),
Value::text(if p.permissive {
"PERMISSIVE"
} else {
"RESTRICTIVE"
}),
Value::text(roles),
Value::text(p.cmd.as_pg_word()),
p.using_expr.clone().map_or(Value::Null, Value::text),
p.with_check_expr.clone().map_or(Value::Null, Value::text),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_pg_largeobject(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
const PAGE: usize = 2048;
let schema = alloc::vec![
ColumnSchema::new("loid", DataType::BigInt, false),
ColumnSchema::new("pageno", DataType::Int, false),
ColumnSchema::new("data", DataType::Bytes, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for (oid, bytes) in cat.large_objects() {
for (pageno, chunk) in bytes.chunks(PAGE).enumerate() {
rows.push(Row::new(alloc::vec![
Value::BigInt(i64::from(*oid)),
Value::Int(i32::try_from(pageno).unwrap_or(i32::MAX)),
Value::Bytes(chunk.to_vec().into()),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_pg_largeobject_metadata(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("lomowner", DataType::BigInt, false),
ColumnSchema::new("lomacl", DataType::Text, true),
];
let rows: Vec<Row<'static>> = cat
.large_objects()
.keys()
.map(|oid| {
Row::new(alloc::vec![
Value::BigInt(i64::from(*oid)),
Value::BigInt(10),
Value::Null,
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_statistic_ext(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("stxrelid", DataType::BigInt, false),
ColumnSchema::new("stxname", DataType::Text, false),
ColumnSchema::new("stxnamespace", DataType::BigInt, false),
ColumnSchema::new("stxowner", DataType::BigInt, false),
ColumnSchema::new("stxkeys", DataType::Text, false),
ColumnSchema::new("stxstattarget", DataType::SmallInt, true),
ColumnSchema::new("stxkind", DataType::Text, false),
ColumnSchema::new("stxexprs", DataType::Text, true),
];
let rows: Vec<Row<'static>> = cat
.statistics_ext()
.iter()
.map(|st| {
Row::new(alloc::vec![
Value::BigInt(0),
Value::BigInt(0),
Value::text(st.name.clone()),
Value::BigInt(2200),
Value::BigInt(0),
Value::text(st.columns.join(" ")),
Value::Null, Value::text(alloc::format!("{{{}}}", st.kinds.join(","))),
Value::Null, ])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_statistic(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("starelid", DataType::BigInt, false),
ColumnSchema::new("staattnum", DataType::SmallInt, false),
ColumnSchema::new("stainherit", DataType::Bool, false),
ColumnSchema::new("stanullfrac", DataType::Float, false),
ColumnSchema::new("stawidth", DataType::Int, false),
ColumnSchema::new("stadistinct", DataType::Float, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut starelid: i64 = 16384;
for name in cat.visible_table_names() {
if crate::is_internal_table_name(&name) {
continue;
}
let Some(t) = cat.get(&name) else {
continue;
};
#[allow(clippy::cast_possible_wrap)]
for (i, _col) in t.schema().columns.iter().enumerate() {
let attnum = (i + 1) as i16;
rows.push(Row::new(alloc::vec![
Value::BigInt(starelid),
Value::SmallInt(attnum),
Value::Bool(false),
Value::Float(0.0),
Value::Int(0),
Value::Float(0.0),
]));
}
starelid = starelid.saturating_add(1);
}
(schema, rows)
}
pub(crate) fn synth_pg_stat_io(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("backend_type", DataType::Text, false),
ColumnSchema::new("object", DataType::Text, false),
ColumnSchema::new("context", DataType::Text, false),
ColumnSchema::new("reads", DataType::BigInt, false),
ColumnSchema::new("read_time", DataType::Float, false),
ColumnSchema::new("writes", DataType::BigInt, false),
ColumnSchema::new("write_time", DataType::Float, false),
ColumnSchema::new("writebacks", DataType::BigInt, false),
ColumnSchema::new("writeback_time", DataType::Float, false),
ColumnSchema::new("extends", DataType::BigInt, false),
ColumnSchema::new("extend_time", DataType::Float, false),
ColumnSchema::new("op_bytes", DataType::BigInt, false),
ColumnSchema::new("hits", DataType::BigInt, false),
ColumnSchema::new("evictions", DataType::BigInt, false),
ColumnSchema::new("reuses", DataType::BigInt, false),
ColumnSchema::new("fsyncs", DataType::BigInt, false),
ColumnSchema::new("fsync_time", DataType::Float, false),
ColumnSchema::new("stats_reset", DataType::Timestamptz, true),
];
let rows = alloc::vec![Row::new(alloc::vec![
Value::text("client backend"),
Value::text("relation"),
Value::text("normal"),
Value::BigInt(0),
Value::Float(0.0),
Value::BigInt(0),
Value::Float(0.0),
Value::BigInt(0),
Value::Float(0.0),
Value::BigInt(0),
Value::Float(0.0),
Value::BigInt(8192), Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::Float(0.0),
Value::Null,
])];
(schema, rows)
}
pub(crate) fn synth_pg_stat_user_functions(
_cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("funcid", DataType::BigInt, false),
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("funcname", DataType::Text, false),
ColumnSchema::new("calls", DataType::BigInt, false),
ColumnSchema::new("total_time", DataType::Float, false),
ColumnSchema::new("self_time", DataType::Float, false),
];
let rows: Vec<Row<'static>> = Vec::new();
(schema, rows)
}
pub(crate) const fn am_oid_of(kind: &spg_storage::IndexKind) -> i64 {
use spg_storage::IndexKind as K;
match kind {
K::Nsw(_) => 0, K::Brin { .. } => 3580,
K::Gin(_) | K::GinTrgm(_) | K::GinFulltext(_) | K::GinJsonb(_) => 2742,
_ => 403, }
}
pub(crate) fn synth_pg_am(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("amname", DataType::Text, false),
ColumnSchema::new("amhandler", DataType::BigInt, false),
ColumnSchema::new("amtype", DataType::Text, false), ];
let rows = alloc::vec![
Row::new(alloc::vec![
Value::BigInt(2),
Value::text("heap"),
Value::BigInt(0),
Value::text("t"),
]),
Row::new(alloc::vec![
Value::BigInt(403),
Value::text("btree"),
Value::BigInt(0),
Value::text("i"),
]),
Row::new(alloc::vec![
Value::BigInt(405),
Value::text("hash"),
Value::BigInt(0),
Value::text("i"),
]),
Row::new(alloc::vec![
Value::BigInt(783),
Value::text("gist"),
Value::BigInt(0),
Value::text("i"),
]),
Row::new(alloc::vec![
Value::BigInt(2742),
Value::text("gin"),
Value::BigInt(0),
Value::text("i"),
]),
Row::new(alloc::vec![
Value::BigInt(4000),
Value::text("spgist"),
Value::BigInt(0),
Value::text("i"),
]),
Row::new(alloc::vec![
Value::BigInt(3580),
Value::text("brin"),
Value::BigInt(0),
Value::text("i"),
]),
];
(schema, rows)
}
pub(crate) fn synth_pg_collation(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("collname", DataType::Text, false),
ColumnSchema::new("collnamespace", DataType::BigInt, false),
ColumnSchema::new("collowner", DataType::BigInt, false),
ColumnSchema::new("collprovider", DataType::Text, false), ColumnSchema::new("collisdeterministic", DataType::Bool, false),
ColumnSchema::new("collencoding", DataType::Int, false),
ColumnSchema::new("collcollate", DataType::Text, true),
ColumnSchema::new("collctype", DataType::Text, true),
ColumnSchema::new("colllocale", DataType::Text, true),
ColumnSchema::new("collicurules", DataType::Text, true),
ColumnSchema::new("collversion", DataType::Text, true),
];
let rows = alloc::vec![
Row::new(alloc::vec![
Value::BigInt(100),
Value::text("default"),
Value::BigInt(11),
Value::BigInt(10),
Value::text("d"),
Value::Bool(true),
Value::Int(-1),
Value::Null,
Value::Null,
Value::Null, Value::Null, Value::Null, ]),
Row::new(alloc::vec![
Value::BigInt(950),
Value::text("C"),
Value::BigInt(11),
Value::BigInt(10),
Value::text("c"),
Value::Bool(true),
Value::Int(-1),
Value::text("C"),
Value::text("C"),
Value::Null,
Value::Null,
Value::Null,
]),
Row::new(alloc::vec![
Value::BigInt(951),
Value::text("POSIX"),
Value::BigInt(11),
Value::BigInt(10),
Value::text("c"),
Value::Bool(true),
Value::Int(-1),
Value::text("POSIX"),
Value::text("POSIX"),
Value::Null,
Value::Null,
Value::Null,
]),
];
(schema, rows)
}
pub(crate) fn synth_pg_stat_archiver(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("archived_count", DataType::BigInt, false),
ColumnSchema::new("last_archived_wal", DataType::Text, true),
ColumnSchema::new("last_archived_time", DataType::Timestamptz, true),
ColumnSchema::new("failed_count", DataType::BigInt, false),
ColumnSchema::new("last_failed_wal", DataType::Text, true),
ColumnSchema::new("last_failed_time", DataType::Timestamptz, true),
ColumnSchema::new("stats_reset", DataType::Timestamptz, true),
];
let rows = alloc::vec![Row::new(alloc::vec![
Value::BigInt(0),
Value::Null,
Value::Null,
Value::BigInt(0),
Value::Null,
Value::Null,
Value::Null,
])];
(schema, rows)
}
pub(crate) fn synth_pg_stat_replication(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("pid", DataType::Int, false),
ColumnSchema::new("usename", DataType::Text, true),
ColumnSchema::new("application_name", DataType::Text, true),
ColumnSchema::new("client_addr", DataType::Text, true),
ColumnSchema::new("state", DataType::Text, false),
ColumnSchema::new("sent_lsn", DataType::Text, true),
ColumnSchema::new("write_lsn", DataType::Text, true),
ColumnSchema::new("flush_lsn", DataType::Text, true),
ColumnSchema::new("replay_lsn", DataType::Text, true),
ColumnSchema::new("sync_state", DataType::Text, false),
ColumnSchema::new("reply_time", DataType::Timestamptz, true),
];
let rows: Vec<Row<'static>> = Vec::new();
(schema, rows)
}
pub(crate) fn synth_pg_stat_slru(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("name", DataType::Text, false),
ColumnSchema::new("blks_zeroed", DataType::BigInt, false),
ColumnSchema::new("blks_hit", DataType::BigInt, false),
ColumnSchema::new("blks_read", DataType::BigInt, false),
ColumnSchema::new("blks_written", DataType::BigInt, false),
ColumnSchema::new("blks_exists", DataType::BigInt, false),
ColumnSchema::new("flushes", DataType::BigInt, false),
ColumnSchema::new("truncates", DataType::BigInt, false),
ColumnSchema::new("stats_reset", DataType::Timestamptz, true),
];
(schema, Vec::new())
}
pub(crate) fn synth_pg_stat_subscription_stats(
_cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("subid", DataType::BigInt, false),
ColumnSchema::new("subname", DataType::Text, false),
ColumnSchema::new("apply_error_count", DataType::BigInt, false),
ColumnSchema::new("sync_error_count", DataType::BigInt, false),
ColumnSchema::new("confl_insert_exists", DataType::BigInt, false),
ColumnSchema::new("confl_update_origin_differs", DataType::BigInt, false),
ColumnSchema::new("confl_update_exists", DataType::BigInt, false),
ColumnSchema::new("confl_update_missing", DataType::BigInt, false),
ColumnSchema::new("confl_delete_origin_differs", DataType::BigInt, false),
ColumnSchema::new("confl_delete_missing", DataType::BigInt, false),
ColumnSchema::new("confl_multiple_unique_conflicts", DataType::BigInt, false),
ColumnSchema::new("stats_reset", DataType::Timestamptz, true),
];
(schema, Vec::new())
}
pub(crate) fn synth_pg_stat_checkpointer(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("num_timed", DataType::BigInt, false),
ColumnSchema::new("num_requested", DataType::BigInt, false),
ColumnSchema::new("num_done", DataType::BigInt, false),
ColumnSchema::new("restartpoints_timed", DataType::BigInt, false),
ColumnSchema::new("restartpoints_req", DataType::BigInt, false),
ColumnSchema::new("restartpoints_done", DataType::BigInt, false),
ColumnSchema::new("write_time", DataType::Float, false),
ColumnSchema::new("sync_time", DataType::Float, false),
ColumnSchema::new("buffers_written", DataType::BigInt, false),
ColumnSchema::new("slru_written", DataType::BigInt, false),
ColumnSchema::new("stats_reset", DataType::Timestamptz, true),
];
let rows = alloc::vec![Row::new(alloc::vec![
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::Float(0.0),
Value::Float(0.0),
Value::BigInt(0),
Value::BigInt(0),
Value::Null,
])];
(schema, rows)
}
pub(crate) fn synth_pg_stat_wal(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("wal_records", DataType::BigInt, false),
ColumnSchema::new("wal_fpi", DataType::BigInt, false),
ColumnSchema::new("wal_bytes", DataType::BigInt, false),
ColumnSchema::new("wal_buffers_full", DataType::BigInt, false),
ColumnSchema::new("stats_reset", DataType::Timestamptz, true),
];
let rows = alloc::vec![Row::new(alloc::vec![
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::Null,
])];
(schema, rows)
}
pub(crate) fn synth_pg_stat_bgwriter(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("checkpoints_timed", DataType::BigInt, false),
ColumnSchema::new("checkpoints_req", DataType::BigInt, false),
ColumnSchema::new("checkpoint_write_time", DataType::Float, false),
ColumnSchema::new("checkpoint_sync_time", DataType::Float, false),
ColumnSchema::new("buffers_checkpoint", DataType::BigInt, false),
ColumnSchema::new("buffers_clean", DataType::BigInt, false),
ColumnSchema::new("maxwritten_clean", DataType::BigInt, false),
ColumnSchema::new("buffers_backend", DataType::BigInt, false),
ColumnSchema::new("buffers_backend_fsync", DataType::BigInt, false),
ColumnSchema::new("buffers_alloc", DataType::BigInt, false),
ColumnSchema::new("stats_reset", DataType::Timestamptz, true),
];
let rows = alloc::vec![Row::new(alloc::vec![
Value::BigInt(0), Value::BigInt(0), Value::Float(0.0),
Value::Float(0.0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::Null,
])];
(schema, rows)
}
pub(crate) fn synth_pg_tablespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("spcname", DataType::Text, false),
ColumnSchema::new("spcowner", DataType::BigInt, false),
ColumnSchema::new("spcacl", DataType::Text, true),
ColumnSchema::new("spcoptions", DataType::Text, true),
];
let rows = alloc::vec![
Row::new(alloc::vec![
Value::BigInt(1663),
Value::text("pg_default"),
Value::BigInt(10),
Value::Null,
Value::Null,
]),
Row::new(alloc::vec![
Value::BigInt(1664),
Value::text("pg_global"),
Value::BigInt(10),
Value::Null,
Value::Null,
]),
];
(schema, rows)
}
pub(crate) fn synth_pg_stat_user_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("relid", DataType::BigInt, false),
ColumnSchema::new("indexrelid", DataType::BigInt, false),
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("relname", DataType::Text, false),
ColumnSchema::new("indexrelname", DataType::Text, false),
ColumnSchema::new("idx_scan", DataType::BigInt, false),
ColumnSchema::new("idx_tup_read", DataType::BigInt, false),
ColumnSchema::new("idx_tup_fetch", DataType::BigInt, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut relid: i64 = 16384;
let mut indexrelid: i64 = 100_000;
for tname in cat.visible_table_names() {
if crate::is_internal_table_name(&tname) {
continue;
}
let Some(t) = cat.get(&tname) else {
continue;
};
for idx in t.indices() {
indexrelid = indexrelid.saturating_add(1);
rows.push(Row::new(alloc::vec![
Value::BigInt(relid),
Value::BigInt(indexrelid),
Value::text("public"),
Value::Text(alloc::borrow::Cow::Owned(tname.clone())),
Value::Text(alloc::borrow::Cow::Owned(idx.name.clone())),
Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), ]));
}
relid = relid.saturating_add(1);
}
(schema, rows)
}
pub(crate) fn synth_pg_stat_user_tables(
cat: &Catalog,
write_stats: &alloc::collections::BTreeMap<alloc::string::String, (u64, u64, u64)>,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("relid", DataType::BigInt, false),
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("relname", DataType::Text, false),
ColumnSchema::new("seq_scan", DataType::BigInt, false),
ColumnSchema::new("seq_tup_read", DataType::BigInt, false),
ColumnSchema::new("idx_scan", DataType::BigInt, false),
ColumnSchema::new("idx_tup_fetch", DataType::BigInt, false),
ColumnSchema::new("n_tup_ins", DataType::BigInt, false),
ColumnSchema::new("n_tup_upd", DataType::BigInt, false),
ColumnSchema::new("n_tup_del", DataType::BigInt, false),
ColumnSchema::new("n_live_tup", DataType::BigInt, false),
ColumnSchema::new("n_dead_tup", DataType::BigInt, false),
ColumnSchema::new("last_vacuum", DataType::Timestamptz, true),
ColumnSchema::new("last_autovacuum", DataType::Timestamptz, true),
ColumnSchema::new("last_analyze", DataType::Timestamptz, true),
ColumnSchema::new("last_autoanalyze", DataType::Timestamptz, true),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut relid: i64 = 16384; for name in cat.visible_table_names() {
if crate::is_internal_table_name(&name) {
continue;
}
let Some(t) = cat.get(&name) else {
continue;
};
let dead = i64::try_from(t.dead_rows()).unwrap_or(i64::MAX);
let live_rows = (t.rows().len() as i64).saturating_sub(dead);
let (ins, upd, del) = write_stats.get(&name).copied().unwrap_or((0, 0, 0));
use core::sync::atomic::Ordering;
let sc = t.scan_stats();
let as_big = |a: &core::sync::atomic::AtomicU64| {
Value::BigInt(i64::try_from(a.load(Ordering::Relaxed)).unwrap_or(i64::MAX))
};
rows.push(Row::new(alloc::vec![
Value::BigInt(relid),
Value::text("public"),
Value::Text(alloc::borrow::Cow::Owned(name)),
as_big(&sc.seq_scan),
as_big(&sc.seq_tup_read),
as_big(&sc.idx_scan),
as_big(&sc.idx_tup_fetch),
Value::BigInt(i64::try_from(ins).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(upd).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(del).unwrap_or(i64::MAX)),
Value::BigInt(live_rows),
Value::BigInt(dead),
Value::Null, t.maintenance_stamps()
.0
.map_or(Value::Null, Value::Timestamp),
t.maintenance_stamps()
.1
.map_or(Value::Null, Value::Timestamp),
Value::Null, ]));
relid = relid.saturating_add(1);
}
(schema, rows)
}
pub(crate) fn synth_pg_stat_database(
eng: &Engine,
tup_inserted: u64,
tup_updated: u64,
tup_deleted: u64,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("datid", DataType::BigInt, false),
ColumnSchema::new("datname", DataType::Text, false),
ColumnSchema::new("numbackends", DataType::Int, false),
ColumnSchema::new("xact_commit", DataType::BigInt, false),
ColumnSchema::new("xact_rollback", DataType::BigInt, false),
ColumnSchema::new("blks_read", DataType::BigInt, false),
ColumnSchema::new("blks_hit", DataType::BigInt, false),
ColumnSchema::new("tup_returned", DataType::BigInt, false),
ColumnSchema::new("tup_fetched", DataType::BigInt, false),
ColumnSchema::new("tup_inserted", DataType::BigInt, false),
ColumnSchema::new("tup_updated", DataType::BigInt, false),
ColumnSchema::new("tup_deleted", DataType::BigInt, false),
ColumnSchema::new("conflicts", DataType::BigInt, false),
ColumnSchema::new("temp_files", DataType::BigInt, false),
ColumnSchema::new("temp_bytes", DataType::BigInt, false),
ColumnSchema::new("deadlocks", DataType::BigInt, false),
ColumnSchema::new("checksum_failures", DataType::BigInt, true),
ColumnSchema::new("checksum_last_failure", DataType::Timestamptz, true),
ColumnSchema::new("blk_read_time", DataType::Float, false),
ColumnSchema::new("blk_write_time", DataType::Float, false),
ColumnSchema::new("session_time", DataType::Float, false),
ColumnSchema::new("active_time", DataType::Float, false),
ColumnSchema::new("idle_in_transaction_time", DataType::Float, false),
ColumnSchema::new("sessions", DataType::BigInt, false),
ColumnSchema::new("sessions_abandoned", DataType::BigInt, false),
ColumnSchema::new("sessions_fatal", DataType::BigInt, false),
ColumnSchema::new("sessions_killed", DataType::BigInt, false),
ColumnSchema::new("parallel_workers_to_launch", DataType::BigInt, false),
ColumnSchema::new("parallel_workers_launched", DataType::BigInt, false),
ColumnSchema::new("stats_reset", DataType::Timestamptz, true),
];
let commits = eng.xact_commit.load(core::sync::atomic::Ordering::Relaxed);
let rollbacks = eng
.xact_rollback
.load(core::sync::atomic::Ordering::Relaxed);
let backends = eng.backend_count_fn.map_or(1, |f| f());
let (mut tup_returned, mut tup_fetched) = (0u64, 0u64);
{
use core::sync::atomic::Ordering;
let cat = eng.active_catalog();
for name in cat.visible_table_names() {
if let Some(t) = cat.get(&name) {
let sc = t.scan_stats();
tup_returned = tup_returned
.saturating_add(sc.seq_tup_read.load(Ordering::Relaxed))
.saturating_add(sc.idx_tup_fetch.load(Ordering::Relaxed));
tup_fetched = tup_fetched.saturating_add(sc.idx_tup_fetch.load(Ordering::Relaxed));
}
}
}
let blks_read = eng
.active_catalog()
.cold_read_stats
.cold_reads
.load(core::sync::atomic::Ordering::Relaxed);
let blks_hit = tup_returned.saturating_sub(blks_read);
let datname = eng
.session_params
.get("spg.database")
.cloned()
.unwrap_or_else(|| alloc::string::String::from("spg"));
let (temp_files, temp_bytes) = {
use core::sync::atomic::Ordering;
(
eng.spill_stats.files.load(Ordering::Relaxed),
eng.spill_stats.bytes.load(Ordering::Relaxed),
)
};
let rows = alloc::vec![Row::new(alloc::vec![
Value::BigInt(16384),
Value::text(datname),
Value::Int(i32::try_from(backends).unwrap_or(i32::MAX)),
Value::BigInt(i64::try_from(commits).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(rollbacks).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(blks_read).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(blks_hit).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(tup_returned).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(tup_fetched).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(tup_inserted).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(tup_updated).unwrap_or(i64::MAX)),
Value::BigInt(i64::try_from(tup_deleted).unwrap_or(i64::MAX)),
Value::BigInt(0), Value::BigInt(i64::try_from(temp_files).unwrap_or(i64::MAX)), Value::BigInt(i64::try_from(temp_bytes).unwrap_or(i64::MAX)), Value::BigInt(0), Value::BigInt(0), Value::Null, Value::Float(0.0), Value::Float(0.0), Value::Float(0.0), Value::Float(0.0), Value::Float(0.0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::Null, ])];
(schema, rows)
}
pub(crate) fn synth_pg_subscription(eng: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("subdbid", DataType::BigInt, false),
ColumnSchema::new("subname", DataType::Text, false),
ColumnSchema::new("subowner", DataType::BigInt, false),
ColumnSchema::new("subenabled", DataType::Bool, false),
ColumnSchema::new("subconninfo", DataType::Text, false),
ColumnSchema::new("subslotname", DataType::Text, true),
ColumnSchema::new("subpublications", DataType::Text, false),
ColumnSchema::new("subbinary", DataType::Bool, false),
ColumnSchema::new("substream", DataType::Bool, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut oid: i64 = 80_000;
for (name, sub) in eng.subscriptions().iter() {
oid = oid.saturating_add(1);
let pubs = sub.publications.join(",");
rows.push(Row::new(alloc::vec![
Value::BigInt(oid),
Value::BigInt(16384), Value::text(name.clone()),
Value::BigInt(10), Value::Bool(sub.enabled),
Value::text("[redacted]"), Value::Null, Value::text(pubs),
Value::Bool(false), Value::Bool(false), ]));
}
(schema, rows)
}
pub(crate) fn synth_pg_publication(eng: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
use spg_sql::ast::PublicationScope;
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("pubname", DataType::Text, false),
ColumnSchema::new("pubowner", DataType::BigInt, false),
ColumnSchema::new("puballtables", DataType::Bool, false),
ColumnSchema::new("pubinsert", DataType::Bool, false),
ColumnSchema::new("pubupdate", DataType::Bool, false),
ColumnSchema::new("pubdelete", DataType::Bool, false),
ColumnSchema::new("pubtruncate", DataType::Bool, false),
ColumnSchema::new("pubviaroot", DataType::Bool, false),
ColumnSchema::new("pubgencols", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut oid: i64 = 70_000;
for (name, scope) in eng.publications().iter() {
oid = oid.saturating_add(1);
let all_tables = matches!(scope, PublicationScope::AllTables);
rows.push(Row::new(alloc::vec![
Value::BigInt(oid),
Value::text(name.clone()),
Value::BigInt(10),
Value::Bool(all_tables),
Value::Bool(true), Value::Bool(true), Value::Bool(true), Value::Bool(true), Value::Bool(false), Value::text("n"), ]));
}
(schema, rows)
}
pub(crate) fn synth_pg_replication_slots(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("slot_name", DataType::Text, false),
ColumnSchema::new("plugin", DataType::Text, true),
ColumnSchema::new("slot_type", DataType::Text, false),
ColumnSchema::new("datoid", DataType::BigInt, true),
ColumnSchema::new("database", DataType::Text, true),
ColumnSchema::new("temporary", DataType::Bool, false),
ColumnSchema::new("active", DataType::Bool, false),
ColumnSchema::new("active_pid", DataType::Int, true),
ColumnSchema::new("xmin", DataType::BigInt, true),
ColumnSchema::new("catalog_xmin", DataType::BigInt, true),
ColumnSchema::new("restart_lsn", DataType::Text, true),
ColumnSchema::new("confirmed_flush_lsn", DataType::Text, true),
ColumnSchema::new("wal_status", DataType::Text, true),
ColumnSchema::new("safe_wal_size", DataType::BigInt, true),
];
let rows: Vec<Row<'static>> = cat
.replication_slots()
.iter()
.map(|(name, (plugin, slot_type))| {
Row::new(alloc::vec![
Value::text(name.clone()),
if plugin.is_empty() {
Value::Null
} else {
Value::text(plugin.clone())
},
Value::text(slot_type.clone()),
Value::BigInt(16384),
Value::text("spg"),
Value::Bool(false),
Value::Bool(false),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::text("unreserved"),
Value::Null,
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_information_schema_attributes(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("udt_catalog", DataType::Text, false),
ColumnSchema::new("udt_schema", DataType::Text, false),
ColumnSchema::new("udt_name", DataType::Text, false),
ColumnSchema::new("attribute_name", DataType::Text, false),
ColumnSchema::new("ordinal_position", DataType::Int, false),
ColumnSchema::new("data_type", DataType::Text, false),
ColumnSchema::new("is_nullable", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for (_name, def) in cat.composite_types() {
for (i, (field_name, field_type)) in def.fields.iter().enumerate() {
#[allow(clippy::cast_possible_wrap)]
let ordinal = (i + 1) as i32;
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(def.name.clone()),
Value::text(field_name.clone()),
Value::Int(ordinal),
Value::text(pg_data_type_text(*field_type)),
Value::text("YES"),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_information_schema_domains(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("domain_catalog", DataType::Text, false),
ColumnSchema::new("domain_schema", DataType::Text, false),
ColumnSchema::new("domain_name", DataType::Text, false),
ColumnSchema::new("data_type", DataType::Text, false),
ColumnSchema::new("udt_catalog", DataType::Text, false),
ColumnSchema::new("udt_schema", DataType::Text, false),
ColumnSchema::new("udt_name", DataType::Text, false),
ColumnSchema::new("domain_default", DataType::Text, true),
ColumnSchema::new("is_nullable", DataType::Text, false),
];
let rows: Vec<Row<'static>> = cat
.domain_types()
.values()
.map(|def| {
let base_name = pg_data_type_text(def.base_type);
Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(def.name.clone()),
Value::text(base_name.clone()),
Value::text("spg"),
Value::text("pg_catalog"),
Value::text(base_name),
def.default
.as_ref()
.map(|s| Value::text(s.clone()))
.unwrap_or(Value::Null),
Value::text(if def.nullable { "YES" } else { "NO" }),
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_enum(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("enumtypid", DataType::BigInt, false),
ColumnSchema::new("enumsortorder", DataType::Float, false),
ColumnSchema::new("enumlabel", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut label_oid: i64 = 60_000;
let (enum_oids, _, _) = user_type_oids(cat);
for ((_name, def), (_, typid)) in cat.enum_types().iter().zip(enum_oids) {
for (i, label) in def.labels.iter().enumerate() {
label_oid = label_oid.saturating_add(1);
rows.push(Row::new(alloc::vec![
Value::BigInt(label_oid),
Value::BigInt(typid),
Value::Float((i + 1) as f64),
Value::text(label.clone()),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_information_schema_table_constraints(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("constraint_catalog", DataType::Text, false),
ColumnSchema::new("constraint_schema", DataType::Text, false),
ColumnSchema::new("constraint_name", DataType::Text, false),
ColumnSchema::new("table_catalog", DataType::Text, false),
ColumnSchema::new("table_schema", DataType::Text, false),
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("constraint_type", DataType::Text, false),
ColumnSchema::new("is_deferrable", DataType::Text, false),
ColumnSchema::new("initially_deferred", DataType::Text, false),
ColumnSchema::new("enforced", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
for uc in t.schema().uniqueness_constraints.iter() {
let conname = pg_unique_conname(t, uc, &tname);
let kind = if uc.is_primary_key {
"PRIMARY KEY"
} else {
"UNIQUE"
};
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(conname),
Value::text("spg"),
Value::text("public"),
Value::text(tname.clone()),
Value::text(kind),
Value::text("NO"),
Value::text("NO"),
Value::text("YES"),
]));
}
for idx in t.indices() {
if !idx.is_unique || idx.partial_predicate.is_some() {
continue;
}
let already = t
.schema()
.uniqueness_constraints
.iter()
.any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
if already {
continue;
}
let is_primary = idx.name.ends_with("_pkey");
let kind = if is_primary { "PRIMARY KEY" } else { "UNIQUE" };
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(idx.name.clone()),
Value::text("spg"),
Value::text("public"),
Value::text(tname.clone()),
Value::text(kind),
Value::text("NO"),
Value::text("NO"),
Value::text("YES"),
]));
}
for fk in t.schema().foreign_keys.iter() {
let conname = fk
.name
.clone()
.unwrap_or_else(|| pg_fk_conname(t, fk, &tname));
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(conname),
Value::text("spg"),
Value::text("public"),
Value::text(tname.clone()),
Value::text("FOREIGN KEY"),
Value::text("NO"),
Value::text("NO"),
Value::text("YES"),
]));
}
let check_names = pg_check_connames(t, &tname, &t.schema().checks);
for (ci, _check) in t.schema().checks.iter().enumerate() {
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(check_names[ci].clone()),
Value::text("spg"),
Value::text("public"),
Value::text(tname.clone()),
Value::text("CHECK"),
Value::text("NO"),
Value::text("NO"),
Value::text("YES"),
]));
}
for col in t.schema().columns.iter() {
if col.nullable {
continue;
}
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(alloc::format!("{tname}_{}_not_null", col.name)),
Value::text("spg"),
Value::text("public"),
Value::text(tname.clone()),
Value::text("CHECK"),
Value::text("NO"),
Value::text("NO"),
Value::text("YES"),
]));
}
}
(schema, rows)
}
pub(crate) const OID_TABLE_BASE: i64 = 16384;
pub(crate) const OID_VIEW_BASE: i64 = 32768;
pub(crate) const OID_INDEX_BASE: i64 = 100_000;
pub(crate) const OID_CAST_BASE: i64 = 200_000;
pub(crate) const OID_SEQ_BASE: i64 = 300_000;
pub(crate) const OID_FUNC_BASE: i64 = 400_000;
pub(crate) const OID_TRIGGER_BASE: i64 = 600_000;
pub(crate) fn function_oid(cat: &Catalog, bare: &str) -> Option<i64> {
let mut oid = OID_FUNC_BASE;
let mut hit = None;
for def in cat.functions().values() {
oid += 1;
if def.name == bare {
if hit.is_some() {
return None;
}
hit = Some(oid);
}
}
hit
}
pub(crate) fn function_oid_by_signature(cat: &Catalog, bare: &str, arg_types: &str) -> Option<i64> {
let mut oid = OID_FUNC_BASE;
for def in cat.functions().values() {
oid += 1;
if def.name == bare && canonical_arg_types(&def.args_repr) == arg_types {
return Some(oid);
}
}
None
}
pub(crate) fn builtin_type_oid_exists(oid: i64) -> bool {
[
DataType::Bool,
DataType::SmallInt,
DataType::Int,
DataType::BigInt,
DataType::Real,
DataType::Float,
DataType::Numeric {
precision: 0,
scale: 0,
},
DataType::Text,
DataType::Bytes,
DataType::Date,
DataType::Timestamp,
DataType::Timestamptz,
DataType::Interval,
DataType::Uuid,
DataType::Json,
DataType::Jsonb,
]
.into_iter()
.any(|t| pg_type_oid(t) == oid)
}
pub(crate) fn relation_name_for_oid(cat: &Catalog, oid: i64) -> Option<String> {
for (pos, tname) in cat.table_names().iter().enumerate() {
if OID_TABLE_BASE + pos as i64 == oid {
return cat.listed_name(tname).map(alloc::string::String::from);
}
}
for (pos, vname) in cat.views_all().keys().enumerate() {
let Some(vname) = cat.listed_name(vname) else {
continue;
};
if OID_VIEW_BASE + pos as i64 == oid {
return Some(alloc::string::String::from(vname));
}
}
let mut idx_oid = OID_INDEX_BASE;
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
for idx in t.indices() {
idx_oid += 1;
if idx_oid == oid {
return Some(idx.name.clone());
}
}
}
let mut seq_oid = OID_SEQ_BASE;
for name in cat.sequences_all().keys() {
let Some(name) = cat.listed_name(name) else {
continue;
};
seq_oid += 1;
if seq_oid == oid {
return Some(alloc::string::String::from(name));
}
}
None
}
pub(crate) fn relation_oid(cat: &Catalog, bare: &str) -> Option<i64> {
let stored = cat.temp_name_for(bare);
for (pos, tname) in cat.table_names().iter().enumerate() {
if tname == bare || Some(tname) == stored.as_ref() {
return Some(OID_TABLE_BASE + pos as i64);
}
}
for (pos, vname) in cat.views_all().keys().enumerate() {
let Some(vname) = cat.listed_name(vname) else {
continue;
};
if vname == bare {
return Some(OID_VIEW_BASE + pos as i64);
}
}
let mut idx_oid = OID_INDEX_BASE;
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
for idx in t.indices() {
idx_oid += 1;
if idx.name == bare {
return Some(idx_oid);
}
}
}
let mut seq_oid = OID_SEQ_BASE;
for name in cat.sequences_all().keys() {
let Some(name) = cat.listed_name(name) else {
continue;
};
seq_oid += 1;
if name == bare {
return Some(seq_oid);
}
}
None
}
#[must_use]
pub(crate) fn schema_name_for_oid(oid: i64) -> Option<alloc::string::String> {
let name = match oid {
11 => "pg_catalog",
2200 => "public",
13000 => "information_schema",
_ => return None,
};
Some(alloc::string::String::from(name))
}
fn pg_class_schema() -> Vec<ColumnSchema> {
alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("relname", DataType::Text, false),
ColumnSchema::new("relnamespace", DataType::BigInt, false),
ColumnSchema::new("reltype", DataType::BigInt, false),
ColumnSchema::new("reloftype", DataType::BigInt, false),
ColumnSchema::new("relowner", DataType::BigInt, false),
ColumnSchema::new("relam", DataType::BigInt, false),
ColumnSchema::new("relfilenode", DataType::BigInt, false),
ColumnSchema::new("reltablespace", DataType::BigInt, false),
ColumnSchema::new("relpages", DataType::Int, false),
ColumnSchema::new("reltuples", DataType::Float, false),
ColumnSchema::new("relallvisible", DataType::Int, false),
ColumnSchema::new("reltoastrelid", DataType::BigInt, false),
ColumnSchema::new("relhasindex", DataType::Bool, false),
ColumnSchema::new("relisshared", DataType::Bool, false),
ColumnSchema::new("relpersistence", DataType::Text, false),
ColumnSchema::new("relkind", DataType::Text, false),
ColumnSchema::new("relnatts", DataType::SmallInt, false),
ColumnSchema::new("relchecks", DataType::SmallInt, false),
ColumnSchema::new("relhasrules", DataType::Bool, false),
ColumnSchema::new("relhastriggers", DataType::Bool, false),
ColumnSchema::new("relhassubclass", DataType::Bool, false),
ColumnSchema::new("relrowsecurity", DataType::Bool, false),
ColumnSchema::new("relforcerowsecurity", DataType::Bool, false),
ColumnSchema::new("relispopulated", DataType::Bool, false),
ColumnSchema::new("relreplident", DataType::Text, false),
ColumnSchema::new("relispartition", DataType::Bool, false),
ColumnSchema::new("relacl", DataType::Text, true),
]
}
pub(crate) fn synth_pg_class(
cat: &Catalog,
frozen_xid: i64,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
use spg_storage::PartitionRole;
let schema = pg_class_schema();
let mut schema = schema;
splice_pg_class_v18_schema(&mut schema);
let mut rows: Vec<Row<'static>> = Vec::new();
let mut view_reloptions: alloc::collections::BTreeMap<alloc::string::String, &'static str> =
alloc::collections::BTreeMap::new();
let parents_with_children: alloc::collections::BTreeSet<alloc::string::String> = cat
.visible_table_names()
.iter()
.filter_map(|n| cat.get(n))
.flat_map(|c| match &c.schema().partition_role {
Some(PartitionRole::Range { parent_name, .. })
| Some(PartitionRole::List { parent_name, .. })
| Some(PartitionRole::Hash { parent_name, .. })
| Some(PartitionRole::Default { parent_name }) => {
alloc::vec![parent_name.to_ascii_lowercase()]
}
Some(PartitionRole::Inherits { parent_names }) => parent_names
.iter()
.map(|p| p.to_ascii_lowercase())
.collect::<alloc::vec::Vec<_>>(),
_ => alloc::vec::Vec::new(),
})
.collect();
for (pos, stored) in cat.table_names().into_iter().enumerate() {
let this_oid = OID_TABLE_BASE + pos as i64;
let Some(tname) = cat.listed_name(&stored).map(alloc::string::String::from) else {
continue;
};
let Some(t) = cat.get(&tname) else { continue };
let is_temp = stored != tname;
let schema_ref = t.schema();
let relkind: &'static str = if cat.materialized_views().contains_key(&tname) {
"m"
} else {
match &schema_ref.partition_role {
Some(PartitionRole::Parent { .. }) => "p", _ => "r", }
};
let is_partition = matches!(
&schema_ref.partition_role,
Some(PartitionRole::Range { .. })
| Some(PartitionRole::List { .. })
| Some(PartitionRole::Hash { .. })
| Some(PartitionRole::Default { .. })
);
let relnatts = i16::try_from(schema_ref.columns.len()).unwrap_or(i16::MAX);
let reltuples = t.rows().len() as f64;
let relpages = i32::try_from(t.hot_bytes().div_ceil(8192)).unwrap_or(i32::MAX);
let has_index = !t.indices().is_empty();
let has_triggers = cat
.triggers()
.iter()
.any(|tr| tr.table.eq_ignore_ascii_case(&tname));
let has_checks = schema_ref.checks.len();
rows.push(Row::new(alloc::vec![
Value::BigInt(this_oid),
Value::text(tname.clone()),
Value::BigInt(2200), Value::BigInt(0), Value::BigInt(0), Value::BigInt(10), Value::BigInt(0), Value::BigInt(this_oid), Value::BigInt(0), Value::Int(relpages), Value::Float(reltuples),
Value::Int(0), Value::BigInt(0), Value::Bool(has_index),
Value::Bool(false), Value::text(if is_temp { "t" } else { "p" }),
Value::text(relkind),
Value::SmallInt(relnatts),
Value::SmallInt(i16::try_from(has_checks).unwrap_or(i16::MAX)),
Value::Bool(
relkind == "m"
|| cat
.rules()
.iter()
.any(|r| r.table.eq_ignore_ascii_case(&tname)),
),
Value::Bool(has_triggers),
Value::Bool(parents_with_children.contains(&tname.to_ascii_lowercase())),
Value::Bool(schema_ref.row_security), Value::Bool(schema_ref.force_row_security), Value::Bool(true), Value::text("d"), Value::Bool(is_partition),
crate::acl::render_relacl(schema_ref).map_or(Value::Null, Value::text),
]));
}
for stored in cat.views_all().keys() {
let Some(vname) = cat.listed_name(stored) else {
continue;
};
let is_temp = stored != vname;
let Some(view_oid) = relation_oid(cat, vname) else {
continue;
};
let relnatts = i16::try_from(crate::describe::describe_view_columns(cat, vname).len())
.unwrap_or(i16::MAX);
if let Some(v) = cat.views_all().get(stored) {
match v.check_option {
1 => {
view_reloptions
.insert(alloc::string::String::from(vname), "check_option=local");
}
2 => {
view_reloptions
.insert(alloc::string::String::from(vname), "check_option=cascaded");
}
_ => {}
}
}
rows.push(Row::new(alloc::vec![
Value::BigInt(view_oid),
Value::text(vname.to_string()),
Value::BigInt(2200), Value::BigInt(0), Value::BigInt(0), Value::BigInt(10), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0),
Value::Int(0), Value::Float(-1.0), Value::Int(0),
Value::BigInt(0),
Value::Bool(false), Value::Bool(false),
Value::text(if is_temp { "t" } else { "p" }),
Value::text("v"), Value::SmallInt(relnatts),
Value::SmallInt(0),
Value::Bool(true), Value::Bool(false), Value::Bool(false),
Value::Bool(false),
Value::Bool(false),
Value::Bool(true), Value::text("n"), Value::Bool(false), Value::Null, ]));
}
let mut idx_oid: i64 = OID_INDEX_BASE;
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
for idx in t.indices() {
idx_oid += 1;
let relnatts = i16::try_from(1 + idx.extra_column_positions.len()).unwrap_or(i16::MAX);
rows.push(Row::new(alloc::vec![
Value::BigInt(idx_oid),
Value::text(idx.name.clone()),
Value::BigInt(2200), Value::BigInt(0), Value::BigInt(0), Value::BigInt(10), Value::BigInt(am_oid_of(&idx.kind)), Value::BigInt(idx_oid),
Value::BigInt(0),
Value::Int(0),
Value::Float(0.0),
Value::Int(0),
Value::BigInt(0),
Value::Bool(false), Value::Bool(false),
Value::text("p"),
Value::text("i"), Value::SmallInt(relnatts),
Value::SmallInt(0),
Value::Bool(false),
Value::Bool(false),
Value::Bool(false),
Value::Bool(false),
Value::Bool(false),
Value::Bool(true),
Value::text("n"), Value::Bool(false),
Value::Null,
]));
}
}
for (stored, def) in cat.sequences_all() {
let Some(name) = cat.listed_name(stored) else {
continue;
};
let is_temp = stored != name;
let Some(seq_oid) = relation_oid(cat, name) else {
continue;
};
rows.push(Row::new(alloc::vec![
Value::BigInt(seq_oid),
Value::text(name.to_string()),
Value::BigInt(2200), Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(10), Value::BigInt(0), Value::BigInt(seq_oid),
Value::BigInt(0),
Value::Int(1), Value::Float(1.0), Value::Int(0),
Value::BigInt(0),
Value::Bool(false), Value::Bool(false),
Value::text(if is_temp { "t" } else { "p" }),
Value::text("S"), Value::SmallInt(3),
Value::SmallInt(0),
Value::Bool(false),
Value::Bool(false),
Value::Bool(false),
Value::Bool(false),
Value::Bool(false),
Value::Bool(true),
Value::text("n"),
Value::Bool(false),
crate::acl::render_acl_list(&def.acl).map_or(Value::Null, Value::text),
]));
}
for (name, oid) in CATALOG_RELATIONS {
let relnatts = catalog_relation_columns(name, cat)
.map_or(0, |c| i16::try_from(c.len()).unwrap_or(i16::MAX));
rows.push(Row::new(alloc::vec![
Value::BigInt(*oid),
Value::text((*name).to_string()),
Value::BigInt(11), Value::BigInt(0), Value::BigInt(0), Value::BigInt(10), Value::BigInt(2), Value::BigInt(*oid),
Value::BigInt(0),
Value::Int(0), Value::Float(-1.0), Value::Int(0),
Value::BigInt(0),
Value::Bool(false), Value::Bool(false), Value::text("p"),
Value::text("r"), Value::SmallInt(relnatts),
Value::SmallInt(0),
Value::Bool(false), Value::Bool(false), Value::Bool(false),
Value::Bool(false),
Value::Bool(false),
Value::Bool(true), Value::text("n"),
Value::Bool(false), Value::Null, ]));
}
for row in &mut rows {
splice_pg_class_v18_row(row, frozen_xid, &view_reloptions);
}
(schema, rows)
}
const PG_CLASS_RELALLVISIBLE: usize = 11;
const PG_CLASS_RELKIND: usize = 16;
const PG_CLASS_RELISPARTITION: usize = 26;
fn pg_attr_collation_named(ty: DataType, declared: Option<&str>) -> i64 {
if let Some(name) = declared {
let n = name.trim();
if n.eq_ignore_ascii_case("C") {
return 950;
}
if n.eq_ignore_ascii_case("POSIX") {
return 951;
}
if n.eq_ignore_ascii_case("default") {
return 100;
}
}
pg_attr_collation(ty)
}
fn pg_attr_collation(ty: DataType) -> i64 {
match ty {
DataType::Text | DataType::Varchar(_) | DataType::Char(_) => 100,
DataType::Name => 950,
_ => 0,
}
}
fn splice_pg_class_v18_schema(schema: &mut Vec<ColumnSchema>) {
schema.push(ColumnSchema::new("reloptions", DataType::TextArray, true));
schema.push(ColumnSchema::new("relpartbound", DataType::Text, true));
schema.insert(
PG_CLASS_RELISPARTITION + 1,
ColumnSchema::new("relminmxid", DataType::BigInt, false),
);
schema.insert(
PG_CLASS_RELISPARTITION + 1,
ColumnSchema::new("relfrozenxid", DataType::Xid, false),
);
schema.insert(
PG_CLASS_RELISPARTITION + 1,
ColumnSchema::new("relrewrite", DataType::BigInt, false),
);
schema.insert(
PG_CLASS_RELALLVISIBLE + 1,
ColumnSchema::new("relallfrozen", DataType::Int, false),
);
}
fn splice_pg_class_v18_row(
row: &mut Row<'static>,
frozen_xid: i64,
view_reloptions: &alloc::collections::BTreeMap<alloc::string::String, &'static str>,
) {
let relkind = match row.values.get(PG_CLASS_RELKIND) {
Some(Value::Text(k)) => k.to_string(),
_ => alloc::string::String::new(),
};
let relname = match row.values.get(1) {
Some(Value::Text(n)) => n.to_string(),
_ => alloc::string::String::new(),
};
let (frozen, minmxid) = if relkind == "r" || relkind == "m" {
(frozen_xid, 1)
} else {
(0, 0)
};
let reloptions = view_reloptions
.get(&relname)
.filter(|_| relkind == "v" || relkind == "m")
.map_or(Value::Null, |o| {
Value::TextArray(alloc::vec![Some(alloc::string::String::from(*o))])
});
row.values.push(reloptions);
row.values.push(Value::Null); row.values
.insert(PG_CLASS_RELISPARTITION + 1, Value::BigInt(minmxid));
row.values.insert(
PG_CLASS_RELISPARTITION + 1,
Value::Xid(u32::try_from(frozen).unwrap_or(u32::MAX)),
);
row.values
.insert(PG_CLASS_RELISPARTITION + 1, Value::BigInt(0)); row.values.insert(PG_CLASS_RELALLVISIBLE + 1, Value::Int(0)); }
const PG_SYSTEM_ATTRIBUTES: &[(&str, i16, i64, i16, bool, &str)] = &[
("ctid", -1, 27, 6, false, "s"),
("xmin", -2, 28, 4, true, "i"),
("cmin", -3, 29, 4, true, "i"),
("xmax", -4, 28, 4, true, "i"),
("cmax", -5, 29, 4, true, "i"),
("tableoid", -6, 26, 4, true, "i"),
];
fn push_system_attributes(rows: &mut Vec<Row<'static>>, attrelid: i64) {
for (name, attnum, typid, attlen, byval, align) in PG_SYSTEM_ATTRIBUTES {
rows.push(Row::new(alloc::vec![
Value::BigInt(attrelid),
Value::text((*name).to_string()),
Value::BigInt(*typid),
Value::Int(0),
Value::SmallInt(*attlen),
Value::SmallInt(*attnum),
Value::Int(0),
Value::Int(-1),
Value::Bool(*byval),
Value::text("p"),
Value::text((*align).to_string()),
Value::Bool(true), Value::Bool(false), Value::text(""),
Value::text(""),
Value::Bool(false), Value::Bool(true), Value::Int(0),
Value::BigInt(0),
Value::Null,
Value::text(""),
Value::Bool(false),
Value::Null,
Value::Null,
Value::Null,
]));
}
}
fn pg_attribute_schema() -> Vec<ColumnSchema> {
alloc::vec![
ColumnSchema::new("attrelid", DataType::BigInt, false),
ColumnSchema::new("attname", DataType::Text, false),
ColumnSchema::new("atttypid", DataType::BigInt, false),
ColumnSchema::new("attstattarget", DataType::Int, false),
ColumnSchema::new("attlen", DataType::SmallInt, false),
ColumnSchema::new("attnum", DataType::SmallInt, false),
ColumnSchema::new("attndims", DataType::Int, false),
ColumnSchema::new("atttypmod", DataType::Int, false),
ColumnSchema::new("attbyval", DataType::Bool, false),
ColumnSchema::new("attstorage", DataType::Text, false),
ColumnSchema::new("attalign", DataType::Text, false),
ColumnSchema::new("attnotnull", DataType::Bool, false),
ColumnSchema::new("atthasdef", DataType::Bool, false),
ColumnSchema::new("attidentity", DataType::Text, false),
ColumnSchema::new("attgenerated", DataType::Text, false),
ColumnSchema::new("attisdropped", DataType::Bool, false),
ColumnSchema::new("attislocal", DataType::Bool, false),
ColumnSchema::new("attinhcount", DataType::Int, false),
ColumnSchema::new("attcollation", DataType::BigInt, false),
ColumnSchema::new("attacl", DataType::Text, true),
ColumnSchema::new("attcompression", DataType::Text, false),
ColumnSchema::new("atthasmissing", DataType::Bool, false),
ColumnSchema::new("attoptions", DataType::Text, true),
ColumnSchema::new("attfdwoptions", DataType::Text, true),
ColumnSchema::new("attmissingval", DataType::Text, true),
]
}
pub(crate) fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = pg_attribute_schema();
let mut rows: Vec<Row<'static>> = Vec::new();
let mut attrelid: i64 = 16384;
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else {
attrelid = attrelid.saturating_add(1);
continue;
};
for (i, col) in t.schema().columns.iter().enumerate() {
#[allow(clippy::cast_possible_wrap)]
let attnum = (i + 1) as i16;
let typlen: i16 = pg_type_len(col.ty);
let attndims: i32 = match col.ty {
DataType::TextArray
| DataType::IntArray
| DataType::BigIntArray
| DataType::SmallIntArray
| DataType::FloatArray
| DataType::BoolArray
| DataType::DateArray
| DataType::TimestampArray
| DataType::TimestamptzArray
| DataType::UuidArray
| DataType::BytesArray
| DataType::NumericArray
| DataType::JsonArray => 1,
DataType::IntArray2D | DataType::BigIntArray2D | DataType::TextArray2D => 2,
_ => 0,
};
let attstorage = if typlen > 0 { "p" } else { "x" };
let attalign = match typlen {
1 => "c",
2 => "s",
4 => "i",
_ => "d",
};
let has_default = col.default.is_some() || col.runtime_default.is_some();
let attidentity = if col.auto_increment { "d" } else { "" };
rows.push(Row::new(alloc::vec![
Value::BigInt(attrelid),
Value::text(col.name.clone()),
Value::BigInt(pg_type_oid(col.ty)),
Value::Int(-1), Value::SmallInt(typlen),
Value::SmallInt(attnum),
Value::Int(attndims),
Value::Int(pg_atttypmod(col.ty)),
Value::Bool(typlen > 0 && typlen <= 8),
Value::text(attstorage),
Value::text(attalign),
Value::Bool(!col.nullable),
Value::Bool(has_default),
Value::text(attidentity),
Value::text(if col.generated_stored_expr.is_some() {
"s"
} else {
""
}),
Value::Bool(false), Value::Bool(true), Value::Int(0), Value::BigInt(pg_attr_collation_named(
col.ty,
col.collation_name.as_deref()
)), crate::acl::render_acl_list(&col.acl).map_or(Value::Null, Value::text),
Value::text(""), Value::Bool(false), Value::Null, Value::Null, Value::Null, ]));
}
push_system_attributes(&mut rows, attrelid);
attrelid = attrelid.saturating_add(1);
}
for vname in cat.views_all().keys() {
let Some(vname) = cat.listed_name(vname) else {
continue;
};
let Some(view_oid) = relation_oid(cat, vname) else {
continue;
};
for (i, col) in crate::describe::describe_view_columns(cat, vname)
.iter()
.enumerate()
{
#[allow(clippy::cast_possible_wrap)]
let attnum = (i + 1) as i16;
let typlen: i16 = pg_type_len(col.ty);
rows.push(Row::new(alloc::vec![
Value::BigInt(view_oid),
Value::text(col.name.clone()),
Value::BigInt(pg_type_oid(col.ty)),
Value::Int(-1),
Value::SmallInt(typlen),
Value::SmallInt(attnum),
Value::Int(0),
Value::Int(-1),
Value::Bool(typlen > 0 && typlen <= 8),
Value::text(if typlen > 0 { "p" } else { "x" }),
Value::text(match typlen {
1 => "c",
2 => "s",
4 => "i",
_ => "d",
}),
Value::Bool(false),
Value::Bool(false), Value::text(""), Value::text(""), Value::Bool(false), Value::Bool(true), Value::Int(0),
Value::BigInt(0),
Value::Null, Value::text(""), Value::Bool(false), Value::Null, Value::Null, Value::Null, ]));
}
}
for (name, oid) in CATALOG_RELATIONS {
let Some(cols) = catalog_relation_columns(name, cat) else {
continue;
};
for (i, col) in cols.iter().enumerate() {
#[allow(clippy::cast_possible_wrap)]
let attnum = (i + 1) as i16;
let typlen: i16 = pg_type_len(col.ty);
rows.push(Row::new(alloc::vec![
Value::BigInt(*oid),
Value::text(col.name.clone()),
Value::BigInt(pg_type_oid(col.ty)),
Value::Int(-1),
Value::SmallInt(typlen),
Value::SmallInt(attnum),
Value::Int(0),
Value::Int(pg_atttypmod(col.ty)),
Value::Bool(typlen > 0 && typlen <= 8),
Value::text(if typlen > 0 { "p" } else { "x" }),
Value::text("i"),
Value::Bool(!col.nullable),
Value::Bool(false), Value::text(""), Value::text(""), Value::Bool(false), Value::Bool(true), Value::Int(0), Value::BigInt(pg_attr_collation(col.ty)), Value::Null, Value::text(""), Value::Bool(false), Value::Null, Value::Null, Value::Null, ]));
}
push_system_attributes(&mut rows, *oid);
}
(schema, rows)
}
const fn pg_type_len(ty: DataType) -> i16 {
match ty {
DataType::Bool => 1,
DataType::SmallInt => 2,
DataType::Int | DataType::Date => 4,
DataType::BigInt | DataType::Float | DataType::Timestamp | DataType::Timestamptz => 8,
_ => -1,
}
}
fn pg_atttypmod(ty: DataType) -> i32 {
match ty {
DataType::Varchar(n) | DataType::Char(n) => {
i32::try_from(n).map_or(-1, |n| n.saturating_add(4))
}
DataType::Numeric { precision, scale } => {
let p = i32::from(precision);
let s = i32::from(scale);
((p << 16) | s).saturating_add(4)
}
_ => -1,
}
}
pub(crate) fn pg_type_oid(ty: DataType) -> i64 {
match ty {
DataType::Bool => 16,
DataType::Bytes => 17,
DataType::Name => 19,
DataType::Xid => 28,
DataType::Xid8 => 5069,
DataType::SmallInt => 21,
DataType::Int => 23,
DataType::BigInt => 20,
DataType::Text => 25,
DataType::Varchar(_) => 1043,
DataType::Char(_) => 1042,
DataType::Float => 701,
DataType::Real => 700,
DataType::Numeric { .. } => 1700,
DataType::Date => 1082,
DataType::Time => 1083,
DataType::TimeTz => 1266,
DataType::Timestamp => 1114,
DataType::Timestamptz => 1184,
DataType::Interval => 1186,
DataType::Uuid => 2950,
DataType::Json => 114,
DataType::Jsonb => 3802,
DataType::TextArray => 1009,
DataType::IntArray => 1007,
DataType::BigIntArray => 1016,
DataType::SmallIntArray => 1005,
DataType::FloatArray => 1022,
DataType::BoolArray => 1000,
DataType::DateArray => 1182,
DataType::TimestampArray => 1115,
DataType::TimestamptzArray => 1185,
DataType::UuidArray => 2951,
DataType::BytesArray => 1001,
DataType::NumericArray => 1231,
DataType::JsonArray => 199,
DataType::IntArray2D => 1007,
DataType::BigIntArray2D => 1016,
DataType::TextArray2D => 1009,
_ => 0,
}
}
fn pg_type_oid_for_domain_base(d: &spg_storage::DomainDef) -> Option<i64> {
let oid = pg_type_oid(d.base_type);
(oid != 0).then_some(oid)
}
pub(crate) fn user_type_oids(
cat: &Catalog,
) -> (
alloc::vec::Vec<(alloc::string::String, i64)>,
alloc::vec::Vec<(alloc::string::String, i64)>,
alloc::vec::Vec<(alloc::string::String, i64)>,
) {
let enums = cat
.enum_types()
.keys()
.enumerate()
.map(|(i, n)| (n.clone(), 50_001 + i as i64))
.collect();
let composites = cat
.composite_types()
.keys()
.enumerate()
.map(|(i, n)| (n.clone(), 54_001 + i as i64))
.collect();
let domains = cat
.domain_types()
.keys()
.enumerate()
.map(|(i, n)| (n.clone(), 58_001 + i as i64))
.collect();
(enums, composites, domains)
}
pub(crate) const ARRAY_TYPE_OIDS: &[(i64, &str, i64)] = &[
(1000, "_bool", 16),
(1001, "_bytea", 17),
(1002, "_char", 18),
(1003, "_name", 19),
(1016, "_int8", 20),
(1005, "_int2", 21),
(1007, "_int4", 23),
(1008, "_regproc", 24),
(1009, "_text", 25),
(1028, "_oid", 26),
(199, "_json", 114),
(143, "_xml", 142),
(1021, "_float4", 700),
(1022, "_float8", 701),
(651, "_cidr", 650),
(1041, "_inet", 869),
(1040, "_macaddr", 829),
(1014, "_bpchar", 1042),
(1015, "_varchar", 1043),
(1182, "_date", 1082),
(1183, "_time", 1083),
(1115, "_timestamp", 1114),
(1185, "_timestamptz", 1184),
(1187, "_interval", 1186),
(1270, "_timetz", 1266),
(1231, "_numeric", 1700),
(791, "_money", 790),
(2951, "_uuid", 2950),
(3807, "_jsonb", 3802),
(3643, "_tsvector", 3614),
(3645, "_tsquery", 3615),
];
pub(crate) fn synth_pg_operator(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("oprname", DataType::Text, false),
ColumnSchema::new("oprnamespace", DataType::BigInt, false),
ColumnSchema::new("oprowner", DataType::BigInt, false),
ColumnSchema::new("oprkind", DataType::Text, false),
ColumnSchema::new("oprcanmerge", DataType::Bool, false),
ColumnSchema::new("oprcanhash", DataType::Bool, false),
ColumnSchema::new("oprleft", DataType::BigInt, false),
ColumnSchema::new("oprright", DataType::BigInt, false),
ColumnSchema::new("oprresult", DataType::BigInt, false),
ColumnSchema::new("oprcom", DataType::BigInt, false),
ColumnSchema::new("oprnegate", DataType::BigInt, false),
ColumnSchema::new("oprcode", DataType::BigInt, false),
ColumnSchema::new("oprrest", DataType::BigInt, false),
ColumnSchema::new("oprjoin", DataType::BigInt, false),
];
const CMP_TYPES: &[i64] = &[
16, 20, 21, 23, 25, 700, 701, 1042, 1043, 1082, 1114, 1184, 1186, 1700, 2950, 17, ];
const NUM_TYPES: &[i64] = &[20, 21, 23, 700, 701, 1700];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut oid: i64 = 70_000;
let mut push = |rows: &mut Vec<Row<'static>>,
oid: &mut i64,
name: &str,
kind: &str,
left: i64,
right: i64,
result: i64,
canmerge: bool,
canhash: bool| {
*oid += 1;
rows.push(Row::new(alloc::vec![
Value::BigInt(*oid),
Value::text::<String>(name.into()),
Value::BigInt(11), Value::BigInt(10), Value::text::<String>(kind.into()),
Value::Bool(canmerge),
Value::Bool(canhash),
Value::BigInt(left),
Value::BigInt(right),
Value::BigInt(result),
Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), ]));
};
for &t in CMP_TYPES {
push(&mut rows, &mut oid, "=", "b", t, t, 16, true, true);
push(&mut rows, &mut oid, "<>", "b", t, t, 16, false, false);
for op in ["<", "<=", ">", ">="] {
push(&mut rows, &mut oid, op, "b", t, t, 16, false, false);
}
}
for &t in NUM_TYPES {
for op in ["+", "-", "*", "/"] {
push(&mut rows, &mut oid, op, "b", t, t, t, false, false);
}
push(&mut rows, &mut oid, "-", "l", 0, t, t, false, false);
}
for &t in &[20i64, 21, 23] {
push(&mut rows, &mut oid, "%", "b", t, t, t, false, false);
}
const EXTRA_OPS: &[(&str, i64, i64, i64)] = &[
("!~", 1042, 25, 16), ("!~*", 1042, 25, 16), ("!~~", 1042, 25, 16), ("!~~*", 1042, 25, 16), ("%", 1700, 1700, 1700), ("&", 869, 869, 869), ("&&", 869, 869, 16), ("*", 700, 701, 701), ("*", 701, 700, 701), ("*", 701, 1186, 1186), ("*", 21, 23, 23), ("*", 21, 20, 20), ("*", 23, 21, 23), ("*", 23, 20, 20), ("*", 20, 21, 20), ("*", 20, 23, 20), ("*", 1186, 701, 1186), ("+", 1082, 23, 1082), ("+", 1082, 1186, 1114), ("+", 1082, 1083, 1114), ("+", 700, 701, 701), ("+", 701, 700, 701), ("+", 869, 20, 869), ("+", 21, 23, 23), ("+", 21, 20, 20), ("+", 23, 1082, 1082), ("+", 23, 21, 23), ("+", 23, 20, 20), ("+", 20, 869, 869), ("+", 20, 21, 20), ("+", 20, 23, 20), ("+", 1186, 1082, 1114), ("+", 1186, 1186, 1186), ("+", 1186, 1083, 1083), ("+", 1186, 1114, 1114), ("+", 1083, 1082, 1114), ("+", 1083, 1186, 1083), ("+", 1114, 1186, 1114), ("-", 1082, 1082, 23), ("-", 1082, 23, 1082), ("-", 1082, 1186, 1114), ("-", 700, 701, 701), ("-", 701, 700, 701), ("-", 869, 869, 20), ("-", 869, 20, 869), ("-", 21, 23, 23), ("-", 21, 20, 20), ("-", 23, 21, 23), ("-", 23, 20, 20), ("-", 20, 21, 20), ("-", 20, 23, 20), ("-", 1186, 1186, 1186), ("-", 3802, 25, 3802), ("-", 1083, 1186, 1083), ("-", 1083, 1083, 1186), ("-", 1114, 1186, 1114), ("-", 1114, 1114, 1186), ("->", 114, 23, 114), ("->", 3802, 23, 3802), ("->>", 114, 23, 25), ("->>", 3802, 23, 25), ("/", 700, 701, 701), ("/", 701, 700, 701), ("/", 21, 23, 23), ("/", 21, 20, 20), ("/", 23, 21, 23), ("/", 23, 20, 20), ("/", 20, 21, 20), ("/", 20, 23, 20), ("/", 1186, 701, 1186), ("<", 1082, 1114, 16), ("<", 700, 701, 16), ("<", 701, 700, 16), ("<", 869, 869, 16), ("<", 21, 23, 16), ("<", 21, 20, 16), ("<", 23, 21, 16), ("<", 23, 20, 16), ("<", 20, 21, 16), ("<", 20, 23, 16), ("<", 3802, 3802, 16), ("<", 1083, 1083, 16), ("<", 1114, 1082, 16), ("<<", 869, 869, 16), ("<<", 21, 23, 21), ("<<", 23, 23, 23), ("<<", 20, 23, 20), ("<<=", 869, 869, 16), ("<=", 1082, 1114, 16), ("<=", 700, 701, 16), ("<=", 701, 700, 16), ("<=", 869, 869, 16), ("<=", 21, 23, 16), ("<=", 21, 20, 16), ("<=", 23, 21, 16), ("<=", 23, 20, 16), ("<=", 20, 21, 16), ("<=", 20, 23, 16), ("<=", 3802, 3802, 16), ("<=", 1083, 1083, 16), ("<=", 1114, 1082, 16), ("<>", 1082, 1114, 16), ("<>", 700, 701, 16), ("<>", 701, 700, 16), ("<>", 869, 869, 16), ("<>", 21, 23, 16), ("<>", 21, 20, 16), ("<>", 23, 21, 16), ("<>", 23, 20, 16), ("<>", 20, 21, 16), ("<>", 20, 23, 16), ("<>", 3802, 3802, 16), ("<>", 1083, 1083, 16), ("<>", 1114, 1082, 16), ("=", 1082, 1114, 16), ("=", 700, 701, 16), ("=", 701, 700, 16), ("=", 869, 869, 16), ("=", 21, 23, 16), ("=", 21, 20, 16), ("=", 23, 21, 16), ("=", 23, 20, 16), ("=", 20, 21, 16), ("=", 20, 23, 16), ("=", 3802, 3802, 16), ("=", 1083, 1083, 16), ("=", 1114, 1082, 16), (">", 1082, 1114, 16), (">", 700, 701, 16), (">", 701, 700, 16), (">", 869, 869, 16), (">", 21, 23, 16), (">", 21, 20, 16), (">", 23, 21, 16), (">", 23, 20, 16), (">", 20, 21, 16), (">", 20, 23, 16), (">", 3802, 3802, 16), (">", 1083, 1083, 16), (">", 1114, 1082, 16), (">=", 1082, 1114, 16), (">=", 700, 701, 16), (">=", 701, 700, 16), (">=", 869, 869, 16), (">=", 21, 23, 16), (">=", 21, 20, 16), (">=", 23, 21, 16), (">=", 23, 20, 16), (">=", 20, 21, 16), (">=", 20, 23, 16), (">=", 3802, 3802, 16), (">=", 1083, 1083, 16), (">=", 1114, 1082, 16), (">>", 869, 869, 16), (">>", 21, 23, 21), (">>", 23, 23, 23), (">>", 20, 23, 20), (">>=", 869, 869, 16), ("^", 701, 701, 701), ("^", 1700, 1700, 1700), ("^@", 25, 25, 16), ("~", 1042, 25, 16), ("~*", 1042, 25, 16), ("~<=~", 25, 25, 16), ("~<~", 25, 25, 16), ("~>=~", 25, 25, 16), ("~>~", 25, 25, 16), ("~~", 1042, 25, 16), ("~~*", 1042, 25, 16), ];
for (name, l, r, res) in EXTRA_OPS {
push(&mut rows, &mut oid, name, "b", *l, *r, *res, false, false);
}
push(&mut rows, &mut oid, "||", "b", 25, 25, 25, false, false);
push(&mut rows, &mut oid, "~~", "b", 25, 25, 16, false, false);
push(&mut rows, &mut oid, "!~~", "b", 25, 25, 16, false, false);
push(&mut rows, &mut oid, "~~*", "b", 25, 25, 16, false, false);
push(&mut rows, &mut oid, "!~~*", "b", 25, 25, 16, false, false);
push(&mut rows, &mut oid, "~", "b", 25, 25, 16, false, false);
push(&mut rows, &mut oid, "!~", "b", 25, 25, 16, false, false);
push(&mut rows, &mut oid, "~*", "b", 25, 25, 16, false, false);
push(&mut rows, &mut oid, "!~*", "b", 25, 25, 16, false, false);
for &j in &[114i64, 3802] {
push(&mut rows, &mut oid, "->", "b", j, 25, j, false, false);
push(&mut rows, &mut oid, "->>", "b", j, 25, 25, false, false);
push(&mut rows, &mut oid, "#>", "b", j, 1009, j, false, false);
push(&mut rows, &mut oid, "#>>", "b", j, 1009, 25, false, false);
}
push(&mut rows, &mut oid, "@>", "b", 3802, 3802, 16, false, false);
push(&mut rows, &mut oid, "<@", "b", 3802, 3802, 16, false, false);
push(&mut rows, &mut oid, "?", "b", 3802, 25, 16, false, false);
push(&mut rows, &mut oid, "@>", "b", 1007, 1007, 16, false, false);
push(&mut rows, &mut oid, "<@", "b", 1007, 1007, 16, false, false);
push(&mut rows, &mut oid, "&&", "b", 1007, 1007, 16, false, false);
for &t in &[20i64, 21, 23] {
for op in ["&", "|", "#"] {
push(&mut rows, &mut oid, op, "b", t, t, t, false, false);
}
}
(schema, rows)
}
pub(crate) fn synth_pg_type(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("typname", DataType::Text, false),
ColumnSchema::new("typnamespace", DataType::BigInt, false),
ColumnSchema::new("typowner", DataType::BigInt, false),
ColumnSchema::new("typlen", DataType::SmallInt, false),
ColumnSchema::new("typbyval", DataType::Bool, false),
ColumnSchema::new("typtype", DataType::Text, false),
ColumnSchema::new("typcategory", DataType::Text, false),
ColumnSchema::new("typispreferred", DataType::Bool, false),
ColumnSchema::new("typisdefined", DataType::Bool, false),
ColumnSchema::new("typdelim", DataType::Text, false),
ColumnSchema::new("typrelid", DataType::BigInt, false),
ColumnSchema::new("typsubscript", DataType::Text, false),
ColumnSchema::new("typelem", DataType::BigInt, false),
ColumnSchema::new("typarray", DataType::BigInt, false),
ColumnSchema::new("typinput", DataType::BigInt, false),
ColumnSchema::new("typoutput", DataType::BigInt, false),
ColumnSchema::new("typreceive", DataType::BigInt, false),
ColumnSchema::new("typsend", DataType::BigInt, false),
ColumnSchema::new("typmodin", DataType::BigInt, false),
ColumnSchema::new("typmodout", DataType::BigInt, false),
ColumnSchema::new("typanalyze", DataType::BigInt, false),
ColumnSchema::new("typalign", DataType::Text, false),
ColumnSchema::new("typstorage", DataType::Text, false),
ColumnSchema::new("typnotnull", DataType::Bool, false),
ColumnSchema::new("typbasetype", DataType::BigInt, false),
ColumnSchema::new("typtypmod", DataType::Int, false),
ColumnSchema::new("typndims", DataType::Int, false),
ColumnSchema::new("typcollation", DataType::BigInt, false),
ColumnSchema::new("typdefaultbin", DataType::Text, true),
ColumnSchema::new("typdefault", DataType::Text, true),
ColumnSchema::new("typacl", DataType::Text, true),
];
let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
(16, "bool", 1, "b", "B", 0, 1000),
(17, "bytea", -1, "b", "U", 0, 1001),
(18, "char", 1, "b", "Z", 0, 1002),
(19, "name", 64, "b", "S", 18, 1003),
(20, "int8", 8, "b", "N", 0, 1016),
(21, "int2", 2, "b", "N", 0, 1005),
(23, "int4", 4, "b", "N", 0, 1007),
(24, "regproc", 4, "b", "N", 0, 1008),
(25, "text", -1, "b", "S", 0, 1009),
(26, "oid", 4, "b", "N", 0, 1028),
(27, "tid", 6, "b", "U", 0, 0),
(28, "xid", 4, "b", "U", 0, 0),
(29, "cid", 4, "b", "U", 0, 0),
(5069, "xid8", 8, "b", "U", 0, 0),
(114, "json", -1, "b", "U", 0, 199),
(142, "xml", -1, "b", "U", 0, 143),
(700, "float4", 4, "b", "N", 0, 1021),
(701, "float8", 8, "b", "N", 0, 1022),
(650, "cidr", -1, "b", "I", 0, 651),
(869, "inet", -1, "b", "I", 0, 1041),
(2206, "regtype", 4, "b", "N", 0, 0),
(2249, "record", -1, "p", "P", 0, 0),
(2277, "anyarray", -1, "p", "P", 0, 0),
(2283, "anyelement", 4, "p", "P", 0, 0),
(4451, "int4multirange", -1, "m", "R", 0, 0),
(4532, "nummultirange", -1, "m", "R", 0, 0),
(4533, "tsmultirange", -1, "m", "R", 0, 0),
(4534, "tstzmultirange", -1, "m", "R", 0, 0),
(4535, "datemultirange", -1, "m", "R", 0, 0),
(4536, "int8multirange", -1, "m", "R", 0, 0),
(1560, "bit", -1, "b", "V", 0, 0),
(1562, "varbit", -1, "b", "V", 0, 0),
(829, "macaddr", 6, "b", "U", 0, 1040),
(1042, "bpchar", -1, "b", "S", 0, 1014),
(1043, "varchar", -1, "b", "S", 0, 1015),
(1082, "date", 4, "b", "D", 0, 1182),
(1083, "time", 8, "b", "D", 0, 1183),
(1114, "timestamp", 8, "b", "D", 0, 1115),
(1184, "timestamptz", 8, "b", "D", 0, 1185),
(1186, "interval", 16, "b", "T", 0, 1187),
(1266, "timetz", 12, "b", "D", 0, 1270),
(1700, "numeric", -1, "b", "N", 0, 1231),
(790, "money", 8, "b", "N", 0, 791),
(2950, "uuid", 16, "b", "U", 0, 2951),
(3802, "jsonb", -1, "b", "U", 0, 3807),
(3614, "tsvector", -1, "b", "U", 0, 3643),
(3615, "tsquery", -1, "b", "U", 0, 3645),
(3908, "tsrange", -1, "r", "R", 0, 0),
(3910, "tstzrange", -1, "r", "R", 0, 0),
(3904, "int4range", -1, "r", "R", 0, 0),
(3926, "int8range", -1, "r", "R", 0, 0),
(3906, "numrange", -1, "r", "R", 0, 0),
(3912, "daterange", -1, "r", "R", 0, 0),
(600, "point", 16, "b", "G", 701, 0),
(774, "macaddr8", 8, "b", "U", 0, 0),
(1033, "aclitem", 16, "b", "U", 0, 1034),
(1034, "_aclitem", -1, "b", "A", 1033, 0),
(2205, "regclass", 4, "b", "N", 0, 0),
(2278, "void", 4, "p", "P", 0, 0),
(2279, "trigger", 4, "p", "P", 0, 0),
(3220, "pg_lsn", 8, "b", "U", 0, 0),
(3831, "anyrange", -1, "p", "P", 0, 0),
(4537, "anymultirange", -1, "p", "P", 0, 0),
(5078, "anycompatiblearray", -1, "p", "P", 0, 0),
];
let arrays: &[(i64, &str, i64)] = ARRAY_TYPE_OIDS;
let mut rows: Vec<Row<'static>> = Vec::with_capacity(scalars.len() + arrays.len());
let preferred_oids: &[i64] = &[16, 25, 23, 1184, 1700];
let build_row = |oid: i64,
name: &str,
len: i16,
ty: &str,
cat: &str,
elem: i64,
arr: i64,
subscript: &str|
-> Row<'static> {
let odd_width = len > 0 && !matches!(len, 1 | 2 | 4 | 8);
let typbyval = len > 0 && len <= 8 && !odd_width;
let typalign = match len {
1 => "c",
2 => "s",
4 => "i",
_ if odd_width => "s",
_ => "d",
};
let typstorage = if len > 0 { "p" } else { "x" };
let typispreferred = preferred_oids.contains(&oid);
Row::new(alloc::vec![
Value::BigInt(oid),
Value::text::<String>(name.into()),
Value::BigInt(2200), Value::BigInt(10), Value::SmallInt(len),
Value::Bool(typbyval),
Value::text::<String>(ty.into()),
Value::text::<String>(cat.into()),
Value::Bool(typispreferred),
Value::Bool(true), Value::text::<String>(",".into()), Value::BigInt(0), Value::text::<String>(subscript.into()), Value::BigInt(elem),
Value::BigInt(arr),
Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::text::<String>(typalign.into()),
Value::text::<String>(typstorage.into()),
Value::Bool(false), Value::BigInt(0), Value::Int(-1), Value::Int(0), Value::BigInt(0), Value::Null, Value::Null, Value::Null, ])
};
for &(oid, name, len, ty, cat, elem, arr) in scalars {
rows.push(build_row(oid, name, len, ty, cat, elem, arr, "-"));
}
for &(oid, name, elem) in arrays {
rows.push(build_row(
oid,
name,
-1,
"b",
"A",
elem,
0,
"array_subscript_handler",
));
}
for (full, base) in INFORMATION_SCHEMA_DOMAINS {
let bare = full.rsplit('.').next().unwrap_or(full);
let (base_oid, len): (i64, i16) = match base {
DataType::Name => (19, 64),
DataType::Int => (23, 4),
_ => (1043, -1),
};
let mut row = build_row(
INFORMATION_SCHEMA_DOMAIN_OID_BASE + base_oid,
bare,
len,
"d",
"S",
0,
0,
"-",
);
row.values[2] = Value::BigInt(13000);
if let Some(slot) = schema.iter().position(|c| c.name == "typbasetype") {
row.values[slot] = Value::BigInt(base_oid);
}
rows.push(row);
}
let (enum_oids, composite_oids, domain_oids) = user_type_oids(cat);
for (name, oid) in enum_oids {
rows.push(build_row(oid, &name, 4, "e", "E", 0, 0, "-"));
}
for (name, oid) in composite_oids {
rows.push(build_row(oid, &name, -1, "c", "C", 0, 0, "-"));
}
for (name, oid) in domain_oids {
let base = cat
.domain_types()
.get(&name)
.and_then(|d| pg_type_oid_for_domain_base(d))
.unwrap_or(0);
let mut r = build_row(oid, &name, -1, "d", "N", 0, 0, "-");
if let Some(i) = schema.iter().position(|c| c.name == "typbasetype")
&& let Some(slot) = r.values.get_mut(i)
{
*slot = Value::BigInt(base);
}
rows.push(r);
}
(schema, rows)
}
pub(crate) const INFORMATION_SCHEMA_DOMAIN_OID_BASE: i64 = 13_500;
pub(crate) fn synth_pg_trigger(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("tgrelid", DataType::BigInt, false),
ColumnSchema::new("tgparentid", DataType::BigInt, false),
ColumnSchema::new("tgname", DataType::Text, false),
ColumnSchema::new("tgfoid", DataType::BigInt, false),
ColumnSchema::new("tgtype", DataType::SmallInt, false),
ColumnSchema::new("tgenabled", DataType::Text, false),
ColumnSchema::new("tgisinternal", DataType::Bool, false),
ColumnSchema::new("tgconstrrelid", DataType::BigInt, false),
ColumnSchema::new("tgconstrindid", DataType::BigInt, false),
ColumnSchema::new("tgconstraint", DataType::BigInt, false),
ColumnSchema::new("tgdeferrable", DataType::Bool, false),
ColumnSchema::new("tginitdeferred", DataType::Bool, false),
ColumnSchema::new("tgnargs", DataType::SmallInt, false),
ColumnSchema::new("tgattr", DataType::Text, true),
ColumnSchema::new("tgargs", DataType::Text, true),
ColumnSchema::new("tgqual", DataType::Text, true),
ColumnSchema::new("tgoldtable", DataType::Text, true),
ColumnSchema::new("tgnewtable", DataType::Text, true),
];
let mut oid = OID_TRIGGER_BASE;
let rows: Vec<Row<'static>> = cat
.triggers()
.iter()
.map(|t| {
oid += 1;
let mut tgtype: i16 = 0;
if !t.timing.eq_ignore_ascii_case("INSTEAD OF") {
tgtype |= 1;
}
if t.timing.eq_ignore_ascii_case("BEFORE") {
tgtype |= 2;
}
if t.timing.eq_ignore_ascii_case("INSTEAD OF") {
tgtype |= 64;
}
for ev in &t.events {
tgtype |= match ev.to_ascii_uppercase().as_str() {
"INSERT" => 4,
"DELETE" => 8,
"UPDATE" => 16,
"TRUNCATE" => 32,
_ => 0,
};
}
Row::new(alloc::vec![
Value::BigInt(oid),
Value::BigInt(relation_oid(cat, &t.table).unwrap_or(0)),
Value::BigInt(0), Value::text(t.name.clone()),
Value::BigInt(function_oid(cat, &t.function).unwrap_or(0)),
Value::SmallInt(tgtype),
Value::text(if t.enabled { "O" } else { "D" }),
Value::Bool(false), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::Bool(false), Value::Bool(false), Value::SmallInt(0), Value::text(""), Value::text("\\x"), Value::Null, Value::Null, Value::Null, ])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_proc(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("proname", DataType::Text, false),
ColumnSchema::new("pronamespace", DataType::BigInt, false),
ColumnSchema::new("proowner", DataType::BigInt, false),
ColumnSchema::new("prolang", DataType::BigInt, false),
ColumnSchema::new("procost", DataType::Float, false),
ColumnSchema::new("prorows", DataType::Float, false),
ColumnSchema::new("provariadic", DataType::BigInt, false),
ColumnSchema::new("prosupport", DataType::BigInt, false),
ColumnSchema::new("prokind", DataType::Text, false),
ColumnSchema::new("prosecdef", DataType::Bool, false),
ColumnSchema::new("proleakproof", DataType::Bool, false),
ColumnSchema::new("proisstrict", DataType::Bool, false),
ColumnSchema::new("proretset", DataType::Bool, false),
ColumnSchema::new("provolatile", DataType::Text, false),
ColumnSchema::new("proparallel", DataType::Text, false),
ColumnSchema::new("pronargs", DataType::SmallInt, false),
ColumnSchema::new("pronargdefaults", DataType::SmallInt, false),
ColumnSchema::new("prorettype", DataType::BigInt, false),
ColumnSchema::new("proargtypes", DataType::Text, false),
ColumnSchema::new("proallargtypes", DataType::Text, true),
ColumnSchema::new("proargmodes", DataType::Text, true),
ColumnSchema::new("proargnames", DataType::TextArray, true),
ColumnSchema::new("proargdefaults", DataType::Text, true),
ColumnSchema::new("protrftypes", DataType::Text, true),
ColumnSchema::new("prosrc", DataType::Text, false),
ColumnSchema::new("probin", DataType::Text, true),
ColumnSchema::new("prosqlbody", DataType::Text, true),
ColumnSchema::new("proconfig", DataType::TextArray, true),
ColumnSchema::new("proacl", DataType::Text, true),
];
let funcs: &[(i64, &str, &str, i32, i64)] = PG_PROC_FUNCS;
let mut rows: Vec<Row<'static>> = Vec::with_capacity(funcs.len());
let volatile_names: &[&str] = &[
"now",
"random",
"gen_random_uuid",
"current_database",
"current_user",
"session_user",
"current_schema",
];
for &(oid, name, kind, nargs, rettype) in funcs {
let provolatile: &str = if volatile_names.contains(&name) {
"v"
} else {
"i"
};
let prorows: f64 = match kind {
"a" | "w" => 1000.0,
_ => 0.0,
};
let arg_count = if nargs < 0 { 0 } else { nargs };
let mut argtypes = alloc::string::String::new();
for i in 0..arg_count {
if i > 0 {
argtypes.push(' ');
}
argtypes.push('0');
}
rows.push(Row::new(alloc::vec![
Value::BigInt(oid),
Value::text::<String>(name.into()),
Value::BigInt(if SPG_ONLY_PROCS.contains(&name) {
13500
} else {
11
}),
Value::BigInt(10), Value::BigInt(12), Value::Float(1.0), Value::Float(prorows),
Value::BigInt(0), Value::BigInt(0), Value::text::<String>(kind.into()),
Value::Bool(false), Value::Bool(false), Value::Bool(true), Value::Bool(kind == "w"), Value::text::<String>(provolatile.into()),
Value::text::<String>("s".into()), Value::SmallInt(i16::try_from(nargs.max(0)).unwrap_or(i16::MAX)),
Value::SmallInt(0), Value::BigInt(rettype),
Value::text(argtypes),
Value::Null, Value::Null, Value::Null, Value::Null, Value::Null, Value::text::<String>(name.into()), Value::Null, Value::Null, Value::Null, Value::Null, ]));
}
let mut user_oid: i64 = OID_FUNC_BASE;
for def in cat.functions().values() {
user_oid += 1;
let nargs = crate::acl::function_arg_count(&def.args_repr);
rows.push(Row::new(alloc::vec![
Value::BigInt(user_oid),
Value::text(def.name.clone()),
Value::BigInt(2200), Value::BigInt(10), Value::BigInt(if def.language.eq_ignore_ascii_case("plpgsql") {
13
} else {
14
}),
Value::Float(def.cost.unwrap_or(100.0)),
Value::Float(def.rows.unwrap_or(0.0)),
Value::BigInt(0),
Value::BigInt(0), Value::text("f"), Value::Bool(def.security_definer),
Value::Bool(def.leakproof),
Value::Bool(def.strict),
Value::Bool(false),
Value::text(alloc::string::String::from(
core::str::from_utf8(&[def.volatility]).unwrap_or("v"),
)), Value::text(alloc::string::String::from(
core::str::from_utf8(&[def.parallel]).unwrap_or("u"),
)), Value::SmallInt(i16::try_from(nargs).unwrap_or(i16::MAX)),
Value::SmallInt(0),
Value::BigInt(0),
Value::text(alloc::string::String::new()),
Value::Null, Value::Null, declared_arg_names(&def.args_repr),
Value::Null, Value::Null, Value::text(def.body.clone()), Value::Null, Value::Null, Value::Null, crate::acl::render_acl_list(&def.acl).map_or(Value::Null, Value::text),
]));
}
(schema, rows)
}
pub(crate) const SPG_ONLY_PROCS: &[&str] = &[
"benchmark",
"connection_id",
"current_catalog",
"current_role",
"database",
"field",
"found_rows",
"from_unixtime",
"gen_uuid_v7",
"ifnull",
"json_array",
"last_insert_id",
"log2",
"nullif",
"pg_backend_start_time",
"pg_current_edition",
"pg_current_query",
"pg_get_wait_event_name",
"pg_get_wait_event_type",
"pg_is_in_backup",
"pg_last_xid",
"pg_object_size",
"pg_prewarm",
"pg_relation_size_pretty",
"pg_rotate_logfile_v2",
"pg_start_backup",
"pg_stat_get_archiver_archived_count",
"pg_stat_get_archiver_failed_count",
"pg_stat_get_archiver_last_archived_wal",
"pg_stat_get_archiver_last_failed_wal",
"pg_stat_get_bgwriter_buf_written_checkpoints",
"pg_stat_get_bgwriter_requested_checkpoints",
"pg_stat_get_bgwriter_timed_checkpoints",
"pg_stat_get_buf_fsync_backend",
"pg_stat_get_buf_written_backend",
"pg_stat_get_checkpoint_sync_time",
"pg_stat_get_checkpoint_write_time",
"pg_stat_get_idx_scan",
"pg_stat_get_idx_tup_fetch",
"pg_stat_get_idx_tup_read",
"pg_stat_get_recovery_prefetch_reset_time",
"pg_stat_get_seq_scan",
"pg_stat_get_seq_scan_pos",
"pg_stat_get_seq_tup_read",
"pg_stat_get_slru_blks_exists",
"pg_stat_get_slru_blks_hit",
"pg_stat_get_slru_blks_read",
"pg_stat_get_slru_blks_written",
"pg_stat_get_slru_blks_zeroed",
"pg_stat_get_slru_flushes",
"pg_stat_get_slru_stat_reset_time",
"pg_stat_get_slru_truncates",
"pg_stat_get_stat_snapshot_timestamp",
"pg_stat_get_tid_scan_pos",
"pg_stat_get_wal_buffers_full",
"pg_stat_get_wal_bytes",
"pg_stat_get_wal_fpi",
"pg_stat_get_wal_records",
"pg_stat_get_wal_sync",
"pg_stat_get_wal_sync_time",
"pg_stat_get_wal_write",
"pg_stat_get_wal_write_time",
"pg_stop_backup",
"pg_terminate_backend_with_timeout",
"pg_wait_for_backend_termination",
"quote",
"rand",
"row",
"row_count",
"similarity",
"sleep",
"spg_build_time",
"spg_edition",
"spg_uptime_seconds",
"spg_version",
"unix_timestamp",
"user",
"uuid_generate_v4",
"uuid_generate_v7",
"uuid_nil",
"uuid_ns_dns",
"uuid_ns_oid",
"uuid_ns_url",
"uuid_ns_x500",
"uuid_short",
"xmlforest",
];
pub(crate) const PG_PROC_FUNCS: &[(i64, &str, &str, i32, i64)] = &[
(1317, "length", "f", 1, 23),
(1318, "length", "f", 1, 23),
(1530, "length", "f", 1, 701),
(1531, "length", "f", 1, 701),
(1681, "length", "f", 1, 23),
(1713, "length", "f", 2, 23),
(2010, "length", "f", 1, 23),
(3711, "length", "f", 1, 23),
(870, "lower", "f", 1, 25),
(871, "upper", "f", 1, 25),
(3848, "lower", "f", 1, 2283),
(3849, "upper", "f", 1, 2283),
(936, "substring", "f", 3, 25),
(937, "substring", "f", 2, 25),
(885, "btrim", "f", 1, 25),
(884, "btrim", "f", 2, 25),
(881, "ltrim", "f", 1, 25),
(875, "ltrim", "f", 2, 25),
(882, "rtrim", "f", 1, 25),
(876, "rtrim", "f", 2, 25),
(1396, "abs", "f", 1, 20),
(1397, "abs", "f", 1, 23),
(1705, "abs", "f", 1, 1700),
(1342, "round", "f", 1, 701),
(1708, "round", "f", 1, 1700),
(1707, "round", "f", 2, 1700),
(2308, "ceil", "f", 1, 701),
(1711, "ceil", "f", 1, 1700),
(2320, "ceiling", "f", 1, 701),
(2167, "ceiling", "f", 1, 1700),
(2309, "floor", "f", 1, 701),
(1712, "floor", "f", 1, 1700),
(1344, "sqrt", "f", 1, 701),
(1730, "sqrt", "f", 1, 1700),
(1341, "ln", "f", 1, 701),
(1734, "ln", "f", 1, 1700),
(1347, "exp", "f", 1, 701),
(1732, "exp", "f", 1, 1700),
(1368, "power", "f", 2, 701),
(2169, "power", "f", 2, 1700),
(1598, "random", "f", 0, 701),
(1299, "now", "f", 0, 1184),
(2020, "date_trunc", "f", 2, 1114),
(2021, "date_part", "f", 2, 701),
(2059, "age", "f", 1, 1186),
(2058, "age", "f", 2, 1186),
(2049, "to_char", "f", 2, 25),
(1772, "to_char", "f", 2, 25),
(861, "current_database", "f", 0, 19),
(745, "current_user", "f", 0, 19),
(746, "session_user", "f", 0, 19),
(1402, "current_schema", "f", 0, 19),
(3058, "concat", "f", 1, 25),
(3059, "concat_ws", "f", 2, 25),
(3539, "format", "f", 2, 25),
(3540, "format", "f", 1, 25),
(1619, "pg_typeof", "f", 1, 2206),
(3200, "json_build_object", "f", 1, 114),
(3273, "jsonb_build_object", "f", 1, 3802),
(3198, "json_build_array", "f", 1, 114),
(3271, "jsonb_build_array", "f", 1, 3802),
(3432, "gen_random_uuid", "f", 0, 2950),
(2147, "count", "a", 1, 20),
(2803, "count", "a", 0, 20),
(2116, "max", "a", 1, 23),
(2129, "max", "a", 1, 25),
(2130, "max", "a", 1, 1700),
(2132, "min", "a", 1, 23),
(2145, "min", "a", 1, 25),
(2146, "min", "a", 1, 1700),
(2108, "sum", "a", 1, 20),
(2114, "sum", "a", 1, 1700),
(2100, "avg", "a", 1, 1700),
(3538, "string_agg", "a", 2, 25),
(2335, "array_agg", "a", 1, 2277),
(2517, "bool_and", "a", 1, 16),
(2518, "bool_or", "a", 1, 16),
(2519, "every", "a", 1, 16),
(3100, "row_number", "w", 0, 20),
(3101, "rank", "w", 0, 20),
(3102, "dense_rank", "w", 0, 20),
(3103, "percent_rank", "w", 0, 701),
(3104, "cume_dist", "w", 0, 701),
(3106, "lag", "w", 1, 2283),
(3107, "lag", "w", 2, 2283),
(3109, "lead", "w", 1, 2283),
(3110, "lead", "w", 2, 2283),
(3112, "first_value", "w", 1, 2283),
(3113, "last_value", "w", 1, 2283),
(3114, "nth_value", "w", 2, 2283),
(1601, "acos", "f", 1, 701),
(338, "amvalidate", "f", 1, 16),
(1620, "ascii", "f", 1, 23),
(1600, "asin", "f", 1, 701),
(1602, "atan", "f", 1, 701),
(1603, "atan2", "f", 2, 701),
(900001, "benchmark", "f", 0, 23),
(1811, "bit_length", "f", 1, 23),
(3952, "brin_summarize_new_values", "f", 1, 20),
(1372, "char_length", "f", 1, 23),
(1367, "character_length", "f", 1, 23),
(900002, "connection_id", "f", 0, 20),
(1605, "cos", "f", 1, 701),
(900003, "current_catalog", "f", 0, 25),
(817, "current_query", "f", 0, 25),
(900004, "current_role", "f", 0, 25),
(1403, "current_schemas", "f", 1, 1009),
(900005, "database", "f", 0, 25),
(6221, "date_add", "f", 2, 1184),
(6177, "date_bin", "f", 3, 1114),
(6223, "date_subtract", "f", 2, 1184),
(4292, "datemultirange", "f", 0, 4535),
(3941, "daterange", "f", 2, 3912),
(1608, "degrees", "f", 1, 701),
(900006, "field", "f", 2, 23),
(900007, "found_rows", "f", 0, 20),
(900008, "from_unixtime", "f", 1, 1114),
(900009, "gen_uuid_v7", "f", 0, 2950),
(3759, "get_current_ts_config", "f", 0, 25),
(1039, "getdatabaseencoding", "f", 0, 25),
(3789, "gin_clean_pending_list", "f", 1, 20),
(3724, "gin_cmp_tslexeme", "f", 2, 23),
(3480, "gin_compare_jsonb", "f", 2, 23),
(3029, "has_any_column_privilege", "f", 2, 16),
(3021, "has_column_privilege", "f", 3, 16),
(2255, "has_database_privilege", "f", 2, 16),
(2261, "has_function_privilege", "f", 2, 16),
(2272, "has_schema_privilege", "f", 2, 16),
(2185, "has_sequence_privilege", "f", 2, 16),
(6099, "icu_unicode_version", "f", 0, 25),
(900010, "ifnull", "f", 2, 23),
(4280, "int4multirange", "f", 0, 4451),
(3840, "int4range", "f", 2, 3904),
(4295, "int8multirange", "f", 0, 4536),
(3945, "int8range", "f", 2, 3926),
(900011, "json_array", "f", 0, 114),
(900012, "last_insert_id", "f", 0, 20),
(1741, "log", "f", 1, 701),
(900013, "log2", "f", 1, 701),
(3846, "make_date", "f", 3, 1082),
(3464, "make_interval", "f", 7, 1186),
(3461, "make_timestamp", "f", 6, 1114),
(3462, "make_timestamptz", "f", 6, 1184),
(1728, "mod", "f", 2, 23),
(900014, "nullif", "f", 2, 23),
(440, "num_nonnulls", "f", 1, 23),
(438, "num_nulls", "f", 1, 23),
(4283, "nummultirange", "f", 0, 4532),
(3844, "numrange", "f", 2, 3906),
(720, "octet_length", "f", 1, 23),
(2884, "pg_advisory_unlock", "f", 1, 16),
(2885, "pg_advisory_unlock_shared", "f", 1, 16),
(2026, "pg_backend_pid", "f", 0, 23),
(900015, "pg_backend_start_time", "f", 0, 1114),
(2172, "pg_backup_start", "f", 2, 25),
(2739, "pg_backup_stop", "f", 1, 25),
(810, "pg_client_encoding", "f", 0, 25),
(3448, "pg_collation_actual_version", "f", 1, 25),
(1269, "pg_column_size", "f", 1, 23),
(2034, "pg_conf_load_time", "f", 0, 1114),
(3098, "pg_create_restore_point", "f", 1, 25),
(900016, "pg_current_edition", "f", 0, 25),
(3800, "pg_current_logfile", "f", 0, 25),
(900017, "pg_current_query", "f", 0, 25),
(5061, "pg_current_snapshot", "f", 0, 25),
(3330, "pg_current_wal_flush_lsn", "f", 0, 25),
(2852, "pg_current_wal_insert_lsn", "f", 0, 25),
(2849, "pg_current_wal_lsn", "f", 0, 25),
(5059, "pg_current_xact_id", "f", 0, 20),
(6249, "pg_database_collation_actual_version", "f", 1, 25),
(2324, "pg_database_size", "f", 1, 20),
(900018, "pg_get_wait_event_name", "f", 0, 25),
(900019, "pg_get_wait_event_type", "f", 0, 25),
(1137, "pg_get_wal_replay_pause_state", "f", 0, 25),
(2710, "pg_has_role", "f", 2, 16),
(3445, "pg_import_system_collations", "f", 1, 23),
(638, "pg_index_column_has_property", "f", 3, 16),
(637, "pg_index_has_property", "f", 2, 16),
(636, "pg_indexam_has_property", "f", 2, 16),
(900020, "pg_is_in_backup", "f", 0, 16),
(3810, "pg_is_in_recovery", "f", 0, 16),
(3073, "pg_is_wal_replay_paused", "f", 0, 16),
(3378, "pg_isolation_test_session_is_blocked", "f", 2, 16),
(315, "pg_jit_available", "f", 0, 16),
(3820, "pg_last_wal_receive_lsn", "f", 0, 25),
(3821, "pg_last_wal_replay_lsn", "f", 0, 25),
(900021, "pg_last_xid", "f", 0, 20),
(3577, "pg_logical_emit_message", "f", 4, 25),
(3296, "pg_notification_queue_usage", "f", 0, 701),
(900022, "pg_object_size", "f", 0, 20),
(2560, "pg_postmaster_start_time", "f", 0, 1114),
(900023, "pg_prewarm", "f", 0, 20),
(3436, "pg_promote", "f", 2, 16),
(3034, "pg_relation_filepath", "f", 1, 25),
(6121, "pg_relation_is_publishable", "f", 1, 16),
(900024, "pg_relation_size_pretty", "f", 0, 20),
(2621, "pg_reload_conf", "f", 0, 16),
(2622, "pg_rotate_logfile", "f", 0, 16),
(900025, "pg_rotate_logfile_v2", "f", 0, 16),
(900026, "pg_start_backup", "f", 0, 25),
(3056, "pg_stat_get_analyze_count", "f", 1, 20),
(900027, "pg_stat_get_archiver_archived_count", "f", 0, 20),
(900028, "pg_stat_get_archiver_failed_count", "f", 0, 20),
(900029, "pg_stat_get_archiver_last_archived_wal", "f", 0, 25),
(900030, "pg_stat_get_archiver_last_failed_wal", "f", 0, 25),
(3057, "pg_stat_get_autoanalyze_count", "f", 1, 20),
(3055, "pg_stat_get_autovacuum_count", "f", 1, 20),
(1391, "pg_stat_get_backend_start", "f", 1, 1114),
(
900031,
"pg_stat_get_bgwriter_buf_written_checkpoints",
"f",
0,
20,
),
(2772, "pg_stat_get_bgwriter_buf_written_clean", "f", 0, 20),
(2773, "pg_stat_get_bgwriter_maxwritten_clean", "f", 0, 20),
(
900032,
"pg_stat_get_bgwriter_requested_checkpoints",
"f",
0,
20,
),
(900033, "pg_stat_get_bgwriter_timed_checkpoints", "f", 0, 20),
(1934, "pg_stat_get_blocks_fetched", "f", 1, 20),
(1935, "pg_stat_get_blocks_hit", "f", 1, 20),
(2859, "pg_stat_get_buf_alloc", "f", 0, 20),
(900034, "pg_stat_get_buf_fsync_backend", "f", 0, 20),
(900035, "pg_stat_get_buf_written_backend", "f", 0, 20),
(900036, "pg_stat_get_checkpoint_sync_time", "f", 0, 20),
(900037, "pg_stat_get_checkpoint_write_time", "f", 0, 20),
(2771, "pg_stat_get_checkpointer_buffers_written", "f", 0, 20),
(2770, "pg_stat_get_checkpointer_num_requested", "f", 0, 20),
(2769, "pg_stat_get_checkpointer_num_timed", "f", 0, 20),
(
6329,
"pg_stat_get_checkpointer_restartpoints_performed",
"f",
0,
20,
),
(
6328,
"pg_stat_get_checkpointer_restartpoints_requested",
"f",
0,
20,
),
(
6327,
"pg_stat_get_checkpointer_restartpoints_timed",
"f",
0,
20,
),
(6314, "pg_stat_get_checkpointer_stat_reset_time", "f", 0, 20),
(3161, "pg_stat_get_checkpointer_sync_time", "f", 0, 20),
(3160, "pg_stat_get_checkpointer_write_time", "f", 0, 20),
(6186, "pg_stat_get_db_active_time", "f", 1, 20),
(2844, "pg_stat_get_db_blk_read_time", "f", 1, 20),
(2845, "pg_stat_get_db_blk_write_time", "f", 1, 20),
(1944, "pg_stat_get_db_blocks_fetched", "f", 1, 20),
(1945, "pg_stat_get_db_blocks_hit", "f", 1, 20),
(3426, "pg_stat_get_db_checksum_failures", "f", 1, 20),
(3070, "pg_stat_get_db_conflict_all", "f", 1, 20),
(3068, "pg_stat_get_db_conflict_bufferpin", "f", 1, 20),
(3066, "pg_stat_get_db_conflict_lock", "f", 1, 20),
(6309, "pg_stat_get_db_conflict_logicalslot", "f", 1, 20),
(3067, "pg_stat_get_db_conflict_snapshot", "f", 1, 20),
(3069, "pg_stat_get_db_conflict_startup_deadlock", "f", 1, 20),
(3065, "pg_stat_get_db_conflict_tablespace", "f", 1, 20),
(3152, "pg_stat_get_db_deadlocks", "f", 1, 20),
(6187, "pg_stat_get_db_idle_in_transaction_time", "f", 1, 20),
(1941, "pg_stat_get_db_numbackends", "f", 1, 20),
(6185, "pg_stat_get_db_session_time", "f", 1, 20),
(6188, "pg_stat_get_db_sessions", "f", 1, 20),
(6189, "pg_stat_get_db_sessions_abandoned", "f", 1, 20),
(6190, "pg_stat_get_db_sessions_fatal", "f", 1, 20),
(6191, "pg_stat_get_db_sessions_killed", "f", 1, 20),
(3151, "pg_stat_get_db_temp_bytes", "f", 1, 20),
(3150, "pg_stat_get_db_temp_files", "f", 1, 20),
(2762, "pg_stat_get_db_tuples_deleted", "f", 1, 20),
(2759, "pg_stat_get_db_tuples_fetched", "f", 1, 20),
(2760, "pg_stat_get_db_tuples_inserted", "f", 1, 20),
(2758, "pg_stat_get_db_tuples_returned", "f", 1, 20),
(2761, "pg_stat_get_db_tuples_updated", "f", 1, 20),
(1942, "pg_stat_get_db_xact_commit", "f", 1, 20),
(1943, "pg_stat_get_db_xact_rollback", "f", 1, 20),
(2879, "pg_stat_get_dead_tuples", "f", 1, 20),
(2978, "pg_stat_get_function_calls", "f", 1, 20),
(2980, "pg_stat_get_function_self_time", "f", 1, 20),
(2979, "pg_stat_get_function_total_time", "f", 1, 20),
(900038, "pg_stat_get_idx_scan", "f", 0, 20),
(900039, "pg_stat_get_idx_tup_fetch", "f", 0, 20),
(900040, "pg_stat_get_idx_tup_read", "f", 0, 20),
(5053, "pg_stat_get_ins_since_vacuum", "f", 1, 20),
(2878, "pg_stat_get_live_tuples", "f", 1, 20),
(3177, "pg_stat_get_mod_since_analyze", "f", 1, 20),
(1928, "pg_stat_get_numscans", "f", 1, 20),
(6248, "pg_stat_get_recovery_prefetch", "f", 0, 20),
(
900041,
"pg_stat_get_recovery_prefetch_reset_time",
"f",
0,
20,
),
(900042, "pg_stat_get_seq_scan", "f", 0, 20),
(900043, "pg_stat_get_seq_scan_pos", "f", 0, 20),
(900044, "pg_stat_get_seq_tup_read", "f", 0, 20),
(900045, "pg_stat_get_slru_blks_exists", "f", 0, 20),
(900046, "pg_stat_get_slru_blks_hit", "f", 0, 20),
(900047, "pg_stat_get_slru_blks_read", "f", 0, 20),
(900048, "pg_stat_get_slru_blks_written", "f", 0, 20),
(900049, "pg_stat_get_slru_blks_zeroed", "f", 0, 20),
(900050, "pg_stat_get_slru_flushes", "f", 0, 20),
(900051, "pg_stat_get_slru_stat_reset_time", "f", 0, 20),
(900052, "pg_stat_get_slru_truncates", "f", 0, 20),
(3788, "pg_stat_get_snapshot_timestamp", "f", 0, 1114),
(900053, "pg_stat_get_stat_snapshot_timestamp", "f", 0, 1114),
(900054, "pg_stat_get_tid_scan_pos", "f", 0, 20),
(1933, "pg_stat_get_tuples_deleted", "f", 1, 20),
(1930, "pg_stat_get_tuples_fetched", "f", 1, 20),
(1972, "pg_stat_get_tuples_hot_updated", "f", 1, 20),
(1931, "pg_stat_get_tuples_inserted", "f", 1, 20),
(6217, "pg_stat_get_tuples_newpage_updated", "f", 1, 20),
(1929, "pg_stat_get_tuples_returned", "f", 1, 20),
(1932, "pg_stat_get_tuples_updated", "f", 1, 20),
(3054, "pg_stat_get_vacuum_count", "f", 1, 20),
(900055, "pg_stat_get_wal_buffers_full", "f", 0, 20),
(900056, "pg_stat_get_wal_bytes", "f", 0, 20),
(900057, "pg_stat_get_wal_fpi", "f", 0, 20),
(900058, "pg_stat_get_wal_records", "f", 0, 20),
(900059, "pg_stat_get_wal_sync", "f", 0, 20),
(900060, "pg_stat_get_wal_sync_time", "f", 0, 20),
(900061, "pg_stat_get_wal_write", "f", 0, 20),
(900062, "pg_stat_get_wal_write_time", "f", 0, 20),
(3044, "pg_stat_get_xact_blocks_fetched", "f", 1, 20),
(3045, "pg_stat_get_xact_blocks_hit", "f", 1, 20),
(3046, "pg_stat_get_xact_function_calls", "f", 1, 20),
(3048, "pg_stat_get_xact_function_self_time", "f", 1, 20),
(3047, "pg_stat_get_xact_function_total_time", "f", 1, 20),
(3037, "pg_stat_get_xact_numscans", "f", 1, 20),
(3042, "pg_stat_get_xact_tuples_deleted", "f", 1, 20),
(3039, "pg_stat_get_xact_tuples_fetched", "f", 1, 20),
(3043, "pg_stat_get_xact_tuples_hot_updated", "f", 1, 20),
(3040, "pg_stat_get_xact_tuples_inserted", "f", 1, 20),
(6218, "pg_stat_get_xact_tuples_newpage_updated", "f", 1, 20),
(3038, "pg_stat_get_xact_tuples_returned", "f", 1, 20),
(3041, "pg_stat_get_xact_tuples_updated", "f", 1, 20),
(900063, "pg_stop_backup", "f", 0, 25),
(3778, "pg_tablespace_location", "f", 1, 25),
(900064, "pg_terminate_backend_with_timeout", "f", 0, 16),
(3163, "pg_trigger_depth", "f", 0, 23),
(2882, "pg_try_advisory_lock", "f", 1, 16),
(2883, "pg_try_advisory_lock_shared", "f", 1, 16),
(3091, "pg_try_advisory_xact_lock", "f", 1, 16),
(3092, "pg_try_advisory_xact_lock_shared", "f", 1, 16),
(900065, "pg_wait_for_backend_termination", "f", 0, 16),
(1610, "pi", "f", 0, 701),
(849, "position", "f", 2, 23),
(1738, "pow", "f", 2, 23),
(900066, "quote", "f", 1, 25),
(1289, "quote_nullable", "f", 1, 25),
(1609, "radians", "f", 1, 701),
(900067, "rand", "f", 0, 701),
(6212, "random_normal", "f", 2, 701),
(900068, "row", "f", 0, 2249),
(900069, "row_count", "f", 0, 20),
(1706, "sign", "f", 1, 23),
(900070, "similarity", "f", 2, 701),
(1604, "sin", "f", 1, 701),
(900071, "sleep", "f", 0, 23),
(900072, "spg_build_time", "f", 0, 25),
(900073, "spg_edition", "f", 0, 25),
(900074, "spg_uptime_seconds", "f", 0, 20),
(900075, "spg_version", "f", 0, 25),
(3696, "starts_with", "f", 2, 16),
(868, "strpos", "f", 2, 23),
(6311, "system_user", "f", 0, 25),
(1606, "tan", "f", 1, 701),
(274, "timeofday", "f", 0, 25),
(1159, "timezone", "f", 2, 1184),
(1780, "to_date", "f", 2, 1082),
(1158, "to_timestamp", "f", 1, 1184),
(4112, "trunc", "f", 1, 23),
(4286, "tsmultirange", "f", 0, 4533),
(3933, "tsrange", "f", 2, 3908),
(4289, "tstzmultirange", "f", 0, 4534),
(3937, "tstzrange", "f", 2, 3910),
(2943, "txid_current", "f", 0, 20),
(2944, "txid_current_snapshot", "f", 0, 25),
(4549, "unicode_version", "f", 0, 25),
(900076, "unix_timestamp", "f", 0, 20),
(900077, "user", "f", 0, 25),
(6342, "uuid_extract_timestamp", "f", 1, 1184),
(900078, "uuid_generate_v4", "f", 0, 2950),
(900079, "uuid_generate_v7", "f", 0, 2950),
(900080, "uuid_nil", "f", 0, 2950),
(900081, "uuid_ns_dns", "f", 0, 2950),
(900082, "uuid_ns_oid", "f", 0, 2950),
(900083, "uuid_ns_url", "f", 0, 2950),
(900084, "uuid_ns_x500", "f", 0, 2950),
(900085, "uuid_short", "f", 0, 20),
(6429, "uuidv7", "f", 0, 2950),
(89, "version", "f", 0, 25),
(900086, "xmlforest", "f", 0, 142),
(3050, "xpath_exists", "f", 2, 16),
(13298, "_pg_char_max_length", "f", 2, 23),
(13299, "_pg_char_octet_length", "f", 2, 23),
(13304, "_pg_datetime_precision", "f", 2, 23),
(13301, "_pg_numeric_precision", "f", 2, 23),
(13303, "_pg_numeric_scale", "f", 2, 23),
(598, "abbrev", "f", 1, 25),
(599, "abbrev", "f", 1, 25),
(3943, "acldefault", "f", 2, 1034),
(2732, "acosd", "f", 1, 701),
(2466, "acosh", "f", 1, 701),
(378, "array_append", "f", 2, 5078),
(383, "array_cat", "f", 2, 5078),
(747, "array_dims", "f", 1, 25),
(1193, "array_fill", "f", 2, 2277),
(1286, "array_fill", "f", 3, 2277),
(2176, "array_length", "f", 2, 23),
(2091, "array_lower", "f", 2, 23),
(748, "array_ndims", "f", 1, 23),
(3277, "array_position", "f", 2, 23),
(3278, "array_position", "f", 3, 23),
(3279, "array_positions", "f", 2, 1007),
(379, "array_prepend", "f", 2, 5078),
(3167, "array_remove", "f", 2, 5078),
(3168, "array_replace", "f", 3, 5078),
(6381, "array_reverse", "f", 1, 2277),
(6216, "array_sample", "f", 2, 2277),
(6215, "array_shuffle", "f", 1, 2277),
(6388, "array_sort", "f", 1, 2277),
(6389, "array_sort", "f", 2, 2277),
(6390, "array_sort", "f", 3, 2277),
(3153, "array_to_json", "f", 1, 114),
(3154, "array_to_json", "f", 2, 114),
(395, "array_to_string", "f", 2, 25),
(384, "array_to_string", "f", 3, 25),
(3327, "array_to_tsvector", "f", 1, 3614),
(2092, "array_upper", "f", 2, 23),
(2731, "asind", "f", 1, 701),
(2465, "asinh", "f", 1, 701),
(2734, "atan2d", "f", 2, 701),
(2733, "atand", "f", 1, 701),
(2467, "atanh", "f", 1, 701),
(6163, "bit_count", "f", 1, 20),
(6162, "bit_count", "f", 1, 20),
(698, "broadcast", "f", 1, 869),
(2011, "byteacat", "f", 2, 17),
(3179, "cardinality", "f", 1, 23),
(6412, "casefold", "f", 1, 25),
(935, "cash_words", "f", 1, 25),
(1345, "cbrt", "f", 1, 701),
(1621, "chr", "f", 1, 25),
(1813, "convert", "f", 3, 17),
(1714, "convert_from", "f", 2, 25),
(1717, "convert_to", "f", 2, 17),
(2736, "cosd", "f", 1, 701),
(2463, "cosh", "f", 1, 701),
(1607, "cot", "f", 1, 701),
(2738, "cotd", "f", 1, 701),
(6364, "crc32", "f", 1, 20),
(6365, "crc32c", "f", 1, 20),
(2077, "current_setting", "f", 1, 25),
(3294, "current_setting", "f", 2, 25),
(1575, "currval", "f", 1, 20),
(1947, "decode", "f", 2, 17),
(1973, "div", "f", 2, 1700),
(1946, "encode", "f", 2, 25),
(6219, "erf", "f", 1, 701),
(6220, "erfc", "f", 1, 701),
(1376, "factorial", "f", 1, 1700),
(711, "family", "f", 1, 23),
(5044, "gcd", "f", 2, 23),
(5045, "gcd", "f", 2, 20),
(5048, "gcd", "f", 2, 1700),
(1192, "generate_subscripts", "f", 2, 23),
(1191, "generate_subscripts", "f", 3, 23),
(723, "get_bit", "f", 2, 23),
(3032, "get_bit", "f", 2, 23),
(721, "get_byte", "f", 2, 23),
(1926, "has_table_privilege", "f", 2, 16),
(1927, "has_table_privilege", "f", 2, 16),
(1923, "has_table_privilege", "f", 3, 16),
(1922, "has_table_privilege", "f", 3, 16),
(1925, "has_table_privilege", "f", 3, 16),
(1924, "has_table_privilege", "f", 3, 16),
(6413, "hashbytea", "f", 1, 23),
(449, "hashint2", "f", 1, 23),
(450, "hashint4", "f", 1, 23),
(949, "hashint8", "f", 1, 23),
(400, "hashtext", "f", 1, 23),
(699, "host", "f", 1, 25),
(4063, "inet_merge", "f", 2, 650),
(4071, "inet_same_family", "f", 2, 16),
(872, "initcap", "f", 1, 25),
(4351, "is_normalized", "f", 2, 16),
(4237, "isempty", "f", 1, 16),
(3850, "isempty", "f", 1, 16),
(2048, "isfinite", "f", 1, 16),
(1373, "isfinite", "f", 1, 16),
(1390, "isfinite", "f", 1, 16),
(1389, "isfinite", "f", 1, 16),
(3956, "json_array_length", "f", 1, 23),
(3202, "json_object", "f", 1, 114),
(3203, "json_object", "f", 2, 114),
(3957, "json_object_keys", "f", 1, 25),
(3261, "json_strip_nulls", "f", 2, 114),
(4215, "json_to_tsvector", "f", 2, 3614),
(4216, "json_to_tsvector", "f", 3, 3614),
(3968, "json_typeof", "f", 1, 25),
(3207, "jsonb_array_length", "f", 1, 23),
(3301, "jsonb_concat", "f", 2, 3802),
(4050, "jsonb_contained", "f", 2, 16),
(4046, "jsonb_contains", "f", 2, 16),
(3343, "jsonb_delete", "f", 2, 3802),
(3302, "jsonb_delete", "f", 2, 3802),
(3303, "jsonb_delete", "f", 2, 3802),
(3304, "jsonb_delete_path", "f", 2, 3802),
(4047, "jsonb_exists", "f", 2, 16),
(4049, "jsonb_exists_all", "f", 2, 16),
(4048, "jsonb_exists_any", "f", 2, 16),
(3579, "jsonb_insert", "f", 4, 3802),
(3263, "jsonb_object", "f", 1, 3802),
(3264, "jsonb_object", "f", 2, 3802),
(3931, "jsonb_object_keys", "f", 1, 25),
(4005, "jsonb_path_exists", "f", 4, 16),
(4009, "jsonb_path_match", "f", 4, 16),
(4006, "jsonb_path_query", "f", 4, 3802),
(4007, "jsonb_path_query_array", "f", 4, 3802),
(4008, "jsonb_path_query_first", "f", 4, 3802),
(3306, "jsonb_pretty", "f", 1, 25),
(3305, "jsonb_set", "f", 4, 3802),
(5054, "jsonb_set_lax", "f", 5, 3802),
(3262, "jsonb_strip_nulls", "f", 2, 3802),
(4213, "jsonb_to_tsvector", "f", 2, 3614),
(4214, "jsonb_to_tsvector", "f", 3, 3614),
(3210, "jsonb_typeof", "f", 1, 25),
(1295, "justify_days", "f", 1, 1186),
(1175, "justify_hours", "f", 1, 1186),
(2711, "justify_interval", "f", 1, 1186),
(2559, "lastval", "f", 0, 20),
(5047, "lcm", "f", 2, 20),
(5049, "lcm", "f", 2, 1700),
(5046, "lcm", "f", 2, 23),
(3060, "left", "f", 2, 25),
(1637, "like_escape", "f", 2, 25),
(2009, "like_escape", "f", 2, 17),
(1481, "log10", "f", 1, 1700),
(1194, "log10", "f", 1, 701),
(3851, "lower_inc", "f", 1, 16),
(4238, "lower_inc", "f", 1, 16),
(3853, "lower_inf", "f", 1, 16),
(4240, "lower_inf", "f", 1, 16),
(879, "lpad", "f", 2, 25),
(873, "lpad", "f", 3, 25),
(4125, "macaddr8_set7bit", "f", 1, 774),
(3847, "make_time", "f", 3, 1083),
(1365, "makeaclitem", "f", 4, 1033),
(697, "masklen", "f", 1, 23),
(2321, "md5", "f", 1, 25),
(2311, "md5", "f", 1, 25),
(5042, "min_scale", "f", 1, 23),
(4298, "multirange", "f", 1, 4537),
(696, "netmask", "f", 1, 869),
(683, "network", "f", 1, 650),
(1574, "nextval", "f", 1, 20),
(4350, "normalize", "f", 2, 25),
(3672, "numnode", "f", 1, 23),
(1348, "obj_description", "f", 1, 25),
(1215, "obj_description", "f", 2, 25),
(1405, "overlay", "f", 3, 25),
(752, "overlay", "f", 3, 17),
(3031, "overlay", "f", 3, 1560),
(1404, "overlay", "f", 4, 25),
(749, "overlay", "f", 4, 17),
(3030, "overlay", "f", 4, 1560),
(1268, "parse_ident", "f", 2, 1009),
(6315, "pg_basetype", "f", 1, 2206),
(3162, "pg_collation_for", "f", 1, 25),
(2121, "pg_column_compression", "f", 1, 25),
(2319, "pg_encoding_max_length", "f", 1, 23),
(4568, "pg_event_trigger_ddl_commands", "f", 0, 2249),
(3566, "pg_event_trigger_dropped_objects", "f", 0, 2249),
(4566, "pg_event_trigger_table_rewrite_oid", "f", 0, 26),
(4567, "pg_event_trigger_table_rewrite_reason", "f", 0, 23),
(1665, "pg_get_serial_sequence", "f", 2, 25),
(6210, "pg_input_is_valid", "f", 2, 16),
(3252, "pg_lsn_hash", "f", 1, 23),
(4187, "pg_lsn_larger", "f", 2, 3220),
(4188, "pg_lsn_smaller", "f", 2, 3220),
(3425, "pg_partition_ancestors", "f", 1, 2205),
(3424, "pg_partition_root", "f", 1, 2205),
(3334, "pg_size_bytes", "f", 1, 20),
(3166, "pg_size_pretty", "f", 1, 25),
(2288, "pg_size_pretty", "f", 1, 25),
(3165, "pg_wal_lsn_diff", "f", 2, 1700),
(5066, "pg_xact_status", "f", 1, 25),
(5001, "phraseto_tsquery", "f", 1, 3615),
(5006, "phraseto_tsquery", "f", 2, 3615),
(3751, "plainto_tsquery", "f", 1, 3615),
(3747, "plainto_tsquery", "f", 2, 3615),
(1440, "point", "f", 2, 600),
(3673, "querytree", "f", 1, 25),
(3862, "range_adjacent", "f", 2, 16),
(4057, "range_merge", "f", 2, 3831),
(6254, "regexp_count", "f", 2, 23),
(6255, "regexp_count", "f", 3, 23),
(6256, "regexp_count", "f", 4, 23),
(6257, "regexp_instr", "f", 2, 23),
(6258, "regexp_instr", "f", 3, 23),
(6259, "regexp_instr", "f", 4, 23),
(6260, "regexp_instr", "f", 5, 23),
(6261, "regexp_instr", "f", 6, 23),
(6262, "regexp_instr", "f", 7, 23),
(6263, "regexp_like", "f", 2, 16),
(6264, "regexp_like", "f", 3, 16),
(3396, "regexp_match", "f", 2, 1009),
(3397, "regexp_match", "f", 3, 1009),
(2763, "regexp_matches", "f", 2, 1009),
(2764, "regexp_matches", "f", 3, 1009),
(2284, "regexp_replace", "f", 3, 25),
(2285, "regexp_replace", "f", 4, 25),
(6253, "regexp_replace", "f", 4, 25),
(6252, "regexp_replace", "f", 5, 25),
(6251, "regexp_replace", "f", 6, 25),
(2767, "regexp_split_to_array", "f", 2, 1009),
(2768, "regexp_split_to_array", "f", 3, 1009),
(6265, "regexp_substr", "f", 2, 25),
(6266, "regexp_substr", "f", 3, 25),
(6267, "regexp_substr", "f", 4, 25),
(6268, "regexp_substr", "f", 5, 25),
(1622, "repeat", "f", 2, 25),
(2087, "replace", "f", 3, 25),
(3062, "reverse", "f", 1, 25),
(6382, "reverse", "f", 1, 17),
(3061, "right", "f", 2, 25),
(3155, "row_to_json", "f", 1, 114),
(880, "rpad", "f", 2, 25),
(874, "rpad", "f", 3, 25),
(3281, "scale", "f", 1, 23),
(724, "set_bit", "f", 3, 17),
(3033, "set_bit", "f", 3, 1560),
(722, "set_byte", "f", 3, 17),
(605, "set_masklen", "f", 2, 869),
(635, "set_masklen", "f", 2, 650),
(1599, "setseed", "f", 1, 2278),
(1576, "setval", "f", 2, 20),
(1765, "setval", "f", 3, 20),
(3624, "setweight", "f", 2, 3614),
(3320, "setweight", "f", 3, 3614),
(3419, "sha224", "f", 1, 17),
(3420, "sha256", "f", 1, 17),
(3421, "sha384", "f", 1, 17),
(3422, "sha512", "f", 1, 17),
(1623, "similar_escape", "f", 2, 25),
(1987, "similar_to_escape", "f", 1, 25),
(1986, "similar_to_escape", "f", 2, 25),
(2735, "sind", "f", 1, 701),
(2462, "sinh", "f", 1, 701),
(2088, "split_part", "f", 3, 25),
(394, "string_to_array", "f", 2, 1009),
(376, "string_to_array", "f", 3, 1009),
(3623, "strip", "f", 1, 3614),
(2086, "substr", "f", 2, 17),
(883, "substr", "f", 2, 25),
(877, "substr", "f", 3, 25),
(2085, "substr", "f", 3, 17),
(1291, "suppress_redundant_updates_trigger", "f", 0, 2279),
(2737, "tand", "f", 1, 701),
(2464, "tanh", "f", 1, 701),
(743, "text_ge", "f", 2, 16),
(742, "text_gt", "f", 2, 16),
(741, "text_le", "f", 2, 16),
(740, "text_lt", "f", 2, 16),
(1258, "textcat", "f", 2, 25),
(67, "texteq", "f", 2, 16),
(157, "textne", "f", 2, 16),
(1845, "to_ascii", "f", 1, 25),
(1846, "to_ascii", "f", 2, 25),
(1847, "to_ascii", "f", 2, 25),
(6330, "to_bin", "f", 1, 25),
(6331, "to_bin", "f", 1, 25),
(2089, "to_hex", "f", 1, 25),
(2090, "to_hex", "f", 1, 25),
(3176, "to_json", "f", 1, 114),
(3787, "to_jsonb", "f", 1, 3802),
(1777, "to_number", "f", 2, 1700),
(6332, "to_oct", "f", 1, 25),
(6333, "to_oct", "f", 1, 25),
(3750, "to_tsquery", "f", 1, 3615),
(3746, "to_tsquery", "f", 2, 3615),
(3749, "to_tsvector", "f", 1, 3614),
(4209, "to_tsvector", "f", 1, 3614),
(4210, "to_tsvector", "f", 1, 3614),
(4211, "to_tsvector", "f", 2, 3614),
(4212, "to_tsvector", "f", 2, 3614),
(3745, "to_tsvector", "f", 2, 3614),
(878, "translate", "f", 3, 25),
(6172, "trim_array", "f", 2, 2277),
(5043, "trim_scale", "f", 1, 1700),
(3323, "ts_delete", "f", 2, 3614),
(3321, "ts_delete", "f", 2, 3614),
(3319, "ts_filter", "f", 2, 3614),
(3755, "ts_headline", "f", 2, 25),
(4204, "ts_headline", "f", 2, 3802),
(4208, "ts_headline", "f", 2, 114),
(3754, "ts_headline", "f", 3, 25),
(4207, "ts_headline", "f", 3, 114),
(4206, "ts_headline", "f", 3, 114),
(4203, "ts_headline", "f", 3, 3802),
(4202, "ts_headline", "f", 3, 3802),
(3744, "ts_headline", "f", 3, 25),
(4201, "ts_headline", "f", 4, 3802),
(4205, "ts_headline", "f", 4, 114),
(3743, "ts_headline", "f", 4, 25),
(3723, "ts_lexize", "f", 2, 1009),
(3706, "ts_rank", "f", 2, 700),
(3705, "ts_rank", "f", 3, 700),
(3704, "ts_rank", "f", 3, 700),
(3703, "ts_rank", "f", 4, 700),
(3710, "ts_rank_cd", "f", 2, 700),
(3708, "ts_rank_cd", "f", 3, 700),
(3709, "ts_rank_cd", "f", 3, 700),
(3707, "ts_rank_cd", "f", 4, 700),
(3684, "ts_rewrite", "f", 3, 3615),
(3669, "tsquery_and", "f", 2, 3615),
(3671, "tsquery_not", "f", 1, 3615),
(3670, "tsquery_or", "f", 2, 3615),
(5003, "tsquery_phrase", "f", 2, 3615),
(5004, "tsquery_phrase", "f", 3, 3615),
(3326, "tsvector_to_array", "f", 1, 1009),
(3752, "tsvector_update_trigger", "f", 0, 2279),
(3753, "tsvector_update_trigger_column", "f", 0, 2279),
(3360, "txid_status", "f", 1, 25),
(6198, "unistr", "f", 1, 25),
(4239, "upper_inc", "f", 1, 16),
(3852, "upper_inc", "f", 1, 16),
(4241, "upper_inf", "f", 1, 16),
(3854, "upper_inf", "f", 1, 16),
(6343, "uuid_extract_version", "f", 1, 21),
(5009, "websearch_to_tsquery", "f", 1, 3615),
(5007, "websearch_to_tsquery", "f", 2, 3615),
(2170, "width_bucket", "f", 4, 23),
(320, "width_bucket", "f", 4, 23),
(2895, "xmlcomment", "f", 1, 142),
(3813, "xmltext", "f", 1, 142),
(1394, "abs", "f", 1, 700),
(1395, "abs", "f", 1, 701),
(1398, "abs", "f", 1, 21),
(1386, "age", "f", 1, 1186),
(1199, "age", "f", 2, 1186),
(4053, "array_agg", "a", 1, 2277),
(2101, "avg", "a", 1, 1700),
(2102, "avg", "a", 1, 1700),
(2103, "avg", "a", 1, 1700),
(2104, "avg", "a", 1, 701),
(2105, "avg", "a", 1, 701),
(2106, "avg", "a", 1, 1186),
(1810, "bit_length", "f", 1, 23),
(1812, "bit_length", "f", 1, 23),
(2015, "btrim", "f", 2, 17),
(1381, "char_length", "f", 1, 23),
(1369, "character_length", "f", 1, 23),
(6178, "date_bin", "f", 3, 1184),
(1171, "date_part", "f", 2, 701),
(1172, "date_part", "f", 2, 701),
(1384, "date_part", "f", 2, 701),
(1217, "date_trunc", "f", 2, 1184),
(1218, "date_trunc", "f", 2, 1186),
(4293, "datemultirange", "f", 1, 4535),
(3025, "has_any_column_privilege", "f", 3, 16),
(3027, "has_any_column_privilege", "f", 3, 16),
(3022, "has_column_privilege", "f", 3, 16),
(3023, "has_column_privilege", "f", 3, 16),
(3014, "has_column_privilege", "f", 4, 16),
(3015, "has_column_privilege", "f", 4, 16),
(3018, "has_column_privilege", "f", 4, 16),
(3019, "has_column_privilege", "f", 4, 16),
(2254, "has_database_privilege", "f", 2, 16),
(2250, "has_database_privilege", "f", 3, 16),
(2251, "has_database_privilege", "f", 3, 16),
(2252, "has_database_privilege", "f", 3, 16),
(2253, "has_database_privilege", "f", 3, 16),
(2257, "has_function_privilege", "f", 3, 16),
(2259, "has_function_privilege", "f", 3, 16),
(2273, "has_schema_privilege", "f", 2, 16),
(2268, "has_schema_privilege", "f", 3, 16),
(2269, "has_schema_privilege", "f", 3, 16),
(2270, "has_schema_privilege", "f", 3, 16),
(2271, "has_schema_privilege", "f", 3, 16),
(2186, "has_sequence_privilege", "f", 2, 16),
(2182, "has_sequence_privilege", "f", 3, 16),
(2184, "has_sequence_privilege", "f", 3, 16),
(4281, "int4multirange", "f", 1, 4451),
(4296, "int8multirange", "f", 1, 4536),
(3199, "json_build_array", "f", 0, 114),
(3201, "json_build_object", "f", 0, 114),
(3272, "jsonb_build_array", "f", 0, 3802),
(3274, "jsonb_build_object", "f", 0, 3802),
(1340, "log", "f", 1, 701),
(6195, "ltrim", "f", 2, 17),
(2050, "max", "a", 1, 2277),
(2115, "max", "a", 1, 20),
(2117, "max", "a", 1, 21),
(2118, "max", "a", 1, 26),
(2119, "max", "a", 1, 700),
(2120, "max", "a", 1, 701),
(2122, "max", "a", 1, 1082),
(2123, "max", "a", 1, 1083),
(2124, "max", "a", 1, 1266),
(2125, "max", "a", 1, 790),
(2126, "max", "a", 1, 1114),
(2127, "max", "a", 1, 1184),
(2128, "max", "a", 1, 1186),
(2244, "max", "a", 1, 1042),
(2797, "max", "a", 1, 27),
(3564, "max", "a", 1, 869),
(4189, "max", "a", 1, 3220),
(5099, "max", "a", 1, 5069),
(6395, "max", "a", 1, 17),
(2051, "min", "a", 1, 2277),
(2131, "min", "a", 1, 20),
(2133, "min", "a", 1, 21),
(2134, "min", "a", 1, 26),
(2135, "min", "a", 1, 700),
(2136, "min", "a", 1, 701),
(2138, "min", "a", 1, 1082),
(2139, "min", "a", 1, 1083),
(2140, "min", "a", 1, 1266),
(2141, "min", "a", 1, 790),
(2142, "min", "a", 1, 1114),
(2143, "min", "a", 1, 1184),
(2144, "min", "a", 1, 1186),
(2245, "min", "a", 1, 1042),
(2798, "min", "a", 1, 27),
(3565, "min", "a", 1, 869),
(4190, "min", "a", 1, 3220),
(5100, "min", "a", 1, 5069),
(6396, "min", "a", 1, 17),
(940, "mod", "f", 2, 21),
(941, "mod", "f", 2, 23),
(947, "mod", "f", 2, 20),
(4284, "nummultirange", "f", 1, 4532),
(1374, "octet_length", "f", 1, 23),
(1375, "octet_length", "f", 1, 23),
(1682, "octet_length", "f", 1, 23),
(2890, "pg_advisory_unlock", "f", 2, 16),
(2891, "pg_advisory_unlock_shared", "f", 2, 16),
(3801, "pg_current_logfile", "f", 1, 25),
(2168, "pg_database_size", "f", 1, 20),
(2709, "pg_has_role", "f", 2, 16),
(2705, "pg_has_role", "f", 3, 16),
(2706, "pg_has_role", "f", 3, 16),
(2707, "pg_has_role", "f", 3, 16),
(2708, "pg_has_role", "f", 3, 16),
(3578, "pg_logical_emit_message", "f", 4, 3220),
(2888, "pg_try_advisory_lock", "f", 2, 16),
(2889, "pg_try_advisory_lock_shared", "f", 2, 16),
(3095, "pg_try_advisory_xact_lock", "f", 2, 16),
(3096, "pg_try_advisory_xact_lock_shared", "f", 2, 16),
(2014, "position", "f", 2, 23),
(1346, "pow", "f", 2, 701),
(1290, "quote_nullable", "f", 1, 25),
(6339, "random", "f", 2, 23),
(6340, "random", "f", 2, 20),
(6341, "random", "f", 2, 1700),
(6196, "rtrim", "f", 2, 17),
(2310, "sign", "f", 1, 701),
(1699, "substring", "f", 2, 1560),
(2013, "substring", "f", 2, 17),
(2073, "substring", "f", 2, 25),
(1680, "substring", "f", 3, 1560),
(2012, "substring", "f", 3, 17),
(2074, "substring", "f", 3, 25),
(2107, "sum", "a", 1, 1700),
(2109, "sum", "a", 1, 20),
(2110, "sum", "a", 1, 700),
(2111, "sum", "a", 1, 701),
(2112, "sum", "a", 1, 790),
(2113, "sum", "a", 1, 1186),
(2037, "timezone", "f", 2, 1266),
(2069, "timezone", "f", 2, 1184),
(1768, "to_char", "f", 2, 25),
(1770, "to_char", "f", 2, 25),
(1773, "to_char", "f", 2, 25),
(1774, "to_char", "f", 2, 25),
(1776, "to_char", "f", 2, 25),
(1778, "to_timestamp", "f", 2, 1184),
(753, "trunc", "f", 1, 829),
(1343, "trunc", "f", 1, 701),
(1710, "trunc", "f", 1, 1700),
(1709, "trunc", "f", 2, 1700),
(4287, "tsmultirange", "f", 1, 4533),
(4290, "tstzmultirange", "f", 1, 4534),
(6430, "uuidv7", "f", 1, 2950),
(3218, "width_bucket", "f", 2, 23),
(3942, "daterange", "f", 3, 3912),
(3841, "int4range", "f", 3, 3904),
(3946, "int8range", "f", 3, 3926),
(4235, "lower", "f", 1, 2283),
(3463, "make_timestamptz", "f", 7, 1184),
(6373, "max", "a", 1, 2249),
(6374, "min", "a", 1, 2249),
(3845, "numrange", "f", 3, 3906),
(1534, "point", "f", 1, 600),
(4228, "range_merge", "f", 1, 3831),
(3156, "row_to_json", "f", 2, 114),
(1775, "to_char", "f", 2, 25),
(3934, "tsrange", "f", 3, 3908),
(3938, "tstzrange", "f", 3, 3910),
(4236, "upper", "f", 1, 2283),
];
pub(crate) fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("user", DataType::Text, false),
ColumnSchema::new("host", DataType::Text, false),
ColumnSchema::new("select_priv", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
rows.push(Row::new(alloc::vec![
Value::text("root"),
Value::text("localhost"),
Value::text("Y"),
]));
for (name, _) in engine.effective_users().iter() {
if name != "root" {
rows.push(Row::new(alloc::vec![
Value::text(name.to_string()),
Value::text::<String>("%".into()),
Value::text::<String>("Y".into()),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("host", DataType::Text, false),
ColumnSchema::new("db", DataType::Text, false),
ColumnSchema::new("user", DataType::Text, false),
ColumnSchema::new("select_priv", DataType::Text, false),
];
let rows = alloc::vec![Row::new(alloc::vec![
Value::text("localhost"),
Value::text("postgres"),
Value::text("root"),
Value::text::<String>("Y".into()),
])];
(schema, rows)
}
pub(crate) fn synth_info_constraint_column_usage(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("table_catalog", DataType::Text, false),
ColumnSchema::new("table_schema", DataType::Text, false),
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("column_name", DataType::Text, false),
ColumnSchema::new("constraint_catalog", DataType::Text, false),
ColumnSchema::new("constraint_schema", DataType::Text, false),
ColumnSchema::new("constraint_name", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut push = |table: &str, column: String, conname: String| {
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(table.to_string()),
Value::text(column),
Value::text("spg"),
Value::text("public"),
Value::text(conname),
]));
};
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
let cols = &t.schema().columns;
let col_name_at = |pos: usize| -> String {
cols.get(pos)
.map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
};
for uc in t.schema().uniqueness_constraints.iter() {
let conname = pg_unique_conname(t, uc, &tname);
for &p in &uc.columns {
push(&tname, col_name_at(p), conname.clone());
}
}
for fk in t.schema().foreign_keys.iter() {
let conname = fk
.name
.clone()
.unwrap_or_else(|| pg_fk_conname(t, fk, &tname));
if let Some(parent) = cat.get(&fk.parent_table) {
for &p in &fk.parent_columns {
let pname = parent
.schema()
.columns
.get(p)
.map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone());
push(&fk.parent_table, pname, conname.clone());
}
}
}
let check_names = pg_check_connames(t, &tname, &t.schema().checks);
for (ci, chk) in t.schema().checks.iter().enumerate() {
for col in referenced_columns(t, &chk.expr) {
push(&tname, col, check_names[ci].clone());
}
}
for col in cols.iter() {
if col.nullable {
continue;
}
push(
&tname,
col.name.clone(),
alloc::format!("{tname}_{}_not_null", col.name),
);
}
}
(schema, rows)
}
pub(crate) fn synth_info_triggers(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("trigger_catalog", DataType::Text, false),
ColumnSchema::new("trigger_schema", DataType::Text, false),
ColumnSchema::new("trigger_name", DataType::Text, false),
ColumnSchema::new("event_manipulation", DataType::Text, false),
ColumnSchema::new("event_object_schema", DataType::Text, false),
ColumnSchema::new("event_object_table", DataType::Text, false),
ColumnSchema::new("action_statement", DataType::Text, false),
ColumnSchema::new("action_orientation", DataType::Text, false),
ColumnSchema::new("action_timing", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for trg in cat.triggers() {
for event in &trg.events {
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(trg.name.clone()),
Value::text(event.clone()),
Value::text("public"),
Value::text(trg.table.clone()),
Value::text(alloc::format!("EXECUTE FUNCTION {}()", trg.function)),
Value::text(trg.for_each.clone()),
Value::text(trg.timing.clone()),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_info_check_constraints(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("constraint_catalog", DataType::Text, false),
ColumnSchema::new("constraint_schema", DataType::Text, false),
ColumnSchema::new("constraint_name", DataType::Text, false),
ColumnSchema::new("check_clause", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
let check_names = pg_check_connames(t, &tname, &t.schema().checks);
for (ci, clause) in t.schema().checks.iter().enumerate() {
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(check_names[ci].clone()),
Value::text(clause.expr.clone()),
]));
}
for col in t.schema().columns.iter() {
if col.nullable {
continue;
}
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(alloc::format!("{tname}_{}_not_null", col.name)),
Value::text(alloc::format!("{} IS NOT NULL", col.name)),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_info_sequences(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("sequence_catalog", DataType::Text, false),
ColumnSchema::new("sequence_schema", DataType::Text, false),
ColumnSchema::new("sequence_name", DataType::Text, false),
ColumnSchema::new("data_type", DataType::Text, false),
ColumnSchema::new("start_value", DataType::BigInt, false),
ColumnSchema::new("minimum_value", DataType::BigInt, false),
ColumnSchema::new("maximum_value", DataType::BigInt, false),
ColumnSchema::new("increment", DataType::BigInt, false),
ColumnSchema::new("cycle_option", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for (name, def) in cat.sequences_all() {
let Some(name) = cat.listed_name(name) else {
continue;
};
let dt = match def.data_type {
spg_storage::SequenceDataType::SmallInt => "smallint",
spg_storage::SequenceDataType::Int => "integer",
spg_storage::SequenceDataType::BigInt => "bigint",
};
rows.push(Row::new(alloc::vec![
Value::text("spg"),
Value::text("public"),
Value::text(name.to_string()),
Value::text::<&str>(dt),
Value::BigInt(def.start),
Value::BigInt(def.min_value),
Value::BigInt(def.max_value),
Value::BigInt(def.increment),
Value::text::<&str>(if def.cycle { "YES" } else { "NO" }),
]));
}
(schema, rows)
}
pub(crate) fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("constraint_name", DataType::Text, false),
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("column_name", DataType::Text, false),
ColumnSchema::new("ordinal_position", DataType::Int, false),
ColumnSchema::new("referenced_table_name", DataType::Text, false),
ColumnSchema::new("referenced_column_name", DataType::Text, false),
ColumnSchema::new("position_in_unique_constraint", DataType::Int, true),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
let cols = &t.schema().columns;
let col_name_at = |pos: usize| -> String {
cols.get(pos)
.map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
};
for fk in t.schema().foreign_keys.iter() {
let conname = fk
.name
.clone()
.unwrap_or_else(|| pg_fk_conname(t, fk, &tname));
for (i, (&local, &parent)) in fk
.local_columns
.iter()
.zip(fk.parent_columns.iter())
.enumerate()
{
let parent_name = cat
.get(&fk.parent_table)
.and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
.unwrap_or_else(|| alloc::format!("col{parent}"));
#[allow(clippy::cast_possible_wrap)]
let ordinal = (i + 1) as i32;
let in_unique = cat
.get(&fk.parent_table)
.and_then(|pt| {
pt.schema()
.uniqueness_constraints
.iter()
.find(|uc| {
uc.columns.len() == fk.parent_columns.len()
&& fk.parent_columns.iter().all(|pc| uc.columns.contains(pc))
})
.and_then(|uc| uc.columns.iter().position(|&c| c == parent))
})
.unwrap_or(i);
#[allow(clippy::cast_possible_wrap)]
let in_unique = (in_unique + 1) as i32;
rows.push(Row::new(alloc::vec![
Value::text(conname.clone()),
Value::text(tname.clone()),
Value::text(col_name_at(local)),
Value::Int(ordinal),
Value::text(fk.parent_table.clone()),
Value::text(parent_name),
Value::Int(in_unique),
]));
}
}
for uc in t.schema().uniqueness_constraints.iter() {
let conname = pg_unique_conname(t, uc, &tname);
for (i, &local) in uc.columns.iter().enumerate() {
#[allow(clippy::cast_possible_wrap)]
let ordinal = (i + 1) as i32;
rows.push(Row::new(alloc::vec![
Value::text(conname.clone()),
Value::text(tname.clone()),
Value::text(col_name_at(local)),
Value::Int(ordinal),
Value::text(String::new()),
Value::text(String::new()),
Value::Null,
]));
}
}
}
(schema, rows)
}
pub(crate) fn synth_info_referential_constraints(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("constraint_name", DataType::Text, false),
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("referenced_table_name", DataType::Text, false),
ColumnSchema::new("unique_constraint_name", DataType::Text, true),
ColumnSchema::new("update_rule", DataType::Text, false),
ColumnSchema::new("delete_rule", DataType::Text, false),
ColumnSchema::new("constraint_catalog", DataType::Text, false),
ColumnSchema::new("constraint_schema", DataType::Text, false),
ColumnSchema::new("unique_constraint_catalog", DataType::Text, true),
ColumnSchema::new("unique_constraint_schema", DataType::Text, true),
ColumnSchema::new("match_option", DataType::Text, false),
];
fn rule_name(a: spg_storage::FkAction) -> &'static str {
match a {
spg_storage::FkAction::Cascade => "CASCADE",
spg_storage::FkAction::SetNull => "SET NULL",
spg_storage::FkAction::SetDefault => "SET DEFAULT",
spg_storage::FkAction::Restrict => "RESTRICT",
spg_storage::FkAction::NoAction => "NO ACTION",
}
}
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
for fk in t.schema().foreign_keys.iter() {
let conname = fk
.name
.clone()
.unwrap_or_else(|| pg_fk_conname(t, fk, &tname));
let unique_name: Value<'static> = cat
.get(&fk.parent_table)
.and_then(|pt| {
pt.schema()
.uniqueness_constraints
.iter()
.find(|uc| {
uc.columns.len() == fk.parent_columns.len()
&& fk.parent_columns.iter().all(|pc| uc.columns.contains(pc))
})
.map(|uc| pg_unique_conname(pt, uc, &fk.parent_table))
})
.map_or(Value::Null, Value::text);
let has_unique = !matches!(unique_name, Value::Null);
let qualifier = |text: &'static str| {
if has_unique {
Value::text(text)
} else {
Value::Null
}
};
rows.push(Row::new(alloc::vec![
Value::text(conname),
Value::text(tname.clone()),
Value::text(fk.parent_table.clone()),
unique_name,
Value::text::<String>(rule_name(fk.on_update).into()),
Value::text::<String>(rule_name(fk.on_delete).into()),
Value::text("spg"),
Value::text("public"),
qualifier("spg"),
qualifier("public"),
Value::text::<&str>(match fk.match_type {
spg_storage::MatchType::Simple => "NONE",
spg_storage::MatchType::Full => "FULL",
}),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("index_name", DataType::Text, false),
ColumnSchema::new("column_name", DataType::Text, false),
ColumnSchema::new("seq_in_index", DataType::Int, false),
ColumnSchema::new("non_unique", DataType::Int, false),
ColumnSchema::new("index_type", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
for idx in t.indices() {
let col = t
.schema()
.columns
.get(idx.column_position)
.map_or("?".into(), |c| c.name.clone());
rows.push(Row::new(alloc::vec![
Value::text(tname.clone()),
Value::text(idx.name.clone()),
Value::text(col),
Value::Int(1),
Value::Int(i32::from(!idx.is_unique)),
Value::text("BTREE"),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("routine_name", DataType::Text, false),
ColumnSchema::new("routine_type", DataType::Text, false),
ColumnSchema::new("data_type", DataType::Text, false),
];
(schema, Vec::new())
}
pub(crate) fn synth_pg_sequence(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("seqrelid", DataType::BigInt, false),
ColumnSchema::new("seqtypid", DataType::BigInt, false),
ColumnSchema::new("seqstart", DataType::BigInt, false),
ColumnSchema::new("seqincrement", DataType::BigInt, false),
ColumnSchema::new("seqmax", DataType::BigInt, false),
ColumnSchema::new("seqmin", DataType::BigInt, false),
ColumnSchema::new("seqcache", DataType::BigInt, false),
ColumnSchema::new("seqcycle", DataType::Bool, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for (name, def) in cat.sequences_all() {
let Some(name) = cat.listed_name(name) else {
continue;
};
let Some(seq_oid) = relation_oid(cat, name) else {
continue;
};
rows.push(Row::new(alloc::vec![
Value::BigInt(seq_oid),
Value::BigInt(20), Value::BigInt(def.start),
Value::BigInt(def.increment),
Value::BigInt(def.max_value),
Value::BigInt(def.min_value),
Value::BigInt(def.cache),
Value::Bool(def.cycle),
]));
}
(schema, rows)
}
pub(crate) fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("conname", DataType::Text, false),
ColumnSchema::new("connamespace", DataType::BigInt, false),
ColumnSchema::new("contype", DataType::Text, false),
ColumnSchema::new("condeferrable", DataType::Bool, false),
ColumnSchema::new("condeferred", DataType::Bool, false),
ColumnSchema::new("conenforced", DataType::Bool, false),
ColumnSchema::new("convalidated", DataType::Bool, false),
ColumnSchema::new("conrelid", DataType::BigInt, false),
ColumnSchema::new("contypid", DataType::BigInt, false),
ColumnSchema::new("conindid", DataType::BigInt, false),
ColumnSchema::new("conparentid", DataType::BigInt, false),
ColumnSchema::new("confrelid", DataType::BigInt, false),
ColumnSchema::new("confupdtype", DataType::Text, false),
ColumnSchema::new("confdeltype", DataType::Text, false),
ColumnSchema::new("confmatchtype", DataType::Text, false),
ColumnSchema::new("conislocal", DataType::Bool, false),
ColumnSchema::new("coninhcount", DataType::Int, false),
ColumnSchema::new("connoinherit", DataType::Bool, false),
ColumnSchema::new("conperiod", DataType::Bool, false),
ColumnSchema::new("conkey", DataType::Text, false),
ColumnSchema::new("confkey", DataType::Text, false),
ColumnSchema::new("conpfeqop", DataType::Text, true),
ColumnSchema::new("conppeqop", DataType::Text, true),
ColumnSchema::new("conffeqop", DataType::Text, true),
ColumnSchema::new("confdelsetcols", DataType::Text, true),
ColumnSchema::new("conexclop", DataType::Text, true),
ColumnSchema::new("conbin", DataType::Text, true),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let names = cat.visible_table_names();
let mut by_table: alloc::collections::BTreeMap<String, i64> =
alloc::collections::BTreeMap::new();
let mut next_oid: i64 = 16384;
for tname in &names {
by_table.insert(tname.clone(), next_oid);
next_oid = next_oid.saturating_add(1);
}
let mut con_oid: i64 = 65536;
let mut next_con_oid = || -> i64 {
let v = con_oid;
con_oid += 1;
v
};
for tname in &names {
let Some(t) = cat.get(tname) else { continue };
let conrelid = *by_table.get(tname).unwrap_or(&0);
let cols = &t.schema().columns;
let col_name_at = |pos: usize| -> String {
cols.get(pos)
.map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
};
let conkey_vec = |positions: &[usize]| -> String {
let mut s = String::from("{");
for (i, p) in positions.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(&alloc::format!("{}", p + 1));
}
s.push('}');
s
};
for uc in t.schema().uniqueness_constraints.iter() {
let kind = if uc.is_primary_key { "p" } else { "u" };
let conname = pg_unique_conname(t, uc, tname);
let conkey_display = conkey_vec(&uc.columns);
rows.push(Row::new(alloc::vec![
Value::BigInt(next_con_oid()),
Value::text(conname),
Value::BigInt(2200),
Value::text::<String>(kind.into()),
Value::Bool(uc.deferrable), Value::Bool(uc.initially_deferred), Value::Bool(true), Value::Bool(true), Value::BigInt(conrelid),
Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::BigInt(0), Value::text(" "), Value::text(" "), Value::text(" "), Value::Bool(true), Value::Int(0), Value::Bool(true),
Value::Bool(false),
Value::text(conkey_display),
Value::text(String::new()),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
]));
}
for idx in t.indices() {
if !idx.is_unique || idx.partial_predicate.is_some() {
continue;
}
let already = t
.schema()
.uniqueness_constraints
.iter()
.any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
if already {
continue;
}
let is_primary = idx.name.ends_with("_pkey");
let kind = if is_primary { "p" } else { "u" };
let positions = alloc::vec![idx.column_position];
let conkey_display = conkey_vec(&positions);
rows.push(Row::new(alloc::vec![
Value::BigInt(next_con_oid()),
Value::text(idx.name.clone()),
Value::BigInt(2200),
Value::text::<String>(kind.into()),
Value::Bool(false),
Value::Bool(false),
Value::Bool(true),
Value::Bool(true),
Value::BigInt(conrelid),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::text(" "),
Value::text(" "),
Value::text(" "),
Value::Bool(true),
Value::Int(0),
Value::Bool(true),
Value::Bool(false),
Value::text(conkey_display),
Value::text(String::new()),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
]));
}
for fk in t.schema().foreign_keys.iter() {
let conname = fk
.name
.clone()
.unwrap_or_else(|| pg_fk_conname(t, fk, tname));
let confrelid = by_table.get(&fk.parent_table).copied().unwrap_or(0);
let conkey = conkey_vec(&fk.local_columns);
let confkey = conkey_vec(&fk.parent_columns);
let upd_action = fk_action_char(fk.on_update);
let del_action = fk_action_char(fk.on_delete);
rows.push(Row::new(alloc::vec![
Value::BigInt(next_con_oid()),
Value::text(conname),
Value::BigInt(2200),
Value::text("f"),
Value::Bool(false),
Value::Bool(false),
Value::Bool(true),
Value::Bool(true),
Value::BigInt(conrelid),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(confrelid),
Value::text::<String>(upd_action.into()),
Value::text::<String>(del_action.into()),
Value::text("s"), Value::Bool(true),
Value::Int(0),
Value::Bool(true),
Value::Bool(false),
Value::text(conkey),
Value::text(confkey),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
]));
}
let check_names = pg_check_connames(t, tname, &t.schema().checks);
for (conname, check_src) in check_names.into_iter().zip(t.schema().checks.iter()) {
rows.push(Row::new(alloc::vec![
Value::BigInt(next_con_oid()),
Value::text(conname),
Value::BigInt(2200),
Value::text("c"),
Value::Bool(false),
Value::Bool(false),
Value::Bool(true),
Value::Bool(check_src.validated),
Value::BigInt(conrelid),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::text(" "),
Value::text(" "),
Value::text(" "),
Value::Bool(true),
Value::Int(0),
Value::Bool(true),
Value::Bool(false),
Value::text(String::new()),
Value::text(String::new()),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::text(check_src.expr.clone()),
]));
}
for ex in t.schema().exclusion_constraints.iter() {
let positions: Vec<usize> = ex.elements.iter().map(|(p, _)| *p).collect();
let conkey_display = conkey_vec(&positions);
rows.push(Row::new(alloc::vec![
Value::BigInt(next_con_oid()),
Value::text(ex.name.clone()),
Value::BigInt(2200),
Value::text("x"),
Value::Bool(false),
Value::Bool(false),
Value::Bool(true),
Value::Bool(true),
Value::BigInt(conrelid),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::text(" "),
Value::text(" "),
Value::text(" "),
Value::Bool(true),
Value::Int(0),
Value::Bool(true),
Value::Bool(false),
Value::text(conkey_display),
Value::text(String::new()),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
]));
}
let pk_cols: alloc::collections::BTreeSet<usize> = t
.schema()
.uniqueness_constraints
.iter()
.filter(|uc| uc.is_primary_key)
.flat_map(|uc| uc.columns.iter().copied())
.collect();
for (i, col) in cols.iter().enumerate() {
if col.nullable && !pk_cols.contains(&i) {
continue;
}
let conname = alloc::format!("{tname}_{}_not_null", col.name);
let conkey_display = alloc::format!("{} [{}]", i + 1, col.name);
rows.push(Row::new(alloc::vec![
Value::BigInt(next_con_oid()),
Value::text(conname),
Value::BigInt(2200),
Value::text("n"),
Value::Bool(false),
Value::Bool(false),
Value::Bool(true),
Value::Bool(true),
Value::BigInt(conrelid),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::text(" "),
Value::text(" "),
Value::text(" "),
Value::Bool(true),
Value::Int(0),
Value::Bool(true),
Value::Bool(false),
Value::text(conkey_display),
Value::text(String::new()),
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
Value::Null,
]));
}
}
(schema, rows)
}
fn fk_action_char(action: spg_storage::FkAction) -> &'static str {
use spg_storage::FkAction as A;
match action {
A::NoAction => "a",
A::Restrict => "r",
A::Cascade => "c",
A::SetNull => "n",
A::SetDefault => "d",
}
}
pub(crate) fn synth_pg_database(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("datname", DataType::Text, false),
ColumnSchema::new("datdba", DataType::BigInt, false),
ColumnSchema::new("encoding", DataType::Int, false),
ColumnSchema::new("datlocprovider", DataType::Text, false),
ColumnSchema::new("datistemplate", DataType::Bool, false),
ColumnSchema::new("datallowconn", DataType::Bool, false),
ColumnSchema::new("dathasloginevt", DataType::Bool, false),
ColumnSchema::new("datconnlimit", DataType::Int, false),
ColumnSchema::new("datfrozenxid", DataType::Xid, false),
ColumnSchema::new("datminmxid", DataType::BigInt, false),
ColumnSchema::new("dattablespace", DataType::BigInt, false),
ColumnSchema::new("datcollate", DataType::Text, false),
ColumnSchema::new("datctype", DataType::Text, false),
ColumnSchema::new("datlocale", DataType::Text, true),
ColumnSchema::new("daticurules", DataType::Text, true),
ColumnSchema::new("datcollversion", DataType::Text, true),
ColumnSchema::new("datacl", DataType::Text, true),
];
let rows = alloc::vec![Row::new(alloc::vec![
Value::BigInt(16384),
Value::text(
engine
.session_params
.get("spg.database")
.cloned()
.unwrap_or_else(|| alloc::string::String::from("spg")),
),
Value::BigInt(10),
Value::Int(6), Value::text("c"),
Value::Bool(false), Value::Bool(true), Value::Bool(false), Value::Int(-1), Value::Xid(u32::try_from(engine.vacuum_oldest_active()).unwrap_or(u32::MAX)),
Value::BigInt(1), Value::BigInt(1663), Value::text("C"),
Value::text("C"),
Value::Null, Value::Null, Value::Null, Value::Null, ])];
(schema, rows)
}
pub(crate) fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("rolname", DataType::Text, false),
ColumnSchema::new("rolsuper", DataType::Bool, false),
ColumnSchema::new("rolinherit", DataType::Bool, false),
ColumnSchema::new("rolcreaterole", DataType::Bool, false),
ColumnSchema::new("rolcreatedb", DataType::Bool, false),
ColumnSchema::new("rolcanlogin", DataType::Bool, false),
ColumnSchema::new("rolreplication", DataType::Bool, false),
ColumnSchema::new("rolconnlimit", DataType::Int, false),
ColumnSchema::new("rolpassword", DataType::Text, true),
ColumnSchema::new("rolvaliduntil", DataType::Timestamptz, true),
ColumnSchema::new("rolbypassrls", DataType::Bool, false),
ColumnSchema::new("rolconfig", DataType::TextArray, true),
ColumnSchema::new("oid", DataType::BigInt, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let oid: i64 = 10;
for (i, (name, rec)) in engine.effective_users().iter().enumerate() {
rows.push(pg_roles_row(
oid + (i as i64) + 1,
name,
rec.superuser,
rec.inherit,
rec.can_login,
));
}
if !rows
.iter()
.any(|r| matches!(&r.values[0], Value::Text(s) if s == "postgres"))
{
rows.insert(0, pg_roles_row(10, "postgres", true, true, true));
}
let me = engine.session_user();
if !rows
.iter()
.any(|r| matches!(&r.values[0], Value::Text(s) if s == me))
{
rows.push(pg_roles_row(
oid + rows.len() as i64 + 1,
me,
true,
true,
true,
));
}
(schema, rows)
}
fn pg_roles_row(
oid: i64,
name: &str,
superuser: bool,
inherit: bool,
can_login: bool,
) -> Row<'static> {
Row::new(alloc::vec![
Value::text(alloc::string::String::from(name)),
Value::Bool(superuser),
Value::Bool(inherit),
Value::Bool(superuser), Value::Bool(superuser), Value::Bool(can_login),
Value::Bool(superuser), Value::Int(-1), Value::text("********"),
Value::Null, Value::Bool(superuser),
Value::Null, Value::BigInt(oid),
])
}
pub(crate) fn synth_pg_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("usename", DataType::Text, false),
ColumnSchema::new("usesysid", DataType::BigInt, false),
ColumnSchema::new("usecreatedb", DataType::Bool, false),
ColumnSchema::new("usesuper", DataType::Bool, false),
ColumnSchema::new("userepl", DataType::Bool, false),
ColumnSchema::new("usebypassrls", DataType::Bool, false),
ColumnSchema::new("passwd", DataType::Text, true),
ColumnSchema::new("valuntil", DataType::Timestamptz, true),
ColumnSchema::new("useconfig", DataType::TextArray, true),
];
let (_, roles) = synth_pg_roles(engine);
let rows = roles
.into_iter()
.filter(|r| matches!(r.values[5], Value::Bool(true)))
.map(|r| {
Row::new(alloc::vec![
r.values[0].clone(),
r.values[12].clone(),
r.values[4].clone(),
r.values[1].clone(),
r.values[6].clone(),
r.values[10].clone(),
Value::text("********"),
Value::Null,
Value::Null,
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_auth_members(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("roleid", DataType::BigInt, false),
ColumnSchema::new("member", DataType::BigInt, false),
ColumnSchema::new("grantor", DataType::BigInt, false),
ColumnSchema::new("admin_option", DataType::Bool, false),
ColumnSchema::new("inherit_option", DataType::Bool, false),
ColumnSchema::new("set_option", DataType::Bool, false),
];
let oid_of = |name: &str| -> i64 {
engine
.users
.iter()
.position(|(n, _)| n == name)
.map_or(10, |i| 10 + (i as i64) + 1)
};
let mut rows: Vec<Row<'static>> = Vec::new();
for (i, (member, role)) in engine.users.all_memberships().enumerate() {
rows.push(Row::new(alloc::vec![
Value::BigInt(200_000 + (i as i64)),
Value::BigInt(oid_of(role)),
Value::BigInt(oid_of(member)),
Value::BigInt(10), Value::Bool(false),
Value::Bool(true), Value::Bool(true), ]));
}
(schema, rows)
}
const EMPTY_PG_CATALOGS: &[(&str, &[(&str, DataType)])] = &[
(
"pg_default_acl",
&[
("oid", DataType::BigInt),
("defaclrole", DataType::BigInt),
("defaclnamespace", DataType::BigInt),
("defaclobjtype", DataType::Text),
("defaclacl", DataType::TextArray),
],
),
(
"pg_conversion",
&[
("oid", DataType::BigInt),
("conname", DataType::Text),
("connamespace", DataType::BigInt),
("conowner", DataType::BigInt),
("conforencoding", DataType::Int),
("contoencoding", DataType::Int),
("conproc", DataType::BigInt),
("condefault", DataType::Bool),
],
),
(
"pg_event_trigger",
&[
("oid", DataType::BigInt),
("evtname", DataType::Text),
("evtevent", DataType::Text),
("evtowner", DataType::BigInt),
("evtfoid", DataType::BigInt),
("evtenabled", DataType::Text),
("evttags", DataType::TextArray),
],
),
(
"pg_file_settings",
&[
("sourcefile", DataType::Text),
("sourceline", DataType::Int),
("seqno", DataType::Int),
("name", DataType::Text),
("setting", DataType::Text),
("applied", DataType::Bool),
("error", DataType::Text),
],
),
(
"pg_foreign_data_wrapper",
&[
("oid", DataType::BigInt),
("fdwname", DataType::Text),
("fdwowner", DataType::BigInt),
("fdwhandler", DataType::BigInt),
("fdwvalidator", DataType::BigInt),
("fdwacl", DataType::TextArray),
("fdwoptions", DataType::TextArray),
],
),
(
"pg_foreign_server",
&[
("oid", DataType::BigInt),
("srvname", DataType::Text),
("srvowner", DataType::BigInt),
("srvfdw", DataType::BigInt),
("srvtype", DataType::Text),
("srvversion", DataType::Text),
("srvacl", DataType::TextArray),
("srvoptions", DataType::TextArray),
],
),
(
"pg_hba_file_rules",
&[
("rule_number", DataType::Int),
("file_name", DataType::Text),
("line_number", DataType::Int),
("type", DataType::Text),
("database", DataType::TextArray),
("user_name", DataType::TextArray),
("address", DataType::Text),
("netmask", DataType::Text),
("auth_method", DataType::Text),
("options", DataType::TextArray),
("error", DataType::Text),
],
),
(
"pg_ident_file_mappings",
&[
("map_number", DataType::Int),
("file_name", DataType::Text),
("line_number", DataType::Int),
("map_name", DataType::Text),
("sys_name", DataType::Text),
("pg_username", DataType::Text),
("error", DataType::Text),
],
),
(
"pg_init_privs",
&[
("objoid", DataType::BigInt),
("classoid", DataType::BigInt),
("objsubid", DataType::Int),
("privtype", DataType::Text),
("initprivs", DataType::TextArray),
],
),
(
"pg_parameter_acl",
&[
("oid", DataType::BigInt),
("parname", DataType::Text),
("paracl", DataType::TextArray),
],
),
(
"pg_prepared_xacts",
&[
("transaction", DataType::BigInt),
("gid", DataType::Text),
("prepared", DataType::Timestamptz),
("owner", DataType::Text),
("database", DataType::Text),
],
),
(
"pg_publication_namespace",
&[
("oid", DataType::BigInt),
("pnpubid", DataType::BigInt),
("pnnspid", DataType::BigInt),
],
),
(
"pg_publication_rel",
&[
("oid", DataType::BigInt),
("prpubid", DataType::BigInt),
("prrelid", DataType::BigInt),
("prqual", DataType::Text),
("prattrs", DataType::Text),
],
),
(
"pg_publication_tables",
&[
("pubname", DataType::Text),
("schemaname", DataType::Text),
("tablename", DataType::Text),
("attnames", DataType::TextArray),
("rowfilter", DataType::Text),
],
),
(
"pg_replication_origin",
&[("roident", DataType::BigInt), ("roname", DataType::Text)],
),
(
"pg_replication_origin_status",
&[
("local_id", DataType::BigInt),
("external_id", DataType::Text),
("remote_lsn", DataType::Text),
("local_lsn", DataType::Text),
],
),
(
"pg_seclabel",
&[
("objoid", DataType::BigInt),
("classoid", DataType::BigInt),
("objsubid", DataType::Int),
("provider", DataType::Text),
("label", DataType::Text),
],
),
(
"pg_seclabels",
&[
("objoid", DataType::BigInt),
("classoid", DataType::BigInt),
("objsubid", DataType::Int),
("objtype", DataType::Text),
("objnamespace", DataType::BigInt),
("objname", DataType::Text),
("provider", DataType::Text),
("label", DataType::Text),
],
),
(
"pg_shdepend",
&[
("dbid", DataType::BigInt),
("classid", DataType::BigInt),
("objid", DataType::BigInt),
("objsubid", DataType::Int),
("refclassid", DataType::BigInt),
("refobjid", DataType::BigInt),
("deptype", DataType::Text),
],
),
(
"pg_shdescription",
&[
("objoid", DataType::BigInt),
("classoid", DataType::BigInt),
("description", DataType::Text),
],
),
(
"pg_shmem_allocations",
&[
("name", DataType::Text),
("off", DataType::BigInt),
("size", DataType::BigInt),
("allocated_size", DataType::BigInt),
],
),
(
"pg_shmem_allocations_numa",
&[
("name", DataType::Text),
("numa_node", DataType::Int),
("size", DataType::BigInt),
],
),
(
"pg_shseclabel",
&[
("objoid", DataType::BigInt),
("classoid", DataType::BigInt),
("provider", DataType::Text),
("label", DataType::Text),
],
),
(
"pg_statistic_ext_data",
&[
("stxoid", DataType::BigInt),
("stxdinherit", DataType::Bool),
("stxdndistinct", DataType::Text),
("stxddependencies", DataType::Text),
("stxdmcv", DataType::Text),
("stxdexpr", DataType::TextArray),
],
),
(
"pg_stats_ext",
&[
("schemaname", DataType::Text),
("tablename", DataType::Text),
("statistics_schemaname", DataType::Text),
("statistics_name", DataType::Text),
("statistics_owner", DataType::Text),
("attnames", DataType::TextArray),
("exprs", DataType::TextArray),
("kinds", DataType::TextArray),
("inherited", DataType::Bool),
("n_distinct", DataType::Text),
("dependencies", DataType::Text),
("most_common_vals", DataType::TextArray),
("most_common_val_nulls", DataType::BoolArray),
("most_common_freqs", DataType::FloatArray),
("most_common_base_freqs", DataType::FloatArray),
],
),
(
"pg_stats_ext_exprs",
&[
("schemaname", DataType::Text),
("tablename", DataType::Text),
("statistics_schemaname", DataType::Text),
("statistics_name", DataType::Text),
("statistics_owner", DataType::Text),
("expr", DataType::Text),
("inherited", DataType::Bool),
("null_frac", DataType::Float),
("avg_width", DataType::Int),
("n_distinct", DataType::Float),
("most_common_vals", DataType::TextArray),
("most_common_freqs", DataType::FloatArray),
("histogram_bounds", DataType::TextArray),
("correlation", DataType::Float),
("most_common_elems", DataType::TextArray),
("most_common_elem_freqs", DataType::FloatArray),
("elem_count_histogram", DataType::FloatArray),
],
),
(
"pg_subscription_rel",
&[
("srsubid", DataType::BigInt),
("srrelid", DataType::BigInt),
("srsubstate", DataType::Text),
("srsublsn", DataType::Text),
],
),
(
"pg_transform",
&[
("oid", DataType::BigInt),
("trftype", DataType::BigInt),
("trflang", DataType::BigInt),
("trffromsql", DataType::Text),
("trftosql", DataType::Text),
],
),
(
"pg_user_mapping",
&[
("oid", DataType::BigInt),
("umuser", DataType::BigInt),
("umserver", DataType::BigInt),
("umoptions", DataType::TextArray),
],
),
(
"pg_user_mappings",
&[
("umid", DataType::BigInt),
("srvid", DataType::BigInt),
("srvname", DataType::Text),
("umuser", DataType::BigInt),
("usename", DataType::Text),
("umoptions", DataType::TextArray),
],
),
];
pub(crate) fn synth_empty_pg_catalog(view: &str) -> Option<(Vec<ColumnSchema>, Vec<Row<'static>>)> {
let bare = view.strip_prefix("__spg_pg_")?;
let (_, cols) = EMPTY_PG_CATALOGS
.iter()
.find(|(name, _)| name.strip_prefix("pg_") == Some(bare))?;
let schema = cols
.iter()
.map(|(n, t)| ColumnSchema::new(*n, *t, true))
.collect();
Some((schema, Vec::new()))
}
pub(crate) fn synth_pg_authid(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("rolname", DataType::Text, false),
ColumnSchema::new("rolsuper", DataType::Bool, false),
ColumnSchema::new("rolinherit", DataType::Bool, false),
ColumnSchema::new("rolcreaterole", DataType::Bool, false),
ColumnSchema::new("rolcreatedb", DataType::Bool, false),
ColumnSchema::new("rolcanlogin", DataType::Bool, false),
ColumnSchema::new("rolreplication", DataType::Bool, false),
ColumnSchema::new("rolbypassrls", DataType::Bool, false),
ColumnSchema::new("rolconnlimit", DataType::Int, false),
ColumnSchema::new("rolpassword", DataType::Text, true),
ColumnSchema::new("rolvaliduntil", DataType::Timestamptz, true),
];
let (_, roles) = synth_pg_roles(engine);
let rows = roles
.into_iter()
.map(|r| {
Row::new(alloc::vec![
r.values[12].clone(),
r.values[0].clone(),
r.values[1].clone(),
r.values[2].clone(),
r.values[3].clone(),
r.values[4].clone(),
r.values[5].clone(),
r.values[6].clone(),
r.values[10].clone(),
r.values[7].clone(),
r.values[8].clone(),
r.values[9].clone(),
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_group(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("groname", DataType::Text, false),
ColumnSchema::new("grosysid", DataType::BigInt, false),
ColumnSchema::new("grolist", DataType::TextArray, true),
];
let (_, members) = synth_pg_auth_members(engine);
let (_, roles) = synth_pg_roles(engine);
let rows = roles
.into_iter()
.map(|r| {
let oid = match &r.values[12] {
Value::BigInt(o) => *o,
_ => 0,
};
let list: Vec<Option<alloc::string::String>> = members
.iter()
.filter(|m| matches!(&m.values[1], Value::BigInt(o) if *o == oid))
.map(|m| {
Some(alloc::format!(
"{}",
crate::eval::value_to_text(&m.values[2])
))
})
.collect();
Row::new(alloc::vec![
r.values[0].clone(),
Value::BigInt(oid),
if list.is_empty() {
Value::Null
} else {
Value::TextArray(list)
},
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_shadow(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
synth_pg_user(engine)
}
pub(crate) fn synth_pg_db_role_setting(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("setdatabase", DataType::BigInt, false),
ColumnSchema::new("setrole", DataType::BigInt, false),
ColumnSchema::new("setconfig", DataType::TextArray, true),
];
let (_, roles) = synth_pg_roles(engine);
let role_oid = |name: &str| -> i64 {
roles
.iter()
.find(|r| matches!(&r.values[0], Value::Text(n) if n.eq_ignore_ascii_case(name)))
.and_then(|r| match &r.values[12] {
Value::BigInt(o) => Some(*o),
_ => None,
})
.unwrap_or(0)
};
let rows = engine
.active_catalog()
.db_role_settings()
.iter()
.map(|((db, role), params)| {
let config: Vec<Option<alloc::string::String>> = params
.iter()
.map(|(k, v)| Some(alloc::format!("{k}={v}")))
.collect();
Row::new(alloc::vec![
Value::BigInt(if db.is_empty() { 0 } else { 16384 }),
Value::BigInt(if role.is_empty() { 0 } else { role_oid(role) }),
Value::TextArray(config),
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_language() -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("lanname", DataType::Text, false),
ColumnSchema::new("lanowner", DataType::BigInt, false),
ColumnSchema::new("lanispl", DataType::Bool, false),
ColumnSchema::new("lanpltrusted", DataType::Bool, false),
ColumnSchema::new("lanplcallfoid", DataType::BigInt, false),
ColumnSchema::new("laninline", DataType::BigInt, false),
ColumnSchema::new("lanvalidator", DataType::BigInt, false),
ColumnSchema::new("lanacl", DataType::Text, true),
];
let row = |oid: i64, name: &'static str, ispl: bool, trusted: bool| {
Row::new(alloc::vec![
Value::BigInt(oid),
Value::text(name),
Value::BigInt(10),
Value::Bool(ispl),
Value::Bool(trusted),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::Null,
])
};
let rows = alloc::vec![
row(12, "internal", false, false),
row(14, "sql", false, true),
row(13647, "plpgsql", true, true),
];
(schema, rows)
}
pub(crate) fn synth_pg_sequences(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("sequencename", DataType::Text, false),
ColumnSchema::new("sequenceowner", DataType::Text, false),
ColumnSchema::new("data_type", DataType::Text, false),
ColumnSchema::new("start_value", DataType::BigInt, false),
ColumnSchema::new("min_value", DataType::BigInt, false),
ColumnSchema::new("max_value", DataType::BigInt, false),
ColumnSchema::new("increment_by", DataType::BigInt, false),
ColumnSchema::new("cycle", DataType::Bool, false),
ColumnSchema::new("cache_size", DataType::BigInt, false),
ColumnSchema::new("last_value", DataType::BigInt, true),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for (stored, def) in cat.sequences_all() {
let Some(name) = cat.listed_name(stored) else {
continue;
};
rows.push(Row::new(alloc::vec![
Value::text("public"),
Value::text(alloc::string::String::from(name)),
Value::text(
def.owner
.clone()
.unwrap_or_else(|| alloc::string::String::from(CATALOG_OWNER)),
),
Value::text("bigint"),
Value::BigInt(def.start),
Value::BigInt(def.min_value),
Value::BigInt(def.max_value),
Value::BigInt(def.increment),
Value::Bool(def.cycle),
Value::BigInt(def.cache),
if def.is_called {
Value::BigInt(def.last_value)
} else {
Value::Null
},
]));
}
(schema, rows)
}
pub(crate) fn synth_pg_range() -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("rngtypid", DataType::BigInt, false),
ColumnSchema::new("rngsubtype", DataType::BigInt, false),
ColumnSchema::new("rngmultitypid", DataType::BigInt, false),
ColumnSchema::new("rngcollation", DataType::BigInt, false),
ColumnSchema::new("rngsubopc", DataType::BigInt, false),
ColumnSchema::new("rngcanonical", DataType::BigInt, false),
ColumnSchema::new("rngsubdiff", DataType::BigInt, false),
];
const RANGES: &[(i64, i64)] = &[
(3904, 23), (3926, 20), (3906, 1700), (3908, 1114), (3910, 1184), (3912, 1082), ];
let rows = RANGES
.iter()
.map(|(rng, sub)| {
Row::new(alloc::vec![
Value::BigInt(*rng),
Value::BigInt(*sub),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
Value::BigInt(0),
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_partitioned_table(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
use spg_storage::{PartitionKind, PartitionRole};
let schema = alloc::vec![
ColumnSchema::new("partrelid", DataType::BigInt, false),
ColumnSchema::new("partstrat", DataType::Text, false),
ColumnSchema::new("partnatts", DataType::SmallInt, false),
ColumnSchema::new("partdefid", DataType::BigInt, false),
ColumnSchema::new("partattrs", DataType::Text, false),
ColumnSchema::new("partclass", DataType::Text, false),
ColumnSchema::new("partcollation", DataType::Text, false),
ColumnSchema::new("partexprs", DataType::Text, true),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
let Some(PartitionRole::Parent {
kind,
key_column_positions,
..
}) = &t.schema().partition_role
else {
continue;
};
let Some(oid) = relation_oid(cat, &tname) else {
continue;
};
let strat = match kind {
PartitionKind::Range => "r",
PartitionKind::List => "l",
PartitionKind::Hash => "h",
};
let attrs = key_column_positions
.iter()
.map(|p| alloc::format!("{}", p + 1))
.collect::<Vec<_>>()
.join(" ");
let zeros = key_column_positions
.iter()
.map(|_| alloc::string::String::from("0"))
.collect::<Vec<_>>()
.join(" ");
rows.push(Row::new(alloc::vec![
Value::BigInt(oid),
Value::text(strat),
Value::SmallInt(i16::try_from(key_column_positions.len()).unwrap_or(1)),
Value::BigInt(0), Value::text(attrs),
Value::text(zeros.clone()),
Value::text(zeros),
Value::Null, ]));
}
(schema, rows)
}
pub(crate) fn synth_pg_cast() -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("castsource", DataType::BigInt, false),
ColumnSchema::new("casttarget", DataType::BigInt, false),
ColumnSchema::new("castfunc", DataType::BigInt, false),
ColumnSchema::new("castcontext", DataType::Text, false),
ColumnSchema::new("castmethod", DataType::Text, false),
];
const CASTS: &[(i64, i64, &str, &str)] = &[
(16, 23, "e", "f"), (16, 25, "a", "f"), (16, 1042, "a", "f"), (16, 1043, "a", "f"), (17, 20, "e", "f"), (17, 21, "e", "f"), (17, 23, "e", "f"), (18, 23, "e", "f"), (18, 25, "i", "f"), (18, 1042, "a", "f"), (18, 1043, "a", "f"), (19, 25, "i", "f"), (19, 1042, "a", "f"), (19, 1043, "a", "f"), (20, 17, "e", "f"), (20, 21, "a", "f"), (20, 23, "a", "f"), (20, 24, "i", "f"), (20, 26, "i", "f"), (20, 700, "i", "f"), (20, 701, "i", "f"), (20, 790, "a", "f"), (20, 1560, "e", "f"), (20, 1700, "i", "f"), (21, 17, "e", "f"), (21, 20, "i", "f"), (21, 23, "i", "f"), (21, 24, "i", "f"), (21, 26, "i", "f"), (21, 700, "i", "f"), (21, 701, "i", "f"), (21, 1700, "i", "f"), (23, 16, "e", "f"), (23, 17, "e", "f"), (23, 18, "e", "f"), (23, 20, "i", "f"), (23, 21, "a", "f"), (23, 24, "i", "b"), (23, 26, "i", "b"), (23, 700, "i", "f"), (23, 701, "i", "f"), (23, 790, "a", "f"), (23, 1560, "e", "f"), (23, 1700, "i", "f"), (24, 20, "a", "f"), (24, 23, "a", "b"), (24, 26, "i", "b"), (25, 18, "a", "f"), (25, 19, "i", "f"), (25, 142, "e", "f"), (25, 1042, "i", "b"), (25, 1043, "i", "b"), (26, 20, "a", "f"), (26, 23, "a", "b"), (26, 24, "i", "b"), (114, 3802, "a", "i"), (142, 25, "a", "b"), (142, 1042, "a", "b"), (142, 1043, "a", "b"), (650, 25, "a", "f"), (650, 869, "i", "b"), (650, 1042, "a", "f"), (650, 1043, "a", "f"), (700, 20, "a", "f"), (700, 21, "a", "f"), (700, 23, "a", "f"), (700, 701, "i", "f"), (700, 1700, "a", "f"), (701, 20, "a", "f"), (701, 21, "a", "f"), (701, 23, "a", "f"), (701, 700, "a", "f"), (701, 1700, "a", "f"), (790, 1700, "a", "f"), (869, 25, "a", "f"), (869, 650, "a", "f"), (869, 1042, "a", "f"), (869, 1043, "a", "f"), (1042, 18, "a", "f"), (1042, 19, "i", "f"), (1042, 25, "i", "f"), (1042, 142, "e", "f"), (1042, 1042, "i", "f"), (1042, 1043, "i", "f"), (1043, 18, "a", "f"), (1043, 19, "i", "f"), (1043, 25, "i", "b"), (1043, 142, "e", "f"), (1043, 1042, "i", "b"), (1043, 1043, "i", "f"), (1082, 1114, "i", "f"), (1082, 1184, "i", "f"), (1083, 1083, "i", "f"), (1083, 1186, "i", "f"), (1083, 1266, "i", "f"), (1114, 1082, "a", "f"), (1114, 1083, "a", "f"), (1114, 1114, "i", "f"), (1114, 1184, "i", "f"), (1184, 1082, "a", "f"), (1184, 1083, "a", "f"), (1184, 1114, "a", "f"), (1184, 1184, "i", "f"), (1184, 1266, "a", "f"), (1186, 1083, "a", "f"), (1186, 1186, "i", "f"), (1266, 1083, "a", "f"), (1266, 1266, "i", "f"), (1560, 20, "e", "f"), (1560, 23, "e", "f"), (1560, 1560, "i", "f"), (1560, 1562, "i", "b"), (1562, 1560, "i", "b"), (1562, 1562, "i", "f"), (1700, 20, "a", "f"), (1700, 21, "a", "f"), (1700, 23, "a", "f"), (1700, 700, "i", "f"), (1700, 701, "i", "f"), (1700, 790, "a", "f"), (1700, 1700, "i", "f"), (3802, 16, "e", "f"), (3802, 20, "e", "f"), (3802, 21, "e", "f"), (3802, 23, "e", "f"), (3802, 114, "a", "i"), (3802, 700, "e", "f"), (3802, 701, "e", "f"), (3802, 1700, "e", "f"), ];
let mut rows: Vec<Row<'static>> = Vec::with_capacity(CASTS.len());
for (i, (src, tgt, ctx, meth)) in CASTS.iter().enumerate() {
rows.push(Row::new(alloc::vec![
Value::BigInt(OID_CAST_BASE + i as i64),
Value::BigInt(*src),
Value::BigInt(*tgt),
Value::BigInt(0),
Value::text((*ctx).to_string()),
Value::text((*meth).to_string()),
]));
}
(schema, rows)
}
pub(crate) fn synth_pg_foreign_table() -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("ftrelid", DataType::BigInt, false),
ColumnSchema::new("ftserver", DataType::BigInt, false),
ColumnSchema::new("ftoptions", DataType::TextArray, true),
];
(schema, Vec::new())
}
pub(crate) const INSTALLED_EXTENSIONS: &[(&str, &str)] = &[
("plpgsql", "1.0"),
("vector", "0.8.0"),
("pg_trgm", "1.6"),
("pgcrypto", "1.3"),
("hstore", "1.8"),
];
pub(crate) fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("extname", DataType::Text, false),
ColumnSchema::new("extowner", DataType::BigInt, false),
ColumnSchema::new("extnamespace", DataType::BigInt, false),
ColumnSchema::new("extrelocatable", DataType::Bool, false),
ColumnSchema::new("extversion", DataType::Text, false),
ColumnSchema::new("extconfig", DataType::TextArray, true),
ColumnSchema::new("extcondition", DataType::TextArray, true),
];
let exts = INSTALLED_EXTENSIONS;
let rows = exts
.iter()
.enumerate()
.map(|(i, (name, ver))| {
Row::new(alloc::vec![
Value::BigInt(16384 + i as i64),
Value::text::<String>((*name).into()),
Value::BigInt(10),
Value::BigInt(11),
Value::Bool(false),
Value::text::<String>((*ver).into()),
Value::Null,
Value::Null,
])
})
.collect();
(schema, rows)
}
pub(crate) fn render_function_def(f: &spg_storage::FunctionDef) -> alloc::string::String {
let args = canonical_arg_list(&f.args_repr);
let returns = canonical_type_word(f.returns.trim());
let attrs = function_attr_words(f);
let attr_line = if attrs.is_empty() {
alloc::string::String::new()
} else {
alloc::format!(" {}\n", attrs.join(" "))
};
alloc::format!(
"CREATE OR REPLACE FUNCTION public.{}({args})\n RETURNS {returns}\n LANGUAGE {}\n{attr_line}AS $function${}$function$\n",
f.name,
f.language,
f.body,
)
}
pub(crate) fn function_attr_words(
f: &spg_storage::FunctionDef,
) -> alloc::vec::Vec<alloc::string::String> {
let mut out = alloc::vec::Vec::new();
match f.volatility {
spg_storage::FN_IMMUTABLE => out.push(alloc::string::String::from("IMMUTABLE")),
spg_storage::FN_STABLE => out.push(alloc::string::String::from("STABLE")),
_ => {}
}
match f.parallel {
spg_storage::FN_PARALLEL_SAFE => out.push(alloc::string::String::from("PARALLEL SAFE")),
spg_storage::FN_PARALLEL_RESTRICTED => {
out.push(alloc::string::String::from("PARALLEL RESTRICTED"));
}
_ => {}
}
if f.strict {
out.push(alloc::string::String::from("STRICT"));
}
if f.security_definer {
out.push(alloc::string::String::from("SECURITY DEFINER"));
}
if f.leakproof {
out.push(alloc::string::String::from("LEAKPROOF"));
}
if let Some(c) = f.cost {
out.push(alloc::format!("COST {}", render_fn_number(c)));
}
if let Some(r) = f.rows {
out.push(alloc::format!("ROWS {}", render_fn_number(r)));
}
out
}
fn render_fn_number(v: f64) -> alloc::string::String {
let whole = v as i64;
if v.abs() < 1e15 && (whole as f64) == v {
alloc::format!("{whole}")
} else {
alloc::format!("{v}")
}
}
fn canonical_arg_list(args_repr: &str) -> alloc::string::String {
let inner = args_repr
.trim()
.trim_start_matches('(')
.trim_end_matches(')');
if inner.trim().is_empty() {
return alloc::string::String::new();
}
inner
.split(',')
.map(|part| {
let part = part.trim();
match part.split_once(char::is_whitespace) {
Some((name, ty)) => alloc::format!("{name} {}", canonical_type_word(ty.trim())),
None => canonical_type_word(part),
}
})
.collect::<alloc::vec::Vec<_>>()
.join(", ")
}
fn declared_arg_names(args_repr: &str) -> Value<'static> {
let inner = args_repr
.trim()
.trim_start_matches('(')
.trim_end_matches(')');
if inner.trim().is_empty() {
return Value::Null;
}
let names: Vec<Option<alloc::string::String>> = inner
.split(',')
.map(|part| {
let part = part.trim();
part.split_once(char::is_whitespace)
.map(|(n, _)| alloc::string::String::from(n))
})
.collect();
if names.iter().any(Option::is_none) {
return Value::Null;
}
Value::TextArray(names)
}
pub(crate) fn canonical_arg_types(args_repr: &str) -> alloc::string::String {
let inner = args_repr
.trim()
.trim_start_matches('(')
.trim_end_matches(')');
if inner.trim().is_empty() {
return alloc::string::String::new();
}
inner
.split(',')
.map(|part| {
let part = part.trim();
let ty = part
.split_once(char::is_whitespace)
.map_or(part, |(_, t)| t);
canonical_type_word(ty.trim())
})
.collect::<alloc::vec::Vec<_>>()
.join(",")
}
fn canonical_type_word(word: &str) -> alloc::string::String {
crate::conversions::type_name_to_data_type(word)
.map_or_else(|| word.to_ascii_lowercase(), pg_type_word)
}
fn pg_type_word(t: DataType) -> alloc::string::String {
crate::conversions::regtype_oid_to_name(pg_type_oid(t))
.map_or_else(|| alloc::format!("{t}").to_ascii_lowercase(), Into::into)
}
fn render_rule_action(cmd: &str, cat: Option<&Catalog>) -> alloc::string::String {
use spg_sql::ast::Statement;
let Ok(stmt) = spg_sql::parser::parse_statement(cmd) else {
return alloc::string::String::from(cmd);
};
match stmt {
Statement::Insert(ins) if ins.ctes.is_empty() => {
let cols = ins.columns.clone().or_else(|| {
cat.and_then(|c| c.get(&ins.table))
.map(|t| t.schema().columns.iter().map(|c| c.name.clone()).collect())
});
let Some(cols) = cols else {
return alloc::string::String::from(cmd);
};
let head = alloc::format!("INSERT INTO {} ({})", ins.table, cols.join(", "));
if let Some(sel) = &ins.select_source {
return alloc::format!("{head} {sel}");
}
let rendered = alloc::format!("{ins}");
let Some(vpos) = rendered.find(" VALUES ") else {
return rendered;
};
alloc::format!("{head}\n VALUES {}", &rendered[vpos + " VALUES ".len()..])
}
Statement::Update(upd) if upd.ctes.is_empty() => {
let sets: alloc::vec::Vec<alloc::string::String> = upd
.assignments
.iter()
.map(|(c, e)| alloc::format!("{c} = {e}"))
.collect();
let mut out = alloc::format!("UPDATE {} SET {}", upd.table, sets.join(", "));
if let Some(w) = &upd.where_ {
out.push_str(&alloc::format!(
"\n WHERE {}",
qualify_bare_columns(w, &upd.table)
));
}
out
}
Statement::Delete(del) if del.ctes.is_empty() => {
let mut out = alloc::format!("DELETE FROM {}", del.table);
if let Some(w) = &del.where_ {
out.push_str(&alloc::format!(
"\n WHERE {}",
qualify_bare_columns(w, &del.table)
));
}
out
}
other => alloc::format!("{other}"),
}
}
fn qualify_bare_columns(e: &spg_sql::ast::Expr, table: &str) -> alloc::string::String {
let mut cloned = e.clone();
qualify_in_place(&mut cloned, table);
alloc::format!("{cloned}")
}
fn qualify_in_place(e: &mut spg_sql::ast::Expr, table: &str) {
use spg_sql::ast::Expr;
let mut stack = alloc::vec![e];
while let Some(node) = stack.pop() {
if let Expr::Column(c) = node {
if c.qualifier.is_none() {
c.qualifier = Some(alloc::string::String::from(table));
}
continue;
}
match node {
Expr::Binary { lhs, rhs, .. } => {
stack.push(lhs);
stack.push(rhs);
}
Expr::Unary { expr, .. }
| Expr::IsNull { expr, .. }
| Expr::BoolTest { expr, .. }
| Expr::Cast { expr, .. } => stack.push(expr),
Expr::FunctionCall { args, .. } => stack.extend(args.iter_mut()),
_ => {}
}
}
}
pub(crate) fn render_rule_def(
r: &spg_storage::RuleDef,
qualify: bool,
cat: Option<&Catalog>,
) -> alloc::string::String {
let table = if qualify {
alloc::format!("public.{}", r.table)
} else {
r.table.clone()
};
let mut def = alloc::format!("CREATE RULE {} AS\n ON {} TO {table}", r.name, r.event);
if !r.when_condition.is_empty() {
def.push_str("\n WHERE ");
def.push_str(&r.when_condition);
}
def.push_str(" DO ");
if r.instead {
def.push_str("INSTEAD ");
}
if !r.commands.is_empty() {
def.push(' ');
} else if !r.instead {
def.push(' ');
}
match r.commands.len() {
0 => def.push_str("NOTHING"),
1 => def.push_str(&render_rule_action(&r.commands[0], cat)),
_ => {
def.push('(');
let rendered: alloc::vec::Vec<alloc::string::String> = r
.commands
.iter()
.map(|c| render_rule_action(c, cat))
.collect();
def.push_str(&rendered.join("; "));
def.push(')');
}
}
def.push(';');
def
}
pub(crate) fn synth_pg_rewrite(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("rulename", DataType::Text, false),
ColumnSchema::new("ev_class", DataType::BigInt, false),
ColumnSchema::new("ev_type", DataType::Text, false),
ColumnSchema::new("ev_enabled", DataType::Text, false),
ColumnSchema::new("is_instead", DataType::Bool, false),
ColumnSchema::new("ev_qual", DataType::Text, true),
ColumnSchema::new("ev_action", DataType::Text, true),
];
let mut names: Vec<String> = cat.visible_table_names();
names.sort();
let by_table: alloc::collections::BTreeMap<String, i64> = names
.iter()
.enumerate()
.map(|(i, n)| (n.clone(), 16_384 + i as i64))
.collect();
let rows: Vec<Row<'static>> = cat
.rules()
.iter()
.enumerate()
.map(|(i, r)| {
let ev_type = match r.event.to_ascii_uppercase().as_str() {
"SELECT" => "1",
"UPDATE" => "2",
"INSERT" => "3",
_ => "4",
};
Row::new(alloc::vec![
Value::BigInt(RULE_OID_BASE + i as i64),
Value::text(r.name.clone()),
Value::BigInt(*by_table.get(&r.table).unwrap_or(&0)),
Value::text(ev_type),
Value::text("O"),
Value::Bool(r.instead),
if r.when_condition.is_empty() {
Value::Null
} else {
Value::text(r.when_condition.clone())
},
Value::text(r.commands.join("; ")),
])
})
.collect();
(schema, rows)
}
pub(crate) const RULE_OID_BASE: i64 = 500_000;
pub(crate) fn synth_pg_rules(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("tablename", DataType::Text, false),
ColumnSchema::new("rulename", DataType::Text, false),
ColumnSchema::new("definition", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for r in cat.rules() {
let def = render_rule_def(r, true, Some(cat));
rows.push(Row::new(alloc::vec![
Value::text("public"),
Value::text(r.table.clone()),
Value::text(r.name.clone()),
Value::text(def),
]));
}
(schema, rows)
}
pub(crate) fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("viewname", DataType::Text, false),
ColumnSchema::new("viewowner", DataType::Text, false),
ColumnSchema::new("definition", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for (name, def) in cat.views_all() {
let Some(name) = cat.listed_name(name) else {
continue;
};
rows.push(Row::new(alloc::vec![
Value::text("public"),
Value::text(name.to_string()),
Value::text(CATALOG_OWNER),
Value::text(def.body.clone()),
]));
}
(schema, rows)
}
pub(crate) const CATALOG_OWNER: &str = "postgres";
pub(crate) fn synth_pg_matviews(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("matviewname", DataType::Text, false),
ColumnSchema::new("matviewowner", DataType::Text, false),
ColumnSchema::new("tablespace", DataType::Text, true),
ColumnSchema::new("hasindexes", DataType::Bool, false),
ColumnSchema::new("ispopulated", DataType::Bool, false),
ColumnSchema::new("definition", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for stored in cat.table_names() {
let Some(name) = cat.listed_name(&stored).map(alloc::string::String::from) else {
continue;
};
let Some(t) = cat.get(&name) else { continue };
let Some(body) = cat.materialized_views().get(&name) else {
continue;
};
rows.push(Row::new(alloc::vec![
Value::text("public"),
Value::text(name.clone()),
Value::text(CATALOG_OWNER),
Value::Null,
Value::Bool(!t.indices().is_empty()),
Value::Bool(true),
Value::text(body.clone()),
]));
}
(schema, rows)
}
pub(crate) fn canonical_gucs() -> &'static [(
&'static str,
&'static str,
&'static str,
&'static str,
&'static str,
)] {
&[
(
"synchronous_commit",
"on",
"Write-Ahead Log / Settings",
"enum",
"user",
),
(
"server_version",
"18.4 (spg)",
"Preset Options",
"string",
"internal",
),
(
"server_version_num",
"180004",
"Preset Options",
"integer",
"internal",
),
(
"server_encoding",
"UTF8",
"Client Connection Defaults",
"string",
"internal",
),
(
"client_encoding",
"UTF8",
"Client Connection Defaults",
"string",
"user",
),
(
"DateStyle",
"ISO, MDY",
"Client Connection Defaults",
"string",
"user",
),
(
"TimeZone",
"UTC",
"Client Connection Defaults",
"string",
"user",
),
(
"IntervalStyle",
"postgres",
"Client Connection Defaults",
"enum",
"user",
),
(
"default_text_search_config",
"pg_catalog.english",
"Client Connection Defaults",
"string",
"user",
),
(
"extra_float_digits",
"1",
"Client Connection Defaults",
"integer",
"user",
),
(
"bytea_output",
"hex",
"Client Connection Defaults",
"enum",
"user",
),
(
"standard_conforming_strings",
"on",
"Compatibility",
"bool",
"user",
),
(
"integer_datetimes",
"on",
"Compatibility",
"bool",
"internal",
),
(
"max_connections",
"100",
"Connections and Authentication",
"integer",
"postmaster",
),
(
"lock_timeout",
"0",
"Client Connection Defaults",
"integer",
"user",
),
(
"idle_in_transaction_session_timeout",
"0",
"Client Connection Defaults",
"integer",
"user",
),
(
"transaction_timeout",
"0",
"Client Connection Defaults",
"integer",
"user",
),
(
"statement_timeout",
"0",
"Client Connection Defaults",
"integer",
"user",
),
(
"client_min_messages",
"notice",
"Client Connection Defaults",
"enum",
"user",
),
(
"default_tablespace",
"",
"Client Connection Defaults",
"string",
"user",
),
(
"default_table_access_method",
"heap",
"Client Connection Defaults",
"string",
"user",
),
(
"row_security",
"on",
"Client Connection Defaults",
"bool",
"user",
),
(
"check_function_bodies",
"on",
"Client Connection Defaults",
"bool",
"user",
),
(
"xmloption",
"content",
"Client Connection Defaults",
"enum",
"user",
),
(
"work_mem",
"4MB",
"Resource Usage / Memory",
"integer",
"user",
),
(
"maintenance_work_mem",
"64MB",
"Resource Usage / Memory",
"integer",
"user",
),
(
"shared_buffers",
"128MB",
"Resource Usage / Memory",
"integer",
"postmaster",
),
(
"effective_cache_size",
"4GB",
"Query Tuning / Planner Cost Constants",
"integer",
"user",
),
(
"search_path",
"\"$user\", public",
"Client Connection Defaults",
"string",
"user",
),
(
"application_name",
"",
"Reporting and Logging",
"string",
"user",
),
(
"default_transaction_isolation",
"read committed",
"Client Connection Defaults",
"enum",
"user",
),
]
}
pub(crate) fn synth_pg_timezone_names(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("name", DataType::Text, false),
ColumnSchema::new("abbrev", DataType::Text, false),
ColumnSchema::new("utc_offset", DataType::Interval, false),
ColumnSchema::new("is_dst", DataType::Bool, false),
];
let now = engine.clock.map_or(0, |f| f());
let rows = engine
.tz_all_at(now)
.into_iter()
.map(|(name, abbrev, off_secs, is_dst)| {
Row::new(alloc::vec![
Value::text(name),
Value::text(abbrev),
Value::Interval {
months: 0,
days: 0,
micros: off_secs * 1_000_000,
},
Value::Bool(is_dst),
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_timezone_abbrevs(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("abbrev", DataType::Text, false),
ColumnSchema::new("utc_offset", DataType::Interval, false),
ColumnSchema::new("is_dst", DataType::Bool, false),
];
let now = engine.clock.map_or(0, |f| f());
let mut seen: alloc::collections::BTreeMap<alloc::string::String, (i64, bool)> =
alloc::collections::BTreeMap::new();
for (_, abbrev, off_secs, is_dst) in engine.tz_all_at(now) {
seen.entry(abbrev).or_insert((off_secs, is_dst));
}
let rows = seen
.into_iter()
.map(|(abbrev, (off_secs, is_dst))| {
Row::new(alloc::vec![
Value::text(abbrev),
Value::Interval {
months: 0,
days: 0,
micros: off_secs * 1_000_000,
},
Value::Bool(is_dst),
])
})
.collect();
(schema, rows)
}
pub(crate) fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("name", DataType::Text, false),
ColumnSchema::new("setting", DataType::Text, false),
ColumnSchema::new("unit", DataType::Text, true),
ColumnSchema::new("category", DataType::Text, false),
ColumnSchema::new("short_desc", DataType::Text, true),
ColumnSchema::new("extra_desc", DataType::Text, true),
ColumnSchema::new("context", DataType::Text, false),
ColumnSchema::new("vartype", DataType::Text, false),
ColumnSchema::new("source", DataType::Text, false),
ColumnSchema::new("min_val", DataType::Text, true),
ColumnSchema::new("max_val", DataType::Text, true),
ColumnSchema::new("enumvals", DataType::Text, true),
ColumnSchema::new("boot_val", DataType::Text, true),
ColumnSchema::new("reset_val", DataType::Text, true),
ColumnSchema::new("sourcefile", DataType::Text, true),
ColumnSchema::new("sourceline", DataType::Int, true),
ColumnSchema::new("pending_restart", DataType::Bool, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let defaults = canonical_gucs();
let mut push = |name: &str, boot: &str, cat: &str, vartype: &str, context: &str| {
let overridden = engine
.session_params
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| v.clone());
let source = if overridden.is_some() {
"session"
} else {
"default"
};
let setting = overridden.unwrap_or_else(|| boot.into());
let raw = |v: &str| crate::session::guc_raw_setting(name, v);
let unit =
crate::session::guc_unit(name).map_or(Value::Null, |u| Value::text::<String>(u.into()));
let setting = raw(&setting).unwrap_or(setting);
let boot_raw = raw(boot).unwrap_or_else(|| boot.into());
rows.push(Row::new(alloc::vec![
Value::text::<String>(name.into()),
Value::text(setting),
unit, Value::text::<String>(cat.into()),
Value::Null, Value::Null, Value::text::<String>(context.into()),
Value::text::<String>(vartype.into()),
Value::text::<String>(source.into()),
Value::Null, Value::Null, Value::Null, Value::text(boot_raw.clone()),
Value::text(boot_raw), Value::Null, Value::Null, Value::Bool(false), ]));
};
for &(name, val, cat, vartype, context) in defaults {
push(name, val, cat, vartype, context);
}
for (k, v) in &engine.session_params {
if defaults.iter().any(|(n, ..)| (*n).eq_ignore_ascii_case(k)) {
continue;
}
if k.contains('.') {
continue;
}
let vartype = infer_guc_vartype(v);
rows.push(Row::new(alloc::vec![
Value::text(k.clone()),
Value::text(v.clone()),
Value::Null,
Value::text::<String>("Session".into()),
Value::Null,
Value::Null,
Value::text::<String>("user".into()),
Value::text::<String>(vartype.into()),
Value::text::<String>("session".into()),
Value::Null,
Value::Null,
Value::Null,
Value::text(v.clone()),
Value::text(v.clone()),
Value::Null,
Value::Null,
Value::Bool(false),
]));
}
(schema, rows)
}
fn infer_guc_vartype(v: &str) -> &'static str {
match v.trim().to_ascii_lowercase().as_str() {
"on" | "off" | "true" | "false" => "bool",
s if s.parse::<i64>().is_ok() => "integer",
s if s.parse::<f64>().is_ok() => "real",
_ => "string",
}
}
pub(crate) fn synth_pg_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("tablename", DataType::Text, false),
ColumnSchema::new("tableowner", DataType::Text, false),
ColumnSchema::new("tablespace", DataType::Text, true),
ColumnSchema::new("hasindexes", DataType::Bool, false),
ColumnSchema::new("hasrules", DataType::Bool, false),
ColumnSchema::new("hastriggers", DataType::Bool, false),
ColumnSchema::new("rowsecurity", DataType::Bool, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
let has_indexes = !t.indices().is_empty() || !t.schema().uniqueness_constraints.is_empty();
rows.push(Row::new(alloc::vec![
Value::text("public"),
Value::text(tname.clone()),
Value::text("admin"),
Value::Null,
Value::Bool(has_indexes),
Value::Bool(false),
Value::Bool(cat.triggers().iter().any(|tg| tg.table == tname)),
Value::Bool(t.schema().row_security),
]));
}
(schema, rows)
}
pub(crate) fn synth_info_role_table_grants(
cat: &Catalog,
grantee: &str,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let cols = alloc::vec![
ColumnSchema::new("grantor", DataType::Text, false),
ColumnSchema::new("grantee", DataType::Text, false),
ColumnSchema::new("table_catalog", DataType::Text, false),
ColumnSchema::new("table_schema", DataType::Text, false),
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("privilege_type", DataType::Text, false),
ColumnSchema::new("is_grantable", DataType::Text, false),
ColumnSchema::new("with_hierarchy", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let _ = grantee;
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
let sc = t.schema();
let owner = sc
.owner
.clone()
.unwrap_or_else(|| alloc::string::String::from(crate::session::LOGIN_ROLE));
let acl: Vec<(alloc::string::String, u16, u16, alloc::string::String)> =
if sc.acl.is_empty() {
alloc::vec![(
owner.clone(),
spg_storage::priv_bits::ALL,
spg_storage::priv_bits::ALL,
owner.clone(),
)]
} else {
sc.acl
.iter()
.map(|a| {
let grantable = if a.grantee.eq_ignore_ascii_case(&owner) {
a.privs
} else {
a.grantable
};
(a.grantee.clone(), a.privs, grantable, a.grantor.clone())
})
.collect()
};
for (who, privs, grantable, grantor) in acl {
for bit in crate::acl::priv_iter(privs & !spg_storage::priv_bits::MAINTAIN) {
let word = crate::acl::priv_word(bit);
rows.push(Row::new(alloc::vec![
Value::text(grantor.clone()),
Value::text(if who.is_empty() {
alloc::string::String::from("PUBLIC")
} else {
who.clone()
}),
Value::text(alloc::string::String::from("app")),
Value::text(alloc::string::String::from("public")),
Value::text(tname.clone()),
Value::text(alloc::string::String::from(word)),
Value::text(alloc::string::String::from(if grantable & bit != 0 {
"YES"
} else {
"NO"
},)),
Value::text(alloc::string::String::from(
if bit == spg_storage::priv_bits::SELECT {
"YES"
} else {
"NO"
},
)),
]));
}
}
}
(cols, rows)
}
pub(crate) fn synth_info_column_privileges(
cat: &Catalog,
) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let cols = alloc::vec![
ColumnSchema::new("grantor", DataType::Text, false),
ColumnSchema::new("grantee", DataType::Text, false),
ColumnSchema::new("table_catalog", DataType::Text, false),
ColumnSchema::new("table_schema", DataType::Text, false),
ColumnSchema::new("table_name", DataType::Text, false),
ColumnSchema::new("column_name", DataType::Text, false),
ColumnSchema::new("privilege_type", DataType::Text, false),
ColumnSchema::new("is_grantable", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
for col in &t.schema().columns {
for a in &col.acl {
for bit in crate::acl::priv_iter(a.privs & !spg_storage::priv_bits::MAINTAIN) {
rows.push(Row::new(alloc::vec![
Value::text(a.grantor.clone()),
Value::text(if a.grantee.is_empty() {
alloc::string::String::from("PUBLIC")
} else {
a.grantee.clone()
}),
Value::text(alloc::string::String::from("app")),
Value::text(alloc::string::String::from("public")),
Value::text(tname.clone()),
Value::text(col.name.clone()),
Value::text(alloc::string::String::from(crate::acl::priv_word(bit))),
Value::text(alloc::string::String::from(if a.grantable & bit != 0 {
"YES"
} else {
"NO"
},)),
]));
}
}
}
}
(cols, rows)
}
pub(crate) fn synth_pg_description(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let cols = alloc::vec![
ColumnSchema::new("objoid", DataType::Int, false),
ColumnSchema::new("classoid", DataType::Int, false),
ColumnSchema::new("objsubid", DataType::Int, false),
ColumnSchema::new("description", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for (key, text) in cat.comments() {
let Some((kind, name)) = key.split_once(':') else {
continue;
};
let (relname, subid) = if kind == "column" {
match name.split_once('.') {
Some((t, c)) => {
let sub = cat
.get(t)
.and_then(|tb| {
tb.schema()
.columns
.iter()
.position(|sc| sc.name.eq_ignore_ascii_case(c))
})
.map_or(0, |p| i32::try_from(p + 1).unwrap_or(0));
(t, sub)
}
None => continue,
}
} else {
(name, 0)
};
let is_relation = matches!(kind, "table" | "view" | "index" | "sequence" | "column");
let objoid = if is_relation {
crate::eval::regclass_name_to_oid(cat, relname)
.and_then(|o| i32::try_from(o).ok())
.unwrap_or(0)
} else {
0
};
let classoid = if is_relation { 1259 } else { 0 };
rows.push(Row::new(alloc::vec![
Value::Int(objoid),
Value::Int(classoid),
Value::Int(subid),
Value::text(text.clone()),
]));
}
(cols, rows)
}
pub(crate) fn render_indexdef(
t: &spg_storage::Table,
idx: &spg_storage::Index,
tname: &str,
) -> alloc::string::String {
let col_at = |pos: usize| -> alloc::string::String {
t.schema()
.columns
.get(pos)
.map_or_else(|| "?".into(), |c| c.name.clone())
};
let mut positions = alloc::vec![idx.column_position];
positions.extend(idx.extra_column_positions.iter().copied());
let collate_prefix = idx
.collation
.as_ref()
.map_or_else(alloc::string::String::new, |c| {
alloc::format!(" COLLATE \"{c}\"")
});
let order_suffix = {
let mut sfx = alloc::string::String::new();
if idx.descending {
sfx.push_str(" DESC");
}
if let Some(nf) = idx.nulls_first
&& nf != idx.descending
{
sfx.push_str(if nf { " NULLS FIRST" } else { " NULLS LAST" });
}
sfx
};
let cols = core::iter::once(alloc::format!(
"{}{collate_prefix}{order_suffix}",
col_at(idx.column_position)
))
.chain(idx.extra_column_positions.iter().map(|&p| col_at(p)))
.collect::<Vec<_>>()
.join(", ");
let col_name_at = |pos: usize| -> alloc::string::String {
t.schema()
.columns
.get(pos)
.map_or_else(|| "?".into(), |c| c.name.clone())
};
let backs_unique_constraint = t.schema().uniqueness_constraints.iter().any(|uc| {
if uc.columns.len() != positions.len() || !positions.iter().all(|p| uc.columns.contains(p))
{
return false;
}
let auto_name = if uc.is_primary_key {
alloc::format!("{tname}_pkey")
} else {
let cols_part = uc
.columns
.iter()
.map(|&p| col_name_at(p))
.collect::<Vec<_>>()
.join("_");
alloc::format!("{tname}_{cols_part}_key")
};
idx.name == auto_name
});
let unique_kw = if idx.is_unique || backs_unique_constraint {
"UNIQUE "
} else {
""
};
let key = match &idx.expression {
Some(expr) if expr.starts_with('(') => {
alloc::format!("({expr}){collate_prefix}{order_suffix}")
}
Some(expr) => alloc::format!("{expr}{collate_prefix}{order_suffix}"),
None => cols,
};
let nnd = if idx.nulls_not_distinct {
" NULLS NOT DISTINCT"
} else {
""
};
let am = match &idx.kind {
spg_storage::IndexKind::Gin(_)
| spg_storage::IndexKind::GinTrgm(_)
| spg_storage::IndexKind::GinFulltext(_)
| spg_storage::IndexKind::GinJsonb(_) => "gin",
spg_storage::IndexKind::Brin { .. } => "brin",
spg_storage::IndexKind::Nsw(_) => "hnsw",
spg_storage::IndexKind::BTree(_) => "btree",
};
match &idx.partial_predicate {
Some(pred) => {
let p = pred.trim();
let wrapped = if p.starts_with('(') && p.ends_with(')') {
alloc::string::String::from(p)
} else {
alloc::format!("({p})")
};
alloc::format!(
"CREATE {unique_kw}INDEX {} ON public.{tname} USING {am} ({key}){nnd} WHERE {wrapped}",
idx.name,
)
}
None => alloc::format!(
"CREATE {unique_kw}INDEX {} ON public.{tname} USING {am} ({key}){nnd}",
idx.name,
),
}
}
pub(crate) fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("schemaname", DataType::Text, false),
ColumnSchema::new("tablename", DataType::Text, false),
ColumnSchema::new("indexname", DataType::Text, false),
ColumnSchema::new("tablespace", DataType::Text, true),
ColumnSchema::new("indexdef", DataType::Text, false),
];
let mut rows: Vec<Row<'static>> = Vec::new();
for tname in cat.visible_table_names() {
let Some(t) = cat.get(&tname) else { continue };
for idx in t.indices() {
let indexdef = render_indexdef(t, idx, &tname);
rows.push(Row::new(alloc::vec![
Value::text("public"),
Value::text(tname.clone()),
Value::text(idx.name.clone()),
Value::Null, Value::text(indexdef),
]));
}
}
(schema, rows)
}
pub(crate) fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("indexrelid", DataType::BigInt, false),
ColumnSchema::new("indrelid", DataType::BigInt, false),
ColumnSchema::new("indnatts", DataType::SmallInt, false),
ColumnSchema::new("indnkeyatts", DataType::SmallInt, false),
ColumnSchema::new("indisunique", DataType::Bool, false),
ColumnSchema::new("indnullsnotdistinct", DataType::Bool, false),
ColumnSchema::new("indisprimary", DataType::Bool, false),
ColumnSchema::new("indisexclusion", DataType::Bool, false),
ColumnSchema::new("indimmediate", DataType::Bool, false),
ColumnSchema::new("indisclustered", DataType::Bool, false),
ColumnSchema::new("indisvalid", DataType::Bool, false),
ColumnSchema::new("indcheckxmin", DataType::Bool, false),
ColumnSchema::new("indisready", DataType::Bool, false),
ColumnSchema::new("indislive", DataType::Bool, false),
ColumnSchema::new("indisreplident", DataType::Bool, false),
ColumnSchema::new("indkey", DataType::Text, false),
ColumnSchema::new("indcollation", DataType::Text, false),
ColumnSchema::new("indclass", DataType::Text, false),
ColumnSchema::new("indoption", DataType::Text, false),
ColumnSchema::new("indexprs", DataType::Text, true),
ColumnSchema::new("indpred", DataType::Text, true),
];
let mut rows: Vec<Row<'static>> = Vec::new();
let mut idx_oid: i64 = 100_000;
let names = cat.visible_table_names();
let mut table_oid: i64 = 16384;
let mut by_table: alloc::collections::BTreeMap<String, i64> =
alloc::collections::BTreeMap::new();
for tname in &names {
by_table.insert(tname.clone(), table_oid);
table_oid = table_oid.saturating_add(1);
}
for tname in &names {
let Some(t) = cat.get(tname) else { continue };
let relid = *by_table.get(tname).unwrap_or(&0);
for idx in t.indices() {
idx_oid += 1;
let n_attrs_total = 1 + idx.extra_column_positions.len();
let mut indkey = alloc::string::String::new();
indkey.push_str(&alloc::format!("{}", idx.column_position + 1));
for extra in &idx.extra_column_positions {
indkey.push(' ');
indkey.push_str(&alloc::format!("{}", extra + 1));
}
let mut indclass = alloc::string::String::new();
let mut indcollation = alloc::string::String::new();
let mut indoption = alloc::string::String::new();
for i in 0..n_attrs_total {
if i > 0 {
indclass.push(' ');
indcollation.push(' ');
indoption.push(' ');
}
indclass.push('0');
indcollation.push('0');
indoption.push('0');
}
let is_primary = idx.name.ends_with("_pkey");
let is_partial = idx.partial_predicate.is_some();
let is_expression = idx.expression.is_some();
let _ = (is_partial, is_expression);
rows.push(Row::new(alloc::vec![
Value::BigInt(idx_oid),
Value::BigInt(relid),
Value::SmallInt(i16::try_from(n_attrs_total).unwrap_or(i16::MAX)),
Value::SmallInt(i16::try_from(n_attrs_total).unwrap_or(i16::MAX)),
Value::Bool(idx.is_unique),
Value::Bool(idx.nulls_not_distinct),
Value::Bool(is_primary),
Value::Bool(false), Value::Bool(true), Value::Bool(false), Value::Bool(true), Value::Bool(false), Value::Bool(true), Value::Bool(true), Value::Bool(false), Value::text(indkey),
Value::text(indcollation),
Value::text(indclass),
Value::text(indoption),
idx.expression.clone().map_or(Value::Null, Value::text), idx.partial_predicate
.clone()
.map_or(Value::Null, Value::text), ]));
}
}
(schema, rows)
}
pub(crate) fn synth_pg_namespace(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row<'static>>) {
let schema = alloc::vec![
ColumnSchema::new("oid", DataType::BigInt, false),
ColumnSchema::new("nspname", DataType::Text, false),
ColumnSchema::new("nspowner", DataType::BigInt, false),
ColumnSchema::new("nspacl", DataType::Text, true),
];
let public_acl = crate::acl::render_nspacl(cat);
let rows = alloc::vec![
Row::new(alloc::vec![
Value::BigInt(11),
Value::text("pg_catalog"),
Value::BigInt(10),
Value::Null,
]),
Row::new(alloc::vec![
Value::BigInt(2200),
Value::text("public"),
Value::BigInt(10),
Value::text(public_acl),
]),
Row::new(alloc::vec![
Value::BigInt(13000),
Value::text("information_schema"),
Value::BigInt(10),
Value::Null,
]),
Row::new(alloc::vec![
Value::BigInt(13500),
Value::text("spg_catalog"),
Value::BigInt(10),
Value::Null,
]),
];
(schema, rows)
}
fn retype_identifier_columns(view: &str, columns: &mut [ColumnSchema]) {
let bare = view.strip_prefix("__spg_").unwrap_or(view);
let Some((_, names)) = PG_CATALOG_NAME_COLUMNS.iter().find(|(v, _)| *v == bare) else {
return;
};
for c in columns.iter_mut() {
if names.iter().any(|n| *n == c.name) {
c.ty = DataType::Name;
}
}
}
static PG_CATALOG_NAME_COLUMNS: &[(&str, &[&str])] = &[
("pg_am", &["amname"]),
("pg_attribute", &["attname"]),
("pg_class", &["relname"]),
("pg_collation", &["collname"]),
("pg_constraint", &["conname"]),
("pg_database", &["datname"]),
("pg_enum", &["enumlabel"]),
("pg_extension", &["extname"]),
(
"pg_indexes",
&["indexname", "schemaname", "tablename", "tablespace"],
),
(
"pg_matviews",
&["matviewname", "matviewowner", "schemaname", "tablespace"],
),
("pg_namespace", &["nspname"]),
("pg_policies", &["policyname", "schemaname", "tablename"]),
("pg_policy", &["polname"]),
("pg_proc", &["proname"]),
("pg_publication", &["pubname"]),
("pg_replication_slots", &["database", "plugin", "slot_name"]),
("pg_rewrite", &["rulename"]),
("pg_roles", &["rolname"]),
("pg_rules", &["rulename", "schemaname", "tablename"]),
("pg_stat_database", &["datname"]),
("pg_stat_progress_analyze", &["datname"]),
("pg_stat_progress_create_index", &["datname"]),
("pg_stat_progress_vacuum", &["datname"]),
("pg_stat_replication", &["usename"]),
("pg_stat_subscription_stats", &["subname"]),
("pg_stat_user_functions", &["funcname", "schemaname"]),
(
"pg_stat_user_indexes",
&["indexrelname", "relname", "schemaname"],
),
("pg_stat_user_tables", &["relname", "schemaname"]),
("pg_statistic_ext", &["stxname"]),
("pg_subscription", &["subname", "subslotname"]),
(
"pg_tables",
&["schemaname", "tablename", "tableowner", "tablespace"],
),
("pg_tablespace", &["spcname"]),
("pg_trigger", &["tgname", "tgnewtable", "tgoldtable"]),
("pg_type", &["typname"]),
("pg_user", &["usename"]),
("pg_views", &["schemaname", "viewname", "viewowner"]),
];
pub(crate) static INFORMATION_SCHEMA_DOMAINS: &[(&str, DataType)] = &[
("information_schema.cardinal_number", DataType::Int),
("information_schema.character_data", DataType::Text),
("information_schema.sql_identifier", DataType::Name),
("information_schema.yes_or_no", DataType::Text),
];
#[must_use]
pub(crate) fn is_information_schema_domain(name: &str) -> bool {
INFORMATION_SCHEMA_DOMAINS.iter().any(|(n, _)| *n == name)
}
static INFORMATION_SCHEMA_DOMAIN_COLUMNS: &[(&str, &[(&str, &str)])] = &[
(
"tables",
&[
("table_catalog", "information_schema.sql_identifier"),
("table_schema", "information_schema.sql_identifier"),
("table_name", "information_schema.sql_identifier"),
("table_type", "information_schema.character_data"),
],
),
(
"columns",
&[
("table_catalog", "information_schema.sql_identifier"),
("table_schema", "information_schema.sql_identifier"),
("table_name", "information_schema.sql_identifier"),
("column_name", "information_schema.sql_identifier"),
("ordinal_position", "information_schema.cardinal_number"),
("is_nullable", "information_schema.yes_or_no"),
("data_type", "information_schema.character_data"),
],
),
(
"table_constraints",
&[
("constraint_catalog", "information_schema.sql_identifier"),
("constraint_schema", "information_schema.sql_identifier"),
("constraint_name", "information_schema.sql_identifier"),
("table_catalog", "information_schema.sql_identifier"),
("table_schema", "information_schema.sql_identifier"),
("table_name", "information_schema.sql_identifier"),
],
),
(
"key_column_usage",
&[
("constraint_name", "information_schema.sql_identifier"),
("table_name", "information_schema.sql_identifier"),
("column_name", "information_schema.sql_identifier"),
("ordinal_position", "information_schema.cardinal_number"),
],
),
(
"schemata",
&[
("catalog_name", "information_schema.sql_identifier"),
("schema_name", "information_schema.sql_identifier"),
("schema_owner", "information_schema.sql_identifier"),
],
),
(
"views",
&[
("table_catalog", "information_schema.sql_identifier"),
("table_schema", "information_schema.sql_identifier"),
("table_name", "information_schema.sql_identifier"),
],
),
];
fn apply_information_schema_domains(view: &str, columns: &mut [ColumnSchema]) {
let Some(bare) = view.strip_prefix("__spg_info_") else {
return;
};
let Some((_, pairs)) = INFORMATION_SCHEMA_DOMAIN_COLUMNS
.iter()
.find(|(v, _)| *v == bare)
else {
return;
};
for c in columns.iter_mut() {
let Some((_, domain)) = pairs.iter().find(|(n, _)| *n == c.name) else {
continue;
};
c.user_domain_type = Some(alloc::string::String::from(*domain));
if let Some((_, base)) = INFORMATION_SCHEMA_DOMAINS.iter().find(|(n, _)| n == domain) {
c.ty = *base;
}
}
}
pub(crate) const CATALOG_RELATIONS: &[(&str, i64)] = &[
("pg_am", 2601),
("pg_attrdef", 2604),
("pg_attribute", 1249),
("pg_cast", 2605),
("pg_class", 1259),
("pg_collation", 3456),
("pg_constraint", 2606),
("pg_depend", 2608),
("pg_enum", 3501),
("pg_index", 2610),
("pg_inherits", 2611),
("pg_ts_config", 3602),
("pg_ts_config_map", 3603),
("pg_ts_dict", 3600),
("pg_ts_parser", 3601),
("pg_ts_template", 3764),
("pg_largeobject", 2613),
("pg_largeobject_metadata", 2995),
("pg_namespace", 2615),
("pg_operator", 2617),
("pg_policy", 3256),
("pg_proc", 1255),
("pg_statistic", 2619),
("pg_statistic_ext", 3381),
("pg_tablespace", 1213),
("pg_trigger", 2620),
("pg_type", 1247),
];
pub(crate) fn is_synthesised_catalog(name: &str, cat: &Catalog) -> bool {
catalog_relation_columns(name, cat).is_some()
}
fn catalog_relation_columns(name: &str, cat: &Catalog) -> Option<Vec<ColumnSchema>> {
Some(match name {
"pg_am" => synth_pg_am(cat).0,
"pg_attrdef" => synth_pg_attrdef(cat).0,
"pg_attribute" => pg_attribute_schema(),
"pg_cast" => synth_pg_cast().0,
"pg_class" => {
let mut c = pg_class_schema();
splice_pg_class_v18_schema(&mut c);
c
}
"pg_collation" => synth_pg_collation(cat).0,
"pg_constraint" => synth_pg_constraint(cat).0,
"pg_depend" => synth_pg_depend(cat).0,
"pg_enum" => synth_pg_enum(cat).0,
"pg_index" => synth_pg_index_raw(cat).0,
"pg_inherits" => synth_pg_inherits(cat).0,
"pg_ts_config" => synth_pg_ts_config(cat).0,
"pg_ts_config_map" => synth_pg_ts_config_map(cat).0,
"pg_ts_dict" => synth_pg_ts_dict(cat).0,
"pg_ts_parser" => synth_pg_ts_parser(cat).0,
"pg_ts_template" => synth_pg_ts_template(cat).0,
"pg_largeobject" => synth_pg_largeobject(cat).0,
"pg_largeobject_metadata" => synth_pg_largeobject_metadata(cat).0,
"pg_namespace" => synth_pg_namespace(cat).0,
"pg_operator" => synth_pg_operator(cat).0,
"pg_policy" => synth_pg_policy(cat).0,
"pg_proc" => synth_pg_proc(cat).0,
"pg_statistic" => synth_pg_statistic(cat).0,
"pg_statistic_ext" => synth_pg_statistic_ext(cat).0,
"pg_tablespace" => synth_pg_tablespace(cat).0,
"pg_trigger" => synth_pg_trigger(cat).0,
"pg_type" => synth_pg_type(cat).0,
_ => return None,
})
}
fn relation_oid_for_meta_view(name: &str) -> i64 {
let bare = name
.strip_prefix("__spg_pg_")
.map(|b| alloc::format!("pg_{b}"))
.or_else(|| {
name.strip_prefix("__spg_info_")
.map(alloc::string::String::from)
})
.unwrap_or_else(|| alloc::string::String::from(name));
match bare.as_str() {
"pg_type" => 1247,
"pg_attribute" => 1249,
"pg_proc" => 1255,
"pg_class" => 1259,
"pg_database" => 1262,
"pg_constraint" => 2606,
"pg_index" => 2610,
"pg_namespace" => 2615,
_ => 0,
}
}
pub(crate) fn materialise_meta_view(
catalog: &mut Catalog,
name: &str,
mut columns: Vec<ColumnSchema>,
rows: Vec<Row<'static>>,
) -> Result<(), EngineError> {
debug_assert!(
rows.iter().all(|r| r.values.len() == columns.len()),
"{name}: {} columns but a row has {}",
columns.len(),
rows.iter()
.map(|r| r.values.len())
.find(|n| *n != columns.len())
.unwrap_or(0)
);
retype_identifier_columns(name, &mut columns);
apply_information_schema_domains(name, &mut columns);
let sys_start = columns.len();
for sys in crate::select::SYSTEM_COLUMNS {
columns.push(ColumnSchema::new(
alloc::string::String::from(sys),
DataType::Text,
false,
));
}
let view_oid = relation_oid_for_meta_view(name);
let schema = TableSchema::new(name.to_string(), columns);
catalog.create_table(schema).map_err(EngineError::Storage)?;
let table = catalog
.get_mut(name)
.expect("just-created meta view must exist");
for (i, mut row) in rows.into_iter().enumerate() {
row.values.truncate(sys_start);
row.values
.push(Value::text(alloc::format!("(0,{})", i + 1)));
row.values.push(Value::text("2")); row.values.push(Value::text("0")); row.values.push(Value::text("0")); row.values.push(Value::text("0")); row.values.push(Value::text(alloc::format!("{view_oid}")));
table.insert(row).map_err(EngineError::Storage)?;
}
Ok(())
}
pub(crate) fn collect_view_refs(
tref: &spg_sql::ast::TableRef,
cat: &spg_storage::Catalog,
into: &mut Vec<String>,
) {
if cat.has_view(&tref.name)
&& cat.get(&tref.name).is_none()
&& !into.iter().any(|n| n == &tref.name)
{
into.push(tref.name.clone());
}
}
pub(crate) fn select_references_meta_view(stmt: &SelectStatement) -> bool {
let mut names = alloc::collections::BTreeSet::new();
collect_meta_view_names(stmt, &mut names);
!names.is_empty()
}
pub(crate) fn collect_meta_view_names(
stmt: &SelectStatement,
into: &mut alloc::collections::BTreeSet<String>,
) {
fn is_meta(name: &str) -> bool {
name.starts_with("__spg_info_")
|| name.starts_with("__spg_pg_")
|| name.starts_with("__spg_mysql_")
}
fn walk_table(t: &spg_sql::ast::TableRef, into: &mut alloc::collections::BTreeSet<String>) {
if is_meta(&t.name) {
into.insert(t.name.clone());
}
if let Some(sub) = &t.lateral_subquery {
collect_meta_view_names(sub, into);
}
}
fn walk_expr(e: &Expr, into: &mut alloc::collections::BTreeSet<String>) {
match e {
Expr::ScalarSubquery(s) => collect_meta_view_names(s, into),
Expr::Exists { subquery, .. } => collect_meta_view_names(subquery, into),
Expr::InSubquery { expr, subquery, .. } => {
walk_expr(expr, into);
collect_meta_view_names(subquery, into);
}
Expr::Binary { lhs, rhs, .. } => {
walk_expr(lhs, into);
walk_expr(rhs, into);
}
Expr::Unary { expr, .. } | Expr::Cast { expr, .. } => walk_expr(expr, into),
Expr::FunctionCall { args, .. } => args.iter().for_each(|a| walk_expr(a, into)),
Expr::Case {
operand,
branches,
else_branch,
} => {
if let Some(o) = operand {
walk_expr(o, into);
}
for (c, v) in branches {
walk_expr(c, into);
walk_expr(v, into);
}
if let Some(x) = else_branch {
walk_expr(x, into);
}
}
Expr::InList { expr, list, .. } => {
walk_expr(expr, into);
for it in list {
walk_expr(it, into);
}
}
Expr::AnyAll { expr, array, .. } => {
walk_expr(expr, into);
walk_expr(array, into);
}
Expr::Array(items) => items.iter().for_each(|it| walk_expr(it, into)),
Expr::ArraySubscript { target, index } => {
walk_expr(target, into);
walk_expr(index, into);
}
_ => {}
}
}
if let Some(from) = &stmt.from {
walk_table(&from.primary, into);
for j in &from.joins {
walk_table(&j.table, into);
if let Some(on) = &j.on {
walk_expr(on, into);
}
}
}
for item in &stmt.items {
if let SelectItem::Expr { expr, .. } = item {
walk_expr(expr, into);
}
}
if let Some(w) = &stmt.where_ {
walk_expr(w, into);
}
if let Some(h) = &stmt.having {
walk_expr(h, into);
}
if let Some(gs) = &stmt.group_by {
for g in gs {
walk_expr(g, into);
}
}
for o in &stmt.order_by {
walk_expr(&o.expr, into);
}
for (_, peer) in &stmt.unions {
collect_meta_view_names(peer, into);
}
for cte in &stmt.ctes {
if let Some(s) = cte.body.as_select() {
collect_meta_view_names(s, into);
}
}
}