Skip to content

Commit 5dfa38c

Browse files
craig[bot]adityamaru27
andcommitted
Merge #38589
38589: pkg: Allow user defined primary key in CREATE TABLE ... AS r=adityamaru27 a=adityamaru27 This change allows users to specify which column in the newly created table should serve as the primary key (#20940). If no such constraint is specified, we generate a unique row_id to serve as the primary key. Co-authored-by: Aditya Maru <adityamaru@cockroachlabs.com>
2 parents 8809566 + f8333e7 commit 5dfa38c

File tree

9 files changed

+385
-84
lines changed

9 files changed

+385
-84
lines changed
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
create_table_as_stmt ::=
2-
'CREATE' 'TABLE' table_name '(' name ( ( ',' name ) )* ')' 'AS' select_stmt
3-
| 'CREATE' 'TABLE' table_name 'AS' select_stmt
4-
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name '(' name ( ( ',' name ) )* ')' 'AS' select_stmt
5-
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name 'AS' select_stmt
2+
'CREATE' 'TABLE' table_name create_as_opt_col_list 'AS' select_stmt
3+
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name create_as_opt_col_list 'AS' select_stmt

docs/generated/sql/bnf/stmt_block.bnf

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -969,8 +969,8 @@ create_table_stmt ::=
969969
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name '(' opt_table_elem_list ')' opt_interleave opt_partition_by
970970

971971
create_table_as_stmt ::=
972-
'CREATE' 'TABLE' table_name opt_column_list 'AS' select_stmt
973-
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name opt_column_list 'AS' select_stmt
972+
'CREATE' 'TABLE' table_name create_as_opt_col_list 'AS' select_stmt
973+
| 'CREATE' 'TABLE' 'IF' 'NOT' 'EXISTS' table_name create_as_opt_col_list 'AS' select_stmt
974974

975975
create_view_stmt ::=
976976
'CREATE' 'VIEW' view_name opt_column_list 'AS' select_stmt
@@ -1352,6 +1352,10 @@ opt_table_elem_list ::=
13521352
table_elem_list
13531353
|
13541354

1355+
create_as_opt_col_list ::=
1356+
'(' create_as_table_defs ')'
1357+
|
1358+
13551359
view_name ::=
13561360
table_name
13571361

@@ -1689,6 +1693,9 @@ partition_by ::=
16891693
| 'PARTITION' 'BY' 'RANGE' '(' name_list ')' '(' range_partitions ')'
16901694
| 'PARTITION' 'BY' 'NOTHING'
16911695

1696+
create_as_table_defs ::=
1697+
( column_name create_as_col_qual_list ) ( ( ',' create_as_constraint_def | ',' column_name create_as_col_qual_list ) )*
1698+
16921699
common_table_expr ::=
16931700
table_alias_name opt_column_list 'AS' '(' preparable_stmt ')'
16941701

@@ -1911,6 +1918,12 @@ list_partitions ::=
19111918
range_partitions ::=
19121919
( range_partition ) ( ( ',' range_partition ) )*
19131920

1921+
create_as_col_qual_list ::=
1922+
( ) ( ( create_as_col_qualification ) )*
1923+
1924+
create_as_constraint_def ::=
1925+
create_as_constraint_elem
1926+
19141927
index_flags_param ::=
19151928
'FORCE_INDEX' '=' index_name
19161929
| 'NO_INDEX_JOIN'
@@ -2121,6 +2134,12 @@ list_partition ::=
21212134
range_partition ::=
21222135
partition 'VALUES' 'FROM' '(' expr_list ')' 'TO' '(' expr_list ')' opt_partition_by
21232136

2137+
create_as_col_qualification ::=
2138+
create_as_col_qualification_elem
2139+
2140+
create_as_constraint_elem ::=
2141+
'PRIMARY' 'KEY' '(' create_as_params ')'
2142+
21242143
col_qualification_elem ::=
21252144
'NOT' 'NULL'
21262145
| 'NULL'
@@ -2230,6 +2249,12 @@ func_expr_windowless ::=
22302249
rowsfrom_list ::=
22312250
( rowsfrom_item ) ( ( ',' rowsfrom_item ) )*
22322251

2252+
create_as_col_qualification_elem ::=
2253+
'PRIMARY' 'KEY'
2254+
2255+
create_as_params ::=
2256+
( create_as_param ) ( ( ',' create_as_param ) )*
2257+
22332258
opt_name_parens ::=
22342259
'(' name ')'
22352260
|
@@ -2295,6 +2320,9 @@ join_outer ::=
22952320
rowsfrom_item ::=
22962321
func_expr_windowless
22972322

2323+
create_as_param ::=
2324+
column_name
2325+
22982326
frame_extent ::=
22992327
frame_bound
23002328
| 'BETWEEN' frame_bound 'AND' frame_bound

pkg/sql/create_table.go

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ func (p *planner) CreateTable(ctx context.Context, n *tree.CreateTable) (planNod
6969
return nil, err
7070
}
7171

72-
numColNames := len(n.AsColumnNames)
72+
numColNames := 0
73+
for i := 0; i < len(n.Defs); i++ {
74+
if _, ok := n.Defs[i].(*tree.ColumnTableDef); ok {
75+
numColNames++
76+
}
77+
}
7378
numColumns := len(planColumns(sourcePlan))
7479
if numColNames != 0 && numColNames != numColumns {
7580
sourcePlan.Close(ctx)
@@ -80,12 +85,22 @@ func (p *planner) CreateTable(ctx context.Context, n *tree.CreateTable) (planNod
8085
}
8186

8287
// Synthesize an input column that provides the default value for the
83-
// hidden rowid column.
88+
// hidden rowid column, if none of the provided columns are specified
89+
// as the PRIMARY KEY.
8490
synthRowID = true
91+
for _, def := range n.Defs {
92+
if d, ok := def.(*tree.ColumnTableDef); ok && d.PrimaryKey {
93+
synthRowID = false
94+
break
95+
}
96+
}
8597
}
8698

8799
ct := &createTableNode{n: n, dbDesc: dbDesc, sourcePlan: sourcePlan}
88100
ct.run.synthRowID = synthRowID
101+
// This method is only invoked if the heuristic planner was used in the
102+
// planning stage.
103+
ct.run.fromHeuristicPlanner = true
89104
return ct, nil
90105
}
91106

