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