-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
*: make insert with calculated value behave the same as MySQL. #4603
Changes from 5 commits
fbf1a26
d0d6763
d384ee0
78e92d5
e7d14f1
13e93b9
12d5964
8ce624e
7798788
04ecc89
6a18c49
498734a
806c09e
fbed899
d23f524
2467c7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -886,15 +886,56 @@ func (e *InsertValues) getRows(cols []*table.Column, ignoreErr bool) (rows [][]t | |
} | ||
|
||
func (e *InsertValues) getRow(cols []*table.Column, list []expression.Expression, ignoreErr bool) ([]types.Datum, error) { | ||
vals := make([]types.Datum, len(list)) | ||
row := make([]types.Datum, len(e.Table.Cols())) | ||
hasValue := make([]bool, len(e.Table.Cols())) | ||
if err := e.fillDefaultValues(row, ignoreErr); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
|
||
for i, expr := range list { | ||
val, err := expr.Eval(nil) | ||
vals[i] = val | ||
val, err := expr.Eval(row) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
val, err = table.CastValue(e.ctx, val, cols[i].ToInfo()) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
|
||
offset := cols[i].Offset | ||
row[offset] = val | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. row[offset], hasValue[offset] = val, true |
||
hasValue[offset] = true | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
} | ||
return e.fillRowData(cols, vals, ignoreErr) | ||
|
||
return e.checkRowData(cols, len(list), hasValue, row, ignoreErr) | ||
} | ||
|
||
func (e *InsertValues) fillDefaultValues(row []types.Datum, ignoreErr bool) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hasValue columns is filled by default values too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
var defaultValueCols []*table.Column | ||
for i, c := range e.Table.Cols() { | ||
var err error | ||
if c.IsGenerated() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a comment for these checks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will these check be influenced by sql_mode? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It use zero value for |
||
continue | ||
} else if mysql.HasAutoIncrementFlag(c.Flag) { | ||
row[i] = table.GetZeroValue(c.ToInfo()) | ||
} else { | ||
row[i], err = table.GetColDefaultValue(e.ctx, c.ToInfo()) | ||
if table.IsNoDefault(err) && mysql.HasNotNullFlag(c.Flag) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If fail to go into this branch, we should handle this error and return? |
||
row[i] = table.GetZeroValue(c.ToInfo()) | ||
} else if err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use filterErr(err) here? |
||
return errors.Trace(err) | ||
} | ||
} | ||
defaultValueCols = append(defaultValueCols, c) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think defaultValueCols is not necessary, just use |
||
} | ||
if err := table.CastValues(e.ctx, row, defaultValueCols, ignoreErr); err != nil { | ||
return errors.Trace(err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (e *InsertValues) getRowsSelect(cols []*table.Column, ignoreErr bool) ([][]types.Datum, error) { | ||
|
@@ -929,6 +970,11 @@ func (e *InsertValues) fillRowData(cols []*table.Column, vals []types.Datum, ign | |
row[offset] = v | ||
hasValue[offset] = true | ||
} | ||
|
||
return e.checkRowData(cols, len(vals), hasValue, row, ignoreErr) | ||
} | ||
|
||
func (e *InsertValues) checkRowData(cols []*table.Column, valLen int, hasValue []bool, row []types.Datum, ignoreErr bool) ([]types.Datum, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this func name is not that explicit, |
||
err := e.initDefaultValues(row, hasValue, ignoreErr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do initDefaultValues again? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we do these check in fillDefaultValues? |
||
if err != nil { | ||
return nil, errors.Trace(err) | ||
|
@@ -939,7 +985,7 @@ func (e *InsertValues) fillRowData(cols []*table.Column, vals []types.Datum, ign | |
if err = e.filterErr(err, ignoreErr); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
offset := cols[len(vals)+i].Offset | ||
offset := cols[valLen+i].Offset | ||
row[offset] = val | ||
} | ||
if err = table.CastValues(e.ctx, row, cols, ignoreErr); err != nil { | ||
|
@@ -980,6 +1026,9 @@ func (e *InsertValues) initDefaultValues(row []types.Datum, hasValue []bool, ign | |
// Just leave generated column as null. It will be calculated later | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a comment for initDefaultValues |
||
// but before we check whether the column can be null or not. | ||
needDefaultValue = false | ||
if !hasValue[i] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any test case for this check? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test L1279-L1281: create table t(a int auto_increment key, b int);
set SQL_MODE=NO_AUTO_VALUE_ON_ZERO;
insert into t (b) value (a+1); If we don't reset |
||
row[i].SetNull() | ||
} | ||
} | ||
if needDefaultValue { | ||
var err error | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1171,3 +1171,106 @@ func (s *testSuite) TestIssue4067(c *C) { | |
tk.MustExec("delete from t1 where id in (select id from t2)") | ||
tk.MustQuery("select * from t1").Check(nil) | ||
} | ||
|
||
func (s *testSuite) TestInsertCalculatedValue(c *C) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add test cases for more generated columns. |
||
tk := testkit.NewTestKit(c, s.store) | ||
tk.MustExec("use test") | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a int, b int)") | ||
tk.MustExec("insert into t set a=1, b=a+1") | ||
tk.MustQuery("select a, b from t").Check(testkit.Rows("1 2")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a int default 100, b int)") | ||
tk.MustExec("insert into t set b=a+1, a=1") | ||
tk.MustQuery("select a, b from t").Check(testkit.Rows("1 101")) | ||
tk.MustExec("insert into t (b) value (a)") | ||
tk.MustQuery("select * from t where b = 100").Check(testkit.Rows("100 100")) | ||
tk.MustExec("insert into t set a=2, b=a+1") | ||
tk.MustQuery("select * from t where a = 2").Check(testkit.Rows("2 3")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t (c int)") | ||
tk.MustExec("insert into test.t set test.t.c = '1'") | ||
tk.MustQuery("select * from t").Check(testkit.Rows("1")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a int default 1)") | ||
tk.MustExec("insert into t values (a)") | ||
tk.MustQuery("select * from t").Check(testkit.Rows("1")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t (a int, b int, c int, d int)") | ||
tk.MustExec("insert into t value (1, 2, a+1, b+1)") | ||
tk.MustQuery("select * from t").Check(testkit.Rows("1 2 2 3")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t (a int not null)") | ||
tk.MustExec("insert into t values (a+2)") | ||
tk.MustExec("insert into t values (a)") | ||
tk.MustQuery("select * from t order by a").Check(testkit.Rows("0", "2")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t (a bigint not null, b bigint not null)") | ||
tk.MustExec("insert into t value(b + 1, a)") | ||
tk.MustExec("insert into t set a = b + a, b = a + 1") | ||
tk.MustExec("insert into t value(1000, a)") | ||
tk.MustQuery("select * from t order by a").Check(testkit.Rows("0 1", "1 1", "1000 1000")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add the case issued by winoros in #4482 , There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add test cases like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add the conner cases like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added these test cases at L1221-L1275. |
||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a int)") | ||
tk.MustExec("insert into t values(a)") | ||
tk.MustQuery("select * from t").Check(testkit.Rows("<nil>")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a enum('a', 'b'))") | ||
tk.MustExec("insert into t values(a)") | ||
tk.MustQuery("select * from t").Check(testkit.Rows("<nil>")) | ||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a enum('a', 'b') default 'a')") | ||
tk.MustExec("insert into t values(a)") | ||
tk.MustExec("insert into t values(a+1)") | ||
tk.MustQuery("select * from t order by a").Check(testkit.Rows("a", "b")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a blob)") | ||
tk.MustExec("insert into t values(a)") | ||
tk.MustQuery("select * from t").Check(testkit.Rows("<nil>")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a varchar(20) default 'a')") | ||
tk.MustExec("insert into t values(a)") | ||
tk.MustExec("insert into t values(upper(a))") | ||
tk.MustQuery("select * from t order by a").Check(testkit.Rows("A", "a")) | ||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a varchar(20) not null, b varchar(20))") | ||
tk.MustExec("insert into t value (a, b)") | ||
tk.MustQuery("select * from t").Check(testkit.Rows(" <nil>")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a int, b int)") | ||
tk.MustExec("insert into t values(a*b, b*b)") | ||
tk.MustQuery("select * from t").Check(testkit.Rows("<nil> <nil>")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t (a json not null, b int)") | ||
tk.MustExec("insert into t value (a,a->'$')") | ||
tk.MustQuery("select * from t").Check(testkit.Rows("null 0")) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a json, b int, c int as (a->'$.a'))") | ||
tk.MustExec("insert into t (a, b) value (a, a->'$.a'+1)") | ||
tk.MustExec("insert into t (b) value (a->'$.a'+1)") | ||
tk.MustQuery("select * from t").Check(testkit.Rows("<nil> <nil> <nil>", "<nil> <nil> <nil>")) | ||
tk.MustExec(`insert into t (a, b) value ('{"a": 1}', a->'$.a'+1)`) | ||
tk.MustQuery("select * from t where c = 1").Check(testkit.Rows(`{"a":1} 2 1`)) | ||
|
||
tk.MustExec("drop table if exists t") | ||
tk.MustExec("create table t(a int auto_increment key, b int)") | ||
tk.MustExec("insert into t (b) value (a)") | ||
tk.MustExec("insert into t value (a, a+1)") | ||
tk.MustExec("set SQL_MODE=NO_AUTO_VALUE_ON_ZERO") | ||
tk.MustExec("insert into t (b) value (a+1)") | ||
tk.MustQuery("select * from t order by a").Check(testkit.Rows("1 0", "2 1", "3 1")) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -693,7 +693,8 @@ func (b *planBuilder) buildInsert(insert *ast.InsertStmt) Plan { | |
return nil | ||
} | ||
tableInfo := tn.TableInfo | ||
schema := expression.TableInfo2Schema(tableInfo) | ||
// Build Schema with DBName otherwise ColumnRef with DBName cannot match any Column in Schema | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add |
||
schema := expression.TableInfo2SchemaWithDBName(tn.Schema, tableInfo) | ||
tableInPlan, ok := b.is.TableByID(tableInfo.ID) | ||
if !ok { | ||
b.err = errors.Errorf("Can't get table %s.", tableInfo.Name.O) | ||
|
@@ -733,6 +734,9 @@ func (b *planBuilder) buildInsert(insert *ast.InsertStmt) Plan { | |
} | ||
} | ||
|
||
mockTablePlan := TableDual{}.init(b.allocator, b.ctx) | ||
mockTablePlan.SetSchema(schema) | ||
|
||
cols := insertPlan.Table.Cols() | ||
maxValuesItemLength := 0 // the max length of items in VALUES list. | ||
for _, valuesItem := range insert.Lists { | ||
|
@@ -752,7 +756,7 @@ func (b *planBuilder) buildInsert(insert *ast.InsertStmt) Plan { | |
RetType: &val.Type, | ||
} | ||
} else { | ||
expr, _, err = b.rewrite(valueItem, nil, nil, true) | ||
expr, _, err = b.rewrite(valueItem, mockTablePlan, nil, true) | ||
} | ||
if err != nil { | ||
b.err = errors.Trace(err) | ||
|
@@ -784,8 +788,6 @@ func (b *planBuilder) buildInsert(insert *ast.InsertStmt) Plan { | |
} | ||
} | ||
|
||
mockTablePlan := TableDual{}.init(b.allocator, b.ctx) | ||
mockTablePlan.SetSchema(schema) | ||
for _, assign := range insert.Setlist { | ||
col, err := schema.FindColumn(assign.Column) | ||
if err != nil { | ||
|
@@ -801,8 +803,6 @@ func (b *planBuilder) buildInsert(insert *ast.InsertStmt) Plan { | |
b.err = ErrBadGeneratedColumn.GenByArgs(assign.Column.Name.O, tableInfo.Name.O) | ||
return nil | ||
} | ||
// Here we keep different behaviours with MySQL. MySQL allow set a = b, b = a and the result is NULL, NULL. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keep the comment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But after merge this PR, result of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok. got it. |
||
// It's unreasonable. | ||
expr, _, err := b.rewrite(assign.Expr, mockTablePlan, nil, true) | ||
if err != nil { | ||
b.err = errors.Trace(err) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ import ( | |
"github.com/pingcap/tidb/mysql" | ||
"github.com/pingcap/tidb/util/hack" | ||
"github.com/pingcap/tidb/util/types" | ||
"github.com/pingcap/tidb/util/types/json" | ||
) | ||
|
||
// Column provides meta data describing a table column. | ||
|
@@ -344,6 +345,11 @@ func getColDefaultValueFromNil(ctx context.Context, col *model.ColumnInfo) (type | |
return types.Datum{}, errNoDefaultValue.Gen("Field '%s' doesn't have a default value", col.Name) | ||
} | ||
|
||
// IsNoDefault check if err is equal to errNoDefaultValue. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like the IsNoDefault is not necessary?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/ check/ checks |
||
func IsNoDefault(err error) bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to define this func, |
||
return errNoDefaultValue.Equal(err) | ||
} | ||
|
||
// GetZeroValue gets zero value for given column type. | ||
func GetZeroValue(col *model.ColumnInfo) types.Datum { | ||
var d types.Datum | ||
|
@@ -378,6 +384,8 @@ func GetZeroValue(col *model.ColumnInfo) types.Datum { | |
d.SetMysqlSet(types.Set{}) | ||
case mysql.TypeEnum: | ||
d.SetMysqlEnum(types.Enum{}) | ||
case mysql.TypeJSON: | ||
d.SetMysqlJSON(json.CreateJSON(nil)) | ||
} | ||
return d | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use e.filterErr(err) here.