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