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::pgrx_attribute::{ArgValue, PgrxArg, PgrxAttribute};
28use crate::pgrx_sql::PgrxSql;
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 [`PgrxSql`], such as
35    /// `#[derive(PostgresType)]` which must include it's relevant in/out functions.
36    fn to_sql(&self, context: &PgrxSql) -> eyre::Result<String>;
37}
38
39/// A parsed `sql` option from a `pgrx` related procedural macro.
40#[derive(Debug, Clone, PartialEq, Eq, Hash)]
41pub struct ToSqlConfig {
42    pub enabled: bool,
43    pub content: Option<syn::LitStr>,
44}
45impl From<bool> for ToSqlConfig {
46    fn from(enabled: bool) -> Self {
47        Self { enabled, content: None }
48    }
49}
50impl From<syn::LitStr> for ToSqlConfig {
51    fn from(content: syn::LitStr) -> Self {
52        Self { enabled: true, content: Some(content) }
53    }
54}
55impl Default for ToSqlConfig {
56    fn default() -> Self {
57        Self { enabled: true, content: None }
58    }
59}
60
61const INVALID_ATTR_CONTENT: &str =
62    "expected `#[pgrx(sql = content)]`, where `content` is a boolean or string literal";
63
64impl ToSqlConfig {
65    /// Used for general purpose parsing from an attribute
66    pub fn from_attribute(attr: &Attribute) -> Result<Option<Self>, syn::Error> {
67        if attr.style != AttrStyle::Outer {
68            return Err(syn::Error::new(
69                attr.span(),
70                "#[pgrx(sql = ..)] is only valid in an outer context",
71            ));
72        }
73
74        let attr = attr.parse_args::<PgrxAttribute>()?;
75        for arg in attr.args.iter() {
76            let PgrxArg::NameValue(nv) = arg;
77            if !nv.path.is_ident("sql") {
78                continue;
79            }
80
81            return match nv.value {
82                ArgValue::Lit(Lit::Bool(ref b)) => {
83                    Ok(Some(Self { enabled: b.value, content: None }))
84                }
85                ArgValue::Lit(Lit::Str(ref s)) => {
86                    Ok(Some(Self { enabled: true, content: Some(s.clone()) }))
87                }
88                ArgValue::Path(ref path) => Err(syn::Error::new(path.span(), INVALID_ATTR_CONTENT)),
89                ArgValue::Lit(ref other) => {
90                    Err(syn::Error::new(other.span(), INVALID_ATTR_CONTENT))
91                }
92            };
93        }
94
95        Ok(None)
96    }
97
98    /// Used to parse a generator config from a set of item attributes
99    pub fn from_attributes(attrs: &[Attribute]) -> Result<Option<Self>, syn::Error> {
100        if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("pgrx")) {
101            Self::from_attribute(attr)
102        } else {
103            Ok(None)
104        }
105    }
106
107    pub fn overrides_default(&self) -> bool {
108        !self.enabled || self.content.is_some()
109    }
110
111    pub fn section_len_tokens(&self) -> TokenStream2 {
112        let content = &self.content;
113        match content {
114            Some(content) => quote! {
115                ::pgrx::pgrx_sql_entity_graph::section::bool_len()
116                    + ::pgrx::pgrx_sql_entity_graph::section::bool_len()
117                    + ::pgrx::pgrx_sql_entity_graph::section::str_len(#content)
118            },
119            None => quote! {
120                ::pgrx::pgrx_sql_entity_graph::section::bool_len()
121                    + ::pgrx::pgrx_sql_entity_graph::section::bool_len()
122            },
123        }
124    }
125
126    pub fn section_writer_tokens(&self, writer: TokenStream2) -> TokenStream2 {
127        let enabled = self.enabled;
128        let content = &self.content;
129        match content {
130            Some(content) => quote! {
131                #writer
132                    .bool(#enabled)
133                    .bool(true)
134                    .str(#content)
135            },
136            None => quote! {
137                #writer
138                    .bool(#enabled)
139                    .bool(false)
140            },
141        }
142    }
143}
144
145impl ToTokens for ToSqlConfig {
146    fn to_tokens(&self, tokens: &mut TokenStream2) {
147        let enabled = self.enabled;
148        let content = &self.content;
149        if let Some(sql) = content {
150            tokens.append_all(quote! {
151                ::pgrx::pgrx_sql_entity_graph::ToSqlConfigEntity {
152                    enabled: #enabled,
153                    content: Some(#sql),
154                }
155            });
156            return;
157        }
158        tokens.append_all(quote! {
159            ::pgrx::pgrx_sql_entity_graph::ToSqlConfigEntity {
160                enabled: #enabled,
161                content: None,
162            }
163        });
164    }
165}