Skip to content
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: refactor the code of building Insert. #7068

Merged
merged 9 commits into from
Jul 25, 2018

Conversation

winoros
Copy link
Member

@winoros winoros commented Jul 17, 2018

What have you changed? (mandatory)

Refactor the code when building Insert plan.
To fix some bugs make code more maintainable.
Fix #7064
Fix #7061
And another bug:
the table have 3 columns.
insert into t values(default, default, default, default) will make tidb panicked.

Main change:
We used to check the length of value_list and column_list when executing. Now we move this change to planner.

What is the type of the changes? (mandatory)

  • Refactor
  • Improvement
  • Bug fix

How has this PR been tested? (mandatory)

existing tests and newly added tests.

Does this PR need to be added to the release notes? (mandatory)

Fix the behavior of INSERT statement in some scenarios.

@winoros winoros added type/bugfix This PR fixes a bug. sig/planner SIG: Planner release-note Denotes a PR that will be considered when it comes time to generate release notes. labels Jul 17, 2018
@jackysp
Copy link
Member

jackysp commented Jul 17, 2018

Please fix CI @winoros

@winoros
Copy link
Member Author

winoros commented Jul 17, 2018

/run-all-tests

@winoros
Copy link
Member Author

winoros commented Jul 17, 2018

/run-integration-common-test tidb-test=pr/589

@winoros
Copy link
Member Author

winoros commented Jul 17, 2018

/run-integration-ddl-test tidb-test=pr/589

@winoros
Copy link
Member Author

winoros commented Jul 17, 2018

Integration-common-test is failed because the bug in tikv.
Now it's ready to review.

