Skip to content

Commit b192e82

Browse files
committedJan 12, 2018
0 parents  commit b192e82

15 files changed

+1598
-0
lines changed
 

‎.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
/target/
3+
**/*.rs.bk

‎Cargo.lock

+82
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "rust-issue-47364"
3+
version = "0.1.0"
4+
authors = ["Jon Gjengset <jon@thesquareplanet.com>"]
5+
6+
[dependencies]
7+
nom_sql = { path = "nom-sql/" }

‎nom-sql/.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Generated by Cargo
2+
# will have compiled files and executables
3+
/target/
4+
5+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6+
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
7+
Cargo.lock

‎nom-sql/Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "nom_sql"
3+
version = "0.0.1"
4+
authors = ["Malte Schwarzkopf <malte@csail.mit.edu>"]
5+
6+
[dependencies]
7+
nom = "^1.2.4"
8+
serde = "1.0"
9+
serde_derive = "1.0"

‎nom-sql/src/caseless_tag.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/// Case-insensitive tag! variant for nom
2+
///
3+
/// By Paul English (https://gist.github.com/log0ymxm/d9c3fc9598cf2d92b8ae89a9ce5341d8)
4+
///
5+
/// This implementation has some performance issues related to extra memory allocations
6+
/// (see https://github.com/Geal/nom/issues/251), but it works for the moment.
7+
8+
macro_rules! caseless_tag (
9+
($i:expr, $inp: expr) => (
10+
{
11+
#[inline(always)]
12+
fn as_lower(b: &str) -> String {
13+
let s = b.to_string();
14+
s.to_lowercase()
15+
}
16+
17+
let expected = $inp;
18+
let lower = as_lower(&expected);
19+
let bytes = lower.as_bytes();
20+
21+
caseless_tag_bytes!($i,bytes)
22+
}
23+
);
24+
);
25+
26+
macro_rules! caseless_tag_bytes (
27+
($i:expr, $bytes: expr) => (
28+
{
29+
use std::cmp::min;
30+
let len = $i.len();
31+
let blen = $bytes.len();
32+
let m = min(len, blen);
33+
let reduced = &$i[..m];
34+
35+
let s = str::from_utf8(reduced).unwrap();
36+
let s2 = s.to_string();
37+
let lowered = s2.to_lowercase();
38+
let lowered_bytes = lowered.as_bytes();
39+
40+
let b = &$bytes[..m];
41+
42+
let res: IResult<_,_> = if lowered_bytes != b {
43+
IResult::Error(Err::Position(ErrorKind::Tag, $i))
44+
} else if m < blen {
45+
IResult::Incomplete(Needed::Size(blen))
46+
} else {
47+
IResult::Done(&$i[blen..], reduced)
48+
};
49+
res
50+
}
51+
);
52+
);
53+
54+
#[test]
55+
fn test_caseless_tag() {
56+
use nom::{Err, ErrorKind, IResult, Needed};
57+
use std::str;
58+
59+
named!(x, caseless_tag!("AbCD"));
60+
let r = x(&b"abcdefGH"[..]);
61+
assert_eq!(r, IResult::Done(&b"efGH"[..], &b"abcd"[..]));
62+
let r = x(&b"aBcdefGH"[..]);
63+
assert_eq!(r, IResult::Done(&b"efGH"[..], &b"aBcd"[..]));
64+
}

‎nom-sql/src/column.rs

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use std::cmp::Ordering;
2+
use std::fmt::{self, Display};
3+
use std::str;
4+
5+
use common::{Literal, SqlType};
6+
7+
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
8+
pub enum FunctionExpression {
9+
Avg(Column, bool),
10+
Count(Column, bool),
11+
CountStar,
12+
Sum(Column, bool),
13+
Max(Column),
14+
Min(Column),
15+
GroupConcat(Column, String),
16+
}
17+
18+
impl Display for FunctionExpression {
19+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20+
match *self {
21+
FunctionExpression::Avg(ref col, d) if d => {
22+
write!(f, "avg(distinct {})", col.name.as_str())
23+
}
24+
FunctionExpression::Count(ref col, d) if d => {
25+
write!(f, "count(distinct {})", col.name.as_str())
26+
}
27+
FunctionExpression::Sum(ref col, d) if d => {
28+
write!(f, "sum(distinct {})", col.name.as_str())
29+
}
30+
31+
FunctionExpression::Avg(ref col, _) => write!(f, "avg({})", col.name.as_str()),
32+
FunctionExpression::Count(ref col, _) => write!(f, "count({})", col.name.as_str()),
33+
FunctionExpression::CountStar => write!(f, "count(all)"),
34+
FunctionExpression::Sum(ref col, _) => write!(f, "sum({})", col.name.as_str()),
35+
FunctionExpression::Max(ref col) => write!(f, "max({})", col.name.as_str()),
36+
FunctionExpression::Min(ref col) => write!(f, "min({})", col.name.as_str()),
37+
FunctionExpression::GroupConcat(ref col, ref s) => {
38+
write!(f, "group_concat({}, {})", col.name.as_str(), s)
39+
}
40+
}
41+
}
42+
}
43+
44+
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
45+
pub struct Column {
46+
pub name: String,
47+
pub alias: Option<String>,
48+
pub table: Option<String>,
49+
pub function: Option<Box<FunctionExpression>>,
50+
}
51+
52+
impl fmt::Display for Column {
53+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54+
if let Some(ref table) = self.table {
55+
write!(f, "{}.{}", table, self.name)?;
56+
} else {
57+
write!(f, "{}", self.name)?;
58+
}
59+
if let Some(ref alias) = self.alias {
60+
write!(f, " AS {}", alias)?;
61+
}
62+
Ok(())
63+
}
64+
}
65+
66+
impl<'a> From<&'a str> for Column {
67+
fn from(c: &str) -> Column {
68+
match c.find(".") {
69+
None => Column {
70+
name: String::from(c),
71+
alias: None,
72+
table: None,
73+
function: None,
74+
},
75+
Some(i) => Column {
76+
name: String::from(&c[i + 1..]),
77+
alias: None,
78+
table: Some(String::from(&c[0..i])),
79+
function: None,
80+
},
81+
}
82+
}
83+
}
84+
85+
impl Ord for Column {
86+
fn cmp(&self, other: &Column) -> Ordering {
87+
if self.table.is_some() && other.table.is_some() {
88+
match self.table.cmp(&other.table) {
89+
Ordering::Equal => self.name.cmp(&other.name),
90+
x => x,
91+
}
92+
} else {
93+
self.name.cmp(&other.name)
94+
}
95+
}
96+
}
97+
98+
impl PartialOrd for Column {
99+
fn partial_cmp(&self, other: &Column) -> Option<Ordering> {
100+
if self.table.is_some() && other.table.is_some() {
101+
match self.table.cmp(&other.table) {
102+
Ordering::Equal => Some(self.name.cmp(&other.name)),
103+
x => Some(x),
104+
}
105+
} else if self.table.is_none() && other.table.is_none() {
106+
Some(self.name.cmp(&other.name))
107+
} else {
108+
None
109+
}
110+
}
111+
}
112+
113+
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
114+
pub enum ColumnConstraint {
115+
NotNull,
116+
DefaultValue(Literal),
117+
AutoIncrement,
118+
}
119+
120+
impl fmt::Display for ColumnConstraint {
121+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122+
match *self {
123+
ColumnConstraint::NotNull => write!(f, "NOT NULL"),
124+
ColumnConstraint::DefaultValue(ref literal) => {
125+
write!(f, "DEFAULT {}", literal.to_string())
126+
}
127+
ColumnConstraint::AutoIncrement => write!(f, "AUTOINCREMENT"),
128+
}
129+
}
130+
}
131+
132+
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
133+
pub struct ColumnSpecification {
134+
pub column: Column,
135+
pub sql_type: SqlType,
136+
pub constraints: Vec<ColumnConstraint>,
137+
}
138+
139+
impl fmt::Display for ColumnSpecification {
140+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141+
write!(f, "{} {}", self.column, self.sql_type)?;
142+
for constraint in self.constraints.iter() {
143+
write!(f, " {}", constraint)?;
144+
}
145+
Ok(())
146+
}
147+
}
148+
149+
impl ColumnSpecification {
150+
pub fn new(c: Column, t: SqlType) -> ColumnSpecification {
151+
ColumnSpecification {
152+
column: c,
153+
sql_type: t,
154+
constraints: vec![],
155+
}
156+
}
157+
158+
pub fn with_constraints(
159+
c: Column,
160+
t: SqlType,
161+
ccs: Vec<ColumnConstraint>,
162+
) -> ColumnSpecification {
163+
ColumnSpecification {
164+
column: c,
165+
sql_type: t,
166+
constraints: ccs,
167+
}
168+
}
169+
}
170+
171+
#[cfg(test)]
172+
mod tests {
173+
use super::*;
174+
175+
#[test]
176+
fn column_from_str() {
177+
let s = "table.col";
178+
let c = Column::from(s);
179+
180+
assert_eq!(
181+
c,
182+
Column {
183+
name: String::from("col"),
184+
alias: None,
185+
table: Some(String::from("table")),
186+
function: None,
187+
}
188+
);
189+
}
190+
}

‎nom-sql/src/common.rs

+640
Large diffs are not rendered by default.

‎nom-sql/src/condition.rs

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
use nom::multispace;
2+
use nom::{Err, ErrorKind, IResult, Needed};
3+
use std::collections::{HashSet, VecDeque};
4+
use std::str;
5+
use std::fmt;
6+
7+
use column::Column;
8+
use common::{binary_comparison_operator, column_identifier, integer_literal, string_literal,
9+
Literal, Operator};
10+
11+
#[derive(Clone, Debug, Hash, PartialEq, Serialize, Deserialize)]
12+
pub enum ConditionBase {
13+
Field(Column),
14+
Literal(Literal),
15+
Placeholder,
16+
}
17+
18+
impl fmt::Display for ConditionBase {
19+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20+
match *self {
21+
ConditionBase::Field(ref col) => write!(f, "{}", col),
22+
ConditionBase::Literal(ref literal) => write!(f, "{}", literal.to_string()),
23+
ConditionBase::Placeholder => write!(f, "?"),
24+
}
25+
}
26+
}
27+
28+
#[derive(Clone, Debug, Hash, PartialEq, Serialize, Deserialize)]
29+
pub struct ConditionTree {
30+
pub operator: Operator,
31+
pub left: Box<ConditionExpression>,
32+
pub right: Box<ConditionExpression>,
33+
}
34+
35+
impl<'a> ConditionTree {
36+
pub fn contained_columns(&'a self) -> HashSet<&'a Column> {
37+
let mut s = HashSet::new();
38+
let mut q = VecDeque::<&'a ConditionTree>::new();
39+
q.push_back(self);
40+
while let Some(ref ct) = q.pop_front() {
41+
match *ct.left.as_ref() {
42+
ConditionExpression::Base(ConditionBase::Field(ref c)) => {
43+
s.insert(c);
44+
}
45+
ConditionExpression::LogicalOp(ref ct)
46+
| ConditionExpression::ComparisonOp(ref ct) => q.push_back(ct),
47+
_ => (),
48+
}
49+
match *ct.right.as_ref() {
50+
ConditionExpression::Base(ConditionBase::Field(ref c)) => {
51+
s.insert(c);
52+
}
53+
ConditionExpression::LogicalOp(ref ct)
54+
| ConditionExpression::ComparisonOp(ref ct) => q.push_back(ct),
55+
_ => (),
56+
}
57+
}
58+
s
59+
}
60+
}
61+
62+
impl fmt::Display for ConditionTree {
63+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64+
write!(f, "{}", self.left)?;
65+
write!(f, " {} ", self.operator)?;
66+
write!(f, "{}", self.right)
67+
}
68+
}
69+
70+
#[derive(Clone, Debug, Hash, PartialEq, Serialize, Deserialize)]
71+
pub enum ConditionExpression {
72+
ComparisonOp(ConditionTree),
73+
LogicalOp(ConditionTree),
74+
NegationOp(Box<ConditionExpression>),
75+
Base(ConditionBase),
76+
}
77+
78+
impl fmt::Display for ConditionExpression {
79+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80+
match *self {
81+
ConditionExpression::ComparisonOp(ref tree) => write!(f, "{}", tree),
82+
ConditionExpression::LogicalOp(ref tree) => write!(f, "{}", tree),
83+
ConditionExpression::NegationOp(ref expr) => write!(f, "NOT {}", expr),
84+
ConditionExpression::Base(ref base) => write!(f, "{}", base),
85+
}
86+
}
87+
}
88+
89+
/// Parse a conditional expression into a condition tree structure
90+
named!(pub condition_expr<&[u8], ConditionExpression>,
91+
alt_complete!(
92+
chain!(
93+
left: and_expr ~
94+
multispace? ~
95+
caseless_tag!("or") ~
96+
multispace ~
97+
right: condition_expr,
98+
|| {
99+
ConditionExpression::LogicalOp(
100+
ConditionTree {
101+
operator: Operator::Or,
102+
left: Box::new(left),
103+
right: Box::new(right),
104+
}
105+
)
106+
}
107+
)
108+
| and_expr)
109+
);
110+
111+
named!(pub and_expr<&[u8], ConditionExpression>,
112+
alt_complete!(
113+
chain!(
114+
left: parenthetical_expr ~
115+
multispace? ~
116+
caseless_tag!("and") ~
117+
multispace ~
118+
right: and_expr,
119+
|| {
120+
ConditionExpression::LogicalOp(
121+
ConditionTree {
122+
operator: Operator::And,
123+
left: Box::new(left),
124+
right: Box::new(right),
125+
}
126+
)
127+
}
128+
)
129+
| parenthetical_expr)
130+
);
131+
132+
named!(pub parenthetical_expr<&[u8], ConditionExpression>,
133+
alt_complete!(
134+
delimited!(tag!("("), condition_expr, chain!(tag!(")") ~ multispace?, ||{}))
135+
| not_expr)
136+
);
137+
138+
named!(pub not_expr<&[u8], ConditionExpression>,
139+
alt_complete!(
140+
chain!(
141+
caseless_tag!("not") ~
142+
multispace ~
143+
right: parenthetical_expr,
144+
|| {
145+
ConditionExpression::NegationOp(Box::new(right))
146+
}
147+
)
148+
| boolean_primary)
149+
);
150+
151+
named!(boolean_primary<&[u8], ConditionExpression>,
152+
chain!(
153+
left: predicate ~
154+
multispace? ~
155+
op: binary_comparison_operator ~
156+
multispace? ~
157+
right: predicate,
158+
|| {
159+
ConditionExpression::ComparisonOp(
160+
ConditionTree {
161+
operator: op,
162+
left: Box::new(left),
163+
right: Box::new(right),
164+
}
165+
)
166+
167+
}
168+
)
169+
);
170+
171+
named!(predicate<&[u8], ConditionExpression>,
172+
delimited!(
173+
opt!(multispace),
174+
alt_complete!(
175+
chain!(
176+
tag!("?"),
177+
|| {
178+
ConditionExpression::Base(
179+
ConditionBase::Placeholder
180+
)
181+
}
182+
)
183+
| chain!(
184+
field: integer_literal,
185+
|| {
186+
ConditionExpression::Base(ConditionBase::Literal(field))
187+
}
188+
)
189+
| chain!(
190+
field: string_literal,
191+
|| {
192+
ConditionExpression::Base(ConditionBase::Literal(field))
193+
}
194+
)
195+
| chain!(
196+
field: column_identifier,
197+
|| {
198+
ConditionExpression::Base(
199+
ConditionBase::Field(field)
200+
)
201+
}
202+
)
203+
),
204+
opt!(multispace)
205+
)
206+
);

‎nom-sql/src/join.rs

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use nom::{Err, ErrorKind, IResult, Needed};
2+
use std::str;
3+
4+
use column::Column;
5+
use condition::ConditionExpression;
6+
use select::{JoinClause, SelectStatement};
7+
use table::Table;
8+
9+
#[derive(Clone, Debug, Hash, PartialEq, Serialize, Deserialize)]
10+
pub enum JoinRightSide {
11+
/// A single table.
12+
Table(Table),
13+
/// A comma-separated (and implicitly joined) sequence of tables.
14+
Tables(Vec<Table>),
15+
/// A nested selection, represented as (query, alias).
16+
NestedSelect(Box<SelectStatement>, Option<String>),
17+
/// A nested join clause.
18+
NestedJoin(Box<JoinClause>),
19+
}
20+
21+
#[derive(Clone, Debug, Hash, PartialEq, Serialize, Deserialize)]
22+
pub enum JoinOperator {
23+
Join,
24+
LeftJoin,
25+
LeftOuterJoin,
26+
InnerJoin,
27+
CrossJoin,
28+
StraightJoin,
29+
}
30+
31+
#[derive(Clone, Debug, Hash, PartialEq, Serialize, Deserialize)]
32+
pub enum JoinConstraint {
33+
On(ConditionExpression),
34+
Using(Vec<Column>),
35+
}
36+
37+
/// Parse binary comparison operators
38+
named!(pub join_operator<&[u8], JoinOperator>,
39+
alt_complete!(
40+
map!(caseless_tag!("join"), |_| JoinOperator::Join)
41+
| map!(caseless_tag!("left join"), |_| JoinOperator::LeftJoin)
42+
| map!(caseless_tag!("left outer join"), |_| JoinOperator::LeftOuterJoin)
43+
| map!(caseless_tag!("inner join"), |_| JoinOperator::InnerJoin)
44+
| map!(caseless_tag!("cross join"), |_| JoinOperator::CrossJoin)
45+
| map!(caseless_tag!("straight_join"), |_| JoinOperator::StraightJoin)
46+
)
47+
);

‎nom-sql/src/keywords.rs

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use nom::{Err, ErrorKind, IResult, Needed};
2+
3+
use std::str;
4+
5+
named!(keyword_follow_char<&[u8], char>,
6+
peek!(one_of!(" \n;()\t,="))
7+
);
8+
9+
named!(keyword_a_to_c<&[u8], &[u8]>,
10+
alt_complete!(
11+
terminated!(caseless_tag!("ABORT"), keyword_follow_char)
12+
| terminated!(caseless_tag!("ACTION"), keyword_follow_char)
13+
| terminated!(caseless_tag!("ADD"), keyword_follow_char)
14+
| terminated!(caseless_tag!("AFTER"), keyword_follow_char)
15+
| terminated!(caseless_tag!("ALL"), keyword_follow_char)
16+
| terminated!(caseless_tag!("ALTER"), keyword_follow_char)
17+
| terminated!(caseless_tag!("ANALYZE"), keyword_follow_char)
18+
| terminated!(caseless_tag!("AND"), keyword_follow_char)
19+
| terminated!(caseless_tag!("AS"), keyword_follow_char)
20+
| terminated!(caseless_tag!("ASC"), keyword_follow_char)
21+
| terminated!(caseless_tag!("ATTACH"), keyword_follow_char)
22+
| terminated!(caseless_tag!("AUTOINCREMENT"), keyword_follow_char)
23+
| terminated!(caseless_tag!("BEFORE"), keyword_follow_char)
24+
| terminated!(caseless_tag!("BEGIN"), keyword_follow_char)
25+
| terminated!(caseless_tag!("BETWEEN"), keyword_follow_char)
26+
| terminated!(caseless_tag!("BY"), keyword_follow_char)
27+
| terminated!(caseless_tag!("CASCADE"), keyword_follow_char)
28+
| terminated!(caseless_tag!("CASE"), keyword_follow_char)
29+
| terminated!(caseless_tag!("CAST"), keyword_follow_char)
30+
| terminated!(caseless_tag!("CHECK"), keyword_follow_char)
31+
| terminated!(caseless_tag!("COLLATE"), keyword_follow_char)
32+
| terminated!(caseless_tag!("COLUMN"), keyword_follow_char)
33+
| terminated!(caseless_tag!("COMMIT"), keyword_follow_char)
34+
| terminated!(caseless_tag!("CONFLICT"), keyword_follow_char)
35+
| terminated!(caseless_tag!("CONSTRAINT"), keyword_follow_char)
36+
| terminated!(caseless_tag!("CREATE"), keyword_follow_char)
37+
| terminated!(caseless_tag!("CROSS"), keyword_follow_char)
38+
| terminated!(caseless_tag!("CURRENT_DATE"), keyword_follow_char)
39+
| terminated!(caseless_tag!("CURRENT_TIME"), keyword_follow_char)
40+
| terminated!(caseless_tag!("CURRENT_TIMESTAMP"), keyword_follow_char)
41+
)
42+
);
43+
44+
named!(keyword_d_to_i<&[u8], &[u8]>,
45+
alt_complete!(
46+
terminated!(caseless_tag!("DATABASE"), keyword_follow_char)
47+
| terminated!(caseless_tag!("DEFAULT"), keyword_follow_char)
48+
| terminated!(caseless_tag!("DEFERRABLE"), keyword_follow_char)
49+
| terminated!(caseless_tag!("DEFERRED"), keyword_follow_char)
50+
| terminated!(caseless_tag!("DELETE"), keyword_follow_char)
51+
| terminated!(caseless_tag!("DESC"), keyword_follow_char)
52+
| terminated!(caseless_tag!("DETACH"), keyword_follow_char)
53+
| terminated!(caseless_tag!("DISTINCT"), keyword_follow_char)
54+
| terminated!(caseless_tag!("DROP"), keyword_follow_char)
55+
| terminated!(caseless_tag!("EACH"), keyword_follow_char)
56+
| terminated!(caseless_tag!("ELSE"), keyword_follow_char)
57+
| terminated!(caseless_tag!("END"), keyword_follow_char)
58+
| terminated!(caseless_tag!("ESCAPE"), keyword_follow_char)
59+
| terminated!(caseless_tag!("EXCEPT"), keyword_follow_char)
60+
| terminated!(caseless_tag!("EXCLUSIVE"), keyword_follow_char)
61+
| terminated!(caseless_tag!("EXISTS"), keyword_follow_char)
62+
| terminated!(caseless_tag!("EXPLAIN"), keyword_follow_char)
63+
| terminated!(caseless_tag!("FAIL"), keyword_follow_char)
64+
| terminated!(caseless_tag!("FOR"), keyword_follow_char)
65+
| terminated!(caseless_tag!("FOREIGN"), keyword_follow_char)
66+
| terminated!(caseless_tag!("FROM"), keyword_follow_char)
67+
| terminated!(caseless_tag!("FULL"), keyword_follow_char)
68+
| terminated!(caseless_tag!("GLOB"), keyword_follow_char)
69+
| terminated!(caseless_tag!("GROUP"), keyword_follow_char)
70+
| terminated!(caseless_tag!("HAVING"), keyword_follow_char)
71+
| terminated!(caseless_tag!("IF"), keyword_follow_char)
72+
| terminated!(caseless_tag!("IGNORE"), keyword_follow_char)
73+
| terminated!(caseless_tag!("IMMEDIATE"), keyword_follow_char)
74+
| terminated!(caseless_tag!("IN"), keyword_follow_char)
75+
| terminated!(caseless_tag!("INDEX"), keyword_follow_char)
76+
| terminated!(caseless_tag!("INDEXED"), keyword_follow_char)
77+
| terminated!(caseless_tag!("INITIALLY"), keyword_follow_char)
78+
| terminated!(caseless_tag!("INNER"), keyword_follow_char)
79+
| terminated!(caseless_tag!("INSERT"), keyword_follow_char)
80+
| terminated!(caseless_tag!("INSTEAD"), keyword_follow_char)
81+
| terminated!(caseless_tag!("INTERSECT"), keyword_follow_char)
82+
| terminated!(caseless_tag!("INTO"), keyword_follow_char)
83+
| terminated!(caseless_tag!("IS"), keyword_follow_char)
84+
| terminated!(caseless_tag!("ISNULL"), keyword_follow_char)
85+
)
86+
);
87+
88+
named!(keyword_j_to_s<&[u8], &[u8]>,
89+
alt_complete!(
90+
terminated!(caseless_tag!("ORDER"), keyword_follow_char)
91+
| terminated!(caseless_tag!("JOIN"), keyword_follow_char)
92+
| terminated!(caseless_tag!("KEY"), keyword_follow_char)
93+
| terminated!(caseless_tag!("LEFT"), keyword_follow_char)
94+
| terminated!(caseless_tag!("LIKE"), keyword_follow_char)
95+
| terminated!(caseless_tag!("LIMIT"), keyword_follow_char)
96+
| terminated!(caseless_tag!("MATCH"), keyword_follow_char)
97+
| terminated!(caseless_tag!("NATURAL"), keyword_follow_char)
98+
| terminated!(caseless_tag!("NO"), keyword_follow_char)
99+
| terminated!(caseless_tag!("NOT"), keyword_follow_char)
100+
| terminated!(caseless_tag!("NOTNULL"), keyword_follow_char)
101+
| terminated!(caseless_tag!("NULL"), keyword_follow_char)
102+
| terminated!(caseless_tag!("OF"), keyword_follow_char)
103+
| terminated!(caseless_tag!("OFFSET"), keyword_follow_char)
104+
| terminated!(caseless_tag!("ON"), keyword_follow_char)
105+
| terminated!(caseless_tag!("OR"), keyword_follow_char)
106+
| terminated!(caseless_tag!("OUTER"), keyword_follow_char)
107+
| terminated!(caseless_tag!("PLAN"), keyword_follow_char)
108+
| terminated!(caseless_tag!("PRAGMA"), keyword_follow_char)
109+
| terminated!(caseless_tag!("PRIMARY"), keyword_follow_char)
110+
| terminated!(caseless_tag!("QUERY"), keyword_follow_char)
111+
| terminated!(caseless_tag!("RAISE"), keyword_follow_char)
112+
| terminated!(caseless_tag!("RECURSIVE"), keyword_follow_char)
113+
| terminated!(caseless_tag!("REFERENCES"), keyword_follow_char)
114+
| terminated!(caseless_tag!("REGEXP"), keyword_follow_char)
115+
| terminated!(caseless_tag!("REINDEX"), keyword_follow_char)
116+
| terminated!(caseless_tag!("RELEASE"), keyword_follow_char)
117+
| terminated!(caseless_tag!("RENAME"), keyword_follow_char)
118+
| terminated!(caseless_tag!("REPLACE"), keyword_follow_char)
119+
| terminated!(caseless_tag!("RESTRICT"), keyword_follow_char)
120+
| terminated!(caseless_tag!("RIGHT"), keyword_follow_char)
121+
| terminated!(caseless_tag!("ROLLBACK"), keyword_follow_char)
122+
| terminated!(caseless_tag!("ROW"), keyword_follow_char)
123+
| terminated!(caseless_tag!("SAVEPOINT"), keyword_follow_char)
124+
| terminated!(caseless_tag!("SELECT"), keyword_follow_char)
125+
| terminated!(caseless_tag!("SET"), keyword_follow_char)
126+
)
127+
);
128+
129+
named!(keyword_t_to_z<&[u8], &[u8]>,
130+
alt_complete!(
131+
terminated!(caseless_tag!("TABLE"), keyword_follow_char)
132+
| terminated!(caseless_tag!("TEMP"), keyword_follow_char)
133+
| terminated!(caseless_tag!("TEMPORARY"), keyword_follow_char)
134+
| terminated!(caseless_tag!("THEN"), keyword_follow_char)
135+
| terminated!(caseless_tag!("TO"), keyword_follow_char)
136+
| terminated!(caseless_tag!("TRANSACTION"), keyword_follow_char)
137+
| terminated!(caseless_tag!("TRIGGER"), keyword_follow_char)
138+
| terminated!(caseless_tag!("UNION"), keyword_follow_char)
139+
| terminated!(caseless_tag!("UNIQUE"), keyword_follow_char)
140+
| terminated!(caseless_tag!("UPDATE"), keyword_follow_char)
141+
| terminated!(caseless_tag!("USING"), keyword_follow_char)
142+
| terminated!(caseless_tag!("VACUUM"), keyword_follow_char)
143+
| terminated!(caseless_tag!("VALUES"), keyword_follow_char)
144+
| terminated!(caseless_tag!("VIEW"), keyword_follow_char)
145+
| terminated!(caseless_tag!("VIRTUAL"), keyword_follow_char)
146+
| terminated!(caseless_tag!("WHEN"), keyword_follow_char)
147+
| terminated!(caseless_tag!("WHERE"), keyword_follow_char)
148+
| terminated!(caseless_tag!("WITH"), keyword_follow_char)
149+
| terminated!(caseless_tag!("WITHOUT"), keyword_follow_char)
150+
)
151+
);
152+
153+
/// Matches any SQL reserved keyword
154+
named!(pub sql_keyword<&[u8], &[u8]>,
155+
complete!(chain!(
156+
kw: alt_complete!(
157+
keyword_a_to_c
158+
| keyword_d_to_i
159+
| keyword_j_to_s
160+
| keyword_t_to_z),
161+
|| { kw }
162+
))
163+
);

‎nom-sql/src/lib.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#[macro_use]
2+
extern crate nom;
3+
4+
extern crate serde;
5+
#[macro_use]
6+
extern crate serde_derive;
7+
8+
pub use self::common::{FieldExpression, Literal, Operator, SqlType, TableKey};
9+
pub use self::column::{Column, ColumnConstraint, ColumnSpecification, FunctionExpression};
10+
pub use self::condition::{ConditionBase, ConditionExpression, ConditionTree};
11+
pub use self::join::{JoinConstraint, JoinOperator, JoinRightSide};
12+
pub use self::select::{selection, JoinClause, SelectStatement};
13+
pub use self::table::Table;
14+
15+
#[macro_use]
16+
mod caseless_tag;
17+
mod keywords;
18+
mod column;
19+
mod common;
20+
mod condition;
21+
mod join;
22+
mod select;
23+
mod table;

‎nom-sql/src/select.rs

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use nom::multispace;
2+
use nom::{Err, ErrorKind, IResult, Needed};
3+
use std::str;
4+
5+
use common::FieldExpression;
6+
use common::{field_definition_expr, field_list, statement_terminator, table_list, table_reference};
7+
use condition::{condition_expr, ConditionExpression};
8+
use join::{join_operator, JoinConstraint, JoinOperator, JoinRightSide};
9+
use table::Table;
10+
11+
#[derive(Clone, Debug, Hash, PartialEq, Serialize, Deserialize)]
12+
pub struct JoinClause {
13+
pub operator: JoinOperator,
14+
pub right: JoinRightSide,
15+
pub constraint: JoinConstraint,
16+
}
17+
18+
#[derive(Clone, Debug, Default, Hash, PartialEq, Serialize, Deserialize)]
19+
pub struct SelectStatement {
20+
pub tables: Vec<Table>,
21+
pub fields: Vec<FieldExpression>,
22+
pub join: Vec<JoinClause>,
23+
pub where_clause: Option<ConditionExpression>,
24+
}
25+
26+
/// Parse JOIN clause
27+
named!(join_clause<&[u8], JoinClause>,
28+
complete!(chain!(
29+
multispace? ~
30+
_natural: opt!(caseless_tag!("natural")) ~
31+
multispace? ~
32+
op: join_operator ~
33+
multispace ~
34+
right: join_rhs ~
35+
multispace ~
36+
constraint: alt_complete!(
37+
chain!(
38+
caseless_tag!("using") ~
39+
multispace ~
40+
fields: delimited!(tag!("("), field_list, tag!(")")),
41+
|| {
42+
JoinConstraint::Using(fields)
43+
}
44+
)
45+
| chain!(
46+
caseless_tag!("on") ~
47+
multispace ~
48+
cond: alt_complete!(delimited!(tag!("("), condition_expr, tag!(")"))
49+
| condition_expr),
50+
|| {
51+
JoinConstraint::On(cond)
52+
}
53+
)
54+
),
55+
|| {
56+
JoinClause {
57+
operator: op,
58+
right: right,
59+
constraint: constraint,
60+
}
61+
}))
62+
);
63+
64+
/// Different options for the right hand side of the join operator in a `join_clause`
65+
named!(join_rhs<&[u8], JoinRightSide>,
66+
alt_complete!(
67+
complete!(chain!(
68+
table: table_reference,
69+
|| {
70+
JoinRightSide::Table(table)
71+
}
72+
))
73+
| complete!(chain!(
74+
tables: delimited!(tag!("("), table_list, tag!(")")),
75+
|| {
76+
JoinRightSide::Tables(tables)
77+
}
78+
))
79+
)
80+
);
81+
82+
/// Parse WHERE clause of a selection
83+
named!(pub where_clause<&[u8], ConditionExpression>,
84+
complete!(chain!(
85+
multispace? ~
86+
caseless_tag!("where") ~
87+
cond: condition_expr,
88+
|| { cond }
89+
))
90+
);
91+
92+
/// Parse rule for a SQL selection query.
93+
named!(pub selection<&[u8], SelectStatement>,
94+
chain!(
95+
select: nested_selection ~
96+
statement_terminator,
97+
|| { select }
98+
)
99+
);
100+
101+
named!(pub nested_selection<&[u8], SelectStatement>,
102+
chain!(
103+
caseless_tag!("select") ~
104+
multispace ~
105+
fields: field_definition_expr ~
106+
delimited!(opt!(multispace), caseless_tag!("from"), opt!(multispace)) ~
107+
tables: table_list ~
108+
join: many0!(join_clause) ~
109+
cond: opt!(where_clause) ~
110+
|| {
111+
SelectStatement {
112+
tables: tables,
113+
fields: fields,
114+
join: join,
115+
where_clause: cond,
116+
}
117+
}
118+
)
119+
);

‎nom-sql/src/table.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use std::str;
2+
use std::fmt;
3+
4+
#[derive(Clone, Debug, Default, Hash, PartialEq, Serialize, Deserialize)]
5+
pub struct Table {
6+
pub name: String,
7+
pub alias: Option<String>,
8+
}
9+
10+
impl fmt::Display for Table {
11+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12+
write!(f, "{}", self.name)?;
13+
if let Some(ref alias) = self.alias {
14+
write!(f, " AS {}", alias)?;
15+
}
16+
Ok(())
17+
}
18+
}
19+
20+
impl<'a> From<&'a str> for Table {
21+
fn from(t: &str) -> Table {
22+
Table {
23+
name: String::from(t),
24+
alias: None,
25+
}
26+
}
27+
}

‎src/main.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
extern crate nom_sql;
2+
3+
fn main() {
4+
let r_txt = "SELECT * FROM Article LEFT JOIN ";
5+
6+
// we process all queries in lowercase to avoid having to deal with capitalization in the
7+
// parser.
8+
let q_bytes = String::from(r_txt.trim()).into_bytes();
9+
10+
nom_sql::selection(&q_bytes);
11+
}

0 commit comments

Comments
 (0)
Please sign in to comment.