Skip to main content

postgres_syntax/
lib.rs

1//! Compile-time syntax checking for PostgreSQL queries.
2//!
3//! `postgres-syntax` exposes the [`sql!`] macro, which parses a PostgreSQL
4//! query string at compile time and returns the string unchanged when parsing
5//! succeeds. If the query is not valid PostgreSQL syntax, compilation fails
6//! with the parser error reported by [`pg_query`].
7//!
8//! This crate validates syntax only. It does not connect to a database, inspect
9//! your schema, validate table or column names, or type-check query parameters.
10//!
11//! # Example
12//!
13//! ```
14//! use postgres_syntax::sql;
15//!
16//! let query = sql!("SELECT name, email_address FROM users WHERE id = $1");
17//! assert_eq!(
18//!     query,
19//!     "SELECT name, email_address FROM users WHERE id = $1",
20//! );
21//! ```
22//!
23//! Invalid SQL produces a compile error:
24//!
25//! ```compile_fail
26//! use postgres_syntax::sql;
27//!
28//! let _ = sql!("SELECT * FROM users WHERE id = $1 BLARG");
29//! ```
30//!
31//! [`pg_query`]: https://docs.rs/pg_query/
32
33use litrs::StringLit;
34use proc_macro::TokenStream;
35use quote::quote;
36
37/// Checks a PostgreSQL query for syntax errors at compile time.
38///
39/// The macro accepts exactly one string literal. When the string parses as
40/// PostgreSQL SQL, the macro expands back to that same literal, so it can be
41/// used anywhere a string literal would be accepted.
42///
43/// ```
44/// use postgres_syntax::sql;
45///
46/// let query: &str = sql!("SELECT now()");
47/// assert_eq!(query, "SELECT now()");
48/// ```
49///
50/// Syntax errors are reported during compilation:
51///
52/// ```compile_fail
53/// use postgres_syntax::sql;
54///
55/// let _ = sql!("SELECT FROM");
56/// ```
57///
58/// The macro only checks PostgreSQL syntax. It does not verify object names,
59/// parameter types, permissions, or any other database-specific semantics.
60#[proc_macro]
61pub fn sql(input: TokenStream) -> TokenStream {
62    let input = input.into_iter().collect::<Vec<_>>();
63    if input.len() != 1 {
64        let msg = format!(
65            "expected exactly one string literal as input, got {} tokens",
66            input.len()
67        );
68        return quote! { compile_error!(#msg) }.into();
69    }
70    let string_lit = match StringLit::try_from(&input[0]) {
71        Ok(lit) => lit,
72        Err(e) => return e.to_compile_error(),
73    };
74
75    if let Err(e) = pg_query::parse(string_lit.value()) {
76        let msg = format!("failed to parse SQL query: {}", e);
77        return quote! { compile_error!(#msg) }.into();
78    }
79
80    input.into_iter().collect()
81}