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}