-
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
plan,executor: calculate generated columns in CRUD. #3951
Changes from 64 commits
2453c2b
cb2b0f4
4fd20ad
e47033c
a5837fd
26f794f
a15a62b
256db5a
768acb8
02f06a8
a66f637
06ef266
d2f5652
b178565
09e63cb
9dc57a3
8bc1dba
4e0e9a1
b8a9e3f
64716c7
d33ee30
2453252
8b21c22
c316e95
abdf0f1
1578bf9
51f5874
b7cc647
932a090
4d1e4a1
eeb9f7d
989ed51
a385e4d
c14ce47
e7d8ae1
38655cc
c594dcb
809d676
f44da3c
31cd85b
cc468d3
9faebad
a6624b3
6b46210
e93a266
ecafbc0
936c4ec
8f503a4
b4c99e3
d9e26dd
e62e6a4
ed530f3
f6b699b
487e382
6b9e67a
7e1c710
253200f
4d4a79f
321aeb0
e7c227a
67b044c
ce39e87
c3b14b9
77051e6
ddde2f2
26f7fc4
96ae5bd
1d321bf
34df3c1
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 |
---|---|---|
|
@@ -1213,6 +1213,123 @@ func (s *testSuite) TestGeneratedColumnWrite(c *C) { | |
} | ||
} | ||
|
||
// TestGeneratedColumnRead tests select generated columns from table. | ||
// They should be calculated from their generation expressions. | ||
func (s *testSuite) TestGeneratedColumnRead(c *C) { | ||
defer func() { | ||
s.cleanEnv(c) | ||
testleak.AfterTest(c)() | ||
}() | ||
tk := testkit.NewTestKit(c, s.store) | ||
tk.MustExec("use test") | ||
tk.MustExec(`CREATE TABLE test_gc_read(a int primary key, b int, c int as (a+b), d int as (a*b) stored)`) | ||
|
||
// Insert only column a and b, leave c and d be calculated from them. | ||
tk.MustExec(`INSERT INTO test_gc_read (a, b) VALUES (0,null),(1,2),(3,4)`) | ||
result := tk.MustQuery(`SELECT * FROM test_gc_read ORDER BY a`) | ||
result.Check(testkit.Rows(`0 <nil> <nil> <nil>`, `1 2 3 2`, `3 4 7 12`)) | ||
|
||
tk.MustExec(`INSERT INTO test_gc_read SET a = 5, b = 10`) | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read ORDER BY a`) | ||
result.Check(testkit.Rows(`0 <nil> <nil> <nil>`, `1 2 3 2`, `3 4 7 12`, `5 10 15 50`)) | ||
|
||
tk.MustExec(`REPLACE INTO test_gc_read (a, b) VALUES (5, 6)`) | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read ORDER BY a`) | ||
result.Check(testkit.Rows(`0 <nil> <nil> <nil>`, `1 2 3 2`, `3 4 7 12`, `5 6 11 30`)) | ||
|
||
tk.MustExec(`INSERT INTO test_gc_read (a, b) VALUES (5, 8) ON DUPLICATE KEY UPDATE b = 9`) | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read ORDER BY a`) | ||
result.Check(testkit.Rows(`0 <nil> <nil> <nil>`, `1 2 3 2`, `3 4 7 12`, `5 9 14 45`)) | ||
|
||
// Test select only-generated-column-without-dependences. | ||
result = tk.MustQuery(`SELECT c, d FROM test_gc_read`) | ||
result.Check(testkit.Rows(`<nil> <nil>`, `3 2`, `7 12`, `14 45`)) | ||
|
||
// Test order of on duplicate key update list. | ||
tk.MustExec(`INSERT INTO test_gc_read (a, b) VALUES (5, 8) ON DUPLICATE KEY UPDATE a = 6, b = a`) | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read ORDER BY a`) | ||
result.Check(testkit.Rows(`0 <nil> <nil> <nil>`, `1 2 3 2`, `3 4 7 12`, `6 6 12 36`)) | ||
|
||
tk.MustExec(`INSERT INTO test_gc_read (a, b) VALUES (6, 8) ON DUPLICATE KEY UPDATE b = 8, a = b`) | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read ORDER BY a`) | ||
result.Check(testkit.Rows(`0 <nil> <nil> <nil>`, `1 2 3 2`, `3 4 7 12`, `8 8 16 64`)) | ||
|
||
// Test where-conditions on virtual/stored generated columns. | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read WHERE c = 7`) | ||
result.Check(testkit.Rows(`3 4 7 12`)) | ||
|
||
result = tk.MustQuery(`SELECT * FROM test_gc_read WHERE d = 64`) | ||
result.Check(testkit.Rows(`8 8 16 64`)) | ||
|
||
// Test update where-conditions on virtual/generated columns. | ||
tk.MustExec(`UPDATE test_gc_read SET a = a + 100 WHERE c = 7`) | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read WHERE c = 107`) | ||
result.Check(testkit.Rows(`103 4 107 412`)) | ||
|
||
// Test update where-conditions on virtual/generated columns. | ||
tk.MustExec(`UPDATE test_gc_read m SET m.a = m.a + 100 WHERE c = 107`) | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read WHERE c = 207`) | ||
result.Check(testkit.Rows(`203 4 207 812`)) | ||
|
||
tk.MustExec(`UPDATE test_gc_read SET a = a - 200 WHERE d = 812`) | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read WHERE d = 12`) | ||
result.Check(testkit.Rows(`3 4 7 12`)) | ||
|
||
// Test on-conditions on virtual/stored generated columns. | ||
tk.MustExec(`CREATE TABLE test_gc_help(a int primary key, b int, c int, d int)`) | ||
tk.MustExec(`INSERT INTO test_gc_help(a, b, c, d) SELECT * FROM test_gc_read`) | ||
|
||
result = tk.MustQuery(`SELECT t1.* FROM test_gc_read t1 JOIN test_gc_help t2 ON t1.c = t2.c ORDER BY t1.a`) | ||
result.Check(testkit.Rows(`1 2 3 2`, `3 4 7 12`, `8 8 16 64`)) | ||
|
||
result = tk.MustQuery(`SELECT t1.* FROM test_gc_read t1 JOIN test_gc_help t2 ON t1.d = t2.d ORDER BY t1.a`) | ||
result.Check(testkit.Rows(`1 2 3 2`, `3 4 7 12`, `8 8 16 64`)) | ||
|
||
// Test generated column in subqueries. | ||
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 mean whether the expression of a generated column can be a subquery. 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. Oh, it can't be subquery. |
||
result = tk.MustQuery(`SELECT * FROM test_gc_read t WHERE t.a not in (SELECT t.a FROM test_gc_read t where t.c > 5)`) | ||
result.Check(testkit.Rows(`0 <nil> <nil> <nil>`, `1 2 3 2`)) | ||
|
||
result = tk.MustQuery(`SELECT * FROM test_gc_read t WHERE t.c in (SELECT t.c FROM test_gc_read t where t.c > 5)`) | ||
result.Check(testkit.Rows(`3 4 7 12`, `8 8 16 64`)) | ||
|
||
result = tk.MustQuery(`SELECT tt.b FROM test_gc_read tt WHERE tt.a = (SELECT max(t.a) FROM test_gc_read t WHERE t.c = tt.c)`) | ||
result.Check(testkit.Rows(`2`, `4`, `8`)) | ||
|
||
// Test aggregation on virtual/stored generated columns. | ||
result = tk.MustQuery(`SELECT c, sum(a) aa, max(d) dd FROM test_gc_read GROUP BY c ORDER BY aa`) | ||
result.Check(testkit.Rows(`<nil> 0 <nil>`, `3 1 2`, `7 3 12`, `16 8 64`)) | ||
|
||
result = tk.MustQuery(`SELECT a, sum(c), sum(d) FROM test_gc_read GROUP BY a ORDER BY a`) | ||
result.Check(testkit.Rows(`0 <nil> <nil>`, `1 3 2`, `3 7 12`, `8 16 64`)) | ||
|
||
// Test multi-update on generated columns. | ||
tk.MustExec(`UPDATE test_gc_read m, test_gc_read n SET m.a = m.a + 10, n.a = n.a + 10`) | ||
result = tk.MustQuery(`SELECT * FROM test_gc_read ORDER BY a`) | ||
result.Check(testkit.Rows(`10 <nil> <nil> <nil>`, `11 2 13 22`, `13 4 17 52`, `18 8 26 144`)) | ||
|
||
// Test not null generated columns. | ||
tk.MustExec(`CREATE TABLE test_gc_read_1(a int primary key, b int, c int as (a+b) not null, d int as (a*b) stored)`) | ||
tk.MustExec(`CREATE TABLE test_gc_read_2(a int primary key, b int, c int as (a+b), d int as (a*b) stored not null)`) | ||
tests := []struct { | ||
stmt string | ||
err int | ||
}{ | ||
// Can't insert these records, because generated columns are not null. | ||
{`insert into test_gc_read_1(a, b) values (1, null)`, mysql.ErrBadNull}, | ||
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. what happens if I insert a, b and c that c is not equal to a+b? 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. you cannot insert generated column explicitly. it will cause fail. 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. so add some 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. Already added at line 1749, function |
||
{`insert into test_gc_read_2(a, b) values (1, null)`, mysql.ErrBadNull}, | ||
} | ||
for _, tt := range tests { | ||
_, err := tk.Exec(tt.stmt) | ||
if tt.err != 0 { | ||
c.Assert(err, NotNil) | ||
terr := errors.Trace(err).(*errors.Err).Cause().(*terror.Error) | ||
c.Assert(terr.Code(), Equals, terror.ErrCode(tt.err)) | ||
} else { | ||
c.Assert(err, IsNil) | ||
} | ||
} | ||
} | ||
|
||
func (s *testSuite) TestToPBExpr(c *C) { | ||
defer func() { | ||
s.cleanEnv(c) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -638,6 +638,9 @@ type InsertValues struct { | |
Lists [][]expression.Expression | ||
Setlist []*expression.Assignment | ||
IsPrepare bool | ||
|
||
GenColumns []*ast.ColumnName | ||
GenExprs []expression.Expression | ||
} | ||
|
||
// InsertExec represents an insert executor. | ||
|
@@ -763,30 +766,32 @@ func (e *InsertValues) getColumns(tableCols []*table.Column) ([]*table.Column, e | |
for _, v := range e.Setlist { | ||
columns = append(columns, v.Col.ColName.O) | ||
} | ||
|
||
for _, v := range e.GenColumns { | ||
columns = append(columns, v.Name.O) | ||
} | ||
cols, err = table.FindCols(tableCols, columns) | ||
if err != nil { | ||
return nil, errors.Errorf("INSERT INTO %s: %s", e.Table.Meta().Name.O, err) | ||
} | ||
|
||
if len(cols) == 0 { | ||
return nil, errors.Errorf("INSERT INTO %s: empty column", e.Table.Meta().Name.O) | ||
} | ||
} else { | ||
} else if len(e.Columns) > 0 { | ||
// Process `name` type column. | ||
columns := make([]string, 0, len(e.Columns)) | ||
for _, v := range e.Columns { | ||
columns = append(columns, v.Name.O) | ||
} | ||
for _, v := range e.GenColumns { | ||
columns = append(columns, v.Name.O) | ||
} | ||
cols, err = table.FindCols(tableCols, columns) | ||
if err != nil { | ||
return nil, errors.Errorf("INSERT INTO %s: %s", e.Table.Meta().Name.O, err) | ||
} | ||
|
||
// If cols are empty, use all columns instead. | ||
if len(cols) == 0 { | ||
cols = tableCols | ||
} | ||
} else { | ||
// If e.Columns are empty, use all columns instead. | ||
cols = tableCols | ||
} | ||
|
||
// Check column whether is specified only once. | ||
|
@@ -812,7 +817,7 @@ func (e *InsertValues) fillValueList() error { | |
return nil | ||
} | ||
|
||
func (e *InsertValues) checkValueCount(insertValueCount, valueCount, num int, cols []*table.Column) error { | ||
func (e *InsertValues) checkValueCount(insertValueCount, valueCount, genColsCount int, num int, cols []*table.Column) 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. the added int can be left out. |
||
// TODO: This check should be done in plan builder. | ||
if insertValueCount != valueCount { | ||
// "insert into t values (), ()" is valid. | ||
|
@@ -825,8 +830,18 @@ func (e *InsertValues) checkValueCount(insertValueCount, valueCount, num int, co | |
if valueCount == 0 && len(e.Columns) > 0 { | ||
// "insert into t (c1) values ()" is not valid. | ||
return ErrWrongValueCountOnRow.GenByArgs(num + 1) | ||
} else if valueCount > 0 && valueCount != len(cols) { | ||
return ErrWrongValueCountOnRow.GenByArgs(num + 1) | ||
} else if valueCount > 0 { | ||
explicitSetLen := 0 | ||
if len(e.Columns) != 0 { | ||
explicitSetLen = len(e.Columns) | ||
} else { | ||
explicitSetLen = len(e.Setlist) | ||
} | ||
if explicitSetLen > 0 && valueCount+genColsCount != len(cols) { | ||
return ErrWrongValueCountOnRow.GenByArgs(num + 1) | ||
} else if explicitSetLen == 0 && valueCount != len(cols) { | ||
return ErrWrongValueCountOnRow.GenByArgs(num + 1) | ||
} | ||
} | ||
return nil | ||
} | ||
|
@@ -840,7 +855,7 @@ func (e *InsertValues) getRows(cols []*table.Column) (rows [][]types.Datum, err | |
rows = make([][]types.Datum, len(e.Lists)) | ||
length := len(e.Lists[0]) | ||
for i, list := range e.Lists { | ||
if err = e.checkValueCount(length, len(list), i, cols); err != nil { | ||
if err = e.checkValueCount(length, len(list), len(e.GenColumns), i, cols); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
e.currRow = int64(i) | ||
|
@@ -903,6 +918,15 @@ func (e *InsertValues) fillRowData(cols []*table.Column, vals []types.Datum, ign | |
if err = table.CastValues(e.ctx, row, cols, ignoreErr); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
for i, expr := range e.GenExprs { | ||
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 this after castValues ? |
||
var val types.Datum | ||
val, err = expr.Eval(row) | ||
if err = e.filterErr(err, ignoreErr); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
offset := cols[len(vals)+i].Offset | ||
row[offset] = val | ||
} | ||
if err = table.CheckNotNull(e.Table.Cols(), row); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
|
@@ -934,7 +958,9 @@ func (e *InsertValues) initDefaultValues(row []types.Datum, hasValue []bool, ign | |
needDefaultValue = true | ||
// TODO: Append Warning ErrColumnCantNull. | ||
} | ||
if mysql.HasAutoIncrementFlag(c.Flag) { | ||
if mysql.HasAutoIncrementFlag(c.Flag) || c.IsGenerated() { | ||
// Just leave generated column as null. It will be calculated later | ||
// but before we check whether the column can be null or not. | ||
needDefaultValue = false | ||
} | ||
if needDefaultValue { | ||
|
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.
add some test in tidb-test