Skip to content
This repository was archived by the owner on Dec 25, 2019. It is now read-only.

Commit e9c5567

Browse files
authored
Merge pull request #135 from andygrove/show-columns
Support MySQL `SHOW COLUMNS` statement
2 parents f4df340 + e1ded18 commit e9c5567

8 files changed

Lines changed: 415 additions & 11 deletions

File tree

src/ast/mod.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,29 @@ pub enum Statement {
440440
/// `RESTRICT` or no drop behavior at all was specified.
441441
cascade: bool,
442442
},
443+
/// SET <variable>
444+
///
445+
/// Note: this is not a standard SQL statement, but it is supported by at
446+
/// least MySQL and PostgreSQL. Not all MySQL-specific syntatic forms are
447+
/// supported yet.
448+
SetVariable {
449+
local: bool,
450+
variable: Ident,
451+
value: SetVariableValue,
452+
},
453+
/// SHOW <variable>
454+
///
455+
/// Note: this is a PostgreSQL-specific statement.
456+
ShowVariable { variable: Ident },
457+
/// SHOW COLUMNS
458+
///
459+
/// Note: this is a MySQL-specific statement.
460+
ShowColumns {
461+
extended: bool,
462+
full: bool,
463+
table_name: ObjectName,
464+
filter: Option<ShowStatementFilter>,
465+
},
443466
/// `{ BEGIN [ TRANSACTION | WORK ] | START TRANSACTION } ...`
444467
StartTransaction { modes: Vec<TransactionMode> },
445468
/// `SET TRANSACTION ...`
@@ -451,6 +474,9 @@ pub enum Statement {
451474
}
452475

453476
impl fmt::Display for Statement {
477+
// Clippy thinks this function is too complicated, but it is painful to
478+
// split up without extracting structs for each `Statement` variant.
479+
#[allow(clippy::cognitive_complexity)]
454480
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
455481
match self {
456482
Statement::Query(s) => write!(f, "{}", s),
@@ -589,6 +615,37 @@ impl fmt::Display for Statement {
589615
display_comma_separated(names),
590616
if *cascade { " CASCADE" } else { "" },
591617
),
618+
Statement::SetVariable {
619+
local,
620+
variable,
621+
value,
622+
} => {
623+
f.write_str("SET ")?;
624+
if *local {
625+
f.write_str("LOCAL ")?;
626+
}
627+
write!(f, "{} = {}", variable, value)
628+
}
629+
Statement::ShowVariable { variable } => write!(f, "SHOW {}", variable),
630+
Statement::ShowColumns {
631+
extended,
632+
full,
633+
table_name,
634+
filter,
635+
} => {
636+
f.write_str("SHOW ")?;
637+
if *extended {
638+
f.write_str("EXTENDED ")?;
639+
}
640+
if *full {
641+
f.write_str("FULL ")?;
642+
}
643+
write!(f, "COLUMNS FROM {}", table_name)?;
644+
if let Some(filter) = filter {
645+
write!(f, " {}", filter)?;
646+
}
647+
Ok(())
648+
}
592649
Statement::StartTransaction { modes } => {
593650
write!(f, "START TRANSACTION")?;
594651
if !modes.is_empty() {
@@ -780,3 +837,35 @@ impl fmt::Display for TransactionIsolationLevel {
780837
})
781838
}
782839
}
840+
841+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
842+
pub enum ShowStatementFilter {
843+
Like(String),
844+
Where(Expr),
845+
}
846+
847+
impl fmt::Display for ShowStatementFilter {
848+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
849+
use ShowStatementFilter::*;
850+
match self {
851+
Like(pattern) => write!(f, "LIKE '{}'", value::escape_single_quote_string(pattern)),
852+
Where(expr) => write!(f, "WHERE {}", expr),
853+
}
854+
}
855+
}
856+
857+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
858+
pub enum SetVariableValue {
859+
Ident(Ident),
860+
Literal(Value),
861+
}
862+
863+
impl fmt::Display for SetVariableValue {
864+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
865+
use SetVariableValue::*;
866+
match self {
867+
Ident(ident) => f.write_str(ident),
868+
Literal(literal) => write!(f, "{}", literal),
869+
}
870+
}
871+
}

