Skip to content

Commit

Permalink
parser: support 'CREATE TABLE ... SELECT' syntax (pingcap#4754)
Browse files Browse the repository at this point in the history
  • Loading branch information
bb7133 committed Jun 21, 2018
1 parent 8bec188 commit 8897eb9
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 30 deletions.
18 changes: 18 additions & 0 deletions ast/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@ type CreateTableStmt struct {
Constraints []*Constraint
Options []*TableOption
Partition *PartitionOptions
OnDuplicate OnDuplicateCreateTableSelectType
Select ResultSetNode
}

// Accept implements Node Accept interface.
Expand Down Expand Up @@ -439,6 +441,14 @@ func (n *CreateTableStmt) Accept(v Visitor) (Node, bool) {
}
n.Constraints[i] = node.(*Constraint)
}
if n.Select != nil {
node, ok := n.Select.Accept(v)
if !ok {
return n, false
}
n.Select = node.(ResultSetNode)
}

return v.Leave(n)
}

Expand Down Expand Up @@ -656,6 +666,14 @@ const (
RowFormatCompact
)

type OnDuplicateCreateTableSelectType int

const (
OnDuplicateCreateTableSelectError OnDuplicateCreateTableSelectType = iota
OnDuplicateCreateTableSelectIgnore
OnDuplicateCreateTableSelectReplace
)

