pg_named_args/lib.rs
1//! This library allows one to use named arguments in PostgreSQL queries. This
2//! library is especially aimed at supporting
3//! [rust-postgres](https://github.com/sfackler/rust-postgres). A macro is provided
4//! to rewrite queries with named arguments, into a query and its positional
5//! arguments.
6//!
7//! # Query Argument Syntax
8//! The macro uses struct syntax for the named arguments.
9//! The struct name `Args` is required to support rustfmt and rust-analyzer.
10//! As can be seen from the example below, shorthand field initialization is also allowed for named arguments.
11//!
12//! ```
13//! # use pg_named_args::query_args;
14//! # struct Period {
15//! # start: u32,
16//! # end: u32,
17//! # }
18//! #
19//! let location = "netherlands";
20//! let period = Period {
21//! start: 2020,
22//! end: 2030,
23//! };
24//!
25//! let (query, args) = query_args!(
26//! r"
27//! SELECT location, time, report
28//! FROM weather_reports
29//! WHERE location = $location
30//! AND time BETWEEN $start AND $end
31//! ORDER BY location, time DESC
32//! ",
33//! Args {
34//! location,
35//! start: period.start,
36//! end: period.end,
37//! }
38//! );
39//! ```
40//! ```ignore
41//! let rows = client.query(query, args).await?;
42//! ```
43//! This expands to the equivalent of
44//! ```ignore
45//! let (query, args) = (
46//! r"
47//! SELECT location, time, report
48//! FROM weather_reports
49//! WHERE location = $1
50//! AND time BETWEEN $2 AND $3
51//! ORDER BY location, time DESC
52//! ",
53//! &[&location, &period.start, &period.end]
54//! );
55//! ```
56//!
57//! # Insert Syntax
58//! For `INSERT`'s a special syntax is supported, which helps to avoid mismatches
59//! between the list of column names and the values:
60//!
61//! ```
62//! # use pg_named_args::query_args;
63//! #
64//! let location = "sweden";
65//! let time = "monday";
66//! let report = "sunny";
67//!
68//! let (query, args) = query_args!(
69//! r"
70//! INSERT INTO weather_reports
71//! ( $[location, time, report] )
72//! VALUES
73//! ( $[..] )
74//! ",
75//! Args {
76//! location,
77//! time,
78//! report
79//! }
80//! );
81//! ```
82//! ```ignore
83//! client.execute(query, args).await?;
84//! ```
85//! The SQL would be
86//! ```sql
87//! INSERT INTO weather_reports
88//! ( location, time, report )
89//! VALUES
90//! ( $1, $2, $3 )
91//! ```
92//!
93//! # Optional Parameter Syntax
94//! When doing updates it can be useful to dynamically build SQL.
95//!
96//! This is essential when for example a column must sometimes be updated, but the column is also nullable.
97//! To differentiate between those cases, `pg_named_args` adds a `Update<T>` type which has the
98//! `Update::Yes(val)` and `Update::No` variants. This type can be combined with the `Option` type as `Update<Option<T>>`.
99//!
100//! The syntax to use optional parameter is like this:
101//!
102//! ```rust
103//! # use pg_named_args::{query_args, Update};
104//! #
105//! fn update_report(id: i64, report: Update<Option<&str>>) {
106//! let (query, args) = query_args!(
107//! r"
108//! UPDATE weather_reports
109//! SET $[report?] = $[..]
110//! WHERE id = $id
111//! ",
112//! Args {
113//! report,
114//! id,
115//! }
116//! );
117//! }
118//!
119//! // Don't update the report.
120//! update_report(0, Update::No);
121//! // Set the report to `null`.
122//! update_report(1, Update::Yes(None));
123//! // Set the report to "sunny".
124//! update_report(2, Update::Yes(Some("sunny")));
125//! ```
126//! The SQL would be
127//! ```sql
128//! UPDATE weather_reports
129//! SET report = CASE WHEN $1 THEN $2 ELSE report END
130//! WHERE id = $3
131//! ```
132//! The optional parameter has been desugared to two parameters, one to indicate if the value should be updated
133//! and another parameter with the new value.
134//!
135//! Note that it is not possible to use the same argument with and without the `?` modifier in the same query.
136//!
137//! # Fragment Syntax
138//! ```
139//! # use pg_named_args::{query_args, fragment};
140//! #
141//! let select = fragment!("
142//! SELECT location, time, report
143//! FROM weather_reports
144//! ");
145//!
146//! let location = "sweden";
147//!
148//! let (query, args) = query_args!(
149//! r"
150//! ${select}
151//! WHERE location = $location
152//! ",
153//! Args {
154//! // Temporary lifetime extension doesn't work when fragments are involved,
155//! // so we have to borrow the parameter here.
156//! location: &location,
157//! },
158//! Sql {
159//! select,
160//! }
161//! );
162//! ```
163//! The SQL would be
164//! ```sql
165//! SELECT location, time, report
166//! FROM weather_reports
167//! WHERE location = $1
168//! ```
169//!
170//! # IDE Support
171//!
172//! First, the syntax used by this macro is compatible with rustfmt.
173//! Run rustfmt as you would normally and it will format the macro.
174//!
175//! Second, the macro is implemented in a way that is rust-analyzer "friendly".
176//! This means that rust-analyzer knows which arguments are required and can complete them.
177//! Use the code action "Fill struct fields" or ask rust-analyzer to complete a field name.
178
179extern crate self as pg_named_args;
180
181use std::borrow::Cow;
182
183pub use pg_named_args_macros::{fragment, query_args};
184#[doc(hidden)]
185pub use postgres_types;
186
187/// Helper type to safely build queries from string literals.
188///
189/// This type can be constructed using the [fragment] macro.
190/// It can then be used in [query_args] as part of the SQL statement.
191#[derive(Default, Clone)]
192pub struct Fragment<'a>(
193 Cow<'static, str>,
194 // TODO: maybe allow `&'a [&'a dyn ..]` for const initialization?
195 Vec<&'a (dyn postgres_types::ToSql + Sync)>,
196);
197
198impl<'a> Fragment<'a> {
199 /// Get the SQL string for this fragment.
200 pub fn get(&self) -> &str {
201 self.0.as_ref()
202 }
203
204 /// Get the parameters for this fragment.
205 pub fn params(&self) -> &[&'a (dyn postgres_types::ToSql + Sync)] {
206 &self.1
207 }
208
209 #[doc(hidden)]
210 /// This is the constructor used by the [fragment!] macro.
211 /// It is not intended to be used manually.
212 pub const fn new_unchecked(
213 sql: Cow<'static, str>,
214 vec: Vec<&'a (dyn postgres_types::ToSql + Sync)>,
215 ) -> Self {
216 Self(sql, vec)
217 }
218}
219
220#[doc(hidden)]
221/// Renumber sql parameters.
222///
223/// This function assumes that every '$' character is followed by a decimal number.
224/// If this is not the case, then this function will panic.
225pub fn renumber(sql: &str, offset: usize) -> String {
226 use std::fmt::Write;
227 let mut parts = sql.split('$');
228 let mut output = String::new();
229
230 // move the part before the first '$' into the output
231 output.extend(parts.next());
232
233 for part in parts {
234 let num_len = part
235 .find(|x: char| !x.is_ascii_digit())
236 .unwrap_or(part.len());
237 let num: usize = part[..num_len].parse().unwrap();
238 let new_num = num + offset;
239 let remainder = &part[num_len..];
240 write!(&mut output, "${new_num}{remainder}").unwrap();
241 }
242 output
243}
244
245/// This enum is used for optional arguments.
246///
247/// If you want to use your own type then you can implement [Update::From<YourType>] and
248/// it will be used automatically by the macros in this crate.
249pub enum Update<T> {
250 /// Indicates that the column should be updated.
251 Yes(T),
252 /// Indicates that the column should not be updated.
253 /// The column name is used to set the column to the value of the same column.
254 No,
255}
256
257impl<T> Update<T> {
258 #[doc(hidden)]
259 pub fn to_bool(&self) -> bool {
260 match self {
261 Update::Yes(_) => true,
262 Update::No => false,
263 }
264 }
265}
266
267impl<'a, T> From<&'a Update<T>> for Update<&'a T> {
268 fn from(value: &'a Update<T>) -> Self {
269 match value {
270 Update::Yes(val) => Update::Yes(val),
271 Update::No => Update::No,
272 }
273 }
274}
275
276#[doc(hidden)]
277#[derive(Debug)]
278pub struct Null;
279
280impl postgres_types::ToSql for Null {
281 fn to_sql(
282 &self,
283 _ty: &postgres_types::Type,
284 _out: &mut bytes::BytesMut,
285 ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>>
286 where
287 Self: Sized,
288 {
289 Ok(postgres_types::IsNull::Yes)
290 }
291
292 fn accepts(_ty: &postgres_types::Type) -> bool
293 where
294 Self: Sized,
295 {
296 true
297 }
298
299 postgres_types::to_sql_checked! {}
300}
301
302#[test]
303fn ui() {
304 let t = trybuild::TestCases::new();
305 t.compile_fail("tests/ui/*.rs");
306}