Repository: vitessio/vitess
Pull request #11709 (2022-11-15 12:21:41)

query timeout hints in unsharded cases
<!--
  Thank you for your contribution to the Vitess project.
  How to contribute: https://vitess.io/docs/contributing/
  Please first make sure there is an open Issue to discuss the feature/fix suggested in this PR.
  If this is a new feature, please mark the Issue as "RFC".
 -->

<!-- if this PR is Work in Progress please create it as a Draft Pull Request -->

## Description

<!-- A few sentences describing the overall goals of the pull request's commits. -->
<!-- If this is a bug fix and you think the fix should be backported, please write so. -->

This PR is an extension of https://github.com/vitessio/vitess/pull/11429. The unsharded cases in select gen4 planner and insert planner were missing the usage of query_timeout_ms comment directive.
This PR fixes that.

## Related Issue(s)

<!-- List related issues and pull requests. If this PR fixes an issue, please add it using Fixes #????  -->
- Fixes https://github.com/vitessio/vitess/issues/11710

## Checklist

-   [X] Tests were added or are not required
-   [X] Documentation was added or is not required

## Deployment Notes

<!-- Notes regarding deployment of the contained body of work. These should note any db migrations, etc. -->


vitess-bot[bot]: ### Review Checklist
            
Hello reviewers! :wave: Please follow this checklist when reviewing this Pull Request.

#### General
- [ ] Ensure that the Pull Request has a descriptive title.
- [ ] If this is a change that users need to know about, please apply the `release notes (needs details)` label so that merging is blocked unless the summary release notes document is included.

#### If a new flag is being introduced:
- [ ] Is it really necessary to add this flag?
- [ ] Flag names should be clear and intuitive (as far as possible)
- [ ] Help text should be descriptive.
- [ ] Flag names should use dashes (`-`) as word separators rather than underscores (`_`).

#### If a workflow is added or modified:
- [ ] Each item in `Jobs` should be named in order to mark it as `required`.
- [ ] If the workflow should be required, the maintainer team should be notified.

#### Bug fixes
- [ ] There should be at least one unit or end-to-end test.
- [ ] The Pull Request description should include a link to an issue that describes the bug.

#### Non-trivial changes
- [ ] There should be some code comments as to why things are implemented the way they are.

#### New/Existing features
- [ ] Should be documented, either by modifying the existing documentation or creating new documentation.
- [ ] New features should have a link to a feature request issue or an RFC that documents the use cases, corner cases and test cases.

#### Backward compatibility            
- [ ] Protobuf changes should be wire-compatible.
- [ ] Changes to `_vt` tables and RPCs need to be backward compatible.
- [ ] `vtctl` command output order should be stable and `awk`-able.
- [ ] RPC changes should be compatible with vitess-operator
- [ ] If a flag is removed, then it should also be removed from VTop, if used there.


