1use eyre::{eyre, WrapErr};
12use owo_colors::OwoColorize;
13use serde::{Deserialize, Serialize};
14use std::collections::{BTreeMap, HashMap};
15use std::ffi::OsString;
16use std::fmt::{self, Debug, Display, Formatter};
17use std::io::ErrorKind;
18use std::path::PathBuf;
19use std::process::{Command, Stdio};
20use std::str::FromStr;
21use thiserror::Error;
22use url::Url;
23
24pub mod cargo;
25
26pub static BASE_POSTGRES_PORT_NO: u16 = 28800;
27pub static BASE_POSTGRES_TESTING_PORT_NO: u16 = 32200;
28
29pub fn get_c_locale_flags() -> &'static [&'static str] {
32 #[cfg(target_os = "macos")]
33 {
34 &["--locale=C", "--lc-ctype=UTF-8"]
35 }
36 #[cfg(not(target_os = "macos"))]
37 {
38 match Command::new("locale").arg("-a").output() {
39 Ok(cmd)
40 if String::from_utf8_lossy(&cmd.stdout)
41 .lines()
42 .any(|l| l == "C.UTF-8" || l == "C.utf8") =>
43 {
44 &["--locale=C.UTF-8"]
45 }
46 _ => &["--locale=C"],
48 }
49 }
50}
51
52mod path_methods;
57pub use path_methods::{get_target_dir, prefix_path};
58
59#[derive(Copy, Clone, Debug, Eq, PartialEq)]
60pub enum PgMinorVersion {
61 Latest,
62 Release(u16),
63 Beta(u16),
64 Rc(u16),
65}
66
67impl Display for PgMinorVersion {
68 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69 match self {
70 PgMinorVersion::Latest => write!(f, ".LATEST"),
71 PgMinorVersion::Release(v) => write!(f, ".{v}"),
72 PgMinorVersion::Beta(v) => write!(f, "beta{v}"),
73 PgMinorVersion::Rc(v) => write!(f, "rc{v}"),
74 }
75 }
76}
77
78impl PgMinorVersion {
79 fn version(&self) -> Option<u16> {
80 match self {
81 PgMinorVersion::Latest => None,
82 PgMinorVersion::Release(v) | PgMinorVersion::Beta(v) | PgMinorVersion::Rc(v) => {
83 Some(*v)
84 }
85 }
86 }
87}
88
89#[derive(Clone, Debug, Eq, PartialEq)]
90pub struct PgVersion {
91 pub major: u16,
92 pub minor: PgMinorVersion,
93 pub url: Option<Url>,
94}
95
96impl PgVersion {
97 pub const fn new(major: u16, minor: PgMinorVersion, url: Option<Url>) -> PgVersion {
98 PgVersion { major, minor, url }
99 }
100
101 pub fn minor(&self) -> Option<u16> {
102 self.minor.version()
103 }
104}
105
106impl Display for PgVersion {
107 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
108 write!(f, "{}{}", self.major, self.minor)
109 }
110}
111
112#[derive(Clone, Debug)]
113pub struct PgConfig {
114 version: Option<PgVersion>,
115 pg_config: Option<PathBuf>,
116 known_props: Option<BTreeMap<String, String>>,
117 base_port: u16,
118 base_testing_port: u16,
119}
120
121impl Display for PgConfig {
122 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
123 write!(f, "{}", self.version().expect("failed to create version string"))
124 }
125}
126
127impl Default for PgConfig {
128 fn default() -> Self {
129 PgConfig {
130 version: None,
131 pg_config: None,
132 known_props: None,
133 base_port: BASE_POSTGRES_PORT_NO,
134 base_testing_port: BASE_POSTGRES_TESTING_PORT_NO,
135 }
136 }
137}
138
139impl From<PgVersion> for PgConfig {
140 fn from(version: PgVersion) -> Self {
141 PgConfig { version: Some(version), pg_config: None, ..Default::default() }
142 }
143}
144
145impl PgConfig {
146 pub fn new(pg_config: PathBuf, base_port: u16, base_testing_port: u16) -> Self {
147 PgConfig {
148 version: None,
149 pg_config: Some(pg_config),
150 known_props: None,
151 base_port,
152 base_testing_port,
153 }
154 }
155
156 pub fn new_with_defaults(pg_config: PathBuf) -> Self {
157 PgConfig {
158 version: None,
159 pg_config: Some(pg_config),
160 known_props: None,
161 base_port: BASE_POSTGRES_PORT_NO,
162 base_testing_port: BASE_POSTGRES_TESTING_PORT_NO,
163 }
164 }
165
166 pub fn from_path() -> Self {
167 let path =
168 pathsearch::find_executable_in_path("pg_config").unwrap_or_else(|| "pg_config".into());
169 Self::new_with_defaults(path)
170 }
171
172 pub fn from_env() -> eyre::Result<Self> {
178 if !Self::is_in_environment() {
179 Err(eyre::eyre!("`PgConfig` not described in the environment"))
180 } else {
181 const PREFIX: &str = "PGRX_PG_CONFIG_";
182
183 let mut known_props = BTreeMap::new();
184 for (k, v) in std::env::vars().filter(|(k, _)| k.starts_with(PREFIX)) {
185 let prop = format!("--{}", k.trim_start_matches(PREFIX).to_lowercase());
187 known_props.insert(prop, v);
188 }
189
190 Ok(Self {
191 version: None,
192 pg_config: None,
193 known_props: Some(known_props),
194 base_port: 0,
195 base_testing_port: 0,
196 })
197 }
198 }
199
200 pub fn is_in_environment() -> bool {
201 match std::env::var("PGRX_PG_CONFIG_AS_ENV") {
202 Ok(value) => value == "true",
203 _ => false,
204 }
205 }
206
207 pub fn is_real(&self) -> bool {
208 self.pg_config.is_some()
209 }
210
211 pub fn label(&self) -> eyre::Result<String> {
212 Ok(format!("pg{}", self.major_version()?))
213 }
214
215 pub fn path(&self) -> Option<PathBuf> {
216 self.pg_config.clone()
217 }
218
219 pub fn parent_path(&self) -> PathBuf {
220 self.path().unwrap().parent().unwrap().to_path_buf()
221 }
222
223 fn parse_version_str(version_str: &str) -> eyre::Result<(u16, PgMinorVersion)> {
224 let version_parts = version_str.split_whitespace().collect::<Vec<&str>>();
225 let mut version = version_parts
226 .get(1)
227 .ok_or_else(|| eyre!("invalid version string: {version_str}"))?
228 .split('.')
229 .collect::<Vec<&str>>();
230
231 let mut beta = false;
232 let mut rc = false;
233
234 if version.len() == 1 {
235 let first = &version[0];
237
238 if first.contains("beta") {
239 beta = true;
240 version = first.split("beta").collect();
241 } else if first.contains("rc") {
242 rc = true;
243 version = first.split("rc").collect();
244 } else {
245 return Err(eyre!("invalid version string: {version_str}"));
246 }
247 }
248
249 let major = u16::from_str(version[0])
250 .map_err(|e| eyre!("invalid major version number `{}`: {:?}", version[0], e))?;
251 let mut minor = version[1];
252 let mut end_index = minor.len();
253 for (i, c) in minor.chars().enumerate() {
254 if !c.is_ascii_digit() {
255 end_index = i;
256 break;
257 }
258 }
259 minor = &minor[0..end_index];
260 let minor = u16::from_str(minor)
261 .map_err(|e| eyre!("invalid minor version number `{minor}`: {e:?}"))?;
262 let minor = if beta {
263 PgMinorVersion::Beta(minor)
264 } else if rc {
265 PgMinorVersion::Rc(minor)
266 } else {
267 PgMinorVersion::Release(minor)
268 };
269 Ok((major, minor))
270 }
271
272 pub fn get_version(&self) -> eyre::Result<PgVersion> {
273 let version_string = self.run("--version")?;
274 let (major, minor) = Self::parse_version_str(&version_string)?;
275 Ok(PgVersion::new(major, minor, None))
276 }
277
278 pub fn major_version(&self) -> eyre::Result<u16> {
279 match &self.version {
280 Some(version) => Ok(version.major),
281 None => Ok(self.get_version()?.major),
282 }
283 }
284
285 fn minor_version(&self) -> eyre::Result<PgMinorVersion> {
286 match &self.version {
287 Some(version) => Ok(version.minor),
288 None => Ok(self.get_version()?.minor),
289 }
290 }
291
292 pub fn version(&self) -> eyre::Result<String> {
293 match self.version.as_ref() {
294 Some(pgver) => Ok(pgver.to_string()),
295 None => {
296 let major = self.major_version()?;
297 let minor = self.minor_version()?;
298 let version = format!("{major}{minor}");
299 Ok(version)
300 }
301 }
302 }
303
304 pub fn url(&self) -> Option<&Url> {
305 match &self.version {
306 Some(version) => version.url.as_ref(),
307 None => None,
308 }
309 }
310
311 pub fn port(&self) -> eyre::Result<u16> {
312 Ok(self.base_port + self.major_version()?)
313 }
314
315 pub fn test_port(&self) -> eyre::Result<u16> {
316 Ok(self.base_testing_port + self.major_version()?)
317 }
318
319 pub fn host(&self) -> &'static str {
320 "localhost"
321 }
322
323 pub fn bin_dir(&self) -> eyre::Result<PathBuf> {
324 Ok(self.run("--bindir")?.into())
325 }
326
327 pub fn postmaster_path(&self) -> eyre::Result<PathBuf> {
328 let mut path = self.bin_dir()?;
329 path.push("postgres");
330
331 Ok(path)
332 }
333
334 pub fn initdb_path(&self) -> eyre::Result<PathBuf> {
335 let mut path = self.bin_dir()?;
336 path.push("initdb");
337 Ok(path)
338 }
339
340 pub fn createdb_path(&self) -> eyre::Result<PathBuf> {
341 let mut path = self.bin_dir()?;
342 path.push("createdb");
343 Ok(path)
344 }
345
346 pub fn dropdb_path(&self) -> eyre::Result<PathBuf> {
347 let mut path = self.bin_dir()?;
348 path.push("dropdb");
349 Ok(path)
350 }
351
352 pub fn psql_path(&self) -> eyre::Result<PathBuf> {
353 let mut path = self.bin_dir()?;
354 path.push("psql");
355 Ok(path)
356 }
357
358 pub fn data_dir(&self) -> eyre::Result<PathBuf> {
359 let mut path = Pgrx::home()?;
360 path.push(format!("data-{}", self.major_version()?));
361 Ok(path)
362 }
363
364 pub fn log_file(&self) -> eyre::Result<PathBuf> {
365 let mut path = Pgrx::home()?;
366 path.push(format!("{}.log", self.major_version()?));
367 Ok(path)
368 }
369
370 pub fn configure(&self) -> eyre::Result<BTreeMap<String, String>> {
372 let stdout = self.run("--configure")?;
373 Ok(stdout
374 .split('\'')
375 .filter(|s| s != &"" && s != &" ")
376 .map(|entry| match entry.split_once('=') {
377 Some((k, v)) => (k.to_owned(), v.to_owned()),
378 None => (entry.to_owned(), String::from("")),
380 })
381 .collect())
382 }
383
384 pub fn includedir_server(&self) -> eyre::Result<PathBuf> {
385 Ok(self.run("--includedir-server")?.into())
386 }
387
388 pub fn pkglibdir(&self) -> eyre::Result<PathBuf> {
389 Ok(self.run("--pkglibdir")?.into())
390 }
391
392 pub fn sharedir(&self) -> eyre::Result<PathBuf> {
393 Ok(self.run("--sharedir")?.into())
394 }
395
396 pub fn cppflags(&self) -> eyre::Result<OsString> {
397 Ok(self.run("--cppflags")?.into())
398 }
399
400 pub fn extension_dir(&self) -> eyre::Result<PathBuf> {
401 let mut path = self.sharedir()?;
402 path.push("extension");
403 Ok(path)
404 }
405
406 fn run(&self, arg: &str) -> eyre::Result<String> {
407 if self.known_props.is_some() {
408 Ok(self
411 .known_props
412 .as_ref()
413 .unwrap()
414 .get(arg)
415 .ok_or_else(|| {
416 std::io::Error::new(
417 ErrorKind::InvalidData,
418 format!("`PgConfig` has no known property named {arg}"),
419 )
420 })
421 .cloned()?)
422 } else {
423 let pg_config = self.pg_config.clone().unwrap_or_else(|| {
426 std::env::var("PG_CONFIG").unwrap_or_else(|_| "pg_config".to_string()).into()
427 });
428
429 match Command::new(&pg_config).arg(arg).output() {
430 Ok(output) => Ok(String::from_utf8(output.stdout).unwrap().trim().to_string()),
431 Err(e) => match e.kind() {
432 ErrorKind::NotFound => Err(e).wrap_err_with(|| {
433 let pg_config_str = pg_config.display().to_string();
434
435 if pg_config_str == "pg_config" {
436 format!("Unable to find `{}` on the system $PATH", "pg_config".yellow())
437 } else if pg_config_str.starts_with('~') {
438 format!("The specified pg_config binary, {}, does not exist. The shell didn't expand the `~`", pg_config_str.yellow())
439 } else {
440 format!(
441 "The specified pg_config binary, `{}`, does not exist",
442 pg_config_str.yellow()
443 )
444 }
445 }),
446 _ => Err(e.into()),
447 },
448 }
449 }
450 }
451}
452
453#[derive(Debug)]
454pub struct Pgrx {
455 pg_configs: Vec<PgConfig>,
456 base_port: u16,
457 base_testing_port: u16,
458}
459
460impl Default for Pgrx {
461 fn default() -> Self {
462 Self {
463 pg_configs: vec![],
464 base_port: BASE_POSTGRES_PORT_NO,
465 base_testing_port: BASE_POSTGRES_TESTING_PORT_NO,
466 }
467 }
468}
469
470#[derive(Debug, Default, Serialize, Deserialize)]
471pub struct ConfigToml {
472 pub configs: HashMap<String, PathBuf>,
473 #[serde(skip_serializing_if = "Option::is_none")]
474 pub base_port: Option<u16>,
475 #[serde(skip_serializing_if = "Option::is_none")]
476 pub base_testing_port: Option<u16>,
477}
478
479pub enum PgConfigSelector<'a> {
480 All,
481 Specific(&'a str),
482 Environment,
483}
484
485impl<'a> PgConfigSelector<'a> {
486 pub fn new(label: &'a str) -> Self {
487 if label == "all" {
488 PgConfigSelector::All
489 } else {
490 PgConfigSelector::Specific(label)
491 }
492 }
493}
494
495#[derive(Debug, Error)]
496pub enum PgrxHomeError {
497 #[error("You don't seem to have a home directory")]
498 NoHomeDirectory,
499 #[error("$PGRX_HOME does not exist")]
501 MissingPgrxHome(PathBuf),
502 #[error(transparent)]
503 IoError(#[from] std::io::Error),
504}
505
506impl From<PgrxHomeError> for std::io::Error {
507 fn from(value: PgrxHomeError) -> Self {
508 match value {
509 PgrxHomeError::NoHomeDirectory => {
510 std::io::Error::new(ErrorKind::NotFound, value.to_string())
511 }
512 PgrxHomeError::MissingPgrxHome(_) => {
513 std::io::Error::new(ErrorKind::NotFound, value.to_string())
514 }
515 PgrxHomeError::IoError(e) => e,
516 }
517 }
518}
519
520impl Pgrx {
521 pub fn new(base_port: u16, base_testing_port: u16) -> Self {
522 Pgrx { pg_configs: vec![], base_port, base_testing_port }
523 }
524
525 pub fn from_config() -> eyre::Result<Self> {
526 match std::env::var("PGRX_PG_CONFIG_PATH") {
527 Ok(pg_config) => {
528 let mut pgrx = Pgrx::default();
530 pgrx.push(PgConfig::new(pg_config.into(), pgrx.base_port, pgrx.base_testing_port));
531 Ok(pgrx)
532 }
533 Err(_) => {
534 let path = Pgrx::config_toml()?;
536 if !path.try_exists()? {
537 return Err(eyre!(
538 "{} not found. Have you run `{}` yet?",
539 path.display(),
540 "cargo pgrx init".bold().yellow()
541 ));
542 };
543
544 match toml::from_str::<ConfigToml>(&std::fs::read_to_string(&path)?) {
545 Ok(configs) => {
546 let mut pgrx = Pgrx::new(
547 configs.base_port.unwrap_or(BASE_POSTGRES_PORT_NO),
548 configs.base_testing_port.unwrap_or(BASE_POSTGRES_TESTING_PORT_NO),
549 );
550
551 for (_, v) in configs.configs {
552 pgrx.push(PgConfig::new(v, pgrx.base_port, pgrx.base_testing_port));
553 }
554 Ok(pgrx)
555 }
556 Err(e) => {
557 Err(e).wrap_err_with(|| format!("Could not read `{}`", path.display()))
558 }
559 }
560 }
561 }
562 }
563
564 pub fn push(&mut self, pg_config: PgConfig) {
565 self.pg_configs.push(pg_config);
566 }
567
568 pub fn iter(
578 &self,
579 which: PgConfigSelector,
580 ) -> impl std::iter::Iterator<Item = eyre::Result<PgConfig>> {
581 match (which, PgConfig::is_in_environment()) {
582 (PgConfigSelector::All, true) | (PgConfigSelector::Environment, _) => {
583 vec![PgConfig::from_env()].into_iter()
584 }
585
586 (PgConfigSelector::All, _) => {
587 let mut configs = self.pg_configs.iter().collect::<Vec<_>>();
588 configs.sort_by(|a, b| {
589 a.major_version()
590 .expect("no major version")
591 .cmp(&b.major_version().expect("no major version"))
592 });
593
594 configs.into_iter().map(|c| Ok(c.clone())).collect::<Vec<_>>().into_iter()
595 }
596 (PgConfigSelector::Specific(label), _) => vec![self.get(label)].into_iter(),
597 }
598 }
599
600 pub fn get(&self, label: &str) -> eyre::Result<PgConfig> {
601 for pg_config in self.pg_configs.iter() {
602 if pg_config.label()? == label {
603 return Ok(pg_config.clone());
604 }
605 }
606 Err(eyre!("Postgres `{label}` is not managed by pgrx"))
607 }
608
609 pub fn is_feature_flag(&self, label: &str) -> bool {
612 for pgver in SUPPORTED_VERSIONS() {
613 if label == format!("pg{}", pgver.major) {
614 return true;
615 }
616 }
617 false
618 }
619
620 pub fn home() -> Result<PathBuf, PgrxHomeError> {
621 let pgrx_home = std::env::var("PGRX_HOME").map_or_else(
622 |_| {
623 let mut pgrx_home = match home::home_dir() {
624 Some(home) => home,
625 None => return Err(PgrxHomeError::NoHomeDirectory),
626 };
627
628 pgrx_home.push(".pgrx");
629 Ok(pgrx_home)
630 },
631 |v| Ok(v.into()),
632 )?;
633
634 match pgrx_home.try_exists() {
635 Ok(true) => Ok(pgrx_home),
636 Ok(false) => Err(PgrxHomeError::MissingPgrxHome(pgrx_home)),
637 Err(e) => Err(PgrxHomeError::IoError(e)),
638 }
639 }
640
641 pub fn postmaster_stub_dir() -> Result<PathBuf, std::io::Error> {
647 let mut stub_dir = Self::home()?;
648 stub_dir.push("postmaster_stubs");
649 Ok(stub_dir)
650 }
651
652 pub fn config_toml() -> Result<PathBuf, std::io::Error> {
653 let mut path = Pgrx::home()?;
654 path.push("config.toml");
655 Ok(path)
656 }
657}
658
659#[allow(non_snake_case)]
660pub fn SUPPORTED_VERSIONS() -> Vec<PgVersion> {
661 vec![
662 PgVersion::new(12, PgMinorVersion::Latest, None),
663 PgVersion::new(13, PgMinorVersion::Latest, None),
664 PgVersion::new(14, PgMinorVersion::Latest, None),
665 PgVersion::new(15, PgMinorVersion::Latest, None),
666 PgVersion::new(16, PgMinorVersion::Latest, None),
667 PgVersion::new(17, PgMinorVersion::Latest, None),
668 ]
669}
670
671pub fn is_supported_major_version(v: u16) -> bool {
672 SUPPORTED_VERSIONS().into_iter().any(|pgver| pgver.major == v)
673}
674
675pub fn createdb(
676 pg_config: &PgConfig,
677 dbname: &str,
678 is_test: bool,
679 if_not_exists: bool,
680 runas: Option<String>,
681) -> eyre::Result<bool> {
682 if if_not_exists && does_db_exist(pg_config, dbname)? {
683 return Ok(false);
684 }
685
686 println!("{} database {}", " Creating".bold().green(), dbname);
687 let createdb_path = pg_config.createdb_path()?;
688 let mut command = if let Some(runas) = runas {
689 let mut cmd = Command::new("sudo");
690 cmd.arg("-u").arg(runas).arg(createdb_path);
691 cmd
692 } else {
693 Command::new(createdb_path)
694 };
695 command
696 .env_remove("PGDATABASE")
697 .env_remove("PGHOST")
698 .env_remove("PGPORT")
699 .env_remove("PGUSER")
700 .arg("-h")
701 .arg(pg_config.host())
702 .arg("-p")
703 .arg(if is_test {
704 pg_config.test_port()?.to_string()
705 } else {
706 pg_config.port()?.to_string()
707 })
708 .arg(dbname)
709 .stdout(Stdio::piped())
710 .stderr(Stdio::piped());
711
712 let command_str = format!("{command:?}");
713
714 let child = command.spawn().wrap_err_with(|| {
715 format!("Failed to spawn process for creating database using command: '{command_str}': ")
716 })?;
717
718 let output = child.wait_with_output().wrap_err_with(|| {
719 format!(
720 "failed waiting for spawned process to create database using command: '{command_str}': "
721 )
722 })?;
723
724 if !output.status.success() {
725 return Err(eyre!(
726 "problem running createdb: {}\n\n{}{}",
727 command_str,
728 String::from_utf8(output.stdout).unwrap(),
729 String::from_utf8(output.stderr).unwrap()
730 ));
731 }
732
733 Ok(true)
734}
735
736fn does_db_exist(pg_config: &PgConfig, dbname: &str) -> eyre::Result<bool> {
737 let mut command = Command::new(pg_config.psql_path()?);
738 command
739 .arg("-XqAt")
740 .env_remove("PGUSER")
741 .arg("-h")
742 .arg(pg_config.host())
743 .arg("-p")
744 .arg(pg_config.port()?.to_string())
745 .arg("template1")
746 .arg("-c")
747 .arg(&format!(
748 "select count(*) from pg_database where datname = '{}';",
749 dbname.replace('\'', "''")
750 ))
751 .stdout(Stdio::piped())
752 .stderr(Stdio::piped());
753
754 let command_str = format!("{command:?}");
755 let output = command.output()?;
756
757 if !output.status.success() {
758 Err(eyre!(
759 "problem checking if database '{}' exists: {}\n\n{}{}",
760 dbname,
761 command_str,
762 String::from_utf8(output.stdout).unwrap(),
763 String::from_utf8(output.stderr).unwrap()
764 ))
765 } else {
766 let count = i32::from_str(String::from_utf8(output.stdout).unwrap().trim())
767 .wrap_err("result is not a number")?;
768 Ok(count > 0)
769 }
770}
771
772#[test]
773fn parse_version() {
774 let versions = [
776 ("PostgreSQL 10.22", 10, 22),
777 ("PostgreSQL 11.2", 11, 2),
778 ("PostgreSQL 11.17", 11, 17),
779 ("PostgreSQL 12.12", 12, 12),
780 ("PostgreSQL 13.8", 13, 8),
781 ("PostgreSQL 14.5", 14, 5),
782 ("PostgreSQL 11.2-FOO-BAR+", 11, 2),
783 ("PostgreSQL 10.22-", 10, 22),
784 ];
785 for (s, major_expected, minor_expected) in versions {
786 let (major, minor) =
787 PgConfig::parse_version_str(s).expect("Unable to parse version string");
788 assert_eq!(major, major_expected, "Major version should match");
789 assert_eq!(minor.version(), Some(minor_expected), "Minor version should match");
790 }
791
792 let _ = PgConfig::parse_version_str("10.22").expect_err("Parsed invalid version string");
794 let _ =
795 PgConfig::parse_version_str("PostgresSQL 10").expect_err("Parsed invalid version string");
796 let _ =
797 PgConfig::parse_version_str("PostgresSQL 10.").expect_err("Parsed invalid version string");
798 let _ =
799 PgConfig::parse_version_str("PostgresSQL 12.f").expect_err("Parsed invalid version string");
800 let _ =
801 PgConfig::parse_version_str("PostgresSQL .53").expect_err("Parsed invalid version string");
802}
803
804#[test]
805fn from_empty_env() -> eyre::Result<()> {
806 let pg_config = PgConfig::from_env();
808 assert!(pg_config.is_err());
809
810 std::env::set_var("PGRX_PG_CONFIG_AS_ENV", "true");
812 std::env::set_var("PGRX_PG_CONFIG_VERSION", "PostgresSQL 15.1");
813 std::env::set_var("PGRX_PG_CONFIG_INCLUDEDIR-SERVER", "/path/to/server/headers");
814 std::env::set_var("PGRX_PG_CONFIG_CPPFLAGS", "some cpp flags");
815
816 let pg_config = PgConfig::from_env().unwrap();
817 assert_eq!(pg_config.major_version()?, 15, "Major version should match");
818 assert_eq!(
819 pg_config.minor_version()?,
820 PgMinorVersion::Release(1),
821 "Minor version should match"
822 );
823 assert_eq!(
824 pg_config.includedir_server()?,
825 PathBuf::from("/path/to/server/headers"),
826 "includdir_server should match"
827 );
828 assert_eq!(pg_config.cppflags()?, OsString::from("some cpp flags"), "cppflags should match");
829
830 assert!(pg_config.sharedir().is_err());
832 Ok(())
833}