From 085e213fe661a85452cc1f466db69225b8fefcfa Mon Sep 17 00:00:00 2001 From: Tam Nguyen Duc <1218621+tamnd@users.noreply.github.com> Date: Tue, 20 Oct 2026 12:52:10 +0700 Subject: [PATCH] Run ALTER DATABASE SET and apply the stored settings at the start of a session --- crates/rupg-exec/src/user.rs | 16 +++++ crates/rupg-func/src/lib.rs | 4 ++ crates/rupg-server/src/lib.rs | 11 ++-- crates/rupg-session/src/connection.rs | 65 ++++++++++++++++--- .../src/connection/alter_database_set.test | 53 +++++++++++++++ .../rupg-session/src/connection/databases.rs | 56 +++++++++++++++- .../rupg-session/src/connection/extended.rs | 2 + crates/rupg-session/src/guc/settings.rs | 44 +++++++++++++ crates/rupg-session/src/query.rs | 7 ++ crates/rupg-session/src/store.rs | 42 +++++++++++- crates/rupg-session/src/utility.rs | 3 +- 11 files changed, 283 insertions(+), 20 deletions(-) create mode 100644 crates/rupg-session/src/connection/alter_database_set.test diff --git a/crates/rupg-exec/src/user.rs b/crates/rupg-exec/src/user.rs index 8d1eaff9..d224c790 100644 --- a/crates/rupg-exec/src/user.rs +++ b/crates/rupg-exec/src/user.rs @@ -146,6 +146,7 @@ pub(crate) fn rows( "pg_rewrite" => rewrites(table, catalog), "pg_init_privs" => init_privs(table, catalog, session)?, "pg_database" => databases(table, session), + "pg_db_role_setting" => db_role_settings(table, session), "pg_ts_template" => ts_templates(table, catalog), "pg_ts_parser" => ts_parsers(table, catalog), "pg_ts_dict" => ts_dictionaries(table, catalog), @@ -271,6 +272,21 @@ fn databases<'a>(table: &'a Table, session: &dyn Session) -> Vec> { .collect() } +/// The rows of `pg_db_role_setting`: the settings of `ALTER DATABASE ... SET`. +fn db_role_settings<'a>(table: &'a Table, session: &dyn Session) -> Vec> { + session + .db_role_settings() + .into_iter() + .map(|(database, role, settings)| { + let mut row = Row::new(table); + row.set("setdatabase", oid(database)) + .set("setrole", oid(role)) + .set("setconfig", text_array(&settings)); + row + }) + .collect() +} + fn namespaces<'a>(table: &'a Table, catalog: &Catalog) -> Vec> { catalog .schemas() diff --git a/crates/rupg-func/src/lib.rs b/crates/rupg-func/src/lib.rs index 35f00c44..d94b02d4 100644 --- a/crates/rupg-func/src/lib.rs +++ b/crates/rupg-func/src/lib.rs @@ -302,6 +302,10 @@ pub trait Session { fn databases(&self) -> Arc<[rupg_catalog::Database]> { Arc::from(Vec::new()) } + /// The rows of `pg_db_role_setting`: the OID of the database, the OID of the role, and the settings, each as `name=value`. A session with no server has no rows. + fn db_role_settings(&self) -> Vec<(u32, u32, Vec)> { + Vec::new() + } /// The rows of the table of the sessions in the order of their numbers, from the snapshot of the transaction. The first call of a transaction takes the snapshot. fn backends(&self) -> Arc<[Backend]> { Arc::from(Vec::new()) diff --git a/crates/rupg-server/src/lib.rs b/crates/rupg-server/src/lib.rs index 2a0c67f3..9ba4f885 100644 --- a/crates/rupg-server/src/lib.rs +++ b/crates/rupg-server/src/lib.rs @@ -833,15 +833,15 @@ fn start_tls(shared: &Shared, tls: &Tls, wire: &mut Wire, early: &[u8], direct: } } -/// The checks of `InitPostgres` after the authentication: the role and the database. -fn admit(shared: &Shared, start: &Start) -> std::result::Result<(), Error> { +/// The checks of `InitPostgres` after the authentication: the role and the database. It gives the OID of the database. +fn admit(shared: &Shared, start: &Start) -> std::result::Result { if start.user != shared.roles.superuser { return Err(Error::new( SqlState::INVALID_AUTHORIZATION_SPECIFICATION, format!("role \"{}\" does not exist", start.user), )); } - shared.store.admit(&start.database).map(drop) + shared.store.admit(&start.database) } /// A session in the cancel registry. @@ -949,8 +949,9 @@ fn session(shared: &Shared, wire: &mut Wire, start: &Start, protocol: u32) { return; }; wire.out.authentication_ok(); - let settings = - admit(shared, start).and_then(|()| connection::session_settings(&shared.base, start)); + let settings = admit(shared, start).and_then(|database| { + connection::session_settings(&shared.base, start, &shared.store.database_settings(database)) + }); let mut settings = match settings { Ok(settings) => settings, Err(error) => { diff --git a/crates/rupg-session/src/connection.rs b/crates/rupg-session/src/connection.rs index 7b8bc7fb..5dba4995 100644 --- a/crates/rupg-session/src/connection.rs +++ b/crates/rupg-session/src/connection.rs @@ -246,16 +246,26 @@ pub fn split_options(options: &str) -> Result, Error> { Ok(settings) } -/// The settings of a new session: the base settings, the values that the server sets for the session, then the `options` of the startup packet and its other parameters, as `process_startup_options` applies them. +/// The settings of a new session: the base settings, the values that the server sets for the session, the settings of `ALTER DATABASE ... SET` in `database`, then the `options` of the startup packet and its other parameters, as `process_startup_options` applies them. /// /// # Errors /// /// A bad `options` parameter and a setting that is not valid. The server sends them as `FATAL`. -pub fn session_settings(base: &Settings, start: &Start) -> Result { +pub fn session_settings( + base: &Settings, + start: &Start, + database: &[String], +) -> Result { let mut settings = base.clone(); settings.set_superuser(start.superuser); settings.set_internal("is_superuser", if start.superuser { "on" } else { "off" })?; settings.set_internal("session_authorization", &start.user)?; + // `process_settings`: a stored setting that is no longer valid gives a warning in PostgreSQL, and the session starts without it. + for item in database { + if let Some((name, value)) = item.split_once('=') { + drop(settings.set_database(name, value)); + } + } let options = match &start.options { Some(options) => split_options(options)?, None => Vec::new(), @@ -1273,6 +1283,9 @@ impl Connection { } else if let Node::DropdbStmt(stmt) = node { drop(self.portals.close(b"")); self.drop_database(stmt, &mut notices) + } else if let Node::AlterDatabaseSetStmt(stmt) = node { + drop(self.portals.close(b"")); + self.alter_database_set(stmt) } else if let Node::DeclareCursorStmt(stmt) = node { drop(self.portals.close(b"")); self.declare(stmt, text, None, &[], &mut notices) @@ -1587,7 +1600,7 @@ mod tests { /// A session with the process ID `pid` on the store of a server. fn connect_to(store: &Arc, pid: i32) -> (Connection, Vec) { let start = start(None, &[("application_name", "test")]); - let settings = session_settings(&base_settings(&[]).unwrap(), &start).unwrap(); + let settings = session_settings(&base_settings(&[]).unwrap(), &start, &[]).unwrap(); let mut connection = Connection::new(&start, settings, PROTOCOL_3_0); connection.set_store(Arc::clone(store)); // The oracle runs the scripts on a Unix socket. @@ -3465,10 +3478,44 @@ mod tests { big_stack(|| script(include_str!("connection/create_database.test"))); } - /// A session of a database other than `postgres`. + /// `ALTER DATABASE ... SET`: the checks of the names and the values, and the rows of `pg_db_role_setting`. `connection/alter_database_set.test` is the output of PostgreSQL 19 for the same script in a new database. + #[test] + fn alter_database_set() { + big_stack(|| script(include_str!("connection/alter_database_set.test"))); + } + + /// A new session of a database takes the settings of `ALTER DATABASE ... SET` with the source `database`, and `RESET` goes back to them. A session of another database does not take them. + #[test] + fn database_settings() { + big_stack(|| { + let store = Arc::new(Store::new()); + let (mut a, _) = connect_to(&store, 1234); + for sql in [ + "CREATE DATABASE x", + "ALTER DATABASE x SET work_mem TO '16MB'", + "ALTER DATABASE x SET my.thing TO 'v'", + ] { + assert!(!line_of(&mut a, sql, "").starts_with("ERROR"), "{sql}"); + } + assert_eq!(line_of(&mut a, "SHOW work_mem", "row"), "row 4MB"); + let mut b = connect_database(&store, 1235, "x"); + assert_eq!(line_of(&mut b, "SHOW work_mem", "row"), "row 16MB"); + assert_eq!(line_of(&mut b, "SHOW my.thing", "row"), "row v"); + let source = "SELECT source FROM pg_settings WHERE name = 'work_mem'"; + assert_eq!(line_of(&mut b, source, "row"), "row database"); + line_of(&mut b, "SET work_mem TO '1MB'", "SET"); + line_of(&mut b, "RESET work_mem", "RESET"); + assert_eq!(line_of(&mut b, "SHOW work_mem", "row"), "row 16MB"); + let mut c = connect_database(&store, 1236, "postgres"); + assert_eq!(line_of(&mut c, "SHOW work_mem", "row"), "row 4MB"); + }); + } + + /// A session of a database other than `postgres`, with the settings of `ALTER DATABASE ... SET` for the database. fn connect_database(store: &Arc, pid: i32, database: &str) -> Connection { let start = Start { database: database.into(), ..start(None, &[]) }; - let settings = session_settings(&base_settings(&[]).unwrap(), &start).unwrap(); + let stored = store.database_settings(store.admit(database).unwrap()); + let settings = session_settings(&base_settings(&[]).unwrap(), &start, &stored).unwrap(); let mut connection = Connection::new(&start, settings, PROTOCOL_3_0); connection.set_store(Arc::clone(store)); let mut out = OutBuf::new(); @@ -4591,12 +4638,12 @@ mod tests { "invalid command-line argument for server process: -x" ); let base = base_settings(&[]).unwrap(); - let settings = session_settings(&base, &start(Some("-c work_mem=16MB"), &[])).unwrap(); + let settings = session_settings(&base, &start(Some("-c work_mem=16MB"), &[]), &[]).unwrap(); assert_eq!(settings.get("work_mem").as_deref(), Some("16MB")); - let latin1 = session_settings(&base, &start(None, &[("client_encoding", "LATIN1")])); + let latin1 = session_settings(&base, &start(None, &[("client_encoding", "LATIN1")]), &[]); assert_eq!(latin1.unwrap_err().state(), SqlState::FEATURE_NOT_SUPPORTED); - let ascii = - session_settings(&base, &start(None, &[("client_encoding", "sql_ascii")])).unwrap(); + let ascii = session_settings(&base, &start(None, &[("client_encoding", "sql_ascii")]), &[]) + .unwrap(); assert_eq!(ascii.get("client_encoding").as_deref(), Some("SQL_ASCII")); } diff --git a/crates/rupg-session/src/connection/alter_database_set.test b/crates/rupg-session/src/connection/alter_database_set.test new file mode 100644 index 00000000..fb3c738c --- /dev/null +++ b/crates/rupg-session/src/connection/alter_database_set.test @@ -0,0 +1,53 @@ +!details +> SET TimeZone = 'UTC' +SET +> CREATE DATABASE rupg_ads_scratch +CREATE DATABASE +> ALTER DATABASE rupg_ads_nosuch SET work_mem = '1MB' +ERROR 3D000 database "rupg_ads_nosuch" does not exist +> ALTER DATABASE rupg_ads_scratch SET nosuch = 1 +ERROR 42704 unrecognized configuration parameter "nosuch" +> ALTER DATABASE rupg_ads_scratch SET shared_buffers = '1MB' +ERROR 55P02 parameter "shared_buffers" cannot be changed without restarting the server +> ALTER DATABASE rupg_ads_scratch SET work_mem = 'abc' +ERROR 22023 invalid value for parameter "work_mem": "abc" +> ALTER DATABASE rupg_ads_scratch SET log_connections = on +ERROR 55P02 parameter "log_connections" cannot be set after connection start +> ALTER DATABASE rupg_ads_scratch SET archive_command = 'x' +ERROR 55P02 parameter "archive_command" cannot be changed now +> ALTER DATABASE rupg_ads_scratch SET my.thing = 'x' +ALTER DATABASE +> ALTER DATABASE rupg_ads_scratch RESET my.other +ALTER DATABASE +> ALTER DATABASE rupg_ads_scratch RESET nosuch +ERROR 42704 unrecognized configuration parameter "nosuch" +> ALTER DATABASE rupg_ads_scratch SET WORK_MEM TO '16MB' +ALTER DATABASE +> ALTER DATABASE rupg_ads_scratch SET lc_messages TO 'C' +ALTER DATABASE +> ALTER DATABASE rupg_ads_scratch SET search_path TO a, "B" +ALTER DATABASE +> ALTER DATABASE rupg_ads_scratch SET work_mem TO '32MB' +ALTER DATABASE +> ALTER DATABASE rupg_ads_scratch SET DateStyle FROM CURRENT +ALTER DATABASE +> SELECT setconfig FROM pg_db_role_setting s JOIN pg_database d ON d.oid = s.setdatabase WHERE d.datname = 'rupg_ads_scratch' +fields setconfig@pg_db_role_setting.3:1009:-1:-1 +row {my.thing=x,work_mem=32MB,lc_messages=C,"search_path=a, \"B\"","DateStyle=ISO, MDY"} +SELECT 1 +> ALTER DATABASE rupg_ads_scratch RESET work_mem +ALTER DATABASE +> ALTER DATABASE rupg_ads_scratch SET lc_messages TO DEFAULT +ALTER DATABASE +> SELECT setconfig FROM pg_db_role_setting s JOIN pg_database d ON d.oid = s.setdatabase WHERE d.datname = 'rupg_ads_scratch' +fields setconfig@pg_db_role_setting.3:1009:-1:-1 +row {my.thing=x,"search_path=a, \"B\"","DateStyle=ISO, MDY"} +SELECT 1 +> ALTER DATABASE rupg_ads_scratch RESET ALL +ALTER DATABASE +> SELECT count(*) FROM pg_db_role_setting s JOIN pg_database d ON d.oid = s.setdatabase WHERE d.datname = 'rupg_ads_scratch' +fields count:20:8:-1 +row 0 +SELECT 1 +> DROP DATABASE rupg_ads_scratch +DROP DATABASE diff --git a/crates/rupg-session/src/connection/databases.rs b/crates/rupg-session/src/connection/databases.rs index 73a99656..ae2a4298 100644 --- a/crates/rupg-session/src/connection/databases.rs +++ b/crates/rupg-session/src/connection/databases.rs @@ -1,17 +1,19 @@ -//! `CREATE DATABASE` and `DROP DATABASE`: the checks of `createdb` and `dropdb` in the order of PostgreSQL, on the rows of `pg_database` that the store keeps. The engine has the encoding `UTF8` and the locale `C` only, so a new database must have them. The checks of PostgreSQL come first, and then the error `0A000` for an encoding or a locale that the engine does not have. +//! `CREATE DATABASE`, `ALTER DATABASE ... SET` and `DROP DATABASE`: the checks of `createdb` and `dropdb` in the order of PostgreSQL, on the rows of `pg_database` that the store keeps. The engine has the encoding `UTF8` and the locale `C` only, so a new database must have them. The checks of PostgreSQL come first, and then the error `0A000` for an encoding or a locale that the engine does not have. use rupg_catalog::Database; use rupg_common::{Error, SqlState}; use rupg_func::{char_to_encoding, encoding_to_char}; use rupg_pgcatalog::builtin; use rupg_sql::Severity; -use rupg_sql::nodes::{CreatedbStmt, DefElem, DropdbStmt, Node}; +use rupg_sql::nodes::{ + AlterDatabaseSetStmt, CreatedbStmt, DefElem, DropdbStmt, Node, VariableSetKind, +}; use rupg_wire::CommandTag; use super::copy::{def_boolean, def_string}; use super::{Connection, Done}; use crate::store::NewDatabase; -use crate::utility::Notice; +use crate::utility::{self, Notice}; /// `FirstNormalObjectId`. const FIRST_NORMAL_OBJECT_ID: u32 = 16384; @@ -687,6 +689,54 @@ impl Connection { self.store.drop_database(&row, force, self.activity.number)?; Ok(Done::Tag(CommandTag::DropDatabase)) } + + /// `AlterDatabaseSet` and `AlterSetting`: the change of the stored settings of a database, which a new session of the database applies. `GUCArrayAdd` puts a new value in place of the value of the same parameter, and `GUCArrayDelete` and `RESET ALL` remove values. + pub(super) fn alter_database_set( + &mut self, + stmt: &AlterDatabaseSetStmt, + ) -> Result { + let name = stmt.dbname.as_deref().unwrap_or_default(); + let Some(row) = self.store.database(name) else { + return Err(Error::new( + SqlState::UNDEFINED_DATABASE, + format!("database \"{name}\" does not exist"), + )); + }; + if !self.superuser() && row.owner != self.role { + return Err(Error::new( + SqlState::INSUFFICIENT_PRIVILEGE, + format!("must be owner of database {name}"), + )); + } + let Some(set) = &stmt.setstmt else { + return Err(Error::internal("ALTER DATABASE SET with no setting")); + }; + let parameter = set.name.as_deref().unwrap_or_default(); + // `ExtractSetVariableArgs`. + let value = match set.kind { + VariableSetKind::VAR_SET_VALUE => utility::flatten(parameter, &set.args)?, + VariableSetKind::VAR_SET_CURRENT => Some(self.settings.borrow().show(parameter)?.1), + _ => None, + }; + if set.kind == VariableSetKind::VAR_RESET_ALL { + self.store.change_database_settings(row.oid, Vec::clear); + return Ok(Done::Tag(CommandTag::AlterDatabase)); + } + let stored = self.settings.borrow().check_stored(parameter, value.as_deref())?; + let prefix = format!("{stored}="); + self.store.change_database_settings(row.oid, |settings| { + let at = settings.iter().position(|item| item.starts_with(&prefix)); + match (value, at) { + (Some(value), Some(at)) => settings[at] = format!("{prefix}{value}"), + (Some(value), None) => settings.push(format!("{prefix}{value}")), + (None, Some(at)) => { + settings.remove(at); + } + (None, None) => {} + } + }); + Ok(Done::Tag(CommandTag::AlterDatabase)) + } } #[cfg(test)] diff --git a/crates/rupg-session/src/connection/extended.rs b/crates/rupg-session/src/connection/extended.rs index e19e48fa..8494f644 100644 --- a/crates/rupg-session/src/connection/extended.rs +++ b/crates/rupg-session/src/connection/extended.rs @@ -579,6 +579,8 @@ impl Connection { position: error.position().map(|at| character_position(&statement.text, at)), error, })? + } else if let Node::AlterDatabaseSetStmt(stmt) = node { + self.alter_database_set(stmt)? } else { let mut notices = Vec::new(); let ts_config = self.ts_config(node)?; diff --git a/crates/rupg-session/src/guc/settings.rs b/crates/rupg-session/src/guc/settings.rs index 1c6391b2..3d94989d 100644 --- a/crates/rupg-session/src/guc/settings.rs +++ b/crates/rupg-session/src/guc/settings.rs @@ -24,6 +24,8 @@ pub enum Source { File, /// A value of the command line of the server. Argument, + /// A value that `ALTER DATABASE ... SET` stored for the database of the session. + Database, /// A value of the startup packet. Client, /// A value that the server sets over the others at start, such as `config_file`. @@ -40,6 +42,7 @@ impl Source { Source::Default => "default", Source::File => "configuration file", Source::Argument => "command line", + Source::Database => "database", Source::Client => "client", Source::Override => "override", Source::Session => "session", @@ -467,6 +470,47 @@ impl Settings { Ok(()) } + /// A value that `ALTER DATABASE ... SET` stored for the database of the session, as `ApplySetting` sets it with `PGC_S_DATABASE` at the start of the session. It comes before the values of the client. A parameter that a statement cannot set is not set. + /// + /// # Errors + /// + /// A name that is not a parameter and not a valid placeholder name, a parameter that cannot change after the start of the session, or a value that the parameter does not take. + pub fn set_database(&mut self, name: &str, text: &str) -> Result<(), Error> { + let Some(parameter) = find(name) else { + placeholder_name(name)?; + let key = name.to_ascii_lowercase(); + let value = + Value { setting: Setting::String(text.to_owned()), source: Source::Database }; + let slot = self.slot(&key, None, name); + slot.current = value.clone(); + slot.reset = value; + return Ok(()); + }; + self.allowed(parameter, Origin::Statement)?; + let setting = self.parse(parameter, text)?; + self.store_reset(parameter, Value { setting, source: Source::Database }); + Ok(()) + } + + /// `validate_option_array_item` for `ALTER DATABASE ... SET`: the check of the name, of the context and of the value, with no change. `text` is `None` for `RESET`. It gives the name that the stored setting has, which is the name of the parameter as PostgreSQL spells it. + /// + /// # Errors + /// + /// A name that is not a parameter and not a valid placeholder name, a parameter that a statement cannot set, and a value that the parameter does not take. + pub fn check_stored(&self, name: &str, text: Option<&str>) -> Result { + let Some(parameter) = find(name) else { + if text.is_some() || !valid_custom_name(name) { + placeholder_name(name)?; + } + return Ok(name.to_owned()); + }; + self.allowed(parameter, Origin::Statement)?; + if let Some(text) = text { + self.parse(parameter, text)?; + } + Ok(parameter.name.to_owned()) + } + /// Reads a value of a parameter with the rules of `set`, and does not keep it. /// /// # Errors diff --git a/crates/rupg-session/src/query.rs b/crates/rupg-session/src/query.rs index 199fa921..890ff0b0 100644 --- a/crates/rupg-session/src/query.rs +++ b/crates/rupg-session/src/query.rs @@ -715,6 +715,13 @@ impl rupg_func::Session for Reader<'_> { } } + fn db_role_settings(&self) -> Vec<(u32, u32, Vec)> { + match self.activity { + Some((_, store)) => store.db_role_settings(), + None => Vec::new(), + } + } + fn backends(&self) -> Arc<[Backend]> { match self.activity { Some((activity, store)) => activity.backends(store, &self.settings()), diff --git a/crates/rupg-session/src/store.rs b/crates/rupg-session/src/store.rs index 09907460..caf19641 100644 --- a/crates/rupg-session/src/store.rs +++ b/crates/rupg-session/src/store.rs @@ -69,6 +69,8 @@ struct Db { row: Database, committed: Arc, locked: bool, + /// The `setconfig` of the row of `pg_db_role_setting` for the database and every role: the settings of `ALTER DATABASE ... SET`, each as `name=value`. + settings: Vec, } impl Default for State { @@ -78,7 +80,17 @@ impl Default for State { let next_oid = catalog.next_oid(); let databases = Database::initdb() .into_iter() - .map(|row| (row.oid, Db { row, committed: Arc::clone(&catalog), locked: false })) + .map(|row| { + ( + row.oid, + Db { + row, + committed: Arc::clone(&catalog), + locked: false, + settings: Vec::new(), + }, + ) + }) .collect(); State { databases, next_oid } } @@ -173,6 +185,32 @@ impl Store { } } + /// The settings of `ALTER DATABASE ... SET` for a database, each as `name=value`, which `process_settings` applies at the start of a session. + pub fn database_settings(&self, database: u32) -> Vec { + self.state().databases.get(&database).map(|db| db.settings.clone()).unwrap_or_default() + } + + /// The rows of `pg_db_role_setting`: one row for each database with settings, with the role 0, in the order of the OIDs of the databases. + pub(crate) fn db_role_settings(&self) -> Vec<(u32, u32, Vec)> { + self.state() + .databases + .iter() + .filter(|(_, db)| !db.settings.is_empty()) + .map(|(oid, db)| (*oid, 0, db.settings.clone())) + .collect() + } + + /// Changes the settings of `ALTER DATABASE ... SET` for a database. The change does not wait for the end of the transaction. + pub(crate) fn change_database_settings( + &self, + database: u32, + change: impl FnOnce(&mut Vec), + ) { + if let Some(db) = self.state().databases.get_mut(&database) { + change(&mut db.settings); + } + } + /// Takes the lock of a database for a transaction, with a copy of the catalog of the last commit. The session waits while another transaction holds the lock. A cancel of the session stops the wait with `57014`. pub(crate) fn lease(self: &Arc, database: u32, cancel: &AtomicBool) -> Result { let mut state = self.state(); @@ -266,7 +304,7 @@ impl Store { Arc::make_mut(&mut committed).advance_oid(next); state.next_oid = next; let oid = row.oid; - state.databases.insert(oid, Db { row, committed, locked: false }); + state.databases.insert(oid, Db { row, committed, locked: false, settings: Vec::new() }); Ok(oid) } diff --git a/crates/rupg-session/src/utility.rs b/crates/rupg-session/src/utility.rs index c1524598..08ea7002 100644 --- a/crates/rupg-session/src/utility.rs +++ b/crates/rupg-session/src/utility.rs @@ -87,7 +87,8 @@ pub fn check(node: &Node, text: &str) -> Result<(), Error> { | Node::PrepareStmt(_) | Node::DeallocateStmt(_) | Node::CreatedbStmt(_) - | Node::DropdbStmt(_) => Ok(()), + | Node::DropdbStmt(_) + | Node::AlterDatabaseSetStmt(_) => Ok(()), _ => Err(not_supported(text)), } }