-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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
executor: fix a bug of update with outer join #7177
Conversation
executor/executor_test.go
Outdated
tk.MustQuery("select id, k, v from t3").Check(testkit.Rows()) | ||
|
||
// test left join and right no records but update no records part. | ||
tk.MustExec("update t1 left join t2 on t1.k = t2.k set t1.v = t2.v, t2.v = 3") // exchange to t2.v = 3, t1.v = t2.v..will got a bug. |
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.
exchange t1.v = t2.v, t2.v = 3
to t2.v = 3, t1.v = t2.v
will be fix later - -
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.
fixed
expression/column.go
Outdated
@@ -140,6 +140,12 @@ func (col *CorrelatedColumn) ResolveIndices(_ *Schema) Expression { | |||
func (col *CorrelatedColumn) resolveIndices(_ *Schema) { | |||
} | |||
|
|||
// ColumnIdentifier represents a identifier of column. | |||
type ColumnIdentifier struct { |
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.
Can you wait for this merged?
#7157
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.
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.
The answer might be no
for now.
expression/schema.go
Outdated
@@ -37,7 +37,8 @@ type Schema struct { | |||
Columns []*Column | |||
Keys []KeyInfo | |||
// TblID2Handle stores the tables' handle column information if we need handle in execution phase. | |||
TblID2Handle map[int64][]*Column | |||
TblID2Handle map[int64][]*Column | |||
ReplenishCols map[ColumnIdentifier]struct{} |
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.
Can this set maintained by INSERT
or UPDATE
instead of in schema?
executor/write.go
Outdated
// Rebase auto increment id if the field is changed. | ||
if mysql.HasAutoIncrementFlag(col.Flag) { | ||
if mysql.HasAutoIncrementFlag(col.Flag) && !(checkReplenish && oldDataAllNull) { |
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.
I think it is acceptable, but the root cause is not this. It seems MySQL sets the auto-id and checks NULL for it just before writing the row. It is much later than TiDB. Maybe someday we should refactor TiDB's implement.
If this PR is ready to be reviewed, |
executor/update.go
Outdated
@@ -173,6 +179,15 @@ func (e *UpdateExec) composeNewRow(rowIdx int, oldRow []types.Datum) ([]types.Da | |||
return newRowData, nil | |||
} | |||
|
|||
func allNullDatums(datums []types.Datum, colRange ColumnIndexRange) bool { |
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.
s/ allNullDatums/ isAllNull
executor/builder.go
Outdated
switch p := selectPlan.(type) { | ||
case *plan.PhysicalHashJoin: | ||
joinType = p.JoinType | ||
break |
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.
we do not need break
in golang
executor/executor_test.go
Outdated
tk.MustExec("insert into t1 values (1, 0)") | ||
tk.MustExec("insert into t4 values (3, 3)") | ||
|
||
// test auto_increment & none-null column in right table of update left join. |
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.
We may need @lilin90 's help here...
For all the comments in this function.
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.
-> test the case that the table with auto_increment or none-null columns as the right table of left join
executor/update.go
Outdated
newRowsData [][]types.Datum // The new values to be set. | ||
fetched bool | ||
cursor int | ||
replenishTbl map[string]ColumnIndexRange |
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.
comment for this attribute
executor/builder.go
Outdated
start, end int | ||
} | ||
|
||
// resolveReplenishTbl resolve sub-join's replenish column info. |
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.
We need a more detailed comment for this function.
executor/update.go
Outdated
@@ -163,6 +165,10 @@ func (e *UpdateExec) handleErr(colName model.CIStr, rowIdx int, err error) error | |||
func (e *UpdateExec) composeNewRow(rowIdx int, oldRow []types.Datum) ([]types.Datum, error) { | |||
newRowData := types.CopyRow(oldRow) | |||
for _, assign := range e.OrderedList { | |||
colRange, isReplenish := e.replenishTbl[assign.Col.DBName.O+"-"+assign.Col.TblName.O] |
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.
Is it an optimization?
Can we remove this?
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.
mum..no..it's a bugfix not optimize, remove this will cause https://github.com/pingcap/tidb/pull/7177/files#diff-6d9a596d0d155b4342a598ba359efc8bR2985 failure
executor/write.go
Outdated
@@ -53,14 +53,15 @@ const ( | |||
// 4. lastInsertID (uint64) : the lastInsertID should be set by the newData. | |||
// 5. err (error) : error in the update. | |||
func updateRecord(ctx sessionctx.Context, h int64, oldData, newData []types.Datum, modified []bool, t table.Table, | |||
onDup bool) (bool, bool, int64, uint64, error) { | |||
onDup, checkReplenish bool) (bool, bool, int64, uint64, error) { |
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.
Can we move the check out of updateRecord
?
This method is too complex already.
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.
Done~
executor/update.go
Outdated
newRowsData [][]types.Datum // The new values to be set. | ||
fetched bool | ||
cursor int | ||
replenishTbl map[string]ColumnIndexRange |
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.
We can use *expression.Column
as the key of the map.
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.
Pls update comments description.
executor/builder.go
Outdated
} | ||
|
||
// Len implements sort.Interface#Less. | ||
// let ranges first sort by `start` increasing order, and sort by `end` decreasing order if `start` are equal. |
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.
- first sort by -> first sorted by
- and sort by -> and then sorted by
executor/builder.go
Outdated
|
||
// Len implements sort.Interface#Less. | ||
// let ranges first sort by `start` increasing order, and sort by `end` decreasing order if `start` are equal. | ||
// so in foldDuplicate can fold duplicate range by this order. |
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.
Please delete the extra "in".
executor/builder.go
Outdated
} | ||
|
||
// foldDuplicate removes the duplicate ranges. | ||
// c must be sorted use sort.Sort. |
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 -> using
executor/builder.go
Outdated
return ranges | ||
} | ||
|
||
// findColRange find the range hit by given column index. |
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.
find -> finds
executor/builder.go
Outdated
} | ||
|
||
// findColRange find the range hit by given column index. | ||
// c must be sorted use sort.Sort. |
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 -> using
executor/executor_test.go
Outdated
tk.MustQuery("select k, v from t1").Check(testkit.Rows("1 <nil>")) | ||
tk.MustQuery("select k, v from t2").Check(testkit.Rows()) | ||
|
||
// test right join and left no records but update no records part. |
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.
-> test right join and the case that the left table has no matching record but has updated the left table columns
executor/executor_test.go
Outdated
tk.MustQuery("select k, v from t1").Check(testkit.Rows("1 0")) | ||
tk.MustQuery("select k, v from t2").Check(testkit.Rows()) | ||
|
||
// test left + right join update |
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.
-> test the case of right join and left join at the same time
executor/executor_test.go
Outdated
tk.MustQuery("select k, v from t2").Check(testkit.Rows()) | ||
tk.MustQuery("select k, v from t4").Check(testkit.Rows("3 4")) | ||
|
||
// test left join and update right data. |
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.
-> test normal left join and the case that the right table has matching rows
executor/executor_test.go
Outdated
tk.MustExec("update t1 left join t2 on t1.k = t2.k set t2.v = 11") | ||
tk.MustQuery("select k, v from t2").Check(testkit.Rows("1 11")) | ||
|
||
// test join same table multiple times, update right record and not insert no exists record. |
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.
-> test the case of continuously joining the same table and updating the unmatching records
executor/executor_test.go
Outdated
tk.MustQuery("select k, v from t1").Check(testkit.Rows("1 111")) | ||
tk.MustQuery("select k, v from t2").Check(testkit.Rows("1 11")) | ||
|
||
// test left join and left all null record's update. |
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.
-> test the left join case that the left table has records but all records are null
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.
thanks very much, ^ ^ I will address them.
@@ -72,7 +72,7 @@ func (e *AnalyzeExec) Next(ctx context.Context, chk *chunk.Chunk) error { | |||
result := <-resultCh | |||
if result.Err != nil { | |||
err = result.Err | |||
if errors.Trace(err) == analyzeWorkerPanic { |
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.
CGO_ENABLED=0 revive -formatter friendly -config revive.toml $(go list ./...| grep -vE "vendor")
✘ error-naming error var analyzeWorkerPanic should have name of the form errFoo
/home/robi/Code/go/src/github.com/pingcap/tidb/executor/analyze.go:119:5
✘ 1 problem (1 error, 0 warnings)
Errors:
1 error-naming
/run-all-tests |
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.
rest lgtm
executor/builder.go
Outdated
cols2Handle = append(cols2Handle, Columns2HandleEntry{offset, end, handleCol.Index}) | ||
} | ||
} | ||
sort.Sort(cols2Handle) |
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.
Do we need to sort them?
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.
for later find at https://github.com/pingcap/tidb/pull/7177/files/d67a05e0fcf141855ede1651256e10a85f41cf0f#diff-badfcd30d7596a08cd207b8e6ae778e6R1284 we need sorted, and for-loop TblID2Handle doesn't make sure order o o?
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.
lgtm
executor/builder.go
Outdated
// Columns2HandleEntry represents an mapper from column index to handle index. | ||
type Columns2HandleEntry struct { | ||
start, end int | ||
handleIdx int |
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.
How about:
- s/handleIndex/handleOrdinal/
- s/Columns2HandleEntry/cols2Handle/
- s/Columns2Handle/cols2HandleSlice/
We can use the word "ordinal" to replace "index" to avoid misunderstanding.
executor/builder.go
Outdated
} | ||
return updateExec | ||
} | ||
|
||
// Columns2HandleEntry represents an mapper from column index to handle index. |
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.
how about:
// Columns2HandleEntry maps a consecutive columns to a handle column.
type Columns2HandleEntry struct {
start, end int32 // Represent the ordinal range [start, end) of the consecutive columns.
handleOrdinal int32 // Represents the ordinal of the handle column.
}
And it seems that start
is always equal to handleIdx
, do we need to store handleIdx
?
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.
handleOrdinal
may equal to start
or end
o.o?
if table without the primary key, the handle will be end
if table with the primary key, the handle will be primary definition index, e.g. create table t5(v int, k int, primary key(k))
, will be start=1, end=3, handle=2
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.
If so, I think to use getTableOffset to decide the begin offset of a table is wrong:
199 func getTableOffset(schema *expression.Schema, handleCol *expression.Column) int {
200 for i, col := range schema.Columns {
201 if col.DBName.L == handleCol.DBName.L && col.TblName.L == handleCol.TblName.L {
202 return i
203 }
204 }
205 panic("Couldn't get column information when do update/delete")
206 }
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.
from this three logic:
make sure handle
will in [start, end]
.....
for getTableOffset
..it will works if schema.Columns is order by columns group that grouped by table... e.g. |t1.x, t1.y| t2.z, t2,u|
....
this logic is same to https://github.com/lysu/tidb/blob/e5922f4ff012e748bb51bd7e854fe02a9813fd93/executor/update.go#L45 update#exec
executor/builder.go
Outdated
if biggerOne == 0 { | ||
return 0, false | ||
} | ||
if c[biggerOne-1].start <= colIndex && colIndex < c[biggerOne-1].end { |
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.
I think here c[biggerOne-1].start <= colIndex
is guaranteed to be true, we only need to check colIndex < c[biggerOne-1].end
executor/builder.go
Outdated
|
||
// findHandle finds the range hit by given column index. | ||
// c must be sorted using sort.Sort. | ||
func (c Columns2Handle) findHandle(colIndex int) (int, bool) { |
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.
how about s/colIndex/colOrdinal/
or s/colIndex/ordinal/
?
executor/builder.go
Outdated
} | ||
|
||
// findHandle finds the range hit by given column index. | ||
// c must be sorted using sort.Sort. |
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.
How about simplify the comment to "findHandle finds the ordinal of the corresponding handle column."? Since the slice is sorted when created, there is no need to specify the sorted
preconditions.
executor/update.go
Outdated
@@ -62,6 +63,11 @@ func (e *UpdateExec) exec(schema *expression.Schema) ([]types.Datum, error) { | |||
for _, col := range cols { | |||
offset := getTableOffset(schema, col) | |||
end := offset + len(tbl.WritableCols()) | |||
handleDatum := row[col.Index] | |||
// handleDatum is nil only when outer join fill no matched columns. |
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.
How about wrapping a function to check whether we can update the current record:
// canNotUpdate checks the handle of a record to decide whether that record
// can not be updated. The handle is NULL only when it is the inner side of an
// outer join: the outer row can not match any inner rows, and in this scenario
// the inner handle field is filled with a NULL value.
//
// This fixes: https://github.com/pingcap/tidb/issues/7176.
func (e *UpdateExec) canNotUpdate(handle types.Datum) bool {
return handle.IsNull()
}
executor/builder.go
Outdated
// Len implements sort.Interface#Less. | ||
// let ranges first sorted by `start` increasing order, and then sorted by `end` increasing order if `start` are equal. | ||
func (c Columns2Handle) Less(i, j int) bool { | ||
if c[i].start == c[j].start { |
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.
This situation should never happen?
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.
I'm not very sure about this...so add this protection.... 23333
/run-all-tests |
executor/builder.go
Outdated
} | ||
|
||
// Len implements sort.Interface#Less. | ||
// let ranges first sorted by `start` increasing order, and then sorted by `end` increasing order if `start` are equal. |
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.
the comments need to be updated 😄
executor/builder.go
Outdated
} | ||
return updateExec | ||
} | ||
|
||
// cols2Handle represents an mapper from column index to handle index. | ||
type cols2Handle struct { | ||
start, end int // Represent the ordinal range [start, end) of the consecutive columns. |
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.
how about int32
?
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.
why? you mean combine two fields into one int32?
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.
ok, I know fixed...
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.
LGTM
@XuHuaiyu PTAL |
executor/builder.go
Outdated
c[i], c[j] = c[j], c[i] | ||
} | ||
|
||
// Len implements sort.Interface#Less. |
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.
s/ Len/ Less
executor/builder.go
Outdated
return len(c) | ||
} | ||
|
||
// Len implements sort.Interface#Swap. |
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.
s/Len/Swap
|
||
// findHandle finds the ordinal of the corresponding handle column. | ||
func (c cols2HandleSlice) findHandle(ordinal int32) (int32, bool) { | ||
if c == nil || len(c) == 0 { |
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.
if c == nil,
c.findHandle will panic?
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.
receiver is slice(point), so it's ok
executor/builder.go
Outdated
|
||
// buildColumns2Handle build columns to handle mapping. | ||
func buildColumns2Handle(schema *expression.Schema, tblID2Table map[int64]table.Table) cols2HandleSlice { | ||
if len(schema.TblID2Handle) < 2 { |
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.
- We need a comment for this check.
- Any test case covers this?
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.
old case tested this...for update single table update t set x =1 where y = 1
...but I had added one in executor_test TestUpdateJoin~
executor/builder.go
Outdated
if c == nil || len(c) == 0 { | ||
return 0, false | ||
} | ||
biggerOne := sort.Search(len(c), func(i int) bool { return c[i].start > ordinal }) |
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.
We need a comment for biggerOne.
executor/builder.go
Outdated
if biggerOne == 0 { | ||
return 0, false | ||
} | ||
if ordinal < c[biggerOne-1].end { |
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.
Will this always be true if we reach here?
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.
for now ...it's true...previous maybe no...- - fixed~
executor/builder.go
Outdated
return 0, false | ||
} | ||
|
||
// buildColumns2Handle build columns to handle mapping. |
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.
s/ build/ builds
executor/update.go
Outdated
newRowsData [][]types.Datum // The new values to be set. | ||
fetched bool | ||
cursor int | ||
columns2Handle cols2HandleSlice |
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.
we need a comment for this attribute.
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.
rest LGTM
executor/builder.go
Outdated
} | ||
return updateExec | ||
} | ||
|
||
// cols2Handle represents an mapper from column index to handle index. | ||
type cols2Handle struct { | ||
start, end int32 // Represent the ordinal range [start, end) of the consecutive columns. |
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.
Put the comment on the top line of the attribute, since it's a complete sentence
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.
done
/run-all-tests |
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.
LGTM
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.
LGTM
What have you changed? (mandatory)
fixes #7176
question
fix update table with outer join, mainly fix two question
more cases can be seen in new unit test
change
right table
columnsWhat is the type of the changes? (mandatory)
How has this PR been tested? (mandatory)
Does this PR affect documentation (docs/docs-cn) update? (mandatory)
no
Does this PR need to be added to the release notes? (mandatory)
Yes
Refer to a related PR or issue link (optional)
#7176
Benchmark result if necessary (optional)
Add a few positive/negative examples (optional)
in testcase
This change is