// TableOption is used for parsing table option from SQL.
type TableOption struct {
Tp TableOptionType
Expand Down
4 changes: 4 additions & 0 deletions ddl/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ var (
ErrWrongTableName = terror.ClassDDL.New(codeWrongTableName, mysql.MySQLErrName[mysql.ErrWrongTableName])
// ErrWrongColumnName returns for wrong column name.
ErrWrongColumnName = terror.ClassDDL.New(codeWrongColumnName, mysql.MySQLErrName[mysql.ErrWrongColumnName])
// ErrTableMustHaveColumns returns for missing column when creating a table.
ErrTableMustHaveColumns = terror.ClassDDL.New(codeTableMustHaveColumns, mysql.MySQLErrName[mysql.ErrTableMustHaveColumns])
// ErrWrongNameForIndex returns for wrong index name.
ErrWrongNameForIndex = terror.ClassDDL.New(codeWrongNameForIndex, mysql.MySQLErrName[mysql.ErrWrongNameForIndex])
// ErrUnknownCharacterSet returns unknown character set.
Expand Down Expand Up @@ -564,6 +566,7 @@ const (
codeErrTooLongIndexComment = terror.ErrCode(mysql.ErrTooLongIndexComment)
codeUnknownCharacterSet = terror.ErrCode(mysql.ErrUnknownCharacterSet)
codeCantCreateTable = terror.ErrCode(mysql.ErrCantCreateTable)
codeTableMustHaveColumns = terror.ErrCode(mysql.ErrTableMustHaveColumns)
)

func init() {
Expand Down Expand Up @@ -592,6 +595,7 @@ func init() {
codeWrongColumnName: mysql.ErrWrongColumnName,
codeWrongKeyColumn: mysql.ErrWrongKeyColumn,
codeWrongNameForIndex: mysql.ErrWrongNameForIndex,
codeTableMustHaveColumns: mysql.ErrTableMustHaveColumns,
codeTooManyFields: mysql.ErrTooManyFields,
codeErrTooLongIndexComment: mysql.ErrTooLongIndexComment,
codeUnknownCharacterSet: mysql.ErrUnknownCharacterSet,
Expand Down
4 changes: 3 additions & 1 deletion ddl/ddl_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func (s *testDBSuite) TestMySQLErrorCode(c *C) {
// drop database
sql = "drop database db_not_exist"
s.testErrorCode(c, sql, tmysql.ErrDBDropExists)
// crate table
// create table
s.tk.MustExec("create table test_error_code_succ (c1 int, c2 int, c3 int, primary key(c3))")
sql = "create table test_error_code_succ (c1 int, c2 int, c3 int)"
s.testErrorCode(c, sql, tmysql.ErrTableExists)
Expand Down Expand Up @@ -179,6 +179,8 @@ func (s *testDBSuite) TestMySQLErrorCode(c *C) {
s.testErrorCode(c, sql, tmysql.ErrUnknownCharacterSet)
sql = "create table test_error_code (a int not null ,b int not null,c int not null, d int not null, foreign key (b, c) references product(id));"
s.testErrorCode(c, sql, tmysql.ErrWrongFkDef)
sql = "create table test_error_code_2;"
s.testErrorCode(c, sql, tmysql.ErrTableMustHaveColumns)
sql = "create table test_error_code_2(c1 int, c2 int, c3 int, primary key(c1), primary key(c2));"
s.testErrorCode(c, sql, tmysql.ErrMultiplePriKey)
sql = "create table test_error_code_3(pt blob ,primary key (pt));"
Expand Down
136 changes: 107 additions & 29 deletions parser/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ import (
ConstraintKeywordOpt "Constraint Keyword or empty"
CreateIndexStmtUnique "CREATE INDEX optional UNIQUE clause"
CreateTableOptionListOpt "create table option list opt"
CreateTableSelectOpt "Select/Union statement in CREATE TABLE ... SELECT"
DatabaseOption "CREATE Database specification"
DatabaseOptionList "CREATE Database specification list"
DatabaseOptionListOpt "CREATE Database specification list opt"
Expand Down Expand Up @@ -653,6 +654,7 @@ import (
JoinType "join type"
KillOrKillTiDB "Kill or Kill TiDB"
LikeEscapeOpt "like escape option"
LikeTableWithOrWithoutParen "LIKE table_name or ( LIKE table_name )"
LimitClause "LIMIT clause"
LimitOption "Limit option could be integer or parameter marker."
Lines "Lines clause"
Expand All @@ -663,6 +665,7 @@ import (
NoWriteToBinLogAliasOpt "NO_WRITE_TO_BINLOG alias LOCAL or empty"
ObjectType "Grant statement object type"
OnDuplicateKeyUpdate "ON DUPLICATE KEY UPDATE value list"
OnDuplicateCreateTableOpt "[IGNORE|REPLACE] in CREATE TABLE ... SELECT statement"
OptFull "Full or empty"
Order "ORDER BY clause optional collation specification"
OrderBy "ORDER BY clause"
Expand Down Expand Up @@ -719,6 +722,7 @@ import (
TableAsNameOpt "table alias name optional"
TableElement "table definition element"
TableElementList "table definition element list"
TableElementListOpt "table definition element list optional"
TableFactor "table factor"
TableLock "Table name and lock type"
TableLockList "Table lock list"
Expand Down Expand Up @@ -799,6 +803,7 @@ import (
TableOptimizerHintList "Table level optimizer hint list"

%type <ident>
AsOpt "AS or EmptyString"
KeyOrIndex "{KEY|INDEX}"
ColumnKeywordOpt "Column keyword or empty"
PrimaryOpt "Optional primary keyword"
Expand Down Expand Up @@ -855,6 +860,8 @@ import (
%precedence set
%precedence lowerThanInsertValues
%precedence insertValues
%precedence lowerThanCreateTableSelect
%precedence createTableSelect
%precedence lowerThanKey
%precedence key

Expand Down Expand Up @@ -1727,42 +1734,26 @@ DatabaseOptionList:
* PRIMARY KEY (P_Id)
* )
*******************************************************************/

CreateTableStmt:
"CREATE" "TABLE" IfNotExists TableName '(' TableElementList ')' CreateTableOptionListOpt PartitionOpt
"CREATE" "TABLE" IfNotExists TableName TableElementListOpt CreateTableOptionListOpt PartitionOpt OnDuplicateCreateTableOpt AsOpt CreateTableSelectOpt
{
tes := $6.([]interface {})
var columnDefs []*ast.ColumnDef
var constraints []*ast.Constraint
for _, te := range tes {
switch te := te.(type) {
case *ast.ColumnDef:
columnDefs = append(columnDefs, te)
case *ast.Constraint:
constraints = append(constraints, te)
}
}
if len(columnDefs) == 0 {
yylex.Errorf("Column Definition List can't be empty.")
return 1
}
var part *ast.PartitionOptions
if $9 != nil {
part = $9.(*ast.PartitionOptions)
}
$$ = &ast.CreateTableStmt{
Table: $4.(*ast.TableName),
IfNotExists: $3.(bool),
Cols: columnDefs,
Constraints: constraints,
Options: $8.([]*ast.TableOption),
Partition: part,
stmt := $5.(*ast.CreateTableStmt)
stmt.Table = $4.(*ast.TableName)
stmt.IfNotExists = $3.(bool)
stmt.Options = $6.([]*ast.TableOption)
if $7 != nil {
stmt.Partition = $7.(*ast.PartitionOptions)
}
stmt.OnDuplicate = $8.(ast.OnDuplicateCreateTableSelectType)
stmt.Select = $10.(*ast.CreateTableStmt).Select
$$ = stmt
}
| "CREATE" "TABLE" IfNotExists TableName "LIKE" TableName
| "CREATE" "TABLE" IfNotExists TableName LikeTableWithOrWithoutParen
{
$$ = &ast.CreateTableStmt{
Table: $4.(*ast.TableName),
ReferTable: $6.(*ast.TableName),
ReferTable: $5.(*ast.TableName),
IfNotExists: $3.(bool),
}
}
Expand Down Expand Up @@ -1814,6 +1805,7 @@ PartitionNumOpt:
{}

PartitionDefinitionListOpt:
/* empty */ %prec lowerThanCreateTableSelect
{
$$ = nil
}
Expand Down Expand Up @@ -1876,6 +1868,57 @@ PartDefStorageOpt:
| "ENGINE" eq Identifier
{}

OnDuplicateCreateTableOpt:
{
$$ = ast.OnDuplicateCreateTableSelectError
}
| "IGNORE"
{
$$ = ast.OnDuplicateCreateTableSelectIgnore
}
| "REPLACE"
{
$$ = ast.OnDuplicateCreateTableSelectReplace
}

AsOpt:
{}
| "AS"
{}

CreateTableSelectOpt:
/* empty */
{
$$ = &ast.CreateTableStmt{}
}
|
SelectStmt
{
$$ = &ast.CreateTableStmt{Select: $1}
}
|
UnionStmt
{
$$ = &ast.CreateTableStmt{Select: $1}
}
|
SubSelect %prec createTableSelect
// TODO: We may need better solution as issue #320.
{
$$ = &ast.CreateTableStmt{Select: $1}
}

LikeTableWithOrWithoutParen:
"LIKE" TableName
{
$$ = $2
}
|
'(' "LIKE" TableName ')'
{
$$ = $3
}

/*******************************************************************
*
* Create View Statement
Expand Down Expand Up @@ -5610,6 +5653,40 @@ TableElementList:
}
}

TableElementListOpt:
/* empty */ %prec lowerThanCreateTableSelect
{
var columnDefs []*ast.ColumnDef
var constraints []*ast.Constraint
$$ = &ast.CreateTableStmt{
Cols: columnDefs,
Constraints: constraints,
}
}
|
'(' TableElementList ')'
{
tes := $2.([]interface {})
var columnDefs []*ast.ColumnDef
var constraints []*ast.Constraint
for _, te := range tes {
switch te := te.(type) {
case *ast.ColumnDef:
columnDefs = append(columnDefs, te)
case *ast.Constraint:
constraints = append(constraints, te)
}
}
if len(columnDefs) == 0 {
yylex.Errorf("Column Definition List can't be empty.")
return 1
}
$$ = &ast.CreateTableStmt{
Cols: columnDefs,
Constraints: constraints,
}
}

TableOption:
"ENGINE" StringName
{
Expand Down Expand Up @@ -5702,6 +5779,7 @@ AlterTableOptionListOpt:
| TableOptionList %prec higherThanComma

CreateTableOptionListOpt:
/* empty */ %prec lowerThanCreateTableSelect
{
$$ = []*ast.TableOption{}
}
Expand Down
8 changes: 8 additions & 0 deletions plan/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,14 @@ func (p *preprocessor) checkCreateTableGrammar(stmt *ast.CreateTableStmt) {
}
}
}
if stmt.Select != nil {
// FIXME: a temp error noticing 'not implemented' (issue 4754)
p.err = errors.New("'CREATE TABLE ... SELECT' is not implemented yet")
return
} else if len(stmt.Cols) == 0 && len(stmt.Constraints) == 0 && stmt.ReferTable == nil {
p.err = ddl.ErrTableMustHaveColumns
return
}
}

func (p *preprocessor) checkDropTableGrammar(stmt *ast.DropTableStmt) {
Expand Down
7 changes: 7 additions & 0 deletions plan/preprocess_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ func (s *testValidatorSuite) TestValidator(c *C) {
{"CREATE TABLE t (a float(255, 31))", false, types.ErrTooBigScale},
{"CREATE TABLE t (a double(256, 30))", false, types.ErrTooBigPrecision},
{"CREATE TABLE t (a double(255, 31))", false, types.ErrTooBigScale},

// FIXME: temporary 'not implemented yet' test for 'CREATE TABLE ... SELECT' (issue 4754)
{"CREATE TABLE t SELECT * FROM u", false, errors.New("'CREATE TABLE ... SELECT' is not implemented yet")},
{"CREATE TABLE t (m int) SELECT * FROM u", false, errors.New("'CREATE TABLE ... SELECT' is not implemented yet")},
{"CREATE TABLE t IGNORE SELECT * FROM u UNION SELECT * from v", false, errors.New("'CREATE TABLE ... SELECT' is not implemented yet")},
{"CREATE TABLE t (m int) REPLACE AS (SELECT * FROM u) UNION (SELECT * FROM v)", false, errors.New("'CREATE TABLE ... SELECT' is not implemented yet")},

{"select * from ( select 1 ) a, (select 2) a;", false, plan.ErrNonUniqTable},
{"select * from ( select 1 ) a, (select 2) b, (select 3) a;", false, plan.ErrNonUniqTable},
{"select * from ( select 1 ) a, (select 2) b, (select 3) A;", false, plan.ErrNonUniqTable},
Expand Down

0 comments on commit 8897eb9

Please sign in to comment.