-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b192e82
Showing
15 changed files
with
1,598 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
|
||
/target/ | ||
**/*.rs.bk |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "rust-issue-47364" | ||
version = "0.1.0" | ||
authors = ["Jon Gjengset <jon@thesquareplanet.com>"] | ||
|
||
[dependencies] | ||
nom_sql = { path = "nom-sql/" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Generated by Cargo | ||
# will have compiled files and executables | ||
/target/ | ||
|
||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries | ||
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[package] | ||
name = "nom_sql" | ||
version = "0.0.1" | ||
authors = ["Malte Schwarzkopf <malte@csail.mit.edu>"] | ||
|
||
[dependencies] | ||
nom = "^1.2.4" | ||
serde = "1.0" | ||
serde_derive = "1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/// Case-insensitive tag! variant for nom | ||
/// | ||
/// By Paul English (https://gist.github.com/log0ymxm/d9c3fc9598cf2d92b8ae89a9ce5341d8) | ||
/// | ||
/// This implementation has some performance issues related to extra memory allocations | ||
/// (see https://github.com/Geal/nom/issues/251), but it works for the moment. | ||
macro_rules! caseless_tag ( | ||
($i:expr, $inp: expr) => ( | ||
{ | ||
#[inline(always)] | ||
fn as_lower(b: &str) -> String { | ||
let s = b.to_string(); | ||
s.to_lowercase() | ||
} | ||
|
||
let expected = $inp; | ||
let lower = as_lower(&expected); | ||
let bytes = lower.as_bytes(); | ||
|
||
caseless_tag_bytes!($i,bytes) | ||
} | ||
); | ||
); | ||
|
||
macro_rules! caseless_tag_bytes ( | ||
($i:expr, $bytes: expr) => ( | ||
{ | ||
use std::cmp::min; | ||
let len = $i.len(); | ||
let blen = $bytes.len(); | ||
let m = min(len, blen); | ||
let reduced = &$i[..m]; | ||
|
||
let s = str::from_utf8(reduced).unwrap(); | ||
let s2 = s.to_string(); | ||
let lowered = s2.to_lowercase(); | ||
let lowered_bytes = lowered.as_bytes(); | ||
|
||
let b = &$bytes[..m]; | ||
|
||
let res: IResult<_,_> = if lowered_bytes != b { | ||
IResult::Error(Err::Position(ErrorKind::Tag, $i)) | ||
} else if m < blen { | ||
IResult::Incomplete(Needed::Size(blen)) | ||
} else { | ||
IResult::Done(&$i[blen..], reduced) | ||
}; | ||
res | ||
} | ||
); | ||
); | ||
|
||
#[test] | ||
fn test_caseless_tag() { | ||
use nom::{Err, ErrorKind, IResult, Needed}; | ||
use std::str; | ||
|
||
named!(x, caseless_tag!("AbCD")); | ||
let r = x(&b"abcdefGH"[..]); | ||
assert_eq!(r, IResult::Done(&b"efGH"[..], &b"abcd"[..])); | ||
let r = x(&b"aBcdefGH"[..]); | ||
assert_eq!(r, IResult::Done(&b"efGH"[..], &b"aBcd"[..])); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
use std::cmp::Ordering; | ||
use std::fmt::{self, Display}; | ||
use std::str; | ||
|
||
use common::{Literal, SqlType}; | ||
|
||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] | ||
pub enum FunctionExpression { | ||
Avg(Column, bool), | ||
Count(Column, bool), | ||
CountStar, | ||
Sum(Column, bool), | ||
Max(Column), | ||
Min(Column), | ||
GroupConcat(Column, String), | ||
} | ||
|
||
impl Display for FunctionExpression { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
match *self { | ||
FunctionExpression::Avg(ref col, d) if d => { | ||
write!(f, "avg(distinct {})", col.name.as_str()) | ||
} | ||
FunctionExpression::Count(ref col, d) if d => { | ||
write!(f, "count(distinct {})", col.name.as_str()) | ||
} | ||
FunctionExpression::Sum(ref col, d) if d => { | ||
write!(f, "sum(distinct {})", col.name.as_str()) | ||
} | ||
|
||
FunctionExpression::Avg(ref col, _) => write!(f, "avg({})", col.name.as_str()), | ||
FunctionExpression::Count(ref col, _) => write!(f, "count({})", col.name.as_str()), | ||
FunctionExpression::CountStar => write!(f, "count(all)"), | ||
FunctionExpression::Sum(ref col, _) => write!(f, "sum({})", col.name.as_str()), | ||
FunctionExpression::Max(ref col) => write!(f, "max({})", col.name.as_str()), | ||
FunctionExpression::Min(ref col) => write!(f, "min({})", col.name.as_str()), | ||
FunctionExpression::GroupConcat(ref col, ref s) => { | ||
write!(f, "group_concat({}, {})", col.name.as_str(), s) | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] | ||
pub struct Column { | ||
pub name: String, | ||
pub alias: Option<String>, | ||
pub table: Option<String>, | ||
pub function: Option<Box<FunctionExpression>>, | ||
} | ||
|
||
impl fmt::Display for Column { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
if let Some(ref table) = self.table { | ||
write!(f, "{}.{}", table, self.name)?; | ||
} else { | ||
write!(f, "{}", self.name)?; | ||
} | ||
if let Some(ref alias) = self.alias { | ||
write!(f, " AS {}", alias)?; | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl<'a> From<&'a str> for Column { | ||
fn from(c: &str) -> Column { | ||
match c.find(".") { | ||
None => Column { | ||
name: String::from(c), | ||
alias: None, | ||
table: None, | ||
function: None, | ||
}, | ||
Some(i) => Column { | ||
name: String::from(&c[i + 1..]), | ||
alias: None, | ||
table: Some(String::from(&c[0..i])), | ||
function: None, | ||
}, | ||
} | ||
} | ||
} | ||
|
||
impl Ord for Column { | ||
fn cmp(&self, other: &Column) -> Ordering { | ||
if self.table.is_some() && other.table.is_some() { | ||
match self.table.cmp(&other.table) { | ||
Ordering::Equal => self.name.cmp(&other.name), | ||
x => x, | ||
} | ||
} else { | ||
self.name.cmp(&other.name) | ||
} | ||
} | ||
} | ||
|
||
impl PartialOrd for Column { | ||
fn partial_cmp(&self, other: &Column) -> Option<Ordering> { | ||
if self.table.is_some() && other.table.is_some() { | ||
match self.table.cmp(&other.table) { | ||
Ordering::Equal => Some(self.name.cmp(&other.name)), | ||
x => Some(x), | ||
} | ||
} else if self.table.is_none() && other.table.is_none() { | ||
Some(self.name.cmp(&other.name)) | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] | ||
pub enum ColumnConstraint { | ||
NotNull, | ||
DefaultValue(Literal), | ||
AutoIncrement, | ||
} | ||
|
||
impl fmt::Display for ColumnConstraint { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
match *self { | ||
ColumnConstraint::NotNull => write!(f, "NOT NULL"), | ||
ColumnConstraint::DefaultValue(ref literal) => { | ||
write!(f, "DEFAULT {}", literal.to_string()) | ||
} | ||
ColumnConstraint::AutoIncrement => write!(f, "AUTOINCREMENT"), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] | ||
pub struct ColumnSpecification { | ||
pub column: Column, | ||
pub sql_type: SqlType, | ||
pub constraints: Vec<ColumnConstraint>, | ||
} | ||
|
||
impl fmt::Display for ColumnSpecification { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!(f, "{} {}", self.column, self.sql_type)?; | ||
for constraint in self.constraints.iter() { | ||
write!(f, " {}", constraint)?; | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl ColumnSpecification { | ||
pub fn new(c: Column, t: SqlType) -> ColumnSpecification { | ||
ColumnSpecification { | ||
column: c, | ||
sql_type: t, | ||
constraints: vec![], | ||
} | ||
} | ||
|
||
pub fn with_constraints( | ||
c: Column, | ||
t: SqlType, | ||
ccs: Vec<ColumnConstraint>, | ||
) -> ColumnSpecification { | ||
ColumnSpecification { | ||
column: c, | ||
sql_type: t, | ||
constraints: ccs, | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn column_from_str() { | ||
let s = "table.col"; | ||
let c = Column::from(s); | ||
|
||
assert_eq!( | ||
c, | ||
Column { | ||
name: String::from("col"), | ||
alias: None, | ||
table: Some(String::from("table")), | ||
function: None, | ||
} | ||
); | ||
} | ||
} |
Oops, something went wrong.