Skip to content

Commit d98e142

Browse files
committed
add analyzer fix to resolve create select and add tests
1 parent fff320b commit d98e142

File tree

3 files changed

+182
-2
lines changed

3 files changed

+182
-2
lines changed

enginetest/queries/script_queries.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,146 @@ type ScriptTestAssertion struct {
122122
// Unlike other engine tests, ScriptTests must be self-contained. No other tables are created outside the definition of
123123
// the tests.
124124
var ScriptTests = []ScriptTest{
125+
{
126+
// https://github.com/dolthub/dolt/issues/9316
127+
Name: "CREATE TABLE with constraints AS SELECT osticket repro",
128+
SetUpScript: []string{
129+
"CREATE TABLE ost_form_entry (id INT PRIMARY KEY, object_id INT, object_type VARCHAR(1))",
130+
"CREATE TABLE ost_form_entry_values (entry_id INT, field_id INT, value VARCHAR(100), value_id INT)",
131+
"CREATE TABLE ost_form_field (id INT PRIMARY KEY)",
132+
"INSERT INTO ost_form_entry VALUES (1, 100, 'U'), (2, 101, 'U'), (3, 102, 'X')",
133+
"INSERT INTO ost_form_entry_values VALUES (1, 1, '[email protected]', 1000), (2, 1, '[email protected]', 1001), (3, 2, 'other', 2000)",
134+
"INSERT INTO ost_form_field VALUES (1), (2)",
135+
},
136+
Assertions: []ScriptTestAssertion{
137+
{
138+
Query: `CREATE TABLE IF NOT EXISTS ost_user__cdata (
139+
PRIMARY KEY (user_id)
140+
) DEFAULT CHARSET=utf8 AS
141+
SELECT
142+
entry.object_id as user_id,
143+
MAX(IF(field.id='1',coalesce(ans.value_id, ans.value),NULL)) as email
144+
FROM ost_form_entry entry
145+
JOIN ost_form_entry_values ans
146+
ON ans.entry_id = entry.id
147+
JOIN ost_form_field field
148+
ON field.id=ans.field_id
149+
WHERE entry.object_type='U' GROUP BY entry.object_id`,
150+
},
151+
{
152+
Query: "SELECT * FROM ost_user__cdata ORDER BY user_id",
153+
Expected: []sql.Row{
154+
{100, "1000"},
155+
{101, "1001"},
156+
},
157+
},
158+
{
159+
Query: "SHOW KEYS FROM ost_user__cdata WHERE Key_name = 'PRIMARY'",
160+
Expected: []sql.Row{
161+
{"ost_user__cdata", 0, "PRIMARY", 1, "user_id", nil, 0, nil, nil, "YES", "BTREE", "", "", "YES", nil},
162+
},
163+
},
164+
},
165+
},
166+
{
167+
// https://github.com/dolthub/dolt/issues/9316
168+
Name: "CREATE TABLE with constraints AS SELECT",
169+
SetUpScript: []string{
170+
"CREATE TABLE t1 (a int not null, b varchar(10))",
171+
"INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three')",
172+
"CREATE TABLE source (id int, name varchar(20))",
173+
"INSERT INTO source VALUES (1, 'alice'), (2, 'bob'), (3, 'charlie')",
174+
"CREATE TABLE override_src (a bigint, b int)",
175+
"INSERT INTO override_src VALUES (100, 200)",
176+
"CREATE TABLE base (a int, b varchar(10))",
177+
"INSERT INTO base VALUES (1, 'alpha'), (2, 'beta')",
178+
"CREATE TABLE multi_src (id int, email varchar(50), age int)",
179+
"INSERT INTO multi_src VALUES (1, '[email protected]', 25), (2, '[email protected]', 30)",
180+
},
181+
Assertions: []ScriptTestAssertion{
182+
{
183+
Query: "CREATE TABLE t2 (PRIMARY KEY(a)) SELECT * FROM t1",
184+
},
185+
{
186+
Query: "SELECT * FROM t2 ORDER BY a",
187+
Expected: []sql.Row{
188+
{1, "one"},
189+
{2, "two"},
190+
{3, "three"},
191+
},
192+
},
193+
{
194+
Query: "SHOW KEYS FROM t2 WHERE Key_name = 'PRIMARY'",
195+
Expected: []sql.Row{
196+
{"t2", 0, "PRIMARY", 1, "a", nil, 0, nil, nil, "", "BTREE", "", "", "YES", nil},
197+
},
198+
},
199+
{
200+
Query: "CREATE TABLE indexed (KEY(name)) SELECT * FROM source",
201+
},
202+
{
203+
Query: "SELECT * FROM indexed ORDER BY id",
204+
Expected: []sql.Row{
205+
{1, "alice"},
206+
{2, "bob"},
207+
{3, "charlie"},
208+
},
209+
},
210+
{
211+
Query: "SHOW KEYS FROM indexed WHERE Key_name = 'name'",
212+
Expected: []sql.Row{
213+
{"indexed", 1, "name", 1, "name", nil, 0, nil, nil, "YES", "BTREE", "", "", "YES", nil},
214+
},
215+
},
216+
{
217+
Query: "CREATE TABLE override (a TINYINT NOT NULL) SELECT a, b FROM override_src",
218+
},
219+
{
220+
Query: "SELECT * FROM override",
221+
Expected: []sql.Row{
222+
{int8(100), 200},
223+
},
224+
},
225+
{
226+
Query: "SHOW CREATE TABLE override",
227+
Expected: []sql.Row{
228+
{"override", "CREATE TABLE `override` (\n `a` tinyint NOT NULL,\n `b` int\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
229+
},
230+
},
231+
{
232+
Query: "CREATE TABLE uniq (UNIQUE KEY(a)) SELECT * FROM base",
233+
},
234+
{
235+
Query: "SELECT * FROM uniq ORDER BY a",
236+
Expected: []sql.Row{
237+
{1, "alpha"},
238+
{2, "beta"},
239+
},
240+
},
241+
{
242+
Query: "SHOW KEYS FROM uniq WHERE Key_name = 'a'",
243+
Expected: []sql.Row{
244+
{"uniq", 0, "a", 1, "a", nil, 0, nil, nil, "YES", "BTREE", "", "", "YES", nil},
245+
},
246+
},
247+
{
248+
Query: "CREATE TABLE multi_idx (PRIMARY KEY(id), KEY(email), KEY(age)) SELECT * FROM multi_src",
249+
},
250+
{
251+
Query: "SELECT * FROM multi_idx ORDER BY id",
252+
Expected: []sql.Row{
253+
{1, "[email protected]", 25},
254+
{2, "[email protected]", 30},
255+
},
256+
},
257+
{
258+
Query: "SELECT COUNT(*) FROM information_schema.statistics WHERE table_name = 'multi_idx'",
259+
Expected: []sql.Row{
260+
{3},
261+
},
262+
},
263+
},
264+
},
125265
{
126266
// https://github.com/dolthub/dolt/issues/9935
127267
Dialect: "mysql",

sql/analyzer/resolve_create_select.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,48 @@ func resolveCreateSelect(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.
3838
newSch[i] = &tempCol
3939
}
4040

41+
// Apply primary key constraints from index definitions to the merged schema
4142
pkOrdinals := make([]int, 0)
43+
var nonPkIndexes sql.IndexDefs
44+
for _, idx := range ct.Indexes() {
45+
if idx.IsPrimary() {
46+
for _, idxCol := range idx.Columns {
47+
for i, schCol := range newSch {
48+
if schCol.Name == idxCol.Name {
49+
newSch[i].PrimaryKey = true
50+
pkOrdinals = append(pkOrdinals, i)
51+
break
52+
}
53+
}
54+
}
55+
} else {
56+
// Keep non-primary key indexes
57+
nonPkIndexes = append(nonPkIndexes, idx)
58+
}
59+
}
60+
61+
// Also check for columns already marked as primary key
4262
for i, col := range newSch {
4363
if col.PrimaryKey {
44-
pkOrdinals = append(pkOrdinals, i)
64+
found := false
65+
for _, pk := range pkOrdinals {
66+
if pk == i {
67+
found = true
68+
break
69+
}
70+
}
71+
if !found {
72+
pkOrdinals = append(pkOrdinals, i)
73+
}
4574
}
4675
}
4776

4877
newSpec := &plan.TableSpec{
49-
Schema: sql.NewPrimaryKeySchema(newSch, pkOrdinals...),
78+
Schema: sql.NewPrimaryKeySchema(newSch, pkOrdinals...),
79+
IdxDefs: nonPkIndexes, // Only pass non-PK indexes since PK is in schema
80+
FkDefs: ct.ForeignKeys(),
81+
ChDefs: ct.Checks(),
82+
Collation: ct.Collation,
5083
}
5184

5285
newCreateTable := plan.NewCreateTable(ct.Database(), ct.Name(), ct.IfNotExists(), ct.Temporary(), newSpec)

sql/analyzer/validate_create_table.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ func validateCreateTable(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.
3838
return nil, transform.SameTree, err
3939
}
4040

41+
// For CREATE TABLE AS SELECT, skip validation here because the schema isn't complete yet.
42+
// The resolveCreateSelect analyzer rule will merge schemas and create a new CreateTable node,
43+
// which will be validated separately after the merge.
44+
if ct.Select() != nil {
45+
return n, transform.SameTree, nil
46+
}
47+
4148
sch := ct.PkSchema().Schema
4249
idxs := ct.Indexes()
4350

0 commit comments

Comments
 (0)