mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-05 21:02:58 +00:00
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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user