Skip to main content

bestool_postgres/pgtune/
conf_block.rs

1//! Rendering and splicing of the managed tuning block in `postgresql.conf`.
2//!
3//! The block is a run of `key = value` lines bounded by a begin and end marker,
4//! kept at the end of the file so its settings override any earlier occurrence.
5//! Splicing is idempotent: re-applying an unchanged block leaves the file byte
6//! for byte identical.
7
8use super::Setting;
9
10/// Opening marker of the managed block.
11pub const BEGIN: &str =
12	"# BEGIN PGTUNE — generated by `bestool tamanu pg-tune`, do not edit by hand";
13/// Closing marker of the managed block.
14pub const END: &str = "# END PGTUNE";
15
16/// The prefix of a header left by hand-tuning before this command existed.
17const LEGACY_HEADER: &str = "# PGTUNE";
18
19/// Render a tuning set as a marker-delimited block, terminated by a newline.
20pub fn render(settings: &[Setting]) -> String {
21	let mut out = String::new();
22	out.push_str(BEGIN);
23	out.push('\n');
24	for setting in settings {
25		out.push_str(setting.key);
26		out.push_str(" = ");
27		out.push_str(&setting.value);
28		out.push('\n');
29	}
30	out.push_str(END);
31	out.push('\n');
32	out
33}
34
35/// Return `existing` with `block` spliced in.
36///
37/// When the markers are already present, the content between them (inclusive) is
38/// replaced. Otherwise, when a legacy `# PGTUNE` header is present, everything
39/// from that header to the end of the file is replaced. Otherwise the block is
40/// appended after a blank line.
41pub fn splice(existing: &str, block: &str) -> String {
42	let block_lines: Vec<&str> = block.trim_end_matches('\n').split('\n').collect();
43	let lines: Vec<&str> = if existing.is_empty() {
44		Vec::new()
45	} else {
46		existing.split('\n').collect()
47	};
48
49	let begin = lines.iter().position(|l| l.trim_end() == BEGIN);
50	let end = lines.iter().position(|l| l.trim_end() == END);
51
52	let mut result: Vec<&str> = Vec::new();
53	match (begin, end) {
54		(Some(b), Some(e)) if b <= e => {
55			result.extend_from_slice(&lines[..b]);
56			result.extend_from_slice(&block_lines);
57			result.extend_from_slice(&lines[e + 1..]);
58		}
59		_ => {
60			let cut = lines
61				.iter()
62				.position(|l| l.trim_start().starts_with(LEGACY_HEADER))
63				.unwrap_or(lines.len());
64			result.extend_from_slice(&lines[..cut]);
65			trim_trailing_empty(&mut result);
66			if !result.is_empty() {
67				result.push("");
68			}
69			result.extend_from_slice(&block_lines);
70		}
71	}
72
73	trim_trailing_empty(&mut result);
74	let mut out = result.join("\n");
75	out.push('\n');
76	out
77}
78
79fn trim_trailing_empty(lines: &mut Vec<&str>) {
80	while lines.last().is_some_and(|l| l.trim().is_empty()) {
81		lines.pop();
82	}
83}
84
85#[cfg(test)]
86mod tests {
87	use super::*;
88
89	fn sample_block() -> String {
90		render(&[
91			Setting::new("shared_buffers", "1GB"),
92			Setting::new("work_mem", "16MB"),
93		])
94	}
95
96	#[test]
97	fn appends_to_a_file_without_markers() {
98		let existing = "listen_addresses = '*'\nport = 5432\n";
99		let out = splice(existing, &sample_block());
100		assert!(out.starts_with("listen_addresses = '*'\nport = 5432\n\n# BEGIN PGTUNE"));
101		assert!(out.ends_with("# END PGTUNE\n"));
102		assert!(out.contains("shared_buffers = 1GB"));
103	}
104
105	#[test]
106	fn replaces_between_existing_markers() {
107		let first = splice("port = 5432\n", &sample_block());
108		let updated = render(&[Setting::new("shared_buffers", "2GB")]);
109		let second = splice(&first, &updated);
110		assert!(second.contains("shared_buffers = 2GB"));
111		assert!(!second.contains("shared_buffers = 1GB"));
112		assert!(!second.contains("work_mem"));
113		// preceding content untouched, markers not duplicated
114		assert!(second.starts_with("port = 5432\n"));
115		assert_eq!(second.matches(BEGIN).count(), 1);
116		assert_eq!(second.matches(END).count(), 1);
117	}
118
119	#[test]
120	fn migrates_a_legacy_header_to_eof() {
121		let existing = "port = 5432\n\n# PGTUNE https://pgtune.leopard.in.ua/\nshared_buffers = 512MB\nwork_mem = 8MB\n";
122		let out = splice(existing, &sample_block());
123		assert!(out.starts_with("port = 5432\n"));
124		assert!(!out.contains("# PGTUNE https"));
125		assert!(!out.contains("512MB"));
126		assert!(out.contains("# BEGIN PGTUNE"));
127		assert!(out.contains("shared_buffers = 1GB"));
128		assert!(out.ends_with("# END PGTUNE\n"));
129	}
130
131	#[test]
132	fn is_idempotent() {
133		let block = sample_block();
134		let once = splice("port = 5432\n", &block);
135		let twice = splice(&once, &block);
136		assert_eq!(once, twice);
137	}
138
139	#[test]
140	fn empty_file_gets_only_the_block() {
141		let out = splice("", &sample_block());
142		assert!(out.starts_with("# BEGIN PGTUNE"));
143		assert!(out.ends_with("# END PGTUNE\n"));
144	}
145}