Skip to content

Commit 1a756a7

Browse files
author
Vinai Rachakonda
authored
Vinai/create table select (#449)
This table adds basic functionality for CREATE TABLE SELECT
1 parent 744269b commit 1a756a7

File tree

11 files changed

+397
-11
lines changed

11 files changed

+397
-11
lines changed

enginetest/enginetests.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,34 @@ func TestCreateTable(t *testing.T, harness Harness) {
13431343
require.True(t, indexFound)
13441344
})
13451345

1346+
t.Run("CREATE TABLE (SELECT * )", func(t *testing.T) {
1347+
TestQuery(t, harness, e, "CREATE TABLE t10 (a INTEGER NOT NULL PRIMARY KEY, "+
1348+
"b VARCHAR(10))", []sql.Row(nil), nil, nil)
1349+
TestQuery(t, harness, e, `INSERT INTO t10 VALUES (1, "1"), (2, "2")`, []sql.Row{sql.Row{sql.OkResult{RowsAffected: 0x2, InsertID: 0x0, Info: fmt.Stringer(nil)}}}, nil, nil)
1350+
1351+
// Create the table with the data from t10
1352+
TestQuery(t, harness, e, "CREATE TABLE t10a SELECT * from t10", []sql.Row{sql.Row{sql.OkResult{RowsAffected: 0x2, InsertID: 0x0, Info: fmt.Stringer(nil)}}}, nil, nil)
1353+
1354+
db, err := e.Catalog.Database("mydb")
1355+
require.NoError(t, err)
1356+
1357+
t10Table, ok, err := db.GetTableInsensitive(ctx, "t10")
1358+
require.NoError(t, err)
1359+
require.True(t, ok)
1360+
t10aTable, ok, err := db.GetTableInsensitive(ctx, "t10a")
1361+
require.NoError(t, err)
1362+
require.True(t, ok)
1363+
1364+
require.Equal(t, sql.Schema{
1365+
{Name: "a", Type: sql.Int32, Nullable: false, PrimaryKey: true, Source: "t10"},
1366+
{Name: "b", Type: sql.MustCreateStringWithDefaults(sqltypes.VarChar, 10), Nullable: true, Source: "t10"},
1367+
}, t10Table.Schema())
1368+
require.Equal(t, sql.Schema{
1369+
{Name: "a", Type: sql.Int32, Nullable: false, PrimaryKey: true, Source: "t10a"},
1370+
{Name: "b", Type: sql.MustCreateStringWithDefaults(sqltypes.VarChar, 10), Nullable: true, Source: "t10a"},
1371+
}, t10aTable.Schema())
1372+
})
1373+
13461374
//TODO: Implement "CREATE TABLE otherDb.tableName"
13471375
}
13481376

enginetest/script_queries.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,44 @@ var ScriptTests = []ScriptTest{
942942
},
943943
},
944944
},
945+
{
946+
Name: "CREATE TABLE SELECT Queries",
947+
SetUpScript: []string{
948+
`CREATE TABLE t1 (pk int PRIMARY KEY, v1 varchar(10))`,
949+
`INSERT INTO t1 VALUES (1,"1"), (2,"2"), (3,"3")`,
950+
`CREATE TABLE t2 AS SELECT * FROM t1`,
951+
//`CREATE TABLE t3(v0 int) AS SELECT pk FROM t1`, // parser problems
952+
`CREATE TABLE t3 AS SELECT pk FROM t1`,
953+
`CREATE TABLE t4 AS SELECT pk, v1 FROM t1`,
954+
`CREATE TABLE t5 SELECT * FROM t1 ORDER BY pk LIMIT 1`,
955+
},
956+
Assertions: []ScriptTestAssertion{
957+
{
958+
Query: `SELECT * FROM t2`,
959+
Expected: []sql.Row{{1, "1"}, {2, "2"}, {3, "3"}},
960+
},
961+
{
962+
Query: `SELECT * FROM t3`,
963+
Expected: []sql.Row{{1}, {2}, {3}},
964+
},
965+
{
966+
Query: `SELECT * FROM t4`,
967+
Expected: []sql.Row{{1, "1"}, {2, "2"}, {3, "3"}},
968+
},
969+
{
970+
Query: `SELECT * FROM t5`,
971+
Expected: []sql.Row{{1, "1"}},
972+
},
973+
{
974+
Query: `CREATE TABLE test SELECT * FROM t1`,
975+
Expected: []sql.Row{sql.Row{sql.OkResult{
976+
RowsAffected: 3,
977+
InsertID: 0,
978+
Info: nil,
979+
}}},
980+
},
981+
},
982+
},
945983
}
946984

