mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-13 16:32:56 +00:00
This makes it possible for the compiler to validate that a match block matched all PostgreSQL versions we support. ## Problem We did not have a complete picture about which places we had to test against PG versions, and what format these versions were: The full PG version ID format (Major/minor/bugfix `MMmmbb`) as transfered in protocol messages, or only the Major release version (`MM`). This meant type confusion was rampant. With this change, it becomes easier to develop new version-dependent features, by making type and niche confusion impossible. ## Summary of changes Every use of `pg_version` is now typed as either `PgVersionId` (u32, valued in decimal `MMmmbb`) or PgMajorVersion (an enum, with a value for every major version we support, serialized and stored like a u32 with the value of that major version) --------- Co-authored-by: Arpad Müller <arpad-m@users.noreply.github.com>
117 lines
3.8 KiB
Rust
117 lines
3.8 KiB
Rust
//! The canonical way we run `initdb` in Neon.
|
|
//!
|
|
//! initdb has implicit defaults that are dependent on the environment, e.g., locales & collations.
|
|
//!
|
|
//! This module's job is to eliminate the environment-dependence as much as possible.
|
|
|
|
use std::fmt;
|
|
|
|
use camino::Utf8Path;
|
|
use postgres_versioninfo::PgMajorVersion;
|
|
|
|
pub struct RunInitdbArgs<'a> {
|
|
pub superuser: &'a str,
|
|
pub locale: &'a str,
|
|
pub initdb_bin: &'a Utf8Path,
|
|
pub pg_version: PgMajorVersion,
|
|
pub library_search_path: &'a Utf8Path,
|
|
pub pgdata: &'a Utf8Path,
|
|
}
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
pub enum Error {
|
|
Spawn(std::io::Error),
|
|
Failed {
|
|
status: std::process::ExitStatus,
|
|
stderr: Vec<u8>,
|
|
},
|
|
WaitOutput(std::io::Error),
|
|
Other(anyhow::Error),
|
|
}
|
|
|
|
impl fmt::Display for Error {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Error::Spawn(e) => write!(f, "Error spawning command: {e:?}"),
|
|
Error::Failed { status, stderr } => write!(
|
|
f,
|
|
"Command failed with status {:?}: {}",
|
|
status,
|
|
String::from_utf8_lossy(stderr)
|
|
),
|
|
Error::WaitOutput(e) => write!(f, "Error waiting for command output: {e:?}"),
|
|
Error::Other(e) => write!(f, "Error: {e:?}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn do_run_initdb(args: RunInitdbArgs<'_>) -> Result<(), Error> {
|
|
let RunInitdbArgs {
|
|
superuser,
|
|
locale,
|
|
initdb_bin: initdb_bin_path,
|
|
pg_version,
|
|
library_search_path,
|
|
pgdata,
|
|
} = args;
|
|
let mut initdb_command = tokio::process::Command::new(initdb_bin_path);
|
|
initdb_command
|
|
.args(["--pgdata", pgdata.as_ref()])
|
|
.args(["--username", superuser])
|
|
.args(["--encoding", "utf8"])
|
|
.args(["--locale", locale])
|
|
.arg("--no-instructions")
|
|
.arg("--no-sync")
|
|
.env_clear()
|
|
.env("LD_LIBRARY_PATH", library_search_path)
|
|
.env("DYLD_LIBRARY_PATH", library_search_path)
|
|
.env(
|
|
"ASAN_OPTIONS",
|
|
std::env::var("ASAN_OPTIONS").unwrap_or_default(),
|
|
)
|
|
.env(
|
|
"UBSAN_OPTIONS",
|
|
std::env::var("UBSAN_OPTIONS").unwrap_or_default(),
|
|
)
|
|
.stdin(std::process::Stdio::null())
|
|
// stdout invocation produces the same output every time, we don't need it
|
|
.stdout(std::process::Stdio::null())
|
|
// we would be interested in the stderr output, if there was any
|
|
.stderr(std::process::Stdio::piped());
|
|
|
|
// Before version 14, only the libc provide was available.
|
|
if pg_version > PgMajorVersion::PG14 {
|
|
// Version 17 brought with it a builtin locale provider which only provides
|
|
// C and C.UTF-8. While being safer for collation purposes since it is
|
|
// guaranteed to be consistent throughout a major release, it is also more
|
|
// performant.
|
|
let locale_provider = if pg_version >= PgMajorVersion::PG17 {
|
|
"builtin"
|
|
} else {
|
|
"libc"
|
|
};
|
|
|
|
initdb_command.args(["--locale-provider", locale_provider]);
|
|
}
|
|
|
|
let initdb_proc = initdb_command.spawn().map_err(Error::Spawn)?;
|
|
|
|
// Ideally we'd select here with the cancellation token, but the problem is that
|
|
// we can't safely terminate initdb: it launches processes of its own, and killing
|
|
// initdb doesn't kill them. After we return from this function, we want the target
|
|
// directory to be able to be cleaned up.
|
|
// See https://github.com/neondatabase/neon/issues/6385
|
|
let initdb_output = initdb_proc
|
|
.wait_with_output()
|
|
.await
|
|
.map_err(Error::WaitOutput)?;
|
|
if !initdb_output.status.success() {
|
|
return Err(Error::Failed {
|
|
status: initdb_output.status,
|
|
stderr: initdb_output.stderr,
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|