@@ -95,10 +110,17 @@ type createTableRun struct {
95110
autoCommit autoCommitOpt
96111

97112
// synthRowID indicates whether an input column needs to be synthesized to
98-
// provide the default value for the hidden rowid column. The optimizer's
99-
// plan already includes this column (so synthRowID is false), whereas the
100-
// heuristic planner's plan does not (so synthRowID is true).
113+
// provide the default value for the hidden rowid column. The optimizer's plan
114+
// already includes this column if a user specified PK does not exist (so
115+
// synthRowID is false), whereas the heuristic planner's plan does not in this
116+
// case (so synthRowID is true).
101117
synthRowID bool
118+
119+
// fromHeuristicPlanner indicates whether the planning was performed by the
120+
// heuristic planner instead of the optimizer. This is used to determine
121+
// whether or not a row_id was synthesized as part of the planning stage, if a
122+
// user defined PK is not specified.
123+
fromHeuristicPlanner bool
102124
}
103125

104126
func (n *createTableNode) startExec(params runParams) error {
@@ -139,15 +161,18 @@ func (n *createTableNode) startExec(params runParams) error {
139161
}
140162

141163
asCols = planColumns(n.sourcePlan)
142-
if !n.run.synthRowID {
143-
// rowID column is already present in the input as the last column, so
144-
// ignore it for the purpose of creating column metadata (because
164+
if !n.run.fromHeuristicPlanner && !n.n.AsHasUserSpecifiedPrimaryKey() {
165+
// rowID column is already present in the input as the last column if it
166+
// was planned by the optimizer and the user did not specify a PRIMARY
167+
// KEY. So ignore it for the purpose of creating column metadata (because
145168
// makeTableDescIfAs does it automatically).
146169
asCols = asCols[:len(asCols)-1]
147170
}
148-
desc, err = makeTableDescIfAs(
171+
172+
desc, err = makeTableDescIfAs(params,
149173
n.n, n.dbDesc.ID, id, creationTime, asCols,
150-
privs, &params.p.semaCtx, params.p.EvalContext())
174+
privs, params.p.EvalContext())
175+
151176
if err != nil {
152177
return err
153178
}
@@ -259,9 +284,9 @@ func (n *createTableNode) startExec(params runParams) error {
259284
return err
260285
}
261286

262-
// Prepare the buffer for row values. At this point, one more
263-
// column has been added by ensurePrimaryKey() to the list of
264-
// columns in sourcePlan.
287+
// Prepare the buffer for row values. At this point, one more column has
288+
// been added by ensurePrimaryKey() to the list of columns in sourcePlan, if
289+
// a PRIMARY KEY is not specified by the user.
265290
rowBuffer := make(tree.Datums, len(desc.Columns))
266291
pkColIdx := len(desc.Columns) - 1
267292

@@ -985,38 +1010,51 @@ func getFinalSourceQuery(source *tree.Select, evalCtx *tree.EvalContext) string
9851010
// makeTableDescIfAs is the MakeTableDesc method for when we have a table
9861011
// that is created with the CREATE AS format.
9871012
func makeTableDescIfAs(
1013+
params runParams,
9881014
p *tree.CreateTable,
9891015
parentID, id sqlbase.ID,
9901016
creationTime hlc.Timestamp,
9911017
resultColumns []sqlbase.ResultColumn,
9921018
privileges *sqlbase.PrivilegeDescriptor,
993-
semaCtx *tree.SemaContext,
9941019
evalContext *tree.EvalContext,
9951020
) (desc sqlbase.MutableTableDescriptor, err error) {
996-
desc = InitTableDescriptor(id, parentID, p.Table.Table(), creationTime, privileges)
997-
desc.CreateQuery = getFinalSourceQuery(p.AsSource, evalContext)
998-
999-
for i, colRes := range resultColumns {
1000-
columnTableDef := tree.ColumnTableDef{Name: tree.Name(colRes.Name), Type: colRes.Typ}
1001-
columnTableDef.Nullable.Nullability = tree.SilentNull
1002-
if len(p.AsColumnNames) > i {
1003-
columnTableDef.Name = p.AsColumnNames[i]
1004-
}
1005-
1006-
// The new types in the CREATE TABLE AS column specs never use
1007-
// SERIAL so we need not process SERIAL types here.
1008-
col, _, _, err := sqlbase.MakeColumnDefDescs(&columnTableDef, semaCtx)
1009-
if err != nil {
1010-
return desc, err
1021+
colResIndex := 0
1022+
// TableDefs for a CREATE TABLE ... AS AST node comprise of a ColumnTableDef
1023+
// for each column, and a ConstraintTableDef for any constraints on those
1024+
// columns.
1025+
for _, defs := range p.Defs {
1026+
var d *tree.ColumnTableDef
1027+
var ok bool
1028+
if d, ok = defs.(*tree.ColumnTableDef); ok {
1029+
d.Type = resultColumns[colResIndex].Typ
1030+
colResIndex++
1031+
}
1032+
}
1033+
1034+
// If there are no TableDefs defined by the parser, then we construct a
1035+
// ColumnTableDef for each column using resultColumns.
1036+
if len(p.Defs) == 0 {
1037+
for _, colRes := range resultColumns {
1038+
var d *tree.ColumnTableDef
1039+
var ok bool
1040+
var tableDef tree.TableDef = &tree.ColumnTableDef{Name: tree.Name(colRes.Name), Type: colRes.Typ}
1041+
if d, ok = tableDef.(*tree.ColumnTableDef); !ok {
1042+
return desc, errors.Errorf("failed to cast type to ColumnTableDef\n")
1043+
}
1044+
d.Nullable.Nullability = tree.SilentNull
1045+
p.Defs = append(p.Defs, tableDef)
10111046
}
1012-
desc.AddColumn(col)
10131047
}
10141048

1015-
// AllocateIDs mutates its receiver. `return desc, desc.AllocateIDs()`
1016-
// happens to work in gc, but does not work in gccgo.
1017-
//
1018-
// See https://github.com/golang/go/issues/23188.
1019-
err = desc.AllocateIDs()
1049+
desc, err = makeTableDesc(
1050+
params,
1051+
p,
1052+
parentID, id,
1053+
creationTime,
1054+
privileges,
1055+
nil, /* affected */
1056+
)
1057+
desc.CreateQuery = getFinalSourceQuery(p.AsSource, evalContext)
10201058
return desc, err
10211059
}
10221060

pkg/sql/logictest/testdata/logic_test/create_as

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,95 @@ SELECT * FROM baz
144144
----
145145
a b c
146146
1 2 4
147+
148+
# Check that CREATE TABLE AS allows users to specify primary key (#20940)
149+
statement ok
150+
CREATE TABLE foo5 (a, b PRIMARY KEY, c) AS SELECT * FROM baz
151+
152+
query TT
153+
SHOW CREATE TABLE foo5
154+
----
155+
foo5 CREATE TABLE foo5 (
156+
a INT8 NULL,
157+
b INT8 NOT NULL,
158+
c INT8 NULL,
159+
CONSTRAINT "primary" PRIMARY KEY (b ASC),
160+
FAMILY "primary" (a, b, c)
161+
)
162+
163+
statement ok
164+
SET OPTIMIZER=ON; CREATE TABLE foo6 (a PRIMARY KEY, b, c) AS SELECT * FROM baz; SET OPTIMIZER=OFF
165+
166+
query TT
167+
SHOW CREATE TABLE foo6
168+
----
169+
foo6 CREATE TABLE foo6 (
170+
a INT8 NOT NULL,
171+
b INT8 NULL,
172+
c INT8 NULL,
173+
CONSTRAINT "primary" PRIMARY KEY (a ASC),
174+
FAMILY "primary" (a, b, c)
175+
)
176+
177+
statement error generate insert row: null value in column "x" violates not-null constraint
178+
CREATE TABLE foo7 (x PRIMARY KEY) AS VALUES (1), (NULL);
179+
180+
statement ok
181+
BEGIN; CREATE TABLE foo8 (item PRIMARY KEY, qty) AS SELECT * FROM stock UNION VALUES ('spoons', 25), ('knives', 50); END
182+
183+
query TT
184+
SHOW CREATE TABLE foo8
185+
----
186+
foo8 CREATE TABLE foo8 (
187+
item STRING NOT NULL,
188+
qty INT8 NULL,
189+
CONSTRAINT "primary" PRIMARY KEY (item ASC),
190+
FAMILY "primary" (item, qty)
191+
)
192+
193+
# Allow CREATE TABLE AS to specify composite primary keys.
194+
statement ok
195+
CREATE TABLE foo9 (a, b, c, PRIMARY KEY (a, c)) AS SELECT * FROM baz
196+
197+
query TT
198+
SHOW CREATE TABLE foo9
199+
----
200+
foo9 CREATE TABLE foo9 (
201+
a INT8 NOT NULL,
202+
b INT8 NULL,
203+
c INT8 NOT NULL,
204+
CONSTRAINT "primary" PRIMARY KEY (a ASC, c ASC),
205+
FAMILY "primary" (a, b, c)
206+
)
207+
208+
statement ok
209+
CREATE TABLE foo10 (a, PRIMARY KEY (c, b, a), b, c) AS SELECT * FROM foo9
210+
211+
query TT
212+
SHOW CREATE TABLE foo10
213+
----
214+
foo10 CREATE TABLE foo10 (
215+
a INT8 NOT NULL,
216+
b INT8 NOT NULL,
217+
c INT8 NOT NULL,
218+
CONSTRAINT "primary" PRIMARY KEY (c ASC, b ASC, a ASC),
219+
FAMILY "primary" (a, b, c)
220+
)
221+
222+
statement ok
223+
CREATE TABLE foo11 (x, y, z, PRIMARY KEY(x, z)) AS VALUES (1, 3, 4), (10, 20, 40);
224+
225+
query TT
226+
SHOW CREATE TABLE foo11
227+
----
228+
foo11 CREATE TABLE foo11 (
229+
x INT8 NOT NULL,
230+
y INT8 NULL,
231+
z INT8 NOT NULL,
232+
CONSTRAINT "primary" PRIMARY KEY (x ASC, z ASC),
233+
FAMILY "primary" (x, y, z)
234+
)
235+
236+
statement error pq: multiple primary keys for table "foo12" are not allowed
237+
CREATE TABLE foo12 (x PRIMARY KEY, y, PRIMARY KEY(y)) AS VALUES (1, 2), (3, 4);
238+

pkg/sql/opt/optbuilder/create_table.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,12 @@ func (b *Builder) buildCreateTable(ct *tree.CreateTable, inScope *scope) (outSco
4242
// Build the input query.
4343
outScope := b.buildSelect(ct.AsSource, nil /* desiredTypes */, inScope)
4444

45-
numColNames := len(ct.AsColumnNames)
45+
numColNames := 0
46+
for i := 0; i < len(ct.Defs); i++ {
47+
if _, ok := ct.Defs[i].(*tree.ColumnTableDef); ok {
48+
numColNames++
49+
}
50+
}
4651
numColumns := len(outScope.cols)
4752
if numColNames != 0 && numColNames != numColumns {
4853
panic(sqlbase.NewSyntaxError(fmt.Sprintf(
@@ -51,17 +56,20 @@ func (b *Builder) buildCreateTable(ct *tree.CreateTable, inScope *scope) (outSco
5156
numColumns, util.Pluralize(int64(numColumns)))))
5257
}
5358

54-
// Synthesize rowid column, and append to end of column list.
55-
props, overloads := builtins.GetBuiltinProperties("unique_rowid")
56-
private := &memo.FunctionPrivate{
57-
Name: "unique_rowid",
58-
Typ: types.Int,
59-
Properties: props,
60-
Overload: &overloads[0],
59+
input = outScope.expr
60+
if !ct.AsHasUserSpecifiedPrimaryKey() {
61+
// Synthesize rowid column, and append to end of column list.
62+
props, overloads := builtins.GetBuiltinProperties("unique_rowid")
63+
private := &memo.FunctionPrivate{
64+
Name: "unique_rowid",
65+
Typ: types.Int,
66+
Properties: props,
67+
Overload: &overloads[0],
68+
}
69+
fn := b.factory.ConstructFunction(memo.EmptyScalarListExpr, private)
70+
scopeCol := b.synthesizeColumn(outScope, "rowid", types.Int, nil /* expr */, fn)
71+
input = b.factory.CustomFuncs().ProjectExtraCol(outScope.expr, fn, scopeCol.id)
6172
}
62-
fn := b.factory.ConstructFunction(memo.EmptyScalarListExpr, private)
63-
scopeCol := b.synthesizeColumn(outScope, "rowid", types.Int, nil /* expr */, fn)
64-
input = b.factory.CustomFuncs().ProjectExtraCol(outScope.expr, fn, scopeCol.id)
6573
inputCols = outScope.makePhysicalProps().Presentation
6674
} else {
6775
// Create dummy empty input.

0 commit comments

Comments
 (0)