Skip to content

Parse SET NAMES syntax in Postgres #1752

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ")?;
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,8 @@ impl Dialect for GenericDialect {
fn supports_match_against(&self) -> bool {
true
}

fn supports_set_names(&self) -> bool {
true
}
}
10 changes: 10 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,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
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ impl Dialect for MySqlDialect {
fn supports_match_against(&self) -> bool {
true
}

fn supports_set_names(&self) -> bool {
true
}
}

/// `LOCK TABLES`
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,4 +254,8 @@ impl Dialect for PostgreSqlDialect {
fn supports_geometric_types(&self) -> bool {
true
}

fn supports_set_names(&self) -> bool {
true
}
}
8 changes: 4 additions & 4 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14617,3 +14617,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");
}
6 changes: 3 additions & 3 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2685,7 +2685,7 @@ fn parse_set_names() {
assert_eq!(
stmt,
Statement::SetNames {
charset_name: "utf8mb4".to_string(),
charset_name: "utf8mb4".into(),
collation_name: None,
}
);
Expand All @@ -2694,7 +2694,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()),
}
);
Expand All @@ -2705,7 +2705,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()),
}]
);
Expand Down