@@ -1332,7 +1332,7 @@ func (s *testPlanSuite) TestVisitInfo(c *C) {
ans []visitInfo
}{
{
sql: "insert into t values (1)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the old test result in an error after this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this the length of value_list is not same with the number of table's columns.

return onDupColSet, dupCols, nil
}

func (p *Insert) getAffectCols(insert *ast.InsertStmt) (affectedValuesCols []*table.Column, err error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to make the receiver of this function be the planBuilder.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about s/getAffectCols/getInsertColumns/?

}

} else if len(insert.Setlist) == 0 {
// Branch for `INSERT INTO tbl_name {VALUES | VALUE} (value_list) [, (value_list)] ...`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

// This is for the following scenario:
// 1. `INSERT INTO tbl_name {VALUES | VALUE} ...`
// 2. `INSERT INTO tbl_name SELECT ...`


func (p *Insert) getAffectCols(insert *ast.InsertStmt) (affectedValuesCols []*table.Column, err error) {
if len(insert.Columns) > 0 {
// Branch for `INSERT INTO tbl_name (col_name [, col_name] ...) {VALUES | VALUE} (value_list) [, (value_list)] ...`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

// This is for the following scenario:
// 1. `INSERT INTO tbl_name (col_name [, col_name] ...) {VALUES | VALUE} ...`
// 2. `INSERT INTO tbl_name (col_name [, col_name] ...) SELECT ...`

}
}
// If the value_list and col_list is empty and we have generated column, we can still write to this table.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment should reside to if len(insert.Columns) == 0 && len(insert.Lists[0]) == 0 branch? Maybe we can remove it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's for the else branch, explaining that else branch doesn't need to do anything.
I think it's useful. But maybe we can move it other place.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can put this comment to the if len(insert.Columns) > 0 || len(insert.Lists[0]) > 0 { to explain why we don't check whether there is generated column in the table when the value_list and col_list is empty.

// "insert into t values (), (1)" is not valid.
// "insert into t values (1), ()" is not valid.
// "insert into t values (1,2), (1)" is not valid.
if i > 0 && len(insert.Lists[i-1]) != len(valuesItem) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this form can help reading the code:

if i > 0 && len(insert.Lists[i]) != len(insert.Lists[i-1])

for j, valueItem := range valuesItem {
var expr expression.Expression
var err error
if dft, ok := valueItem.(*ast.DefaultExpr); ok {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually the abbreviation for default is def, see https://www.abbreviations.com/abbreviation/Default

for j, valueItem := range valuesItem {
var expr expression.Expression
var err error
if dft, ok := valueItem.(*ast.DefaultExpr); ok {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the "type switch" is more suitable.

return nil
totalTableCols := insertPlan.Table.Cols()
for i, valuesItem := range insert.Lists {
// The length of the value_list should keep unchanged.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

// The lengths of all the value lists should be the same, e.g:

if err != nil {
b.err = errors.Trace(err)
return nil
// Check that there's no generated column.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CaitinChen @lilin90 PTAL this comment, thanks

Copy link
Contributor

@CaitinChen CaitinChen Jul 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check to guarantee that no generated column exists.

Copy link
Member

@jackysp jackysp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@winoros
Copy link
Member Author

winoros commented Jul 24, 2018

PTAL @zz-jason

// i.e. table t have two column a and b where b is generated column.
// `insert into t (b) select * from t` will raise a error which tells you that the column count is not matched.
// `insert into t select * from t` will raise a error which tells you that there's generated column in column list.
// If we do this check before the above one, the first example will raise the error same with the second example.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CaitinChen Could help us to refine this comment block 😂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do this check before the above one, "insert into t (b) select * from t" will raise an error that there's a generated column in the column list.
Note: I don't use "the first example" and "the second example" because there is only one example. "insert into t (b) select * from t" and "insert into t select * from t" are not named as two examples. Both of them are in this example.

jackysp
jackysp previously approved these changes Jul 24, 2018
return nil
// Check to guarantee that there's no generated column.
// This check is done after the above one is to make compatible with mysql.
// i.e. table t have two column a and b where b is generated column.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, table t has two columns, namely a and b, and b is a generated column.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: "i.e." means "that is to say" or "namely".

// This check is done after the above one is to make compatible with mysql.
// i.e. table t have two column a and b where b is generated column.
// `insert into t (b) select * from t` will raise a error which tells you that the column count is not matched.
// `insert into t select * from t` will raise a error which tells you that there's generated column in column list.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"insert into t select * from t" will raise an error that there's a generated column in the column list.

// Check to guarantee that there's no generated column.
// This check is done after the above one is to make compatible with mysql.
// i.e. table t have two column a and b where b is generated column.
// `insert into t (b) select * from t` will raise a error which tells you that the column count is not matched.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"insert into t (b) select * from t" will raise an error that the column count is not matched.

Note: I use "insert into t (b) select * from t" instead of insert into t (b) select * from t because the inline code format will not be displayed in the code comments as expected.

// i.e. table t have two column a and b where b is generated column.
// `insert into t (b) select * from t` will raise a error which tells you that the column count is not matched.
// `insert into t select * from t` will raise a error which tells you that there's generated column in column list.
// If we do this check before the above one, the first example will raise the error same with the second example.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do this check before the above one, "insert into t (b) select * from t" will raise an error that there's a generated column in the column list.
Note: I don't use "the first example" and "the second example" because there is only one example. "insert into t (b) select * from t" and "insert into t select * from t" are not named as two examples. Both of them are in this example.

b.err = errors.Trace(err)
return nil
// Check to guarantee that there's no generated column.
// This check is done after the above one is to make compatible with mysql.
Copy link
Contributor

@CaitinChen CaitinChen Jul 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check should be done after the above one to make its behavior compatible with MySQL.

Copy link
Member

@zz-jason zz-jason left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@winoros
Copy link
Member Author

winoros commented Jul 25, 2018

@jackysp PTAL

Copy link
Member

@jackysp jackysp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jackysp
Copy link
Member

jackysp commented Jul 25, 2018

/run-all-tests

@jackysp jackysp added the status/LGT2 Indicates that a PR has LGTM 2. label Jul 25, 2018
@winoros
Copy link
Member Author

winoros commented Jul 25, 2018

/run-integration-ddl-test tidb-test=pr/589

@winoros winoros merged commit b5f9b35 into pingcap:master Jul 25, 2018
@winoros winoros deleted the insert-plan-refactor branch July 25, 2018 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/planner SIG: Planner status/LGT2 Indicates that a PR has LGTM 2. type/bugfix This PR fixes a bug.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Error when executing insert select where table has generated column Wrong behavior when insert default value
4 participants