mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-29 15:06:59 +00:00
feat: create database (#451)
* feat: parsing create database statement * feat: impl create database in datanode * feat: supports insert into catalog.schema.table * fix: conflicts with develop branch * test: create database then insert and query * fix: grpc schema provider * feat: use CatalogManager::register_schema instead of CatalogProvide::register_schema * refactor: revert InsertExpr catalog_name and schema_name * fix: revert database.proto * fix: revert client cargo * feat: accepts schema.table as table name in sql Co-authored-by: Lei, HUANG <mrsatangel@gmail.com>
This commit is contained in:
co-authored by
Lei, HUANG
parent
d5ae5e6afa
commit
e7b4d24df5
@@ -19,7 +19,7 @@ pub enum Error {
|
||||
Unsupported { sql: String, keyword: String },
|
||||
|
||||
#[snafu(display(
|
||||
"Unexpected token while parsing SQL statement: {}, expected: {}, found: {}, source: {}",
|
||||
"Unexpected token while parsing SQL statement: {}, expected: '{}', found: {}, source: {}",
|
||||
sql,
|
||||
expected,
|
||||
actual,
|
||||
|
||||
@@ -234,7 +234,7 @@ impl<'a> ParserContext<'a> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peek_token_as_string(&self) -> String {
|
||||
pub(crate) fn peek_token_as_string(&self) -> String {
|
||||
self.parser.peek_token().to_string()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ use table_engine::engine;
|
||||
use crate::ast::{ColumnDef, Ident, TableConstraint, Value as SqlValue};
|
||||
use crate::error::{self, InvalidTimeIndexSnafu, Result, SyntaxSnafu};
|
||||
use crate::parser::ParserContext;
|
||||
use crate::statements::create_table::{CreateTable, PartitionEntry, Partitions, TIME_INDEX};
|
||||
use crate::statements::create::{
|
||||
CreateDatabase, CreateTable, PartitionEntry, Partitions, TIME_INDEX,
|
||||
};
|
||||
use crate::statements::statement::Statement;
|
||||
use crate::statements::{sql_data_type_to_concrete_data_type, sql_value_to_value};
|
||||
|
||||
@@ -26,9 +28,37 @@ static THAN: Lazy<Token> = Lazy::new(|| Token::make_keyword("THAN"));
|
||||
/// Parses create [table] statement
|
||||
impl<'a> ParserContext<'a> {
|
||||
pub(crate) fn parse_create(&mut self) -> Result<Statement> {
|
||||
self.parser
|
||||
.expect_keyword(Keyword::TABLE)
|
||||
.context(error::SyntaxSnafu { sql: self.sql })?;
|
||||
match self.parser.peek_token() {
|
||||
Token::Word(w) => match w.keyword {
|
||||
Keyword::TABLE => self.parse_create_table(),
|
||||
|
||||
Keyword::DATABASE => self.parse_create_database(),
|
||||
|
||||
_ => self.unsupported(w.to_string()),
|
||||
},
|
||||
unexpected => self.unsupported(unexpected.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_create_database(&mut self) -> Result<Statement> {
|
||||
self.parser.next_token();
|
||||
|
||||
let database_name = self
|
||||
.parser
|
||||
.parse_object_name()
|
||||
.context(error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: "a database name",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
|
||||
Ok(Statement::CreateDatabase(CreateDatabase {
|
||||
name: database_name,
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_create_table(&mut self) -> Result<Statement> {
|
||||
self.parser.next_token();
|
||||
let if_not_exists =
|
||||
self.parser
|
||||
.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||
@@ -36,7 +66,12 @@ impl<'a> ParserContext<'a> {
|
||||
let table_name = self
|
||||
.parser
|
||||
.parse_object_name()
|
||||
.context(error::SyntaxSnafu { sql: self.sql })?;
|
||||
.context(error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: "a table name",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
|
||||
let (columns, constraints) = self.parse_columns()?;
|
||||
|
||||
let partitions = self.parse_partitions()?;
|
||||
@@ -59,7 +94,7 @@ impl<'a> ParserContext<'a> {
|
||||
};
|
||||
validate_create(&create_table)?;
|
||||
|
||||
Ok(Statement::Create(create_table))
|
||||
Ok(Statement::CreateTable(create_table))
|
||||
}
|
||||
|
||||
// "PARTITION BY ..." syntax:
|
||||
@@ -70,7 +105,11 @@ impl<'a> ParserContext<'a> {
|
||||
}
|
||||
self.parser
|
||||
.expect_keywords(&[Keyword::BY, Keyword::RANGE, Keyword::COLUMNS])
|
||||
.context(error::SyntaxSnafu { sql: self.sql })?;
|
||||
.context(error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: "BY, RANGE, COLUMNS",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
|
||||
let column_list = self
|
||||
.parser
|
||||
@@ -88,7 +127,11 @@ impl<'a> ParserContext<'a> {
|
||||
fn parse_partition_entry(&mut self) -> Result<PartitionEntry> {
|
||||
self.parser
|
||||
.expect_keyword(Keyword::PARTITION)
|
||||
.context(error::SyntaxSnafu { sql: self.sql })?;
|
||||
.context(error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: "PARTITION",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
|
||||
let name = self
|
||||
.parser
|
||||
@@ -128,7 +171,11 @@ impl<'a> ParserContext<'a> {
|
||||
{
|
||||
self.parser
|
||||
.expect_token(&Token::LParen)
|
||||
.context(error::SyntaxSnafu { sql: self.sql })?;
|
||||
.context(error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: "(",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
|
||||
let mut values = vec![];
|
||||
while self.parser.peek_token() != Token::RParen {
|
||||
@@ -140,7 +187,12 @@ impl<'a> ParserContext<'a> {
|
||||
|
||||
self.parser
|
||||
.expect_token(&Token::RParen)
|
||||
.context(error::SyntaxSnafu { sql: self.sql })?;
|
||||
.context(error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: ")",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
@@ -196,7 +248,11 @@ impl<'a> ParserContext<'a> {
|
||||
Token::Word(w) if w.keyword == Keyword::PRIMARY => {
|
||||
self.parser
|
||||
.expect_keyword(Keyword::KEY)
|
||||
.context(error::SyntaxSnafu { sql: self.sql })?;
|
||||
.context(error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: "KEY",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
let columns = self
|
||||
.parser
|
||||
.parse_parenthesized_column_list(Mandatory)
|
||||
@@ -210,7 +266,12 @@ impl<'a> ParserContext<'a> {
|
||||
Token::Word(w) if w.keyword == Keyword::TIME => {
|
||||
self.parser
|
||||
.expect_keyword(Keyword::INDEX)
|
||||
.context(error::SyntaxSnafu { sql: self.sql })?;
|
||||
.context(error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: "INDEX",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
|
||||
let columns = self
|
||||
.parser
|
||||
.parse_parenthesized_column_list(Mandatory)
|
||||
@@ -248,7 +309,11 @@ impl<'a> ParserContext<'a> {
|
||||
|
||||
self.parser
|
||||
.expect_token(&Token::Eq)
|
||||
.context(error::SyntaxSnafu { sql: self.sql })?;
|
||||
.context(error::UnexpectedSnafu {
|
||||
sql: self.sql,
|
||||
expected: "=",
|
||||
actual: self.peek_token_as_string(),
|
||||
})?;
|
||||
|
||||
match self.parser.next_token() {
|
||||
Token::Word(w) => Ok(w.value),
|
||||
@@ -420,6 +485,27 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_create_database() {
|
||||
let sql = "create database";
|
||||
let result = ParserContext::create_with_dialect(sql, &GenericDialect {});
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Unexpected token while parsing SQL statement"));
|
||||
|
||||
let sql = "create database prometheus";
|
||||
let stmts = ParserContext::create_with_dialect(sql, &GenericDialect {}).unwrap();
|
||||
|
||||
assert_eq!(1, stmts.len());
|
||||
match &stmts[0] {
|
||||
Statement::CreateDatabase(c) => {
|
||||
assert_eq!(c.name.to_string(), "prometheus");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_create() {
|
||||
let sql = r"
|
||||
@@ -555,7 +641,7 @@ ENGINE=mito";
|
||||
let result = ParserContext::create_with_dialect(sql, &GenericDialect {}).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
match &result[0] {
|
||||
Statement::Create(c) => {
|
||||
Statement::CreateTable(c) => {
|
||||
assert!(c.partitions.is_some());
|
||||
|
||||
let partitions = c.partitions.as_ref().unwrap();
|
||||
@@ -670,7 +756,7 @@ ENGINE=mito";
|
||||
let result = ParserContext::create_with_dialect(sql, &GenericDialect {}).unwrap();
|
||||
assert_eq!(1, result.len());
|
||||
match &result[0] {
|
||||
Statement::Create(c) => {
|
||||
Statement::CreateTable(c) => {
|
||||
assert!(!c.if_not_exists);
|
||||
assert_eq!("demo", c.name.to_string());
|
||||
assert_eq!("mito", c.engine);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod alter;
|
||||
pub mod create_table;
|
||||
pub mod create;
|
||||
pub mod insert;
|
||||
pub mod query;
|
||||
pub mod show;
|
||||
@@ -32,6 +32,11 @@ pub fn table_idents_to_full_name(obj_name: &ObjectName) -> Result<(String, Strin
|
||||
DEFAULT_SCHEMA_NAME.to_string(),
|
||||
table.value.clone(),
|
||||
)),
|
||||
[schema, table] => Ok((
|
||||
DEFAULT_CATALOG_NAME.to_string(),
|
||||
schema.value.clone(),
|
||||
table.value.clone(),
|
||||
)),
|
||||
[catalog, schema, table] => Ok((
|
||||
catalog.value.clone(),
|
||||
schema.value.clone(),
|
||||
@@ -39,7 +44,7 @@ pub fn table_idents_to_full_name(obj_name: &ObjectName) -> Result<(String, Strin
|
||||
)),
|
||||
_ => error::InvalidSqlSnafu {
|
||||
msg: format!(
|
||||
"expect table name to be <catalog>.<schema>.<table> or <table>, actual: {}",
|
||||
"expect table name to be <catalog>.<schema>.<table>, <schema>.<table> or <table>, actual: {}",
|
||||
obj_name
|
||||
),
|
||||
}
|
||||
|
||||
@@ -29,3 +29,8 @@ pub struct PartitionEntry {
|
||||
pub name: Ident,
|
||||
pub value_list: Vec<SqlValue>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct CreateDatabase {
|
||||
pub name: ObjectName,
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use sqlparser::parser::ParserError;
|
||||
|
||||
use crate::ast::{Expr, Value};
|
||||
use crate::error::{self, Result};
|
||||
use crate::statements::table_idents_to_full_name;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Insert {
|
||||
@@ -11,12 +12,9 @@ pub struct Insert {
|
||||
}
|
||||
|
||||
impl Insert {
|
||||
pub fn table_name(&self) -> String {
|
||||
pub fn full_table_name(&self) -> Result<(String, String, String)> {
|
||||
match &self.inner {
|
||||
Statement::Insert { table_name, .. } => {
|
||||
// FIXME(dennis): table_name may be in the form of "catalog.schema.table"
|
||||
table_name.to_string()
|
||||
}
|
||||
Statement::Insert { table_name, .. } => table_idents_to_full_name(table_name),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use sqlparser::ast::Statement as SpStatement;
|
||||
use sqlparser::parser::ParserError;
|
||||
|
||||
use crate::statements::alter::AlterTable;
|
||||
use crate::statements::create_table::CreateTable;
|
||||
use crate::statements::create::{CreateDatabase, CreateTable};
|
||||
use crate::statements::insert::Insert;
|
||||
use crate::statements::query::Query;
|
||||
use crate::statements::show::{ShowCreateTable, ShowDatabases, ShowTables};
|
||||
@@ -15,7 +15,9 @@ pub enum Statement {
|
||||
// Insert
|
||||
Insert(Box<Insert>),
|
||||
/// CREATE TABLE
|
||||
Create(CreateTable),
|
||||
CreateTable(CreateTable),
|
||||
// CREATE DATABASE
|
||||
CreateDatabase(CreateDatabase),
|
||||
/// ALTER TABLE
|
||||
Alter(AlterTable),
|
||||
// Databases.
|
||||
@@ -43,7 +45,9 @@ impl TryFrom<Statement> for SpStatement {
|
||||
)),
|
||||
Statement::Query(s) => Ok(SpStatement::Query(Box::new(s.inner))),
|
||||
Statement::Insert(i) => Ok(i.inner),
|
||||
Statement::Create(_) | Statement::Alter(_) => unimplemented!(),
|
||||
Statement::CreateDatabase(_) | Statement::CreateTable(_) | Statement::Alter(_) => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user