diff --git a/go/vt/sqlparser/ast.go b/go/vt/sqlparser/ast.go index df3dfd75dd8..7881f24398a 100644 --- a/go/vt/sqlparser/ast.go +++ b/go/vt/sqlparser/ast.go @@ -2379,6 +2379,32 @@ func (node *ComparisonExpr) replace(from, to Expr) bool { return replaceExprs(from, to, &node.Left, &node.Right, &node.Escape) } +// IsImpossible returns true if the comparison in the expression can never evaluate to true. +// Note that this is not currently exhaustive to ALL impossible comparisons. +func (node *ComparisonExpr) IsImpossible() bool { + var left, right *SQLVal + var ok bool + if left, ok = node.Left.(*SQLVal); !ok { + return false + } + if right, ok = node.Right.(*SQLVal); !ok { + return false + } + if node.Operator == NotEqualStr && left.Type == right.Type { + if len(left.Val) != len(right.Val) { + return false + } + + for i := range left.Val { + if left.Val[i] != right.Val[i] { + return false + } + } + return true + } + return false +} + // RangeCond represents a BETWEEN or a NOT BETWEEN expression. type RangeCond struct { Operator string diff --git a/go/vt/sqlparser/ast_test.go b/go/vt/sqlparser/ast_test.go index 08bd05d96d8..cf66c254001 100644 --- a/go/vt/sqlparser/ast_test.go +++ b/go/vt/sqlparser/ast_test.go @@ -412,6 +412,35 @@ func TestIsAggregate(t *testing.T) { } } +func TestIsImpossible(t *testing.T) { + f := ComparisonExpr{ + Operator: NotEqualStr, + Left: newIntVal("1"), + Right: newIntVal("1"), + } + if !f.IsImpossible() { + t.Error("IsImpossible: false, want true") + } + + f = ComparisonExpr{ + Operator: EqualStr, + Left: newIntVal("1"), + Right: newIntVal("1"), + } + if f.IsImpossible() { + t.Error("IsImpossible: true, want false") + } + + f = ComparisonExpr{ + Operator: NotEqualStr, + Left: newIntVal("1"), + Right: newIntVal("2"), + } + if f.IsImpossible() { + t.Error("IsImpossible: true, want false") + } +} + func TestReplaceExpr(t *testing.T) { tcases := []struct { in, out string diff --git a/go/vt/vttablet/endtoend/queries_test.go b/go/vt/vttablet/endtoend/queries_test.go index 56021b831da..f407e08b91f 100644 --- a/go/vt/vttablet/endtoend/queries_test.go +++ b/go/vt/vttablet/endtoend/queries_test.go @@ -1789,6 +1789,38 @@ func TestQueries(t *testing.T) { }, }, }, + &framework.MultiCase{ + Name: "impossible queries", + Cases: []framework.Testable{ + &framework.TestCase{ + Name: "specific column", + Query: "select eid from vitess_a where 1 != 1", + Rewritten: []string{ + "select eid from vitess_a where 1 != 1", + }, + RowsAffected: 0, + }, + &framework.TestCase{ + Name: "all columns", + Query: "select * from vitess_a where 1 != 1", + Rewritten: []string{ + "select * from vitess_a where 1 != 1", + }, + RowsAffected: 0, + }, + &framework.TestCase{ + Name: "bind vars", + Query: "select :bv from vitess_a where 1 != 1", + BindVars: map[string]*querypb.BindVariable{ + "bv": sqltypes.Int64BindVariable(1), + }, + Rewritten: []string{ + "select 1 from vitess_a where 1 != 1 limit 10001", + }, + RowsAffected: 0, + }, + }, + }, } for _, tcase := range testCases { if err := tcase.Test("", client); err != nil { diff --git a/go/vt/vttablet/tabletserver/planbuilder/dml.go b/go/vt/vttablet/tabletserver/planbuilder/dml.go index bdaed0ac5f8..c1cadcc4d73 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/dml.go +++ b/go/vt/vttablet/tabletserver/planbuilder/dml.go @@ -202,6 +202,14 @@ func analyzeSelect(sel *sqlparser.Select, tables map[string]*schema.Table) (plan return nil, err } + if sel.Where != nil { + comp, ok := sel.Where.Expr.(*sqlparser.ComparisonExpr) + if ok && comp.IsImpossible() { + plan.PlanID = PlanSelectImpossible + return plan, nil + } + } + // Check if it's a NEXT VALUE statement. if nextVal, ok := sel.SelectExprs[0].(sqlparser.Nextval); ok { if table.Type != schema.Sequence { diff --git a/go/vt/vttablet/tabletserver/planbuilder/plan.go b/go/vt/vttablet/tabletserver/planbuilder/plan.go index bbeecd963ea..befeae05e40 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/plan.go +++ b/go/vt/vttablet/tabletserver/planbuilder/plan.go @@ -83,6 +83,8 @@ const ( PlanOtherAdmin // PlanMessageStream is used for streaming messages. PlanMessageStream + // PlanSelectImpossible is used for where or having clauses that can never be true. + PlanSelectImpossible // NumPlans stores the total number of plans NumPlans ) @@ -105,6 +107,7 @@ var planName = [NumPlans]string{ "OTHER_READ", "OTHER_ADMIN", "MESSAGE_STREAM", + "SELECT_IMPOSSIBLE", } func (pt PlanType) String() string { @@ -126,7 +129,7 @@ func PlanByName(s string) (pt PlanType, ok bool) { // IsSelect returns true if PlanType is about a select query. func (pt PlanType) IsSelect() bool { - return pt == PlanPassSelect || pt == PlanSelectLock + return pt == PlanPassSelect || pt == PlanSelectLock || pt == PlanSelectImpossible } // MarshalJSON returns a json string for PlanType. @@ -140,22 +143,23 @@ func (pt PlanType) MinRole() tableacl.Role { } var tableACLRoles = map[PlanType]tableacl.Role{ - PlanPassSelect: tableacl.READER, - PlanSelectLock: tableacl.READER, - PlanSet: tableacl.READER, - PlanPassDML: tableacl.WRITER, - PlanDMLPK: tableacl.WRITER, - PlanDMLSubquery: tableacl.WRITER, - PlanInsertPK: tableacl.WRITER, - PlanInsertSubquery: tableacl.WRITER, - PlanInsertMessage: tableacl.WRITER, - PlanDDL: tableacl.ADMIN, - PlanSelectStream: tableacl.READER, - PlanOtherRead: tableacl.READER, - PlanOtherAdmin: tableacl.ADMIN, - PlanUpsertPK: tableacl.WRITER, - PlanNextval: tableacl.WRITER, - PlanMessageStream: tableacl.WRITER, + PlanPassSelect: tableacl.READER, + PlanSelectLock: tableacl.READER, + PlanSet: tableacl.READER, + PlanPassDML: tableacl.WRITER, + PlanDMLPK: tableacl.WRITER, + PlanDMLSubquery: tableacl.WRITER, + PlanInsertPK: tableacl.WRITER, + PlanInsertSubquery: tableacl.WRITER, + PlanInsertMessage: tableacl.WRITER, + PlanDDL: tableacl.ADMIN, + PlanSelectStream: tableacl.READER, + PlanOtherRead: tableacl.READER, + PlanOtherAdmin: tableacl.ADMIN, + PlanUpsertPK: tableacl.WRITER, + PlanNextval: tableacl.WRITER, + PlanMessageStream: tableacl.WRITER, + PlanSelectImpossible: tableacl.READER, } //_______________________________________________ diff --git a/go/vt/vttablet/tabletserver/query_executor.go b/go/vt/vttablet/tabletserver/query_executor.go index 28b9c42eeec..6e8747e952c 100644 --- a/go/vt/vttablet/tabletserver/query_executor.go +++ b/go/vt/vttablet/tabletserver/query_executor.go @@ -106,6 +106,17 @@ func (qre *QueryExecutor) Execute() (reply *sqltypes.Result, err error) { return qre.execDDL() case planbuilder.PlanNextval: return qre.execNextval() + case planbuilder.PlanSelectImpossible: + if qre.plan.Fields != nil { + return &sqltypes.Result{ + Fields: qre.plan.Fields, + RowsAffected: 0, + InsertID: 0, + Rows: nil, + Extras: nil, + }, nil + } + break } if qre.transactionID != 0 { @@ -137,7 +148,7 @@ func (qre *QueryExecutor) Execute() (reply *sqltypes.Result, err error) { return qre.execUpsertPK(conn) case planbuilder.PlanSet: return qre.txFetch(conn, qre.plan.FullQuery, qre.bindVars, nil, nil, true, true) - case planbuilder.PlanPassSelect, planbuilder.PlanSelectLock: + case planbuilder.PlanPassSelect, planbuilder.PlanSelectLock, planbuilder.PlanSelectImpossible: return qre.execDirect(conn) default: // handled above: @@ -151,7 +162,7 @@ func (qre *QueryExecutor) Execute() (reply *sqltypes.Result, err error) { } } else { switch qre.plan.PlanID { - case planbuilder.PlanPassSelect: + case planbuilder.PlanPassSelect, planbuilder.PlanSelectImpossible: return qre.execSelect() case planbuilder.PlanSelectLock: return nil, vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "%s disallowed outside transaction", qre.plan.PlanID.String()) diff --git a/go/vt/vttablet/tabletserver/query_executor_test.go b/go/vt/vttablet/tabletserver/query_executor_test.go index 84ff6fd6016..334c026a28b 100644 --- a/go/vt/vttablet/tabletserver/query_executor_test.go +++ b/go/vt/vttablet/tabletserver/query_executor_test.go @@ -1242,6 +1242,31 @@ func TestQueryExecutorPlanPassSelect(t *testing.T) { } } +func TestQueryExecutorPlanSelectImpossible(t *testing.T) { + db := setUpQueryExecutorTest(t) + defer db.Close() + query := "select * from test_table where 1 != 1" + want := &sqltypes.Result{ + Fields: getTestTableFields(), + } + db.AddQuery(query, want) + db.AddQuery("select * from test_table where 1 != 1", &sqltypes.Result{ + Fields: getTestTableFields(), + }) + ctx := context.Background() + tsv := newTestTabletServer(ctx, noFlags, db) + qre := newTestQueryExecutor(ctx, tsv, query, 0) + defer tsv.StopService() + checkPlanID(t, planbuilder.PlanSelectImpossible, qre.plan.PlanID) + got, err := qre.Execute() + if err != nil { + t.Fatalf("qre.Execute() = %v, want nil", err) + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("got: %v, want: %v", got, want) + } +} + func TestQueryExecutorPlanPassSelectSqlSelectLimit(t *testing.T) { db := setUpQueryExecutorTest(t) defer db.Close() @@ -1732,7 +1757,7 @@ func TestQueryExecutorTableAclDualTableExempt(t *testing.T) { query := "select * from test_table where 1 != 1" qre := newTestQueryExecutor(ctx, tsv, query, 0) defer tsv.StopService() - checkPlanID(t, planbuilder.PlanPassSelect, qre.plan.PlanID) + checkPlanID(t, planbuilder.PlanSelectImpossible, qre.plan.PlanID) // query should fail because nobody has read access to test_table _, err := qre.Execute() if code := vterrors.Code(err); code != vtrpcpb.Code_PERMISSION_DENIED {