947985
var CreateCheckConstraintsScripts = []ScriptTest{

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
8383
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
8484
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
8585
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
86-
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
8786
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
8887
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
8988
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -149,7 +148,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
149148
gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc=
150149
gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg=
151150
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
152-
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
153151
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
154152
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
155153
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

sql/analyzer/resolve_create_select.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package analyzer
2+
3+
import (
4+
"github.com/dolthub/go-mysql-server/sql"
5+
"github.com/dolthub/go-mysql-server/sql/plan"
6+
)
7+
8+
func resolveCreateSelect(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope) (sql.Node, error) {
9+
planCreate, ok := n.(*plan.CreateTable)
10+
if !ok || planCreate.Select() == nil {
11+
return n, nil
12+
}
13+
14+
analyzedSelect, err := a.Analyze(ctx, planCreate.Select(), scope)
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
// Get the correct schema of the CREATE TABLE based on the select query
20+
inputSpec := planCreate.TableSpec()
21+
selectSchema := analyzedSelect.Schema()
22+
mergedSchema := mergeSchemas(inputSpec.Schema, selectSchema)
23+
newSch := make(sql.Schema, len(mergedSchema))
24+
25+
for i, col := range mergedSchema {
26+
tempCol := *col
27+
tempCol.Source = planCreate.Name()
28+
newSch[i] = &tempCol
29+
}
30+
31+
newSpec := inputSpec.WithSchema(newSch)
32+
33+
newCreateTable := plan.NewCreateTable(planCreate.Database(), planCreate.Name(), planCreate.IfNotExists(), planCreate.Temporary(), newSpec)
34+
analyzedCreate, err := a.Analyze(ctx, newCreateTable, scope)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
return plan.NewTableCopier(planCreate.Database(), stripQueryProcess(analyzedCreate), stripQueryProcess(analyzedSelect), plan.CopierProps{}), nil
40+
}
41+
42+
// mergeSchemas takes in the table spec of the CREATE TABLE and merges it with the schema used by the
43+
// select query. The ultimate structure for the new table will be [CREATE TABLE exclusive columns, columns with the same
44+
// name, SELECT exclusive columns]
45+
func mergeSchemas(inputSchema sql.Schema, selectSchema sql.Schema) sql.Schema {
46+
if inputSchema == nil {
47+
return selectSchema
48+
}
49+
50+
// Get the matching columns between the two via name
51+
matchingColumns := make([]*sql.Column, 0)
52+
leftExclusive := make([]*sql.Column, 0)
53+
for _, col := range inputSchema {
54+
found := false
55+
for _, col2 := range selectSchema {
56+
if col.Name == col2.Name {
57+
matchingColumns = append(matchingColumns, col)
58+
found = true
59+
}
60+
}
61+
62+
if !found {
63+
leftExclusive = append(leftExclusive, col)
64+
}
65+
}
66+
67+
rightExclusive := make([]*sql.Column, 0)
68+
for _, col := range selectSchema {
69+
found := false
70+
for _, col2 := range inputSchema {
71+
if col.Name == col2.Name {
72+
found = true
73+
break
74+
}
75+
}
76+
77+
if !found {
78+
rightExclusive = append(rightExclusive, col)
79+
}
80+
}
81+
82+
return append(append(leftExclusive, matchingColumns...), rightExclusive...)
83+
}

sql/analyzer/rules.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ var OnceBeforeDefault = []Rule{
3232
{"load_check_constraints", loadChecks},
3333
{"resolve_set_variables", resolveSetVariables},
3434
{"resolve_create_like", resolveCreateLike},
35+
{"resolve_create_select", resolveCreateSelect},
3536
{"resolve_subqueries", resolveSubqueries},
3637
{"resolve_unions", resolveUnions},
3738
{"resolve_describe_query", resolveDescribeQuery},

sql/core.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,13 @@ type TemporaryTableDatabase interface {
569569
GetAllTemporaryTables(ctx *Context) ([]Table, error)
570570
}
571571

572+
// TableCopierDatabase is a database that can copy a source table's data (without preserving indexed, fks, etc.) into
573+
// another destination table.
574+
type TableCopierDatabase interface {
575+
// CopyTableData copies the sourceTable data to the destinationTable and returns the number of rows copied.
576+
CopyTableData(ctx *Context, sourceTable string, destinationTable string) (uint64, error)
577+
}
578+
572579
// GetTableInsensitive implements a case insensitive map lookup for tables keyed off of the table name.
573580
// Looks for exact matches first. If no exact matches are found then any table matching the name case insensitively
574581
// should be returned. If there is more than one table that matches a case insensitive comparison the resolution

sql/errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,10 @@ var (
289289
// ErrInvalidSyntax is returned for syntax errors that aren't picked up by the parser, e.g. the wrong type of
290290
// expression used in part of statement.
291291
ErrInvalidSyntax = errors.NewKind("Invalid syntax: %s")
292+
293+
// ErrTableCopyingNotSupported is returned when a table invokes the TableCopierDatabase interface's
294+
// CopyTableData method without supporting the interface
295+
ErrTableCopyingNotSupported = errors.NewKind("error: Table copying not supported")
292296
)
293297

294298
func CastSQLError(err error) (*mysql.SQLError, bool) {

sql/parse/parse.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,19 @@ func convertCreateTable(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
12271227
), nil
12281228
}
12291229

1230+
// In the case that no table spec is given but a SELECT Statement return the CREATE TABLE noder.
1231+
// if the table spec != nil it will get parsed below.
1232+
if c.TableSpec == nil && c.OptSelect != nil {
1233+
tableSpec := &plan.TableSpec{}
1234+
1235+
selectNode, err := convertSelectStatement(ctx, c.OptSelect.Select)
1236+
if err != nil {
1237+
return nil, err
1238+
}
1239+
1240+
return plan.NewCreateTableSelect(sql.UnresolvedDatabase(c.Table.Qualifier.String()), c.Table.Name.String(), selectNode, tableSpec, plan.IfNotExistsOption(c.IfNotExists), plan.TempTableOption(c.Temporary)), nil
1241+
}
1242+
12301243
schema, err := TableSpecToSchema(nil, c.TableSpec)
12311244
if err != nil {
12321245
return nil, err
@@ -1321,6 +1334,15 @@ func convertCreateTable(ctx *sql.Context, c *sqlparser.DDL) (sql.Node, error) {
13211334
ChDefs: chDefs,
13221335
}
13231336

1337+
if c.OptSelect != nil {
1338+
selectNode, err := convertSelectStatement(ctx, c.OptSelect.Select)
1339+
if err != nil {
1340+
return nil, err
1341+
}
1342+
1343+
return plan.NewCreateTableSelect(sql.UnresolvedDatabase(qualifier), c.Table.Name.String(), selectNode, tableSpec, plan.IfNotExistsOption(c.IfNotExists), plan.TempTableOption(c.Temporary)), nil
1344+
}
1345+
13241346
return plan.NewCreateTable(
13251347
sql.UnresolvedDatabase(qualifier), c.Table.Name.String(), plan.IfNotExistsOption(c.IfNotExists), plan.TempTableOption(c.Temporary), tableSpec), nil
13261348
}

sql/parse/parse_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,13 @@ CREATE TABLE t2
842842
}},
843843
},
844844
),
845+
`CREATE TEMPORARY TABLE mytable AS SELECT * from othertable`: plan.NewCreateTableSelect(
846+
sql.UnresolvedDatabase(""),
847+
"mytable",
848+
plan.NewProject([]sql.Expression{expression.NewStar()}, plan.NewUnresolvedTable("othertable", "")),
849+
&plan.TableSpec{},
850+
plan.IfNotExistsAbsent,
851+
plan.IsTempTable),
845852
`DROP TABLE foo;`: plan.NewDropTable(
846853
sql.UnresolvedDatabase(""), false, "foo",
847854
),

