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