Skip to main content

rupg_analyze/ddl/
mod.rs

1//! The statements that define objects: `CREATE TABLE`, `CREATE INDEX`, `CREATE SCHEMA`, `CREATE VIEW`, `CREATE RULE`, `CREATE TRIGGER`, `CREATE DOMAIN`, `CREATE FUNCTION`, `COMMENT`, `DROP`, `ALTER TABLE`, `ALTER SCHEMA ... OWNER TO`, `CREATE TEXT SEARCH`, `ALTER TEXT SEARCH CONFIGURATION`, `CREATE OPERATOR` and `TRUNCATE`. They run as `transformCreateStmt`, `DefineRelation`, `DefineIndex`, `CreateSchemaCommand`, `DefineView`, `DefineRule`, `CreateTrigger`, `DefineDomain`, `CreateFunction`, `CommentObject`, `ExecDropStmt`, `AlterTable`, `AlterSchemaOwner`, the functions of `tsearchcmds.c`, `DefineOperator` and `ExecuteTruncate` run them, and they change a [`Catalog`]. `CREATE FUNCTION` makes a function in SQL. [`lock_table`] runs the checks of `LOCK TABLE` and takes its locks. It finds its relations by name as these statements do.
2
3mod aggregate;
4mod alter;
5mod alter_column;
6mod alter_constraint;
7mod alter_function;
8mod alter_inherit;
9mod alter_type;
10mod attmap;
11mod cluster;
12mod comment;
13mod copy;
14mod create_table_as;
15mod domain;
16mod drop;
17mod drop_constraint;
18mod drop_function;
19mod enum_label;
20mod enumtype;
21mod fkey;
22mod fkey_partition;
23mod function;
24mod generated;
25mod grant;
26mod identity;
27mod index;
28mod lock;
29mod matview;
30mod opclass;
31mod operator;
32mod owned;
33mod partition;
34mod partition_alter;
35mod partition_attach;
36mod partition_index;
37mod rangetype;
38mod reindex;
39mod relation_options;
40mod reloptions;
41mod rename;
42mod row_type;
43mod rule;
44mod schema_elements;
45mod sequence;
46mod set_schema;
47mod statistics;
48mod table;
49#[cfg(test)]
50mod tests;
51mod trigger;
52mod truncate;
53mod tsearch;
54mod type_change;
55mod using_index;
56mod vacuum;
57mod validate;
58mod view;
59
60pub use copy::{
61    CopyRelation, CopyTarget, attnums, copy_filter, copy_insert, copy_relation, copy_target,
62    rewrite_insert,
63};
64pub(crate) use drop::quote_identifier;
65pub use function::{
66    Body, REGRESS_SYMBOLS, Script, SqlBody, find_library, function_body, function_script,
67    function_sqlbody,
68};
69pub use grant::acl_roles;
70pub(crate) use lock::relkind;
71pub use lock::{Allowed, Take, lock_table};
72pub use owned::{acl_dependents, owned_objects};
73pub use reindex::{ReindexOptions, reindex_options};
74pub use reloptions::tablespace_options;
75pub use rule::{RuleTree, rule_tree};
76pub use schema_elements::SchemaElements;
77pub(crate) use table::check_attribute_type;
78pub use trigger::trigger_when;
79pub use vacuum::vacuum_relations;
80
81use std::cell::RefCell;
82
83use rupg_catalog::{Catalog, ObjRef, PG_CLASS, PG_NAMESPACE, PG_PROC, PG_TYPE, RelKind};
84use rupg_common::{Error, Result, SqlState};
85use rupg_pgcatalog::builtin::{self, ClassRow, Named, NamedRow, Owned};
86use rupg_sql::nodes::{CreateSchemaStmt, GrantTargetType, Node, ObjectType, RangeVar};
87use rupg_types::{Value, oid};
88
89use crate::coerce::AtOpt;
90use crate::expr::{Expr, ExprKind};
91use crate::typename::place;
92use crate::{Analyzer, Env, PG_CATALOG_NAMESPACE, Params};
93
94/// The OID of the schema `pg_toast`, where a user cannot create a relation.
95const PG_TOAST_NAMESPACE: u32 = 99;
96
97/// `BOOTSTRAP_SUPERUSERID`, the owner of the temporary schemas.
98const BOOTSTRAP_SUPERUSER: u32 = 10;
99
100/// A message of a statement that defines an object. The client gets the messages before the result.
101#[derive(Clone, Debug, PartialEq)]
102pub enum Message {
103    /// A `WARNING`, such as a precision that is too large.
104    Warning(Error),
105    /// A `NOTICE`, such as an object that `IF NOT EXISTS` skips.
106    Notice(Error),
107}
108
109/// The result of [`define`].
110#[derive(Debug)]
111pub struct Defined {
112    /// The messages in order. The client gets them also when the statement fails.
113    pub messages: Vec<Message>,
114    /// The result of the statement. After an error the caller discards the changes of the catalog, but the OIDs that the statement used stay used, as in PostgreSQL.
115    pub result: Result<()>,
116    /// The tables that the statement read with a sequential scan, once for each scan, as `pgstat_count_heap_scan` counts them: the build of an index, the check of new constraints by `ALTER TABLE` and the check of a new foreign key. The scans count also when the statement fails.
117    pub scans: Vec<u32>,
118    /// The tables of `TRUNCATE`, whose rows the caller removes after the checks pass.
119    pub truncated: Vec<u32>,
120    /// The expressions of `USING` of `ALTER COLUMN ... TYPE`, which give the new values of the columns when the caller writes the rows of the table again.
121    pub transforms: Vec<Transform>,
122    /// The defaults that the new columns of `ADD COLUMN` had when the subcommand ran, for each new column whose default a later subcommand of the same statement changed. The caller writes the rows of the table again with these defaults.
123    pub fills: Vec<Fill>,
124    /// The table of `CREATE TABLE AS` or the materialized view of `CREATE MATERIALIZED VIEW` or `REFRESH MATERIALIZED VIEW`, which the caller fills with the rows of the query, or `None`. It is `None` also for `WITH NO DATA` and for a table that exists with `IF NOT EXISTS`.
125    pub filled: Option<u32>,
126    /// The views whose query reads an object that the statement renamed. The catalog keeps the text of a view, so the caller writes the text again from the query that the old names give.
127    pub views: Vec<u32>,
128    /// The partitions of `ATTACH PARTITION` whose rows the caller checks against their new partition constraints after the statement, as `QueuePartitionConstraintValidation` queues them.
129    pub checks: Vec<PartitionCheck>,
130    /// The partitions of `DETACH PARTITION` whose keys a foreign key refers to through the parent. Before the statement the caller checks that no row of a referencing table has a key of the partition, as `ATDetachCheckNoForeignKeyRefs` checks it, with the catalog before the statement.
131    pub detached: Vec<u32>,
132    /// The roles of `DROP OWNED`. The caller removes the grants of membership that these roles made, as `shdepDropOwned` drops the rows of `pg_auth_members` with the role as the grantor.
133    pub grantors: Vec<u32>,
134    /// The elements of `CREATE SCHEMA`, which the caller runs after it keeps the new schema, or `None`.
135    pub schema: Option<SchemaElements>,
136    /// The check of `PreventInTransactionBlock` that the statement needs after it found its relations, such as `REINDEX` of a partitioned table, or `None`. The caller runs the check before it keeps the changes, and after an error the messages of the statement go.
137    pub top_level: Option<TopLevel>,
138    /// The new index of `CREATE INDEX CONCURRENTLY` on a table that is not temporary, or `None`. The first transaction of `DefineIndex` commits the index before the build, so after a failure of the build the caller keeps the index with `indisvalid` and `indisready` false.
139    pub concurrent: Option<u32>,
140}
141
142/// A statement that cannot run inside a transaction block or in a function, as `PreventInTransactionBlock` checks it.
143#[derive(Clone, Debug, PartialEq, Eq)]
144pub struct TopLevel {
145    /// The name of the statement in the error, such as `REINDEX TABLE`.
146    pub statement: &'static str,
147    /// The context of the error, such as the partitioned table of `REINDEX`.
148    pub context: String,
149}
150
151/// A partition whose rows must satisfy its partition constraint, one entry of `tab->partition_constraint`.
152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
153pub struct PartitionCheck {
154    /// The OID of the partition. The caller checks the rows of each of its leaf partitions.
155    pub table: u32,
156    /// True for the default partition, whose constraint is smaller after a new partition, with the error of `validateDefaultPartitionConstraint` for a row that violates it.
157    pub default: bool,
158}
159
160/// The expression of `USING` of `ALTER COLUMN ... TYPE` for one column, as `ATPrepAlterColumnType` keeps it in `tab->newvals`.
161#[derive(Clone, Debug)]
162pub struct Transform {
163    /// The OID of the table.
164    pub table: u32,
165    /// The name of the column.
166    pub column: String,
167    /// The expression before its transform. It refers to the columns of the table by their names, and it gives the old values of the columns.
168    pub expr: Node,
169}
170
171/// The default of a new column of `ALTER TABLE ... ADD COLUMN`, as `ATExecAddColumn` keeps it in `tab->newvals`, or the new expression of a generated column of `SET EXPRESSION`. The rows of the table get this default, also when a later subcommand of the statement sets or drops the default of the column.
172#[derive(Clone, Debug)]
173pub struct Fill {
174    /// The OID of the table.
175    pub table: u32,
176    /// The number of the column.
177    pub column: i16,
178    /// The text of the default expression, or `None` for a column with no default, which gets a null.
179    pub default: Option<String>,
180}
181
182/// True for a statement that [`define`] runs.
183pub fn is_definition(stmt: &Node) -> bool {
184    matches!(
185        stmt,
186        Node::CreateStmt(_)
187            | Node::CreateTableAsStmt(_)
188            | Node::IndexStmt(_)
189            | Node::CreateSchemaStmt(_)
190            | Node::CreateDomainStmt(_)
191            | Node::CompositeTypeStmt(_)
192            | Node::CreateEnumStmt(_)
193            | Node::AlterEnumStmt(_)
194            | Node::CreateRangeStmt(_)
195            | Node::ViewStmt(_)
196            | Node::RuleStmt(_)
197            | Node::CreateTrigStmt(_)
198            | Node::CreateFunctionStmt(_)
199            | Node::AlterFunctionStmt(_)
200            | Node::CommentStmt(_)
201            | Node::CreateSeqStmt(_)
202            | Node::AlterSeqStmt(_)
203            | Node::AlterTSConfigurationStmt(_)
204            | Node::AlterOperatorStmt(_)
205            | Node::CreateOpFamilyStmt(_)
206            | Node::CreateOpClassStmt(_)
207            | Node::AlterOpFamilyStmt(_)
208            | Node::TruncateStmt(_)
209            | Node::RefreshMatViewStmt(_)
210            | Node::CreateStatsStmt(_)
211            | Node::AlterStatsStmt(_)
212            | Node::DropOwnedStmt(_)
213            | Node::ReassignOwnedStmt(_)
214            | Node::AlterDefaultPrivilegesStmt(_)
215    ) || matches!(stmt, Node::DefineStmt(define) if tsearch::is_ts_kind(define.kind) || matches!(define.kind, ObjectType::OBJECT_OPERATOR | ObjectType::OBJECT_AGGREGATE))
216        || matches!(stmt, Node::DropStmt(drop) if is_drop_kind(drop.removeType))
217        || matches!(stmt, Node::AlterTableStmt(alter) if is_alter_kind(alter.objtype) || alter.objtype == ObjectType::OBJECT_TYPE)
218        || matches!(stmt, Node::AlterOwnerStmt(alter) if matches!(alter.objectType, ObjectType::OBJECT_SCHEMA | ObjectType::OBJECT_OPERATOR | ObjectType::OBJECT_STATISTIC_EXT) || is_opcf_kind(alter.objectType) || type_change::is_type_kind(alter.objectType) || alter_function::is_function_kind(alter.objectType))
219        || matches!(stmt, Node::RenameStmt(rename) if rename::is_rename_kind(rename.renameType))
220        || matches!(stmt, Node::AlterObjectSchemaStmt(alter) if is_alter_kind(alter.objectType) || matches!(alter.objectType, ObjectType::OBJECT_OPERATOR | ObjectType::OBJECT_STATISTIC_EXT) || is_opcf_kind(alter.objectType) || type_change::is_type_kind(alter.objectType) || alter_function::is_function_kind(alter.objectType))
221        || matches!(stmt, Node::SelectStmt(select) if select.intoClause.is_some())
222        || matches!(stmt, Node::GrantStmt(grant) if grant.targtype != GrantTargetType::ACL_TARGET_DEFAULTS)
223}
224
225/// True for an operator class and an operator family.
226fn is_opcf_kind(kind: ObjectType) -> bool {
227    matches!(kind, ObjectType::OBJECT_OPCLASS | ObjectType::OBJECT_OPFAMILY)
228}
229
230/// True for the kinds of relations of `ALTER TABLE` and of the other statements that change a relation. `ALTER TYPE` of the attributes of a composite type is an `AlterTableStmt` too.
231fn is_alter_kind(kind: ObjectType) -> bool {
232    matches!(
233        kind,
234        ObjectType::OBJECT_TABLE
235            | ObjectType::OBJECT_VIEW
236            | ObjectType::OBJECT_INDEX
237            | ObjectType::OBJECT_SEQUENCE
238            | ObjectType::OBJECT_MATVIEW
239            | ObjectType::OBJECT_FOREIGN_TABLE
240    )
241}
242
243/// True for the kinds of objects that `DROP` can remove.
244fn is_drop_kind(kind: ObjectType) -> bool {
245    matches!(
246        kind,
247        ObjectType::OBJECT_TABLE
248            | ObjectType::OBJECT_VIEW
249            | ObjectType::OBJECT_MATVIEW
250            | ObjectType::OBJECT_INDEX
251            | ObjectType::OBJECT_SEQUENCE
252            | ObjectType::OBJECT_SCHEMA
253            | ObjectType::OBJECT_TYPE
254            | ObjectType::OBJECT_DOMAIN
255            | ObjectType::OBJECT_RULE
256            | ObjectType::OBJECT_TRIGGER
257            | ObjectType::OBJECT_FUNCTION
258            | ObjectType::OBJECT_PROCEDURE
259            | ObjectType::OBJECT_ROUTINE
260            | ObjectType::OBJECT_AGGREGATE
261            | ObjectType::OBJECT_TSPARSER
262            | ObjectType::OBJECT_TSDICTIONARY
263            | ObjectType::OBJECT_TSTEMPLATE
264            | ObjectType::OBJECT_TSCONFIGURATION
265            | ObjectType::OBJECT_OPERATOR
266            | ObjectType::OBJECT_OPCLASS
267            | ObjectType::OBJECT_OPFAMILY
268            | ObjectType::OBJECT_STATISTIC_EXT
269    )
270}
271
272/// Runs a statement that defines an object, for the role `user`, and adds the object to the catalog. The statement is the `stmt` of a `RawStmt`, and `text` is its text, which the catalog keeps for a view and for a function.
273pub fn define(stmt: &Node, text: &str, env: &dyn Env, catalog: &mut Catalog, user: u32) -> Defined {
274    let schemas = SchemaEnv::new(env, catalog);
275    let mut definer = Definer {
276        an: Analyzer::new(&schemas, &Params::default()),
277        catalog,
278        user,
279        messages: Vec::new(),
280        scans: Vec::new(),
281        truncated: Vec::new(),
282        transforms: Vec::new(),
283        added: Vec::new(),
284        views: Vec::new(),
285        checks: Vec::new(),
286        detached: Vec::new(),
287        grantors: Vec::new(),
288        schema: None,
289        top_level: None,
290        concurrent: None,
291    };
292    let mut filled = None;
293    let result = match stmt {
294        Node::CreateStmt(create) => definer.create_table(create),
295        Node::CreateTableAsStmt(ctas) => {
296            definer.create_table_as(ctas, text).map(|table| filled = table)
297        }
298        Node::SelectStmt(select) => definer.select_into(select).map(|table| filled = table),
299        Node::RefreshMatViewStmt(stmt) => {
300            definer.refresh_matview(stmt).map(|matview| filled = matview)
301        }
302        Node::IndexStmt(index) => definer.create_index(index, false),
303        Node::CreateSchemaStmt(schema) => definer.create_schema(schema, text),
304        Node::ViewStmt(view) => definer.create_view(view, text),
305        Node::RuleStmt(rule) => definer.create_rule(rule, text),
306        Node::CreateTrigStmt(trigger) => definer.create_trigger(trigger),
307        Node::CreateDomainStmt(domain) => definer.create_domain(domain).map(|_| ()),
308        Node::CompositeTypeStmt(stmt) => definer.create_composite_type(stmt),
309        Node::CreateEnumStmt(stmt) => definer.create_enum(stmt),
310        Node::AlterEnumStmt(stmt) => definer.alter_enum(stmt),
311        Node::CreateRangeStmt(stmt) => definer.create_range(stmt),
312        Node::CreateFunctionStmt(function) => definer.create_function(function, text).map(|_| ()),
313        Node::AlterFunctionStmt(alter) => definer.alter_function(alter),
314        Node::CommentStmt(comment) => definer.comment(comment),
315        Node::DropStmt(drop) => definer.drop(drop),
316        Node::AlterTableStmt(alter) => definer.alter_table(alter),
317        Node::AlterOwnerStmt(alter) => definer.alter_owner(alter),
318        Node::RenameStmt(rename) => definer.rename(rename),
319        Node::AlterObjectSchemaStmt(alter) if type_change::is_type_kind(alter.objectType) => {
320            definer.alter_type_schema(alter)
321        }
322        Node::AlterObjectSchemaStmt(alter)
323            if alter_function::is_function_kind(alter.objectType) =>
324        {
325            definer.alter_function_schema(alter)
326        }
327        Node::AlterObjectSchemaStmt(alter) if alter.objectType == ObjectType::OBJECT_OPERATOR => {
328            definer.alter_operator_schema(alter)
329        }
330        Node::AlterObjectSchemaStmt(alter) if is_opcf_kind(alter.objectType) => {
331            definer.alter_opcf_schema(alter)
332        }
333        Node::AlterObjectSchemaStmt(alter)
334            if alter.objectType == ObjectType::OBJECT_STATISTIC_EXT =>
335        {
336            definer.alter_stats_schema(alter)
337        }
338        Node::AlterObjectSchemaStmt(alter) => definer.alter_relation_schema(alter),
339        Node::CreateStatsStmt(create) => definer.create_statistics(create),
340        Node::AlterStatsStmt(alter) => definer.alter_statistics(alter),
341        Node::CreateSeqStmt(create) => definer.create_sequence(create),
342        Node::AlterSeqStmt(alter) => definer.alter_sequence(alter),
343        Node::DefineStmt(define) if tsearch::is_ts_kind(define.kind) => definer.define_ts(define),
344        Node::DefineStmt(define) if define.kind == ObjectType::OBJECT_OPERATOR => {
345            definer.create_operator(define)
346        }
347        Node::DefineStmt(define) if define.kind == ObjectType::OBJECT_AGGREGATE => {
348            definer.create_aggregate(define)
349        }
350        Node::AlterOperatorStmt(alter) => definer.alter_operator(alter),
351        Node::CreateOpFamilyStmt(create) => definer.create_opfamily(create),
352        Node::CreateOpClassStmt(create) => definer.create_opclass(create),
353        Node::AlterOpFamilyStmt(alter) => definer.alter_opfamily(alter),
354        Node::AlterTSConfigurationStmt(alter) => definer.alter_ts_config(alter),
355        Node::TruncateStmt(truncate) => definer.truncate(truncate),
356        Node::GrantStmt(grant) => definer.grant(grant),
357        Node::DropOwnedStmt(stmt) => definer.drop_owned(stmt),
358        Node::AlterDefaultPrivilegesStmt(stmt) => definer.alter_default_privileges(stmt),
359        Node::ReassignOwnedStmt(stmt) => definer.reassign_owned(stmt),
360        Node::ReindexStmt(stmt) => definer.reindex(stmt),
361        _ => {
362            Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, "this statement is not supported yet"))
363        }
364    };
365    definer.flush();
366    let catalog = &*definer.catalog;
367    let fills = definer
368        .added
369        .into_iter()
370        .filter(|fill| {
371            let column = catalog.relation(fill.table).and_then(|r| r.column(fill.column));
372            let live = column.is_some_and(|c| !c.dropped);
373            // A stored generated column computes its values again from the expression that it has now.
374            let generated = column.is_some_and(|c| c.generated != 0);
375            let now = catalog.default_of(fill.table, fill.column).map(|d| &d.expr);
376            live && (generated || now != fill.default.as_ref())
377        })
378        .collect();
379    Defined {
380        messages: definer.messages,
381        result,
382        scans: definer.scans,
383        truncated: definer.truncated,
384        transforms: definer.transforms,
385        fills,
386        filled,
387        views: definer.views,
388        checks: definer.checks,
389        detached: definer.detached,
390        grantors: definer.grantors,
391        schema: definer.schema,
392        top_level: definer.top_level,
393        concurrent: definer.concurrent,
394    }
395}
396
397/// The session with the schemas of the catalog, which the analyzer finds by name.
398struct SchemaEnv<'e> {
399    env: &'e dyn Env,
400    schemas: Vec<(String, u32)>,
401    /// The relations that the statement added before the last [`Env::command_counter_increment`], which the session does not have: the schema, the name and the OID.
402    relations: RefCell<Vec<(u32, String, u32)>>,
403}
404
405impl<'e> SchemaEnv<'e> {
406    fn new(env: &'e dyn Env, catalog: &Catalog) -> SchemaEnv<'e> {
407        let schemas = catalog.schemas().map(|s| (s.name.clone(), s.oid)).collect();
408        SchemaEnv { env, schemas, relations: RefCell::new(Vec::new()) }
409    }
410
411    /// The OID of a relation that the statement added, for the input `text` of `regclass`, as `regclassin` finds it after `CommandCounterIncrement`.
412    fn new_class(&self, text: &str) -> Option<u32> {
413        let names = rupg_types::qualified_name_list(text).ok()?;
414        let (spaces, name) = match names.as_slice() {
415            [name] => (self.search_path(), name),
416            [schema, name] => (vec![self.namespace(schema)?], name),
417            _ => return None,
418        };
419        let relations = self.relations.borrow();
420        spaces.iter().find_map(|&ns| {
421            relations.iter().find(|(n, rel, _)| *n == ns && rel == name).map(|(_, _, oid)| *oid)
422        })
423    }
424}
425
426impl Env for SchemaEnv<'_> {
427    fn search_path(&self) -> Vec<u32> {
428        self.env.search_path()
429    }
430
431    fn namespace(&self, name: &str) -> Option<u32> {
432        self.schemas
433            .iter()
434            .find(|(n, _)| n == name)
435            .map(|(_, oid)| *oid)
436            .or_else(|| self.env.namespace(name))
437    }
438
439    fn database(&self) -> String {
440        self.env.database()
441    }
442
443    fn input(&self, ty: u32, text: &str, typmod: i32) -> Result<Value> {
444        match self.env.input(ty, text, typmod) {
445            Err(error) if ty == oid::REGCLASS && error.state() == SqlState::UNDEFINED_TABLE => {
446                self.new_class(text).map(Value::Oid).ok_or(error)
447            }
448            value => value,
449        }
450    }
451
452    fn catalog(&self) -> Option<&Catalog> {
453        self.env.catalog()
454    }
455
456    fn allow_system_table_mods(&self) -> bool {
457        self.env.allow_system_table_mods()
458    }
459
460    fn check_function_bodies(&self) -> bool {
461        self.env.check_function_bodies()
462    }
463
464    fn sequence_row(&self, file: u32) -> Option<rupg_catalog::SequenceData> {
465        self.env.sequence_row(file)
466    }
467
468    fn deparse(&self, text: &str, relation: u32) -> Result<String> {
469        self.env.deparse(text, relation)
470    }
471
472    fn temp_number(&self) -> Option<i32> {
473        self.env.temp_number()
474    }
475
476    fn check_function(&self, function: &rupg_catalog::Function) -> Result<()> {
477        self.env.check_function(function)
478    }
479
480    fn function_setting(
481        &self,
482        set: &rupg_sql::nodes::VariableSetStmt,
483    ) -> Result<(String, Option<String>)> {
484        self.env.function_setting(set)
485    }
486
487    fn push_function_settings(&self, config: &[String]) -> Result<()> {
488        self.env.push_function_settings(config)
489    }
490
491    fn pop_function_settings(&self) {
492        self.env.pop_function_settings();
493    }
494
495    fn command_counter_increment(&self, catalog: &Catalog) {
496        let session = self.env.catalog();
497        *self.relations.borrow_mut() = catalog
498            .relations()
499            .filter(|r| session.is_none_or(|s| s.relation(r.oid).is_none()))
500            .map(|r| (r.namespace, r.name.clone(), r.oid))
501            .collect();
502        self.env.command_counter_increment(catalog);
503    }
504
505    fn evaluate(&self, expr: &Expr) -> Result<Value> {
506        self.env.evaluate(expr)
507    }
508
509    fn compare(&self, ty: u32, a: &Value, b: &Value) -> Result<std::cmp::Ordering> {
510        self.env.compare(ty, a, b)
511    }
512
513    fn deparse_constant(&self, text: &str) -> Result<String> {
514        self.env.deparse_constant(text)
515    }
516}
517
518/// A relation that a name gives.
519#[derive(Clone, Copy, Debug)]
520enum Found {
521    /// A relation of the catalog, with its OID.
522    User(u32),
523    /// A relation of the built-in catalog.
524    Builtin(&'static ClassRow),
525}
526
527/// The state of one statement that defines an object.
528pub(crate) struct Definer<'a, 'c> {
529    an: Analyzer<'a>,
530    catalog: &'c mut Catalog,
531    user: u32,
532    messages: Vec<Message>,
533    /// The tables of [`Defined::scans`].
534    scans: Vec<u32>,
535    /// The tables of [`Defined::truncated`].
536    truncated: Vec<u32>,
537    /// The expressions of [`Defined::transforms`].
538    transforms: Vec<Transform>,
539    /// The defaults of the new columns, from which [`Defined::fills`] keeps the changed ones.
540    added: Vec<Fill>,
541    /// The views of [`Defined::views`].
542    views: Vec<u32>,
543    /// The partitions of [`Defined::checks`].
544    checks: Vec<PartitionCheck>,
545    /// The partitions of [`Defined::detached`].
546    detached: Vec<u32>,
547    /// The roles of [`Defined::grantors`].
548    grantors: Vec<u32>,
549    /// The elements of [`Defined::schema`].
550    schema: Option<SchemaElements>,
551    /// The check of [`Defined::top_level`].
552    top_level: Option<TopLevel>,
553    /// The index of [`Defined::concurrent`].
554    concurrent: Option<u32>,
555}
556
557/// An error for a part of a statement that rupg does not support yet.
558fn not_yet(what: &str, at: Option<usize>) -> Error {
559    Error::new(SqlState::FEATURE_NOT_SUPPORTED, format!("{what} is not supported yet")).at_opt(at)
560}
561
562/// The name of a relation as the query wrote it, with the schema and the database.
563fn qualified(rv: &RangeVar) -> String {
564    [rv.catalogname.as_deref(), rv.schemaname.as_deref(), rv.relname.as_deref()]
565        .into_iter()
566        .flatten()
567        .collect::<Vec<_>>()
568        .join(".")
569}
570
571/// The name of the relation of a `RangeVar`.
572fn relname(rv: &RangeVar) -> &str {
573    rv.relname.as_deref().unwrap_or_default()
574}
575
576/// `contain_mutable_functions_after_planning`: true when the expression calls a function that is not immutable. The planner inlines `textanycat` and `anytextcat`, so a call of them is mutable only when the output function of the type of the other argument is.
577fn is_mutable(catalog: &Catalog, expr: &Expr) -> bool {
578    let mutable_proc = |oid: u32| match builtin::proc_by_oid(oid) {
579        Some(proc) => proc.volatile != b'i',
580        None => catalog.function(oid).is_none_or(|f| f.volatile != b'i'),
581    };
582    expr.find(0, &mut |e, _| {
583        let mutable = match &e.kind {
584            // `textanycat` and `anytextcat`: `$1 || $2::text` and `$1::text || $2`.
585            ExprKind::Func(f) if matches!(f.oid, 2003 | 2004) => {
586                let other = f.args.get(if f.oid == 2003 { 1 } else { 0 });
587                other.is_none_or(|arg| {
588                    mutable_proc(builtin::type_by_oid(arg.ty).map_or(0, |t| t.output))
589                })
590            }
591            ExprKind::Func(f) => mutable_proc(f.oid),
592            ExprKind::NullIf { equal, .. } | ExprKind::Distinct { equal, .. } => {
593                mutable_proc(*equal)
594            }
595            ExprKind::ScalarArrayOp { func, .. } => mutable_proc(*func),
596            ExprKind::CoerceViaIo(arg, _) => {
597                let output = builtin::type_by_oid(arg.ty).map_or(0, |t| t.output);
598                let input = builtin::type_by_oid(e.ty).map_or(0, |t| t.input);
599                mutable_proc(output) || mutable_proc(input)
600            }
601            ExprKind::SqlValue(_) => true,
602            _ => false,
603        };
604        mutable.then_some(())
605    })
606    .is_some()
607}
608
609/// The attribute numbers of the columns of the relation that the expressions read, in the order of their first use, without duplicates.
610fn column_numbers(exprs: &[&Expr]) -> Vec<i16> {
611    let mut numbers = Vec::new();
612    for expr in exprs {
613        expr.find(0, &mut |e, depth| {
614            if let ExprKind::Var(var) = e.kind
615                && var.levels_up == depth
616                && !numbers.contains(&var.attnum)
617            {
618                numbers.push(var.attnum);
619            }
620            None::<()>
621        });
622    }
623    numbers
624}
625
626/// `recordDependencyOnSingleRelExpr`: the objects that the expressions refer to. These are the columns of the relation, the functions that the expressions call, the relations and types of `regclass` and `regtype` constants, and the fields of composite types that the expressions select. The catalog drops the references to pinned objects. A `regrole` or `regdatabase` constant is an error, because a role and a database are shared objects.
627fn references(catalog: &Catalog, relation: u32, exprs: &[&Expr]) -> Result<Vec<ObjRef>> {
628    shared_constants(exprs)?;
629    let mut refs: Vec<ObjRef> =
630        column_numbers(exprs).into_iter().map(|n| ObjRef::column(relation, n)).collect();
631    for expr in exprs {
632        expr.find(0, &mut |e, _| {
633            let found = match (&e.kind, e.ty) {
634                (ExprKind::Const(Value::Oid(oid)), oid::REGCLASS) => {
635                    Some(ObjRef::new(PG_CLASS, *oid))
636                }
637                (ExprKind::Const(Value::Oid(oid)), oid::REGTYPE) => {
638                    Some(ObjRef::new(PG_TYPE, *oid))
639                }
640                (ExprKind::Func(f), _) => Some(ObjRef::new(PG_PROC, f.oid)),
641                _ => field_reference(catalog, e),
642            };
643            for found in found.into_iter().chain(type_reference(e)) {
644                if !refs.contains(&found) {
645                    refs.push(found);
646                }
647            }
648            None::<()>
649        });
650    }
651    Ok(refs)
652}
653
654/// The error of `find_expr_references_walker` for a `regrole` or `regdatabase` constant in the expressions, because a role and a database are shared objects.
655fn shared_constants(exprs: &[&Expr]) -> Result<()> {
656    for expr in exprs {
657        let shared = expr.find(0, &mut |e, _| match (&e.kind, e.ty) {
658            (ExprKind::Const(Value::Oid(_)), oid::REGROLE) => Some("regrole"),
659            (ExprKind::Const(Value::Oid(_)), oid::REGDATABASE) => Some("regdatabase"),
660            _ => None,
661        });
662        if let Some(name) = shared {
663            return Err(Error::new(
664                SqlState::FEATURE_NOT_SUPPORTED,
665                format!("constant of the type {name} cannot be used here"),
666            ));
667        }
668    }
669    Ok(())
670}
671
672/// The column that `find_expr_references_walker` records for a `FieldSelect` of a composite type of the catalog, also through a domain: the column of the relation of the type that has the field.
673pub(crate) fn field_reference(catalog: &Catalog, expr: &Expr) -> Option<ObjRef> {
674    let ExprKind::FieldSelect(arg, field) = &expr.kind else { return None };
675    let rel = row_type::row_relation(catalog, arg.ty)?;
676    let (at, _) = rel.columns.iter().enumerate().filter(|(_, c)| !c.dropped).nth(*field)?;
677    Some(ObjRef::column(rel.oid, i16::try_from(at + 1).ok()?))
678}
679
680/// The type that `find_expr_references_walker` records for a constant, a cast or the `Param` of a column of a subquery, which is the type of its result. The catalog drops the references to the built-in types, so only a type such as a domain of `initdb` stays.
681pub(crate) fn type_reference(expr: &Expr) -> Option<ObjRef> {
682    match expr.kind {
683        ExprKind::Const(_)
684        | ExprKind::Relabel(..)
685        | ExprKind::CoerceViaIo(..)
686        | ExprKind::ArrayCoerce { .. }
687        | ExprKind::CoerceToDomain(..)
688        | ExprKind::SubColumn(_) => Some(ObjRef::new(PG_TYPE, expr.ty)),
689        _ => None,
690    }
691}
692
693impl Definer<'_, '_> {
694    /// Moves the warnings and the notices of the analyzer to the messages.
695    fn flush(&mut self) {
696        self.messages.extend(self.an.notices.drain(..).map(|error| {
697            if error.is_notice() { Message::Notice(error) } else { Message::Warning(error) }
698        }));
699    }
700
701    /// Adds a `NOTICE` after the warnings that came before it.
702    fn notice(&mut self, state: SqlState, message: String) {
703        self.flush();
704        self.messages.push(Message::Notice(Error::new(state, message)));
705    }
706
707    /// Adds a `WARNING` after the messages that came before it.
708    fn warning(&mut self, state: SqlState, message: String) {
709        self.flush();
710        self.messages.push(Message::Warning(Error::new(state, message)));
711    }
712
713    /// True when the role of the statement is a superuser.
714    fn is_superuser(&self) -> bool {
715        rupg_pgcatalog::roles::current().superuser(self.user)
716    }
717
718    /// The built-in schema with the name, unless `DROP SCHEMA` removed it.
719    fn builtin_schema(&self, name: &str) -> Option<&'static NamedRow> {
720        builtin::named(Named::Namespace)
721            .iter()
722            .find(|n| n.name == name && !self.catalog.schema_dropped(n.oid))
723    }
724
725    /// The owner of a schema, or `None` for a schema that does not exist.
726    fn schema_owner(&self, namespace: u32) -> Option<u32> {
727        match self.catalog.schema(namespace) {
728            Some(schema) => Some(schema.owner),
729            None if self.catalog.schema_dropped(namespace) => None,
730            None => builtin::owned_by_oid(Owned::Namespace, namespace).map(|n| n.owner),
731        }
732    }
733
734    /// The name of a schema.
735    fn schema_name(&self, namespace: u32) -> String {
736        match self.catalog.schema(namespace) {
737            Some(schema) => schema.name.clone(),
738            None => builtin::named_by_oid(Named::Namespace, namespace)
739                .filter(|n| !self.catalog.schema_dropped(n.oid))
740                .map_or_else(String::new, |n| n.name.to_string()),
741        }
742    }
743
744    /// `object_aclcheck` of `CREATE` on a schema for a role: a superuser, the owner of the schema and a role that `nspacl` grants `CREATE` can create objects in it.
745    fn may_create_in(&self, namespace: u32, role: u32) -> bool {
746        rupg_pgcatalog::roles::current().superuser(role)
747            || self.schema_owner(namespace) == Some(role)
748            || self.schema_create_allowed(namespace, role)
749    }
750
751    /// The check of `CREATE` on a schema for the role of the statement.
752    fn check_create_in(&self, namespace: u32, at: Option<usize>) -> Result<()> {
753        if self.may_create_in(namespace, self.user) {
754            return Ok(());
755        }
756        Err(Error::new(
757            SqlState::INSUFFICIENT_PRIVILEGE,
758            format!("permission denied for schema {}", self.schema_name(namespace)),
759        )
760        .at_opt(at))
761    }
762
763    /// `RangeVarGetAndCheckCreationNamespace`: the schema where a new relation goes. A temporary relation and a relation in `pg_temp` go to the temporary schema of the session, and `RangeVarAdjustRelationPersistence` checks the persistence of the relation against its schema.
764    fn creation_namespace(&mut self, rv: &RangeVar) -> Result<u32> {
765        let at = place(rv.location);
766        if let Some(catalog) = rv.catalogname.as_deref()
767            && catalog != self.an.env.database()
768        {
769            return Err(Error::new(
770                SqlState::FEATURE_NOT_SUPPORTED,
771                format!("cross-database references are not implemented: \"{}\"", qualified(rv)),
772            )
773            .at_opt(at));
774        }
775        let unlogged = rv.relpersistence == b'u';
776        // `RangeVarAdjustRelationPersistence`: an unlogged relation cannot go to a temporary schema.
777        let only_temporary = || {
778            Error::new(
779                SqlState::INVALID_TABLE_DEFINITION,
780                "only temporary relations may be created in temporary schemas",
781            )
782            .at_opt(at)
783        };
784        let temp = rv.relpersistence == b't';
785        if rv.schemaname.as_deref() == Some("pg_temp") || (temp && rv.schemaname.is_none()) {
786            let namespace = self.temp_namespace()?;
787            return if unlogged { Err(only_temporary()) } else { Ok(namespace) };
788        }
789        let namespace = match rv.schemaname.as_deref() {
790            Some(schema) => self.an.schema(schema).ok_or_else(|| {
791                Error::new(
792                    SqlState::UNDEFINED_SCHEMA,
793                    format!("schema \"{schema}\" does not exist"),
794                )
795                .at_opt(at)
796            })?,
797            None => self.an.env.search_path().first().copied().ok_or_else(|| {
798                Error::new(SqlState::UNDEFINED_SCHEMA, "no schema has been selected to create in")
799                    .at_opt(at)
800            })?,
801        };
802        if unlogged && self.catalog.is_temp_schema(namespace) {
803            return Err(only_temporary());
804        }
805        let own = namespace == self.an.temp || self.is_own_toast_temp(namespace);
806        if !own && self.catalog.is_temp_schema(namespace) {
807            return Err(Error::new(
808                SqlState::INVALID_TABLE_DEFINITION,
809                "cannot create relations in temporary schemas of other sessions",
810            ));
811        }
812        if temp && !own {
813            return Err(Error::new(
814                SqlState::INVALID_TABLE_DEFINITION,
815                "cannot create temporary relation in non-temporary schema",
816            )
817            .at_opt(at));
818        }
819        if !own {
820            self.check_create_in(namespace, at)?;
821        }
822        Ok(namespace)
823    }
824
825    /// True for the TOAST schema `pg_toast_temp_N` of the temporary schema of the session.
826    fn is_own_toast_temp(&self, namespace: u32) -> bool {
827        let Some(n) = self.an.env.temp_number() else { return false };
828        self.catalog.schema(namespace).is_some_and(|s| s.name == format!("pg_toast_temp_{n}"))
829    }
830
831    /// `AccessTempTableNamespace` and `InitTempTableNamespace`: the temporary schema `pg_temp_N` of the session. The first temporary object of the session makes it and its TOAST schema `pg_toast_temp_N`, which the bootstrap superuser owns.
832    fn temp_namespace(&mut self) -> Result<u32> {
833        if self.an.temp != 0 {
834            return Ok(self.an.temp);
835        }
836        let Some(n) = self.an.env.temp_number() else {
837            return Err(not_yet("a temporary object in a session with no server", None));
838        };
839        let name = format!("pg_temp_{n}");
840        let oid = match self.catalog.schema_by_name(&name) {
841            Some(schema) => schema.oid,
842            None => {
843                let oid = self.catalog.create_schema(&name, BOOTSTRAP_SUPERUSER)?;
844                self.catalog.create_schema(&format!("pg_toast_temp_{n}"), BOOTSTRAP_SUPERUSER)?;
845                oid
846            }
847        };
848        self.an.temp = oid;
849        self.an.path.retain(|&ns| ns != oid);
850        if !self.an.temp_path.contains(&oid) {
851            self.an.temp_path.insert(0, oid);
852        }
853        Ok(oid)
854    }
855
856    /// A relation with this name in the schema, of the catalog or built in.
857    fn relation_in(&self, namespace: u32, name: &str) -> Option<Found> {
858        if let Some(rel) = self.catalog.relation_by_name(namespace, name) {
859            return Some(Found::User(rel.oid));
860        }
861        builtin::named(Named::Class)
862            .iter()
863            .find(|r| r.namespace == namespace && r.name == name)
864            .and_then(|r| builtin::class_by_oid(r.oid))
865            .map(Found::Builtin)
866    }
867
868    /// `RangeVarGetRelid`: the relation that a name gives. The errors have no position, as in PostgreSQL.
869    fn lookup_relation(&self, rv: &RangeVar) -> Result<Found> {
870        let name = relname(rv);
871        if let Some(catalog) = rv.catalogname.as_deref()
872            && catalog != self.an.env.database()
873        {
874            return Err(Error::new(
875                SqlState::FEATURE_NOT_SUPPORTED,
876                format!("cross-database references are not implemented: \"{}\"", qualified(rv)),
877            ));
878        }
879        let found = match rv.schemaname.as_deref() {
880            Some(schema) => {
881                let namespace = self.an.schema(schema).ok_or_else(|| {
882                    Error::new(
883                        SqlState::UNDEFINED_SCHEMA,
884                        format!("schema \"{schema}\" does not exist"),
885                    )
886                })?;
887                self.relation_in(namespace, name)
888            }
889            None => self.an.temp_path.iter().find_map(|&ns| self.relation_in(ns, name)),
890        };
891        found.ok_or_else(|| {
892            let shown = match rv.schemaname.as_deref() {
893                Some(schema) => format!("{schema}.{name}"),
894                None => name.to_string(),
895            };
896            Error::new(SqlState::UNDEFINED_TABLE, format!("relation \"{shown}\" does not exist"))
897        })
898    }
899
900    /// `IsSystemRelation`: true for a relation of the catalog and for a TOAST table or its index.
901    fn is_system(&self, found: Found) -> bool {
902        match found {
903            Found::Builtin(row) => row.oid < rupg_catalog::FIRST_UNPINNED_OID,
904            Found::User(oid) => {
905                self.catalog.relation(oid).is_some_and(|r| r.namespace == PG_TOAST_NAMESPACE)
906            }
907        }
908    }
909
910    /// The kind of a relation as a `relkind` code.
911    fn kind_of(&self, found: Found) -> u8 {
912        match found {
913            Found::User(oid) => match self.catalog.relation(oid).map(|r| r.kind) {
914                Some(RelKind::Index) => b'i',
915                Some(RelKind::Sequence) => b'S',
916                Some(RelKind::Toast) => b't',
917                Some(RelKind::View) => b'v',
918                Some(RelKind::Composite) => b'c',
919                Some(RelKind::Partitioned) => b'p',
920                Some(RelKind::PartitionedIndex) => b'I',
921                Some(RelKind::MatView) => b'm',
922                _ => b'r',
923            },
924            Found::Builtin(row) => row.kind,
925        }
926    }
927
928    /// `CreateSchemaCommand`. The elements of the statement go to [`Defined::schema`] with their parts of `text`, and the caller runs them.
929    fn create_schema(&mut self, stmt: &CreateSchemaStmt, text: &str) -> Result<()> {
930        let owner = match &stmt.authrole {
931            None => self.user,
932            Some(role) => self.role_oid(role)?,
933        };
934        let roles = rupg_pgcatalog::roles::current();
935        let owner_name = roles.name(owner);
936        let name = match stmt.schemaname.as_deref() {
937            Some(name) => name.to_string(),
938            None => owner_name.unwrap_or_default().to_string(),
939        };
940        if !self.is_superuser() {
941            return Err(Error::new(
942                SqlState::INSUFFICIENT_PRIVILEGE,
943                format!("permission denied for database {}", self.an.env.database()),
944            ));
945        }
946        if name.starts_with("pg_") {
947            return Err(Error::new(
948                SqlState::RESERVED_NAME,
949                format!("unacceptable schema name \"{name}\""),
950            )
951            .with_detail("The prefix \"pg_\" is reserved for system schemas."));
952        }
953        let exists =
954            self.catalog.schema_by_name(&name).is_some() || self.builtin_schema(&name).is_some();
955        if exists && stmt.if_not_exists {
956            self.notice(
957                SqlState::DUPLICATE_SCHEMA,
958                format!("schema \"{name}\" already exists, skipping"),
959            );
960            return Ok(());
961        }
962        if exists {
963            return Err(Error::new(
964                SqlState::DUPLICATE_SCHEMA,
965                format!("schema \"{name}\" already exists"),
966            ));
967        }
968        let schema = self.catalog.create_schema(&name, owner)?;
969        self.apply_default_acl(ObjRef::new(PG_NAMESPACE, schema))?;
970        if !stmt.schemaElts.is_empty() {
971            let elements: Vec<Node> = stmt.schemaElts.iter().flatten().cloned().collect();
972            let elements = schema_elements::schema_elements(&elements, &name, text)?;
973            self.schema = Some(SchemaElements { owner, schema: name, elements });
974        }
975        Ok(())
976    }
977}
978
979/// The detail of `errdetail_relkind_not_supported`.
980fn relkind_detail(kind: u8) -> &'static str {
981    match kind {
982        b'i' => "This operation is not supported for indexes.",
983        b'I' => "This operation is not supported for partitioned indexes.",
984        b'p' => "This operation is not supported for partitioned tables.",
985        b'S' => "This operation is not supported for sequences.",
986        b'v' => "This operation is not supported for views.",
987        b'm' => "This operation is not supported for materialized views.",
988        b'c' => "This operation is not supported for composite types.",
989        b'f' => "This operation is not supported for foreign tables.",
990        b't' => "This operation is not supported for TOAST tables.",
991        _ => "This operation is not supported for tables.",
992    }
993}
994
995/// The OID of the schema `pg_catalog` or `pg_toast`, where only the bootstrap can create relations.
996fn is_system_namespace(namespace: u32) -> bool {
997    namespace == PG_CATALOG_NAMESPACE || namespace == PG_TOAST_NAMESPACE
998}