Skip to content

Commit

Permalink
feat: support Create Table ... Like
Browse files Browse the repository at this point in the history
  • Loading branch information
KKould committed Feb 23, 2024
1 parent b144836 commit 1ec4159
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 37 deletions.
6 changes: 4 additions & 2 deletions src/frontend/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,10 @@ pub fn check_permission(
// database ops won't be checked
Statement::CreateDatabase(_) | Statement::ShowDatabases(_) => {}
// show create table and alter are not supported yet
Statement::ShowCreateTable(_) | Statement::CreateExternalTable(_) | Statement::Alter(_) => {
}
Statement::ShowCreateTable(_)
| Statement::CreateExternalTable(_)
| Statement::CreateTableLike(_)
| Statement::Alter(_) => {}
// set/show variable now only alter/show variable in session
Statement::SetVariables(_) | Statement::ShowVariables(_) => {}

Expand Down
2 changes: 1 addition & 1 deletion src/operator/src/expr_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ pub(crate) async fn create_external_expr(
Ok(expr)
}

/// Convert `CreateTable` statement to `CreateExpr` gRPC request.
/// Convert `CreateTable` statement to `CreateTableExpr` gRPC request.
pub fn create_to_expr(create: &CreateTable, query_ctx: QueryContextRef) -> Result<CreateTableExpr> {
let (catalog_name, schema_name, table_name) =
table_idents_to_full_name(&create.name, &query_ctx)
Expand Down
6 changes: 4 additions & 2 deletions src/operator/src/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ impl StatementExecutor {
let _ = self.create_table(stmt, query_ctx).await?;
Ok(Output::AffectedRows(0))
}
Statement::CreateTableLike(stmt) => {
let _ = self.create_table_like(stmt, query_ctx).await?;
Ok(Output::AffectedRows(0))
}
Statement::CreateExternalTable(stmt) => {
let _ = self.create_external_table(stmt, query_ctx).await?;
Ok(Output::AffectedRows(0))
Expand All @@ -174,7 +178,6 @@ impl StatementExecutor {
let table_name = TableName::new(catalog, schema, table);
self.truncate_table(table_name).await
}

Statement::CreateDatabase(stmt) => {
self.create_database(
query_ctx.current_catalog(),
Expand All @@ -183,7 +186,6 @@ impl StatementExecutor {
)
.await
}

Statement::ShowCreateTable(show) => {
let (catalog, schema, table) =
table_idents_to_full_name(&show.table_name, &query_ctx)
Expand Down
47 changes: 46 additions & 1 deletion src/operator/src/statement/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use catalog::CatalogManagerRef;
use chrono::Utc;
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
use common_catalog::format_full_table_name;
use common_error::ext::BoxedError;
use common_meta::cache_invalidator::Context;
use common_meta::ddl::ExecutorContext;
use common_meta::key::schema_name::{SchemaNameKey, SchemaNameValue};
Expand All @@ -34,12 +35,13 @@ use datatypes::prelude::ConcreteDataType;
use datatypes::schema::RawSchema;
use lazy_static::lazy_static;
use partition::partition::{PartitionBound, PartitionDef};
use query::sql::show_create_table;
use regex::Regex;
use session::context::QueryContextRef;
use snafu::{ensure, IntoError, OptionExt, ResultExt};
use sql::ast::Value as SqlValue;
use sql::statements::alter::AlterTable;
use sql::statements::create::{CreateExternalTable, CreateTable, Partitions};
use sql::statements::create::{CreateExternalTable, CreateTable, CreateTableLike, Partitions};
use sql::statements::sql_value_to_value;
use sql::MAXVALUE;
use table::dist_table::DistTable;
Expand All @@ -57,6 +59,8 @@ use crate::error::{
UnrecognizedTableOptionSnafu,
};
use crate::expr_factory;
use crate::statement::show::create_partitions_stmt;
use crate::table::table_idents_to_full_name;

lazy_static! {
static ref NAME_PATTERN_REG: Regex = Regex::new(&format!("^{NAME_PATTERN}$")).unwrap();
Expand All @@ -74,6 +78,47 @@ impl StatementExecutor {
.await
}

#[tracing::instrument(skip_all)]
pub async fn create_table_like(
&self,
stmt: CreateTableLike,
ctx: QueryContextRef,
) -> Result<TableRef> {
let (catalog, schema, table) = table_idents_to_full_name(&stmt.target, &ctx)
.map_err(BoxedError::new)
.context(error::ExternalSnafu)?;
let table_ref = self
.catalog_manager
.table(&catalog, &schema, &table)
.await
.context(error::CatalogSnafu)?
.context(error::TableNotFoundSnafu { table_name: &table })?;
let partitions = self
.partition_manager
.find_table_partitions(table_ref.table_info().table_id())
.await
.context(error::FindTablePartitionRuleSnafu { table_name: table })?;

let quote_style = ctx.quote_style();
let mut create_stmt =
show_create_table::create_table_stmt(&table_ref.table_info(), quote_style)
.context(error::ParseQuerySnafu)?;
create_stmt.name = stmt.name;
create_stmt.if_not_exists = false;

let partitions = create_partitions_stmt(partitions)?.and_then(|mut partitions| {
if !partitions.column_list.is_empty() {
partitions.set_quote(quote_style);
Some(partitions)
} else {
None
}
});

let create_expr = &mut expr_factory::create_to_expr(&create_stmt, ctx.clone())?;
self.create_table_inner(create_expr, partitions, &ctx).await
}

#[tracing::instrument(skip_all)]
pub async fn create_external_table(
&self,
Expand Down
2 changes: 1 addition & 1 deletion src/operator/src/statement/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl StatementExecutor {
}
}

fn create_partitions_stmt(partitions: Vec<PartitionInfo>) -> Result<Option<Partitions>> {
pub(crate) fn create_partitions_stmt(partitions: Vec<PartitionInfo>) -> Result<Option<Partitions>> {
if partitions.is_empty() {
return Ok(None);
}
Expand Down
11 changes: 2 additions & 9 deletions src/query/src/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

mod show_create_table;
pub mod show_create_table;

use std::collections::HashMap;
use std::sync::Arc;
Expand Down Expand Up @@ -258,14 +258,7 @@ pub fn show_create_table(
let table_info = table.table_info();
let table_name = &table_info.name;

// Default to double quote and fallback to back quote
let quote_style = if query_ctx.sql_dialect().is_delimited_identifier_start('"') {
'"'
} else if query_ctx.sql_dialect().is_delimited_identifier_start('\'') {
'\''
} else {
'`'
};
let quote_style = query_ctx.quote_style();

let mut stmt = show_create_table::create_table_stmt(&table_info, quote_style)?;
stmt.partitions = partitions.map(|mut p| {
Expand Down
11 changes: 11 additions & 0 deletions src/session/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ impl QueryContext {
session.set_timezone(tz.as_ref().clone())
}
}

/// Default to double quote and fallback to back quote
pub fn quote_style(&self) -> char {
if self.sql_dialect().is_delimited_identifier_start('"') {
'"'
} else if self.sql_dialect().is_delimited_identifier_start('\'') {
'\''
} else {
'`'
}
}
}

impl QueryContextBuilder {
Expand Down
63 changes: 43 additions & 20 deletions src/sql/src/parsers/create_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use datatypes::prelude::ConcreteDataType;
use itertools::Itertools;
use once_cell::sync::Lazy;
use snafu::{ensure, OptionExt, ResultExt};
use sqlparser::ast::{ColumnOption, ColumnOptionDef, DataType, Value};
use sqlparser::ast::{ColumnOption, ColumnOptionDef, DataType, ObjectName, Value};
use sqlparser::dialect::keywords::Keyword;
use sqlparser::keywords::ALL_KEYWORDS;
use sqlparser::parser::IsOptional::Mandatory;
Expand All @@ -35,7 +35,8 @@ use crate::error::{
};
use crate::parser::ParserContext;
use crate::statements::create::{
CreateDatabase, CreateExternalTable, CreateTable, PartitionEntry, Partitions, TIME_INDEX,
CreateDatabase, CreateExternalTable, CreateTable, CreateTableLike, PartitionEntry, Partitions,
TIME_INDEX,
};
use crate::statements::statement::Statement;
use crate::statements::{
Expand Down Expand Up @@ -74,15 +75,7 @@ impl<'a> ParserContext<'a> {
let if_not_exists =
self.parser
.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
let raw_table_name = self
.parser
.parse_object_name()
.context(error::UnexpectedSnafu {
sql: self.sql,
expected: "a table name",
actual: self.peek_token_as_string(),
})?;
let table_name = Self::canonicalize_object_name(raw_table_name);
let table_name = self.parse_table_name()?;
let (columns, constraints) = self.parse_columns()?;
let engine = self.parse_table_engine(common_catalog::consts::FILE_ENGINE)?;
let options = self
Expand Down Expand Up @@ -144,15 +137,16 @@ impl<'a> ParserContext<'a> {
self.parser
.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);

let raw_table_name = self
.parser
.parse_object_name()
.context(error::UnexpectedSnafu {
sql: self.sql,
expected: "a table name",
actual: self.peek_token_as_string(),
})?;
let table_name = Self::canonicalize_object_name(raw_table_name);
let table_name = self.parse_table_name()?;

if self.parser.parse_keyword(Keyword::LIKE) {
let target_table_name = self.parse_table_name()?;

return Ok(Statement::CreateTableLike(CreateTableLike {
name: table_name,
target: target_table_name,
}));
}

let (columns, constraints) = self.parse_columns()?;

Expand Down Expand Up @@ -188,6 +182,18 @@ impl<'a> ParserContext<'a> {
Ok(Statement::CreateTable(create_table))
}

fn parse_table_name(&mut self) -> Result<ObjectName> {
let raw_table_name = self
.parser
.parse_object_name()
.context(error::UnexpectedSnafu {
sql: self.sql,
expected: "a table name",
actual: self.peek_token_as_string(),
})?;
Ok(Self::canonicalize_object_name(raw_table_name))
}

// "PARTITION BY ..." syntax:
// https://dev.mysql.com/doc/refman/8.0/en/partitioning-columns-range.html
fn parse_partitions(&mut self) -> Result<Option<Partitions>> {
Expand Down Expand Up @@ -884,6 +890,23 @@ mod tests {
use crate::dialect::GreptimeDbDialect;
use crate::parser::ParseOptions;

#[test]
fn test_parse_create_table_like() {
let sql = "CREATE TABLE t1 LIKE t2";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();

assert_eq!(1, stmts.len());
match &stmts[0] {
Statement::CreateTableLike(c) => {
assert_eq!(c.name.to_string(), "t1");
assert_eq!(c.target.to_string(), "t2");
}
_ => unreachable!(),
}
}

#[test]
fn test_validate_external_table_options() {
let sql = "CREATE EXTERNAL TABLE city (
Expand Down
7 changes: 7 additions & 0 deletions src/sql/src/statements/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ pub struct CreateExternalTable {
pub engine: String,
}

#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)]
pub struct CreateTableLike {
/// Table name
pub name: ObjectName,
pub target: ObjectName,
}

#[cfg(test)]
mod tests {
use crate::dialect::GreptimeDbDialect;
Expand Down
6 changes: 5 additions & 1 deletion src/sql/src/statements/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ use sqlparser_derive::{Visit, VisitMut};
use super::show::ShowVariables;
use crate::error::{ConvertToDfStatementSnafu, Error};
use crate::statements::alter::AlterTable;
use crate::statements::create::{CreateDatabase, CreateExternalTable, CreateTable};
use crate::statements::create::{
CreateDatabase, CreateExternalTable, CreateTable, CreateTableLike,
};
use crate::statements::delete::Delete;
use crate::statements::describe::DescribeTable;
use crate::statements::drop::DropTable;
Expand All @@ -45,6 +47,8 @@ pub enum Statement {
CreateTable(CreateTable),
// CREATE EXTERNAL TABLE
CreateExternalTable(CreateExternalTable),
// CREATE TABLE ... LIKE
CreateTableLike(CreateTableLike),
// DROP TABLE
DropTable(DropTable),
// CREATE DATABASE
Expand Down
38 changes: 38 additions & 0 deletions tests/cases/standalone/common/create/create.result
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,41 @@ DROP TABLE neg_default_value;

Affected Rows: 0

CREATE TABLE test_like_1 (i INTEGER, j TIMESTAMP TIME INDEX);

Affected Rows: 0

CREATE TABLE test_like_2 LIKE test_like_1;

Affected Rows: 0

CREATE TABLE test_like_2 LIKE test_like_1;

Error: 4000(TableAlreadyExists), Table already exists: `greptime.public.test_like_2`

DESC TABLE test_like_1;

+--------+----------------------+-----+------+---------+---------------+
| Column | Type | Key | Null | Default | Semantic Type |
+--------+----------------------+-----+------+---------+---------------+
| i | Int32 | | YES | | FIELD |
| j | TimestampMillisecond | PRI | NO | | TIMESTAMP |
+--------+----------------------+-----+------+---------+---------------+

DESC TABLE test_like_2;

+--------+----------------------+-----+------+---------+---------------+
| Column | Type | Key | Null | Default | Semantic Type |
+--------+----------------------+-----+------+---------+---------------+
| i | Int32 | | YES | | FIELD |
| j | TimestampMillisecond | PRI | NO | | TIMESTAMP |
+--------+----------------------+-----+------+---------+---------------+

DROP TABLE test_like_1;

Affected Rows: 0

DROP TABLE test_like_2;

Affected Rows: 0

14 changes: 14 additions & 0 deletions tests/cases/standalone/common/create/create.sql
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,17 @@ CREATE TABLE neg_default_value(i INT DEFAULT -1024, ts TIMESTAMP TIME INDEX);
desc TABLE neg_default_value;

DROP TABLE neg_default_value;

CREATE TABLE test_like_1 (i INTEGER, j TIMESTAMP TIME INDEX);

CREATE TABLE test_like_2 LIKE test_like_1;

CREATE TABLE test_like_2 LIKE test_like_1;

DESC TABLE test_like_1;

DESC TABLE test_like_2;

DROP TABLE test_like_1;

DROP TABLE test_like_2;

0 comments on commit 1ec4159

Please sign in to comment.