pgx_sql_entity_graph/to_sql/
mod.rs1pub 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
30pub trait ToSql {
32 fn to_sql(&self, context: &PgxSql) -> eyre::Result<String>;
37}
38
39pub type ToSqlFn =
48 fn(
49 &SqlGraphEntity,
50 &PgxSql,
51 ) -> std::result::Result<String, Box<dyn std::error::Error + Send + Sync + 'static>>;
52
53#[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 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 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}