Skip to content

Commit f8333e7

Browse files
committed
sql: Refactors CTAS logic to allow user defined PKs.
This change removes the need for a separate `AsColumnNames` field in the CreateTable node, and uses `ColumnTableDefs` instead. This is similar to a normal CREATE TABLE query, thereby allowing us to piggyback on most of the existing logic. It ensures `row_id` key generation only occurs if no PK is explicitly specified. Some special handling is required in pretty printing, as column types are not populated at the time of parsing. Release note: None
1 parent a111dac commit f8333e7

File tree

5 files changed

+244
-63
lines changed

5 files changed

+244
-63
lines changed

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

@@ -975,38 +1000,51 @@ func getFinalSourceQuery(source *tree.Select, evalCtx *tree.EvalContext) string
9751000
// makeTableDescIfAs is the MakeTableDesc method for when we have a table
9761001
// that is created with the CREATE AS format.
9771002
func makeTableDescIfAs(
1003+
params runParams,
9781004
p *tree.CreateTable,
9791005
parentID, id sqlbase.ID,
9801006
creationTime hlc.Timestamp,
9811007
resultColumns []sqlbase.ResultColumn,
9821008
privileges *sqlbase.PrivilegeDescriptor,
983-
semaCtx *tree.SemaContext,
9841009
evalContext *tree.EvalContext,
9851010
) (desc sqlbase.MutableTableDescriptor, err error) {
986-
desc = InitTableDescriptor(id, parentID, p.Table.Table(), creationTime, privileges)
987-
desc.CreateQuery = getFinalSourceQuery(p.AsSource, evalContext)
988-
989-
for i, colRes := range resultColumns {
990-
columnTableDef := tree.ColumnTableDef{Name: tree.Name(colRes.Name), Type: colRes.Typ}
991-
columnTableDef.Nullable.Nullability = tree.SilentNull
992-
if len(p.AsColumnNames) > i {
993-
columnTableDef.Name = p.AsColumnNames[i]
994-
}
995-
996-
// The new types in the CREATE TABLE AS column specs never use
997-
// SERIAL so we need not process SERIAL types here.
998-
col, _, _, err := sqlbase.MakeColumnDefDescs(&columnTableDef, semaCtx)
999-
if err != nil {
1000-
return desc, err
1011+
colResIndex := 0
1012+
// TableDefs for a CREATE TABLE ... AS AST node comprise of a ColumnTableDef
1013+
// for each column, and a ConstraintTableDef for any constraints on those
1014+
// columns.
1015+
for _, defs := range p.Defs {
1016+
var d *tree.ColumnTableDef
1017+
var ok bool
1018+
if d, ok = defs.(*tree.ColumnTableDef); ok {
1019+
d.Type = resultColumns[colResIndex].Typ
1020+
colResIndex++
1021+
}
1022+
}
1023+
1024+
// If there are no TableDefs defined by the parser, then we construct a
1025+
// ColumnTableDef for each column using resultColumns.
1026+
if len(p.Defs) == 0 {
1027+
for _, colRes := range resultColumns {
1028+
var d *tree.ColumnTableDef
1029+
var ok bool
1030+
var tableDef tree.TableDef = &tree.ColumnTableDef{Name: tree.Name(colRes.Name), Type: colRes.Typ}
1031+
if d, ok = tableDef.(*tree.ColumnTableDef); !ok {
1032+
return desc, errors.Errorf("failed to cast type to ColumnTableDef\n")
1033+
}
1034+
d.Nullable.Nullability = tree.SilentNull
1035+
p.Defs = append(p.Defs, tableDef)
10011036
}
1002-
desc.AddColumn(col)
10031037
}
10041038

1005-
// AllocateIDs mutates its receiver. `return desc, desc.AllocateIDs()`
1006-
// happens to work in gc, but does not work in gccgo.
1007-
//
1008-
// See https://github.com/golang/go/issues/23188.
1009-
err = desc.AllocateIDs()
1039+
desc, err = makeTableDesc(
1040+
params,
1041+
p,
1042+
parentID, id,
1043+
creationTime,
1044+
privileges,
1045+
nil, /* affected */
1046+
)
1047+
desc.CreateQuery = getFinalSourceQuery(p.AsSource, evalContext)
10101048
return desc, err
10111049
}
10121050

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
@@ -41,7 +41,12 @@ func (b *Builder) buildCreateTable(ct *tree.CreateTable, inScope *scope) (outSco
4141
// Build the input query.
4242
outScope := b.buildSelect(ct.AsSource, nil /* desiredTypes */, inScope)
4343

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

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

pkg/sql/sem/tree/create.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,14 @@ func (node *ColumnTableDef) HasColumnFamily() bool {
356356
// Format implements the NodeFormatter interface.
357357
func (node *ColumnTableDef) Format(ctx *FmtCtx) {
358358
ctx.FormatNode(&node.Name)
359-
ctx.WriteByte(' ')
360-
ctx.WriteString(node.columnTypeString())
359+
360+
// ColumnTableDef node type will not be specified if it represents a CREATE
361+
// TABLE ... AS query.
362+
if node.Type != nil {
363+
ctx.WriteByte(' ')
364+
ctx.WriteString(node.columnTypeString())
365+
}
366+
361367
if node.Nullable.Nullability != SilentNull && node.Nullable.ConstraintName != "" {
362368
ctx.WriteString(" CONSTRAINT ")
363369
ctx.FormatNode(&node.Nullable.ConstraintName)
@@ -885,13 +891,15 @@ func (node *RangePartition) Format(ctx *FmtCtx) {
885891

886892
// CreateTable represents a CREATE TABLE statement.
887893
type CreateTable struct {
888-
IfNotExists bool
889-
Table TableName
890-
Interleave *InterleaveDef
891-
PartitionBy *PartitionBy
892-
Defs TableDefs
893-
AsSource *Select
894-
AsColumnNames NameList // Only to be used in conjunction with AsSource
894+
IfNotExists bool
895+
Table TableName
896+
Interleave *InterleaveDef
897+
PartitionBy *PartitionBy
898+
// In CREATE...AS queries, Defs represents a list of ColumnTableDefs, one for
899+
// each column, and a ConstraintTableDef for each constraint on a subset of
900+
// these columns.
901+
Defs TableDefs
902+
AsSource *Select
895903
}
896904

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

911+
// AsHasUserSpecifiedPrimaryKey returns true if a CREATE TABLE ... AS statement
912+
// has a PRIMARY KEY constraint specified.
913+
func (node *CreateTable) AsHasUserSpecifiedPrimaryKey() bool {
914+
if node.As() {
915+
for _, def := range node.Defs {
916+
if d, ok := def.(*ColumnTableDef); !ok {
917+
return false
918+
} else if d.PrimaryKey {
919+
return true
920+
}
921+
}
922+
}
923+
return false
924+
}
925+
903926
// Format implements the NodeFormatter interface.
904927
func (node *CreateTable) Format(ctx *FmtCtx) {
905928
ctx.WriteString("CREATE TABLE ")
@@ -914,9 +937,9 @@ func (node *CreateTable) Format(ctx *FmtCtx) {
914937
// but the CREATE TABLE tableName part.
915938
func (node *CreateTable) FormatBody(ctx *FmtCtx) {
916939
if node.As() {
917-
if len(node.AsColumnNames) > 0 {
940+
if len(node.Defs) > 0 {
918941
ctx.WriteString(" (")
919-
ctx.FormatNode(&node.AsColumnNames)
942+
ctx.FormatNode(&node.Defs)
920943
ctx.WriteByte(')')
921944
}
922945
ctx.WriteString(" AS ")

0 commit comments

Comments
 (0)