Skip to main content

pgrx_sql_entity_graph/
lib.rs

1//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2//LICENSE
3//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4//LICENSE
5//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
6//LICENSE
7//LICENSE All rights reserved.
8//LICENSE
9//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10/*!
11
12Rust to SQL mapping support.
13
14> Like all of the [`sql_entity_graph`][crate] APIs, this is considered **internal**
15> to the `pgrx` framework and very subject to change between versions. While you may use this, please do it with caution.
16
17*/
18#[macro_export]
19macro_rules! pgrx_resolved_type {
20    ($ty:ty) => {
21        concat!(module_path!(), "::", stringify!($ty))
22    };
23}
24
25pub use aggregate::entity::{AggregateTypeEntity, PgAggregateEntity};
26pub use aggregate::{
27    AggregateType, AggregateTypeList, FinalizeModify, ParallelOption, PgAggregate,
28};
29pub use control_file::ControlFile;
30pub use enrich::CodeEnrichment;
31pub use extension_sql::entity::{ExtensionSqlEntity, SqlDeclaredEntity};
32pub use extension_sql::{ExtensionSql, ExtensionSqlFile, SqlDeclared};
33pub use extern_args::{ExternArgs, parse_extern_attributes};
34pub use pg_extern::attribute::Attribute;
35pub use pg_extern::entity::{
36    PgCastEntity, PgExternArgumentEntity, PgExternEntity, PgExternReturnEntity,
37    PgExternReturnEntityIteratedItem, PgOperatorEntity,
38};
39pub use pg_extern::{NameMacro, PgCast, PgExtern, PgExternArgument, PgOperator};
40pub use pg_trigger::PgTrigger;
41pub use pg_trigger::attribute::PgTriggerAttribute;
42pub use pg_trigger::entity::PgTriggerEntity;
43pub use pgrx_sql::PgrxSql;
44pub use positioning_ref::PositioningRef;
45pub use postgres_enum::PostgresEnum;
46pub use postgres_enum::entity::PostgresEnumEntity;
47pub use postgres_hash::PostgresHash;
48pub use postgres_hash::entity::PostgresHashEntity;
49pub use postgres_ord::PostgresOrd;
50pub use postgres_ord::entity::PostgresOrdEntity;
51pub use postgres_type::PostgresTypeDerive;
52pub use postgres_type::entity::PostgresTypeEntity;
53pub use schema::Schema;
54pub use schema::entity::SchemaEntity;
55pub use to_sql::entity::ToSqlConfigEntity;
56pub use to_sql::{ToSql, ToSqlConfig};
57pub use used_type::{UsedType, UsedTypeEntity};
58
59pub(crate) mod aggregate;
60pub(crate) mod composite_type;
61pub(crate) mod control_file;
62pub(crate) mod enrich;
63pub(crate) mod extension_sql;
64pub(crate) mod extern_args;
65pub(crate) mod finfo;
66#[macro_use]
67pub(crate) mod fmt;
68pub mod lifetimes;
69pub mod metadata;
70pub(crate) mod pg_extern;
71pub(crate) mod pg_trigger;
72pub(crate) mod pgrx_attribute;
73pub(crate) mod pgrx_sql;
74pub mod positioning_ref;
75pub(crate) mod postgres_enum;
76pub(crate) mod postgres_hash;
77pub(crate) mod postgres_ord;
78pub(crate) mod postgres_type;
79pub(crate) mod schema;
80pub mod section;
81pub(crate) mod to_sql;
82pub(crate) mod used_type;
83
84/// Able to produce a GraphViz DOT format identifier.
85pub trait SqlGraphIdentifier {
86    /// A dot style identifier for the entity.
87    ///
88    /// Typically this is a 'archetype' prefix (eg `fn` or `type`) then result of
89    /// [`std::module_path`], [`core::any::type_name`], or some combination of [`std::file`] and
90    /// [`std::line`].
91    fn dot_identifier(&self) -> String;
92
93    /// A Rust identifier for the entity.
94    ///
95    /// Typically this is the result of [`std::module_path`], [`core::any::type_name`],
96    /// or some combination of [`std::file`] and [`std::line`].
97    fn rust_identifier(&self) -> String;
98
99    fn file(&self) -> Option<&str>;
100
101    fn line(&self) -> Option<u32>;
102}
103
104pub use postgres_type::Alignment;
105
106/// An entity corresponding to some SQL required by the extension.
107#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
108#[allow(clippy::large_enum_variant)]
109pub enum SqlGraphEntity<'a> {
110    ExtensionRoot(ControlFile),
111    Schema(SchemaEntity<'a>),
112    CustomSql(ExtensionSqlEntity<'a>),
113    Function(PgExternEntity<'a>),
114    Type(PostgresTypeEntity<'a>),
115    BuiltinType(String),
116    Enum(PostgresEnumEntity<'a>),
117    Ord(PostgresOrdEntity<'a>),
118    Hash(PostgresHashEntity<'a>),
119    Aggregate(PgAggregateEntity<'a>),
120    Trigger(PgTriggerEntity<'a>),
121}
122
123impl SqlGraphEntity<'_> {
124    pub fn sql_anchor_comment(&self) -> String {
125        let maybe_file_and_line = if let (Some(file), Some(line)) = (self.file(), self.line()) {
126            format!("-- {file}:{line}\n")
127        } else {
128            String::default()
129        };
130        format!(
131            "\
132            {maybe_file_and_line}\
133            -- {rust_identifier}\
134        ",
135            rust_identifier = self.rust_identifier(),
136        )
137    }
138
139    pub fn type_ident_matches(&self, type_ident: &str) -> bool {
140        match self {
141            SqlGraphEntity::CustomSql(entity) => {
142                entity.creates.iter().any(|declared| declared.matches_type_ident(type_ident))
143            }
144            SqlGraphEntity::Enum(entity) => entity.matches_type_ident(type_ident),
145            SqlGraphEntity::Type(entity) => entity.matches_type_ident(type_ident),
146            SqlGraphEntity::BuiltinType(string) => string == type_ident,
147            _ => false,
148        }
149    }
150
151    pub fn type_matches(&self, arg: &dyn TypeIdentifiable) -> bool {
152        self.type_ident_matches(arg.type_ident())
153    }
154}
155
156pub trait TypeMatch {
157    fn type_ident(&self) -> &str;
158
159    fn matches_type_ident(&self, arg: &str) -> bool {
160        self.type_ident() == arg
161    }
162}
163
164pub fn type_keyed<'a, 'b, A: TypeMatch, B>((a, b): (&'a A, &'b B)) -> (&'a dyn TypeMatch, &'b B) {
165    (a, b)
166}
167
168pub trait TypeIdentifiable {
169    fn type_ident(&self) -> &str;
170    fn ty_name(&self) -> &str;
171}
172
173impl SqlGraphIdentifier for SqlGraphEntity<'_> {
174    fn dot_identifier(&self) -> String {
175        match self {
176            SqlGraphEntity::Schema(item) => item.dot_identifier(),
177            SqlGraphEntity::CustomSql(item) => item.dot_identifier(),
178            SqlGraphEntity::Function(item) => item.dot_identifier(),
179            SqlGraphEntity::Type(item) => item.dot_identifier(),
180            SqlGraphEntity::BuiltinType(item) => format!("builtin type {item}"),
181            SqlGraphEntity::Enum(item) => item.dot_identifier(),
182            SqlGraphEntity::Ord(item) => item.dot_identifier(),
183            SqlGraphEntity::Hash(item) => item.dot_identifier(),
184            SqlGraphEntity::Aggregate(item) => item.dot_identifier(),
185            SqlGraphEntity::Trigger(item) => item.dot_identifier(),
186            SqlGraphEntity::ExtensionRoot(item) => item.dot_identifier(),
187        }
188    }
189
190    fn rust_identifier(&self) -> String {
191        match self {
192            SqlGraphEntity::Schema(item) => item.rust_identifier(),
193            SqlGraphEntity::CustomSql(item) => item.rust_identifier(),
194            SqlGraphEntity::Function(item) => item.rust_identifier(),
195            SqlGraphEntity::Type(item) => item.rust_identifier(),
196            SqlGraphEntity::BuiltinType(item) => item.to_string(),
197            SqlGraphEntity::Enum(item) => item.rust_identifier(),
198            SqlGraphEntity::Ord(item) => item.rust_identifier(),
199            SqlGraphEntity::Hash(item) => item.rust_identifier(),
200            SqlGraphEntity::Aggregate(item) => item.rust_identifier(),
201            SqlGraphEntity::Trigger(item) => item.rust_identifier(),
202            SqlGraphEntity::ExtensionRoot(item) => item.rust_identifier(),
203        }
204    }
205
206    fn file(&self) -> Option<&str> {
207        match self {
208            SqlGraphEntity::Schema(item) => item.file(),
209            SqlGraphEntity::CustomSql(item) => item.file(),
210            SqlGraphEntity::Function(item) => item.file(),
211            SqlGraphEntity::Type(item) => item.file(),
212            SqlGraphEntity::BuiltinType(_item) => None,
213            SqlGraphEntity::Enum(item) => item.file(),
214            SqlGraphEntity::Ord(item) => item.file(),
215            SqlGraphEntity::Hash(item) => item.file(),
216            SqlGraphEntity::Aggregate(item) => item.file(),
217            SqlGraphEntity::Trigger(item) => item.file(),
218            SqlGraphEntity::ExtensionRoot(item) => item.file(),
219        }
220    }
221
222    fn line(&self) -> Option<u32> {
223        match self {
224            SqlGraphEntity::Schema(item) => item.line(),
225            SqlGraphEntity::CustomSql(item) => item.line(),
226            SqlGraphEntity::Function(item) => item.line(),
227            SqlGraphEntity::Type(item) => item.line(),
228            SqlGraphEntity::BuiltinType(_item) => None,
229            SqlGraphEntity::Enum(item) => item.line(),
230            SqlGraphEntity::Ord(item) => item.line(),
231            SqlGraphEntity::Hash(item) => item.line(),
232            SqlGraphEntity::Aggregate(item) => item.line(),
233            SqlGraphEntity::Trigger(item) => item.line(),
234            SqlGraphEntity::ExtensionRoot(item) => item.line(),
235        }
236    }
237}
238
239impl ToSql for SqlGraphEntity<'_> {
240    fn to_sql(&self, context: &PgrxSql) -> eyre::Result<String> {
241        match self {
242            SqlGraphEntity::Schema(SchemaEntity { name: "public" | "pg_catalog", .. }) => {
243                Ok(String::default())
244            }
245            SqlGraphEntity::Schema(item) => item.to_sql(context),
246            SqlGraphEntity::CustomSql(item) => item.to_sql(context),
247            SqlGraphEntity::Function(item) => {
248                if let Some(result) = item.to_sql_config.to_sql(self, context) {
249                    result
250                } else if context
251                    .graph
252                    .neighbors_undirected(*context.externs.get(item).unwrap())
253                    .any(|neighbor| {
254                        let SqlGraphEntity::Type(PostgresTypeEntity {
255                            module_path,
256                            in_fn_path,
257                            out_fn_path,
258                            receive_fn_path,
259                            send_fn_path,
260                            ..
261                        }) = &context.graph[neighbor]
262                        else {
263                            return false;
264                        };
265
266                        let resolve = |path: &str| {
267                            if path.contains("::") {
268                                path.to_string()
269                            } else {
270                                format!("{module_path}::{path}")
271                            }
272                        };
273                        let is_in_fn = item.full_path == resolve(in_fn_path);
274                        let is_out_fn = item.full_path == resolve(out_fn_path);
275                        let is_receive_fn = receive_fn_path
276                            .as_ref()
277                            .is_some_and(|path| item.full_path == resolve(path));
278                        let is_send_fn = send_fn_path
279                            .as_ref()
280                            .is_some_and(|path| item.full_path == resolve(path));
281                        is_in_fn || is_out_fn || is_receive_fn || is_send_fn
282                    })
283                {
284                    Ok(String::default())
285                } else {
286                    item.to_sql(context)
287                }
288            }
289            SqlGraphEntity::Type(item) => {
290                item.to_sql_config.to_sql(self, context).unwrap_or_else(|| item.to_sql(context))
291            }
292            SqlGraphEntity::BuiltinType(_) => Ok(String::default()),
293            SqlGraphEntity::Enum(item) => {
294                item.to_sql_config.to_sql(self, context).unwrap_or_else(|| item.to_sql(context))
295            }
296            SqlGraphEntity::Ord(item) => {
297                item.to_sql_config.to_sql(self, context).unwrap_or_else(|| item.to_sql(context))
298            }
299            SqlGraphEntity::Hash(item) => {
300                item.to_sql_config.to_sql(self, context).unwrap_or_else(|| item.to_sql(context))
301            }
302            SqlGraphEntity::Aggregate(item) => {
303                item.to_sql_config.to_sql(self, context).unwrap_or_else(|| item.to_sql(context))
304            }
305            SqlGraphEntity::Trigger(item) => {
306                item.to_sql_config.to_sql(self, context).unwrap_or_else(|| item.to_sql(context))
307            }
308            SqlGraphEntity::ExtensionRoot(item) => item.to_sql(context),
309        }
310    }
311}
312
313/// Validate that a given ident is acceptable to PostgreSQL
314///
315/// PostgreSQL places some restrictions on identifiers for things like functions.
316///
317/// Namely:
318///
319/// * It must be less than 64 characters
320///
321// This list is incomplete, you could expand it!
322pub fn ident_is_acceptable_to_postgres(ident: &syn::Ident) -> Result<(), syn::Error> {
323    // Roughly `pgrx::pg_sys::NAMEDATALEN`
324    //
325    // Technically it **should** be that, but we need to guess at build time
326    const POSTGRES_IDENTIFIER_MAX_LEN: usize = 64;
327
328    let len = ident.to_string().len();
329    if len >= POSTGRES_IDENTIFIER_MAX_LEN {
330        return Err(syn::Error::new(
331            ident.span(),
332            format!(
333                "Identifier `{ident}` was {len} characters long, PostgreSQL will truncate identifiers with less than \
334                {POSTGRES_IDENTIFIER_MAX_LEN} characters, opt for an identifier which Postgres won't truncate"
335            ),
336        ));
337    }
338
339    Ok(())
340}