Skip to content

Commit 6b489dd

Browse files
committed
Parse Timestamps
1 parent 8fc8d1e commit 6b489dd

File tree

6 files changed

+134
-11
lines changed

6 files changed

+134
-11
lines changed

src/ast/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ 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, ParsedDate, ParsedDateTime, Value};
52+
pub use self::value::{
53+
DateTimeField, Interval, IntervalValue, ParsedDate, ParsedDateTime, ParsedTimestamp, Value,
54+
};
5355

5456
struct DisplaySeparated<'a, T>
5557
where

src/ast/value.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use bigdecimal::BigDecimal;
1414
use std::fmt;
1515

1616
mod datetime;
17-
pub use datetime::{DateTimeField, Interval, IntervalValue, ParsedDate, ParsedDateTime};
17+
pub use datetime::{
18+
DateTimeField, Interval, IntervalValue, ParsedDate, ParsedDateTime, ParsedTimestamp,
19+
};
1820

1921
#[derive(Debug)]
2022
pub struct ValueError(String);
@@ -47,7 +49,7 @@ pub enum Value {
4749
/// `TIME '...'` literals
4850
Time(String),
4951
/// `TIMESTAMP '...'` literals
50-
Timestamp(String),
52+
Timestamp(String, ParsedTimestamp),
5153
/// INTERVAL literals, roughly in the following format:
5254
///
5355
/// ```text
@@ -76,7 +78,7 @@ impl fmt::Display for Value {
7678
Value::Boolean(v) => write!(f, "{}", v),
7779
Value::Date(v, _) => write!(f, "DATE '{}'", escape_single_quote_string(v)),
7880
Value::Time(v) => write!(f, "TIME '{}'", escape_single_quote_string(v)),
79-
Value::Timestamp(v) => write!(f, "TIMESTAMP '{}'", escape_single_quote_string(v)),
81+
Value::Timestamp(v, _) => write!(f, "TIMESTAMP '{}'", escape_single_quote_string(v)),
8082
Value::Interval(IntervalValue {
8183
parsed: _,
8284
value,

src/ast/value/datetime.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,37 @@ pub enum Interval {
256256
},
257257
}
258258

259-
/// The fields of a date
259+
/// The fields of a Date
260+
///
261+
/// This is not guaranteed to be a valid date
260262
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
261263
pub struct ParsedDate {
262264
pub year: i64,
263265
pub month: u8,
264266
pub day: u8,
265267
}
266268

267-
/// All of the fields that can appear in a literal `TIMESTAMP` or `INTERVAL` string
269+
/// The fields in a `Timestamp`
270+
///
271+
/// Similar to a [`ParsedDateTime`], except that all the fields are required.
272+
///
273+
/// This is not guaranteed to be a valid date
274+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
275+
pub struct ParsedTimestamp {
276+
pub year: i64,
277+
pub month: u8,
278+
pub day: u8,
279+
pub hour: u8,
280+
pub minute: u8,
281+
pub second: u8,
282+
pub nano: u32,
283+
}
284+
285+
/// All of the fields that can appear in a literal `DATE`, `TIMESTAMP` or `INTERVAL` string
286+
///
287+
/// This is only used in an `Interval`, which can have any contiguous set of
288+
/// fields set, otherwise you are probably looking for [`ParsedDate`] or
289+
/// [`ParsedTimestamp`].
268290
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
269291
pub struct ParsedDateTime {
270292
pub is_positive: bool,

src/parser.rs

+69-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use super::tokenizer::*;
2222
use std::error::Error;
2323
use std::fmt;
2424

25-
use crate::ast::ParsedDate;
25+
use crate::ast::{ParsedDate, ParsedTimestamp};
2626

2727
// Use `Parser::expected` instead, if possible
2828
macro_rules! parser_err {
@@ -212,7 +212,7 @@ impl Parser {
212212
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
213213
}),
214214
"TIME" => Ok(Expr::Value(Value::Time(self.parse_literal_string()?))),
215-
"TIMESTAMP" => Ok(Expr::Value(Value::Timestamp(self.parse_literal_string()?))),
215+
"TIMESTAMP" => Ok(Expr::Value(self.parse_timestamp()?)),
216216
// Here `w` is a word, check if it's a part of a multi-part
217217
// identifier, a function call, or a simple identifier:
218218
_ => match self.peek_token() {
@@ -504,6 +504,73 @@ impl Parser {
504504
}
505505
}
506506

507+
fn parse_timestamp(&mut self) -> Result<Value, ParserError> {
508+
use std::convert::TryInto;
509+
510+
let value = self.parse_literal_string()?;
511+
let pdt = Self::parse_interval_string(&value, &DateTimeField::Year)?;
512+
513+
match (
514+
pdt.year, pdt.month, pdt.day, pdt.hour, pdt.minute, pdt.second, pdt.nano,
515+
) {
516+
(Some(year), Some(month), Some(day), Some(hour), Some(minute), Some(second), nano) => {
517+
let p_err = |e: std::num::TryFromIntError, field: &str| {
518+
ParserError::ParserError(format!(
519+
"{} in date '{}' is invalid: {}",
520+
field, value, e
521+
))
522+
};
523+
524+
// type inference with try_into() fails so we need to mutate it negative
525+
let mut year: i64 = year.try_into().map_err(|e| p_err(e, "Year"))?;
526+
year *= pdt.positivity();
527+
if month > 12 || month == 0 {
528+
return parser_err!(
529+
"Month in date '{}' must be a number between 1 and 12, got: {}",
530+
value,
531+
month
532+
);
533+
}
534+
let month: u8 = month.try_into().expect("invalid month");
535+
if month == 0 {
536+
parser_err!("Month in timestamp '{}' cannot be zero: {}", value, day)?;
537+
}
538+
let day: u8 = day.try_into().map_err(|e| p_err(e, "Day"))?;
539+
if day == 0 {
540+
parser_err!("Day in timestamp '{}' cannot be zero: {}", value, day)?;
541+
}
542+
let hour: u8 = hour.try_into().map_err(|e| p_err(e, "Hour"))?;
543+
if hour > 23 {
544+
parser_err!("Hour in timestamp '{}' cannot be > 23: {}", value, hour)?;
545+
}
546+
let minute: u8 = minute.try_into().map_err(|e| p_err(e, "Minute"))?;
547+
if minute > 59 {
548+
parser_err!("Minute in timestamp '{}' cannot be > 59: {}", value, minute)?;
549+
}
550+
let second: u8 = second.try_into().map_err(|e| p_err(e, "Minute"))?;
551+
if second > 60 {
552+
parser_err!("Second in timestamp '{}' cannot be > 60: {}", value, second)?;
553+
}
554+
Ok(Value::Timestamp(
555+
value,
556+
ParsedTimestamp {
557+
year,
558+
month,
559+
day,
560+
hour,
561+
minute,
562+
second,
563+
nano: nano.unwrap_or(0),
564+
},
565+
))
566+
}
567+
_ => Err(ParserError::ParserError(format!(
568+
"timestamp is missing fields, year through second are all required, got: '{}'",
569+
value
570+
))),
571+
}
572+
}
573+
507574
/// Parse an INTERVAL literal.
508575
///
509576
/// Some syntactically valid intervals:

src/parser/datetime.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ pub(crate) fn tokenize_interval(value: &str) -> Result<Vec<IntervalToken>, Parse
5858
num_buf, e
5959
))
6060
})?;
61-
let leading_zeroes = num_buf.chars().take_while(|c| c == &'0').count() as u32;
62-
let multiplicand = 1_000_000_000 / 10_u32.pow(1 + leading_zeroes);
61+
// this is guaranteed to be ascii, so len is fine
62+
let chars = num_buf.len() as u32;
63+
let multiplicand = 1_000_000_000 / 10_u32.pow(chars);
6364

