feat: support timestamp new syntax (#697)

* feat: support timestamp new syntax

* fix: not null at end of new time stamp index syntax

* chore: simplify code
This commit is contained in:
SSebo
2022-12-09 10:52:14 +08:00
committed by GitHub
parent 4fdf26810c
commit b26982c5d7

View File

@@ -18,7 +18,8 @@ use itertools::Itertools;
use mito::engine;
use once_cell::sync::Lazy;
use snafu::{ensure, OptionExt, ResultExt};
use sqlparser::ast::Value;
use sqlparser::ast::ColumnOption::NotNull;
use sqlparser::ast::{ColumnOptionDef, DataType, Value};
use sqlparser::dialect::keywords::Keyword;
use sqlparser::parser::IsOptional::Mandatory;
use sqlparser::tokenizer::{Token, Word};
@@ -220,11 +221,7 @@ impl<'a> ParserContext<'a> {
if let Some(constraint) = self.parse_optional_table_constraint()? {
constraints.push(constraint);
} else if let Token::Word(_) = self.parser.peek_token() {
columns.push(
self.parser
.parse_column_def()
.context(SyntaxSnafu { sql: self.sql })?,
);
self.parse_column(&mut columns, &mut constraints)?;
} else {
return self.expected(
"column name or constraint definition",
@@ -246,6 +243,75 @@ impl<'a> ParserContext<'a> {
Ok((columns, constraints))
}
fn parse_column(
&mut self,
columns: &mut Vec<ColumnDef>,
constraints: &mut Vec<TableConstraint>,
) -> Result<()> {
let column = self
.parser
.parse_column_def()
.context(SyntaxSnafu { sql: self.sql })?;
if !matches!(column.data_type, DataType::Timestamp)
|| matches!(self.parser.peek_token(), Token::Comma)
{
columns.push(column);
return Ok(());
}
// for supporting `ts TIMESTAMP TIME INDEX,` syntax.
self.parse_time_index(column, columns, constraints)
}
fn parse_time_index(
&mut self,
mut column: ColumnDef,
columns: &mut Vec<ColumnDef>,
constraints: &mut Vec<TableConstraint>,
) -> Result<()> {
self.parser
.expect_keywords(&[Keyword::TIME, Keyword::INDEX])
.context(error::UnexpectedSnafu {
sql: self.sql,
expected: "TIME INDEX",
actual: self.peek_token_as_string(),
})?;
let constraint = TableConstraint::Unique {
name: Some(Ident {
value: TIME_INDEX.to_owned(),
quote_style: None,
}),
columns: vec![Ident {
value: column.name.value.clone(),
quote_style: None,
}],
is_primary: false,
};
column.options = vec![ColumnOptionDef {
name: None,
option: NotNull,
}];
columns.push(column);
constraints.push(constraint);
if let Token::Comma = self.parser.peek_token() {
return Ok(());
}
self.parser
.expect_keywords(&[Keyword::NOT, Keyword::NULL])
.context(error::UnexpectedSnafu {
sql: self.sql,
expected: "NOT NULL",
actual: self.peek_token_as_string(),
})?;
Ok(())
}
// Copy from sqlparser by boyan
fn parse_optional_table_constraint(&mut self) -> Result<Option<TableConstraint>> {
let name = if self.parser.parse_keyword(Keyword::CONSTRAINT) {
@@ -705,6 +771,160 @@ ENGINE=mito";
}
}
#[test]
fn test_parse_create_table_with_timestamp_index() {
let sql1 = r"
CREATE TABLE monitor (
host_id INT,
idc STRING,
ts TIMESTAMP TIME INDEX,
cpu DOUBLE DEFAULT 0,
memory DOUBLE,
PRIMARY KEY (host),
)
ENGINE=mito";
let result1 = ParserContext::create_with_dialect(sql1, &GenericDialect {}).unwrap();
if let Statement::CreateTable(c) = &result1[0] {
assert_eq!(c.constraints.len(), 2);
let tc = c.constraints[0].clone();
match tc {
TableConstraint::Unique {
name,
columns,
is_primary,
} => {
assert_eq!(name.unwrap().to_string(), "__time_index");
assert_eq!(columns.len(), 1);
assert_eq!(&columns[0].value, "ts");
assert!(!is_primary);
}
_ => panic!("should be time index constraint"),
};
} else {
panic!("should be create_table statement");
}
// `TIME INDEX` should be in front of `PRIMARY KEY`
// in order to equal the `TIMESTAMP TIME INDEX` constraint options vector
let sql2 = r"
CREATE TABLE monitor (
host_id INT,
idc STRING,
ts TIMESTAMP NOT NULL,
cpu DOUBLE DEFAULT 0,
memory DOUBLE,
TIME INDEX (ts),
PRIMARY KEY (host),
)
ENGINE=mito";
let result2 = ParserContext::create_with_dialect(sql2, &GenericDialect {}).unwrap();
assert_eq!(result1, result2);
// TIMESTAMP can be NULL which is not equal to above
let sql3 = r"
CREATE TABLE monitor (
host_id INT,
idc STRING,
ts TIMESTAMP,
cpu DOUBLE DEFAULT 0,
memory DOUBLE,
TIME INDEX (ts),
PRIMARY KEY (host),
)
ENGINE=mito";
let result3 = ParserContext::create_with_dialect(sql3, &GenericDialect {}).unwrap();
assert_ne!(result1, result3);
}
#[test]
fn test_parse_create_table_with_timestamp_index_not_null() {
let sql = r"
CREATE TABLE monitor (
host_id INT,
idc STRING,
ts TIMESTAMP TIME INDEX,
cpu DOUBLE DEFAULT 0,
memory DOUBLE,
TIME INDEX (ts),
PRIMARY KEY (host),
)
ENGINE=mito";
let result = ParserContext::create_with_dialect(sql, &GenericDialect {}).unwrap();
assert_eq!(result.len(), 1);
if let Statement::CreateTable(c) = &result[0] {
let ts = c.columns[2].clone();
assert_eq!(ts.name.to_string(), "ts");
assert_eq!(ts.options[0].option, NotNull);
} else {
panic!("should be create table statement");
}
let sql1 = r"
CREATE TABLE monitor (
host_id INT,
idc STRING,
ts TIMESTAMP NOT NULL TIME INDEX,
cpu DOUBLE DEFAULT 0,
memory DOUBLE,
TIME INDEX (ts),
PRIMARY KEY (host),
)
ENGINE=mito";
let result1 = ParserContext::create_with_dialect(sql1, &GenericDialect {}).unwrap();
assert_eq!(result, result1);
let sql2 = r"
CREATE TABLE monitor (
host_id INT,
idc STRING,
ts TIMESTAMP TIME INDEX NOT NULL,
cpu DOUBLE DEFAULT 0,
memory DOUBLE,
TIME INDEX (ts),
PRIMARY KEY (host),
)
ENGINE=mito";
let result2 = ParserContext::create_with_dialect(sql2, &GenericDialect {}).unwrap();
assert_eq!(result, result2);
let sql3 = r"
CREATE TABLE monitor (
host_id INT,
idc STRING,
ts TIMESTAMP TIME INDEX NULL NOT,
cpu DOUBLE DEFAULT 0,
memory DOUBLE,
TIME INDEX (ts),
PRIMARY KEY (host),
)
ENGINE=mito";
let result3 = ParserContext::create_with_dialect(sql3, &GenericDialect {});
assert!(result3.is_err());
let sql4 = r"
CREATE TABLE monitor (
host_id INT,
idc STRING,
ts TIMESTAMP TIME INDEX NOT NULL NULL,
cpu DOUBLE DEFAULT 0,
memory DOUBLE,
TIME INDEX (ts),
PRIMARY KEY (host),
)
ENGINE=mito";
let result4 = ParserContext::create_with_dialect(sql4, &GenericDialect {});
assert!(result4.is_err());
}
#[test]
fn test_parse_partitions_with_error_syntax() {
let sql = r"