Skip to main content

sqlparser/dialect/
postgresql.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18// Licensed under the Apache License, Version 2.0 (the "License");
19// you may not use this file except in compliance with the License.
20// You may obtain a copy of the License at
21//
22// http://www.apache.org/licenses/LICENSE-2.0
23//
24// Unless required by applicable law or agreed to in writing, software
25// distributed under the License is distributed on an "AS IS" BASIS,
26// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
27// See the License for the specific language governing permissions and
28// limitations under the License.
29use log::debug;
30
31use crate::dialect::{Dialect, Precedence};
32use crate::keywords::Keyword;
33use crate::parser::{Parser, ParserError};
34use crate::tokenizer::Token;
35
36use super::keywords::{self, RESERVED_FOR_IDENTIFIER};
37
38/// Keywords in [`keywords::RESERVED_FOR_TABLE_ALIAS`] because of other dialects, yet are safe for aliasing in PostgreSQL.
39/// See <https://www.postgresql.org/docs/current/sql-keywords-appendix.html>.
40const RESERVED_EXCLUSIONS_FOR_TABLE_ALIAS: &[Keyword] = &[
41    Keyword::CLUSTER,
42    Keyword::DISTRIBUTE,
43    Keyword::EXPLAIN,
44    Keyword::MINUS,
45    Keyword::SAMPLE,
46    Keyword::SORT,
47    Keyword::START,
48    Keyword::TOP,
49    Keyword::VIEW,
50];
51
52/// A [`Dialect`] for [PostgreSQL](https://www.postgresql.org/)
53#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct PostgreSqlDialect {}
56
57const PERIOD_PREC: u8 = 200;
58const DOUBLE_COLON_PREC: u8 = 140;
59const BRACKET_PREC: u8 = 130;
60const COLLATE_PREC: u8 = 120;
61const AT_TZ_PREC: u8 = 110;
62const CARET_PREC: u8 = 100;
63const MUL_DIV_MOD_OP_PREC: u8 = 90;
64const PLUS_MINUS_PREC: u8 = 80;
65// there's no XOR operator in PostgreSQL, but support it here to avoid breaking tests
66const XOR_PREC: u8 = 75;
67const PG_OTHER_PREC: u8 = 70;
68const BETWEEN_LIKE_PREC: u8 = 60;
69const EQ_PREC: u8 = 50;
70const IS_PREC: u8 = 40;
71const NOT_PREC: u8 = 30;
72const AND_PREC: u8 = 20;
73const OR_PREC: u8 = 10;
74
75impl Dialect for PostgreSqlDialect {
76    fn supports_do_statement(&self) -> bool {
77        true
78    }
79
80    fn is_select_item_alias(&self, explicit: bool, keyword: &Keyword, parser: &mut Parser) -> bool {
81        if !explicit && *keyword == Keyword::ON && parser.peek_keyword(Keyword::CONFLICT) {
82            return false;
83        }
84        explicit || !keywords::RESERVED_FOR_COLUMN_ALIAS.contains(keyword)
85    }
86
87    fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
88        Some('"')
89    }
90
91    fn is_delimited_identifier_start(&self, ch: char) -> bool {
92        ch == '"' // Postgres does not support backticks to quote identifiers
93    }
94
95    fn is_identifier_start(&self, ch: char) -> bool {
96        ch.is_alphabetic() || ch == '_' ||
97        // PostgreSQL implements Unicode characters in identifiers.
98        !ch.is_ascii()
99    }
100
101    fn is_identifier_part(&self, ch: char) -> bool {
102        ch.is_alphabetic() || ch.is_ascii_digit() || ch == '$' || ch == '_'  ||
103        // PostgreSQL implements Unicode characters in identifiers.
104        !ch.is_ascii()
105    }
106
107    fn supports_unicode_string_literal(&self) -> bool {
108        true
109    }
110
111    fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
112        if matches!(kw, Keyword::INTERVAL) {
113            false
114        } else {
115            RESERVED_FOR_IDENTIFIER.contains(&kw)
116        }
117    }
118
119    fn is_table_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
120        !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
121            || RESERVED_EXCLUSIONS_FOR_TABLE_ALIAS.contains(kw)
122    }
123
124    /// See <https://www.postgresql.org/docs/current/sql-createoperator.html>
125    fn is_custom_operator_part(&self, ch: char) -> bool {
126        matches!(
127            ch,
128            '+' | '-'
129                | '*'
130                | '/'
131                | '<'
132                | '>'
133                | '='
134                | '~'
135                | '!'
136                | '@'
137                | '#'
138                | '%'
139                | '^'
140                | '&'
141                | '|'
142                | '`'
143                | '?'
144        )
145    }
146
147    fn get_next_precedence(&self, parser: &Parser) -> Option<Result<u8, ParserError>> {
148        let token = parser.peek_token_ref();
149        debug!("get_next_precedence() {token:?}");
150
151        // we only return some custom value here when the behaviour (not merely the numeric value) differs
152        // from the default implementation
153        match &token.token {
154            Token::Word(w)
155                if w.keyword == Keyword::COLLATE && !parser.in_column_definition_state() =>
156            {
157                Some(Ok(COLLATE_PREC))
158            }
159            Token::LBracket => Some(Ok(BRACKET_PREC)),
160            Token::Arrow
161            | Token::LongArrow
162            | Token::HashArrow
163            | Token::HashLongArrow
164            | Token::AtArrow
165            | Token::ArrowAt
166            | Token::HashMinus
167            | Token::AtQuestion
168            | Token::AtAt
169            | Token::Question
170            | Token::QuestionAnd
171            | Token::QuestionPipe
172            | Token::ExclamationMark
173            | Token::Overlap
174            | Token::CaretAt
175            | Token::StringConcat
176            | Token::Sharp
177            | Token::ShiftRight
178            | Token::ShiftLeft
179            | Token::CustomBinaryOperator(_) => Some(Ok(PG_OTHER_PREC)),
180            // lowest prec to prevent it from turning into a binary op
181            Token::Colon => Some(Ok(self.prec_unknown())),
182            _ => None,
183        }
184    }
185
186    fn supports_filter_during_aggregation(&self) -> bool {
187        true
188    }
189
190    fn supports_group_by_expr(&self) -> bool {
191        true
192    }
193
194    fn supports_alter_user_as_alter_role(&self) -> bool {
195        true
196    }
197
198    fn prec_value(&self, prec: Precedence) -> u8 {
199        match prec {
200            Precedence::Period => PERIOD_PREC,
201            Precedence::DoubleColon => DOUBLE_COLON_PREC,
202            Precedence::AtTz => AT_TZ_PREC,
203            Precedence::MulDivModOp => MUL_DIV_MOD_OP_PREC,
204            Precedence::PlusMinus => PLUS_MINUS_PREC,
205            Precedence::Xor => XOR_PREC,
206            Precedence::Ampersand => PG_OTHER_PREC,
207            Precedence::Caret => CARET_PREC,
208            Precedence::Pipe => PG_OTHER_PREC,
209            Precedence::Colon => PG_OTHER_PREC,
210            Precedence::Between => BETWEEN_LIKE_PREC,
211            Precedence::Eq => EQ_PREC,
212            Precedence::Like => BETWEEN_LIKE_PREC,
213            Precedence::Is => IS_PREC,
214            Precedence::PgOther => PG_OTHER_PREC,
215            Precedence::UnaryNot => NOT_PREC,
216            Precedence::And => AND_PREC,
217            Precedence::Or => OR_PREC,
218        }
219    }
220
221    fn allow_extract_custom(&self) -> bool {
222        true
223    }
224
225    fn allow_extract_single_quotes(&self) -> bool {
226        true
227    }
228
229    fn supports_create_index_with_clause(&self) -> bool {
230        true
231    }
232
233    /// see <https://www.postgresql.org/docs/current/sql-explain.html>
234    fn supports_explain_with_utility_options(&self) -> bool {
235        true
236    }
237
238    /// see <https://www.postgresql.org/docs/current/sql-listen.html>
239    /// see <https://www.postgresql.org/docs/current/sql-unlisten.html>
240    /// see <https://www.postgresql.org/docs/current/sql-notify.html>
241    fn supports_listen_notify(&self) -> bool {
242        true
243    }
244
245    /// see <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE>
246    fn supports_exclude_constraint(&self) -> bool {
247        true
248    }
249
250    /// see <https://www.postgresql.org/docs/13/functions-math.html>
251    fn supports_factorial_operator(&self) -> bool {
252        true
253    }
254
255    fn supports_bitwise_shift_operators(&self) -> bool {
256        true
257    }
258
259    /// see <https://www.postgresql.org/docs/current/sql-comment.html>
260    fn supports_comment_on(&self) -> bool {
261        true
262    }
263
264    /// See <https://www.postgresql.org/docs/current/sql-load.html>
265    fn supports_load_extension(&self) -> bool {
266        true
267    }
268
269    /// See <https://www.postgresql.org/docs/current/functions-json.html>
270    ///
271    /// Required to support the colon in:
272    /// ```sql
273    /// SELECT json_object('a': 'b')
274    /// ```
275    fn supports_named_fn_args_with_colon_operator(&self) -> bool {
276        true
277    }
278
279    /// See <https://www.postgresql.org/docs/current/functions-json.html>
280    ///
281    /// Required to support the label in:
282    /// ```sql
283    /// SELECT json_object('label': 'value')
284    /// ```
285    fn supports_named_fn_args_with_expr_name(&self) -> bool {
286        true
287    }
288
289    /// Return true if the dialect supports empty projections in SELECT statements
290    ///
291    /// Example
292    /// ```sql
293    /// SELECT from table_name
294    /// ```
295    fn supports_empty_projections(&self) -> bool {
296        true
297    }
298
299    fn supports_nested_comments(&self) -> bool {
300        true
301    }
302
303    fn supports_string_escape_constant(&self) -> bool {
304        true
305    }
306
307    fn supports_numeric_literal_underscores(&self) -> bool {
308        true
309    }
310
311    /// See: <https://www.postgresql.org/docs/current/arrays.html#ARRAYS-DECLARATION>
312    fn supports_array_typedef_with_brackets(&self) -> bool {
313        true
314    }
315
316    fn supports_geometric_types(&self) -> bool {
317        true
318    }
319
320    fn supports_order_by_using_operator(&self) -> bool {
321        true
322    }
323
324    fn supports_set_names(&self) -> bool {
325        true
326    }
327
328    fn supports_set_schema(&self) -> bool {
329        true
330    }
331
332    fn supports_reset_time_zone(&self) -> bool {
333        true
334    }
335
336    fn supports_alter_column_type_using(&self) -> bool {
337        true
338    }
339
340    /// PostgreSQL supports right-deep join chains: `t0 JOIN t1 JOIN t2 ON c1 ON c2`
341    /// See: <https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-JOIN>
342    fn supports_left_associative_joins_without_parens(&self) -> bool {
343        false
344    }
345
346    /// Postgres supports `NOTNULL` as an alias for `IS NOT NULL`
347    /// See: <https://www.postgresql.org/docs/17/functions-comparison.html>
348    fn supports_notnull_operator(&self) -> bool {
349        true
350    }
351
352    /// [Postgres] supports optional field and precision options for `INTERVAL` data type.
353    ///
354    /// [Postgres]: https://www.postgresql.org/docs/17/datatype-datetime.html
355    fn supports_interval_options(&self) -> bool {
356        true
357    }
358
359    fn supports_insert_table_alias(&self) -> bool {
360        true
361    }
362
363    fn supports_create_table_like_parenthesized(&self) -> bool {
364        true
365    }
366
367    fn supports_select_wildcard_with_alias(&self) -> bool {
368        true
369    }
370
371    fn supports_comma_separated_trim(&self) -> bool {
372        true
373    }
374
375    fn supports_xml_expressions(&self) -> bool {
376        true
377    }
378
379    fn supports_aliased_function_args(&self) -> bool {
380        true
381    }
382
383    /// Postgres supports query optimizer hints via the `pg_hint_plan` extension,
384    /// using the same comment-prefixed-with-`+` syntax as MySQL and Oracle.
385    ///
386    /// See <https://github.com/ossc-db/pg_hint_plan>
387    fn supports_comment_optimizer_hint(&self) -> bool {
388        true
389    }
390}