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

Commit 8fc8d1e

Browse files
committed
Parse Dates
This uses some of the infrastructure added for Intervals to support extracting the fields of `DATE 'yyyy-mm-dd'`. It has a similar philosophical position -- it just extracts the fields into a struct that has numeric fields for the dates, and does very little verification -- the only thing that it does is verify that month and day are not 0 and fit into a u8, since the exact number of days in any given month are variable.
1 parent 1c69bee commit 8fc8d1e

7 files changed

Lines changed: 135 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Nothing here yet! Check https://github.com/andygrove/sqlparser-rs/commits/master
1313
- Now supports parsing date and time types
1414
- ast::Value::Interval changed its representation to include an inner
1515
IntervalValue that includes a `ParsedDateTime` and some useful methods.
16+
- ast::Value::Date changed its representation to include an inner
17+
`ParsedDate`
1618

1719
## [0.4.0] - 2019-07-02
1820
This release brings us closer to SQL-92 support, mainly thanks to the improvements contributed back from @MaterializeInc's fork and other work by @benesch.

src/ast/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub use self::query::{
4949
Cte, Fetch, Join, JoinConstraint, JoinOperator, OrderByExpr, Query, Select, SelectItem,
5050
SetExpr, SetOperator, TableAlias, TableFactor, TableWithJoins, Values,
5151
};
52-
pub use self::value::{DateTimeField, Interval, IntervalValue, ParsedDateTime, Value};
52+
pub use self::value::{DateTimeField, Interval, IntervalValue, ParsedDate, ParsedDateTime, Value};
5353

5454
struct DisplaySeparated<'a, T>
5555
where

src/ast/value.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use bigdecimal::BigDecimal;
1414
use std::fmt;
1515

1616
mod datetime;
17-
pub use datetime::{DateTimeField, Interval, IntervalValue, ParsedDateTime};
17+
pub use datetime::{DateTimeField, Interval, IntervalValue, ParsedDate, ParsedDateTime};
1818

