Parse SET NAMES syntax in Postgres (#1752)

This commit is contained in:
Michael Victor Zink 2025-02-28 22:12:25 -08:00 committed by GitHub
parent a629ddf89b
commit 9e09b617e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 39 additions and 12 deletions

View file

@ -2956,10 +2956,8 @@ pub enum Statement {
/// ```sql
/// SET NAMES 'charset_name' [COLLATE 'collation_name']
/// ```
///
/// Note: this is a MySQL-specific statement.
SetNames {
charset_name: String,
charset_name: Ident,
collation_name: Option<String>,
},
/// ```sql
@ -4684,8 +4682,7 @@ impl fmt::Display for Statement {
charset_name,
collation_name,
} => {
f.write_str("SET NAMES ")?;
f.write_str(charset_name)?;
write!(f, "SET NAMES {}", charset_name)?;
if let Some(collation) = collation_name {
f.write_str(" COLLATE ")?;

View file

@ -155,4 +155,8 @@ impl Dialect for GenericDialect {
fn supports_match_against(&self) -> bool {
true
}
fn supports_set_names(&self) -> bool {
true
}
}

View file

@ -980,6 +980,16 @@ pub trait Dialect: Debug + Any {
fn supports_order_by_all(&self) -> bool {
false
}
/// Returns true if the dialect supports `SET NAMES <charset_name> [COLLATE <collation_name>]`.
///
/// - [MySQL](https://dev.mysql.com/doc/refman/8.4/en/set-names.html)
/// - [Postgres](https://www.postgresql.org/docs/17/sql-set.html)
///
/// Note: Postgres doesn't support the `COLLATE` clause, but we permissively parse it anyway.
fn supports_set_names(&self) -> bool {
false
}
}
/// This represents the operators for which precedence must be defined

View file

@ -137,6 +137,10 @@ impl Dialect for MySqlDialect {
fn supports_match_against(&self) -> bool {
true
}
fn supports_set_names(&self) -> bool {
true
}
}
/// `LOCK TABLES`

View file

@ -254,4 +254,8 @@ impl Dialect for PostgreSqlDialect {
fn supports_geometric_types(&self) -> bool {
true
}
fn supports_set_names(&self) -> bool {
true
}
}

View file

@ -10962,14 +10962,14 @@ impl<'a> Parser<'a> {
OneOrManyWithParens::One(self.parse_object_name(false)?)
};
if matches!(&variables, OneOrManyWithParens::One(variable) if variable.to_string().eq_ignore_ascii_case("NAMES")
&& dialect_of!(self is MySqlDialect | GenericDialect))
{
let names = matches!(&variables, OneOrManyWithParens::One(variable) if variable.to_string().eq_ignore_ascii_case("NAMES"));
if names && self.dialect.supports_set_names() {
if self.parse_keyword(Keyword::DEFAULT) {
return Ok(Statement::SetNamesDefault {});
}
let charset_name = self.parse_literal_string()?;
let charset_name = self.parse_identifier()?;
let collation_name = if self.parse_one_of_keywords(&[Keyword::COLLATE]).is_some() {
Some(self.parse_literal_string()?)
} else {

View file

@ -14631,3 +14631,11 @@ fn parse_array_type_def_with_brackets() {
dialects.verified_stmt("SELECT x::INT[]");
dialects.verified_stmt("SELECT STRING_TO_ARRAY('1,2,3', ',')::INT[3]");
}
#[test]
fn parse_set_names() {
let dialects = all_dialects_where(|d| d.supports_set_names());
dialects.verified_stmt("SET NAMES 'UTF8'");
dialects.verified_stmt("SET NAMES 'utf8'");
dialects.verified_stmt("SET NAMES UTF8 COLLATE bogus");
}

View file

@ -2696,7 +2696,7 @@ fn parse_set_names() {
assert_eq!(
stmt,
Statement::SetNames {
charset_name: "utf8mb4".to_string(),
charset_name: "utf8mb4".into(),
collation_name: None,
}
);
@ -2705,7 +2705,7 @@ fn parse_set_names() {
assert_eq!(
stmt,
Statement::SetNames {
charset_name: "utf8mb4".to_string(),
charset_name: "utf8mb4".into(),
collation_name: Some("bogus".to_string()),
}
);
@ -2716,7 +2716,7 @@ fn parse_set_names() {
assert_eq!(
stmt,
vec![Statement::SetNames {
charset_name: "utf8mb4".to_string(),
charset_name: "utf8mb4".into(),
collation_name: Some("bogus".to_string()),
}]
);