sql/plan/ddl.go

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,28 +98,28 @@ type TableSpec struct {
9898
IdxDefs []*IndexDefinition
9999
}
100100

101-
func (c *TableSpec) WithSchema(schema sql.Schema) (*TableSpec, error) {
101+
func (c *TableSpec) WithSchema(schema sql.Schema) *TableSpec {
102102
nc := *c
103103
nc.Schema = schema
104-
return &nc, nil
104+
return &nc
105105
}
106106

107-
func (c *TableSpec) WithForeignKeys(fkDefs []*sql.ForeignKeyConstraint) (*TableSpec, error) {
107+
func (c *TableSpec) WithForeignKeys(fkDefs []*sql.ForeignKeyConstraint) *TableSpec {
108108
nc := *c
109109
nc.FkDefs = fkDefs
110-
return &nc, nil
110+
return &nc
111111
}
112112

113-
func (c *TableSpec) WithCheckConstraints(chDefs []*sql.CheckConstraint) (*TableSpec, error) {
113+
func (c *TableSpec) WithCheckConstraints(chDefs []*sql.CheckConstraint) *TableSpec {
114114
nc := *c
115115
nc.ChDefs = chDefs
116-
return &nc, nil
116+
return &nc
117117
}
118118

119-
func (c *TableSpec) WithIndices(idxDefs []*IndexDefinition) (*TableSpec, error) {
119+
func (c *TableSpec) WithIndices(idxDefs []*IndexDefinition) *TableSpec {
120120
nc := *c
121121
nc.IdxDefs = idxDefs
122-
return &nc, nil
122+
return &nc
123123
}
124124

125125
// CreateTable is a node describing the creation of some table.
@@ -133,6 +133,7 @@ type CreateTable struct {
133133
idxDefs []*IndexDefinition
134134
like sql.Node
135135
temporary TempTableOption
136+
selectNode sql.Node
136137
}
137138

138139
var _ sql.Databaser = (*CreateTable)(nil)
@@ -168,6 +169,25 @@ func NewCreateTableLike(db sql.Database, name string, likeTable sql.Node, ifn If
168169
}
169170
}
170171

172+
// NewCreateTableSelect create a new CreateTable node for CREATE TABLE [AS] SELECT
173+
func NewCreateTableSelect(db sql.Database, name string, selectNode sql.Node, tableSpec *TableSpec, ifn IfNotExistsOption, temp TempTableOption) *CreateTable {
174+
for _, s := range tableSpec.Schema {
175+
s.Source = name
176+
}
177+
178+
return &CreateTable{
179+
ddlNode: ddlNode{db: db},
180+
schema: tableSpec.Schema,
181+
fkDefs: tableSpec.FkDefs,
182+
chDefs: tableSpec.ChDefs,
183+
idxDefs: tableSpec.IdxDefs,
184+
name: name,
185+
selectNode: selectNode,
186+
ifNotExists: ifn,
187+
temporary: temp,
188+
}
189+
}
190+
171191
// WithDatabase implements the sql.Databaser interface.
172192
func (c *CreateTable) WithDatabase(db sql.Database) (sql.Node, error) {
173193
nc := *c
@@ -317,6 +337,8 @@ func (c *CreateTable) createChecks(ctx *sql.Context, tableNode sql.Table) error
317337
func (c *CreateTable) Children() []sql.Node {
318338
if c.like != nil {
319339
return []sql.Node{c.like}
340+
} else if c.selectNode != nil {
341+
return []sql.Node{c.selectNode}
320342
}
321343
return nil
322344
}
@@ -326,8 +348,16 @@ func (c *CreateTable) WithChildren(children ...sql.Node) (sql.Node, error) {
326348
if len(children) == 0 {
327349
return c, nil
328350
} else if len(children) == 1 {
351+
child := children[0]
329352
nc := *c
330-
nc.like = children[0]
353+
354+
switch child.(type) {
355+
case *Project, *Limit:
356+
nc.selectNode = child
357+
default:
358+
nc.like = child
359+
}
360+
331361
return &nc, nil
332362
} else {
333363
return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), 1)
@@ -429,6 +459,21 @@ func (c *CreateTable) Like() sql.Node {
429459
return c.like
430460
}
431461

462+
func (c *CreateTable) Select() sql.Node {
463+
return c.selectNode
464+
}
465+
466+
func (c *CreateTable) TableSpec() *TableSpec {
467+
tableSpec := TableSpec{}
468+
469+
ret := tableSpec.WithSchema(c.schema)
470+
ret = tableSpec.WithForeignKeys(c.fkDefs)
471+
ret = tableSpec.WithIndices(c.idxDefs)
472+
ret = tableSpec.WithCheckConstraints(c.chDefs)
473+
474+
return ret
475+
}
476+
432477
func (c *CreateTable) Name() string {
433478
return c.name
434479
}

0 commit comments

Comments
 (0)