use super::{parse_search_path_list, Engine, SQLError, StorageBackendResult};
impl Engine {
pub fn search_path(&self) -> Vec<String> {
self.session.state.read().search_path.clone()
}
pub fn load_library(&self, library: &str) -> Result<(), SQLError> {
let requested = library.strip_prefix("$libdir/").unwrap_or(library);
let base = requested.strip_suffix(".so").unwrap_or(requested);
if base == "age" && !requested.contains('/') {
return Ok(());
}
let path = if library.contains('/') {
library.to_string()
} else {
format!("$libdir/{library}")
};
Err(SQLError::Routine {
sqlstate: "58P01".into(),
message: format!("could not access file \"{path}\": No such file or directory"),
})
}
pub(crate) fn search_path_contains(&self, schema: &str) -> bool {
self.session
.state
.read()
.search_path
.iter()
.any(|entry| entry == schema)
}
pub fn current_schema_name(&self) -> StorageBackendResult<Option<String>> {
self.synchronize_catalog_registries()?;
let session = self.session.state.read();
let schemas = self.durable.schemas.read();
let graphs = self.durable.graphs.read();
Ok(session
.search_path
.iter()
.find(|name| {
schemas.contains(name.as_str())
|| super::schemas::is_virtual_system_schema(name)
|| graphs.contains_key(name.as_str())
})
.cloned())
}
pub fn current_schema_names(
&self,
include_implicit: bool,
) -> StorageBackendResult<Vec<String>> {
self.synchronize_catalog_registries()?;
let session = self.session.state.read();
let schemas = self.durable.schemas.read();
let graphs = self.durable.graphs.read();
let path = &session.search_path;
let mut out = Vec::new();
if include_implicit && !path.iter().any(|name| name == "pg_catalog") {
out.push("pg_catalog".to_string());
}
for name in path {
if (schemas.contains(name.as_str())
|| super::schemas::is_virtual_system_schema(name)
|| graphs.contains_key(name.as_str()))
&& !out.contains(name)
{
out.push(name.clone());
}
}
Ok(out)
}
pub fn next_random_u64(&self) -> u64 {
let mut state = self.session.random_state.lock();
let s0 = state.s0;
let mixed = state.s1 ^ s0;
let value = s0.wrapping_mul(5).rotate_left(7).wrapping_mul(9);
state.s0 = s0.rotate_left(24) ^ mixed ^ (mixed << 16);
state.s1 = mixed.rotate_left(37);
value
}
pub fn next_random_value(&self) -> f64 {
let sample = self.next_random_u64() >> 12;
sample as f64 * (1.0 / ((1_u64 << 52) as f64))
}
pub fn set_random_seed(&self, seed: f64) -> Result<(), String> {
if !seed.is_finite() || !(-1.0..=1.0).contains(&seed) {
return Err(format!(
"setseed parameter {seed} is out of allowed range [-1,1]"
));
}
let scaled = (((1_u64 << 52) - 1) as f64 * seed) as i64;
*self.session.random_state.lock() = crate::random_state_from_seed(scaled as u64);
Ok(())
}
pub fn set_search_path(&self, path: Vec<String>) {
let mut value = path;
if value.is_empty() {
value.push("public".to_string());
}
let mut session = self.session.state.write();
session.search_path = value;
session.sql_statement_cache.clear();
}
pub fn set_variable(&self, name: &str, value: &str) -> Result<(), SQLError> {
if !crate::is_known_runtime_parameter(name) {
return Err(SQLError::Routine {
sqlstate: "42704".into(),
message: format!("unrecognized configuration parameter \"{name}\""),
});
}
if !crate::is_mutable_runtime_parameter(name) {
return Err(SQLError::Routine {
sqlstate: "55P02".into(),
message: format!("parameter \"{name}\" cannot be changed"),
});
}
if name.eq_ignore_ascii_case("transaction_isolation")
|| name.eq_ignore_ascii_case("transaction_read_only")
|| name.eq_ignore_ascii_case("transaction_deferrable")
{
return self.set_transaction_parameter(name, value);
}
let value = Self::validate_default_transaction_parameter(name, value)?;
if name.eq_ignore_ascii_case("work_mem") {
Self::parse_work_mem_bytes(&value)?;
}
if name.eq_ignore_ascii_case("search_path") {
let parts = parse_search_path_list(&value)?;
let mut session = self.session.state.write();
session.search_path = if parts.is_empty() {
vec!["public".to_string()]
} else {
parts
};
session.session_vars.insert(name.to_string(), value);
session.sql_statement_cache.clear();
return Ok(());
}
self.session
.state
.write()
.session_vars
.insert(name.to_string(), value);
Ok(())
}
pub fn reset_variable(&self, name: &str) -> Result<(), SQLError> {
if !crate::is_known_runtime_parameter(name) {
return Err(SQLError::Routine {
sqlstate: "42704".into(),
message: format!("unrecognized configuration parameter \"{name}\""),
});
}
if name.eq_ignore_ascii_case("transaction_isolation")
|| name.eq_ignore_ascii_case("transaction_read_only")
|| name.eq_ignore_ascii_case("transaction_deferrable")
{
return Err(SQLError::Routine {
sqlstate: "0A000".into(),
message: format!("parameter \"{name}\" cannot be reset"),
});
}
if !crate::is_mutable_runtime_parameter(name) {
return Err(SQLError::Routine {
sqlstate: "55P02".into(),
message: format!("parameter \"{name}\" cannot be changed"),
});
}
let mut session = self.session.state.write();
session
.session_vars
.retain(|key, _| !key.eq_ignore_ascii_case(name));
if name.eq_ignore_ascii_case("search_path") {
session.search_path = vec!["public".into()];
}
session.sql_statement_cache.clear();
Ok(())
}
pub fn reset_all_variables(&self) {
let mut session = self.session.state.write();
session.session_vars.clear();
session.search_path = vec!["public".into()];
session.sql_statement_cache.clear();
}
pub fn show_variable(&self, name: &str) -> Result<String, SQLError> {
if name.eq_ignore_ascii_case("search_path") {
return Ok(self.search_path().join(","));
}
if let Some(value) = self.transaction_parameter_value(name) {
return Ok(value);
}
let session = self.session.state.read();
if let Some(value) = session.session_vars.get(name) {
return Ok(value.clone());
}
if let Some((_, value)) = session
.session_vars
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
{
return Ok(value.clone());
}
crate::default_runtime_parameter(name)
.map(str::to_string)
.ok_or_else(|| SQLError::Routine {
sqlstate: "42704".into(),
message: format!("unrecognized configuration parameter \"{name}\""),
})
}
pub(crate) fn work_mem_bytes(&self) -> Result<usize, SQLError> {
Self::parse_work_mem_bytes(&self.show_variable("work_mem")?)
}
fn parse_work_mem_bytes(raw: &str) -> Result<usize, SQLError> {
let compact = raw
.trim()
.chars()
.filter(|character| !character.is_ascii_whitespace())
.collect::<String>();
let digits = compact.bytes().take_while(u8::is_ascii_digit).count();
if digits == 0 {
return Err(SQLError::TypeMismatch(format!(
"work_mem must be a positive byte size, got {raw:?}"
)));
}
let amount = compact[..digits].parse::<usize>().map_err(|_| {
SQLError::TypeMismatch(format!("work_mem is outside the supported range: {raw:?}"))
})?;
if amount == 0 {
return Err(SQLError::TypeMismatch(
"work_mem must be greater than zero".into(),
));
}
let unit = compact[digits..].to_ascii_lowercase();
let exponent = match unit.as_str() {
"b" => 0,
"" | "k" | "kb" | "kib" => 1,
"m" | "mb" | "mib" => 2,
"g" | "gb" | "gib" => 3,
"t" | "tb" | "tib" => 4,
_ => {
return Err(SQLError::TypeMismatch(format!(
"unsupported work_mem unit in {raw:?}"
)))
}
};
let multiplier = 1024_usize.checked_pow(exponent).ok_or_else(|| {
SQLError::TypeMismatch(format!("work_mem is outside the supported range: {raw:?}"))
})?;
amount.checked_mul(multiplier).ok_or_else(|| {
SQLError::TypeMismatch(format!("work_mem is outside the supported range: {raw:?}"))
})
}
pub fn discard(&self, target: uqa_sql::ast::DiscardTarget) -> Result<(), SQLError> {
use uqa_sql::ast::DiscardTarget;
let _statement = self.runtime.statement_gate.lock();
if self.transaction_depth() != 0 {
return Err(SQLError::Routine {
sqlstate: "25001".into(),
message: "DISCARD cannot run inside a transaction block".into(),
});
}
if matches!(target, DiscardTarget::All | DiscardTarget::Temp) {
self.discard_temporary_relations();
}
let mut session = self.session.state.write();
match target {
DiscardTarget::All => {
session.session_vars.clear();
session.prepared.clear();
session.sequence_currvals.clear();
session.sql_statement_cache.clear();
session.search_path = vec!["public".to_string()];
let session_user = session.session_user.clone();
session.current_user = session_user;
drop(session);
self.session.portals.lock().clear();
return Ok(());
}
DiscardTarget::Plans => {
session.prepared.clear();
session.sql_statement_cache.clear();
}
DiscardTarget::Sequences => {
session.sequence_currvals.clear();
}
DiscardTarget::Temp => {}
}
Ok(())
}
fn discard_temporary_relations(&self) {
let schema = self.temporary_schema_name();
let temporary_tables = self
.storage
.tables
.read()
.keys()
.filter(|relation| relation.schema == schema)
.cloned()
.collect::<Vec<_>>();
let temporary_table_names = temporary_tables
.iter()
.map(super::RelationIdentity::qualified_name)
.collect::<std::collections::BTreeSet<_>>();
self.storage
.tables
.write()
.retain(|relation, _| relation.schema != schema);
self.durable
.table_field_analyzers
.write()
.retain(|(table, _), _| !temporary_table_names.contains(table));
self.durable
.catalog_indexes
.write()
.retain(|_, index| !temporary_table_names.contains(&index.table_name));
self.durable
.views
.write()
.retain(|relation, _| relation.schema != schema);
let temporary_sequences = self
.durable
.sequence_persistence
.read()
.iter()
.filter(|(relation, persistence)| {
relation.schema == schema
&& **persistence == uqa_sql::ast::RelationPersistence::Temporary
})
.map(|(relation, _)| relation.clone())
.collect::<std::collections::BTreeSet<_>>();
self.durable
.sequences
.write()
.retain(|relation, _| !temporary_sequences.contains(relation));
self.durable
.sequence_persistence
.write()
.retain(|relation, _| !temporary_sequences.contains(relation));
self.session
.state
.write()
.sequence_currvals
.retain(|relation, _| !temporary_sequences.contains(relation));
if !temporary_tables.is_empty() {
self.note_table_catalog_changed();
}
self.note_catalog_registry_changed();
}
}