6465
toks.push(IntervalToken::Nanos(raw * multiplicand));
6566
}

tests/sqlparser_common.rs

+30-1
Original file line numberDiff line numberDiff line change
@@ -1289,7 +1289,36 @@ fn parse_literal_timestamp() {
12891289
let sql = "SELECT TIMESTAMP '1999-01-01 01:23:34'";
12901290
let select = verified_only_select(sql);
12911291
assert_eq!(
1292-
&Expr::Value(Value::Timestamp("1999-01-01 01:23:34".into())),
1292+
&Expr::Value(Value::Timestamp(
1293+
"1999-01-01 01:23:34".into(),
1294+
ParsedTimestamp {
1295+
year: 1999,
1296+
month: 1,
1297+
day: 1,
1298+
hour: 1,
1299+
minute: 23,
1300+
second: 34,
1301+
nano: 0
1302+
}
1303+
)),
1304+
expr_from_projection(only(&select.projection)),
1305+
);
1306+
1307+
let sql = "SELECT TIMESTAMP '1999-01-01 01:23:34.555'";
1308+
let select = verified_only_select(sql);
1309+
assert_eq!(
1310+
&Expr::Value(Value::Timestamp(
1311+
"1999-01-01 01:23:34.555".into(),
1312+
ParsedTimestamp {
1313+
year: 1999,
1314+
month: 1,
1315+
day: 1,
1316+
hour: 1,
1317+
minute: 23,
1318+
second: 34,
1319+
nano: 555_000_000
1320+
}
1321+
)),
12931322
expr_from_projection(only(&select.projection)),
12941323
);
12951324
}

0 commit comments

Comments
 (0)