Patch:
diff --git a/go/vt/vtgate/planbuilder/gen4_planner.go b/go/vt/vtgate/planbuilder/gen4_planner.go
index 60ee65e843..6d9a7ae28f 100644
--- a/go/vt/vtgate/planbuilder/gen4_planner.go
+++ b/go/vt/vtgate/planbuilder/gen4_planner.go
@@ -173,12 +173,12 @@ func newBuildSelectPlan(
 	reservedVars *sqlparser.ReservedVars,
 	vschema plancontext.VSchema,
 	version querypb.ExecuteOptions_PlannerVersion,
-) (logicalPlan, *semantics.SemTable, error) {
+) (plan logicalPlan, semTable *semantics.SemTable, err error) {
 	ksName := ""
 	if ks, _ := vschema.DefaultKeyspace(); ks != nil {
 		ksName = ks.Name
 	}
-	semTable, err := semantics.Analyze(selStmt, ksName, vschema)
+	semTable, err = semantics.Analyze(selStmt, ksName, vschema)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -188,7 +188,14 @@ func newBuildSelectPlan(
 	ctx := plancontext.NewPlanningContext(reservedVars, semTable, vschema, version)
 
 	if ks, _ := semTable.SingleUnshardedKeyspace(); ks != nil {
-		plan, err := unshardedShortcut(ctx, selStmt, ks)
+		plan, err = unshardedShortcut(ctx, selStmt, ks)
+		if err != nil {
+			return nil, nil, err
+		}
+		plan, err = pushCommentDirectivesOnPlan(plan, selStmt)
+		if err != nil {
+			return nil, nil, err
+		}
 		return plan, semTable, err
 	}
 
@@ -207,7 +214,7 @@ func newBuildSelectPlan(
 		return nil, nil, err
 	}
 
-	plan, err := transformToLogicalPlan(ctx, op, true)
+	plan, err = transformToLogicalPlan(ctx, op, true)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -216,12 +223,12 @@ func newBuildSelectPlan(
 
 	sel, isSel := selStmt.(*sqlparser.Select)
 	if isSel {
-		if err := setMiscFunc(plan, sel); err != nil {
+		if err = setMiscFunc(plan, sel); err != nil {
 			return nil, nil, err
 		}
 	}
 
-	if err := plan.WireupGen4(ctx); err != nil {
+	if err = plan.WireupGen4(ctx); err != nil {
 		return nil, nil, err
 	}
 
diff --git a/go/vt/vtgate/planbuilder/insert.go b/go/vt/vtgate/planbuilder/insert.go
index ec9dc67065..7a0ab6dcf8 100644
--- a/go/vt/vtgate/planbuilder/insert.go
+++ b/go/vt/vtgate/planbuilder/insert.go
@@ -73,6 +73,8 @@ func buildInsertUnshardedPlan(ins *sqlparser.Insert, table *vindexes.Table, rese
 		table,
 		table.Keyspace,
 	)
+	applyCommentDirectives(ins, eins)
+
 	var rows sqlparser.Values
 	tc := &tableCollector{}
 	tc.addVindexTable(table)
diff --git a/go/vt/vtgate/semantics/early_rewriter.go b/go/vt/vtgate/semantics/early_rewriter.go
index edb085e58e..cd36962d74 100644
--- a/go/vt/vtgate/semantics/early_rewriter.go
+++ b/go/vt/vtgate/semantics/early_rewriter.go
@@ -19,6 +19,8 @@ package semantics
 import (
 	"strconv"
 
+	"vitess.io/vitess/go/vt/vtgate/evalengine"
+
 	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
 	"vitess.io/vitess/go/vt/sqlparser"
 	"vitess.io/vitess/go/vt/vterrors"
@@ -56,6 +58,11 @@ func (r *earlyRewriter) down(cursor *sqlparser.Cursor) error {
 	case sqlparser.OrderBy:
 		r.clause = "order clause"
 		rewriteHavingAndOrderBy(cursor, node)
+	case *sqlparser.OrExpr:
+		newNode := rewriteOrFalse(*node)
+		if newNode != nil {
+			cursor.Replace(newNode)
+		}
 	case sqlparser.GroupBy:
 		r.clause = "group statement"
 
@@ -236,6 +243,37 @@ func realCloneOfColNames(expr sqlparser.Expr, union bool) sqlparser.Expr {
 	}, nil).(sqlparser.Expr)
 }
 
+func rewriteOrFalse(orExpr sqlparser.OrExpr) sqlparser.Expr {
+	// we are looking for the pattern `WHERE c = 1 OR 1 = 0`
+	isFalse := func(subExpr sqlparser.Expr) bool {
+		evalEnginePred, err := evalengine.Translate(subExpr, nil)
+		if err != nil {
+			return false
+		}
+
+		env := evalengine.EmptyExpressionEnv()
+		res, err := env.Evaluate(evalEnginePred)
+		if err != nil {
+			return false
+		}
+
+		boolValue, err := res.Value().ToBool()
+		if err != nil {
+			return false
+		}
+
+		return !boolValue
+	}
+
+	if isFalse(orExpr.Left) {
+		return orExpr.Right
+	} else if isFalse(orExpr.Right) {
+		return orExpr.Left
+	}
+
+	return nil
+}
+
 func rewriteJoinUsing(
 	current *scope,
 	using sqlparser.Columns,


Test patch:
diff --git a/go/test/endtoend/cluster/topo_process.go b/go/test/endtoend/cluster/topo_process.go
index bb5901db9c..7326aa57a5 100644
--- a/go/test/endtoend/cluster/topo_process.go
+++ b/go/test/endtoend/cluster/topo_process.go
@@ -103,6 +103,7 @@ func (topo *TopoProcess) SetupEtcd() (err error) {
 	topo.exit = make(chan error)
 	go func() {
 		topo.exit <- topo.proc.Wait()
+		close(topo.exit)
 	}()
 
 	timeout := time.Now().Add(60 * time.Second)
@@ -227,6 +228,7 @@ func (topo *TopoProcess) SetupConsul(cluster *LocalProcessCluster) (err error) {
 	topo.exit = make(chan error)
 	go func() {
 		topo.exit <- topo.proc.Wait()
+		close(topo.exit)
 	}()
 
 	timeout := time.Now().Add(60 * time.Second)
@@ -289,8 +291,9 @@ func (topo *TopoProcess) TearDown(Cell string, originalVtRoot string, currentRoo
 
 		case <-time.After(10 * time.Second):
 			topo.proc.Process.Kill()
+			err := <-topo.exit
 			topo.proc = nil
-			return <-topo.exit
+			return err
 		}
 	}
 
diff --git a/go/test/endtoend/cluster/vtbackup_process.go b/go/test/endtoend/cluster/vtbackup_process.go
index 73253d3be3..b7beed6793 100644
--- a/go/test/endtoend/cluster/vtbackup_process.go
+++ b/go/test/endtoend/cluster/vtbackup_process.go
@@ -92,6 +92,14 @@ func (vtbackup *VtbackupProcess) Setup() (err error) {
 		return
 	}
 
+	vtbackup.exit = make(chan error)
+	go func() {
+		if vtbackup.proc != nil {
+			vtbackup.exit <- vtbackup.proc.Wait()
+			close(vtbackup.exit)
+		}
+	}()
+
 	return nil
 }
 
@@ -111,8 +119,9 @@ func (vtbackup *VtbackupProcess) TearDown() error {
 
 	case <-time.After(10 * time.Second):
 		vtbackup.proc.Process.Kill()
+		err := <-vtbackup.exit
 		vtbackup.proc = nil
-		return <-vtbackup.exit
+		return err
 	}
 }
 
diff --git a/go/test/endtoend/cluster/vtctld_process.go b/go/test/endtoend/cluster/vtctld_process.go
index 074234fb97..3656b7b3ec 100644
--- a/go/test/endtoend/cluster/vtctld_process.go
+++ b/go/test/endtoend/cluster/vtctld_process.go
@@ -87,6 +87,7 @@ func (vtctld *VtctldProcess) Setup(cell string, extraArgs ...string) (err error)
 	vtctld.exit = make(chan error)
 	go func() {
 		vtctld.exit <- vtctld.proc.Wait()
+		close(vtctld.exit)
 	}()
 
 	timeout := time.Now().Add(60 * time.Second)
@@ -138,8 +139,9 @@ func (vtctld *VtctldProcess) TearDown() error {
 
 	case <-time.After(10 * time.Second):
 		vtctld.proc.Process.Kill()
+		err := <-vtctld.exit
 		vtctld.proc = nil
-		return <-vtctld.exit
+		return err
 	}
 }
 
diff --git a/go/test/endtoend/cluster/vtgate_process.go b/go/test/endtoend/cluster/vtgate_process.go
index 54166f47fa..80e2d4cc4d 100644
--- a/go/test/endtoend/cluster/vtgate_process.go
+++ b/go/test/endtoend/cluster/vtgate_process.go
@@ -138,6 +138,7 @@ func (vtgate *VtgateProcess) Setup() (err error) {
 	go func() {
 		if vtgate.proc != nil {
 			vtgate.exit <- vtgate.proc.Wait()
+			close(vtgate.exit)
 		}
 	}()
 
@@ -236,8 +237,9 @@ func (vtgate *VtgateProcess) TearDown() error {
 
 	case <-time.After(30 * time.Second):
 		vtgate.proc.Process.Kill()
+		err := <-vtgate.exit
 		vtgate.proc = nil
-		return <-vtgate.exit
+		return err
 	}
 }
 
diff --git a/go/test/endtoend/cluster/vtgr_process.go b/go/test/endtoend/cluster/vtgr_process.go
index f2205a9323..1960e46948 100644
--- a/go/test/endtoend/cluster/vtgr_process.go
+++ b/go/test/endtoend/cluster/vtgr_process.go
@@ -77,6 +77,7 @@ func (vtgr *VtgrProcess) Start(alias string) (err error) {
 	go func() {
 		if vtgr.proc != nil {
 			vtgr.exit <- vtgr.proc.Wait()
+			close(vtgr.exit)
 		}
 	}()
 
@@ -97,8 +98,9 @@ func (vtgr *VtgrProcess) TearDown() error {
 		return nil
 
 	case <-time.After(10 * time.Second):
-		_ = vtgr.proc.Process.Kill()
+		vtgr.proc.Process.Kill()
+		err := <-vtgr.exit
 		vtgr.proc = nil
-		return <-vtgr.exit
+		return err
 	}
 }
diff --git a/go/test/endtoend/cluster/vtorc_process.go b/go/test/endtoend/cluster/vtorc_process.go
index 14f475fa48..e0a384bdbc 100644
--- a/go/test/endtoend/cluster/vtorc_process.go
+++ b/go/test/endtoend/cluster/vtorc_process.go
@@ -139,6 +139,7 @@ func (orc *VTOrcProcess) Setup() (err error) {
 	go func() {
 		if orc.proc != nil {
 			orc.exit <- orc.proc.Wait()
+			close(orc.exit)
 		}
 	}()
 
@@ -160,8 +161,9 @@ func (orc *VTOrcProcess) TearDown() error {
 
 	case <-time.After(30 * time.Second):
 		_ = orc.proc.Process.Kill()
+		err := <-orc.exit
 		orc.proc = nil
-		return <-orc.exit
+		return err
 	}
 }
 
diff --git a/go/test/endtoend/cluster/vttablet_process.go b/go/test/endtoend/cluster/vttablet_process.go
index 77e3e738a5..a5eba51f22 100644
--- a/go/test/endtoend/cluster/vttablet_process.go
+++ b/go/test/endtoend/cluster/vttablet_process.go
@@ -145,6 +145,7 @@ func (vttablet *VttabletProcess) Setup() (err error) {
 	go func() {
 		if vttablet.proc != nil {
 			vttablet.exit <- vttablet.proc.Wait()
+			close(vttablet.exit)
 		}
 	}()
 
@@ -399,12 +400,10 @@ func (vttablet *VttabletProcess) TearDownWithTimeout(timeout time.Duration) erro
 		return nil
 
 	case <-time.After(timeout):
-		proc := vttablet.proc
-		if proc != nil {
-			vttablet.proc.Process.Kill()
-			vttablet.proc = nil
-		}
-		return <-vttablet.exit
+		vttablet.proc.Process.Kill()
+		err := <-vttablet.exit
+		vttablet.proc = nil
+		return err
 	}
 }
 
diff --git a/go/test/endtoend/vtgate/queries/misc/main_test.go b/go/test/endtoend/vtgate/queries/misc/main_test.go
index 99984153bd..de2d00219b 100644
--- a/go/test/endtoend/vtgate/queries/misc/main_test.go
+++ b/go/test/endtoend/vtgate/queries/misc/main_test.go
@@ -34,8 +34,12 @@ var (
 	vtParams        mysql.ConnParams
 	mysqlParams     mysql.ConnParams
 	keyspaceName    = "ks_misc"
+	uks             = "uks"
 	cell            = "test_misc"
 
+	//go:embed uschema.sql
+	uschemaSQL string
+
 	//go:embed schema.sql
 	schemaSQL string
 
@@ -57,6 +61,19 @@ func TestMain(m *testing.M) {
 			return 1
 		}
 
+		clusterInstance.VtTabletExtraArgs = append(clusterInstance.VtTabletExtraArgs, "--queryserver-config-max-result-size", "1000000",
+			"--queryserver-config-query-timeout", "200",
+			"--queryserver-config-query-pool-timeout", "200")
+		// Start Unsharded keyspace
+		ukeyspace := &cluster.Keyspace{
+			Name:      uks,
+			SchemaSQL: uschemaSQL,
+		}
+		err = clusterInstance.StartUnshardedKeyspace(*ukeyspace, 0, false)
+		if err != nil {
+			return 1
+		}
+
 		// Start keyspace
 		keyspace := &cluster.Keyspace{
 			Name:      keyspaceName,
diff --git a/go/test/endtoend/vtgate/queries/misc/misc_test.go b/go/test/endtoend/vtgate/queries/misc/misc_test.go
index 4b3903cbf3..ff6798c6af 100644
--- a/go/test/endtoend/vtgate/queries/misc/misc_test.go
+++ b/go/test/endtoend/vtgate/queries/misc/misc_test.go
@@ -17,6 +17,8 @@ limitations under the License.
 package misc
 
 import (
+	"fmt"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -91,7 +93,7 @@ func TestInvalidDateTimeTimestampVals(t *testing.T) {
 	require.Error(t, err)
 }
 
-func TestQueryTimeout(t *testing.T) {
+func TestQueryTimeoutWithDual(t *testing.T) {
 	mcmp, closer := start(t)
 	defer closer()
 
@@ -112,3 +114,43 @@ func TestQueryTimeout(t *testing.T) {
 	_, err = utils.ExecAllowError(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 QUERY_TIMEOUT_MS=10 */ sleep(0.001) from dual")
 	assert.NoError(t, err)
 }
+
+func TestQueryTimeoutWithTables(t *testing.T) {
+	mcmp, closer := start(t)
+	defer closer()
+
+	// unsharded
+	utils.Exec(t, mcmp.VtConn, "insert /*vt+ QUERY_TIMEOUT_MS=1000 */ into uks.unsharded(id1) values (1),(2),(3),(4),(5)")
+	for i := 0; i < 12; i++ {
+		utils.Exec(t, mcmp.VtConn, "insert /*vt+ QUERY_TIMEOUT_MS=1000 */ into uks.unsharded(id1) select id1+5 from uks.unsharded")
+	}
+
+	utils.Exec(t, mcmp.VtConn, "select count(*) from uks.unsharded where id1 > 31")
+	utils.Exec(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 QUERY_TIMEOUT_MS=100 */ count(*) from uks.unsharded where id1 > 31")
+
+	// the query usually takes more than 5ms to return. So this should fail.
+	_, err := utils.ExecAllowError(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 QUERY_TIMEOUT_MS=1 */ count(*) from uks.unsharded where id1 > 31")
+	require.Error(t, err)
+	assert.Contains(t, err.Error(), "context deadline exceeded (errno 1317) (sqlstate 70100)")
+
+	// sharded
+	for i := 0; i < 300000; i += 1000 {
+		var str strings.Builder
+		for j := 1; j <= 1000; j++ {
+			if j == 1 {
+				str.WriteString(fmt.Sprintf("(%d)", i*1000+j))
+				continue
+			}
+			str.WriteString(fmt.Sprintf(",(%d)", i*1000+j))
+		}
+		utils.Exec(t, mcmp.VtConn, fmt.Sprintf("insert /*vt+ QUERY_TIMEOUT_MS=1000 */ into t1(id1) values %s", str.String()))
+	}
+
+	utils.Exec(t, mcmp.VtConn, "select count(*) from t1 where id1 > 31")
+	utils.Exec(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 QUERY_TIMEOUT_MS=100 */ count(*) from t1 where id1 > 31")
+
+	// the query usually takes more than 5ms to return. So this should fail.
+	_, err = utils.ExecAllowError(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 QUERY_TIMEOUT_MS=1 */ count(*) from t1 where id1 > 31")
+	require.Error(t, err)
+	assert.Contains(t, err.Error(), "context deadline exceeded (errno 1317) (sqlstate 70100)")
+}
diff --git a/go/test/endtoend/vtgate/queries/misc/uschema.sql b/go/test/endtoend/vtgate/queries/misc/uschema.sql
new file mode 100644
index 0000000000..6ba158b134
--- /dev/null
+++ b/go/test/endtoend/vtgate/queries/misc/uschema.sql
@@ -0,0 +1,5 @@
+create table unsharded(
+    id1 bigint,
+    id2 bigint,
+    key(id1)
+) Engine=InnoDB;
\ No newline at end of file
diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.json b/go/vt/vtgate/planbuilder/testdata/select_cases.json
index 5157e1acc9..c6e0c2fd58 100644
--- a/go/vt/vtgate/planbuilder/testdata/select_cases.json
+++ b/go/vt/vtgate/planbuilder/testdata/select_cases.json
@@ -253,6 +253,45 @@
       ]
     }
   },
+  {
+    "comment": "select limit with timeout directive sets QueryTimeout in the route",
+    "query": "select /*vt+ QUERY_TIMEOUT_MS=1000 */ * from main.unsharded limit 10",
+    "v3-plan": {
+      "QueryType": "SELECT",
+      "Original": "select /*vt+ QUERY_TIMEOUT_MS=1000 */ * from main.unsharded limit 10",
+      "Instructions": {
+        "OperatorType": "Route",
+        "Variant": "Unsharded",
+        "Keyspace": {
+          "Name": "main",
+          "Sharded": false
+        },
+        "FieldQuery": "select * from unsharded where 1 != 1",
+        "Query": "select /*vt+ QUERY_TIMEOUT_MS=1000 */ * from unsharded limit 10",
+        "QueryTimeout": 1000,
+        "Table": "unsharded"
+      }
+    },
+    "gen4-plan": {
+      "QueryType": "SELECT",
+      "Original": "select /*vt+ QUERY_TIMEOUT_MS=1000 */ * from main.unsharded limit 10",
+      "Instructions": {
+        "OperatorType": "Route",
+        "Variant": "Unsharded",
+        "Keyspace": {
+          "Name": "main",
+          "Sharded": false
+        },
+        "FieldQuery": "select * from unsharded where 1 != 1",
+        "Query": "select /*vt+ QUERY_TIMEOUT_MS=1000 */ * from unsharded limit 10",
+        "QueryTimeout": 1000,
+        "Table": "unsharded"
+      },
+      "TablesUsed": [
+        "main.unsharded"
+      ]
+    }
+  },
   {
     "comment": "select with partial scatter directive",
     "query": "select /*vt+ SCATTER_ERRORS_AS_WARNINGS */ * from user",
@@ -7870,5 +7909,87 @@
         "main.dual"
       ]
     }
+  },
+  {
+    "comment": "(OR 1 = 0) doesn't cause unnecessary scatter",
+    "query": "select * from user where id = 1 or 1 = 0",
+    "v3-plan": {
+      "QueryType": "SELECT",
+      "Original": "select * from user where id = 1 or 1 = 0",
+      "Instructions": {
+        "OperatorType": "Route",
+        "Variant": "Scatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select * from `user` where 1 != 1",
+        "Query": "select * from `user` where id = 1 or 1 = 0",
+        "Table": "`user`"
+      }
+    },
+    "gen4-plan": {
+      "QueryType": "SELECT",
+      "Original": "select * from user where id = 1 or 1 = 0",
+      "Instructions": {
+        "OperatorType": "Route",
+        "Variant": "EqualUnique",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select * from `user` where 1 != 1",
+        "Query": "select * from `user` where id = 1",
+        "Table": "`user`",
+        "Values": [
+          "INT64(1)"
+        ],
+        "Vindex": "user_index"
+      },
+      "TablesUsed": [
+        "user.user"
+      ]
+    }
+  },
+  {
+    "comment": "(OR 2 < 1) doesn't cause unnecessary scatter",
+    "query": "select * from user where id = 1 or 2 < 1",
+    "v3-plan": {
+      "QueryType": "SELECT",
+      "Original": "select * from user where id = 1 or 2 < 1",
+      "Instructions": {
+        "OperatorType": "Route",
+        "Variant": "Scatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select * from `user` where 1 != 1",
+        "Query": "select * from `user` where id = 1 or 2 < 1",
+        "Table": "`user`"
+      }
+    },
+    "gen4-plan": {
+      "QueryType": "SELECT",
+      "Original": "select * from user where id = 1 or 2 < 1",
+      "Instructions": {
+        "OperatorType": "Route",
+        "Variant": "EqualUnique",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select * from `user` where 1 != 1",
+        "Query": "select * from `user` where id = 1",
+        "Table": "`user`",
+        "Values": [
+          "INT64(1)"
+        ],
+        "Vindex": "user_index"
+      },
+      "TablesUsed": [
+        "user.user"
+      ]
+    }
   }
-]
\ No newline at end of file
+]