mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-24 16:53:46 +00:00
Support MySQL SHOW COLUMNS
statement
Co-authored-by: Nikhil Benesch <nikhil.benesch@gmail.com>
This commit is contained in:
parent
35a20091ea
commit
f64928e994
7 changed files with 248 additions and 3 deletions
|
@ -440,6 +440,15 @@ pub enum Statement {
|
||||||
/// `RESTRICT` or no drop behavior at all was specified.
|
/// `RESTRICT` or no drop behavior at all was specified.
|
||||||
cascade: bool,
|
cascade: bool,
|
||||||
},
|
},
|
||||||
|
/// SHOW COLUMNS
|
||||||
|
///
|
||||||
|
/// Note: this is a MySQL-specific statement.
|
||||||
|
ShowColumns {
|
||||||
|
extended: bool,
|
||||||
|
full: bool,
|
||||||
|
table_name: ObjectName,
|
||||||
|
filter: Option<ShowStatementFilter>,
|
||||||
|
},
|
||||||
/// `{ BEGIN [ TRANSACTION | WORK ] | START TRANSACTION } ...`
|
/// `{ BEGIN [ TRANSACTION | WORK ] | START TRANSACTION } ...`
|
||||||
StartTransaction { modes: Vec<TransactionMode> },
|
StartTransaction { modes: Vec<TransactionMode> },
|
||||||
/// `SET TRANSACTION ...`
|
/// `SET TRANSACTION ...`
|
||||||
|
@ -451,6 +460,9 @@ pub enum Statement {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Statement {
|
impl fmt::Display for Statement {
|
||||||
|
// Clippy thinks this function is too complicated, but it is painful to
|
||||||
|
// split up without extracting structs for each `Statement` variant.
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Statement::Query(s) => write!(f, "{}", s),
|
Statement::Query(s) => write!(f, "{}", s),
|
||||||
|
@ -589,6 +601,25 @@ impl fmt::Display for Statement {
|
||||||
display_comma_separated(names),
|
display_comma_separated(names),
|
||||||
if *cascade { " CASCADE" } else { "" },
|
if *cascade { " CASCADE" } else { "" },
|
||||||
),
|
),
|
||||||
|
Statement::ShowColumns {
|
||||||
|
extended,
|
||||||
|
full,
|
||||||
|
table_name,
|
||||||
|
filter,
|
||||||
|
} => {
|
||||||
|
f.write_str("SHOW ")?;
|
||||||
|
if *extended {
|
||||||
|
f.write_str("EXTENDED ")?;
|
||||||
|
}
|
||||||
|
if *full {
|
||||||
|
f.write_str("FULL ")?;
|
||||||
|
}
|
||||||
|
write!(f, "COLUMNS FROM {}", table_name)?;
|
||||||
|
if let Some(filter) = filter {
|
||||||
|
write!(f, " {}", filter)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Statement::StartTransaction { modes } => {
|
Statement::StartTransaction { modes } => {
|
||||||
write!(f, "START TRANSACTION")?;
|
write!(f, "START TRANSACTION")?;
|
||||||
if !modes.is_empty() {
|
if !modes.is_empty() {
|
||||||
|
@ -780,3 +811,19 @@ impl fmt::Display for TransactionIsolationLevel {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ShowStatementFilter {
|
||||||
|
Like(String),
|
||||||
|
Where(Expr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ShowStatementFilter {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use ShowStatementFilter::*;
|
||||||
|
match self {
|
||||||
|
Like(pattern) => write!(f, "LIKE '{}'", value::escape_single_quote_string(pattern)),
|
||||||
|
Where(expr) => write!(f, "WHERE {}", expr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -139,7 +139,8 @@ impl fmt::Display for DateTimeField {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EscapeSingleQuoteString<'a>(&'a str);
|
pub struct EscapeSingleQuoteString<'a>(&'a str);
|
||||||
|
|
||||||
impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
|
impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
for c in self.0.chars() {
|
for c in self.0.chars() {
|
||||||
|
@ -152,6 +153,7 @@ impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> {
|
|
||||||
|
pub fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> {
|
||||||
EscapeSingleQuoteString(s)
|
EscapeSingleQuoteString(s)
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,7 @@ define_keywords!(
|
||||||
COLLATE,
|
COLLATE,
|
||||||
COLLECT,
|
COLLECT,
|
||||||
COLUMN,
|
COLUMN,
|
||||||
|
COLUMNS,
|
||||||
COMMIT,
|
COMMIT,
|
||||||
COMMITTED,
|
COMMITTED,
|
||||||
CONDITION,
|
CONDITION,
|
||||||
|
@ -166,10 +167,12 @@ define_keywords!(
|
||||||
EXECUTE,
|
EXECUTE,
|
||||||
EXISTS,
|
EXISTS,
|
||||||
EXP,
|
EXP,
|
||||||
|
EXTENDED,
|
||||||
EXTERNAL,
|
EXTERNAL,
|
||||||
EXTRACT,
|
EXTRACT,
|
||||||
FALSE,
|
FALSE,
|
||||||
FETCH,
|
FETCH,
|
||||||
|
FIELDS,
|
||||||
FIRST,
|
FIRST,
|
||||||
FILTER,
|
FILTER,
|
||||||
FIRST_VALUE,
|
FIRST_VALUE,
|
||||||
|
@ -334,6 +337,7 @@ define_keywords!(
|
||||||
SERIALIZABLE,
|
SERIALIZABLE,
|
||||||
SESSION_USER,
|
SESSION_USER,
|
||||||
SET,
|
SET,
|
||||||
|
SHOW,
|
||||||
SIMILAR,
|
SIMILAR,
|
||||||
SMALLINT,
|
SMALLINT,
|
||||||
SOME,
|
SOME,
|
||||||
|
|
|
@ -14,6 +14,7 @@ mod ansi;
|
||||||
mod generic;
|
mod generic;
|
||||||
pub mod keywords;
|
pub mod keywords;
|
||||||
mod mssql;
|
mod mssql;
|
||||||
|
mod mysql;
|
||||||
mod postgresql;
|
mod postgresql;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -21,6 +22,7 @@ use std::fmt::Debug;
|
||||||
pub use self::ansi::AnsiDialect;
|
pub use self::ansi::AnsiDialect;
|
||||||
pub use self::generic::GenericDialect;
|
pub use self::generic::GenericDialect;
|
||||||
pub use self::mssql::MsSqlDialect;
|
pub use self::mssql::MsSqlDialect;
|
||||||
|
pub use self::mysql::MySqlDialect;
|
||||||
pub use self::postgresql::PostgreSqlDialect;
|
pub use self::postgresql::PostgreSqlDialect;
|
||||||
|
|
||||||
pub trait Dialect: Debug {
|
pub trait Dialect: Debug {
|
||||||
|
|
33
src/dialect/mysql.rs
Normal file
33
src/dialect/mysql.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use crate::dialect::Dialect;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MySqlDialect {}
|
||||||
|
|
||||||
|
impl Dialect for MySqlDialect {
|
||||||
|
fn is_identifier_start(&self, ch: char) -> bool {
|
||||||
|
// See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html.
|
||||||
|
// We don't yet support identifiers beginning with numbers, as that
|
||||||
|
// makes it hard to distinguish numeric literals.
|
||||||
|
(ch >= 'a' && ch <= 'z')
|
||||||
|
|| (ch >= 'A' && ch <= 'Z')
|
||||||
|
|| ch == '_'
|
||||||
|
|| ch == '$'
|
||||||
|
|| (ch >= '\u{0080}' && ch <= '\u{ffff}')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier_part(&self, ch: char) -> bool {
|
||||||
|
self.is_identifier_start(ch) || (ch >= '0' && ch <= '9')
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,6 +125,7 @@ impl Parser {
|
||||||
"UPDATE" => Ok(self.parse_update()?),
|
"UPDATE" => Ok(self.parse_update()?),
|
||||||
"ALTER" => Ok(self.parse_alter()?),
|
"ALTER" => Ok(self.parse_alter()?),
|
||||||
"COPY" => Ok(self.parse_copy()?),
|
"COPY" => Ok(self.parse_copy()?),
|
||||||
|
"SHOW" => Ok(self.parse_show()?),
|
||||||
"START" => Ok(self.parse_start_transaction()?),
|
"START" => Ok(self.parse_start_transaction()?),
|
||||||
"SET" => Ok(self.parse_set_transaction()?),
|
"SET" => Ok(self.parse_set_transaction()?),
|
||||||
// `BEGIN` is a nonstandard but common alias for the
|
// `BEGIN` is a nonstandard but common alias for the
|
||||||
|
@ -763,7 +764,11 @@ impl Parser {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn parse_one_of_keywords(&mut self, keywords: &[&'static str]) -> Option<&'static str> {
|
pub fn parse_one_of_keywords(&mut self, keywords: &[&'static str]) -> Option<&'static str> {
|
||||||
for keyword in keywords {
|
for keyword in keywords {
|
||||||
assert!(keywords::ALL_KEYWORDS.contains(keyword));
|
assert!(
|
||||||
|
keywords::ALL_KEYWORDS.contains(keyword),
|
||||||
|
"{} is not contained in keyword list",
|
||||||
|
keyword
|
||||||
|
);
|
||||||
}
|
}
|
||||||
match self.peek_token() {
|
match self.peek_token() {
|
||||||
Some(Token::Word(ref k)) => keywords
|
Some(Token::Word(ref k)) => keywords
|
||||||
|
@ -1588,6 +1593,48 @@ impl Parser {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_show(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
if self
|
||||||
|
.parse_one_of_keywords(&["EXTENDED", "FULL", "COLUMNS", "FIELDS"])
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self.prev_token();
|
||||||
|
self.parse_show_columns()
|
||||||
|
} else {
|
||||||
|
self.expected("EXTENDED, FULL, COLUMNS, or FIELDS", self.peek_token())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_show_columns(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
let extended = self.parse_keyword("EXTENDED");
|
||||||
|
let full = self.parse_keyword("FULL");
|
||||||
|
self.expect_one_of_keywords(&["COLUMNS", "FIELDS"])?;
|
||||||
|
self.expect_one_of_keywords(&["FROM", "IN"])?;
|
||||||
|
let table_name = self.parse_object_name()?;
|
||||||
|
// MySQL also supports FROM <database> here. In other words, MySQL
|
||||||
|
// allows both FROM <table> FROM <database> and FROM <database>.<table>,
|
||||||
|
// while we only support the latter for now.
|
||||||
|
let filter = self.parse_show_statement_filter()?;
|
||||||
|
Ok(Statement::ShowColumns {
|
||||||
|
extended,
|
||||||
|
full,
|
||||||
|
table_name,
|
||||||
|
filter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_show_statement_filter(&mut self) -> Result<Option<ShowStatementFilter>, ParserError> {
|
||||||
|
if self.parse_keyword("LIKE") {
|
||||||
|
Ok(Some(ShowStatementFilter::Like(
|
||||||
|
self.parse_literal_string()?,
|
||||||
|
)))
|
||||||
|
} else if self.parse_keyword("WHERE") {
|
||||||
|
Ok(Some(ShowStatementFilter::Where(self.parse_expr()?)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_table_and_joins(&mut self) -> Result<TableWithJoins, ParserError> {
|
pub fn parse_table_and_joins(&mut self) -> Result<TableWithJoins, ParserError> {
|
||||||
let relation = self.parse_table_factor()?;
|
let relation = self.parse_table_factor()?;
|
||||||
|
|
||||||
|
|
110
tests/sqlparser_mysql.rs
Normal file
110
tests/sqlparser_mysql.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
|
||||||
|
//! Test SQL syntax specific to MySQL. The parser based on the generic dialect
|
||||||
|
//! is also tested (on the inputs it can handle).
|
||||||
|
|
||||||
|
use sqlparser::ast::*;
|
||||||
|
use sqlparser::dialect::{GenericDialect, MySqlDialect};
|
||||||
|
use sqlparser::test_utils::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_identifiers() {
|
||||||
|
mysql().verified_stmt("SELECT $a$, àà");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_show_columns() {
|
||||||
|
let table_name = ObjectName(vec!["mytable".to_string()]);
|
||||||
|
assert_eq!(
|
||||||
|
mysql_and_generic().verified_stmt("SHOW COLUMNS FROM mytable"),
|
||||||
|
Statement::ShowColumns {
|
||||||
|
extended: false,
|
||||||
|
full: false,
|
||||||
|
table_name: table_name.clone(),
|
||||||
|
filter: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mysql_and_generic().verified_stmt("SHOW COLUMNS FROM mydb.mytable"),
|
||||||
|
Statement::ShowColumns {
|
||||||
|
extended: false,
|
||||||
|
full: false,
|
||||||
|
table_name: ObjectName(vec!["mydb".to_string(), "mytable".to_string()]),
|
||||||
|
filter: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mysql_and_generic().verified_stmt("SHOW EXTENDED COLUMNS FROM mytable"),
|
||||||
|
Statement::ShowColumns {
|
||||||
|
extended: true,
|
||||||
|
full: false,
|
||||||
|
table_name: table_name.clone(),
|
||||||
|
filter: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mysql_and_generic().verified_stmt("SHOW FULL COLUMNS FROM mytable"),
|
||||||
|
Statement::ShowColumns {
|
||||||
|
extended: false,
|
||||||
|
full: true,
|
||||||
|
table_name: table_name.clone(),
|
||||||
|
filter: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mysql_and_generic().verified_stmt("SHOW COLUMNS FROM mytable LIKE 'pattern'"),
|
||||||
|
Statement::ShowColumns {
|
||||||
|
extended: false,
|
||||||
|
full: false,
|
||||||
|
table_name: table_name.clone(),
|
||||||
|
filter: Some(ShowStatementFilter::Like("pattern".into())),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mysql_and_generic().verified_stmt("SHOW COLUMNS FROM mytable WHERE 1 = 2"),
|
||||||
|
Statement::ShowColumns {
|
||||||
|
extended: false,
|
||||||
|
full: false,
|
||||||
|
table_name: table_name.clone(),
|
||||||
|
filter: Some(ShowStatementFilter::Where(
|
||||||
|
mysql_and_generic().verified_expr("1 = 2")
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
mysql_and_generic()
|
||||||
|
.one_statement_parses_to("SHOW FIELDS FROM mytable", "SHOW COLUMNS FROM mytable");
|
||||||
|
mysql_and_generic()
|
||||||
|
.one_statement_parses_to("SHOW COLUMNS IN mytable", "SHOW COLUMNS FROM mytable");
|
||||||
|
mysql_and_generic()
|
||||||
|
.one_statement_parses_to("SHOW FIELDS IN mytable", "SHOW COLUMNS FROM mytable");
|
||||||
|
|
||||||
|
// unhandled things are truly unhandled
|
||||||
|
match mysql_and_generic().parse_sql_statements("SHOW COLUMNS FROM mytable FROM mydb") {
|
||||||
|
Err(_) => {}
|
||||||
|
Ok(val) => panic!("unexpected successful parse: {:?}", val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mysql() -> TestedDialects {
|
||||||
|
TestedDialects {
|
||||||
|
dialects: vec![Box::new(MySqlDialect {})],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mysql_and_generic() -> TestedDialects {
|
||||||
|
TestedDialects {
|
||||||
|
dialects: vec![Box::new(MySqlDialect {}), Box::new(GenericDialect {})],
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue