Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Create Table ... Like #3372

Merged
merged 5 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/frontend/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ pub fn check_permission(
Statement::CreateTable(stmt) => {
validate_param(&stmt.name, query_ctx)?;
}
Statement::CreateTableLike(stmt) => {
validate_param(&stmt.table_name, query_ctx)?;
validate_param(&stmt.source_name, query_ctx)?;
}
Statement::DropTable(drop_stmt) => {
validate_param(drop_stmt.table_name(), query_ctx)?;
}
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,11 +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 session::table_name::table_idents_to_full_name;
use snafu::{ensure, IntoError, OptionExt, ResultExt};
use sql::statements::alter::AlterTable;
use sql::statements::create::{CreateExternalTable, CreateTable, Partitions};
use sql::statements::create::{CreateExternalTable, CreateTable, CreateTableLike, Partitions};
use table::dist_table::DistTable;
use table::metadata::{self, RawTableInfo, RawTableMeta, TableId, TableInfo, TableType};
use table::requests::{AlterKind, AlterTableRequest, TableOptions};
Expand All @@ -54,6 +57,7 @@ use crate::error::{
UnrecognizedTableOptionSnafu,
};
use crate::expr_factory;
use crate::statement::show::create_partitions_stmt;

lazy_static! {
static ref NAME_PATTERN_REG: Regex = Regex::new(&format!("^{NAME_PATTERN}$")).unwrap();
Expand All @@ -71,6 +75,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.source_name, &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.table_name;
create_stmt.if_not_exists = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like create table like can support if_not_exists too, just like

CREATE TABLE IF NOT EXISTS snapshot LIKE source_data;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I thought so at first, but I checked MySQL's Create Table ... Like and it seems that it is not supported, so I chose to keep it unified. Of course, this is up to you to decide.

ref: https://dev.mysql.com/doc/refman/8.0/en/create-table-like.html

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Let's leave it currently.


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 @@ -76,7 +76,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
7 changes: 5 additions & 2 deletions src/sql/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,17 @@ impl<'a> ParserContext<'a> {
.try_with_sql(sql)
.context(SyntaxSnafu)?;

Self::_parse_table_name(&mut parser, sql)
}

pub(crate) fn _parse_table_name(parser: &mut Parser, sql: &'a str) -> Result<ObjectName> {
let raw_table_name = parser.parse_object_name().context(error::UnexpectedSnafu {
sql,
expected: "a table name",
actual: parser.peek_token().to_string(),
})?;
let table_name = Self::canonicalize_object_name(raw_table_name);

Ok(table_name)
Ok(Self::canonicalize_object_name(raw_table_name))
}

pub fn parse_function(sql: &'a str, dialect: &dyn Dialect) -> Result<Expr> {
Expand Down
48 changes: 29 additions & 19 deletions src/sql/src/parsers/create_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::error::{
};
use crate::parser::ParserContext;
use crate::statements::create::{
CreateDatabase, CreateExternalTable, CreateTable, Partitions, TIME_INDEX,
CreateDatabase, CreateExternalTable, CreateTable, CreateTableLike, Partitions, TIME_INDEX,
};
use crate::statements::get_data_type_by_alias_name;
use crate::statements::statement::Statement;
Expand Down Expand Up @@ -66,15 +66,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 = ParserContext::_parse_table_name(&mut self.parser, self.sql)?;
let (columns, constraints) = self.parse_columns()?;
let engine = self.parse_table_engine(common_catalog::consts::FILE_ENGINE)?;
let options = self
Expand Down Expand Up @@ -136,15 +128,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 = ParserContext::_parse_table_name(&mut self.parser, self.sql)?;

if self.parser.parse_keyword(Keyword::LIKE) {
let source_name = ParserContext::_parse_table_name(&mut self.parser, self.sql)?;

return Ok(Statement::CreateTableLike(CreateTableLike {
table_name,
source_name,
}));
}

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

Expand Down Expand Up @@ -739,6 +732,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.table_name.to_string(), "t1");
assert_eq!(c.source_name.to_string(), "t2");
}
_ => unreachable!(),
}
}

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

#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)]
pub struct CreateTableLike {
/// Table name
pub table_name: ObjectName,
/// The table that is designated to be imitated by `Like`
pub source_name: 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
40 changes: 40 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,43 @@ DROP TABLE neg_default_value;

Affected Rows: 0

CREATE TABLE test_like_1 (PK STRING PRIMARY KEY, i INTEGER DEFAULT 7, 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 |
+--------+----------------------+-----+------+---------+---------------+
| pk | String | PRI | YES | | TAG |
| i | Int32 | | YES | 7 | FIELD |
| j | TimestampMillisecond | PRI | NO | | TIMESTAMP |
+--------+----------------------+-----+------+---------+---------------+

DESC TABLE test_like_2;

+--------+----------------------+-----+------+---------+---------------+
| Column | Type | Key | Null | Default | Semantic Type |
+--------+----------------------+-----+------+---------+---------------+
| pk | String | PRI | YES | | TAG |
| i | Int32 | | YES | 7 | 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 (PK STRING PRIMARY KEY, i INTEGER DEFAULT 7, 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;