Skip to main content

pgrx_sql_entity_graph/to_sql/
mod.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
12`sql = ...` fragment related macro expansion for Rust to SQL translation
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 mod entity;
19
20use std::hash::Hash;
21
22use proc_macro2::TokenStream as TokenStream2;
23use quote::{ToTokens, TokenStreamExt, quote};
24use syn::spanned::Spanned;
25use syn::{AttrStyle, Attribute, Lit};
26
27use crate::SqlGraphEntity;
28use crate::pgrx_attribute::{ArgValue, PgrxArg, PgrxAttribute};
29use crate::pgrx_sql::PgrxSql;
30
31/// Able to be transformed into to SQL.
32pub trait ToSql {
33    /// Attempt to transform this type into SQL.
34    ///
35    /// Some entities require additional context from a [`PgrxSql`], such as
36    /// `#[derive(PostgresType)]` which must include it's relevant in/out functions.
37    fn to_sql(&self, context: &PgrxSql) -> eyre::Result<String>;
38}
39
40/// The signature of a function that can transform a SqlGraphEntity to a SQL string
41///
42/// This is used to provide a facility for overriding the default SQL generator behavior using
43/// the `#[to_sql(path::to::function)]` attribute in circumstances where the default behavior is
44/// not desirable.
45///
46/// Implementations can invoke `ToSql::to_sql(entity, context)` on the unwrapped SqlGraphEntity
47/// type should they wish to delegate to the default behavior for any reason.
48pub type ToSqlFn =
49    fn(
50        &SqlGraphEntity,
51        &PgrxSql,
52    ) -> std::result::Result<String, Box<dyn std::error::Error + Send + Sync + 'static>>;
53
54/// A parsed `sql` option from a `pgrx` related procedural macro.
55#[derive(Debug, Clone, PartialEq, Eq, Hash)]
56pub struct ToSqlConfig {
57    pub enabled: bool,
58    pub callback: Option<syn::Path>,
59    pub content: Option<syn::LitStr>,
60}
61impl From<bool> for ToSqlConfig {
62    fn from(enabled: bool) -> Self {
63        Self { enabled, callback: None, content: None }
64    }
65}
66impl From<syn::Path> for ToSqlConfig {
67    fn from(path: syn::Path) -> Self {
68        Self { enabled: true, callback: Some(path), content: None }
69    }
70}
71impl From<syn::LitStr> for ToSqlConfig {
72    fn from(content: syn::LitStr) -> Self {
73        Self { enabled: true, callback: None, content: Some(content) }
74    }
75}
76impl Default for ToSqlConfig {
77    fn default() -> Self {
78        Self { enabled: true, callback: None, content: None }
79    }
80}
81
82const INVALID_ATTR_CONTENT: &str = "expected `#[pgrx(sql = content)]`, where `content` is a boolean, string, or path to a function";
83
84impl ToSqlConfig {
85    /// Used for general purpose parsing from an attribute
86    pub fn from_attribute(attr: &Attribute) -> Result<Option<Self>, syn::Error> {
87        if attr.style != AttrStyle::Outer {
88            return Err(syn::Error::new(
89                attr.span(),
90                "#[pgrx(sql = ..)] is only valid in an outer context",
91            ));
92        }
93
94        let attr = attr.parse_args::<PgrxAttribute>()?;
95        for arg in attr.args.iter() {
96            let PgrxArg::NameValue(nv) = arg;
97            if !nv.path.is_ident("sql") {
98                continue;
99            }
100
101            return match nv.value {
102                ArgValue::Path(ref callback_path) => Ok(Some(Self {
103                    enabled: true,
104                    callback: Some(callback_path.clone()),
105                    content: None,
106                })),
107                ArgValue::Lit(Lit::Bool(ref b)) => {
108                    Ok(Some(Self { enabled: b.value, callback: None, content: None }))
109                }
110                ArgValue::Lit(Lit::Str(ref s)) => {
111                    Ok(Some(Self { enabled: true, callback: None, content: Some(s.clone()) }))
112                }
113                ArgValue::Lit(ref other) => {
114                    Err(syn::Error::new(other.span(), INVALID_ATTR_CONTENT))
115                }
116            };
117        }
118
119        Ok(None)
120    }
121
122    /// Used to parse a generator config from a set of item attributes
123    pub fn from_attributes(attrs: &[Attribute]) -> Result<Option<Self>, syn::Error> {
124        if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("pgrx")) {
125            Self::from_attribute(attr)
126        } else {
127            Ok(None)
128        }
129    }
130
131    pub fn overrides_default(&self) -> bool {
132        !self.enabled || self.callback.is_some() || self.content.is_some()
133    }
134}
135
136impl ToTokens for ToSqlConfig {
137    fn to_tokens(&self, tokens: &mut TokenStream2) {
138        let enabled = self.enabled;
139        let callback = &self.callback;
140        let content = &self.content;
141        if let Some(callback_path) = callback {
142            tokens.append_all(quote! {
143                ::pgrx::pgrx_sql_entity_graph::ToSqlConfigEntity {
144                    enabled: #enabled,
145                    callback: Some(#callback_path),
146                    content: None,
147                }
148            });
149            return;
150        }
151        if let Some(sql) = content {
152            tokens.append_all(quote! {
153                ::pgrx::pgrx_sql_entity_graph::ToSqlConfigEntity {
154                    enabled: #enabled,
155                    callback: None,
156                    content: Some(#sql),
157                }
158            });
159            return;
160        }
161        tokens.append_all(quote! {
162            ::pgrx::pgrx_sql_entity_graph::ToSqlConfigEntity {
163                enabled: #enabled,
164                callback: None,
165                content: None,
166            }
167        });
168    }
169}