use uqa_sql::catalog::access_methods::{access_method_kind, AccessMethodKind};
use uqa_sql::semantics::parameters::custom::{
check_assignable_custom_name, reserved_placeholder_warning,
};
use uqa_sql::semantics::parameters::definition::{
ParameterContext, ParameterDefinition, ParameterFlags,
};
use uqa_sql::semantics::parameters::identifier_list::split_identifier_list;
use uqa_sql::semantics::parameters::value::{
invalid_value_message, name_truncation_notice, parse_setting_with_context,
};
use uqa_sql::SQLNotice;
use super::{default_search_path, Engine, SQLError};
use crate::capabilities::session_parameters::{setting_key, SessionParameter};
const TABLESPACES: [&str; 2] = ["pg_default", "pg_global"];
fn invalid_value(definition: &ParameterDefinition, value: &str, detail: String) -> SQLError {
SQLError::Diagnostic {
sqlstate: "22023".into(),
message: invalid_value_message(definition, value),
detail: Some(detail),
hint: None,
}
}
fn is_transaction_characteristic(definition: &ParameterDefinition) -> bool {
matches!(
definition.name,
"transaction_isolation" | "transaction_read_only" | "transaction_deferrable"
)
}
fn clean_ascii(value: &str) -> String {
use std::fmt::Write;
let mut cleaned = String::with_capacity(value.len());
for byte in value.bytes() {
if (32..127).contains(&byte) {
cleaned.push(char::from(byte));
} else {
write!(cleaned, "\\x{byte:02x}").expect("writing into a String cannot fail");
}
}
cleaned
}
impl Engine {
pub(crate) fn parser_settings(&self) -> uqa_sql::parser::ParserSettings {
let parameters = self.session.parameters.lock();
let state = self.session.state.read();
uqa_sql::parser::ParserSettings::from_settings(|name| {
state
.session_vars
.get(name)
.map(String::as_str)
.or_else(|| parameters.client_setting(name))
})
}
pub(super) fn assign_parameter(&self, name: &str, value: &str) -> Result<(), SQLError> {
match self.session_execution_view().resolve_parameter(name) {
Some(SessionParameter::Defined(definition)) => {
self.check_parameter_change(definition)?;
if is_transaction_characteristic(definition) {
return self.set_transaction_parameter(definition.name, value);
}
let setting = self.checked_setting(definition, value)?;
self.store_setting(definition, Some(setting));
Ok(())
}
Some(SessionParameter::Placeholder(placeholder)) => {
self.store_placeholder(&placeholder, Some(value.to_string()));
Ok(())
}
None => {
self.create_placeholder(name)?;
self.store_placeholder(name, Some(value.to_string()));
Ok(())
}
}
}
pub(super) fn reset_parameter(&self, name: &str) -> Result<(), SQLError> {
match self.session_execution_view().resolve_parameter(name) {
Some(SessionParameter::Defined(definition)) => {
if definition.has_flag(ParameterFlags::NO_RESET) {
return Err(SQLError::Routine {
sqlstate: "0A000".into(),
message: format!("parameter \"{}\" cannot be reset", definition.name),
});
}
self.check_parameter_change(definition)?;
self.store_setting(definition, None);
Ok(())
}
Some(SessionParameter::Placeholder(placeholder)) => {
self.store_placeholder(&placeholder, None);
Ok(())
}
None => self.create_placeholder(name),
}
}
fn check_parameter_change(&self, definition: &ParameterDefinition) -> Result<(), SQLError> {
match definition.context {
ParameterContext::Internal => Err(SQLError::Routine {
sqlstate: "55P02".into(),
message: format!("parameter \"{}\" cannot be changed", definition.name),
}),
ParameterContext::Superuser if !self.current_user_is_superuser() => {
Err(SQLError::Routine {
sqlstate: "42501".into(),
message: format!("permission denied to set parameter \"{}\"", definition.name),
})
}
ParameterContext::Superuser | ParameterContext::User => Ok(()),
}
}
fn checked_setting(
&self,
definition: &ParameterDefinition,
value: &str,
) -> Result<String, SQLError> {
if let Some(notice) = name_truncation_notice(definition, value) {
self.push_sql_notice(SQLNotice::notice(notice).with_sqlstate("42622"));
}
let setting = parse_setting_with_context(
definition,
value,
&self.session.setting(definition.name),
&self.session.reset_setting(definition),
)?;
match definition.name {
"application_name" => Ok(clean_ascii(&setting)),
"search_path" => {
if split_identifier_list(&setting, b',').is_none() {
return Err(invalid_value(
definition,
&setting,
"List syntax is invalid.".into(),
));
}
Ok(setting)
}
"default_with_oids" if setting == "on" => Err(SQLError::Routine {
sqlstate: "0A000".into(),
message: "tables declared WITH OIDS are not supported".into(),
}),
"default_tablespace" => {
if !setting.is_empty() && !TABLESPACES.contains(&setting.as_str()) {
return Err(invalid_value(
definition,
&setting,
format!("Tablespace \"{setting}\" does not exist."),
));
}
Ok(setting)
}
"default_table_access_method" => match access_method_kind(&setting) {
_ if setting.is_empty() => Err(invalid_value(
definition,
&setting,
"\"default_table_access_method\" cannot be empty.".into(),
)),
Some(AccessMethodKind::Table) => Ok(setting),
Some(AccessMethodKind::Index) => Err(SQLError::Routine {
sqlstate: "55000".into(),
message: format!("access method \"{setting}\" is not of type TABLE"),
}),
None => Err(invalid_value(
definition,
&setting,
format!("Table access method \"{setting}\" does not exist."),
)),
},
_ => Ok(setting),
}
}
fn store_setting(&self, definition: &ParameterDefinition, setting: Option<String>) {
let reset_path = (definition.name == "search_path" && setting.is_none())
.then(|| self.session_execution_view().reset_setting(definition));
let key = setting_key(definition.name);
let mut state = self.session.state.write();
match definition.name {
"search_path" => {
let text = setting
.as_deref()
.or(reset_path.as_deref())
.unwrap_or_default();
state.search_path =
split_identifier_list(text, b',').expect("checked search path syntax");
state.sql_statement_cache.clear();
}
"session_replication_role" => state.sql_statement_cache.clear(),
_ => {}
}
match setting {
Some(setting) => {
state.session_vars.insert(key, setting);
}
None => {
state.session_vars.remove(&key);
}
}
}
fn store_placeholder(&self, name: &str, setting: Option<String>) {
let key = setting_key(name);
let mut state = self.session.state.write();
match setting {
Some(setting) => {
state.session_vars.insert(key, setting);
}
None => {
state.session_vars.remove(&key);
}
}
}
fn create_placeholder(&self, name: &str) -> Result<(), SQLError> {
let mut registry = self.session.parameters.lock();
check_assignable_custom_name(name, registry.reserved_prefixes())?;
registry.add_placeholder(name);
Ok(())
}
pub(super) fn reset_search_path(&self) -> Vec<String> {
let definition = uqa_sql::semantics::parameters::catalog::find_parameter("search_path")
.expect("search_path is defined");
let setting = self.session_execution_view().reset_setting(definition);
split_identifier_list(&setting, b',').unwrap_or_else(default_search_path)
}
pub(crate) fn set_config(
&self,
name: &str,
value: Option<&str>,
local: bool,
) -> Result<String, SQLError> {
self.set_runtime_parameter(name, value, local)?;
self.session_execution_view().show_variable(name)
}
pub fn set_client_parameter(&self, name: &str, value: &str) -> Result<(), SQLError> {
match self.session_execution_view().resolve_parameter(name) {
Some(SessionParameter::Defined(definition))
if is_transaction_characteristic(definition)
|| matches!(definition.name, "role" | "session_authorization") =>
{
self.set_variable(definition.name, value)
}
Some(SessionParameter::Defined(definition)) => {
self.check_parameter_change(definition)?;
let setting = self.checked_setting(definition, value)?;
if definition.name == "search_path" {
self.session.state.write().search_path =
split_identifier_list(&setting, b',').expect("checked search path syntax");
}
if definition.name == "client_min_messages" {
if let Some(level) = crate::state::message_level(&setting) {
self.session.state.set_reset_client_level(level);
}
}
if definition.name == "lock_timeout" {
if let Ok(milliseconds) = setting.parse::<u64>() {
self.session.state.set_reset_lock_timeout(milliseconds);
}
}
self.session
.parameters
.lock()
.set_client_setting(definition.name, setting);
if matches!(
definition.name,
"idle_session_timeout"
| "idle_in_transaction_session_timeout"
| "transaction_timeout"
) && self.runtime.terminations.is_idle()
{
self.session_became_idle();
}
Ok(())
}
Some(SessionParameter::Placeholder(placeholder)) => {
self.store_placeholder(&placeholder, Some(value.to_string()));
Ok(())
}
None => {
self.create_placeholder(name)?;
self.store_placeholder(name, Some(value.to_string()));
Ok(())
}
}
}
pub(crate) fn load_parameter_library(&self, library: &'static str) {
let placeholders = {
let mut registry = self.session.parameters.lock();
if !registry.load_library(library) {
return;
}
registry.placeholders_with_prefix(library)
};
for placeholder in placeholders {
let key = setting_key(&placeholder);
let value = self.session.state.read().session_vars.get(&key).cloned();
self.session
.parameters
.lock()
.remove_placeholder(&placeholder);
let definition = uqa_sql::semantics::parameters::catalog::find_parameter(&placeholder)
.filter(|definition| definition.library == Some(library));
let Some(definition) = definition else {
let (message, detail) = reserved_placeholder_warning(&placeholder, library);
self.push_sql_notice(
SQLNotice::warning(message)
.with_sqlstate("42602")
.with_detail(detail),
);
self.session.state.write().session_vars.remove(&key);
continue;
};
let Some(value) = value else {
continue;
};
match self.checked_setting(definition, &value) {
Ok(setting) => {
self.session.state.write().session_vars.insert(key, setting);
}
Err(error) => {
self.push_sql_notice(
SQLNotice::warning(error.to_string())
.with_sqlstate(error.sqlstate().unwrap_or("22023")),
);
self.session.state.write().session_vars.remove(&key);
}
}
}
}
}