pgx_utils/sql_entity_graph/
control_file.rs

1/*
2Portions Copyright 2019-2021 ZomboDB, LLC.
3Portions Copyright 2021-2022 Technology Concepts & Design, Inc. <support@tcdi.com>
4
5All rights reserved.
6
7Use of this source code is governed by the MIT license that can be found in the LICENSE file.
8*/
9/*!
10
11`pgx_module_magic!()` related macro expansion for Rust to SQL translation
12
13> Like all of the [`sql_entity_graph`][crate::sql_entity_graph] APIs, this is considered **internal**
14to the `pgx` framework and very subject to change between versions. While you may use this, please do it with caution.
15
16*/
17use super::{SqlGraphEntity, SqlGraphIdentifier, ToSql};
18use core::convert::TryFrom;
19use std::collections::HashMap;
20use tracing_error::SpanTrace;
21
22/// The parsed contents of a `.control` file.
23///
24/// ```rust
25/// use pgx_utils::sql_entity_graph::ControlFile;
26/// use std::convert::TryFrom;
27/// # fn main() -> eyre::Result<()> {
28/// let context = include_str!("../../../pgx-examples/custom_types/custom_types.control");
29/// let _control_file = ControlFile::try_from(context)?;
30/// # Ok(())
31/// # }
32/// ```
33#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
34pub struct ControlFile {
35    pub comment: String,
36    pub default_version: String,
37    pub module_pathname: Option<String>,
38    pub relocatable: bool,
39    pub superuser: bool,
40    pub schema: Option<String>,
41}
42
43impl ControlFile {
44    /// Parse a `.control` file.
45    ///
46    /// ```rust
47    /// use pgx_utils::sql_entity_graph::ControlFile;
48    /// # fn main() -> eyre::Result<()> {
49    /// let context = include_str!("../../../pgx-examples/custom_types/custom_types.control");
50    /// let _control_file = ControlFile::from_str(context)?;
51    /// # Ok(())
52    /// # }
53    /// ```
54    #[tracing::instrument(level = "error")]
55    pub fn from_str(input: &str) -> Result<Self, ControlFileError> {
56        let mut temp = HashMap::new();
57        for line in input.lines() {
58            let parts: Vec<&str> = line.split('=').collect();
59
60            if parts.len() != 2 {
61                continue;
62            }
63
64            let (k, v) = (parts.get(0).unwrap().trim(), parts.get(1).unwrap().trim());
65
66            let v = v.trim_start_matches('\'');
67            let v = v.trim_end_matches('\'');
68
69            temp.insert(k, v);
70        }
71        Ok(ControlFile {
72            comment: temp
73                .get("comment")
74                .ok_or(ControlFileError::MissingField {
75                    field: "comment",
76                    context: SpanTrace::capture(),
77                })?
78                .to_string(),
79            default_version: temp
80                .get("default_version")
81                .ok_or(ControlFileError::MissingField {
82                    field: "default_version",
83                    context: SpanTrace::capture(),
84                })?
85                .to_string(),
86            module_pathname: temp.get("module_pathname").map(|v| v.to_string()),
87            relocatable: temp.get("relocatable").ok_or(ControlFileError::MissingField {
88                field: "relocatable",
89                context: SpanTrace::capture(),
90            })? == &"true",
91            superuser: temp.get("superuser").ok_or(ControlFileError::MissingField {
92                field: "superuser",
93                context: SpanTrace::capture(),
94            })? == &"true",
95            schema: temp.get("schema").map(|v| v.to_string()),
96        })
97    }
98}
99
100impl From<ControlFile> for SqlGraphEntity {
101    fn from(val: ControlFile) -> Self {
102        SqlGraphEntity::ExtensionRoot(val)
103    }
104}
105
106/// An error met while parsing a `.control` file.
107#[derive(Debug, Clone)]
108pub enum ControlFileError {
109    MissingField { field: &'static str, context: SpanTrace },
110}
111
112impl std::fmt::Display for ControlFileError {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        match self {
115            ControlFileError::MissingField { field, context } => {
116                write!(f, "Missing field in control file! Please add `{}`.", field)?;
117                context.fmt(f)?;
118            }
119        };
120        Ok(())
121    }
122}
123
124impl std::error::Error for ControlFileError {}
125
126impl TryFrom<&str> for ControlFile {
127    type Error = ControlFileError;
128
129    fn try_from(input: &str) -> Result<Self, Self::Error> {
130        Self::from_str(input)
131    }
132}
133
134impl ToSql for ControlFile {
135    #[tracing::instrument(level = "debug", err, skip(self, _context))]
136    fn to_sql(&self, _context: &super::PgxSql) -> eyre::Result<String> {
137        let sql = format!(
138            "\
139            /* \n\
140            This file is auto generated by pgx.\n\
141            \n\
142            The ordering of items is not stable, it is driven by a dependency graph.\n\
143            */\
144        "
145        );
146        tracing::trace!(%sql);
147        Ok(sql)
148    }
149}
150
151impl SqlGraphIdentifier for ControlFile {
152    fn dot_identifier(&self) -> String {
153        format!("extension root")
154    }
155    fn rust_identifier(&self) -> String {
156        format!("root")
157    }
158
159    fn file(&self) -> Option<&'static str> {
160        None
161    }
162
163    fn line(&self) -> Option<u32> {
164        None
165    }
166}