Skip to content

Commit

Permalink
parser: Adds new grammar to support user specified PK in CTAS query.
Browse files Browse the repository at this point in the history
This change adds grammar rules to support users specifying the
primary key columns in a CREATE TABLE...AS query. It is a subset of
the grammar which is used by a normal CREATE TABLE query, without
having to specify the types for each of the columns in the new table.

Release note: sql change
  • Loading branch information
adityamaru27 committed Jul 15, 2019
1 parent 8c6fdc6 commit 3fd121a
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 38 deletions.
6 changes: 2 additions & 4 deletions docs/generated/sql/bnf/create_table_as_stmt.bnf
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
create_table_as_stmt ::=
'CREATE' 'TABLE' table_name '(' name ( ( ',' name ) )* ')' 'AS' select_stmt
| 'CREATE' 'TABLE' table_name 'AS' select_stmt
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name '(' name ( ( ',' name ) )* ')' 'AS' select_stmt
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name 'AS' select_stmt
'CREATE' 'TABLE' table_name create_as_opt_col_list 'AS' select_stmt
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name create_as_opt_col_list 'AS' select_stmt
32 changes: 30 additions & 2 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -969,8 +969,8 @@ create_table_stmt ::=
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name '(' opt_table_elem_list ')' opt_interleave opt_partition_by

create_table_as_stmt ::=
'CREATE' 'TABLE' table_name opt_column_list 'AS' select_stmt
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name opt_column_list 'AS' select_stmt
'CREATE' 'TABLE' table_name create_as_opt_col_list 'AS' select_stmt
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name create_as_opt_col_list 'AS' select_stmt

create_view_stmt ::=
'CREATE' 'VIEW' view_name opt_column_list 'AS' select_stmt
Expand Down Expand Up @@ -1352,6 +1352,10 @@ opt_table_elem_list ::=
table_elem_list
|

create_as_opt_col_list ::=
'(' create_as_table_defs ')'
|

view_name ::=
table_name

Expand Down Expand Up @@ -1689,6 +1693,9 @@ partition_by ::=
| 'PARTITION' 'BY' 'RANGE' '(' name_list ')' '(' range_partitions ')'
| 'PARTITION' 'BY' 'NOTHING'

create_as_table_defs ::=
( column_name create_as_col_qual_list ) ( ( ',' create_as_constraint_def | ',' column_name create_as_col_qual_list ) )*

common_table_expr ::=
table_alias_name opt_column_list 'AS' '(' preparable_stmt ')'

Expand Down Expand Up @@ -1911,6 +1918,12 @@ list_partitions ::=
range_partitions ::=
( range_partition ) ( ( ',' range_partition ) )*

create_as_col_qual_list ::=
( ) ( ( create_as_col_qualification ) )*

create_as_constraint_def ::=
create_as_constraint_elem

index_flags_param ::=
'FORCE_INDEX' '=' index_name
| 'NO_INDEX_JOIN'
Expand Down Expand Up @@ -2121,6 +2134,12 @@ list_partition ::=
range_partition ::=
partition 'VALUES' 'FROM' '(' expr_list ')' 'TO' '(' expr_list ')' opt_partition_by

create_as_col_qualification ::=
create_as_col_qualification_elem

create_as_constraint_elem ::=
'PRIMARY' 'KEY' '(' create_as_params ')'

col_qualification_elem ::=
'NOT' 'NULL'
| 'NULL'
Expand Down Expand Up @@ -2230,6 +2249,12 @@ func_expr_windowless ::=
rowsfrom_list ::=
( rowsfrom_item ) ( ( ',' rowsfrom_item ) )*

create_as_col_qualification_elem ::=
'PRIMARY' 'KEY'

create_as_params ::=
( create_as_param ) ( ( ',' create_as_param ) )*

opt_name_parens ::=
'(' name ')'
|
Expand Down Expand Up @@ -2295,6 +2320,9 @@ join_outer ::=
rowsfrom_item ::=
func_expr_windowless

create_as_param ::=
column_name

frame_extent ::=
frame_bound
| 'BETWEEN' frame_bound 'AND' frame_bound
Expand Down
4 changes: 4 additions & 0 deletions pkg/sql/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ func TestParse(t *testing.T) {
{`CREATE TABLE IF NOT EXISTS a AS SELECT * FROM b UNION SELECT * FROM c`},
{`CREATE TABLE a AS SELECT * FROM b UNION VALUES ('one', 1) ORDER BY c LIMIT 5`},
{`CREATE TABLE IF NOT EXISTS a AS SELECT * FROM b UNION VALUES ('one', 1) ORDER BY c LIMIT 5`},
{`CREATE TABLE a (z PRIMARY KEY) AS SELECT * FROM b`},
{`CREATE TABLE IF NOT EXISTS a (z PRIMARY KEY) AS SELECT * FROM b`},
{`CREATE TABLE a (x, y, z, PRIMARY KEY (x, y, z)) AS SELECT * FROM b`},
{`CREATE TABLE IF NOT EXISTS a (x, y, z, PRIMARY KEY (x, y, z)) AS SELECT * FROM b`},
{`CREATE TABLE a (b STRING COLLATE de)`},
{`CREATE TABLE a (b STRING(3) COLLATE de)`},
{`CREATE TABLE a (b STRING[] COLLATE de)`},
Expand Down
121 changes: 106 additions & 15 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,7 @@ func newNameFromStr(s string) *tree.Name {
%type <tree.IsolationLevel> iso_level
%type <tree.UserPriority> user_priority

%type <tree.TableDefs> opt_table_elem_list table_elem_list
%type <tree.TableDefs> opt_table_elem_list table_elem_list create_as_opt_col_list create_as_table_defs
%type <*tree.InterleaveDef> opt_interleave
%type <*tree.PartitionBy> opt_partition_by partition_by
%type <str> partition opt_partition
Expand All @@ -816,7 +816,7 @@ func newNameFromStr(s string) *tree.Name {
%type <tree.NameList> opt_column_list insert_column_list opt_stats_columns
%type <tree.OrderBy> sort_clause opt_sort_clause
%type <[]*tree.Order> sortby_list
%type <tree.IndexElemList> index_params
%type <tree.IndexElemList> index_params create_as_params
%type <tree.NameList> name_list privilege_list
%type <[]int32> opt_array_bounds
%type <*tree.From> from_clause update_from_clause
Expand Down Expand Up @@ -896,7 +896,7 @@ func newNameFromStr(s string) *tree.Name {
%type <tree.AliasClause> alias_clause opt_alias_clause
%type <bool> opt_ordinality opt_compact opt_automatic
%type <*tree.Order> sortby
%type <tree.IndexElem> index_elem
%type <tree.IndexElem> index_elem create_as_param
%type <tree.TableExpr> table_ref func_table
%type <tree.Exprs> rowsfrom_list
%type <tree.Expr> rowsfrom_item
Expand Down Expand Up @@ -941,12 +941,12 @@ func newNameFromStr(s string) *tree.Name {
%type <str> unreserved_keyword type_func_name_keyword cockroachdb_extra_type_func_name_keyword
%type <str> col_name_keyword reserved_keyword cockroachdb_extra_reserved_keyword extra_var_value

%type <tree.ConstraintTableDef> table_constraint constraint_elem
%type <tree.ConstraintTableDef> table_constraint constraint_elem create_as_constraint_def create_as_constraint_elem
%type <tree.TableDef> index_def
%type <tree.TableDef> family_def
%type <[]tree.NamedColumnQualification> col_qual_list
%type <tree.NamedColumnQualification> col_qualification
%type <tree.ColumnQualification> col_qualification_elem
%type <[]tree.NamedColumnQualification> col_qual_list create_as_col_qual_list
%type <tree.NamedColumnQualification> col_qualification create_as_col_qualification
%type <tree.ColumnQualification> col_qualification_elem create_as_col_qualification_elem
%type <tree.CompositeKeyMatchMethod> key_match
%type <tree.ReferenceActions> reference_actions
%type <tree.ReferenceAction> reference_action reference_on_delete reference_on_update
Expand Down Expand Up @@ -3997,7 +3997,6 @@ create_table_stmt:
Interleave: $8.interleave(),
Defs: $6.tblDefs(),
AsSource: nil,
AsColumnNames: nil,
PartitionBy: $9.partitionBy(),
}
}
Expand All @@ -4010,7 +4009,6 @@ create_table_stmt:
Interleave: $11.interleave(),
Defs: $9.tblDefs(),
AsSource: nil,
AsColumnNames: nil,
PartitionBy: $12.partitionBy(),
}
}
Expand All @@ -4021,28 +4019,26 @@ opt_table_with:
| WITH name error { return unimplemented(sqllex, "create table with " + $2) }

create_table_as_stmt:
CREATE opt_temp TABLE table_name opt_column_list opt_table_with AS select_stmt opt_create_as_data
CREATE opt_temp TABLE table_name create_as_opt_col_list opt_table_with AS select_stmt opt_create_as_data
{
name := $4.unresolvedObjectName().ToTableName()
$$.val = &tree.CreateTable{
Table: name,
IfNotExists: false,
Interleave: nil,
Defs: nil,
Defs: $5.tblDefs(),
AsSource: $8.slct(),
AsColumnNames: $5.nameList(),
}
}
| CREATE opt_temp TABLE IF NOT EXISTS table_name opt_column_list opt_table_with AS select_stmt opt_create_as_data
| CREATE opt_temp TABLE IF NOT EXISTS table_name create_as_opt_col_list opt_table_with AS select_stmt opt_create_as_data
{
name := $7.unresolvedObjectName().ToTableName()
$$.val = &tree.CreateTable{
Table: name,
IfNotExists: true,
Interleave: nil,
Defs: nil,
Defs: $8.tblDefs(),
AsSource: $11.slct(),
AsColumnNames: $8.nameList(),
}
}

Expand Down Expand Up @@ -4425,6 +4421,101 @@ constraint_elem:
}
}


create_as_opt_col_list:
'(' create_as_table_defs ')'
{
$$.val = $2.val
}
| /* EMPTY */
{
$$.val = tree.TableDefs(nil)
}

create_as_table_defs:
column_name create_as_col_qual_list
{
tableDef, err := tree.NewColumnTableDef(tree.Name($1), nil, false, $2.colQuals())
if err != nil {
return setErr(sqllex, err)
}

var colToTableDef tree.TableDef = tableDef
$$.val = tree.TableDefs{colToTableDef}
}
| create_as_table_defs ',' create_as_constraint_def
{

var constraintToTableDef tree.TableDef = $3.constraintDef()
$$.val = append($1.tblDefs(), constraintToTableDef)
}
| create_as_table_defs ',' column_name create_as_col_qual_list
{
tableDef, err := tree.NewColumnTableDef(tree.Name($3), nil, false, $4.colQuals())
if err != nil {
return setErr(sqllex, err)
}

var colToTableDef tree.TableDef = tableDef

$$.val = append($1.tblDefs(), colToTableDef)
}

create_as_constraint_def:
create_as_constraint_elem
{
$$.val = $1.constraintDef()
}

create_as_constraint_elem:
PRIMARY KEY '(' create_as_params ')'
{
$$.val = &tree.UniqueConstraintTableDef{
IndexTableDef: tree.IndexTableDef{
Columns: $4.idxElems(),
},
PrimaryKey: true,
}
}

