1use std::{
2 path::{Path, PathBuf},
3 str::FromStr,
4};
5
6use either::Either;
7
8use crate::{PostgresExtensionsCollection, PostgresVersion, SqlDialect};
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct EngineVersion(Either<PostgresVersion, String>);
12
13impl<'de> serde::Deserialize<'de> for EngineVersion {
14 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
15 where
16 D: serde::Deserializer<'de>,
17 {
18 let s = String::deserialize(deserializer)?;
19 Self::from_str(&s)
20 .map_err(|e| serde::de::Error::custom(format!("invalid value for EngineVersion {e}")))
21 }
22}
23
24impl FromStr for EngineVersion {
25 type Err = String;
26
27 fn from_str(s: &str) -> Result<Self, Self::Err> {
28 if let Ok(pg_ver) = PostgresVersion::from_str(s) {
29 Ok(Self(Either::Left(pg_ver)))
30 } else {
31 Ok(Self(Either::Right(s.to_string())))
33 }
34 }
35}
36
37impl std::fmt::Display for EngineVersion {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match &self.0 {
40 Either::Left(v) => write!(f, "{v}"),
41 Either::Right(v) => write!(f, "{v}"),
42 }
43 }
44}
45
46impl EngineVersion {
47 pub fn is_wellknown(&self) -> bool {
48 self.0.is_left()
49 }
50
51 pub fn definition_base_path<P: AsRef<Path>>(&self, home: P) -> PathBuf {
52 if self.is_wellknown() {
53 home.as_ref()
54 .join("postgres")
55 .join(Path::new(&self.to_string()))
56 } else {
57 home.as_ref()
58 .join("postgres")
59 .join("custom")
60 .join(self.to_string())
61 }
62 }
63}
64
65#[derive(serde::Deserialize, Debug)]
67pub enum WellKnownDbEngine {
68 PostgreSQL,
70}
71
72impl FromStr for WellKnownDbEngine {
73 type Err = String;
74
75 fn from_str(s: &str) -> Result<Self, Self::Err> {
76 if s == "PostgreSQL" {
77 Ok(Self::PostgreSQL)
78 } else {
79 Err(s.to_string())
80 }
81 }
82}
83
84#[derive(Debug)]
86pub struct DatabaseEngine(Either<WellKnownDbEngine, String>);
87
88impl<'de> serde::Deserialize<'de> for DatabaseEngine {
89 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
90 where
91 D: serde::Deserializer<'de>,
92 {
93 let s = String::deserialize(deserializer)?;
94 Ok(match s.parse::<WellKnownDbEngine>() {
95 Ok(wk) => DatabaseEngine(Either::Left(wk)),
96 Err(_) => DatabaseEngine(Either::Right(s)),
97 })
98 }
99}
100
101impl DatabaseEngine {
102 #[must_use]
103 pub fn is_wellknown(&self) -> bool {
104 self.0.is_left()
105 }
106
107 #[must_use]
108 pub fn is_custom(&self) -> bool {
109 self.0.is_right()
110 }
111
112 #[must_use]
113 pub fn as_wellknown(&self) -> Option<&WellKnownDbEngine> {
114 match &self.0 {
115 Either::Left(l) => Some(l),
116 Either::Right(_) => None,
117 }
118 }
119
120 #[must_use]
121 pub fn as_custom(&self) -> Option<&String> {
122 match &self.0 {
123 Either::Right(r) => Some(r),
124 Either::Left(_) => None,
125 }
126 }
127}
128
129#[derive(Debug, serde::Deserialize)]
132pub struct SqlFunMetadata {
133 engine: DatabaseEngine,
135 dialect: SqlDialect,
137 #[serde(rename = "version")]
139 engine_version: Option<EngineVersion>,
140 extensions: Option<PostgresExtensionsCollection>,
142 search_path: Option<Vec<String>>,
144 cte_catalog: Option<PathBuf>,
146 connector: Option<String>,
148}
149
150#[derive(Debug, thiserror::Error)]
152pub enum MetadataError {
153 #[error("sql-fun metadata can not load from {0}: {1}")]
155 MetadataLoadFromFile(PathBuf, String),
156
157 #[error("metadata io error {0}")]
159 Io(#[from] std::io::Error),
160
161 #[error("metadata format error {0}")]
163 Serde(#[from] toml::de::Error),
164
165 #[error("CTE catalog not exists in {0}")]
167 CteCatalogNotExist(PathBuf),
168}
169
170impl MetadataError {
171 pub fn metadata_load_from_file<T, P: AsRef<Path>>(path: P, message: &str) -> Result<T, Self> {
177 Err(Self::MetadataLoadFromFile(
178 path.as_ref().to_path_buf(),
179 String::from(message),
180 ))
181 }
182
183 pub fn cte_catalog_not_exist<T, P: AsRef<Path>>(path: P) -> Result<T, Self> {
189 Err(Self::CteCatalogNotExist(path.as_ref().to_path_buf()))
190 }
191}
192
193impl SqlFunMetadata {
194 pub fn load_from<P: AsRef<Path> + std::fmt::Debug>(path: P) -> Result<Self, MetadataError> {
203 let file_text = std::fs::read_to_string(&path)?;
204
205 Self::from_str(&file_text, path)
206 }
207
208 pub(crate) fn from_str<P: AsRef<Path> + std::fmt::Debug>(
210 source: &str,
211 path_hint: P,
212 ) -> Result<Self, MetadataError> {
213 use MetadataError as Error;
214 let cargo_metadata: toml::Value = toml::from_str(source)?;
215 let Some(package) = cargo_metadata.get("package") else {
216 Error::metadata_load_from_file(
217 &path_hint,
218 &format!("package not defined in {path_hint:?}"),
219 )?
220 };
221 let Some(metadata) = package.get("metadata") else {
222 Error::metadata_load_from_file(
223 &path_hint,
224 &format!("package.metadata not defined in {path_hint:?}"),
225 )?
226 };
227 let Some(database) = metadata.get("database") else {
228 Error::metadata_load_from_file(
229 &path_hint,
230 &format!("package.metadata.database not defined in {path_hint:?}"),
231 )?
232 };
233
234 Self::from_value(database)
235 }
236
237 pub(crate) fn from_value(database: &toml::Value) -> Result<Self, MetadataError> {
243 let db_matadata: Self = database.clone().try_into()?;
244 Ok(db_matadata)
245 }
246
247 pub fn cte_catalog(&self) -> Result<Option<&PathBuf>, MetadataError> {
253 let Some(cte_catalog) = &self.cte_catalog else {
254 return Ok(None);
255 };
256 if cte_catalog.exists() {
257 Ok(Some(cte_catalog))
258 } else {
259 MetadataError::cte_catalog_not_exist(cte_catalog)?
260 }
261 }
262
263 #[must_use]
265 pub fn sql_dialect(&self) -> SqlDialect {
266 self.dialect
267 }
268
269 #[must_use]
271 pub fn engine(&self) -> &DatabaseEngine {
272 &self.engine
273 }
274
275 #[must_use]
282 pub fn engine_version(&self) -> &Option<EngineVersion> {
283 &self.engine_version
284 }
285
286 #[must_use]
293 pub fn search_path(&self) -> &Option<Vec<String>> {
294 &self.search_path
295 }
296
297 #[must_use]
299 pub fn postgres_extensions(&self) -> &Option<PostgresExtensionsCollection> {
300 &self.extensions
301 }
302
303 #[must_use]
305 pub fn connector(&self) -> &Option<String> {
306 &self.connector
307 }
308}