pgrx_sql_entity_graph/to_sql/entity.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 entities 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*/
18use crate::pgrx_sql::PgrxSql;
19use crate::to_sql::ToSqlFn;
20use crate::SqlGraphEntity;
21
22/// Represents configuration options for tuning the SQL generator.
23///
24/// When an item that can be rendered to SQL has these options at hand, they should be
25/// respected. If an item does not have them, then it is not expected that the SQL generation
26/// for those items can be modified.
27///
28/// The default configuration has `enabled` set to `true`, and `callback` to `None`, which indicates
29/// that the default SQL generation behavior will be used. These are intended to be mutually exclusive
30/// options, so `callback` should only be set if generation is enabled.
31///
32/// When `enabled` is false, no SQL is generated for the item being configured.
33///
34/// When `callback` has a value, the corresponding `ToSql` implementation should invoke the
35/// callback instead of performing their default behavior.
36#[derive(Default, Clone)]
37pub struct ToSqlConfigEntity {
38 pub enabled: bool,
39 pub callback: Option<ToSqlFn>,
40 pub content: Option<&'static str>,
41}
42impl ToSqlConfigEntity {
43 /// Helper used to implement traits (`Eq`, `Ord`, etc) despite `ToSqlFn` not
44 /// having an implementation for them.
45 #[inline]
46 fn fields(&self) -> (bool, Option<&str>, Option<usize>) {
47 (self.enabled, self.content, self.callback.map(|f| f as usize))
48 }
49 /// Given a SqlGraphEntity, this function converts it to SQL based on the current configuration.
50 ///
51 /// If the config overrides the default behavior (i.e. using the `ToSql` trait), then `Some(eyre::Result)`
52 /// is returned. If the config does not override the default behavior, then `None` is returned. This can
53 /// be used to dispatch SQL generation in a single line, e.g.:
54 ///
55 /// ```rust,ignore
56 /// config.to_sql(entity, context).unwrap_or_else(|| entity.to_sql(context))?
57 /// ```
58 pub fn to_sql(
59 &self,
60 entity: &SqlGraphEntity,
61 context: &PgrxSql,
62 ) -> Option<eyre::Result<String>> {
63 use eyre::{eyre, WrapErr};
64
65 if !self.enabled {
66 return Some(Ok(format!(
67 "\n\
68 {sql_anchor_comment}\n\
69 -- Skipped due to `#[pgrx(sql = false)]`\n",
70 sql_anchor_comment = entity.sql_anchor_comment(),
71 )));
72 }
73
74 if let Some(content) = self.content {
75 let module_pathname = context.get_module_pathname();
76
77 let content = content.replace("@MODULE_PATHNAME@", &module_pathname);
78
79 return Some(Ok(format!(
80 "\n\
81 {sql_anchor_comment}\n\
82 {content}\n\
83 ",
84 content = content,
85 sql_anchor_comment = entity.sql_anchor_comment()
86 )));
87 }
88
89 if let Some(callback) = self.callback {
90 let content = callback(entity, context)
91 .map_err(|e| eyre!(e))
92 .wrap_err("Failed to run specified `#[pgrx(sql = path)] function`");
93 return match content {
94 Ok(content) => {
95 let module_pathname = &context.get_module_pathname();
96
97 let content = content.replace("@MODULE_PATHNAME@", module_pathname);
98
99 Some(Ok(format!(
100 "\n\
101 {sql_anchor_comment}\n\
102 {content}\
103 ",
104 content = content,
105 sql_anchor_comment = entity.sql_anchor_comment(),
106 )))
107 }
108 Err(e) => Some(Err(e)),
109 };
110 }
111
112 None
113 }
114}
115
116impl std::cmp::PartialOrd for ToSqlConfigEntity {
117 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
118 Some(self.cmp(other))
119 }
120}
121impl std::cmp::Ord for ToSqlConfigEntity {
122 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
123 self.fields().cmp(&other.fields())
124 }
125}
126impl std::cmp::PartialEq for ToSqlConfigEntity {
127 fn eq(&self, other: &Self) -> bool {
128 self.fields() == other.fields()
129 }
130}
131impl std::cmp::Eq for ToSqlConfigEntity {}
132impl std::hash::Hash for ToSqlConfigEntity {
133 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
134 self.fields().hash(state);
135 }
136}
137impl std::fmt::Debug for ToSqlConfigEntity {
138 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
139 let (enabled, content, callback) = self.fields();
140 f.debug_struct("ToSqlConfigEntity")
141 .field("enabled", &enabled)
142 .field("callback", &callback)
143 .field("content", &content)
144 .finish()
145 }
146}