pgrx_sql_entity_graph/
control_file.rs1use super::{SqlGraphEntity, SqlGraphIdentifier, ToSql};
19use std::collections::HashMap;
20use std::path::PathBuf;
21use thiserror::Error;
22
23#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
36pub struct ControlFile {
37 pub comment: String,
38 pub default_version: String,
39 pub module_pathname: Option<String>,
40 pub relocatable: bool,
41 pub superuser: bool,
42 pub schema: Option<String>,
43 pub trusted: bool,
44}
45
46impl ControlFile {
47 #[allow(clippy::should_implement_trait)]
70 pub fn from_str(input: &str) -> Result<Self, ControlFileError> {
71 fn do_var_replacements(mut input: String) -> Result<String, ControlFileError> {
72 const CARGO_VERSION: &str = "@CARGO_VERSION@";
73
74 if input.contains(CARGO_VERSION) {
76 input = input.replace(
77 CARGO_VERSION,
78 &std::env::var("CARGO_PKG_VERSION").map_err(|_| {
79 ControlFileError::MissingEnvvar("CARGO_PKG_VERSION".to_string())
80 })?,
81 );
82 }
83
84 Ok(input)
85 }
86
87 let mut temp = HashMap::new();
88 for line in input.lines() {
89 let parts: Vec<&str> = line.split('=').collect();
90
91 if parts.len() != 2 {
92 continue;
93 }
94
95 let (k, v) = (parts.first().unwrap().trim(), parts.get(1).unwrap().trim());
96
97 let v = v.trim_start_matches('\'');
98 let v = v.trim_end_matches('\'');
99
100 temp.insert(k, do_var_replacements(v.to_string())?);
101 }
102 let control_file = ControlFile {
103 comment: temp
104 .get("comment")
105 .ok_or(ControlFileError::MissingField { field: "comment" })?
106 .to_string(),
107 default_version: temp
108 .get("default_version")
109 .ok_or(ControlFileError::MissingField { field: "default_version" })?
110 .to_string(),
111 module_pathname: temp.get("module_pathname").map(|v| v.to_string()),
112 relocatable: temp
113 .get("relocatable")
114 .ok_or(ControlFileError::MissingField { field: "relocatable" })?
115 == "true",
116 superuser: temp
117 .get("superuser")
118 .ok_or(ControlFileError::MissingField { field: "superuser" })?
119 == "true",
120 schema: temp.get("schema").map(|v| v.to_string()),
121 trusted: if let Some(v) = temp.get("trusted") { v == "true" } else { false },
122 };
123
124 if !control_file.superuser && control_file.trusted {
125 return Err(ControlFileError::RedundantField { field: "trusted" });
127 }
128
129 Ok(control_file)
130 }
131}
132
133impl From<ControlFile> for SqlGraphEntity {
134 fn from(val: ControlFile) -> Self {
135 SqlGraphEntity::ExtensionRoot(val)
136 }
137}
138
139#[derive(Debug, Error)]
141pub enum ControlFileError {
142 #[error("Filesystem error reading control file")]
143 IOError {
144 #[from]
145 error: std::io::Error,
146 },
147 #[error("Missing field in control file! Please add `{field}`.")]
148 MissingField { field: &'static str },
149 #[error("Redundant field in control file! Please remove `{field}`.")]
150 RedundantField { field: &'static str },
151 #[error("Missing environment variable: {0}")]
152 MissingEnvvar(String),
153}
154
155impl TryFrom<PathBuf> for ControlFile {
156 type Error = ControlFileError;
157
158 fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
159 let contents = std::fs::read_to_string(value)?;
160 ControlFile::try_from(contents.as_str())
161 }
162}
163
164impl TryFrom<&str> for ControlFile {
165 type Error = ControlFileError;
166
167 fn try_from(input: &str) -> Result<Self, Self::Error> {
168 Self::from_str(input)
169 }
170}
171
172impl ToSql for ControlFile {
173 fn to_sql(&self, _context: &super::PgrxSql) -> eyre::Result<String> {
174 let comment = r#"
175/*
176This file is auto generated by pgrx.
177
178The ordering of items is not stable, it is driven by a dependency graph.
179*/
180"#;
181 Ok(comment.into())
182 }
183}
184
185impl SqlGraphIdentifier for ControlFile {
186 fn dot_identifier(&self) -> String {
187 "extension root".into()
188 }
189 fn rust_identifier(&self) -> String {
190 "root".into()
191 }
192
193 fn file(&self) -> Option<&'static str> {
194 None
195 }
196
197 fn line(&self) -> Option<u32> {
198 None
199 }
200}