1#![cfg_attr(not(feature = "std"), no_std)]
3
4#[cfg(not(feature = "std"))]
5#[macro_use]
6extern crate alloc;
7
8mod display_utils;
9mod dml;
10mod query;
11mod string;
12
13#[cfg(not(feature = "std"))]
14use alloc::{
15 boxed::Box,
16 string::{String, ToString},
17 vec::Vec,
18};
19
20use core::cmp::Ordering;
21use core::fmt::{self, Display};
22use core::hash;
23
24use display_utils::{NewLine, SpaceOrNewline};
25
26pub use self::dml::{Assignment, AssignmentTarget, Delete, Insert, Update};
27pub use self::query::{
28 Copy, Cte, CteAsMaterialized, Distinct, ExprWithAlias, Join, JoinConstraint, JoinOperator,
29 LateralView, OrderBy, OrderByExpr, OrderByKind, OrderByOptions, Query, RelExpr, RelNamed,
30 Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, TableAlias, TableVersion,
31 Values, With,
32};
33
34pub use self::string::escape as escape_string;
35pub use display_utils::{DisplayCommaSeparated, Indent};
36
37pub struct DisplaySeparated<'a, T>
38where
39 T: fmt::Display,
40{
41 slice: &'a [T],
42 sep: &'static str,
43}
44
45impl<T> fmt::Display for DisplaySeparated<'_, T>
46where
47 T: fmt::Display,
48{
49 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50 let mut delim = "";
51 for t in self.slice {
52 f.write_str(delim)?;
53 delim = self.sep;
54 t.fmt(f)?;
55 }
56 Ok(())
57 }
58}
59
60pub fn display_separated<'a, T>(slice: &'a [T], sep: &'static str) -> DisplaySeparated<'a, T>
61where
62 T: fmt::Display,
63{
64 DisplaySeparated { slice, sep }
65}
66
67pub fn display_comma_separated<T>(slice: &[T]) -> DisplaySeparated<'_, T>
68where
69 T: fmt::Display,
70{
71 DisplaySeparated { slice, sep: ", " }
72}
73
74#[derive(Debug, Clone)]
76pub struct Ident {
77 pub value: String,
79 pub quote_style: Option<char>,
82}
83
84impl PartialEq for Ident {
85 fn eq(&self, other: &Self) -> bool {
86 let Ident {
87 value,
88 quote_style,
89 } = self;
91
92 value == &other.value && quote_style == &other.quote_style
93 }
94}
95
96impl core::hash::Hash for Ident {
97 fn hash<H: hash::Hasher>(&self, state: &mut H) {
98 let Ident {
99 value,
100 quote_style,
101 } = self;
103
104 value.hash(state);
105 quote_style.hash(state);
106 }
107}
108
109impl Eq for Ident {}
110
111impl PartialOrd for Ident {
112 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
113 Some(self.cmp(other))
114 }
115}
116
117impl Ord for Ident {
118 fn cmp(&self, other: &Self) -> Ordering {
119 let Ident {
120 value,
121 quote_style,
122 } = self;
124
125 let Ident {
126 value: other_value,
127 quote_style: other_quote_style,
128 } = other;
130
131 value
133 .cmp(other_value)
134 .then_with(|| quote_style.cmp(other_quote_style))
135 }
136}
137
138impl Ident {
139 pub fn new<S>(value: S) -> Self
141 where
142 S: Into<String>,
143 {
144 Ident {
145 value: value.into(),
146 quote_style: None,
147 }
148 }
149
150 pub fn with_quote_if_needed<S>(quote: char, value: S) -> Self
153 where
154 S: Into<String>,
155 {
156 let value = value.into();
157 let quote_style = if valid_ident_regex().is_match(&value) && !is_keyword(&value) {
158 None
159 } else {
160 Some(quote)
161 };
162 Ident { value, quote_style }
163 }
164}
165
166fn valid_ident_regex() -> &'static regex::Regex {
167 static VALID_IDENT: once_cell::race::OnceBox<regex::Regex> = once_cell::race::OnceBox::new();
168 VALID_IDENT.get_or_init(|| {
169 Box::new(regex::Regex::new(r"^((\*)|(^[a-z_\$][a-z0-9_\$]*))$").unwrap())
176 })
177}
178
179fn is_keyword(ident: &str) -> bool {
180 const KEYWORDS: &[&str] = &[
181 "select", "from", "where", "group", "by", "limit", "offset", "distinct", "on", "none",
182 "some", "end", "time",
183 ];
184 KEYWORDS.contains(&ident)
185}
186
187impl From<&str> for Ident {
188 fn from(value: &str) -> Self {
189 Ident {
190 value: value.to_string(),
191 quote_style: None,
192 }
193 }
194}
195
196impl fmt::Display for Ident {
197 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198 match self.quote_style {
199 Some(q) if q == '"' || q == '\'' || q == '`' => {
200 let escaped = string::escape(&self.value, q);
201 write!(f, "{q}{escaped}{q}")
202 }
203 Some('[') => write!(f, "[{}]", self.value),
204 None => f.write_str(&self.value),
205 _ => panic!("unexpected quote style"),
206 }
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
212pub struct ObjectName(pub Vec<Ident>);
213
214impl From<Vec<Ident>> for ObjectName {
215 fn from(idents: Vec<Ident>) -> Self {
216 ObjectName(idents)
217 }
218}
219
220impl fmt::Display for ObjectName {
221 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222 write!(f, "{}", display_separated(&self.0, "."))
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
229pub struct CaseWhen {
230 pub condition: Expr,
231 pub result: Expr,
232}
233
234impl fmt::Display for CaseWhen {
235 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
236 f.write_str("WHEN ")?;
237 self.condition.fmt(f)?;
238 f.write_str(" THEN")?;
239 SpaceOrNewline.fmt(f)?;
240 Indent(&self.result).fmt(f)?;
241 Ok(())
242 }
243}
244
245#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
263pub enum Expr {
264 Source(String),
266 Identifier(Ident),
267 CompoundIdentifier(Vec<Ident>),
268 IndexBy(Vec<Expr>),
269 Case {
270 operand: Option<Box<Expr>>,
271 cases: Vec<CaseWhen>,
272 else_result: Option<Box<Expr>>,
273 },
274 Subquery(Box<Query>),
275}
276
277impl fmt::Display for Expr {
278 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279 match self {
280 Expr::Source(s) => f.write_str(s),
281 Expr::Identifier(s) => write!(f, "{s}"),
282 Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")),
283
284 Expr::IndexBy(keys) => {
285 f.write_str("(ROW_NUMBER() OVER (")?;
286 if !keys.is_empty() {
287 f.write_str("ORDER BY ")?;
288 display_comma_separated(keys).fmt(f)?;
289 }
290 f.write_str(")-1)::int4")
291 }
292
293 Expr::Case {
294 operand,
295 cases,
296 else_result,
297 } => {
298 f.write_str("CASE")?;
299 if let Some(operand) = operand {
300 f.write_str(" ")?;
301 operand.fmt(f)?;
302 }
303 for case in cases {
304 SpaceOrNewline.fmt(f)?;
305 Indent(case).fmt(f)?;
306 }
307 if let Some(else_result) = else_result {
308 SpaceOrNewline.fmt(f)?;
309 Indent("ELSE").fmt(f)?;
310 SpaceOrNewline.fmt(f)?;
311 Indent(Indent(else_result)).fmt(f)?;
312 }
313 SpaceOrNewline.fmt(f)?;
314 f.write_str("END")
315 }
316 Expr::Subquery(s) => {
317 f.write_str("(")?;
318 SpaceOrNewline.fmt(f)?;
319 Indent(s).fmt(f)?;
320 SpaceOrNewline.fmt(f)?;
321 f.write_str(")")
322 }
323 }
324 }
325}