Skip to content

Commit 0f1cfb7

Browse files
authored
Merge pull request #2345 from dolthub/nicktobey/mysql-errors
Return correct MySQL error code when inserting into nonexistent columns.
2 parents c79a923 + 5826e68 commit 0f1cfb7

File tree

6 files changed

+86
-13
lines changed

6 files changed

+86
-13
lines changed

enginetest/queries/load_queries.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"time"
2020

2121
"github.com/dolthub/go-mysql-server/sql"
22-
"github.com/dolthub/go-mysql-server/sql/plan"
2322
)
2423

2524
var LoadDataScripts = []ScriptTest{
@@ -279,15 +278,15 @@ var LoadDataErrorScripts = []ScriptTest{
279278
Assertions: []ScriptTestAssertion{
280279
{
281280
Query: "LOAD DATA INFILE './testdata/test1.txt' INTO TABLE loadtable FIELDS ENCLOSED BY '\"' (fake_col, pk, i)",
282-
ExpectedErr: plan.ErrInsertIntoNonexistentColumn,
281+
ExpectedErr: sql.ErrUnknownColumn,
283282
},
284283
{
285284
Query: "LOAD DATA INFILE './testdata/test1.txt' INTO TABLE loadtable FIELDS ENCLOSED BY '\"' (pk, fake_col, i)",
286-
ExpectedErr: plan.ErrInsertIntoNonexistentColumn,
285+
ExpectedErr: sql.ErrUnknownColumn,
287286
},
288287
{
289288
Query: "LOAD DATA INFILE './testdata/test1.txt' INTO TABLE loadtable FIELDS ENCLOSED BY '\"' (pk, i, fake_col)",
290-
ExpectedErr: plan.ErrInsertIntoNonexistentColumn,
289+
ExpectedErr: sql.ErrUnknownColumn,
291290
},
292291
},
293292
},

server/handler_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,76 @@ func TestHandlerOutput(t *testing.T) {
212212
})
213213
}
214214

215+
func TestHandlerErrors(t *testing.T) {
216+
e, pro := setupMemDB(require.New(t))
217+
dbFunc := pro.Database
218+
219+
dummyConn := newConn(1)
220+
handler := &Handler{
221+
e: e,
222+
sm: NewSessionManager(
223+
testSessionBuilder(pro),
224+
sql.NoopTracer,
225+
dbFunc,
226+
sql.NewMemoryManager(nil),
227+
sqle.NewProcessList(),
228+
"foo",
229+
),
230+
readTimeout: time.Second,
231+
}
232+
handler.NewConnection(dummyConn)
233+
234+
type expectedValues struct {
235+
callsToCallback int
236+
lenLastBatch int
237+
lastRowsAffected uint64
238+
}
239+
240+
setupCommands := []string{"CREATE TABLE `test_table` ( `id` INT NOT NULL PRIMARY KEY, `v` INT );"}
241+
242+
tests := []struct {
243+
name string
244+
query string
245+
expectedErrorCode int
246+
}{
247+
{
248+
name: "insert with nonexistent field name",
249+
query: "INSERT INTO `test_table` (`id`, `v_`) VALUES (1, 2)",
250+
expectedErrorCode: mysql.ERBadFieldError,
251+
},
252+
{
253+
name: "insert into nonexistent table",
254+
query: "INSERT INTO `test`.`no_such_table` (`id`, `v`) VALUES (1, 2)",
255+
expectedErrorCode: mysql.ERNoSuchTable,
256+
},
257+
{
258+
name: "insert into same column twice",
259+
query: "INSERT INTO `test`.`test_table` (`id`, `id`, `v`) VALUES (1, 2, 3)",
260+
expectedErrorCode: mysql.ERFieldSpecifiedTwice,
261+
},
262+
}
263+
264+
handler.ComInitDB(dummyConn, "test")
265+
for _, setupCommand := range setupCommands {
266+
err := handler.ComQuery(dummyConn, setupCommand, func(res *sqltypes.Result, more bool) error {
267+
return nil
268+
})
269+
require.NoError(t, err)
270+
}
271+
272+
for _, test := range tests {
273+
t.Run(test.name, func(t *testing.T) {
274+
err := handler.ComQuery(dummyConn, test.query, func(res *sqltypes.Result, more bool) error {
275+
return nil
276+
})
277+
require.NotNil(t, err)
278+
sqlErr, isSqlError := err.(*mysql.SQLError)
279+
require.True(t, isSqlError)
280+
require.Equal(t, test.expectedErrorCode, sqlErr.Number())
281+
})
282+
}
283+
}
284+
215285
// TestHandlerComReset asserts that the Handler.ComResetConnection method correctly clears all session
216286
// state (e.g. table locks, prepared statements, user variables, session variables), and keeps the current
217287
// database selected.

sql/errors.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,8 @@ var (
883883
ErrInsertIntoMismatchValueCount = errors.NewKind("number of values does not match number of columns provided")
884884

885885
ErrInvalidTypeForLimit = errors.NewKind("invalid limit. expected %T, found %T")
886+
887+
ErrColumnSpecifiedTwice = errors.NewKind("column '%v' specified twice")
886888
)
887889