1919
#[derive(Debug)]
2020
pub struct ValueError(String);
@@ -43,7 +43,7 @@ pub enum Value {
4343
/// Boolean value true or false
4444
Boolean(bool),
4545
/// `DATE '...'` literals
46-
Date(String),
46+
Date(String, ParsedDate),
4747
/// `TIME '...'` literals
4848
Time(String),
4949
/// `TIMESTAMP '...'` literals
@@ -74,7 +74,7 @@ impl fmt::Display for Value {
7474
Value::NationalStringLiteral(v) => write!(f, "N'{}'", v),
7575
Value::HexStringLiteral(v) => write!(f, "X'{}'", v),
7676
Value::Boolean(v) => write!(f, "{}", v),
77-
Value::Date(v) => write!(f, "DATE '{}'", escape_single_quote_string(v)),
77+
Value::Date(v, _) => write!(f, "DATE '{}'", escape_single_quote_string(v)),
7878
Value::Time(v) => write!(f, "TIME '{}'", escape_single_quote_string(v)),
7979
Value::Timestamp(v) => write!(f, "TIMESTAMP '{}'", escape_single_quote_string(v)),
8080
Value::Interval(IntervalValue {

src/ast/value/datetime.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,14 @@ impl IntervalValue {
6464
match &self.leading_field {
6565
Year => match &self.last_field {
6666
Some(Month) => Ok(Interval::Months(
67-
self.positivity() * self.parsed.year.unwrap_or(0) as i64 * 12
67+
self.parsed.positivity() * self.parsed.year.unwrap_or(0) as i64 * 12
6868
+ self.parsed.month.unwrap_or(0) as i64,
6969
)),
7070
Some(Year) | None => self
7171
.parsed
7272
.year
7373
.ok_or_else(|| ValueError("No YEAR provided".into()))
74-
.map(|year| Interval::Months(self.positivity() * year as i64 * 12)),
74+
.map(|year| Interval::Months(self.parsed.positivity() * year as i64 * 12)),
7575
Some(invalid) => Err(ValueError(format!(
7676
"Invalid specifier for YEAR precision: {}",
7777
&invalid
@@ -82,7 +82,7 @@ impl IntervalValue {
8282
.parsed
8383
.month
8484
.ok_or_else(|| ValueError("No MONTH provided".into()))
85-
.map(|m| Interval::Months(self.positivity() * m as i64)),
85+
.map(|m| Interval::Months(self.parsed.positivity() * m as i64)),
8686
Some(invalid) => Err(ValueError(format!(
8787
"Invalid specifier for MONTH precision: {}",
8888
&invalid
@@ -218,15 +218,6 @@ impl IntervalValue {
218218
.filter(|field| self.units_of(&field).is_some()),
219219
)
220220
}
221-
222-
/// `1` if is_positive, otherwise `-1`
223-
fn positivity(&self) -> i64 {
224-
if self.parsed.is_positive {
225-
1
226-
} else {
227-
-1
228-
}
229-
}
230221
}
231222

232223
fn fields_msg(fields: impl Iterator<Item = DateTimeField>) -> String {
@@ -265,6 +256,14 @@ pub enum Interval {
265256
},
266257
}
267258

259+
/// The fields of a date
260+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
261+
pub struct ParsedDate {
262+
pub year: i64,
263+
pub month: u8,
264+
pub day: u8,
265+
}
266+
268267
/// All of the fields that can appear in a literal `TIMESTAMP` or `INTERVAL` string
269268
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
270269
pub struct ParsedDateTime {
@@ -278,6 +277,16 @@ pub struct ParsedDateTime {
278277
pub nano: Option<u32>,
279278
}
280279

280+
impl ParsedDateTime {
281+
/// `1` if is_positive, else `-1`
282+
pub(crate) fn positivity(&self) -> i64 {
283+
match self.is_positive {
284+
true => 1,
285+
false => -1,
286+
}
287+
}
288+
}
289+
281290
impl Default for ParsedDateTime {
282291
fn default() -> ParsedDateTime {
283292
ParsedDateTime {

src/parser.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ use super::tokenizer::*;
2222
use std::error::Error;
2323
use std::fmt;
2424

25+
use crate::ast::ParsedDate;
26+
2527
// Use `Parser::expected` instead, if possible
2628
macro_rules! parser_err {
2729
($MSG:expr) => {
@@ -201,7 +203,7 @@ impl Parser {
201203
}
202204
"CASE" => self.parse_case_expr(),
203205
"CAST" => self.parse_cast_expr(),
204-
"DATE" => Ok(Expr::Value(Value::Date(self.parse_literal_string()?))),
206+
"DATE" => Ok(Expr::Value(self.parse_date()?)),
205207
"EXISTS" => self.parse_exists_expr(),
206208
"EXTRACT" => self.parse_extract_expr(),
207209
"INTERVAL" => self.parse_literal_interval(),
@@ -458,6 +460,50 @@ impl Parser {
458460
}
459461
}
460462

463+
fn parse_date(&mut self) -> Result<Value, ParserError> {
464+
use std::convert::TryInto;
465+
466+
let value = self.parse_literal_string()?;
467+
let pdt = Self::parse_interval_string(&value, &DateTimeField::Year)?;
468+
469+
match (pdt.year, pdt.month, pdt.day, pdt.hour) {
470+
(Some(year), Some(month), Some(day), None) => {
471+
let p_err = |e: std::num::TryFromIntError, field: &str| {
472+
ParserError::ParserError(format!(
473+
"{} in date '{}' is invalid: {}",
474+
field, value, e
475+
))
476+
};
477+
478+
// type inference with try_into() fails so we need to mutate it negative
479+
let mut year: i64 = year.try_into().map_err(|e| p_err(e, "Year"))?;
480+
year *= pdt.positivity();
481+
if month > 12 || month == 0 {
482+
return parser_err!(
483+
"Month in date '{}' must be a number between 1 and 12, got: {}",
484+
value,
485+
month
486+
);
487+
}
488+
let month: u8 = month.try_into().expect("invalid month");
489+
let day: u8 = day.try_into().map_err(|e| p_err(e, "Day"))?;
490+
if day == 0 {
491+
parser_err!("Day in date '{}' cannot be zero: {}", value, day)?;
492+
}
493+
Ok(Value::Date(value, ParsedDate { year, month, day }))
494+
}
495+
(Some(_), Some(_), Some(_), Some(hours)) => parser_err!(
496+
"Hours cannot be supplied for DATE, got {} in '{}'",
497+
hours,
498+
value
499+
),
500+
(_, _, _, _) => Err(ParserError::ParserError(format!(
501+
"year, day and month are all required, got: '{}'",
502+
value
503+
))),
504+
}
505+
}
506+
461507
/// Parse an INTERVAL literal.
462508
///
463509
/// Some syntactically valid intervals:

src/parser/datetime.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,18 @@ pub(crate) fn build_parsed_datetime(
145145
let val = *val;
146146
match current_field {
147147
DateTimeField::Year => pdt.year = Some(val),
148-
DateTimeField::Month => pdt.month = Some(val),
149-
DateTimeField::Day => pdt.day = Some(val),
148+
DateTimeField::Month => {
149+
if val < 1 {
150+
return parser_err!("Invalid Month {} in {}", val, value);
151+
}
152+
pdt.month = Some(val)
153+
}
154+
DateTimeField::Day => {
155+
if val < 1 {
156+
return parser_err!("Invalid Day {} in {}", val, value);
157+
}
158+
pdt.day = Some(val)
159+
}
150160
DateTimeField::Hour => pdt.hour = Some(val),
151161
DateTimeField::Minute => pdt.minute = Some(val),
152162
DateTimeField::Second if seconds_seen == 0 => {

tests/sqlparser_common.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1221,9 +1221,57 @@ fn parse_literal_date() {
12211221
let sql = "SELECT DATE '1999-01-01'";
12221222
let select = verified_only_select(sql);
12231223
assert_eq!(
1224-
&Expr::Value(Value::Date("1999-01-01".into())),
1224+
&Expr::Value(Value::Date(
1225+
"1999-01-01".into(),
1226+
ParsedDate {
1227+
year: 1999,
1228+
month: 1,
1229+
day: 1,
1230+
}
1231+
)),
1232+
expr_from_projection(only(&select.projection)),
1233+
);
1234+
1235+
let sql = "SELECT DATE '-1-01-01'";
1236+
let select = verified_only_select(sql);
1237+
assert_eq!(
1238+
&Expr::Value(Value::Date(
1239+
"-1-01-01".into(),
1240+
ParsedDate {
1241+
year: -1,
1242+
month: 1,
1243+
day: 1,
1244+
}
1245+
)),
1246+
expr_from_projection(only(&select.projection)),
1247+
);
1248+
1249+
let sql = "SELECT DATE '0-01-01'";
1250+
let select = verified_only_select(sql);
1251+
assert_eq!(
1252+
&Expr::Value(Value::Date(
1253+
"0-01-01".into(),
1254+
ParsedDate {
1255+
year: 0,
1256+
month: 1,
1257+
day: 1,
1258+
}
1259+
)),
12251260
expr_from_projection(only(&select.projection)),
12261261
);
1262+
1263+
assert_eq!(
1264+
ParserError::ParserError("Invalid Month 0 in 0-00-00".into()),
1265+
parse_sql_statements("SELECT DATE '0-00-00'").unwrap_err(),
1266+
);
1267+
assert_eq!(
1268+
ParserError::ParserError("Invalid Day 0 in 0-01-00".into()),
1269+
parse_sql_statements("SELECT DATE '0-01-00'").unwrap_err(),
1270+
);
1271+
assert_eq!(
1272+
ParserError::ParserError("Hours cannot be supplied for DATE, got 2 in '1-1-1 2'".into()),
1273+
parse_sql_statements("SELECT DATE '1-1-1 2").unwrap_err(),
1274+
);
12271275
}
12281276

12291277
#[test]

0 commit comments

Comments
 (0)