Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
jonhoo committed Jan 12, 2018
0 parents commit b192e82
Show file tree
Hide file tree
Showing 15 changed files with 1,598 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

/target/
**/*.rs.bk
82 changes: 82 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
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/" }
7 changes: 7 additions & 0 deletions nom-sql/.gitignore
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
9 changes: 9 additions & 0 deletions nom-sql/Cargo.toml
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"
64 changes: 64 additions & 0 deletions nom-sql/src/caseless_tag.rs
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"[..]));
}
190 changes: 190 additions & 0 deletions nom-sql/src/column.rs
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,
}
);
}
}
Loading

0 comments on commit b192e82

Please sign in to comment.