use std::path::PathBuf;
use anyhow::{Result, bail};
use crate::pglite::base::{PreparedRoot, RootPlan, RootSource, RootTarget, prepare_root};
use crate::pglite::client::Pglite;
use crate::pglite::config::{PostgresConfig, StartupConfig};
#[cfg(feature = "extensions")]
use crate::pglite::extensions::{Extension, resolve_extension_set};
use crate::pglite::interface::DebugLevel;
#[derive(Debug, Clone)]
pub struct PgliteBuilder {
target: Option<PgliteTarget>,
template_cache: bool,
postgres_config: PostgresConfig,
startup_config: StartupConfig,
load_data_dir_archive: Option<Vec<u8>>,
#[cfg(feature = "extensions")]
extensions: Vec<Extension>,
}
#[derive(Debug, Clone)]
enum PgliteTarget {
Path(PathBuf),
AppId {
qualifier: String,
organization: String,
application: String,
},
Temporary,
}
impl Default for PgliteBuilder {
fn default() -> Self {
Self {
target: None,
template_cache: true,
postgres_config: PostgresConfig::default(),
startup_config: StartupConfig::default(),
load_data_dir_archive: None,
#[cfg(feature = "extensions")]
extensions: Vec::new(),
}
}
}
impl PgliteBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn path(mut self, root: impl Into<PathBuf>) -> Self {
self.target = Some(PgliteTarget::Path(root.into()));
self
}
pub fn app(
mut self,
qualifier: impl Into<String>,
organization: impl Into<String>,
application: impl Into<String>,
) -> Self {
self.target = Some(PgliteTarget::AppId {
qualifier: qualifier.into(),
organization: organization.into(),
application: application.into(),
});
self
}
pub fn app_id(self, app_id: (&str, &str, &str)) -> Self {
self.app(app_id.0, app_id.1, app_id.2)
}
pub fn temporary(mut self) -> Self {
self.target = Some(PgliteTarget::Temporary);
self
}
pub fn template_cache(mut self, enabled: bool) -> Self {
self.template_cache = enabled;
self
}
pub fn fresh_temporary(self) -> Self {
self.temporary().template_cache(false)
}
pub fn postgres_config(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.postgres_config.insert(name, value);
self
}
pub fn postgres_configs<K, V>(mut self, settings: impl IntoIterator<Item = (K, V)>) -> Self
where
K: Into<String>,
V: Into<String>,
{
for (name, value) in settings {
self.postgres_config.insert(name, value);
}
self
}
pub fn username(mut self, username: impl Into<String>) -> Self {
self.startup_config.username = username.into();
self
}
pub fn database(mut self, database: impl Into<String>) -> Self {
self.startup_config.database = database.into();
self
}
pub fn debug_level(mut self, level: DebugLevel) -> Self {
self.startup_config.debug_level = Some(level);
self
}
pub fn relaxed_durability(mut self, enabled: bool) -> Self {
self.startup_config.relaxed_durability = enabled;
self
}
pub fn startup_arg(mut self, arg: impl Into<String>) -> Self {
self.startup_config.extra_args.push(arg.into());
self
}
pub fn startup_args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.startup_config
.extra_args
.extend(args.into_iter().map(Into::into));
self
}
pub fn load_data_dir_archive(mut self, archive: impl Into<Vec<u8>>) -> Self {
self.load_data_dir_archive = Some(archive.into());
self
}
#[cfg(feature = "extensions")]
pub fn extension(mut self, extension: Extension) -> Self {
self.extensions.push(extension);
self
}
#[cfg(feature = "extensions")]
pub fn extensions(mut self, extensions: impl IntoIterator<Item = Extension>) -> Self {
self.extensions.extend(extensions);
self
}
pub fn open(self) -> Result<Pglite> {
self.postgres_config.validate()?;
self.startup_config.validate()?;
let target = match self.target.clone() {
Some(PgliteTarget::Path(root)) => RootTarget::Path(root),
Some(PgliteTarget::AppId {
qualifier,
organization,
application,
}) => RootTarget::AppId {
qualifier,
organization,
application,
},
Some(PgliteTarget::Temporary) => RootTarget::Temporary,
None => {
bail!(
"PgliteBuilder target is not set; call path, app_id, or temporary before open"
)
}
};
let source = if let Some(archive) = self.load_data_dir_archive.clone() {
RootSource::DataDirArchive(archive)
} else if self.template_cache {
RootSource::Template
} else {
RootSource::FreshInitdb
};
#[cfg(feature = "extensions")]
let extensions = resolve_extension_set(&self.extensions)?;
let plan = RootPlan::new(target, source);
#[cfg(feature = "extensions")]
let plan = plan.with_extensions(extensions.clone(), self.postgres_config.clone());
let prepared = prepare_root(plan)?;
#[cfg(feature = "extensions")]
{
self.open_prepared_root(prepared, extensions)
}
#[cfg(not(feature = "extensions"))]
{
self.open_prepared_root(prepared)
}
}
fn open_prepared_root(
self,
prepared: PreparedRoot,
#[cfg(feature = "extensions")] extensions: Vec<Extension>,
) -> Result<Pglite> {
let PreparedRoot {
temp_dir,
root_lock,
outcome,
..
} = prepared;
#[cfg(feature = "extensions")]
let preinstalled_extensions = outcome.preinstalled_extensions.clone();
let mut instance =
Pglite::new_prepared_with_config(outcome, self.postgres_config, self.startup_config)?;
if let Some(lock) = root_lock {
instance.attach_root_lock(lock);
}
if let Some(temp_dir) = temp_dir {
instance.attach_temp_dir(temp_dir);
}
#[cfg(feature = "extensions")]
let mut instance = instance;
#[cfg(feature = "extensions")]
for extension in extensions {
if preinstalled_extensions
.iter()
.any(|sql_name| sql_name == extension.sql_name())
{
instance.enable_preinstalled_extension(extension)?;
} else {
instance.enable_extension(extension)?;
}
}
Ok(instance)
}
}