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