create_as_params:
create_as_param
{
$$.val = tree.IndexElemList{$1.idxElem()}
}
| create_as_params ',' create_as_param
{
$$.val = append($1.idxElems(), $3.idxElem())
}

create_as_param:
column_name
{
$$.val = tree.IndexElem{Column: tree.Name($1)}
}

create_as_col_qual_list:
create_as_col_qual_list create_as_col_qualification
{
$$.val = append($1.colQuals(), $2.colQual())
}
| /* EMPTY */
{
$$.val = []tree.NamedColumnQualification(nil)
}

create_as_col_qualification:
create_as_col_qualification_elem
{
$$.val = tree.NamedColumnQualification{Qualification: $1.colQualElem()}
}

create_as_col_qualification_elem:
PRIMARY KEY
{
$$.val = tree.PrimaryKeyConstraint{}
}

opt_deferrable:
/* EMPTY */ { /* no error */ }
| DEFERRABLE { return unimplementedWithIssueDetail(sqllex, 31632, "deferrable") }
Expand Down
44 changes: 33 additions & 11 deletions pkg/sql/sem/tree/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,14 @@ func (node *ColumnTableDef) HasColumnFamily() bool {
// Format implements the NodeFormatter interface.
func (node *ColumnTableDef) Format(ctx *FmtCtx) {
ctx.FormatNode(&node.Name)
ctx.WriteByte(' ')
ctx.WriteString(node.columnTypeString())

// ColumnTableDef node type will not be specified if it represents a CREATE
// TABLE ... AS query.
if node.Type != nil {
ctx.WriteByte(' ')
ctx.WriteString(node.columnTypeString())
}

if node.Nullable.Nullability != SilentNull && node.Nullable.ConstraintName != "" {
ctx.WriteString(" CONSTRAINT ")
ctx.FormatNode(&node.Nullable.ConstraintName)
Expand Down Expand Up @@ -885,13 +891,14 @@ func (node *RangePartition) Format(ctx *FmtCtx) {

// CreateTable represents a CREATE TABLE statement.
type CreateTable struct {
IfNotExists bool
Table TableName
Interleave *InterleaveDef
PartitionBy *PartitionBy
Defs TableDefs
AsSource *Select
AsColumnNames NameList // Only to be used in conjunction with AsSource
IfNotExists bool
Table TableName
Interleave *InterleaveDef
PartitionBy *PartitionBy
// In CREATE...AS queries, Defs represents a list of column defs, one for each
// column, and a constraint def if a PRIMARY KEY(...) constraint is specified.
Defs TableDefs
AsSource *Select
}

// As returns true if this table represents a CREATE TABLE ... AS statement,
Expand All @@ -900,6 +907,21 @@ func (node *CreateTable) As() bool {
return node.AsSource != nil
}

// AsHasUserSpecifiedPrimaryKey returns true if a CREATE TABLE ... AS statement
// has a PRIMARY KEY constraint specified.
func (node *CreateTable) AsHasUserSpecifiedPrimaryKey() bool {
if node.As() {
for _, def := range node.Defs {
if d, ok := def.(*ColumnTableDef); !ok {
return false
} else if d.PrimaryKey {
return true
}
}
}
return false
}

// Format implements the NodeFormatter interface.
func (node *CreateTable) Format(ctx *FmtCtx) {
ctx.WriteString("CREATE TABLE ")
Expand All @@ -914,9 +936,9 @@ func (node *CreateTable) Format(ctx *FmtCtx) {
// but the CREATE TABLE tableName part.
func (node *CreateTable) FormatBody(ctx *FmtCtx) {
if node.As() {
if len(node.AsColumnNames) > 0 {
if len(node.Defs) > 0 {
ctx.WriteString(" (")
ctx.FormatNode(&node.AsColumnNames)
ctx.FormatNode(&node.Defs)
ctx.WriteByte(')')
}
ctx.WriteString(" AS ")
Expand Down
Loading

0 comments on commit 3fd121a

Please sign in to comment.