888890
// CastSQLError returns a *mysql.SQLError with the error code and in some cases, also a SQL state, populated for the
@@ -949,6 +951,10 @@ func CastSQLError(err error) *mysql.SQLError {
949951
code = 1553 // TODO: Needs to be added to vitess
950952
case ErrInvalidValue.Is(err):
951953
code = mysql.ERTruncatedWrongValueForField
954+
case ErrUnknownColumn.Is(err):
955+
code = mysql.ERBadFieldError
956+
case ErrColumnSpecifiedTwice.Is(err):
957+
code = mysql.ERFieldSpecifiedTwice
952958
case ErrLockDeadlock.Is(err):
953959
// ER_LOCK_DEADLOCK signals that the transaction was rolled back
954960
// due to a deadlock between concurrent transactions.

sql/plan/insert.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ var ErrReplaceIntoNotSupported = errors.NewKind("table doesn't support REPLACE I
2929
var ErrOnDuplicateKeyUpdateNotSupported = errors.NewKind("table doesn't support ON DUPLICATE KEY UPDATE")
3030
var ErrAutoIncrementNotSupported = errors.NewKind("table doesn't support AUTO_INCREMENT")
3131
var ErrInsertIntoUnsupportedValues = errors.NewKind("%T is unsupported for inserts")
32-
var ErrInsertIntoDuplicateColumn = errors.NewKind("duplicate column name %v")
33-
var ErrInsertIntoNonexistentColumn = errors.NewKind("invalid column name %v")
3432
var ErrInsertIntoIncompatibleTypes = errors.NewKind("cannot convert type %s to %s")
3533

3634
// cc: https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sql-mode-strict

sql/planbuilder/dml.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (b *Builder) buildInsert(inScope *scope, i *ast.Insert) (outScope *scope) {
8181
if rt != nil {
8282
sch = b.resolveSchemaDefaults(destScope, rt.Schema())
8383
}
84-
srcScope := b.insertRowsToNode(inScope, i.Rows, columns, sch)
84+
srcScope := b.insertRowsToNode(inScope, i.Rows, columns, tableName, sch)
8585

8686
// TODO: on duplicate expressions need to reference both VALUES and
8787
// derived columns equally in ON DUPLICATE UPDATE expressions.
@@ -120,20 +120,20 @@ func (b *Builder) buildInsert(inScope *scope, i *ast.Insert) (outScope *scope) {
120120
return
121121
}
122122

123-
func (b *Builder) insertRowsToNode(inScope *scope, ir ast.InsertRows, columnNames []string, destSchema sql.Schema) (outScope *scope) {
123+
func (b *Builder) insertRowsToNode(inScope *scope, ir ast.InsertRows, columnNames []string, tableName string, destSchema sql.Schema) (outScope *scope) {
124124
switch v := ir.(type) {
125125
case ast.SelectStatement:
126126
return b.buildSelectStmt(inScope, v)
127127
case ast.Values:
128-
outScope = b.buildInsertValues(inScope, v, columnNames, destSchema)
128+
outScope = b.buildInsertValues(inScope, v, columnNames, tableName, destSchema)
129129
default:
130130
err := sql.ErrUnsupportedSyntax.New(ast.String(ir))
131131
b.handleErr(err)
132132
}
133133
return
134134
}
135135

136-
func (b *Builder) buildInsertValues(inScope *scope, v ast.Values, columnNames []string, destSchema sql.Schema) (outScope *scope) {
136+
func (b *Builder) buildInsertValues(inScope *scope, v ast.Values, columnNames []string, tableName string, destSchema sql.Schema) (outScope *scope) {
137137
columnDefaultValues := make([]*sql.ColumnDefaultValue, len(columnNames))
138138

139139
for i, columnName := range columnNames {
@@ -142,7 +142,7 @@ func (b *Builder) buildInsertValues(inScope *scope, v ast.Values, columnNames []
142142
if !b.TriggerCtx().Call && len(b.TriggerCtx().UnresolvedTables) > 0 {
143143
continue
144144
}
145-
err := plan.ErrInsertIntoNonexistentColumn.New(columnName)
145+
err := sql.ErrUnknownColumn.New(columnName, tableName)
146146
b.handleErr(err)
147147
}
148148

sql/planbuilder/dml_validate.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,15 @@ func validateColumns(tableName string, columnNames []string, dstSchema sql.Schem
103103
for i, columnName := range columnNames {
104104
dstCol, exists := dstColNames[columnName]
105105
if !exists {
106-
return plan.ErrInsertIntoNonexistentColumn.New(columnName)
106+
return sql.ErrUnknownColumn.New(columnName, tableName)
107107
}
108108
if dstCol.Generated != nil && !validGeneratedColumnValue(i, source) {
109109
return sql.ErrGeneratedColumnValue.New(dstCol.Name, tableName)
110110
}
111111
if _, exists := usedNames[columnName]; !exists {
112112
usedNames[columnName] = struct{}{}
113113
} else {
114-
return plan.ErrInsertIntoDuplicateColumn.New(columnName)
114+
return sql.ErrColumnSpecifiedTwice.New(columnName)
115115
}
116116
}
117117
return nil

0 commit comments

Comments
 (0)