Repository: pingcap/tidb (Go)

Issue:
batch point get should return error when column count doesn't match
## Bug Report

Please answer these questions before submitting your issue. Thanks!

### 1. Minimal reproduce step (Required)

```sql
create table t (a int, b int,primary key(a,b));
select * from t where (a,b) in (1,2);
```

<!-- a step by step guide for reproducing the bug. -->

### 2. What did you expect to see? (Required)

```sql
>select * from t where (a,b) in (1,2);
ERROR 1241 (21000): Operand should contain 2 column(s)
```

### 3. What did you see instead (Required)

```sql
>select * from t where (a,b) in (1,2);
+---+---+
| a | b |
+---+---+
0 rows in set
Time: 0.009s
```

### 4. What is your TiDB version? (Required)

<!-- Paste the output of SELECT tidb_version() -->

```sql

>select tidb_version();
+-------------------------------------------------------------------+
| tidb_version()                                                    |
+-------------------------------------------------------------------+
| Release Version: v4.0.0-beta.2-1920-g933e456be                    |
| Edition: Community                                                |
| Git Commit Hash: 933e456bebd7211fe82ffaaaec27147392b5e792         |
| Git Branch: master                                                |
| UTC Build Time: 2020-12-26 11:14:41                               |
| GoVersion: go1.14.6                                               |
| Race Enabled: false                                               |
| TiKV Min Version: v3.0.0-60965b006877ca7234adaced7890d7b029ed1306 |
| Check Table Before Drop: false                                    |
+-------------------------------------------------------------------+
1 row in set
```



Discussion:
## Please edit this comment or add a new comment to complete the following information

### Not a bug

1. Remove the 'type/bug' label
2. Add notes to indicate why it is not a bug

### Duplicate bug

1. Add the 'type/duplicate' label
2. Add the link to the original bug

### Bug

Note: Make Sure that 'component', and 'severity' labels are added
Example for how to fill out the template: https://github.com/pingcap/tidb/issues/20100

#### 1. Root Cause Analysis (RCA) (optional) 
<!-- Write down the reason why this bug occurs -->

#### 2. Symptom (optional)

<!-- What will the user see when this bug occurs. The error message may be in the terminal, log or monitoring -->

#### 3. All Trigger Conditions (optional)

<!-- All the user scenarios that may trigger this bug -->

#### 4. Workaround (optional)

#### 5. Affected versions

<!--
In the format of [start_version:end_version], multiple version ranges are
accepted. If the bug only affects the unreleased version, please input:
"unreleased". For example:

Notes:
  1. Do not use any white spaces in '[]'.
  2. The range in '[]' is a closed interval
  3. The version format is `v$Major.$Minor.$Patch`, the $Majoy and $Minor
     number in a version range should be the same. [v3.0.1:v3.1.2] is
     invalid because the $Minor number of the version range is different.

Example 1: [v3.0.1:v3.0.5], [v4.0.1:v4.0.5]
Example 2: unreleased
-->

#### 6. Fixed versions

<!--
The first released version that contains this fix in each minor version. If the bug's affected version has been released, the fixed version should be a detailed version number; If the bug doesn't affect any released version, the fixed version can be "master";  

Example 1: v3.0.13, v4.0.5
Example 2: master
-->



Patch:
diff --git a/planner/core/point_get_plan.go b/planner/core/point_get_plan.go
index 95fef18b04d59..abbd1ca8fdd32 100644
--- a/planner/core/point_get_plan.go
+++ b/planner/core/point_get_plan.go
@@ -619,11 +619,19 @@ func newBatchPointGetPlan(
 				}
 			}
 		case *driver.ValueExpr:
+			// if any item is `ValueExpr` type, `Expr` should contain only one column,
+			// otherwise column count doesn't match and no plan can be built.
+			if len(whereColNames) != 1 {
+				return nil
+			}
 			if !checkCanConvertInPointGet(colInfos[0], x.Datum) {
 				return nil
 			}
 			values = []types.Datum{x.Datum}
 		case *driver.ParamMarkerExpr:
+			if len(whereColNames) != 1 {
+				return nil
+			}
 			if !checkCanConvertInPointGet(colInfos[0], x.Datum) {
 				return nil
 			}


Test patch:
diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go
index 40dfb63da0718..c36cf1d6377c6 100644
--- a/planner/core/integration_test.go
+++ b/planner/core/integration_test.go
@@ -2339,3 +2339,22 @@ func (s *testIntegrationSuite) TestConvertRangeToPoint(c *C) {
 		tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...))
 	}
 }
+
+func (s *testIntegrationSuite) TestIssue22040(c *C) {
+	// #22040
+	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, primary key(a,b))")
+	// valid case
+	tk.MustExec("select * from t where (a,b) in ((1,2),(1,2))")
+	// invalid case, column count doesn't match
+	{
+		err := tk.ExecToErr("select * from t where (a,b) in (1,2)")
+		c.Assert(errors.Cause(err), FitsTypeOf, expression.ErrOperandColumns)
+	}
+	{
+		err := tk.ExecToErr("select * from t where (a,b) in ((1,2),1)")
+		c.Assert(errors.Cause(err), FitsTypeOf, expression.ErrOperandColumns)
+	}
+}
diff --git a/planner/core/logical_plan_test.go b/planner/core/logical_plan_test.go
index 51526f3a1b6a2..d350477bddfb1 100644
--- a/planner/core/logical_plan_test.go
+++ b/planner/core/logical_plan_test.go
@@ -1703,3 +1703,45 @@ func (s *testPlanSuite) TestResolvingCorrelatedAggregate(c *C) {
 		c.Assert(ToString(p), Equals, tt.best, comment)
 	}
 }
+
+func (s *testPlanSuite) TestFastPathInvalidBatchPointGet(c *C) {
+	// #22040
+	defer testleak.AfterTest(c)()
+	tt := []struct {
+		sql      string
+		fastPlan bool
+	}{
+		{
+			// column count doesn't match, not use idx
+			sql:      "select * from t where (a,b) in ((1,2),1)",
+			fastPlan: false,
+		},
+		{
+			// column count doesn't match, not use idx
+			sql:      "select * from t where (a,b) in (1,2)",
+			fastPlan: false,
+		},
+		{
+			// column count doesn't match, use idx
+			sql:      "select * from t where (f,g) in ((1,2),1)",
+			fastPlan: false,
+		},
+		{
+			// column count doesn't match, use idx
+			sql:      "select * from t where (f,g) in (1,2)",
+			fastPlan: false,
+		},
+	}
+	for i, tc := range tt {
+		comment := Commentf("case:%v sql:%s", i, tc.sql)
+		stmt, err := s.ParseOneStmt(tc.sql, "", "")
+		c.Assert(err, IsNil, comment)
+		c.Assert(Preprocess(s.ctx, stmt, s.is), IsNil, comment)
+		plan := TryFastPlan(s.ctx, stmt)
+		if tc.fastPlan {
+			c.Assert(plan, NotNil)
+		} else {
+			c.Assert(plan, IsNil)
+		}
+	}
+}