src/ast/value.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ impl fmt::Display for DateTimeField {
140140
}
141141
}
142142

143-
struct EscapeSingleQuoteString<'a>(&'a str);
143+
pub struct EscapeSingleQuoteString<'a>(&'a str);
144+
144145
impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
145146
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146147
for c in self.0.chars() {
@@ -153,6 +154,7 @@ impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
153154
Ok(())
154155
}
155156
}
156-
fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> {
157+
158+
pub fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> {
157159
EscapeSingleQuoteString(s)
158160
}

src/dialect/keywords.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ define_keywords!(
102102
COLLATE,
103103
COLLECT,
104104
COLUMN,
105+
COLUMNS,
105106
COMMIT,
106107
COMMITTED,
107108
CONDITION,
@@ -166,10 +167,12 @@ define_keywords!(
166167
EXECUTE,
167168
EXISTS,
168169
EXP,
170+
EXTENDED,
169171
EXTERNAL,
170172
EXTRACT,
171173
FALSE,
172174
FETCH,
175+
FIELDS,
173176
FIRST,
174177
FILTER,
175178
FIRST_VALUE,
@@ -332,8 +335,10 @@ define_keywords!(
332335
SELECT,
333336
SENSITIVE,
334337
SERIALIZABLE,
338+
SESSION,
335339
SESSION_USER,
336340
SET,
341+
SHOW,
337342
SIMILAR,
338343
SMALLINT,
339344
SOME,

src/dialect/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ mod ansi;
1414
mod generic;
1515
pub mod keywords;
1616
mod mssql;
17+
mod mysql;
1718
mod postgresql;
1819

1920
use std::fmt::Debug;
2021

2122
pub use self::ansi::AnsiDialect;
2223
pub use self::generic::GenericDialect;
2324
pub use self::mssql::MsSqlDialect;
25+
pub use self::mysql::MySqlDialect;
2426
pub use self::postgresql::PostgreSqlDialect;
2527

2628
pub trait Dialect: Debug {

src/dialect/mysql.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed under the Apache License, Version 2.0 (the "License");
2+
// you may not use this file except in compliance with the License.
3+
// You may obtain a copy of the License at
4+
//
5+
// http://www.apache.org/licenses/LICENSE-2.0
6+
//
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS,
9+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
// See the License for the specific language governing permissions and
11+
// limitations under the License.
12+
13+
use crate::dialect::Dialect;
14+
15+
#[derive(Debug)]
16+
pub struct MySqlDialect {}
17+
18+
impl Dialect for MySqlDialect {
19+
fn is_identifier_start(&self, ch: char) -> bool {
20+
// See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html.
21+
// We don't yet support identifiers beginning with numbers, as that
22+
// makes it hard to distinguish numeric literals.
23+
(ch >= 'a' && ch <= 'z')
24+
|| (ch >= 'A' && ch <= 'Z')
25+
|| ch == '_'
26+
|| ch == '$'
27+
|| (ch >= '\u{0080}' && ch <= '\u{ffff}')
28+
}
29+
30+
fn is_identifier_part(&self, ch: char) -> bool {
31+
self.is_identifier_start(ch) || (ch >= '0' && ch <= '9')
32+
}
33+
}

src/parser.rs

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,9 @@ impl Parser {
125125
"UPDATE" => Ok(self.parse_update()?),
126126
"ALTER" => Ok(self.parse_alter()?),
127127
"COPY" => Ok(self.parse_copy()?),
128+
"SET" => Ok(self.parse_set()?),
129+
"SHOW" => Ok(self.parse_show()?),
128130
"START" => Ok(self.parse_start_transaction()?),
129-
"SET" => Ok(self.parse_set_transaction()?),
130131
// `BEGIN` is a nonstandard but common alias for the
131132
// standard `START TRANSACTION` statement. It is supported
132133
// by at least PostgreSQL and MySQL.
@@ -763,7 +764,11 @@ impl Parser {
763764
#[must_use]
764765
pub fn parse_one_of_keywords(&mut self, keywords: &[&'static str]) -> Option<&'static str> {
765766
for keyword in keywords {
766-
assert!(keywords::ALL_KEYWORDS.contains(keyword));
767+
assert!(
768+
keywords::ALL_KEYWORDS.contains(keyword),
769+
"{} is not contained in keyword list",
770+
keyword
771+
);
767772
}
768773
match self.peek_token() {
769774
Some(Token::Word(ref k)) => keywords
@@ -1597,6 +1602,74 @@ impl Parser {
15971602
})
15981603
}
15991604

1605+
pub fn parse_set(&mut self) -> Result<Statement, ParserError> {
1606+
let modifier = self.parse_one_of_keywords(&["SESSION", "LOCAL"]);
1607+
let variable = self.parse_identifier()?;
1608+
if self.consume_token(&Token::Eq) || self.parse_keyword("TO") {
1609+
let token = self.peek_token();
1610+
let value = match (self.parse_value(), token) {
1611+
(Ok(value), _) => SetVariableValue::Literal(value),
1612+
(Err(_), Some(Token::Word(ident))) => SetVariableValue::Ident(ident.as_ident()),
1613+
(Err(_), other) => self.expected("variable value", other)?,
1614+
};
1615+
Ok(Statement::SetVariable {
1616+
local: modifier == Some("LOCAL"),
1617+
variable,
1618+
value,
1619+
})
1620+
} else if variable == "TRANSACTION" && modifier.is_none() {
1621+
Ok(Statement::SetTransaction {
1622+
modes: self.parse_transaction_modes()?,
1623+
})
1624+
} else {
1625+
self.expected("equals sign or TO", self.peek_token())
1626+
}
1627+
}
1628+
1629+
pub fn parse_show(&mut self) -> Result<Statement, ParserError> {
1630+
if self
1631+
.parse_one_of_keywords(&["EXTENDED", "FULL", "COLUMNS", "FIELDS"])
1632+
.is_some()
1633+
{
1634+
self.prev_token();
1635+
self.parse_show_columns()
1636+
} else {
1637+
Ok(Statement::ShowVariable {
1638+
variable: self.parse_identifier()?,
1639+
})
1640+
}
1641+
}
1642+
1643+
fn parse_show_columns(&mut self) -> Result<Statement, ParserError> {
1644+
let extended = self.parse_keyword("EXTENDED");
1645+
let full = self.parse_keyword("FULL");
1646+
self.expect_one_of_keywords(&["COLUMNS", "FIELDS"])?;
1647+
self.expect_one_of_keywords(&["FROM", "IN"])?;
1648+
let table_name = self.parse_object_name()?;
1649+
// MySQL also supports FROM <database> here. In other words, MySQL
1650+
// allows both FROM <table> FROM <database> and FROM <database>.<table>,
1651+
// while we only support the latter for now.
1652+
let filter = self.parse_show_statement_filter()?;
1653+
Ok(Statement::ShowColumns {
1654+
extended,
1655+
full,
1656+
table_name,
1657+
filter,
1658+
})
1659+
}
1660+
1661+
fn parse_show_statement_filter(&mut self) -> Result<Option<ShowStatementFilter>, ParserError> {
1662+
if self.parse_keyword("LIKE") {
1663+
Ok(Some(ShowStatementFilter::Like(
1664+
self.parse_literal_string()?,
1665+
)))
1666+
} else if self.parse_keyword("WHERE") {
1667+
Ok(Some(ShowStatementFilter::Where(self.parse_expr()?)))
1668+
} else {
1669+
Ok(None)
1670+
}
1671+
}
1672+
16001673
pub fn parse_table_and_joins(&mut self) -> Result<TableWithJoins, ParserError> {
16011674
let relation = self.parse_table_factor()?;
16021675

@@ -1932,13 +2005,6 @@ impl Parser {
19322005
})
19332006
}
19342007

1935-
pub fn parse_set_transaction(&mut self) -> Result<Statement, ParserError> {
1936-
self.expect_keyword("TRANSACTION")?;
1937-
Ok(Statement::SetTransaction {
1938-
modes: self.parse_transaction_modes()?,
1939-
})
1940-
}
1941-
19422008
pub fn parse_transaction_modes(&mut self) -> Result<Vec<TransactionMode>, ParserError> {
19432009
let mut modes = vec![];
19442010
let mut required = false;

0 commit comments

Comments
 (0)