Repository: vitessio/vitess
Pull request #6590 (2020-08-19 02:22:49)

Update some option docstrings for accuracy.
Signed-off-by: Jacques Grove <aquarapid@gmail.com>


Patch:
diff --git a/.github/workflows/region_example.yml b/.github/workflows/region_example.yml
index bc74a0b13f..0da77d8c89 100644
--- a/.github/workflows/region_example.yml
+++ b/.github/workflows/region_example.yml
@@ -51,5 +51,5 @@ jobs:
         fi
         # Make sure that testing is entirely non-reliant on config
         mv config config-moved
-        sed -i 's/user\/my-vitess/runner\/work\/vitess\/vitess/g' examples/region_sharding/main_vschema_sharded.json #set correct path to countries.json
+        sed -i 's/user\/my-vitess/runner\/work\/vitess\/vitess/g' examples/region_sharding/main_vschema.json #set correct path to countries.json
         eatmydata -- test/region_example.sh
diff --git a/Makefile b/Makefile
index 9872d582dd..4a04dbe2a1 100644
--- a/Makefile
+++ b/Makefile
@@ -155,13 +155,14 @@ ifndef NOBANNER
 	echo $$(date): Compiling proto definitions
 endif
 
+# TODO(sougou): find a better way around this temp hack.
+VTTOP=$(VTROOT)/../../..
 $(PROTO_GO_OUTS): install_protoc-gen-go proto/*.proto
 	for name in $(PROTO_SRC_NAMES); do \
-		$(VTROOT)/bin/protoc --go_out=plugins=grpc:. -Iproto proto/$${name}.proto && \
-		goimports -w vitess.io/vitess/go/vt/proto/$${name}/$${name}.pb.go; \
+		cd $(VTTOP)/src && \
+		$(VTROOT)/bin/protoc --go_out=plugins=grpc:. -Ivitess.io/vitess/proto vitess.io/vitess/proto/$${name}.proto && \
+		goimports -w $(VTROOT)/go/vt/proto/$${name}/$${name}.pb.go; \
 	done
-	cp -Rf vitess.io/vitess/go/vt/proto/* go/vt/proto
-	rm -rf vitess.io/vitess/go/vt/proto/
 
 # Helper targets for building Docker images.
 # Please read docker/README.md to understand the different available images.
diff --git a/examples/compose/vtcompose/vtcompose.go b/examples/compose/vtcompose/vtcompose.go
index 5337d87b2f..412542d8ce 100644
--- a/examples/compose/vtcompose/vtcompose.go
+++ b/examples/compose/vtcompose/vtcompose.go
@@ -434,7 +434,6 @@ func applyKeyspaceDependentPatches(
 	if keyspaceData.shards < 2 {
 		tabAlias = tabAlias + 100
 		dockerComposeFile = applyTabletPatches(dockerComposeFile, tabAlias, shard, keyspaceData, externalDbInfoMap, opts)
-		dockerComposeFile = applyShardPatches(dockerComposeFile, tabAlias, shard, keyspaceData, opts)
 	} else {
 		// Determine shard range
 		for i := 0; i < keyspaceData.shards; i++ {
@@ -447,7 +446,6 @@ func applyKeyspaceDependentPatches(
 			}
 			tabAlias = tabAlias + 100
 			dockerComposeFile = applyTabletPatches(dockerComposeFile, tabAlias, shard, keyspaceData, externalDbInfoMap, opts)
-			dockerComposeFile = applyShardPatches(dockerComposeFile, tabAlias, shard, keyspaceData, opts)
 		}
 	}
 
@@ -477,39 +475,6 @@ func applyDockerComposePatches(
 	return dockerComposeFile
 }
 
-func applyShardPatches(
-	dockerComposeFile []byte,
-	tabAlias int,
-	shard string,
-	keyspaceData keyspaceInfo,
-	opts vtOptions,
-) []byte {
-	dockerComposeFile = applyInMemoryPatch(dockerComposeFile, generateDefaultShard(tabAlias, shard, keyspaceData, opts))
-	return dockerComposeFile
-}
-
-func generateDefaultShard(tabAlias int, shard string, keyspaceData keyspaceInfo, opts vtOptions) string {
-	aliases := []int{tabAlias + 1} // master alias, e.g. 201
-	for i := 0; i < keyspaceData.replicaTablets; i++ {
-		aliases = append(aliases, tabAlias+2+i) // replica aliases, e.g. 202, 203, ...
-	}
-	tabletDepends := make([]string, len(aliases))
-	for i, tabletId := range aliases {
-		tabletDepends[i] = fmt.Sprintf("vttablet%d: {condition : service_healthy}", tabletId)
-	}
-	// Wait on all shard tablets to be healthy
-	dependsOn := "depends_on: {" + strings.Join(tabletDepends, ", ") + "}"
-
-	return fmt.Sprintf(`
-- op: add
-  path: /services/init_shard_master%[2]d
-  value:
-    image: vitess/base
-    command: ["sh", "-c", "$$VTROOT/bin/vtctl %[5]s InitShardMaster -force %[4]s/%[3]s %[6]s-%[2]d "]
-    %[1]s
-`, dependsOn, aliases[0], shard, keyspaceData.keyspace, opts.topologyFlags, opts.cell)
-}
-
 func applyTabletPatches(
 	dockerComposeFile []byte,
 	tabAlias int,
@@ -709,7 +674,7 @@ func generatePrimaryVIndex(tableName, column, name string) string {
 	return fmt.Sprintf(`
 [{"op": "add",
 "path": "/tables/%[1]s",
-"value":
+"value": 
   {"column_vindexes": [
     {
       "column": "%[2]s",
diff --git a/examples/compose/vttablet-up.sh b/examples/compose/vttablet-up.sh
index f9f0808433..7284fc37d7 100755
--- a/examples/compose/vttablet-up.sh
+++ b/examples/compose/vttablet-up.sh
@@ -1,13 +1,13 @@
 #!/bin/bash
 
 # Copyright 2019 The Vitess Authors.
-#
+# 
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
-#
+# 
 #     http://www.apache.org/licenses/LICENSE-2.0
-#
+# 
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -120,6 +120,8 @@ $VTROOT/bin/vtctl $TOPOLOGY_FLAGS AddCellInfo -root vitess/$CELL -server_address
 $VTROOT/bin/vtctl $TOPOLOGY_FLAGS CreateKeyspace $keyspace || true
 $VTROOT/bin/vtctl $TOPOLOGY_FLAGS CreateShard $keyspace/$shard || true
 
+$VTROOT/bin/vtctl $TOPOLOGY_FLAGS InitTablet -shard $shard -keyspace $keyspace -grpc_port $grpc_port -port $web_port -allow_master_override $alias $tablet_role
+
 #Populate external db conditional args
 if [ "$external" = "1" ]; then
     if [ $role = "master" ]; then
diff --git a/examples/region_sharding/101_initial_cluster.sh b/examples/region_sharding/101_initial_cluster.sh
index cf06cff086..46ed082886 100755
--- a/examples/region_sharding/101_initial_cluster.sh
+++ b/examples/region_sharding/101_initial_cluster.sh
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# this script brings up topo server and all the vitess components
+# this script brings up zookeeper and all the vitess components
 # required for a single shard deployment.
 
 source ./env.sh
@@ -31,17 +31,27 @@ fi
 # start vtctld
 CELL=zone1 ./scripts/vtctld-up.sh
 
-# start unsharded keyspace and tablet
+# start vttablets for main keyspace. we start only one tablet each (master)
 CELL=zone1 TABLET_UID=100 ./scripts/mysqlctl-up.sh
-SHARD=0 CELL=zone1 KEYSPACE=main TABLET_UID=100 ./scripts/vttablet-up.sh
-
-vtctlclient InitShardMaster -force main/0 zone1-100
+SHARD=-40 CELL=zone1 KEYSPACE=main TABLET_UID=100 ./scripts/vttablet-up.sh
+CELL=zone1 TABLET_UID=200 ./scripts/mysqlctl-up.sh
+SHARD=40-80 CELL=zone1 KEYSPACE=main TABLET_UID=200 ./scripts/vttablet-up.sh
+CELL=zone1 TABLET_UID=300 ./scripts/mysqlctl-up.sh
+SHARD=80-c0 CELL=zone1 KEYSPACE=main TABLET_UID=300 ./scripts/vttablet-up.sh
+CELL=zone1 TABLET_UID=400 ./scripts/mysqlctl-up.sh
+SHARD=c0- CELL=zone1 KEYSPACE=main TABLET_UID=400 ./scripts/vttablet-up.sh
+
+# set master
+vtctlclient InitShardMaster -force main/-40 zone1-100
+vtctlclient InitShardMaster -force main/40-80 zone1-200
+vtctlclient InitShardMaster -force main/80-c0 zone1-300
+vtctlclient InitShardMaster -force main/c0- zone1-400
 
 # create the schema
 vtctlclient ApplySchema -sql-file create_main_schema.sql main
 
 # create the vschema
-vtctlclient ApplyVSchema -vschema_file main_vschema_initial.json main
+vtctlclient ApplyVSchema -vschema_file main_vschema.json main
 
 # start vtgate
 CELL=zone1 ./scripts/vtgate-up.sh
diff --git a/examples/region_sharding/201_main_sharded.sh b/examples/region_sharding/201_main_sharded.sh
deleted file mode 100755
index 2b59971d9d..0000000000
--- a/examples/region_sharding/201_main_sharded.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-
-# Copyright 2020 The Vitess Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source ./env.sh
-
-# apply sharding vschema
-vtctlclient ApplyVSchema -vschema_file main_vschema_sharded.json main
-
-# optional: create the schema needed for lookup vindex
-#vtctlclient ApplySchema -sql-file create_lookup_schema.sql main
-
-# create the lookup vindex
-vtctlclient CreateLookupVindex -tablet_types=MASTER main "$(cat lookup_vindex.json)"
-
-# we have to wait for replication to catch up
-# Can see on vttablet status page Vreplication that copy is complete
-sleep 5
-
-#externalize vindex
-vtctlclient ExternalizeVindex main.customer_region_lookup
diff --git a/examples/region_sharding/301_teardown.sh b/examples/region_sharding/201_teardown.sh
similarity index 96%
rename from examples/region_sharding/301_teardown.sh
rename to examples/region_sharding/201_teardown.sh
index 1c7d7f304d..4f8e869ffe 100755
--- a/examples/region_sharding/301_teardown.sh
+++ b/examples/region_sharding/201_teardown.sh
@@ -21,7 +21,7 @@ source ./env.sh
 
 ./scripts/vtgate-down.sh
 
-for tablet in 200 300 400 500; do
+for tablet in 100 200 300 400; do
 	if vtctlclient -server localhost:15999 GetTablet zone1-$tablet >/dev/null 2>&1; then
 		echo "Shutting down tablet zone1-$tablet"
 		CELL=zone1 TABLET_UID=$tablet ./scripts/vttablet-down.sh
@@ -45,7 +45,7 @@ fi
 if [ ! -z "$VTDATAROOT" ]; then
 
 	if pgrep -f -l "$VTDATAROOT" >/dev/null; then
-		echo "ERROR: Stale processes detected! It is recommended to manually kill them:"
+		echo "ERROR: Stale processes detected! It is recommended to manuallly kill them:"
 		pgrep -f -l "$VTDATAROOT"
 	else
 		echo "All good! It looks like every process has shut down"
diff --git a/examples/region_sharding/202_new_tablets.sh b/examples/region_sharding/202_new_tablets.sh
deleted file mode 100755
index 5e385ab622..0000000000
--- a/examples/region_sharding/202_new_tablets.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-
-# Copyright 2020 The Vitess Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source ./env.sh
-
-# start vttablets for new shards. we start only one tablet each (master)
-CELL=zone1 TABLET_UID=200 ./scripts/mysqlctl-up.sh
-SHARD=-40 CELL=zone1 KEYSPACE=main TABLET_UID=200 ./scripts/vttablet-up.sh
-CELL=zone1 TABLET_UID=300 ./scripts/mysqlctl-up.sh
-SHARD=40-80 CELL=zone1 KEYSPACE=main TABLET_UID=300 ./scripts/vttablet-up.sh
-CELL=zone1 TABLET_UID=400 ./scripts/mysqlctl-up.sh
-SHARD=80-c0 CELL=zone1 KEYSPACE=main TABLET_UID=400 ./scripts/vttablet-up.sh
-CELL=zone1 TABLET_UID=500 ./scripts/mysqlctl-up.sh
-SHARD=c0- CELL=zone1 KEYSPACE=main TABLET_UID=500 ./scripts/vttablet-up.sh
-
-# set master
-vtctlclient InitShardMaster -force main/-40 zone1-200
-vtctlclient InitShardMaster -force main/40-80 zone1-300
-vtctlclient InitShardMaster -force main/80-c0 zone1-400
-vtctlclient InitShardMaster -force main/c0- zone1-500
diff --git a/examples/region_sharding/203_reshard.sh b/examples/region_sharding/203_reshard.sh
deleted file mode 100755
index b868885aa3..0000000000
--- a/examples/region_sharding/203_reshard.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-# Copyright 2020 The Vitess Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-source ./env.sh
-
-vtctlclient Reshard -tablet_types=MASTER main.main2regions '0' '-40,40-80,80-c0,c0-'
diff --git a/examples/region_sharding/204_switch_reads.sh b/examples/region_sharding/204_switch_reads.sh
deleted file mode 100755
index 8ff123cf8e..0000000000
--- a/examples/region_sharding/204_switch_reads.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-
-# Copyright 2020 The Vitess Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# this script migrates traffic for the rdonly and replica tablets
-
-source ./env.sh
-
-vtctlclient SwitchReads -tablet_type=rdonly main.main2regions
-vtctlclient SwitchReads -tablet_type=replica main.main2regions
diff --git a/examples/region_sharding/205_switch_writes.sh b/examples/region_sharding/205_switch_writes.sh
deleted file mode 100755
index a7f22f430c..0000000000
--- a/examples/region_sharding/205_switch_writes.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-# Copyright 2020 The Vitess Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# this script migrates traffic for the master tablet
-
-source ./env.sh
-
-vtctlclient SwitchWrites main.main2regions
-
-# to go back to unsharded
-# call SwitchReads and SwitchWrites with workflow main.main2regions_reverse
-# delete vreplication rows from sharded tablets
-# drop all the tables
-# change vschema back to unsharded
-# drop lookup table
-
diff --git a/examples/region_sharding/206_down_shard_0.sh b/examples/region_sharding/206_down_shard_0.sh
deleted file mode 100755
index d302a071a4..0000000000
--- a/examples/region_sharding/206_down_shard_0.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The Vitess Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-# this script brings down the tablets for main/0 keyspace/shard
-
-source ./env.sh
-
-CELL=zone1 TABLET_UID=100 ./scripts/vttablet-down.sh
-CELL=zone1 TABLET_UID=100 ./scripts/mysqlctl-down.sh
diff --git a/examples/region_sharding/207_delete_shard_0.sh b/examples/region_sharding/207_delete_shard_0.sh
deleted file mode 100755
index b626dcbf1a..0000000000
--- a/examples/region_sharding/207_delete_shard_0.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 The Vitess Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# this script deletes the old shard 0 which has been replaced by 4 shards
-
-source ./env.sh
-
-vtctlclient DeleteShard -recursive main/0
diff --git a/examples/region_sharding/README.md b/examples/region_sharding/README.md
index 21c53f1b49..6392463553 100644
--- a/examples/region_sharding/README.md
+++ b/examples/region_sharding/README.md
@@ -10,45 +10,13 @@ This document contains the summary of the commands to be run.
 	    "region_map": "/home/user/vitess/examples/region_sharding/countries.json",
 
 
-# Bring up initial cluster and main keyspace (unsharded)
+# Bring up initial cluster and main keyspace
 ./101_initial_cluster.sh
 
-# setup aliases
-alias mysql="command mysql -h 127.0.0.1 -P 15306"
-alias vtctlclient="command vtctlclient -server localhost:15999 -log_dir ${VTDATAROOT}/tmp -alsologtostderr"
-
 # Insert and verify data
 mysql < insert_customers.sql
-mysql --table < show_initial_data.sql
-
-# create schema and vschema for sharding (+lookup vindex)
-./201_main_sharded.sh
-
-# bring up shards and tablets
-./202_new_tablets.sh
-
-# reshard
-./203_reshard.sh
-
-# SwitchReads
-./204_switch_reads.sh
-
-# run script to create traffic before switching writes
-#./client.sh
-
-# SwitchWrites
-./205_switch_writes.sh
-# show no / minimal write errors during switch
-
-# verify sharded data
 mysql --table < show_data.sql
 
-# down shard
-./206_down_shard_0.sh
-
-# delete shard 0
-./207_delete_shard_0.sh
-
 # Down cluster
-./301_teardown.sh
+./201_teardown.sh
 ```
diff --git a/examples/region_sharding/create_lookup_schema.sql b/examples/region_sharding/create_lookup_schema.sql
deleted file mode 100644
index 550a1e399e..0000000000
--- a/examples/region_sharding/create_lookup_schema.sql
+++ /dev/null
@@ -1,5 +0,0 @@
-CREATE TABLE customer_lookup (
-  id int NOT NULL,
-  keyspace_id varbinary(128),
-  primary key(id)
-  );
diff --git a/examples/region_sharding/create_main_schema.sql b/examples/region_sharding/create_main_schema.sql
index 6a26ce660e..2e41cb35e5 100644
--- a/examples/region_sharding/create_main_schema.sql
+++ b/examples/region_sharding/create_main_schema.sql
@@ -5,3 +5,8 @@ CREATE TABLE customer (
   country varbinary(256),
   primary key(id)
   );
+CREATE TABLE customer_lookup (
+  id int NOT NULL,
+  keyspace_id varbinary(256),
+  primary key(id)
+  );
diff --git a/examples/region_sharding/lookup_vindex.json b/examples/region_sharding/lookup_vindex.json
deleted file mode 100644
index 17f3b4471c..0000000000
--- a/examples/region_sharding/lookup_vindex.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "sharded": true,
-  "vindexes": {
-    "customer_region_lookup": {
-      "type": "consistent_lookup_unique",
-      "params": {
-        "table": "main.customer_lookup",
-        "from": "id",
-        "to": "keyspace_id"
-      },
-      "owner": "customer"
-    }
-  },
-  "tables": {
-    "customer": {
-      "column_vindexes": [
-        {
-          "column": "id",
-          "name": "customer_region_lookup"
-        }
-      ]
-    }
-  }
-}
diff --git a/examples/region_sharding/main_vschema.json b/examples/region_sharding/main_vschema.json
new file mode 100644
index 0000000000..48a8f93dce
--- /dev/null
+++ b/examples/region_sharding/main_vschema.json
@@ -0,0 +1,46 @@
+{
+  "sharded": true,
+  "vindexes": {
+    "region_vdx": {
+	"type": "region_json",
+	"params": {
+	    "region_map": "/home/user/my-vitess/examples/region_sharding/countries.json",
+	    "region_bytes": "1"
+	}
+    },
+    "customer_region_lookup": {
+        "type": "consistent_lookup_unique",
+        "params": {
+            "table": "customer_lookup",
+            "from": "id",
+            "to": "keyspace_id"
+        },
+        "owner": "customer"
+    },
+      "identity": {
+	  "type": "binary"
+      }
+  },
+  "tables": {
+    "customer_lookup": {
+      "column_vindexes": [
+	{
+            "column": "keyspace_id",
+            "name": "identity"
+        }
+      ]
+    },
+    "customer": {
+      "column_vindexes": [
+        {
+            "columns": ["id", "country"],
+	    "name": "region_vdx"
+        },
+	{
+            "column": "id",
+            "name": "customer_region_lookup"
+        }
+      ]
+    }
+  }
+}
diff --git a/examples/region_sharding/main_vschema_initial.json b/examples/region_sharding/main_vschema_initial.json
deleted file mode 100644
index b8c11210f8..0000000000
--- a/examples/region_sharding/main_vschema_initial.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "tables": {
-    "customer": {}
-  }
-}
\ No newline at end of file
diff --git a/examples/region_sharding/main_vschema_sharded.json b/examples/region_sharding/main_vschema_sharded.json
deleted file mode 100644
index 94770e57ea..0000000000
--- a/examples/region_sharding/main_vschema_sharded.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  "sharded": true,
-  "vindexes": {
-    "region_vdx": {
-      "type": "region_json",
-      "params": {
-        "region_map": "/home/user/my-vitess/examples/region_sharding/countries.json",
-        "region_bytes": "1"
-      }
-    }
-  },
-  "tables": {
-    "customer": {
-      "column_vindexes": [
-        {
-          "columns": ["id", "country"],
-	      "name": "region_vdx"
-        }
-      ]
-    }
-  }
-}
diff --git a/examples/region_sharding/show_initial_data.sql b/examples/region_sharding/show_initial_data.sql
deleted file mode 100644
index c85ce49140..0000000000
--- a/examples/region_sharding/show_initial_data.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-use main;
-select * from customer;
diff --git a/go/mysql/constants.go b/go/mysql/constants.go
index f2f6403f60..7236a7e742 100644
--- a/go/mysql/constants.go
+++ b/go/mysql/constants.go
@@ -496,13 +496,8 @@ const (
 // Originally found in include/mysql/mysql_com.h
 // See http://dev.mysql.com/doc/internals/en/status-flags.html
 const (
-	// ServerStatusAutocommit is SERVER_STATUS_IN_TRANS
-	ServerStatusInTransaction   = 0x0001
-	NoServerStatusInTransaction = 0xFFFE
-
-	// ServerStatusAutocommit is SERVER_STATUS_AUTOCOMMIT
-	ServerStatusAutocommit   = 0x0002
-	NoServerStatusAutocommit = 0xFFFD
+	// ServerStatusAutocommit is SERVER_STATUS_AUTOCOMMIT.
+	ServerStatusAutocommit = 0x0002
 
 	// ServerMoreResultsExists is SERVER_MORE_RESULTS_EXISTS
 	ServerMoreResultsExists = 0x0008
diff --git a/go/pools/numbered.go b/go/pools/numbered.go
index 19f8bb1ef9..f1e7648816 100644
--- a/go/pools/numbered.go
+++ b/go/pools/numbered.go
@@ -131,15 +131,13 @@ func (nu *Numbered) Get(id int64, purpose string) (val interface{}, err error) {
 }
 
 // Put unlocks a resource for someone else to use.
-func (nu *Numbered) Put(id int64, updateTime bool) {
+func (nu *Numbered) Put(id int64) {
 	nu.mu.Lock()
 	defer nu.mu.Unlock()
 	if nw, ok := nu.resources[id]; ok {
 		nw.inUse = false
 		nw.purpose = ""
-		if updateTime {
-			nw.timeUsed = time.Now()
-		}
+		nw.timeUsed = time.Now()
 	}
 }
 
@@ -164,7 +162,7 @@ func (nu *Numbered) GetOutdated(age time.Duration, purpose string) (vals []inter
 		if nw.inUse || !nw.enforceTimeout {
 			continue
 		}
-		if nw.timeUsed.Add(age).Sub(now) <= 0 {
+		if nw.timeCreated.Add(age).Sub(now) <= 0 {
 			nw.inUse = true
 			nw.purpose = purpose
 			vals = append(vals, nw.val)
diff --git a/go/vt/discovery/replicationlag.go b/go/vt/discovery/replicationlag.go
index b527bfb8ab..4840bd6205 100644
--- a/go/vt/discovery/replicationlag.go
+++ b/go/vt/discovery/replicationlag.go
@@ -26,8 +26,8 @@ import (
 var (
 	// lowReplicationLag defines the duration that replication lag is low enough that the VTTablet is considered healthy.
 	lowReplicationLag             = flag.Duration("discovery_low_replication_lag", 30*time.Second, "the replication lag that is considered low enough to be healthy")
-	highReplicationLagMinServing  = flag.Duration("discovery_high_replication_lag_minimum_serving", 2*time.Hour, "the replication lag that is considered too high when selecting the minimum num vttablets for serving")
-	minNumTablets                 = flag.Int("min_number_serving_vttablets", 2, "the minimum number of vttablets that will be continue to be used even with low replication lag")
+	highReplicationLagMinServing  = flag.Duration("discovery_high_replication_lag_minimum_serving", 2*time.Hour, "the replication lag that is considered too high when applying the min_number_serving_vttablets threshold")
+	minNumTablets                 = flag.Int("min_number_serving_vttablets", 2, "the minimum number of vttablets for each replicating tablet_type (e.g. replica, rdonly) that will be continue to be used even with replication lag above discovery_low_replication_lag, but still below discovery_high_replication_lag_minimum_serving")
 	legacyReplicationLagAlgorithm = flag.Bool("legacy_replication_lag_algorithm", true, "use the legacy algorithm when selecting the vttablets for serving")
 )
 
diff --git a/go/vt/discovery/tablets_cache_status.go b/go/vt/discovery/tablets_cache_status.go
index 60e2c65fee..46d2fd4ba4 100644
--- a/go/vt/discovery/tablets_cache_status.go
+++ b/go/vt/discovery/tablets_cache_status.go
@@ -6,7 +6,7 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 
 	querypb "vitess.io/vitess/go/vt/proto/query"
 	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
diff --git a/go/vt/vtctld/api.go b/go/vt/vtctld/api.go
index 28663876e3..ea0c31015d 100644
--- a/go/vt/vtctld/api.go
+++ b/go/vt/vtctld/api.go
@@ -42,7 +42,6 @@ import (
 
 	"vitess.io/vitess/go/vt/mysqlctl"
 	logutilpb "vitess.io/vitess/go/vt/proto/logutil"
-	querypb "vitess.io/vitess/go/vt/proto/query"
 	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
 	"vitess.io/vitess/go/vt/proto/vttime"
 )
@@ -61,16 +60,8 @@ const (
 	jsonContentType = "application/json; charset=utf-8"
 )
 
-// TabletStats represents realtime stats from a discovery.LegacyTabletStats struct.
-type TabletStats struct {
-	LastError string                 `json:"last_error,omitempty"`
-	Realtime  *querypb.RealtimeStats `json:"realtime,omitempty"`
-	Serving   bool                   `json:"serving"`
-	Up        bool                   `json:"up"`
-}
-
-// TabletWithStatsAndURL wraps topo.Tablet, adding a URL property and optional realtime stats.
-type TabletWithStatsAndURL struct {
+// TabletWithURL wraps topo.Tablet and adds a URL property.
+type TabletWithURL struct {
 	Alias               *topodatapb.TabletAlias `json:"alias,omitempty"`
 	Hostname            string                  `json:"hostname,omitempty"`
 	PortMap             map[string]int32        `json:"port_map,omitempty"`
@@ -83,48 +74,9 @@ type TabletWithStatsAndURL struct {
 	MysqlHostname       string                  `json:"mysql_hostname,omitempty"`
 	MysqlPort           int32                   `json:"mysql_port,omitempty"`
 	MasterTermStartTime *vttime.Time            `json:"master_term_start_time,omitempty"`
-	Stats               *TabletStats            `json:"stats,omitempty"`
 	URL                 string                  `json:"url,omitempty"`
 }
 
-func newTabletWithStatsAndURL(t *topodatapb.Tablet, realtimeStats *realtimeStats) *TabletWithStatsAndURL {
-	tablet := &TabletWithStatsAndURL{
-		Alias:               t.Alias,
-		Hostname:            t.Hostname,
-		PortMap:             t.PortMap,
-		Keyspace:            t.Keyspace,
-		Shard:               t.Shard,
-		KeyRange:            t.KeyRange,
-		Type:                t.Type,
-		DbNameOverride:      t.DbNameOverride,
-		Tags:                t.Tags,
-		MysqlHostname:       t.MysqlHostname,
-		MysqlPort:           t.MysqlPort,
-		MasterTermStartTime: t.MasterTermStartTime,
-	}
-
-	if *proxyTablets {
-		tablet.URL = fmt.Sprintf("/vttablet/%s-%d/debug/status", t.Alias.Cell, t.Alias.Uid)
-	} else {
-		tablet.URL = "http://" + netutil.JoinHostPort(t.Hostname, t.PortMap["vt"])
-	}
-
-	if realtimeStats != nil {
-		if stats, err := realtimeStats.tabletStats(tablet.Alias); err == nil {
-			tablet.Stats = &TabletStats{
-				Realtime: stats.Stats,
-				Serving:  stats.Serving,
-				Up:       stats.Up,
-			}
-			if stats.LastError != nil {
-				tablet.Stats.LastError = stats.LastError.Error()
-			}
-		}
-	}
-
-	return tablet
-}
-
 func httpErrorf(w http.ResponseWriter, r *http.Request, format string, args ...interface{}) {
 	errMsg := fmt.Sprintf(format, args...)
 	log.Errorf("HTTP error on %v: %v, request: %#v", r.URL.Path, errMsg, r)
@@ -266,23 +218,10 @@ func initAPI(ctx context.Context, ts *topo.Server, actions *ActionRepository, re
 				return nil, err
 			}
 		}
-
-		if err := r.ParseForm(); err != nil {
-			return nil, err
-		}
-		cell := r.FormValue("cell")
-		cells := r.FormValue("cells")
-		filterCells := []string{} // empty == all cells
-		if cell != "" {
-			filterCells = []string{cell} // single cell
-		} else if cells != "" {
-			filterCells = strings.Split(cells, ",") // list of cells
-		}
-
-		tablets := [](*TabletWithStatsAndURL){}
+		tablets := [](*topodatapb.Tablet){}
 		for _, shard := range shardNames {
 			// Get tablets for this shard.
-			tabletAliases, err := ts.FindAllTabletAliasesInShardByCell(ctx, keyspace, shard, filterCells)
+			tabletAliases, err := ts.FindAllTabletAliasesInShard(ctx, keyspace, shard)
 			if err != nil && !topo.IsErrType(err, topo.PartialResult) {
 				return nil, err
 			}
@@ -291,8 +230,7 @@ func initAPI(ctx context.Context, ts *topo.Server, actions *ActionRepository, re
 				if err != nil {
 					return nil, err
 				}
-				tablet := newTabletWithStatsAndURL(t.Tablet, realtimeStats)
-				tablets = append(tablets, tablet)
+				tablets = append(tablets, t.Tablet)
 			}
 		}
 		return tablets, nil
@@ -464,7 +402,26 @@ func initAPI(ctx context.Context, ts *topo.Server, actions *ActionRepository, re
 			return nil, err
 		}
 
-		return newTabletWithStatsAndURL(t.Tablet, nil), nil
+		tab := &TabletWithURL{
+			Alias:               t.Alias,
+			Hostname:            t.Hostname,
+			PortMap:             t.PortMap,
+			Keyspace:            t.Keyspace,
+			Shard:               t.Shard,
+			KeyRange:            t.KeyRange,
+			Type:                t.Type,
+			DbNameOverride:      t.DbNameOverride,
+			Tags:                t.Tags,
+			MysqlHostname:       t.MysqlHostname,
+			MysqlPort:           t.MysqlPort,
+			MasterTermStartTime: t.MasterTermStartTime,
+		}
+		if *proxyTablets {
+			tab.URL = fmt.Sprintf("/vttablet/%s-%d/debug/status", t.Alias.Cell, t.Alias.Uid)
+		} else {
+			tab.URL = "http://" + netutil.JoinHostPort(t.Hostname, t.PortMap["vt"])
+		}
+		return tab, nil
 	})
 
 	// Healthcheck real time status per (cell, keyspace, tablet type, metric).
diff --git a/go/vt/vtgate/engine/route.go b/go/vt/vtgate/engine/route.go
index 5c4c49227c..16adb70100 100644
--- a/go/vt/vtgate/engine/route.go
+++ b/go/vt/vtgate/engine/route.go
@@ -133,7 +133,9 @@ func (obp OrderbyParams) String() string {
 }
 
 // RouteOpcode is a number representing the opcode
-// for the Route primitve.
+// for the Route primitve. Adding new opcodes here
+// will require review of the join code and
+// the finalizeOptions code in planbuilder.
 type RouteOpcode int
 
 // This is the list of RouteOpcode values.
@@ -164,8 +166,6 @@ const (
 	SelectReference
 	// SelectNone is used for queries that always return empty values
 	SelectNone
-	// NumRouteOpcodes is the number of opcodes
-	NumRouteOpcodes
 )
 
 var routeName = map[RouteOpcode]string{
diff --git a/go/vt/vtgate/planbuilder/builder.go b/go/vt/vtgate/planbuilder/builder.go
index 14f446962b..6b73b22be8 100644
--- a/go/vt/vtgate/planbuilder/builder.go
+++ b/go/vt/vtgate/planbuilder/builder.go
@@ -124,7 +124,7 @@ type builder interface {
 // info about tables.
 type ContextVSchema interface {
 	FindTable(tablename sqlparser.TableName) (*vindexes.Table, string, topodatapb.TabletType, key.Destination, error)
-	FindTableOrVindex(tablename sqlparser.TableName) (*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error)
+	FindTablesOrVindex(tablename sqlparser.TableName) ([]*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error)
 	DefaultKeyspace() (*vindexes.Keyspace, error)
 	TargetString() string
 	Destination() key.Destination
diff --git a/go/vt/vtgate/planbuilder/dml.go b/go/vt/vtgate/planbuilder/dml.go
index 46e109006e..67da3070e3 100644
--- a/go/vt/vtgate/planbuilder/dml.go
+++ b/go/vt/vtgate/planbuilder/dml.go
@@ -101,14 +101,14 @@ func nameMatch(node sqlparser.Expr, col sqlparser.ColIdent) bool {
 }
 
 func buildDMLPlan(vschema ContextVSchema, dmlType string, stmt sqlparser.Statement, tableExprs sqlparser.TableExprs, where *sqlparser.Where, orderBy sqlparser.OrderBy, limit *sqlparser.Limit, comments sqlparser.Comments, nodes ...sqlparser.SQLNode) (*engine.DML, vindexes.SingleColumn, string, error) {
-	edml := &engine.DML{}
+	eupd := &engine.DML{}
 	pb := newPrimitiveBuilder(vschema, newJointab(sqlparser.GetBindvars(stmt)))
-	rb, err := pb.processDMLTable(tableExprs)
+	ro, err := pb.processDMLTable(tableExprs)
 	if err != nil {
 		return nil, nil, "", err
 	}
-	edml.Keyspace = rb.eroute.Keyspace
-	if !edml.Keyspace.Sharded {
+	eupd.Keyspace = ro.eroute.Keyspace
+	if !eupd.Keyspace.Sharded {
 		// We only validate non-table subexpressions because the previous analysis has already validated them.
 		var subqueryArgs []sqlparser.SQLNode
 		subqueryArgs = append(subqueryArgs, nodes...)
@@ -116,61 +116,60 @@ func buildDMLPlan(vschema ContextVSchema, dmlType string, stmt sqlparser.Stateme
 		if !pb.finalizeUnshardedDMLSubqueries(subqueryArgs...) {
 			return nil, nil, "", vterrors.New(vtrpcpb.Code_UNIMPLEMENTED, "unsupported: sharded subqueries in DML")
 		}
-		edml.Opcode = engine.Unsharded
+		eupd.Opcode = engine.Unsharded
 		// Generate query after all the analysis. Otherwise table name substitutions for
 		// routed tables won't happen.
-		edml.Query = generateQuery(stmt)
-		return edml, nil, "", nil
+		eupd.Query = generateQuery(stmt)
+		return eupd, nil, "", nil
 	}
 
 	if hasSubquery(stmt) {
 		return nil, nil, "", vterrors.New(vtrpcpb.Code_UNIMPLEMENTED, "unsupported: subqueries in sharded DML")
 	}
 
+	if len(pb.st.tables) != 1 {
+		return nil, nil, "", vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "unsupported: multi-table %s statement in sharded keyspace", dmlType)
+	}
+
 	// Generate query after all the analysis. Otherwise table name substitutions for
 	// routed tables won't happen.
-	edml.Query = generateQuery(stmt)
+	eupd.Query = generateQuery(stmt)
 
 	directives := sqlparser.ExtractCommentDirectives(comments)
 	if directives.IsSet(sqlparser.DirectiveMultiShardAutocommit) {
-		edml.MultiShardAutocommit = true
+		eupd.MultiShardAutocommit = true
 	}
 
-	edml.QueryTimeout = queryTimeout(directives)
-
-	if len(pb.st.tables) != 1 {
-		return nil, nil, "", vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "unsupported: multi-table %s statement in sharded keyspace", dmlType)
-	}
-	for _, tval := range pb.st.tables {
-		// There is only one table.
-		edml.Table = tval.vschemaTable
+	eupd.QueryTimeout = queryTimeout(directives)
+	eupd.Table = ro.vschemaTable
+	if eupd.Table == nil {
+		return nil, nil, "", vterrors.New(vtrpcpb.Code_INTERNAL, "internal error: table.vindexTable is mysteriously nil")
 	}
 
-	routingType, ksidVindex, ksidCol, vindex, values, err := getDMLRouting(where, edml.Table)
-	if err != nil {
-		return nil, nil, "", err
-	}
-
-	if rb.eroute.TargetDestination != nil {
-		if rb.eroute.TargetTabletType != topodatapb.TabletType_MASTER {
+	if ro.eroute.TargetDestination != nil {
+		if ro.eroute.TargetTabletType != topodatapb.TabletType_MASTER {
 			return nil, nil, "", vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "unsupported: %s statement with a replica target", dmlType)
 		}
-		edml.Opcode = engine.ByDestination
-		edml.TargetDestination = rb.eroute.TargetDestination
-		return edml, ksidVindex, ksidCol, nil
+		eupd.Opcode = engine.ByDestination
+		eupd.TargetDestination = ro.eroute.TargetDestination
+		return eupd, nil, "", nil
 	}
 
-	edml.Opcode = routingType
+	routingType, ksidVindex, ksidCol, vindex, values, err := getDMLRouting(where, eupd.Table)
+	if err != nil {
+		return nil, nil, "", err
+	}
+	eupd.Opcode = routingType
 	if routingType == engine.Scatter {
 		if limit != nil {
 			return nil, nil, "", vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "unsupported: multi shard %s with limit", dmlType)
 		}
 	} else {
-		edml.Vindex = vindex
-		edml.Values = values
+		eupd.Vindex = vindex
+		eupd.Values = values
 	}
 
-	return edml, ksidVindex, ksidCol, nil
+	return eupd, ksidVindex, ksidCol, nil
 }
 
 func generateDMLSubquery(where *sqlparser.Where, orderBy sqlparser.OrderBy, limit *sqlparser.Limit, table *vindexes.Table, ksidCol string) string {
diff --git a/go/vt/vtgate/planbuilder/expr.go b/go/vt/vtgate/planbuilder/expr.go
index 595f4cefcb..e7a51664e6 100644
--- a/go/vt/vtgate/planbuilder/expr.go
+++ b/go/vt/vtgate/planbuilder/expr.go
@@ -211,7 +211,7 @@ func hasSubquery(node sqlparser.SQLNode) bool {
 func (pb *primitiveBuilder) finalizeUnshardedDMLSubqueries(nodes ...sqlparser.SQLNode) bool {
 	var keyspace string
 	if rb, ok := pb.bldr.(*route); ok {
-		keyspace = rb.eroute.Keyspace.Name
+		keyspace = rb.routeOptions[0].eroute.Keyspace.Name
 	} else {
 		// This code is unreachable because the caller checks.
 		return false
@@ -239,11 +239,11 @@ func (pb *primitiveBuilder) finalizeUnshardedDMLSubqueries(nodes ...sqlparser.SQ
 					samePlan = false
 					return false, errors.New("dummy")
 				}
-				if innerRoute.eroute.Keyspace.Name != keyspace {
+				if !innerRoute.removeOptionsWithUnmatchedKeyspace(keyspace) {
 					samePlan = false
 					return false, errors.New("dummy")
 				}
-				for _, sub := range innerRoute.substitutions {
+				for _, sub := range innerRoute.routeOptions[0].substitutions {
 					*sub.oldExpr = *sub.newExpr
 				}
 			case *sqlparser.Union:
@@ -260,7 +260,7 @@ func (pb *primitiveBuilder) finalizeUnshardedDMLSubqueries(nodes ...sqlparser.SQ
 					samePlan = false
 					return false, errors.New("dummy")
 				}
-				if innerRoute.eroute.Keyspace.Name != keyspace {
+				if !innerRoute.removeOptionsWithUnmatchedKeyspace(keyspace) {
 					samePlan = false
 					return false, errors.New("dummy")
 				}
diff --git a/go/vt/vtgate/planbuilder/from.go b/go/vt/vtgate/planbuilder/from.go
index 642e22de8e..772d224f13 100644
--- a/go/vt/vtgate/planbuilder/from.go
+++ b/go/vt/vtgate/planbuilder/from.go
@@ -28,8 +28,8 @@ import (
 
 // This file has functions to analyze the FROM clause.
 
-// processDMLTable analyzes the FROM clause for DMLs and returns a route.
-func (pb *primitiveBuilder) processDMLTable(tableExprs sqlparser.TableExprs) (*route, error) {
+// processDMLTable analyzes the FROM clause for DMLs and returns a routeOption.
+func (pb *primitiveBuilder) processDMLTable(tableExprs sqlparser.TableExprs) (*routeOption, error) {
 	if err := pb.processTableExprs(tableExprs); err != nil {
 		return nil, err
 	}
@@ -37,10 +37,11 @@ func (pb *primitiveBuilder) processDMLTable(tableExprs sqlparser.TableExprs) (*r
 	if !ok {
 		return nil, errors.New("unsupported: multi-shard or vindex write statement")
 	}
-	for _, sub := range rb.substitutions {
+	ro := rb.routeOptions[0]
+	for _, sub := range ro.substitutions {
 		*sub.oldExpr = *sub.newExpr
 	}
-	return rb, nil
+	return ro, nil
 }
 
 // processTableExprs analyzes the FROM clause. It produces a builder
@@ -123,10 +124,6 @@ func (pb *primitiveBuilder) processAliasedTable(tableExpr *sqlparser.AliasedTabl
 		// FROM clause. This allows for other constructs to be
 		// later pushed into it.
 		rb, st := newRoute(&sqlparser.Select{From: []sqlparser.TableExpr{tableExpr}})
-		rb.substitutions = subroute.substitutions
-		rb.condition = subroute.condition
-		rb.eroute = subroute.eroute
-		subroute.Redirect = rb
 
 		// The subquery needs to be represented as a new logical table in the symtab.
 		// The new route will inherit the routeOptions of the underlying subquery.
@@ -135,29 +132,39 @@ func (pb *primitiveBuilder) processAliasedTable(tableExpr *sqlparser.AliasedTabl
 		// a new set of column references will be generated against the new tables,
 		// and those vindex maps will be returned. They have to replace the old vindex
 		// maps of the inherited route options.
-		vschemaTable := &vindexes.Table{
-			Keyspace: subroute.eroute.Keyspace,
-		}
-		for _, rc := range subroute.ResultColumns() {
-			if rc.column.vindex == nil {
-				continue
+		vschemaTables := make([]*vindexes.Table, 0, len(subroute.routeOptions))
+		for _, ro := range subroute.routeOptions {
+			vst := &vindexes.Table{
+				Keyspace: ro.eroute.Keyspace,
 			}
-			// Check if a colvindex of the same name already exists.
-			// Dups are not allowed in subqueries in this situation.
-			for _, colVindex := range vschemaTable.ColumnVindexes {
-				if colVindex.Columns[0].Equal(rc.alias) {
-					return fmt.Errorf("duplicate column aliases: %v", rc.alias)
+			vschemaTables = append(vschemaTables, vst)
+			for _, rc := range subroute.ResultColumns() {
+				vindex, ok := ro.vindexMap[rc.column]
+				if !ok {
+					continue
 				}
+				// Check if a colvindex of the same name already exists.
+				// Dups are not allowed in subqueries in this situation.
+				for _, colVindex := range vst.ColumnVindexes {
+					if colVindex.Columns[0].Equal(rc.alias) {
+						return fmt.Errorf("duplicate column aliases: %v", rc.alias)
+					}
+				}
+				vst.ColumnVindexes = append(vst.ColumnVindexes, &vindexes.ColumnVindex{
+					Columns: []sqlparser.ColIdent{rc.alias},
+					Vindex:  vindex,
+				})
 			}
-			vschemaTable.ColumnVindexes = append(vschemaTable.ColumnVindexes, &vindexes.ColumnVindex{
-				Columns: []sqlparser.ColIdent{rc.alias},
-				Vindex:  rc.column.vindex,
-			})
 		}
-		if err := st.AddVSchemaTable(sqlparser.TableName{Name: tableExpr.As}, vschemaTable, rb); err != nil {
+		vindexMaps, err := st.AddVSchemaTable(sqlparser.TableName{Name: tableExpr.As}, vschemaTables, rb)
+		if err != nil {
 			return err
 		}
-
+		for i, ro := range subroute.routeOptions {
+			ro.SubqueryToTable(rb, vindexMaps[i])
+		}
+		rb.routeOptions = subroute.routeOptions
+		subroute.Redirect = rb
 		pb.bldr, pb.st = rb, st
 		return nil
 	}
@@ -178,7 +185,7 @@ func (pb *primitiveBuilder) buildTablePrimitive(tableExpr *sqlparser.AliasedTabl
 			return err
 		}
 		rb, st := newRoute(sel)
-		rb.eroute = engine.NewSimpleRoute(engine.SelectDBA, ks)
+		rb.routeOptions = []*routeOption{newSimpleRouteOption(rb, engine.NewSimpleRoute(engine.SelectDBA, ks))}
 		pb.bldr, pb.st = rb, st
 		// Add the table to symtab
 		return st.AddTable(&table{
@@ -187,7 +194,7 @@ func (pb *primitiveBuilder) buildTablePrimitive(tableExpr *sqlparser.AliasedTabl
 		})
 	}
 
-	vschemaTable, vindex, _, destTableType, destTarget, err := pb.vschema.FindTableOrVindex(tableName)
+	vschemaTables, vindex, _, destTableType, destTarget, err := pb.vschema.FindTablesOrVindex(tableName)
 	if err != nil {
 		return err
 	}
@@ -202,59 +209,58 @@ func (pb *primitiveBuilder) buildTablePrimitive(tableExpr *sqlparser.AliasedTabl
 
 	rb, st := newRoute(sel)
 	pb.bldr, pb.st = rb, st
-	if err := st.AddVSchemaTable(alias, vschemaTable, rb); err != nil {
+	vindexMaps, err := st.AddVSchemaTable(alias, vschemaTables, rb)
+	if err != nil {
 		return err
 	}
-
-	sub := &tableSubstitution{
-		oldExpr: tableExpr,
-	}
-	if tableExpr.As.IsEmpty() {
-		if tableName.Name != vschemaTable.Name {
-			// Table name does not match. Change and alias it to old name.
-			sub.newExpr = &sqlparser.AliasedTableExpr{
-				Expr: sqlparser.TableName{Name: vschemaTable.Name},
-				As:   tableName.Name,
-			}
+	for i, vst := range vschemaTables {
+		sub := &tableSubstitution{
+			oldExpr: tableExpr,
 		}
-	} else {
-		// Table is already aliased.
-		if tableName.Name != vschemaTable.Name {
-			// Table name does not match. Change it and reuse existing alias.
-			sub.newExpr = &sqlparser.AliasedTableExpr{
-				Expr: sqlparser.TableName{Name: vschemaTable.Name},
-				As:   tableExpr.As,
+		if tableExpr.As.IsEmpty() {
+			if tableName.Name != vst.Name {
+				// Table name does not match. Change and alias it to old name.
+				sub.newExpr = &sqlparser.AliasedTableExpr{
+					Expr: sqlparser.TableName{Name: vst.Name},
+					As:   tableName.Name,
+				}
+			}
+		} else {
+			// Table is already aliased.
+			if tableName.Name != vst.Name {
+				// Table name does not match. Change it and reuse existing alias.
+				sub.newExpr = &sqlparser.AliasedTableExpr{
+					Expr: sqlparser.TableName{Name: vst.Name},
+					As:   tableExpr.As,
+				}
 			}
 		}
-	}
-	if sub != nil && sub.newExpr != nil {
-		rb.substitutions = []*tableSubstitution{sub}
-	}
+		var eroute *engine.Route
+		switch {
+		case vst.Type == vindexes.TypeSequence:
+			eroute = engine.NewSimpleRoute(engine.SelectNext, vst.Keyspace)
+		case vst.Type == vindexes.TypeReference:
+			eroute = engine.NewSimpleRoute(engine.SelectReference, vst.Keyspace)
+		case !vst.Keyspace.Sharded:
+			eroute = engine.NewSimpleRoute(engine.SelectUnsharded, vst.Keyspace)
+		case vst.Pinned == nil:
+			eroute = engine.NewSimpleRoute(engine.SelectScatter, vst.Keyspace)
+			eroute.TargetDestination = destTarget
+			eroute.TargetTabletType = destTableType
+		default:
+			// Pinned tables have their keyspace ids already assigned.
+			// Use the Binary vindex, which is the identity function
+			// for keyspace id.
+			eroute = engine.NewSimpleRoute(engine.SelectEqualUnique, vst.Keyspace)
+			vindex, _ = vindexes.NewBinary("binary", nil)
+			eroute.Vindex, _ = vindex.(vindexes.SingleColumn)
+			eroute.Values = []sqltypes.PlanValue{{Value: sqltypes.MakeTrusted(sqltypes.VarBinary, vst.Pinned)}}
+		}
+		// set table name into route
+		eroute.TableName = vst.Name.String()
 
-	var eroute *engine.Route
-	switch {
-	case vschemaTable.Type == vindexes.TypeSequence:
-		eroute = engine.NewSimpleRoute(engine.SelectNext, vschemaTable.Keyspace)
-	case vschemaTable.Type == vindexes.TypeReference:
-		eroute = engine.NewSimpleRoute(engine.SelectReference, vschemaTable.Keyspace)
-	case !vschemaTable.Keyspace.Sharded:
-		eroute = engine.NewSimpleRoute(engine.SelectUnsharded, vschemaTable.Keyspace)
-	case vschemaTable.Pinned == nil:
-		eroute = engine.NewSimpleRoute(engine.SelectScatter, vschemaTable.Keyspace)
-		eroute.TargetDestination = destTarget
-		eroute.TargetTabletType = destTableType
-	default:
-		// Pinned tables have their keyspace ids already assigned.
-		// Use the Binary vindex, which is the identity function
-		// for keyspace id.
-		eroute = engine.NewSimpleRoute(engine.SelectEqualUnique, vschemaTable.Keyspace)
-		vindex, _ = vindexes.NewBinary("binary", nil)
-		eroute.Vindex, _ = vindex.(vindexes.SingleColumn)
-		eroute.Values = []sqltypes.PlanValue{{Value: sqltypes.MakeTrusted(sqltypes.VarBinary, vschemaTable.Pinned)}}
+		rb.routeOptions = append(rb.routeOptions, newRouteOption(rb, vst, sub, vindexMaps[i], eroute))
 	}
-	eroute.TableName = vschemaTable.Name.String()
-	rb.eroute = eroute
-
 	return nil
 }
 
@@ -310,30 +316,45 @@ func (pb *primitiveBuilder) join(rpb *primitiveBuilder, ajoin *sqlparser.JoinTab
 	}
 
 	// Try merging the routes.
-	if !lRoute.JoinCanMerge(pb, rRoute, ajoin) {
-		return newJoin(pb, rpb, ajoin)
+	isLeftJoin := ajoin != nil && ajoin.Join == sqlparser.LeftJoinStr
+	var mergedRouteOptions []*routeOption
+outer:
+	for _, lro := range lRoute.routeOptions {
+		for _, rro := range rRoute.routeOptions {
+			if lro.JoinCanMerge(pb, rro, ajoin) {
+				lro.MergeJoin(rro, isLeftJoin)
+				mergedRouteOptions = append(mergedRouteOptions, lro)
+				continue outer
+			}
+		}
 	}
-
-	if lRoute.eroute.Opcode == engine.SelectReference {
-		// Swap the conditions & eroutes, and then merge.
-		lRoute.condition, rRoute.condition = rRoute.condition, lRoute.condition
-		lRoute.eroute, rRoute.eroute = rRoute.eroute, lRoute.eroute
+	if len(mergedRouteOptions) != 0 {
+		return pb.mergeRoutes(rpb, mergedRouteOptions, ajoin)
 	}
-	lRoute.substitutions = append(lRoute.substitutions, rRoute.substitutions...)
-	rRoute.Redirect = lRoute
 
-	// Merge the AST.
+	return newJoin(pb, rpb, ajoin)
+}
+
+// mergeRoutes merges the two routes. The ON clause is also analyzed to
+// see if the primitive can be improved. The operation can fail if
+// the expression contains a non-pushable subquery. ajoin can be nil
+// if the join is on a ',' operator.
+func (pb *primitiveBuilder) mergeRoutes(rpb *primitiveBuilder, routeOptions []*routeOption, ajoin *sqlparser.JoinTableExpr) error {
+	lRoute := pb.bldr.(*route)
+	rRoute := rpb.bldr.(*route)
 	sel := lRoute.Select.(*sqlparser.Select)
+
 	if ajoin == nil {
 		rhsSel := rRoute.Select.(*sqlparser.Select)
 		sel.From = append(sel.From, rhsSel.From...)
 	} else {
 		sel.From = sqlparser.TableExprs{ajoin}
 	}
-
+	rRoute.Redirect = lRoute
 	// Since the routes have merged, set st.singleRoute to point at
 	// the merged route.
 	pb.st.singleRoute = lRoute
+	lRoute.routeOptions = routeOptions
 	if ajoin == nil {
 		return nil
 	}
@@ -344,7 +365,7 @@ func (pb *primitiveBuilder) join(rpb *primitiveBuilder, ajoin *sqlparser.JoinTab
 	ajoin.Condition.On = expr
 	pb.addPullouts(pullouts)
 	for _, filter := range splitAndExpression(nil, ajoin.Condition.On) {
-		lRoute.UpdatePlan(pb, filter)
+		lRoute.UpdatePlans(pb, filter)
 	}
 	return nil
 }
diff --git a/go/vt/vtgate/planbuilder/insert.go b/go/vt/vtgate/planbuilder/insert.go
index 88e3f07ac3..aa584b1928 100644
--- a/go/vt/vtgate/planbuilder/insert.go
+++ b/go/vt/vtgate/planbuilder/insert.go
@@ -22,7 +22,6 @@ import (
 	"strconv"
 
 	"vitess.io/vitess/go/sqltypes"
-	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
 	"vitess.io/vitess/go/vt/sqlparser"
 	"vitess.io/vitess/go/vt/vterrors"
 	"vitess.io/vitess/go/vt/vtgate/engine"
@@ -34,35 +33,25 @@ func buildInsertPlan(stmt sqlparser.Statement, vschema ContextVSchema) (engine.P
 	ins := stmt.(*sqlparser.Insert)
 	pb := newPrimitiveBuilder(vschema, newJointab(sqlparser.GetBindvars(ins)))
 	exprs := sqlparser.TableExprs{&sqlparser.AliasedTableExpr{Expr: ins.Table}}
-	rb, err := pb.processDMLTable(exprs)
+	ro, err := pb.processDMLTable(exprs)
 	if err != nil {
 		return nil, err
 	}
 	// The table might have been routed to a different one.
 	ins.Table = exprs[0].(*sqlparser.AliasedTableExpr).Expr.(sqlparser.TableName)
-	if rb.eroute.TargetDestination != nil {
+	if ro.eroute.TargetDestination != nil {
 		return nil, errors.New("unsupported: INSERT with a target destination")
 	}
-
-	if len(pb.st.tables) != 1 {
-		// Unreachable.
-		return nil, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "unsupported: multi-table insert statement in sharded keyspace")
-	}
-	var vschemaTable *vindexes.Table
-	for _, tval := range pb.st.tables {
-		// There is only one table.
-		vschemaTable = tval.vschemaTable
-	}
-	if !rb.eroute.Keyspace.Sharded {
+	if !ro.vschemaTable.Keyspace.Sharded {
 		if !pb.finalizeUnshardedDMLSubqueries(ins) {
 			return nil, errors.New("unsupported: sharded subquery in insert values")
 		}
-		return buildInsertUnshardedPlan(ins, vschemaTable)
+		return buildInsertUnshardedPlan(ins, ro.vschemaTable)
 	}
 	if ins.Action == sqlparser.ReplaceStr {
 		return nil, errors.New("unsupported: REPLACE INTO with sharded schema")
 	}
-	return buildInsertShardedPlan(ins, vschemaTable)
+	return buildInsertShardedPlan(ins, ro.vschemaTable)
 }
 
 func buildInsertUnshardedPlan(ins *sqlparser.Insert, table *vindexes.Table) (engine.Primitive, error) {
diff --git a/go/vt/vtgate/planbuilder/merge_sort.go b/go/vt/vtgate/planbuilder/merge_sort.go
index 67f182b6df..249d15604e 100644
--- a/go/vt/vtgate/planbuilder/merge_sort.go
+++ b/go/vt/vtgate/planbuilder/merge_sort.go
@@ -99,22 +99,24 @@ func (ms *mergeSort) Wireup(bldr builder, jt *jointab) error {
 	// and use that value instead. This is because we cannot mimic
 	// mysql's collation behavior yet.
 	rb := ms.input.(*route)
-	for i, orderby := range rb.eroute.OrderBy {
+	rb.finalizeOptions()
+	ro := rb.routeOptions[0]
+	for i, orderby := range ro.eroute.OrderBy {
 		rc := ms.resultColumns[orderby.Col]
 		if sqltypes.IsText(rc.column.typ) {
 			// If a weight string was previously requested, reuse it.
 			if colNumber, ok := ms.weightStrings[rc]; ok {
-				rb.eroute.OrderBy[i].Col = colNumber
+				ro.eroute.OrderBy[i].Col = colNumber
 				continue
 			}
 			var err error
-			rb.eroute.OrderBy[i].Col, err = rb.SupplyWeightString(orderby.Col)
+			ro.eroute.OrderBy[i].Col, err = rb.SupplyWeightString(orderby.Col)
 			if err != nil {
 				return err
 			}
 			ms.truncateColumnCount = len(ms.resultColumns)
 		}
 	}
-	rb.eroute.TruncateColumnCount = ms.truncateColumnCount
+	ro.eroute.TruncateColumnCount = ms.truncateColumnCount
 	return ms.input.Wireup(bldr, jt)
 }
diff --git a/go/vt/vtgate/planbuilder/ordered_aggregate.go b/go/vt/vtgate/planbuilder/ordered_aggregate.go
index 54548faffd..6a7324e6f5 100644
--- a/go/vt/vtgate/planbuilder/ordered_aggregate.go
+++ b/go/vt/vtgate/planbuilder/ordered_aggregate.go
@@ -65,7 +65,7 @@ type orderedAggregate struct {
 // can handle.
 func (pb *primitiveBuilder) checkAggregates(sel *sqlparser.Select) error {
 	rb, isRoute := pb.bldr.(*route)
-	if isRoute && rb.isSingleShard() {
+	if isRoute && rb.removeMultishardOptions() {
 		return nil
 	}
 
@@ -101,14 +101,20 @@ func (pb *primitiveBuilder) checkAggregates(sel *sqlparser.Select) error {
 	// shards, which will require us to perform the grouping
 	// at the vtgate level.
 	if sel.Distinct {
-		for _, selectExpr := range sel.SelectExprs {
-			switch selectExpr := selectExpr.(type) {
-			case *sqlparser.AliasedExpr:
-				vindex := pb.st.Vindex(selectExpr.Expr, rb)
-				if vindex != nil && vindex.IsUnique() {
-					return nil
+		success := rb.removeOptions(func(ro *routeOption) bool {
+			for _, selectExpr := range sel.SelectExprs {
+				switch selectExpr := selectExpr.(type) {
+				case *sqlparser.AliasedExpr:
+					vindex := ro.FindVindex(pb, selectExpr.Expr)
+					if vindex != nil && vindex.IsUnique() {
+						return true
+					}
 				}
 			}
+			return false
+		})
+		if success {
+			return nil
 		}
 	}
 
@@ -175,40 +181,42 @@ func nodeHasAggregates(node sqlparser.SQLNode) bool {
 // error conditions are treated as no match for simplicity; They will be
 // subsequently caught downstream.
 func (pb *primitiveBuilder) groupByHasUniqueVindex(sel *sqlparser.Select, rb *route) bool {
-	for _, expr := range sel.GroupBy {
-		var matchedExpr sqlparser.Expr
-		switch node := expr.(type) {
-		case *sqlparser.ColName:
-			if expr := findAlias(node, sel.SelectExprs); expr != nil {
-				matchedExpr = expr
-			} else {
-				matchedExpr = node
-			}
-		case *sqlparser.SQLVal:
-			if node.Type != sqlparser.IntVal {
-				continue
-			}
-			num, err := strconv.ParseInt(string(node.Val), 0, 64)
-			if err != nil {
-				continue
-			}
-			if num < 1 || num > int64(len(sel.SelectExprs)) {
+	return rb.removeOptions(func(ro *routeOption) bool {
+		for _, expr := range sel.GroupBy {
+			var matchedExpr sqlparser.Expr
+			switch node := expr.(type) {
+			case *sqlparser.ColName:
+				if expr := findAlias(node, sel.SelectExprs); expr != nil {
+					matchedExpr = expr
+				} else {
+					matchedExpr = node
+				}
+			case *sqlparser.SQLVal:
+				if node.Type != sqlparser.IntVal {
+					continue
+				}
+				num, err := strconv.ParseInt(string(node.Val), 0, 64)
+				if err != nil {
+					continue
+				}
+				if num < 1 || num > int64(len(sel.SelectExprs)) {
+					continue
+				}
+				expr, ok := sel.SelectExprs[num-1].(*sqlparser.AliasedExpr)
+				if !ok {
+					continue
+				}
+				matchedExpr = expr.Expr
+			default:
 				continue
 			}
-			expr, ok := sel.SelectExprs[num-1].(*sqlparser.AliasedExpr)
-			if !ok {
-				continue
+			vindex := ro.FindVindex(pb, matchedExpr)
+			if vindex != nil && vindex.IsUnique() {
+				return true
 			}
-			matchedExpr = expr.Expr
-		default:
-			continue
-		}
-		vindex := pb.st.Vindex(matchedExpr, rb)
-		if vindex != nil && vindex.IsUnique() {
-			return true
 		}
-	}
-	return false
+		return false
+	})
 }
 
 func findAlias(colname *sqlparser.ColName, selects sqlparser.SelectExprs) sqlparser.Expr {
@@ -349,11 +357,14 @@ func (oa *orderedAggregate) needDistinctHandling(pb *primitiveBuilder, funcExpr
 		// Unreachable
 		return true, innerAliased, nil
 	}
-	vindex := pb.st.Vindex(innerAliased.Expr, rb)
-	if vindex != nil && vindex.IsUnique() {
-		return false, nil, nil
-	}
-	return true, innerAliased, nil
+	success := rb.removeOptions(func(ro *routeOption) bool {
+		vindex := ro.FindVindex(pb, innerAliased.Expr)
+		if vindex != nil && vindex.IsUnique() {
+			return true
+		}
+		return false
+	})
+	return !success, innerAliased, nil
 }
 
 func (oa *orderedAggregate) MakeDistinct() error {
diff --git a/go/vt/vtgate/planbuilder/postprocess.go b/go/vt/vtgate/planbuilder/postprocess.go
index 33e16ad54f..1f2f830fb5 100644
--- a/go/vt/vtgate/planbuilder/postprocess.go
+++ b/go/vt/vtgate/planbuilder/postprocess.go
@@ -58,7 +58,7 @@ func (pb *primitiveBuilder) pushLimit(limit *sqlparser.Limit) error {
 		return nil
 	}
 	rb, ok := pb.bldr.(*route)
-	if ok && rb.isSingleShard() {
+	if ok && rb.removeMultishardOptions() {
 		rb.SetLimit(limit)
 		return nil
 	}
diff --git a/go/vt/vtgate/planbuilder/route.go b/go/vt/vtgate/planbuilder/route.go
index 2eead2e5e9..4f810805cd 100644
--- a/go/vt/vtgate/planbuilder/route.go
+++ b/go/vt/vtgate/planbuilder/route.go
@@ -23,7 +23,6 @@ import (
 	"vitess.io/vitess/go/sqltypes"
 	"vitess.io/vitess/go/vt/sqlparser"
 	"vitess.io/vitess/go/vt/vtgate/engine"
-	"vitess.io/vitess/go/vt/vtgate/vindexes"
 )
 
 var _ builder = (*route)(nil)
@@ -33,6 +32,10 @@ var _ builder = (*route)(nil)
 // SelectScatter, etc. Portions of the original Select AST
 // are moved into this node, which will be used to build
 // the final SQL for this route.
+// A route can have multiple routeOptions. They are kept
+// up-to-date as the route improves. Those that don't
+// qualify are continuously removed from the options.
+// A single best route is chosen before the Wireup phase.
 type route struct {
 	order int
 
@@ -53,20 +56,7 @@ type route struct {
 	// are added to be used for collation of text columns.
 	weightStrings map[*resultColumn]int
 
-	// substitutions contain the list of table expressions that
-	// have to be substituted in the route's query.
-	substitutions []*tableSubstitution
-
-	// condition stores the AST condition that will be used
-	// to resolve the ERoute Values field.
-	condition sqlparser.Expr
-
-	// eroute is the primitive being built.
-	eroute *engine.Route
-}
-
-type tableSubstitution struct {
-	newExpr, oldExpr *sqlparser.AliasedTableExpr
+	routeOptions []*routeOption
 }
 
 func newRoute(stmt sqlparser.SelectStatement) (*route, *symtab) {
@@ -99,7 +89,7 @@ func (rb *route) Reorder(order int) {
 
 // Primitive satisfies the builder interface.
 func (rb *route) Primitive() engine.Primitive {
-	return rb.eroute
+	return rb.routeOptions[0].eroute
 }
 
 // PushLock satisfies the builder interface.
@@ -128,10 +118,16 @@ func (rb *route) PushFilter(pb *primitiveBuilder, filter sqlparser.Expr, whereTy
 	case sqlparser.HavingStr:
 		sel.AddHaving(filter)
 	}
-	rb.UpdatePlan(pb, filter)
+	rb.UpdatePlans(pb, filter)
 	return nil
 }
 
+func (rb *route) UpdatePlans(pb *primitiveBuilder, filter sqlparser.Expr) {
+	for _, ro := range rb.routeOptions {
+		ro.UpdatePlan(pb, filter)
+	}
+}
+
 // PushSelect satisfies the builder interface.
 func (rb *route) PushSelect(_ *primitiveBuilder, expr *sqlparser.AliasedExpr, _ builder) (rc *resultColumn, colNumber int, err error) {
 	sel := rb.Select.(*sqlparser.Select)
@@ -190,7 +186,7 @@ func (rb *route) PushOrderBy(orderBy sqlparser.OrderBy) (builder, error) {
 		}
 	}
 
-	if rb.isSingleShard() {
+	if rb.removeMultishardOptions() {
 		for _, order := range orderBy {
 			rb.Select.AddOrder(order)
 		}
@@ -226,7 +222,9 @@ func (rb *route) PushOrderBy(orderBy sqlparser.OrderBy) (builder, error) {
 			Col:  colNumber,
 			Desc: order.Direction == sqlparser.DescScr,
 		}
-		rb.eroute.OrderBy = append(rb.eroute.OrderBy, ob)
+		for _, ro := range rb.routeOptions {
+			ro.eroute.OrderBy = append(ro.eroute.OrderBy, ob)
+		}
 
 		rb.Select.AddOrder(order)
 	}
@@ -255,16 +253,19 @@ func (rb *route) PushMisc(sel *sqlparser.Select) {
 
 // Wireup satisfies the builder interface.
 func (rb *route) Wireup(bldr builder, jt *jointab) error {
+	rb.finalizeOptions()
+
 	// Precaution: update ERoute.Values only if it's not set already.
-	if rb.eroute.Values == nil {
+	ro := rb.routeOptions[0]
+	if ro.eroute.Values == nil {
 		// Resolve values stored in the builder.
-		switch vals := rb.condition.(type) {
+		switch vals := ro.condition.(type) {
 		case *sqlparser.ComparisonExpr:
 			pv, err := rb.procureValues(bldr, jt, vals.Right)
 			if err != nil {
 				return err
 			}
-			rb.eroute.Values = []sqltypes.PlanValue{pv}
+			ro.eroute.Values = []sqltypes.PlanValue{pv}
 			vals.Right = sqlparser.ListArg("::" + engine.ListVarName)
 		case nil:
 			// no-op.
@@ -273,7 +274,7 @@ func (rb *route) Wireup(bldr builder, jt *jointab) error {
 			if err != nil {
 				return err
 			}
-			rb.eroute.Values = []sqltypes.PlanValue{pv}
+			ro.eroute.Values = []sqltypes.PlanValue{pv}
 		}
 	}
 
@@ -290,7 +291,7 @@ func (rb *route) Wireup(bldr builder, jt *jointab) error {
 			}
 		case *sqlparser.ComparisonExpr:
 			if node.Operator == sqlparser.EqualStr {
-				if rb.exprIsValue(node.Left) && !rb.exprIsValue(node.Right) {
+				if ro.exprIsValue(node.Left) && !ro.exprIsValue(node.Right) {
 					node.Left, node.Right = node.Right, node.Left
 				}
 			}
@@ -299,7 +300,7 @@ func (rb *route) Wireup(bldr builder, jt *jointab) error {
 	}, rb.Select)
 
 	// Substitute table names
-	for _, sub := range rb.substitutions {
+	for _, sub := range ro.substitutions {
 		*sub.oldExpr = *sub.newExpr
 	}
 
@@ -324,8 +325,8 @@ func (rb *route) Wireup(bldr builder, jt *jointab) error {
 	}
 	buf := sqlparser.NewTrackedBuffer(varFormatter)
 	varFormatter(buf, rb.Select)
-	rb.eroute.Query = buf.ParsedQuery().Query
-	rb.eroute.FieldQuery = rb.generateFieldQuery(rb.Select, jt)
+	ro.eroute.Query = buf.ParsedQuery().Query
+	ro.eroute.FieldQuery = rb.generateFieldQuery(rb.Select, jt)
 	return nil
 }
 
@@ -336,6 +337,16 @@ func systemTable(qualifier string) bool {
 		strings.EqualFold(qualifier, "mysql")
 }
 
+func (rb *route) finalizeOptions() {
+	bestOption := rb.routeOptions[0]
+	for i := 1; i < len(rb.routeOptions); i++ {
+		if cur := rb.routeOptions[i]; cur.isBetterThan(bestOption) {
+			bestOption = cur
+		}
+	}
+	rb.routeOptions = []*routeOption{bestOption}
+}
+
 // procureValues procures and converts the input into
 // the expected types for rb.Values.
 func (rb *route) procureValues(bldr builder, jt *jointab, val sqlparser.Expr) (sqltypes.PlanValue, error) {
@@ -438,8 +449,19 @@ func (rb *route) SupplyWeightString(colNumber int) (weightcolNumber int, err err
 // MergeSubquery returns true if the subquery route could successfully be merged
 // with the outer route.
 func (rb *route) MergeSubquery(pb *primitiveBuilder, inner *route) bool {
-	if rb.SubqueryCanMerge(pb, inner) {
-		rb.substitutions = append(rb.substitutions, inner.substitutions...)
+	var mergedRouteOptions []*routeOption
+outer:
+	for _, lro := range rb.routeOptions {
+		for _, rro := range inner.routeOptions {
+			if lro.SubqueryCanMerge(pb, rro) {
+				lro.MergeSubquery(rro)
+				mergedRouteOptions = append(mergedRouteOptions, lro)
+				continue outer
+			}
+		}
+	}
+	if len(mergedRouteOptions) != 0 {
+		rb.routeOptions = mergedRouteOptions
 		inner.Redirect = rb
 		return true
 	}
@@ -449,295 +471,59 @@ func (rb *route) MergeSubquery(pb *primitiveBuilder, inner *route) bool {
 // MergeUnion returns true if the rhs route could successfully be merged
 // with the rb route.
 func (rb *route) MergeUnion(right *route) bool {
-	if rb.UnionCanMerge(right) {
-		rb.substitutions = append(rb.substitutions, right.substitutions...)
-		right.Redirect = rb
-		return true
-	}
-	return false
-}
-
-func (rb *route) isSingleShard() bool {
-	switch rb.eroute.Opcode {
-	case engine.SelectUnsharded, engine.SelectDBA, engine.SelectNext, engine.SelectEqualUnique, engine.SelectReference:
-		return true
-	}
-	return false
-}
-
-// JoinCanMerge, SubqueryCanMerge and UnionCanMerge have subtly different behaviors.
-// The difference in behavior is around SelectReference.
-// It's not worth trying to reuse the code between them.
-func (rb *route) JoinCanMerge(pb *primitiveBuilder, rrb *route, ajoin *sqlparser.JoinTableExpr) bool {
-	if rb.eroute.Keyspace.Name != rrb.eroute.Keyspace.Name {
-		return false
-	}
-	if rrb.eroute.Opcode == engine.SelectReference {
-		// Any opcode can join with a reference table.
-		return true
-	}
-	switch rb.eroute.Opcode {
-	case engine.SelectUnsharded, engine.SelectDBA:
-		return rb.eroute.Opcode == rrb.eroute.Opcode
-	case engine.SelectEqualUnique:
-		// Check if they target the same shard.
-		if rrb.eroute.Opcode == engine.SelectEqualUnique && rb.eroute.Vindex == rrb.eroute.Vindex && valEqual(rb.condition, rrb.condition) {
-			return true
-		}
-	case engine.SelectReference:
-		return true
-	case engine.SelectNext:
-		return false
-	}
-	if ajoin == nil {
-		return false
-	}
-	for _, filter := range splitAndExpression(nil, ajoin.Condition.On) {
-		if rb.canMergeOnFilter(pb, rrb, filter) {
-			return true
-		}
-	}
-	return false
-}
-
-func (rb *route) SubqueryCanMerge(pb *primitiveBuilder, inner *route) bool {
-	if rb.eroute.Keyspace.Name != inner.eroute.Keyspace.Name {
-		return false
-	}
-	switch rb.eroute.Opcode {
-	case engine.SelectUnsharded, engine.SelectDBA, engine.SelectReference:
-		return rb.eroute.Opcode == inner.eroute.Opcode || inner.eroute.Opcode == engine.SelectReference
-	case engine.SelectEqualUnique:
-		// Check if they target the same shard.
-		if inner.eroute.Opcode == engine.SelectEqualUnique && rb.eroute.Vindex == inner.eroute.Vindex && valEqual(rb.condition, inner.condition) {
-			return true
+	var mergedRouteOptions []*routeOption
+outer:
+	for _, lro := range rb.routeOptions {
+		for _, rro := range right.routeOptions {
+			if lro.UnionCanMerge(rro) {
+				lro.MergeUnion(rro)
+				mergedRouteOptions = append(mergedRouteOptions, lro)
+				continue outer
+			}
 		}
-	case engine.SelectNext:
-		return false
 	}
-	// Any sharded plan (including SelectEqualUnique) can merge on a reference table subquery.
-	// This excludes the case of SelectReference with a sharded subquery.
-	if inner.eroute.Opcode == engine.SelectReference {
+	if len(mergedRouteOptions) != 0 {
+		rb.routeOptions = mergedRouteOptions
+		right.Redirect = rb
 		return true
 	}
-	switch vals := inner.condition.(type) {
-	case *sqlparser.ColName:
-		if pb.st.Vindex(vals, rb) == inner.eroute.Vindex {
-			return true
-		}
-	}
 	return false
 }
 
-func (rb *route) UnionCanMerge(rrb *route) bool {
-	if rb.eroute.Keyspace.Name != rrb.eroute.Keyspace.Name {
-		return false
-	}
-	switch rb.eroute.Opcode {
-	case engine.SelectUnsharded, engine.SelectDBA, engine.SelectReference:
-		return rb.eroute.Opcode == rrb.eroute.Opcode
-	case engine.SelectEqualUnique:
-		// Check if they target the same shard.
-		if rrb.eroute.Opcode == engine.SelectEqualUnique && rb.eroute.Vindex == rrb.eroute.Vindex && valEqual(rb.condition, rrb.condition) {
+// removeMultishardOptions removes all multi-shard options from the
+// route. It returns false if no such options exist.
+func (rb *route) removeMultishardOptions() bool {
+	return rb.removeOptions(func(ro *routeOption) bool {
+		switch ro.eroute.Opcode {
+		case engine.SelectUnsharded, engine.SelectDBA, engine.SelectNext, engine.SelectEqualUnique, engine.SelectReference:
 			return true
 		}
-	case engine.SelectNext:
-		return false
-	}
-	return false
-}
-
-// canMergeOnFilter returns true if the join constraint makes the routes
-// mergeable by unique vindex. The constraint has to be an equality
-// like a.id = b.id where both columns have the same unique vindex.
-func (rb *route) canMergeOnFilter(pb *primitiveBuilder, rrb *route, filter sqlparser.Expr) bool {
-	comparison, ok := filter.(*sqlparser.ComparisonExpr)
-	if !ok {
-		return false
-	}
-	if comparison.Operator != sqlparser.EqualStr {
 		return false
-	}
-	left := comparison.Left
-	right := comparison.Right
-	lVindex := pb.st.Vindex(left, rb)
-	if lVindex == nil {
-		left, right = right, left
-		lVindex = pb.st.Vindex(left, rb)
-	}
-	if lVindex == nil || !lVindex.IsUnique() {
-		return false
-	}
-	rVindex := pb.st.Vindex(right, rrb)
-	if rVindex == nil {
-		return false
-	}
-	return rVindex == lVindex
-}
-
-// UpdatePlan evaluates the primitive against the specified
-// filter. If it's an improvement, the primitive is updated.
-// We assume that the filter has already been pushed into
-// the route.
-func (rb *route) UpdatePlan(pb *primitiveBuilder, filter sqlparser.Expr) {
-	switch rb.eroute.Opcode {
-	// For these opcodes, a new filter will not make any difference, so we can just exit early
-	case engine.SelectUnsharded, engine.SelectNext, engine.SelectDBA, engine.SelectReference, engine.SelectNone:
-		return
-	}
-	opcode, vindex, values := rb.computePlan(pb, filter)
-	if opcode == engine.SelectScatter {
-		return
-	}
-	// If we get SelectNone in next filters, override the previous route plan.
-	if opcode == engine.SelectNone {
-		rb.updateRoute(opcode, vindex, values)
-		return
-	}
-	switch rb.eroute.Opcode {
-	case engine.SelectEqualUnique:
-		if opcode == engine.SelectEqualUnique && vindex.Cost() < rb.eroute.Vindex.Cost() {
-			rb.updateRoute(opcode, vindex, values)
-		}
-	case engine.SelectEqual:
-		switch opcode {
-		case engine.SelectEqualUnique:
-			rb.updateRoute(opcode, vindex, values)
-		case engine.SelectEqual:
-			if vindex.Cost() < rb.eroute.Vindex.Cost() {
-				rb.updateRoute(opcode, vindex, values)
-			}
-		}
-	case engine.SelectIN:
-		switch opcode {
-		case engine.SelectEqualUnique, engine.SelectEqual:
-			rb.updateRoute(opcode, vindex, values)
-		case engine.SelectIN:
-			if vindex.Cost() < rb.eroute.Vindex.Cost() {
-				rb.updateRoute(opcode, vindex, values)
-			}
-		}
-	case engine.SelectScatter:
-		switch opcode {
-		case engine.SelectEqualUnique, engine.SelectEqual, engine.SelectIN, engine.SelectNone:
-			rb.updateRoute(opcode, vindex, values)
-		}
-	}
+	})
 }
 
-func (rb *route) updateRoute(opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
-	rb.eroute.Opcode = opcode
-	rb.eroute.Vindex = vindex
-	rb.condition = condition
+// removeOptionsWithUnmatchedKeyspace removes all options that don't match
+// the specified keyspace. It returns false if no such options exist.
+func (rb *route) removeOptionsWithUnmatchedKeyspace(keyspace string) bool {
+	return rb.removeOptions(func(ro *routeOption) bool {
+		return ro.eroute.Keyspace.Name == keyspace
+	})
 }
 
-// computePlan computes the plan for the specified filter.
-func (rb *route) computePlan(pb *primitiveBuilder, filter sqlparser.Expr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
-	switch node := filter.(type) {
-	case *sqlparser.ComparisonExpr:
-		switch node.Operator {
-		case sqlparser.EqualStr:
-			return rb.computeEqualPlan(pb, node)
-		case sqlparser.InStr:
-			return rb.computeINPlan(pb, node)
-		case sqlparser.NotInStr:
-			return rb.computeNotInPlan(node.Right), nil, nil
+// removeOptions removes all options that don't match on
+// the criteria function. It returns false if no such options exist.
+func (rb *route) removeOptions(match func(*routeOption) bool) bool {
+	var newOptions []*routeOption
+	for _, ro := range rb.routeOptions {
+		if match(ro) {
+			newOptions = append(newOptions, ro)
 		}
-	case *sqlparser.IsExpr:
-		return rb.computeISPlan(pb, node)
 	}
-	return engine.SelectScatter, nil, nil
-}
-
-// computeEqualPlan computes the plan for an equality constraint.
-func (rb *route) computeEqualPlan(pb *primitiveBuilder, comparison *sqlparser.ComparisonExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
-	left := comparison.Left
-	right := comparison.Right
-
-	if sqlparser.IsNull(right) {
-		return engine.SelectNone, nil, nil
-	}
-
-	vindex = pb.st.Vindex(left, rb)
-	if vindex == nil {
-		left, right = right, left
-		vindex = pb.st.Vindex(left, rb)
-		if vindex == nil {
-			return engine.SelectScatter, nil, nil
-		}
-	}
-	if !rb.exprIsValue(right) {
-		return engine.SelectScatter, nil, nil
-	}
-	if vindex.IsUnique() {
-		return engine.SelectEqualUnique, vindex, right
-	}
-	return engine.SelectEqual, vindex, right
-}
-
-// computeEqualPlan computes the plan for an equality constraint.
-func (rb *route) computeISPlan(pb *primitiveBuilder, comparison *sqlparser.IsExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
-	// we only handle IS NULL correct. IsExpr can contain other expressions as well
-	if comparison.Operator != sqlparser.IsNullStr {
-		return engine.SelectScatter, nil, nil
-	}
-
-	vindex = pb.st.Vindex(comparison.Expr, rb)
-	// fallback to scatter gather if there is no vindex
-	if vindex == nil {
-		return engine.SelectScatter, nil, nil
-	}
-	if vindex.IsUnique() {
-		return engine.SelectEqualUnique, vindex, &sqlparser.NullVal{}
-	}
-	return engine.SelectEqual, vindex, &sqlparser.NullVal{}
-}
-
-// computeINPlan computes the plan for an IN constraint.
-func (rb *route) computeINPlan(pb *primitiveBuilder, comparison *sqlparser.ComparisonExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
-	vindex = pb.st.Vindex(comparison.Left, rb)
-	if vindex == nil {
-		return engine.SelectScatter, nil, nil
-	}
-	switch node := comparison.Right.(type) {
-	case sqlparser.ValTuple:
-		if len(node) == 1 && sqlparser.IsNull(node[0]) {
-			return engine.SelectNone, nil, nil
-		}
-
-		for _, n := range node {
-			if !rb.exprIsValue(n) {
-				return engine.SelectScatter, nil, nil
-			}
-		}
-		return engine.SelectIN, vindex, comparison
-	case sqlparser.ListArg:
-		return engine.SelectIN, vindex, comparison
-	}
-	return engine.SelectScatter, nil, nil
-}
-
-// computeNotInPlan looks for null values to produce a SelectNone if found
-func (rb *route) computeNotInPlan(right sqlparser.Expr) engine.RouteOpcode {
-	switch node := right.(type) {
-	case sqlparser.ValTuple:
-		for _, n := range node {
-			if sqlparser.IsNull(n) {
-				return engine.SelectNone
-			}
-		}
-	}
-
-	return engine.SelectScatter
-}
-
-// exprIsValue returns true if the expression can be treated as a value
-// for the routeOption. External references are treated as value.
-func (rb *route) exprIsValue(expr sqlparser.Expr) bool {
-	if node, ok := expr.(*sqlparser.ColName); ok {
-		return node.Metadata.(*column).Origin() != rb
+	if len(newOptions) == 0 {
+		return false
 	}
-	return sqlparser.IsValue(expr)
+	rb.routeOptions = newOptions
+	return true
 }
 
 // queryTimeout returns DirectiveQueryTimeout value if set, otherwise returns 0.
diff --git a/go/vt/vtgate/planbuilder/route_option.go b/go/vt/vtgate/planbuilder/route_option.go
new file mode 100644
index 0000000000..4710cde0bf
--- /dev/null
+++ b/go/vt/vtgate/planbuilder/route_option.go
@@ -0,0 +1,403 @@
+/*
+Copyright 2019 The Vitess Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package planbuilder
+
+import (
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vtgate/engine"
+	"vitess.io/vitess/go/vt/vtgate/vindexes"
+)
+
+// routeOption contains all the information for one route option.
+// A route can have multiple options.
+type routeOption struct {
+	rb *route
+
+	// vschemaTable is set only if a single table is referenced
+	// in the from clause. It's used only for DMLs.
+	vschemaTable *vindexes.Table
+
+	// substitutions contain the list of table expressions that
+	// have to be substituted in the route's query.
+	substitutions []*tableSubstitution
+
+	// vindexMap is a map of all vindexMap that can be used
+	// for the routeOption.
+	vindexMap map[*column]vindexes.SingleColumn
+
+	// condition stores the AST condition that will be used
+	// to resolve the ERoute Values field.
+	condition sqlparser.Expr
+
+	// eroute is the primitive being built.
+	eroute *engine.Route
+}
+
+type tableSubstitution struct {
+	newExpr, oldExpr *sqlparser.AliasedTableExpr
+}
+
+func newSimpleRouteOption(rb *route, eroute *engine.Route) *routeOption {
+	return &routeOption{
+		rb:     rb,
+		eroute: eroute,
+	}
+}
+
+func newRouteOption(rb *route, vst *vindexes.Table, sub *tableSubstitution, vindexMap map[*column]vindexes.SingleColumn, eroute *engine.Route) *routeOption {
+	var subs []*tableSubstitution
+	if sub != nil && sub.newExpr != nil {
+		subs = []*tableSubstitution{sub}
+	}
+	return &routeOption{
+		rb:            rb,
+		vschemaTable:  vst,
+		substitutions: subs,
+		vindexMap:     vindexMap,
+		eroute:        eroute,
+	}
+}
+
+func (ro *routeOption) JoinCanMerge(pb *primitiveBuilder, rro *routeOption, ajoin *sqlparser.JoinTableExpr) bool {
+	return ro.canMerge(rro, func() bool {
+		if ajoin == nil {
+			return false
+		}
+		for _, filter := range splitAndExpression(nil, ajoin.Condition.On) {
+			if ro.canMergeOnFilter(pb, rro, filter) {
+				return true
+			}
+		}
+		return false
+	})
+}
+
+func (ro *routeOption) MergeJoin(rro *routeOption, isLeftJoin bool) {
+	ro.merge(rro)
+	ro.vschemaTable = nil
+	for c, v := range rro.vindexMap {
+		if ro.vindexMap == nil {
+			ro.vindexMap = make(map[*column]vindexes.SingleColumn)
+		}
+		ro.vindexMap[c] = v
+	}
+}
+
+// merge merges two routeOptions. If the LHS (ro) is a SelectReference,
+// then the RHS option values supersede LHS.
+func (ro *routeOption) merge(rro *routeOption) {
+	if ro.eroute.Opcode == engine.SelectReference {
+		// Swap the values and then merge.
+		*ro, *rro = *rro, *ro
+	}
+	ro.substitutions = append(ro.substitutions, rro.substitutions...)
+}
+
+func (ro *routeOption) SubqueryCanMerge(pb *primitiveBuilder, inner *routeOption) bool {
+	return ro.canMerge(inner, func() bool {
+		switch vals := inner.condition.(type) {
+		case *sqlparser.ColName:
+			if ro.FindVindex(pb, vals) == inner.eroute.Vindex {
+				return true
+			}
+		}
+		return false
+	})
+}
+
+func (ro *routeOption) MergeSubquery(subqueryOption *routeOption) {
+	ro.merge(subqueryOption)
+}
+
+func (ro *routeOption) UnionCanMerge(rro *routeOption) bool {
+	return ro.canMerge(rro, func() bool { return false })
+}
+
+func (ro *routeOption) MergeUnion(rro *routeOption) {
+	ro.merge(rro)
+	ro.vschemaTable = nil
+}
+
+func (ro *routeOption) SubqueryToTable(rb *route, vindexMap map[*column]vindexes.SingleColumn) {
+	ro.rb = rb
+	ro.vschemaTable = nil
+	ro.vindexMap = vindexMap
+}
+
+func (ro *routeOption) canMerge(rro *routeOption, customCheck func() bool) bool {
+	if ro.eroute.Keyspace.Name != rro.eroute.Keyspace.Name {
+		return false
+	}
+	if rro.eroute.Opcode == engine.SelectReference {
+		// Any opcode can join with a reference table.
+		return true
+	}
+	switch ro.eroute.Opcode {
+	case engine.SelectUnsharded, engine.SelectDBA:
+		return ro.eroute.Opcode == rro.eroute.Opcode
+	case engine.SelectEqualUnique:
+		// Check if they target the same shard.
+		if rro.eroute.Opcode == engine.SelectEqualUnique && ro.eroute.Vindex == rro.eroute.Vindex && valEqual(ro.condition, rro.condition) {
+			return true
+		}
+	case engine.SelectReference:
+		return true
+	case engine.SelectNext:
+		return false
+	}
+	if customCheck != nil {
+		return customCheck()
+	}
+	return false
+}
+
+// canMergeOnFilter returns true if the join constraint makes the routes
+// mergeable by unique vindex. The constraint has to be an equality
+// like a.id = b.id where both columns have the same unique vindex.
+func (ro *routeOption) canMergeOnFilter(pb *primitiveBuilder, rro *routeOption, filter sqlparser.Expr) bool {
+	comparison, ok := filter.(*sqlparser.ComparisonExpr)
+	if !ok {
+		return false
+	}
+	if comparison.Operator != sqlparser.EqualStr {
+		return false
+	}
+	left := comparison.Left
+	right := comparison.Right
+	lVindex := ro.FindVindex(pb, left)
+	if lVindex == nil {
+		left, right = right, left
+		lVindex = ro.FindVindex(pb, left)
+	}
+	if lVindex == nil || !lVindex.IsUnique() {
+		return false
+	}
+	rVindex := rro.FindVindex(pb, right)
+	if rVindex == nil {
+		return false
+	}
+	return rVindex == lVindex
+}
+
+// UpdatePlan evaluates the primitive against the specified
+// filter. If it's an improvement, the primitive is updated.
+// We assume that the filter has already been pushed into
+// the route.
+func (ro *routeOption) UpdatePlan(pb *primitiveBuilder, filter sqlparser.Expr) {
+	switch ro.eroute.Opcode {
+	// For these opcodes, a new filter will not make any difference, so we can just exit early
+	case engine.SelectUnsharded, engine.SelectNext, engine.SelectDBA, engine.SelectReference, engine.SelectNone:
+		return
+	}
+	opcode, vindex, values := ro.computePlan(pb, filter)
+	if opcode == engine.SelectScatter {
+		return
+	}
+	// If we get SelectNone in next filters, override the previous route plan.
+	if opcode == engine.SelectNone {
+		ro.updateRoute(opcode, vindex, values)
+		return
+	}
+	switch ro.eroute.Opcode {
+	case engine.SelectEqualUnique:
+		if opcode == engine.SelectEqualUnique && vindex.Cost() < ro.eroute.Vindex.Cost() {
+			ro.updateRoute(opcode, vindex, values)
+		}
+	case engine.SelectEqual:
+		switch opcode {
+		case engine.SelectEqualUnique:
+			ro.updateRoute(opcode, vindex, values)
+		case engine.SelectEqual:
+			if vindex.Cost() < ro.eroute.Vindex.Cost() {
+				ro.updateRoute(opcode, vindex, values)
+			}
+		}
+	case engine.SelectIN:
+		switch opcode {
+		case engine.SelectEqualUnique, engine.SelectEqual:
+			ro.updateRoute(opcode, vindex, values)
+		case engine.SelectIN:
+			if vindex.Cost() < ro.eroute.Vindex.Cost() {
+				ro.updateRoute(opcode, vindex, values)
+			}
+		}
+	case engine.SelectScatter:
+		switch opcode {
+		case engine.SelectEqualUnique, engine.SelectEqual, engine.SelectIN, engine.SelectNone:
+			ro.updateRoute(opcode, vindex, values)
+		}
+	}
+}
+
+func (ro *routeOption) updateRoute(opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
+	ro.eroute.Opcode = opcode
+	ro.eroute.Vindex = vindex
+	ro.condition = condition
+}
+
+// computePlan computes the plan for the specified filter.
+func (ro *routeOption) computePlan(pb *primitiveBuilder, filter sqlparser.Expr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
+	switch node := filter.(type) {
+	case *sqlparser.ComparisonExpr:
+		switch node.Operator {
+		case sqlparser.EqualStr:
+			return ro.computeEqualPlan(pb, node)
+		case sqlparser.InStr:
+			return ro.computeINPlan(pb, node)
+		case sqlparser.NotInStr:
+			return ro.computeNotInPlan(node.Right), nil, nil
+		}
+	case *sqlparser.IsExpr:
+		return ro.computeISPlan(pb, node)
+	}
+	return engine.SelectScatter, nil, nil
+}
+
+// computeEqualPlan computes the plan for an equality constraint.
+func (ro *routeOption) computeEqualPlan(pb *primitiveBuilder, comparison *sqlparser.ComparisonExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
+	left := comparison.Left
+	right := comparison.Right
+
+	if sqlparser.IsNull(right) {
+		return engine.SelectNone, nil, nil
+	}
+
+	vindex = ro.FindVindex(pb, left)
+	if vindex == nil {
+		left, right = right, left
+		vindex = ro.FindVindex(pb, left)
+		if vindex == nil {
+			return engine.SelectScatter, nil, nil
+		}
+	}
+	if !ro.exprIsValue(right) {
+		return engine.SelectScatter, nil, nil
+	}
+	if vindex.IsUnique() {
+		return engine.SelectEqualUnique, vindex, right
+	}
+	return engine.SelectEqual, vindex, right
+}
+
+// computeEqualPlan computes the plan for an equality constraint.
+func (ro *routeOption) computeISPlan(pb *primitiveBuilder, comparison *sqlparser.IsExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
+	// we only handle IS NULL correct. IsExpr can contain other expressions as well
+	if comparison.Operator != sqlparser.IsNullStr {
+		return engine.SelectScatter, nil, nil
+	}
+
+	vindex = ro.FindVindex(pb, comparison.Expr)
+	// fallback to scatter gather if there is no vindex
+	if vindex == nil {
+		return engine.SelectScatter, nil, nil
+	}
+	if vindex.IsUnique() {
+		return engine.SelectEqualUnique, vindex, &sqlparser.NullVal{}
+	}
+	return engine.SelectEqual, vindex, &sqlparser.NullVal{}
+}
+
+// computeINPlan computes the plan for an IN constraint.
+func (ro *routeOption) computeINPlan(pb *primitiveBuilder, comparison *sqlparser.ComparisonExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) {
+	vindex = ro.FindVindex(pb, comparison.Left)
+	if vindex == nil {
+		return engine.SelectScatter, nil, nil
+	}
+	switch node := comparison.Right.(type) {
+	case sqlparser.ValTuple:
+		if len(node) == 1 && sqlparser.IsNull(node[0]) {
+			return engine.SelectNone, nil, nil
+		}
+
+		for _, n := range node {
+			if !ro.exprIsValue(n) {
+				return engine.SelectScatter, nil, nil
+			}
+		}
+		return engine.SelectIN, vindex, comparison
+	case sqlparser.ListArg:
+		return engine.SelectIN, vindex, comparison
+	}
+	return engine.SelectScatter, nil, nil
+}
+
+// computeNotInPlan looks for null values to produce a SelectNone if found
+func (*routeOption) computeNotInPlan(right sqlparser.Expr) engine.RouteOpcode {
+	switch node := right.(type) {
+	case sqlparser.ValTuple:
+		for _, n := range node {
+			if sqlparser.IsNull(n) {
+				return engine.SelectNone
+			}
+		}
+	}
+
+	return engine.SelectScatter
+}
+
+var planCost = map[engine.RouteOpcode]int{
+	engine.SelectUnsharded:   0,
+	engine.SelectNext:        0,
+	engine.SelectDBA:         0,
+	engine.SelectReference:   0,
+	engine.SelectEqualUnique: 1,
+	engine.SelectIN:          2,
+	engine.SelectEqual:       3,
+	engine.SelectScatter:     4,
+}
+
+func (ro *routeOption) isBetterThan(other *routeOption) bool {
+	ropc := planCost[ro.eroute.Opcode]
+	otherpc := planCost[other.eroute.Opcode]
+	if ropc < otherpc {
+		return true
+	}
+	if ropc == otherpc {
+		switch other.eroute.Opcode {
+		case engine.SelectEqualUnique, engine.SelectIN, engine.SelectEqual:
+			return ro.eroute.Vindex.Cost() < other.eroute.Vindex.Cost()
+		}
+	}
+	return false
+}
+
+func (ro *routeOption) FindVindex(pb *primitiveBuilder, expr sqlparser.Expr) vindexes.SingleColumn {
+	col, ok := expr.(*sqlparser.ColName)
+	if !ok {
+		return nil
+	}
+	if col.Metadata == nil {
+		// Find will set the Metadata.
+		if _, _, err := pb.st.Find(col); err != nil {
+			return nil
+		}
+	}
+	c := col.Metadata.(*column)
+	if c.Origin() != ro.rb {
+		return nil
+	}
+	return ro.vindexMap[c]
+}
+
+// exprIsValue returns true if the expression can be treated as a value
+// for the routeOption. External references are treated as value.
+func (ro *routeOption) exprIsValue(expr sqlparser.Expr) bool {
+	if node, ok := expr.(*sqlparser.ColName); ok {
+		return node.Metadata.(*column).Origin() != ro.rb
+	}
+	return sqlparser.IsValue(expr)
+}
diff --git a/go/vt/vtgate/planbuilder/select.go b/go/vt/vtgate/planbuilder/select.go
index d33596d025..4f55df6a4e 100644
--- a/go/vt/vtgate/planbuilder/select.go
+++ b/go/vt/vtgate/planbuilder/select.go
@@ -105,14 +105,16 @@ func (pb *primitiveBuilder) processSelect(sel *sqlparser.Select, outer *symtab)
 
 	if rb, ok := pb.bldr.(*route); ok {
 		// TODO(sougou): this can probably be improved.
-		directives := sqlparser.ExtractCommentDirectives(sel.Comments)
-		rb.eroute.QueryTimeout = queryTimeout(directives)
-		if rb.eroute.TargetDestination != nil {
-			return errors.New("unsupported: SELECT with a target destination")
-		}
+		for _, ro := range rb.routeOptions {
+			directives := sqlparser.ExtractCommentDirectives(sel.Comments)
+			ro.eroute.QueryTimeout = queryTimeout(directives)
+			if ro.eroute.TargetDestination != nil {
+				return errors.New("unsupported: SELECT with a target destination")
+			}
 
-		if directives.IsSet(sqlparser.DirectiveScatterErrorsAsWarnings) {
-			rb.eroute.ScatterErrorsAsWarnings = true
+			if directives.IsSet(sqlparser.DirectiveScatterErrorsAsWarnings) {
+				ro.eroute.ScatterErrorsAsWarnings = true
+			}
 		}
 	}
 
@@ -312,10 +314,12 @@ func (pb *primitiveBuilder) pushSelectRoutes(selectExprs sqlparser.SelectExprs)
 				// This code is unreachable because the parser doesn't allow joins for next val statements.
 				return nil, errors.New("unsupported: SELECT NEXT query in cross-shard query")
 			}
-			if rb.eroute.Opcode != engine.SelectNext {
-				return nil, errors.New("NEXT used on a non-sequence table")
+			for _, ro := range rb.routeOptions {
+				if ro.eroute.Opcode != engine.SelectNext {
+					return nil, errors.New("NEXT used on a non-sequence table")
+				}
+				ro.eroute.Opcode = engine.SelectNext
 			}
-			rb.eroute.Opcode = engine.SelectNext
 			resultColumns = append(resultColumns, rb.PushAnonymous(node))
 		default:
 			return nil, fmt.Errorf("BUG: unexpected select expression type: %T", node)
diff --git a/go/vt/vtgate/planbuilder/symtab.go b/go/vt/vtgate/planbuilder/symtab.go
index ced54a6dc1..654245c318 100644
--- a/go/vt/vtgate/planbuilder/symtab.go
+++ b/go/vt/vtgate/planbuilder/symtab.go
@@ -84,63 +84,79 @@ func newSymtabWithRoute(rb *route) *symtab {
 	}
 }
 
-// AddVSchemaTable adds a vschema table to symtab.
-func (st *symtab) AddVSchemaTable(alias sqlparser.TableName, vschemaTable *vindexes.Table, rb *route) error {
+// AddVSchemaTable takes a list of vschema tables as input and
+// creates a table with multiple route options. It returns a
+// list of vindex maps, one for each input.
+func (st *symtab) AddVSchemaTable(alias sqlparser.TableName, vschemaTables []*vindexes.Table, rb *route) (vindexMaps []map[*column]vindexes.SingleColumn, err error) {
 	t := &table{
-		alias:        alias,
-		origin:       rb,
-		vschemaTable: vschemaTable,
+		alias:  alias,
+		origin: rb,
 	}
 
-	for _, col := range vschemaTable.Columns {
-		if _, err := t.mergeColumn(col.Name, &column{
-			origin: rb,
-			st:     st,
-			typ:    col.Type,
-		}); err != nil {
-			return err
+	vindexMaps = make([]map[*column]vindexes.SingleColumn, len(vschemaTables))
+	for i, vst := range vschemaTables {
+		// The following logic allows the first table to be authoritative while the rest
+		// are not. But there's no need to reveal this flexibility to the user.
+		if i != 0 && vst.ColumnListAuthoritative && !t.isAuthoritative {
+			return nil, fmt.Errorf("intermixing of authoritative and non-authoritative tables not allowed: %v", vst.Name)
 		}
-	}
-	if vschemaTable.ColumnListAuthoritative {
-		// This will prevent new columns from being added.
-		t.isAuthoritative = true
-	}
 
-	for _, cv := range vschemaTable.ColumnVindexes {
-		single, ok := cv.Vindex.(vindexes.SingleColumn)
-		if !ok {
-			continue
-		}
-		for i, cvcol := range cv.Columns {
-			col, err := t.mergeColumn(cvcol, &column{
+		for _, col := range vst.Columns {
+			if _, err := t.mergeColumn(col.Name, &column{
 				origin: rb,
 				st:     st,
-			})
-			if err != nil {
-				return err
+				typ:    col.Type,
+			}); err != nil {
+				return nil, err
 			}
-			if i == 0 {
-				if col.vindex == nil || col.vindex.Cost() > single.Cost() {
-					col.vindex = single
+		}
+		if i == 0 && vst.ColumnListAuthoritative {
+			// This will prevent new columns from being added.
+			t.isAuthoritative = true
+		}
+
+		var vindexMap map[*column]vindexes.SingleColumn
+		for _, cv := range vst.ColumnVindexes {
+			single, ok := cv.Vindex.(vindexes.SingleColumn)
+			if !ok {
+				continue
+			}
+			for j, cvcol := range cv.Columns {
+				col, err := t.mergeColumn(cvcol, &column{
+					origin: rb,
+					st:     st,
+				})
+				if err != nil {
+					return nil, err
+				}
+				if j == 0 {
+					// For now, only the first column is used for vindex Map functions.
+					if vindexMap == nil {
+						vindexMap = make(map[*column]vindexes.SingleColumn)
+					}
+					if vindexMap[col] == nil || vindexMap[col].Cost() > single.Cost() {
+						vindexMap[col] = single
+					}
 				}
 			}
 		}
-	}
-
-	if ai := vschemaTable.AutoIncrement; ai != nil {
-		if _, ok := t.columns[ai.Column.Lowered()]; !ok {
-			if _, err := t.mergeColumn(ai.Column, &column{
-				origin: rb,
-				st:     st,
-			}); err != nil {
-				return err
+		vindexMaps[i] = vindexMap
+
+		if ai := vst.AutoIncrement; ai != nil {
+			if _, ok := t.columns[ai.Column.Lowered()]; !ok {
+				if _, err := t.mergeColumn(ai.Column, &column{
+					origin: rb,
+					st:     st,
+				}); err != nil {
+					return nil, err
+				}
 			}
 		}
 	}
 	if err := st.AddTable(t); err != nil {
-		return err
+		return nil, err
 	}
-	return nil
+	return vindexMaps, nil
 }
 
 // Merge merges the new symtab into the current one.
@@ -390,26 +406,6 @@ func ResultFromNumber(rcs []*resultColumn, val *sqlparser.SQLVal) (int, error) {
 	return int(num - 1), nil
 }
 
-// Vindex returns the vindex if the expression is a plain column reference
-// that is part of the specified route, and has an associated vindex.
-func (st *symtab) Vindex(expr sqlparser.Expr, scope *route) vindexes.SingleColumn {
-	col, ok := expr.(*sqlparser.ColName)
-	if !ok {
-		return nil
-	}
-	if col.Metadata == nil {
-		// Find will set the Metadata.
-		if _, _, err := st.Find(col); err != nil {
-			return nil
-		}
-	}
-	c := col.Metadata.(*column)
-	if c.Origin() != scope {
-		return nil
-	}
-	return c.vindex
-}
-
 // BuildColName builds a *sqlparser.ColName for the resultColumn specified
 // by the index. The built ColName will correctly reference the resultColumn
 // it was built from.
@@ -459,7 +455,6 @@ type table struct {
 	columnNames     []sqlparser.ColIdent
 	isAuthoritative bool
 	origin          builder
-	vschemaTable    *vindexes.Table
 }
 
 func (t *table) addColumn(alias sqlparser.ColIdent, c *column) {
@@ -508,8 +503,6 @@ func (t *table) Origin() builder {
 // column represents a unique symbol in the query that other
 // parts can refer to.
 // Every column contains the builder it originates from.
-// If a column has associated vindexes, then the one with the
-// lowest cost is set.
 //
 // Two columns are equal if their pointer values match.
 //
@@ -518,7 +511,6 @@ func (t *table) Origin() builder {
 type column struct {
 	origin    builder
 	st        *symtab
-	vindex    vindexes.SingleColumn
 	typ       querypb.Type
 	colNumber int
 }
diff --git a/go/vt/vtgate/plugin_mysql_server.go b/go/vt/vtgate/plugin_mysql_server.go
index b4b9d5726d..ded421e99b 100644
--- a/go/vt/vtgate/plugin_mysql_server.go
+++ b/go/vt/vtgate/plugin_mysql_server.go
@@ -214,23 +214,9 @@ func (vh *vtgateHandler) ComQuery(c *mysql.Conn, query string, callback func(*sq
 	if err != nil {
 		return err
 	}
-	fillInTxStatusFlags(c, session)
 	return callback(result)
 }
 
-func fillInTxStatusFlags(c *mysql.Conn, session *vtgatepb.Session) {
-	if session.InTransaction {
-		c.StatusFlags |= mysql.ServerStatusInTransaction
-	} else {
-		c.StatusFlags &= mysql.NoServerStatusInTransaction
-	}
-	if session.Autocommit {
-		c.StatusFlags |= mysql.ServerStatusAutocommit
-	} else {
-		c.StatusFlags &= mysql.NoServerStatusAutocommit
-	}
-}
-
 // ComPrepare is the handler for command prepare.
 func (vh *vtgateHandler) ComPrepare(c *mysql.Conn, query string, bindVars map[string]*querypb.BindVariable) ([]*querypb.Field, error) {
 	var ctx context.Context
@@ -317,7 +303,6 @@ func (vh *vtgateHandler) ComStmtExecute(c *mysql.Conn, prepare *mysql.PrepareDat
 		err = mysql.NewSQLErrorFromError(err)
 		return err
 	}
-	fillInTxStatusFlags(c, session)
 
 	return callback(qr)
 }
diff --git a/go/vt/vtgate/scatter_conn.go b/go/vt/vtgate/scatter_conn.go
index 5e7387b681..e39e587b11 100644
--- a/go/vt/vtgate/scatter_conn.go
+++ b/go/vt/vtgate/scatter_conn.go
@@ -24,7 +24,7 @@ import (
 
 	"vitess.io/vitess/go/mysql"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 
 	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
 
diff --git a/go/vt/vtgate/vcursor_impl.go b/go/vt/vtgate/vcursor_impl.go
index bed3290cb3..940385653d 100644
--- a/go/vt/vtgate/vcursor_impl.go
+++ b/go/vt/vtgate/vcursor_impl.go
@@ -218,8 +218,8 @@ func (vc *vcursorImpl) FindTable(name sqlparser.TableName) (*vindexes.Table, str
 	return table, destKeyspace, destTabletType, dest, err
 }
 
-// FindTableOrVindex finds the specified table or vindex.
-func (vc *vcursorImpl) FindTableOrVindex(name sqlparser.TableName) (*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) {
+// FindTablesOrVindex finds the specified table or vindex.
+func (vc *vcursorImpl) FindTablesOrVindex(name sqlparser.TableName) ([]*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) {
 	destKeyspace, destTabletType, dest, err := vc.executor.ParseDestinationTarget(name.Qualifier.String())
 	if err != nil {
 		return nil, nil, "", destTabletType, nil, err
@@ -227,11 +227,11 @@ func (vc *vcursorImpl) FindTableOrVindex(name sqlparser.TableName) (*vindexes.Ta
 	if destKeyspace == "" {
 		destKeyspace = vc.keyspace
 	}
-	table, vindex, err := vc.vschema.FindTableOrVindex(destKeyspace, name.Name.String(), vc.tabletType)
+	tables, vindex, err := vc.vschema.FindTablesOrVindex(destKeyspace, name.Name.String(), vc.tabletType)
 	if err != nil {
 		return nil, nil, "", destTabletType, nil, err
 	}
-	return table, vindex, destKeyspace, destTabletType, dest, nil
+	return tables, vindex, destKeyspace, destTabletType, dest, nil
 }
 
 // DefaultKeyspace returns the default keyspace of the current request
diff --git a/go/vt/vtgate/vindexes/vschema.go b/go/vt/vtgate/vindexes/vschema.go
index 15828285bd..517b7dfe2e 100644
--- a/go/vt/vtgate/vindexes/vschema.go
+++ b/go/vt/vtgate/vindexes/vschema.go
@@ -397,12 +397,6 @@ func buildRoutingRule(source *vschemapb.SrvVSchema, vschema *VSchema) {
 outer:
 	for _, rule := range source.RoutingRules.Rules {
 		rr := &RoutingRule{}
-		if len(rule.ToTables) > 1 {
-			vschema.RoutingRules[rule.FromTable] = &RoutingRule{
-				Error: fmt.Errorf("table %v has more than one target: %v", rule.FromTable, rule.ToTables),
-			}
-			continue
-		}
 		for _, toTable := range rule.ToTables {
 			if _, ok := vschema.RoutingRules[rule.FromTable]; ok {
 				vschema.RoutingRules[rule.FromTable] = &RoutingRule{
@@ -424,6 +418,14 @@ outer:
 				}
 				continue outer
 			}
+			for _, existing := range rr.Tables {
+				if existing == t {
+					vschema.RoutingRules[rule.FromTable] = &RoutingRule{
+						Error: fmt.Errorf("table %s specified more than once", toTable),
+					}
+					continue outer
+				}
+			}
 			rr.Tables = append(rr.Tables, t)
 		}
 		vschema.RoutingRules[rule.FromTable] = rr
@@ -496,7 +498,7 @@ func (vschema *VSchema) findTable(keyspace, tablename string) (*Table, error) {
 	return table, nil
 }
 
-func (vschema *VSchema) findRoutedTable(keyspace, tablename string, tabletType topodatapb.TabletType) (*Table, error) {
+func (vschema *VSchema) findTables(keyspace, tablename string, tabletType topodatapb.TabletType) ([]*Table, error) {
 	qualified := tablename
 	if keyspace != "" {
 		qualified = keyspace + "." + tablename
@@ -513,15 +515,22 @@ func (vschema *VSchema) findRoutedTable(keyspace, tablename string, tabletType t
 			if len(rr.Tables) == 0 {
 				return nil, fmt.Errorf("table %s has been disabled", tablename)
 			}
-			return rr.Tables[0], nil
+			return rr.Tables, nil
 		}
 	}
-	return vschema.findTable(keyspace, tablename)
+	t, err := vschema.findTable(keyspace, tablename)
+	if err != nil {
+		return nil, err
+	}
+	if t == nil {
+		return nil, nil
+	}
+	return []*Table{t}, nil
 }
 
-// FindTableOrVindex finds a table or a Vindex by name using Find and FindVindex.
-func (vschema *VSchema) FindTableOrVindex(keyspace, name string, tabletType topodatapb.TabletType) (*Table, Vindex, error) {
-	tables, err := vschema.findRoutedTable(keyspace, name, tabletType)
+// FindTablesOrVindex finds a table or a Vindex by name using Find and FindVindex.
+func (vschema *VSchema) FindTablesOrVindex(keyspace, name string, tabletType topodatapb.TabletType) ([]*Table, Vindex, error) {
+	tables, err := vschema.findTables(keyspace, name, tabletType)
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/go/vt/vttablet/endtoend/framework/server.go b/go/vt/vttablet/endtoend/framework/server.go
index ba60cdcc46..32be799bb1 100644
--- a/go/vt/vttablet/endtoend/framework/server.go
+++ b/go/vt/vttablet/endtoend/framework/server.go
@@ -53,10 +53,10 @@ var (
 	TopoServer *topo.Server
 )
 
-// StartCustomServer starts the server and initializes
+// StartServer starts the server and initializes
 // all the global variables. This function should only be called
 // once at the beginning of the test.
-func StartCustomServer(connParams, connAppDebugParams mysql.ConnParams, dbName string, config *tabletenv.TabletConfig) error {
+func StartServer(connParams, connAppDebugParams mysql.ConnParams, dbName string) error {
 	// Setup a fake vtgate server.
 	protocol := "resolveTest"
 	*vtgateconn.VtgateProtocol = protocol
@@ -68,6 +68,14 @@ func StartCustomServer(connParams, connAppDebugParams mysql.ConnParams, dbName s
 
 	dbcfgs := dbconfigs.NewTestDBConfigs(connParams, connAppDebugParams, dbName)
 
+	config := tabletenv.NewDefaultConfig()
+	config.StrictTableACL = true
+	config.TwoPCEnable = true
+	config.TwoPCAbandonAge = 1
+	config.TwoPCCoordinatorAddress = "fake"
+	config.HotRowProtection.Mode = tabletenv.Enable
+	config.TrackSchemaVersions = true
+
 	Target = querypb.Target{
 		Keyspace:   "vttest",
 		Shard:      "0",
@@ -100,20 +108,6 @@ func StartCustomServer(connParams, connAppDebugParams mysql.ConnParams, dbName s
 	return nil
 }
 
-// StartServer starts the server and initializes
-// all the global variables. This function should only be called
-// once at the beginning of the test.
-func StartServer(connParams, connAppDebugParams mysql.ConnParams, dbName string) error {
-	config := tabletenv.NewDefaultConfig()
-	config.StrictTableACL = true
-	config.TwoPCEnable = true
-	config.TwoPCAbandonAge = 1
-	config.TwoPCCoordinatorAddress = "fake"
-	config.HotRowProtection.Mode = tabletenv.Enable
-	config.TrackSchemaVersions = true
-	return StartCustomServer(connParams, connAppDebugParams, dbName, config)
-}
-
 // StopServer must be called once all the tests are done.
 func StopServer() {
 	Server.StopService()
diff --git a/go/vt/vttablet/tabletserver/health_streamer.go b/go/vt/vttablet/tabletserver/health_streamer.go
index 85393c76ad..86200ea751 100644
--- a/go/vt/vttablet/tabletserver/health_streamer.go
+++ b/go/vt/vttablet/tabletserver/health_streamer.go
@@ -22,7 +22,7 @@ import (
 	"sync"
 	"time"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 	"golang.org/x/net/context"
 	"vitess.io/vitess/go/history"
 	querypb "vitess.io/vitess/go/vt/proto/query"
diff --git a/go/vt/vttablet/tabletserver/planbuilder/plan.go b/go/vt/vttablet/tabletserver/planbuilder/plan.go
index 8f1285a97d..694ec49ae0 100644
--- a/go/vt/vttablet/tabletserver/planbuilder/plan.go
+++ b/go/vt/vttablet/tabletserver/planbuilder/plan.go
@@ -180,7 +180,9 @@ func Build(statement sqlparser.Statement, tables map[string]*schema.Table, isRes
 		// DDLs and other statements below don't get fully parsed.
 		// We have to use the original query at the time of execution.
 		plan = &Plan{PlanID: PlanDDL}
-	case *sqlparser.Show, *sqlparser.OtherRead, *sqlparser.Explain:
+	case *sqlparser.Show:
+		plan, err = &Plan{PlanID: PlanOtherRead}, nil
+	case *sqlparser.OtherRead:
 		plan, err = &Plan{PlanID: PlanOtherRead}, nil
 	case *sqlparser.OtherAdmin:
 		plan, err = &Plan{PlanID: PlanOtherAdmin}, nil
diff --git a/go/vt/vttablet/tabletserver/schema/historian.go b/go/vt/vttablet/tabletserver/schema/historian.go
index 0ca5c4efa1..ad1186b8cd 100644
--- a/go/vt/vttablet/tabletserver/schema/historian.go
+++ b/go/vt/vttablet/tabletserver/schema/historian.go
@@ -22,7 +22,7 @@ import (
 	"sort"
 	"sync"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 	"vitess.io/vitess/go/mysql"
 	"vitess.io/vitess/go/sqltypes"
 	"vitess.io/vitess/go/vt/log"
diff --git a/go/vt/vttablet/tabletserver/schema/tracker.go b/go/vt/vttablet/tabletserver/schema/tracker.go
index 32fdd801e5..ba473d93a1 100644
--- a/go/vt/vttablet/tabletserver/schema/tracker.go
+++ b/go/vt/vttablet/tabletserver/schema/tracker.go
@@ -27,7 +27,7 @@ import (
 
 	"vitess.io/vitess/go/vt/sqlparser"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 	"vitess.io/vitess/go/sqltypes"
 	"vitess.io/vitess/go/vt/log"
 	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
diff --git a/go/vt/vttablet/tabletserver/stateful_connection.go b/go/vt/vttablet/tabletserver/stateful_connection.go
index 2fe980c544..17b353094a 100644
--- a/go/vt/vttablet/tabletserver/stateful_connection.go
+++ b/go/vt/vttablet/tabletserver/stateful_connection.go
@@ -116,25 +116,13 @@ func (sc *StatefulConnection) execWithRetry(ctx context.Context, query string, m
 // Unlock returns the connection to the pool. The connection remains active.
 // This method is idempotent and can be called multiple times
 func (sc *StatefulConnection) Unlock() {
-	// when in a transaction, we count from the time created, so each use of the connection does not update the time
-	updateTime := !sc.IsInTransaction()
-	sc.unlock(updateTime)
-}
-
-// UnlockUpdateTime returns the connection to the pool. The connection remains active.
-// This method is idempotent and can be called multiple times
-func (sc *StatefulConnection) UnlockUpdateTime() {
-	sc.unlock(true)
-}
-
-func (sc *StatefulConnection) unlock(updateTime bool) {
 	if sc.dbConn == nil {
 		return
 	}
 	if sc.dbConn.IsClosed() {
 		sc.Releasef("unlocked closed connection")
 	} else {
-		sc.pool.markAsNotInUse(sc.ConnID, updateTime)
+		sc.pool.markAsNotInUse(sc.ConnID)
 	}
 }
 
diff --git a/go/vt/vttablet/tabletserver/stateful_connection_pool.go b/go/vt/vttablet/tabletserver/stateful_connection_pool.go
index 3a8051589e..f4e7acc32c 100644
--- a/go/vt/vttablet/tabletserver/stateful_connection_pool.go
+++ b/go/vt/vttablet/tabletserver/stateful_connection_pool.go
@@ -185,8 +185,8 @@ func (sf *StatefulConnectionPool) unregister(id tx.ConnID, reason string) {
 }
 
 //markAsNotInUse marks the connection as not in use at the moment
-func (sf *StatefulConnectionPool) markAsNotInUse(id tx.ConnID, updateTime bool) {
-	sf.active.Put(id, updateTime)
+func (sf *StatefulConnectionPool) markAsNotInUse(id tx.ConnID) {
+	sf.active.Put(id)
 }
 
 // Capacity returns the pool capacity.
diff --git a/go/vt/vttablet/tabletserver/tabletenv/config.go b/go/vt/vttablet/tabletserver/tabletenv/config.go
index f68e8da3c0..1a25f8c7d0 100644
--- a/go/vt/vttablet/tabletserver/tabletenv/config.go
+++ b/go/vt/vttablet/tabletserver/tabletenv/config.go
@@ -149,8 +149,8 @@ func init() {
 	flag.BoolVar(&currentConfig.CacheResultFields, "enable-query-plan-field-caching", defaultConfig.CacheResultFields, "This option fetches & caches fields (columns) when storing query plans")
 
 	flag.DurationVar(&healthCheckInterval, "health_check_interval", 20*time.Second, "Interval between health checks")
-	flag.DurationVar(&degradedThreshold, "degraded_threshold", 30*time.Second, "replication lag after which a replica is considered degraded (only used in status UI)")
-	flag.DurationVar(&unhealthyThreshold, "unhealthy_threshold", 2*time.Hour, "replication lag  after which a replica is considered unhealthy")
+	flag.DurationVar(&degradedThreshold, "degraded_threshold", 30*time.Second, "replication lag after which a replica is considered degraded")
+	flag.DurationVar(&unhealthyThreshold, "unhealthy_threshold", 2*time.Hour, "replication lag after which a replica is considered unhealthy")
 	flag.DurationVar(&transitionGracePeriod, "serving_state_grace_period", 0, "how long to pause after broadcasting health to vtgate, before enforcing a new serving state")
 
 	flag.BoolVar(&enableReplicationReporter, "enable_replication_reporter", false, "Use polling to track replication lag.")
diff --git a/go/vt/vttablet/tabletserver/tx_engine.go b/go/vt/vttablet/tabletserver/tx_engine.go
index 3437356d22..88e9df05bd 100644
--- a/go/vt/vttablet/tabletserver/tx_engine.go
+++ b/go/vt/vttablet/tabletserver/tx_engine.go
@@ -247,7 +247,7 @@ func (te *TxEngine) Begin(ctx context.Context, preQueries []string, reservedID i
 	if err != nil {
 		return 0, "", err
 	}
-	defer conn.UnlockUpdateTime()
+	defer conn.Unlock()
 	return conn.ID(), beginSQL, err
 }
 
@@ -570,7 +570,7 @@ func (te *TxEngine) ReserveBegin(ctx context.Context, options *querypb.ExecuteOp
 	if err != nil {
 		return 0, vterrors.Wrap(err, "TxEngine.ReserveBegin")
 	}
-	defer conn.UnlockUpdateTime()
+	defer conn.Unlock()
 	_, err = te.txPool.begin(ctx, options, te.state == AcceptingReadOnly, conn, nil)
 	if err != nil {
 		conn.Close()
diff --git a/go/vt/vttablet/tabletserver/tx_pool.go b/go/vt/vttablet/tabletserver/tx_pool.go
index ecfbde88ce..f959829752 100644
--- a/go/vt/vttablet/tabletserver/tx_pool.go
+++ b/go/vt/vttablet/tabletserver/tx_pool.go
@@ -125,21 +125,13 @@ func (tp *TxPool) transactionKiller() {
 	defer tp.env.LogError()
 	for _, conn := range tp.scp.GetOutdated(tp.Timeout(), "for tx killer rollback") {
 		log.Warningf("killing transaction (exceeded timeout: %v): %s", tp.Timeout(), conn.String())
-		switch {
-		case conn.IsTainted():
-			conn.Close()
+		if conn.IsTainted() {
 			tp.env.Stats().KillCounters.Add("ReservedConnection", 1)
-		case conn.IsInTransaction():
-			_, err := conn.Exec(context.Background(), "rollback", 1, false)
-			if err != nil {
-				conn.Close()
-			}
-			tp.env.Stats().KillCounters.Add("Transactions", 1)
 		}
-		// For logging, as transaction is killed as the connection is closed.
-		if conn.IsTainted() && conn.IsInTransaction() {
+		if conn.IsInTransaction() {
 			tp.env.Stats().KillCounters.Add("Transactions", 1)
 		}
+		conn.Close()
 		conn.Releasef("exceeded timeout: %v", tp.Timeout())
 	}
 }
diff --git a/go/vt/wrangler/materializer.go b/go/vt/wrangler/materializer.go
index 57d12feaf3..67f4476adb 100644
--- a/go/vt/wrangler/materializer.go
+++ b/go/vt/wrangler/materializer.go
@@ -26,7 +26,7 @@ import (
 	"vitess.io/vitess/go/vt/mysqlctl/tmutils"
 	"vitess.io/vitess/go/vt/vtgate/evalengine"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 	"golang.org/x/net/context"
 
 	"vitess.io/vitess/go/json2"
diff --git a/go/vt/wrangler/traffic_switcher.go b/go/vt/wrangler/traffic_switcher.go
index 14ef269932..48885cc384 100644
--- a/go/vt/wrangler/traffic_switcher.go
+++ b/go/vt/wrangler/traffic_switcher.go
@@ -105,9 +105,6 @@ type trafficSwitcher struct {
 	targetKeyspace  string
 	tables          []string
 	sourceKSSchema  *vindexes.KeyspaceSchema
-	optCells        string //cells option passed to MoveTables/Reshard
-	optTabletTypes  string //tabletTypes option passed to MoveTables/Reshard
-
 }
 
 // tsTarget contains the metadata for each migration target.
@@ -374,7 +371,7 @@ func (wr *Wrangler) DropSources(ctx context.Context, targetKeyspace, workflow st
 }
 
 func (wr *Wrangler) buildTrafficSwitcher(ctx context.Context, targetKeyspace, workflow string) (*trafficSwitcher, error) {
-	targets, frozen, optCells, optTabletTypes, err := wr.buildTargets(ctx, targetKeyspace, workflow)
+	targets, frozen, err := wr.buildTargets(ctx, targetKeyspace, workflow)
 	if err != nil {
 		return nil, err
 	}
@@ -388,8 +385,6 @@ func (wr *Wrangler) buildTrafficSwitcher(ctx context.Context, targetKeyspace, wo
 		sources:         make(map[string]*tsSource),
 		targetKeyspace:  targetKeyspace,
 		frozen:          frozen,
-		optCells:        optCells,
-		optTabletTypes:  optTabletTypes,
 	}
 	ts.wr.Logger().Infof("Migration ID for workflow %s: %d", workflow, ts.id)
 
@@ -460,11 +455,11 @@ func (wr *Wrangler) buildTrafficSwitcher(ctx context.Context, targetKeyspace, wo
 	return ts, nil
 }
 
-func (wr *Wrangler) buildTargets(ctx context.Context, targetKeyspace, workflow string) (targets map[string]*tsTarget, frozen bool, optCells string, optTabletTypes string, err error) {
+func (wr *Wrangler) buildTargets(ctx context.Context, targetKeyspace, workflow string) (targets map[string]*tsTarget, frozen bool, err error) {
 	targets = make(map[string]*tsTarget)
 	targetShards, err := wr.ts.GetShardNames(ctx, targetKeyspace)
 	if err != nil {
-		return nil, false, "", "", err
+		return nil, false, err
 	}
 	// We check all target shards. All of them may not have a stream.
 	// For example, if we're splitting -80 to -40,40-80, only those
@@ -472,19 +467,19 @@ func (wr *Wrangler) buildTargets(ctx context.Context, targetKeyspace, workflow s
 	for _, targetShard := range targetShards {
 		targetsi, err := wr.ts.GetShard(ctx, targetKeyspace, targetShard)
 		if err != nil {
-			return nil, false, "", "", err
+			return nil, false, err
 		}
 		if targetsi.MasterAlias == nil {
 			// This can happen if bad inputs are given.
-			return nil, false, "", "", fmt.Errorf("shard %v:%v doesn't have a master set", targetKeyspace, targetShard)
+			return nil, false, fmt.Errorf("shard %v:%v doesn't have a master set", targetKeyspace, targetShard)
 		}
 		targetMaster, err := wr.ts.GetTablet(ctx, targetsi.MasterAlias)
 		if err != nil {
-			return nil, false, "", "", err
+			return nil, false, err
 		}
-		p3qr, err := wr.tmc.VReplicationExec(ctx, targetMaster.Tablet, fmt.Sprintf("select id, source, message, cell, tablet_types from _vt.vreplication where workflow=%s and db_name=%s", encodeString(workflow), encodeString(targetMaster.DbName())))
+		p3qr, err := wr.tmc.VReplicationExec(ctx, targetMaster.Tablet, fmt.Sprintf("select id, source, message from _vt.vreplication where workflow=%s and db_name=%s", encodeString(workflow), encodeString(targetMaster.DbName())))
 		if err != nil {
-			return nil, false, "", "", err
+			return nil, false, err
 		}
 		// If there's no vreplication stream, check the next target.
 		if len(p3qr.Rows) < 1 {
@@ -500,26 +495,24 @@ func (wr *Wrangler) buildTargets(ctx context.Context, targetKeyspace, workflow s
 		for _, row := range qr.Rows {
 			id, err := evalengine.ToInt64(row[0])
 			if err != nil {
-				return nil, false, "", "", err
+				return nil, false, err
 			}
 
 			var bls binlogdatapb.BinlogSource
 			if err := proto.UnmarshalText(row[1].ToString(), &bls); err != nil {
-				return nil, false, "", "", err
+				return nil, false, err
 			}
 			targets[targetShard].sources[uint32(id)] = &bls
 
 			if row[2].ToString() == frozenStr {
 				frozen = true
 			}
-			optCells = row[3].ToString()
-			optTabletTypes = row[4].ToString()
 		}
 	}
 	if len(targets) == 0 {
-		return nil, false, "", "", fmt.Errorf("no streams found in keyspace %s for: %s", targetKeyspace, workflow)
+		return nil, false, fmt.Errorf("no streams found in keyspace %s for: %s", targetKeyspace, workflow)
 	}
-	return targets, frozen, optCells, optTabletTypes, nil
+	return targets, frozen, nil
 }
 
 // hashStreams produces a reproducible hash based on the input parameters.
@@ -855,37 +848,11 @@ func (ts *trafficSwitcher) createReverseVReplication(ctx context.Context) error
 		}
 
 		_, err := ts.wr.VReplicationExec(ctx, source.master.Alias, binlogplayer.CreateVReplicationState(ts.reverseWorkflow, reverseBls, target.position, binlogplayer.BlpStopped, source.master.DbName()))
-		if err != nil {
-			return err
-		}
-
-		// if user has defined the cell/tablet_types parameters in the forward workflow, update the reverse workflow as well
-		updateQuery := ts.getReverseVReplicationUpdateQuery(target.master.Alias.Cell, source.master.Alias.Cell, source.master.DbName())
-		if updateQuery != "" {
-			_, err = ts.wr.VReplicationExec(ctx, source.master.Alias, updateQuery)
-			return err
-		}
-		return nil
+		return err
 	})
 	return err
 }
 
-func (ts *trafficSwitcher) getReverseVReplicationUpdateQuery(targetCell string, sourceCell string, dbname string) string {
-	// we try to be clever to understand what user intends:
-	// if target's cell is present in cells but not source's cell we replace it with the source's cell
-	if ts.optCells != "" && targetCell != sourceCell && strings.Contains(ts.optCells+",", targetCell+",") &&
-		!strings.Contains(ts.optCells+",", sourceCell+",") {
-		ts.optCells = strings.Replace(ts.optCells, targetCell, sourceCell, 1)
-	}
-
-	if ts.optCells != "" || ts.optTabletTypes != "" {
-		query := fmt.Sprintf("update _vt.vreplication set cell = '%s', tablet_types = '%s' where workflow = '%s' and db_name = '%s'",
-			ts.optCells, ts.optTabletTypes, ts.reverseWorkflow, dbname)
-		return query
-	}
-	return ""
-}
-
 func (ts *trafficSwitcher) deleteReverseVReplication(ctx context.Context) error {
 	return ts.forAllSources(func(source *tsSource) error {
 		query := fmt.Sprintf("delete from _vt.vreplication where db_name=%s and workflow=%s", encodeString(source.master.DbName()), encodeString(ts.reverseWorkflow))
diff --git a/go/vt/wrangler/vexec.go b/go/vt/wrangler/vexec.go
index 0df718ac23..a2bbbdba91 100644
--- a/go/vt/wrangler/vexec.go
+++ b/go/vt/wrangler/vexec.go
@@ -28,7 +28,7 @@ import (
 
 	"vitess.io/vitess/go/vt/log"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 	"github.com/olekukonko/tablewriter"
 	"vitess.io/vitess/go/sqltypes"
 	"vitess.io/vitess/go/vt/concurrency"
@@ -135,7 +135,7 @@ func (vx *vexec) exec(query string) (map[*topo.TabletInfo]*querypb.QueryResult,
 				allErrors.RecordError(err)
 			} else {
 				if qr.RowsAffected == 0 {
-					log.Infof("no matching streams found for workflow %s, tablet %s, query %s", workflow, master.Alias, query)
+					allErrors.RecordError(fmt.Errorf("\nno matching streams found for workflow %s, tablet %s, query %s", workflow, master.Alias, query))
 				} else {
 					mu.Lock()
 					results[master] = qr
@@ -379,10 +379,6 @@ func (wr *Wrangler) listStreams(ctx context.Context, workflow, keyspace string)
 	if err != nil {
 		return err
 	}
-	if len(replStatus.Statuses) == 0 {
-		return fmt.Errorf("no streams found for workflow %s in keyspace %s", workflow, keyspace)
-	}
-
 	if err := dumpStreamListAsJSON(replStatus, wr); err != nil {
 		return err
 	}


Test patch:
diff --git a/go/pools/numbered_test.go b/go/pools/numbered_test.go
index 65ae5764cf..467e220fed 100644
--- a/go/pools/numbered_test.go
+++ b/go/pools/numbered_test.go
@@ -44,7 +44,7 @@ func TestNumbered(t *testing.T) {
 	if _, err = p.Get(id, "test1"); err.Error() != "in use: test" {
 		t.Errorf("want 'in use: test', got '%v'", err)
 	}
-	p.Put(id, true)
+	p.Put(id)
 	if _, err = p.Get(1, "test2"); err.Error() != "not found" {
 		t.Errorf("want 'not found', got '%v'", err)
 	}
@@ -75,9 +75,9 @@ func TestNumbered(t *testing.T) {
 		t.Errorf("want 'in use: by outdated', got '%v'", err)
 	}
 	for _, v := range vals {
-		p.Put(v.(int64), true)
+		p.Put(v.(int64))
 	}
-	p.Put(2, true) // put to 2 to ensure it's not idle
+	p.Put(2) // put to 2 to ensure it's not idle
 	time.Sleep(100 * time.Millisecond)
 
 	// p has 0, 1, 2 (2 is idle)
diff --git a/go/test/endtoend/vtgate/lookup_test.go b/go/test/endtoend/vtgate/lookup_test.go
index 29829410d3..90ac5e2a1f 100644
--- a/go/test/endtoend/vtgate/lookup_test.go
+++ b/go/test/endtoend/vtgate/lookup_test.go
@@ -164,186 +164,6 @@ func TestConsistentLookup(t *testing.T) {
 	exec(t, conn, "delete from t1 where id1=1")
 }
 
-func TestDMLScatter(t *testing.T) {
-	ctx := context.Background()
-	conn, err := mysql.Connect(ctx, &vtParams)
-	require.NoError(t, err)
-	defer conn.Close()
-
-	/* Simple insert. after this dml, the tables will contain the following:
-	t3 (id5, id6, id7):
-	1 2 3
-	2 2 3
-	3 4 3
-	4 5 4
-
-	t3_id7_idx (id7, keyspace_id:id6):
-	3 2
-	3 2
-	3 4
-	4 5
-	*/
-	exec(t, conn, "begin")
-	exec(t, conn, "insert into t3(id5, id6, id7) values(1, 2, 3), (2, 2, 3), (3, 4, 3), (4, 5, 4)")
-	exec(t, conn, "commit")
-	qr := exec(t, conn, "select id5, id6, id7 from t3 order by id5")
-	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(1) INT64(2) INT64(3)] [INT64(2) INT64(2) INT64(3)] [INT64(3) INT64(4) INT64(3)] [INT64(4) INT64(5) INT64(4)]]"; got != want {
-		t.Errorf("select:\n%v want\n%v", got, want)
-	}
-
-	/* Updating a non lookup column. after this dml, the tables will contain the following:
-	t3 (id5, id6, id7):
-	42 2 3
-	2 2 3
-	3 4 3
-	4 5 4
-
-	t3_id7_idx (id7, keyspace_id:id6):
-	3 2
-	3 2
-	3 4
-	4 5
-	*/
-	exec(t, conn, "update `ks[-]`.t3 set id5 = 42 where id5 = 1")
-	qr = exec(t, conn, "select id5, id6, id7 from t3 order by id5")
-	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(2) INT64(2) INT64(3)] [INT64(3) INT64(4) INT64(3)] [INT64(4) INT64(5) INT64(4)] [INT64(42) INT64(2) INT64(3)]]"; got != want {
-		t.Errorf("select:\n%v want\n%v", got, want)
-	}
-
-	/* Updating a lookup column. after this dml, the tables will contain the following:
-	t3 (id5, id6, id7):
-	42 2 42
-	2 2 42
-	3 4 3
-	4 5 4
-
-	t3_id7_idx (id7, keyspace_id:id6):
-	42 2
-	42 2
-	3 4
-	4 5
-	*/
-	exec(t, conn, "begin")
-	exec(t, conn, "update t3 set id7 = 42 where id6 = 2")
-	exec(t, conn, "commit")
-	qr = exec(t, conn, "select id5, id6, id7 from t3 order by id5")
-	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(2) INT64(2) INT64(42)] [INT64(3) INT64(4) INT64(3)] [INT64(4) INT64(5) INT64(4)] [INT64(42) INT64(2) INT64(42)]]"; got != want {
-		t.Errorf("select:\n%v want\n%v", got, want)
-	}
-
-	/* delete one specific keyspace id. after this dml, the tables will contain the following:
-	t3 (id5, id6, id7):
-	3 4 3
-	4 5 4
-
-	t3_id7_idx (id7, keyspace_id:id6):
-	3 4
-	4 5
-	*/
-	exec(t, conn, "delete from t3 where id6 = 2")
-	qr = exec(t, conn, "select * from t3 where id6 = 2")
-	require.Empty(t, qr.Rows)
-	qr = exec(t, conn, "select * from t3_id7_idx where id6 = 2")
-	require.Empty(t, qr.Rows)
-
-	// delete all the rows.
-	exec(t, conn, "delete from `ks[-]`.t3")
-	qr = exec(t, conn, "select * from t3")
-	require.Empty(t, qr.Rows)
-	qr = exec(t, conn, "select * from t3_id7_idx")
-	require.Empty(t, qr.Rows)
-}
-
-func TestDMLIn(t *testing.T) {
-	ctx := context.Background()
-	conn, err := mysql.Connect(ctx, &vtParams)
-	require.NoError(t, err)
-	defer conn.Close()
-
-	/* Simple insert. after this dml, the tables will contain the following:
-	t3 (id5, id6, id7):
-	1 2 3
-	2 2 3
-	3 4 3
-	4 5 4
-
-	t3_id7_idx (id7, keyspace_id:id6):
-	3 2
-	3 2
-	3 4
-	4 5
-	*/
-	exec(t, conn, "begin")
-	exec(t, conn, "insert into t3(id5, id6, id7) values(1, 2, 3), (2, 2, 3), (3, 4, 3), (4, 5, 4)")
-	exec(t, conn, "commit")
-	qr := exec(t, conn, "select id5, id6, id7 from t3 order by id5, id6")
-	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(1) INT64(2) INT64(3)] [INT64(2) INT64(2) INT64(3)] [INT64(3) INT64(4) INT64(3)] [INT64(4) INT64(5) INT64(4)]]"; got != want {
-		t.Errorf("select:\n%v want\n%v", got, want)
-	}
-
-	/* Updating a non lookup column. after this dml, the tables will contain the following:
-	t3 (id5, id6, id7):
-	1 2 3
-	2 2 3
-	42 4 3
-	42 5 4
-
-	t3_id7_idx (id7, keyspace_id:id6):
-	3 2
-	3 2
-	3 4
-	4 5
-	*/
-	exec(t, conn, "update t3 set id5 = 42 where id6 in (4, 5)")
-	qr = exec(t, conn, "select id5, id6, id7 from t3 order by id5, id6")
-	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(1) INT64(2) INT64(3)] [INT64(2) INT64(2) INT64(3)] [INT64(42) INT64(4) INT64(3)] [INT64(42) INT64(5) INT64(4)]]"; got != want {
-		t.Errorf("select:\n%v want\n%v", got, want)
-	}
-
-	/* Updating a non lookup column. after this dml, the tables will contain the following:
-	t3 (id5, id6, id7):
-	1 2 42
-	2 2 42
-	42 4 3
-	42 5 4
-
-	t3_id7_idx (id7, keyspace_id:id6):
-	42 2
-	42 2
-	3 4
-	42 5
-	*/
-	exec(t, conn, "begin")
-	exec(t, conn, "update t3 set id7 = 42 where id6 in (2, 5)")
-	exec(t, conn, "commit")
-	qr = exec(t, conn, "select id5, id6, id7 from t3 order by id5, id6")
-	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(1) INT64(2) INT64(42)] [INT64(2) INT64(2) INT64(42)] [INT64(42) INT64(4) INT64(3)] [INT64(42) INT64(5) INT64(42)]]"; got != want {
-		t.Errorf("select:\n%v want\n%v", got, want)
-	}
-
-	/* Updating a non lookup column. after this dml, the tables will contain the following:
-	t3 (id5, id6, id7):
-	42 4 3
-	42 5 4
-
-	t3_id7_idx (id7, keyspace_id:id6):
-	3 4
-	42 5
-	*/
-	exec(t, conn, "delete from t3 where id6 in (2)")
-	qr = exec(t, conn, "select * from t3 where id6 = 2")
-	require.Empty(t, qr.Rows)
-	qr = exec(t, conn, "select * from t3_id7_idx where id6 = 2")
-	require.Empty(t, qr.Rows)
-
-	// delete all the rows.
-	exec(t, conn, "delete from t3 where id6 in (4, 5)")
-	qr = exec(t, conn, "select * from t3")
-	require.Empty(t, qr.Rows)
-	qr = exec(t, conn, "select * from t3_id7_idx")
-	require.Empty(t, qr.Rows)
-}
-
 func TestConsistentLookupMultiInsert(t *testing.T) {
 	defer cluster.PanicHandler(t)
 	ctx := context.Background()
diff --git a/go/test/endtoend/vtgate/misc_test.go b/go/test/endtoend/vtgate/misc_test.go
index ec48411b6b..aec30cd7b6 100644
--- a/go/test/endtoend/vtgate/misc_test.go
+++ b/go/test/endtoend/vtgate/misc_test.go
@@ -34,6 +34,186 @@ import (
 	"vitess.io/vitess/go/test/endtoend/cluster"
 )
 
+func TestDMLScatter(t *testing.T) {
+	ctx := context.Background()
+	conn, err := mysql.Connect(ctx, &vtParams)
+	require.NoError(t, err)
+	defer conn.Close()
+
+	/* Simple insert. after this dml, the tables will contain the following:
+	t3 (id5, id6, id7):
+	1 2 3
+	2 2 3
+	3 4 3
+	4 5 4
+
+	t3_id7_idx (id7, keyspace_id:id6):
+	3 2
+	3 2
+	3 4
+	4 5
+	*/
+	exec(t, conn, "begin")
+	exec(t, conn, "insert into t3(id5, id6, id7) values(1, 2, 3), (2, 2, 3), (3, 4, 3), (4, 5, 4)")
+	exec(t, conn, "commit")
+	qr := exec(t, conn, "select id5, id6, id7 from t3 order by id5")
+	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(1) INT64(2) INT64(3)] [INT64(2) INT64(2) INT64(3)] [INT64(3) INT64(4) INT64(3)] [INT64(4) INT64(5) INT64(4)]]"; got != want {
+		t.Errorf("select:\n%v want\n%v", got, want)
+	}
+
+	/* Updating a non lookup column. after this dml, the tables will contain the following:
+	t3 (id5, id6, id7):
+	42 2 3
+	2 2 3
+	3 4 3
+	4 5 4
+
+	t3_id7_idx (id7, keyspace_id:id6):
+	3 2
+	3 2
+	3 4
+	4 5
+	*/
+	exec(t, conn, "update t3 set id5 = 42 where id5 = 1")
+	qr = exec(t, conn, "select id5, id6, id7 from t3 order by id5")
+	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(2) INT64(2) INT64(3)] [INT64(3) INT64(4) INT64(3)] [INT64(4) INT64(5) INT64(4)] [INT64(42) INT64(2) INT64(3)]]"; got != want {
+		t.Errorf("select:\n%v want\n%v", got, want)
+	}
+
+	/* Updating a lookup column. after this dml, the tables will contain the following:
+	t3 (id5, id6, id7):
+	42 2 42
+	2 2 42
+	3 4 3
+	4 5 4
+
+	t3_id7_idx (id7, keyspace_id:id6):
+	42 2
+	42 2
+	3 4
+	4 5
+	*/
+	exec(t, conn, "begin")
+	exec(t, conn, "update t3 set id7 = 42 where id6 = 2")
+	exec(t, conn, "commit")
+	qr = exec(t, conn, "select id5, id6, id7 from t3 order by id5")
+	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(2) INT64(2) INT64(42)] [INT64(3) INT64(4) INT64(3)] [INT64(4) INT64(5) INT64(4)] [INT64(42) INT64(2) INT64(42)]]"; got != want {
+		t.Errorf("select:\n%v want\n%v", got, want)
+	}
+
+	/* delete one specific keyspace id. after this dml, the tables will contain the following:
+	t3 (id5, id6, id7):
+	3 4 3
+	4 5 4
+
+	t3_id7_idx (id7, keyspace_id:id6):
+	3 4
+	4 5
+	*/
+	exec(t, conn, "delete from t3 where id6 = 2")
+	qr = exec(t, conn, "select * from t3 where id6 = 2")
+	require.Empty(t, qr.Rows)
+	qr = exec(t, conn, "select * from t3_id7_idx where id6 = 2")
+	require.Empty(t, qr.Rows)
+
+	// delete all the rows.
+	exec(t, conn, "delete from t3")
+	qr = exec(t, conn, "select * from t3")
+	require.Empty(t, qr.Rows)
+	qr = exec(t, conn, "select * from t3_id7_idx")
+	require.Empty(t, qr.Rows)
+}
+
+func TestDMLIn(t *testing.T) {
+	ctx := context.Background()
+	conn, err := mysql.Connect(ctx, &vtParams)
+	require.NoError(t, err)
+	defer conn.Close()
+
+	/* Simple insert. after this dml, the tables will contain the following:
+	t3 (id5, id6, id7):
+	1 2 3
+	2 2 3
+	3 4 3
+	4 5 4
+
+	t3_id7_idx (id7, keyspace_id:id6):
+	3 2
+	3 2
+	3 4
+	4 5
+	*/
+	exec(t, conn, "begin")
+	exec(t, conn, "insert into t3(id5, id6, id7) values(1, 2, 3), (2, 2, 3), (3, 4, 3), (4, 5, 4)")
+	exec(t, conn, "commit")
+	qr := exec(t, conn, "select id5, id6, id7 from t3 order by id5, id6")
+	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(1) INT64(2) INT64(3)] [INT64(2) INT64(2) INT64(3)] [INT64(3) INT64(4) INT64(3)] [INT64(4) INT64(5) INT64(4)]]"; got != want {
+		t.Errorf("select:\n%v want\n%v", got, want)
+	}
+
+	/* Updating a non lookup column. after this dml, the tables will contain the following:
+	t3 (id5, id6, id7):
+	1 2 3
+	2 2 3
+	42 4 3
+	42 5 4
+
+	t3_id7_idx (id7, keyspace_id:id6):
+	3 2
+	3 2
+	3 4
+	4 5
+	*/
+	exec(t, conn, "update t3 set id5 = 42 where id6 in (4, 5)")
+	qr = exec(t, conn, "select id5, id6, id7 from t3 order by id5, id6")
+	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(1) INT64(2) INT64(3)] [INT64(2) INT64(2) INT64(3)] [INT64(42) INT64(4) INT64(3)] [INT64(42) INT64(5) INT64(4)]]"; got != want {
+		t.Errorf("select:\n%v want\n%v", got, want)
+	}
+
+	/* Updating a non lookup column. after this dml, the tables will contain the following:
+	t3 (id5, id6, id7):
+	1 2 42
+	2 2 42
+	42 4 3
+	42 5 4
+
+	t3_id7_idx (id7, keyspace_id:id6):
+	42 2
+	42 2
+	3 4
+	42 5
+	*/
+	exec(t, conn, "begin")
+	exec(t, conn, "update t3 set id7 = 42 where id6 in (2, 5)")
+	exec(t, conn, "commit")
+	qr = exec(t, conn, "select id5, id6, id7 from t3 order by id5, id6")
+	if got, want := fmt.Sprintf("%v", qr.Rows), "[[INT64(1) INT64(2) INT64(42)] [INT64(2) INT64(2) INT64(42)] [INT64(42) INT64(4) INT64(3)] [INT64(42) INT64(5) INT64(42)]]"; got != want {
+		t.Errorf("select:\n%v want\n%v", got, want)
+	}
+
+	/* Updating a non lookup column. after this dml, the tables will contain the following:
+	t3 (id5, id6, id7):
+	42 4 3
+	42 5 4
+
+	t3_id7_idx (id7, keyspace_id:id6):
+	3 4
+	42 5
+	*/
+	exec(t, conn, "delete from t3 where id6 in (2)")
+	qr = exec(t, conn, "select * from t3 where id6 = 2")
+	require.Empty(t, qr.Rows)
+	qr = exec(t, conn, "select * from t3_id7_idx where id6 = 2")
+	require.Empty(t, qr.Rows)
+
+	// delete all the rows.
+	exec(t, conn, "delete from t3 where id6 in (4, 5)")
+	qr = exec(t, conn, "select * from t3")
+	require.Empty(t, qr.Rows)
+	qr = exec(t, conn, "select * from t3_id7_idx")
+	require.Empty(t, qr.Rows)
+}
+
 func TestSelectNull(t *testing.T) {
 	ctx := context.Background()
 	conn, err := mysql.Connect(ctx, &vtParams)
@@ -244,19 +424,6 @@ func TestSavepointAdditionalCase(t *testing.T) {
 	assertMatches(t, conn, "select id1 from t1 order by id1", `[]`)
 }
 
-func TestExplainPassthrough(t *testing.T) {
-	defer cluster.PanicHandler(t)
-	ctx := context.Background()
-	conn, err := mysql.Connect(ctx, &vtParams)
-	require.Nil(t, err)
-	defer conn.Close()
-
-	result := exec(t, conn, "explain select * from t1")
-	got := fmt.Sprintf("%v", result.Rows)
-	require.Contains(t, got, "SIMPLE") // there is a lot more coming from mysql,
-	// but we are trying to make the test less fragile
-}
-
 func assertMatches(t *testing.T, conn *mysql.Conn, query, expected string) {
 	t.Helper()
 	qr := exec(t, conn, query)
diff --git a/go/vt/vtctld/api_test.go b/go/vt/vtctld/api_test.go
index ebf2880bc1..0ff316ce18 100644
--- a/go/vt/vtctld/api_test.go
+++ b/go/vt/vtctld/api_test.go
@@ -75,28 +75,22 @@ func TestAPI(t *testing.T) {
 	ts.SaveVSchema(ctx, "ks1", vs)
 
 	tablet1 := topodatapb.Tablet{
-		Alias:         &topodatapb.TabletAlias{Cell: "cell1", Uid: 100},
-		Keyspace:      "ks1",
-		Shard:         "-80",
-		Type:          topodatapb.TabletType_REPLICA,
-		KeyRange:      &topodatapb.KeyRange{Start: nil, End: []byte{0x80}},
-		PortMap:       map[string]int32{"vt": 100},
-		Hostname:      "mysql1-cell1.test.net",
-		MysqlHostname: "mysql1-cell1.test.net",
-		MysqlPort:     int32(3306),
+		Alias:    &topodatapb.TabletAlias{Cell: "cell1", Uid: 100},
+		Keyspace: "ks1",
+		Shard:    "-80",
+		Type:     topodatapb.TabletType_REPLICA,
+		KeyRange: &topodatapb.KeyRange{Start: nil, End: []byte{0x80}},
+		PortMap:  map[string]int32{"vt": 100},
 	}
 	ts.CreateTablet(ctx, &tablet1)
 
 	tablet2 := topodatapb.Tablet{
-		Alias:         &topodatapb.TabletAlias{Cell: "cell2", Uid: 200},
-		Keyspace:      "ks1",
-		Shard:         "-80",
-		Type:          topodatapb.TabletType_REPLICA,
-		KeyRange:      &topodatapb.KeyRange{Start: nil, End: []byte{0x80}},
-		PortMap:       map[string]int32{"vt": 200},
-		Hostname:      "mysql2-cell2.test.net",
-		MysqlHostname: "mysql2-cell2.test.net",
-		MysqlPort:     int32(3306),
+		Alias:    &topodatapb.TabletAlias{Cell: "cell2", Uid: 200},
+		Keyspace: "ks1",
+		Shard:    "-80",
+		Type:     topodatapb.TabletType_REPLICA,
+		KeyRange: &topodatapb.KeyRange{Start: nil, End: []byte{0x80}},
+		PortMap:  map[string]int32{"vt": 200},
 	}
 	ts.CreateTablet(ctx, &tablet2)
 
@@ -132,55 +126,6 @@ func TestAPI(t *testing.T) {
 	realtimeStats.StatsUpdate(ts5)
 	realtimeStats.StatsUpdate(ts6)
 
-	// all-tablets response for keyspace/ks1/tablets/ endpoints
-	keyspaceKs1AllTablets := `[
-		{
-			"alias": {
-				"cell": "cell1",
-				"uid": 100
-			},
-			"hostname": "mysql1-cell1.test.net",
-			"port_map": {
-				"vt": 100
-			},
-			"keyspace": "ks1",
-			"shard": "-80",
-			"key_range": {
-				"end": "gA=="
-			},
-			"type": 2,
-			"mysql_hostname": "mysql1-cell1.test.net",
-			"mysql_port": 3306,
-			"stats": {
-				"realtime": {
-					"seconds_behind_master": 100
-				},
-				"serving": true,
-				"up": true
-			},
-			"url": "http://mysql1-cell1.test.net:100"
-		},
-		{
-			"alias": {
-				"cell": "cell2",
-				"uid": 200
-			},
-			"hostname": "mysql2-cell2.test.net",
-			"port_map": {
-				"vt": 200
-			},
-			"keyspace": "ks1",
-			"shard": "-80",
-			"key_range": {
-				"end": "gA=="
-			},
-			"type": 2,
-			"mysql_hostname": "mysql2-cell2.test.net",
-			"mysql_port": 3306,
-			"url": "http://mysql2-cell2.test.net:200"
-		}
-	]`
-
 	// Test cases.
 	table := []struct {
 		method, path, body, want string
@@ -194,64 +139,6 @@ func TestAPI(t *testing.T) {
 		// Cells
 		{"GET", "cells", "", `["cell1","cell2"]`},
 
-		// Keyspace
-		{"GET", "keyspace/doesnt-exist/tablets/", "", ``},
-		{"GET", "keyspace/ks1/tablets/", "", keyspaceKs1AllTablets},
-		{"GET", "keyspace/ks1/tablets/-80", "", keyspaceKs1AllTablets},
-		{"GET", "keyspace/ks1/tablets/80-", "", `[]`},
-		{"GET", "keyspace/ks1/tablets/?cells=cell1,cell2", "", keyspaceKs1AllTablets},
-		{"GET", "keyspace/ks1/tablets/?cells=cell1", "", `[
-			{
-				"alias": {
-					"cell": "cell1",
-					"uid": 100
-				},
-				"hostname": "mysql1-cell1.test.net",
-				"port_map": {
-					"vt": 100
-				},
-				"keyspace": "ks1",
-				"shard": "-80",
-				"key_range": {
-					"end": "gA=="
-				},
-				"type": 2,
-				"mysql_hostname": "mysql1-cell1.test.net",
-				"mysql_port": 3306,
-				"stats": {
-					"realtime": {
-						"seconds_behind_master": 100
-					},
-					"serving": true,
-					"up": true
-				},
-				"url": "http://mysql1-cell1.test.net:100"
-			}
-		]`},
-		{"GET", "keyspace/ks1/tablets/?cells=cell3", "", `[]`},
-		{"GET", "keyspace/ks1/tablets/?cell=cell2", "", `[
-			{
-				"alias": {
-					"cell": "cell2",
-					"uid": 200
-				},
-				"hostname": "mysql2-cell2.test.net",
-				"port_map": {
-					"vt": 200
-				},
-				"keyspace": "ks1",
-				"shard": "-80",
-				"key_range": {
-					"end": "gA=="
-				},
-				"type": 2,
-				"mysql_hostname": "mysql2-cell2.test.net",
-				"mysql_port": 3306,
-				"url": "http://mysql2-cell2.test.net:200"
-			}
-		]`},
-		{"GET", "keyspace/ks1/tablets/?cell=cell3", "", `[]`},
-
 		// Keyspaces
 		{"GET", "keyspaces", "", `["ks1", "ks3"]`},
 		{"GET", "keyspaces/ks1", "", `{
@@ -306,7 +193,6 @@ func TestAPI(t *testing.T) {
 		{"GET", "tablets/?shard=ks1%2F80-&cell=cell1", "", `[]`},
 		{"GET", "tablets/cell1-100", "", `{
 				"alias": {"cell": "cell1", "uid": 100},
-				"hostname": "mysql1-cell1.test.net",
 				"port_map": {"vt": 100},
 				"keyspace": "ks1",
 				"shard": "-80",
@@ -314,9 +200,7 @@ func TestAPI(t *testing.T) {
 					"end": "gA=="
 				},
 				"type": 2,
-				"mysql_hostname": "mysql1-cell1.test.net",
-				"mysql_port": 3306,
-				"url":"http://mysql1-cell1.test.net:100"
+				"url":"http://:100"
 			}`},
 		{"GET", "tablets/nonexistent-999", "", "404 page not found"},
 		{"POST", "tablets/cell1-100?action=TestTabletAction", "", `{
diff --git a/go/vt/vtgate/executor_select_test.go b/go/vt/vtgate/executor_select_test.go
index 0f3eb6046d..8addc7a59c 100644
--- a/go/vt/vtgate/executor_select_test.go
+++ b/go/vt/vtgate/executor_select_test.go
@@ -329,12 +329,17 @@ func testRowCount(t *testing.T, executor *Executor, wantRowCount int64) {
 }
 
 func TestSelectLastInsertIdInUnion(t *testing.T) {
-	executor, _, _, _ := createLegacyExecutorEnv()
+	executor, sbc1, _, _ := createLegacyExecutorEnv()
 	executor.normalize = true
 	sql := "select last_insert_id() as id union select id from user"
 	_, err := executorExec(executor, sql, map[string]*querypb.BindVariable{})
-	require.Error(t, err)
-	assert.Contains(t, err.Error(), "unsupported: UNION cannot be executed as a single route")
+	require.NoError(t, err)
+	wantQueries := []*querypb.BoundQuery{{
+		Sql:           "select :__lastInsertId as id from dual union select id from user",
+		BindVariables: map[string]*querypb.BindVariable{"__lastInsertId": sqltypes.Uint64BindVariable(0)},
+	}}
+
+	assert.Equal(t, wantQueries, sbc1.Queries)
 }
 
 func TestSelectLastInsertIdInWhere(t *testing.T) {
diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go
index 5ef028d556..b307f4c4d3 100644
--- a/go/vt/vtgate/planbuilder/plan_test.go
+++ b/go/vt/vtgate/planbuilder/plan_test.go
@@ -303,16 +303,16 @@ func (vw *vschemaWrapper) FindTable(tab sqlparser.TableName) (*vindexes.Table, s
 	return table, destKeyspace, destTabletType, destTarget, nil
 }
 
-func (vw *vschemaWrapper) FindTableOrVindex(tab sqlparser.TableName) (*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) {
+func (vw *vschemaWrapper) FindTablesOrVindex(tab sqlparser.TableName) ([]*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) {
 	destKeyspace, destTabletType, destTarget, err := topoproto.ParseDestination(tab.Qualifier.String(), topodatapb.TabletType_MASTER)
 	if err != nil {
 		return nil, nil, destKeyspace, destTabletType, destTarget, err
 	}
-	table, vindex, err := vw.v.FindTableOrVindex(destKeyspace, tab.Name.String(), topodatapb.TabletType_MASTER)
+	tables, vindex, err := vw.v.FindTablesOrVindex(destKeyspace, tab.Name.String(), topodatapb.TabletType_MASTER)
 	if err != nil {
 		return nil, nil, destKeyspace, destTabletType, destTarget, err
 	}
-	return table, vindex, destKeyspace, destTabletType, destTarget, nil
+	return tables, vindex, destKeyspace, destTabletType, destTarget, nil
 }
 
 func (vw *vschemaWrapper) DefaultKeyspace() (*vindexes.Keyspace, error) {
diff --git a/go/vt/vtgate/planbuilder/route_option_test.go b/go/vt/vtgate/planbuilder/route_option_test.go
new file mode 100644
index 0000000000..c39dfe48b0
--- /dev/null
+++ b/go/vt/vtgate/planbuilder/route_option_test.go
@@ -0,0 +1,187 @@
+/*
+Copyright 2019 The Vitess Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package planbuilder
+
+import (
+	"testing"
+
+	"vitess.io/vitess/go/vt/vtgate/engine"
+	"vitess.io/vitess/go/vt/vtgate/vindexes"
+)
+
+func TestIsBetterThan(t *testing.T) {
+	// All combinations aren't worth it.
+	testcases := []struct {
+		left, right         engine.RouteOpcode
+		leftcost, rightcost int
+		out                 bool
+	}{{
+		left:  engine.SelectUnsharded,
+		right: engine.SelectUnsharded,
+		out:   false,
+	}, {
+		left:  engine.SelectUnsharded,
+		right: engine.SelectNext,
+		out:   false,
+	}, {
+		left:  engine.SelectUnsharded,
+		right: engine.SelectDBA,
+		out:   false,
+	}, {
+		left:  engine.SelectUnsharded,
+		right: engine.SelectReference,
+		out:   false,
+	}, {
+		left:  engine.SelectUnsharded,
+		right: engine.SelectEqualUnique,
+		out:   true,
+	}, {
+		left:  engine.SelectUnsharded,
+		right: engine.SelectIN,
+		out:   true,
+	}, {
+		left:  engine.SelectUnsharded,
+		right: engine.SelectEqual,
+		out:   true,
+	}, {
+		left:  engine.SelectUnsharded,
+		right: engine.SelectScatter,
+		out:   true,
+	}, {
+		left:  engine.SelectNext,
+		right: engine.SelectUnsharded,
+		out:   false,
+	}, {
+		left:  engine.SelectDBA,
+		right: engine.SelectUnsharded,
+		out:   false,
+	}, {
+		left:  engine.SelectReference,
+		right: engine.SelectUnsharded,
+		out:   false,
+	}, {
+		left:  engine.SelectEqualUnique,
+		right: engine.SelectUnsharded,
+		out:   false,
+	}, {
+		left:      engine.SelectEqualUnique,
+		right:     engine.SelectEqualUnique,
+		leftcost:  2,
+		rightcost: 1,
+		out:       false,
+	}, {
+		left:      engine.SelectEqualUnique,
+		right:     engine.SelectEqualUnique,
+		leftcost:  1,
+		rightcost: 1,
+		out:       false,
+	}, {
+		left:      engine.SelectEqualUnique,
+		right:     engine.SelectEqualUnique,
+		leftcost:  1,
+		rightcost: 2,
+		out:       true,
+	}, {
+		left:  engine.SelectEqualUnique,
+		right: engine.SelectIN,
+		out:   true,
+	}, {
+		left:  engine.SelectEqualUnique,
+		right: engine.SelectIN,
+		out:   true,
+	}, {
+		left:      engine.SelectIN,
+		right:     engine.SelectIN,
+		leftcost:  2,
+		rightcost: 1,
+		out:       false,
+	}, {
+		left:      engine.SelectIN,
+		right:     engine.SelectIN,
+		leftcost:  1,
+		rightcost: 1,
+		out:       false,
+	}, {
+		left:      engine.SelectIN,
+		right:     engine.SelectIN,
+		leftcost:  1,
+		rightcost: 2,
+		out:       true,
+	}, {
+		left:  engine.SelectIN,
+		right: engine.SelectEqual,
+		out:   true,
+	}, {
+		left:  engine.SelectEqual,
+		right: engine.SelectUnsharded,
+		out:   false,
+	}, {
+		left:      engine.SelectEqual,
+		right:     engine.SelectEqual,
+		leftcost:  2,
+		rightcost: 1,
+		out:       false,
+	}, {
+		left:      engine.SelectEqual,
+		right:     engine.SelectEqual,
+		leftcost:  1,
+		rightcost: 1,
+		out:       false,
+	}, {
+		left:      engine.SelectEqual,
+		right:     engine.SelectEqual,
+		leftcost:  1,
+		rightcost: 2,
+		out:       true,
+	}, {
+		left:  engine.SelectEqual,
+		right: engine.SelectScatter,
+		out:   true,
+	}, {
+		left:  engine.SelectScatter,
+		right: engine.SelectUnsharded,
+		out:   false,
+	}, {
+		left:  engine.SelectScatter,
+		right: engine.SelectScatter,
+		out:   false,
+	}}
+	buildOption := func(opt engine.RouteOpcode, cost int) *routeOption {
+		var v vindexes.Vindex
+		switch cost {
+		case 1:
+			v, _ = newHashIndex("", nil)
+		case 2:
+			v, _ = newLookupIndex("", nil)
+		}
+		single, _ := v.(vindexes.SingleColumn)
+		return &routeOption{
+			eroute: &engine.Route{
+				Opcode: opt,
+				Vindex: single,
+			},
+		}
+	}
+	for _, tcase := range testcases {
+		left := buildOption(tcase.left, tcase.leftcost)
+		right := buildOption(tcase.right, tcase.rightcost)
+		got := left.isBetterThan(right)
+		if got != tcase.out {
+			t.Errorf("isBetterThan(%v, %v, %v, %v): %v, want %v", tcase.left, tcase.leftcost, tcase.right, tcase.rightcost, got, tcase.out)
+		}
+	}
+}
diff --git a/go/vt/vtgate/planbuilder/route_test.go b/go/vt/vtgate/planbuilder/route_test.go
deleted file mode 100644
index 0eb6abde3a..0000000000
--- a/go/vt/vtgate/planbuilder/route_test.go
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
-Copyright 2020 The Vitess Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package planbuilder
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"vitess.io/vitess/go/vt/sqlparser"
-	"vitess.io/vitess/go/vt/vtgate/engine"
-	"vitess.io/vitess/go/vt/vtgate/vindexes"
-)
-
-/*
-For easy reference, opcodes are:
-	SelectUnsharded   0
-	SelectEqualUnique 1
-	SelectEqual       2
-	SelectIN          3
-	SelectScatter     4
-	SelectNext        5
-	SelectDBA         6
-	SelectReference   7
-	SelectNone        8
-	NumRouteOpcodes   9
-*/
-
-func TestJoinCanMerge(t *testing.T) {
-	testcases := [engine.NumRouteOpcodes][engine.NumRouteOpcodes]bool{
-		{true, false, false, false, false, false, false, true, false},
-		{false, true, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, true, true, false},
-		{true, true, true, true, true, true, true, true, true},
-		{false, false, false, false, false, false, false, true, false},
-	}
-
-	ks := &vindexes.Keyspace{}
-	lRoute := &route{
-		// Setting condition will make SelectEqualUnique match itself.
-		condition: &sqlparser.ColName{},
-	}
-	pb := &primitiveBuilder{
-		bldr: lRoute,
-	}
-	rRoute := &route{
-		condition: &sqlparser.ColName{},
-	}
-	for left, vals := range testcases {
-		lRoute.eroute = engine.NewSimpleRoute(engine.RouteOpcode(left), ks)
-		for right, val := range vals {
-			rRoute.eroute = engine.NewSimpleRoute(engine.RouteOpcode(right), ks)
-			assert.Equal(t, val, lRoute.JoinCanMerge(pb, rRoute, nil), fmt.Sprintf("%v:%v", lRoute.eroute.RouteType(), rRoute.eroute.RouteType()))
-		}
-	}
-}
-
-func TestSubqueryCanMerge(t *testing.T) {
-	testcases := [engine.NumRouteOpcodes][engine.NumRouteOpcodes]bool{
-		{true, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, false, false},
-		{false, false, false, false, false, false, true, true, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, true, false},
-	}
-
-	ks := &vindexes.Keyspace{}
-	lRoute := &route{}
-	pb := &primitiveBuilder{
-		bldr: lRoute,
-	}
-	rRoute := &route{}
-	for left, vals := range testcases {
-		lRoute.eroute = engine.NewSimpleRoute(engine.RouteOpcode(left), ks)
-		for right, val := range vals {
-			rRoute.eroute = engine.NewSimpleRoute(engine.RouteOpcode(right), ks)
-			assert.Equal(t, val, lRoute.SubqueryCanMerge(pb, rRoute), fmt.Sprintf("%v:%v", lRoute.eroute.RouteType(), rRoute.eroute.RouteType()))
-		}
-	}
-}
-
-func TestUnionCanMerge(t *testing.T) {
-	testcases := [engine.NumRouteOpcodes][engine.NumRouteOpcodes]bool{
-		{true, false, false, false, false, false, false, false, false},
-		{false, false, false, false, false, false, false, false, false},
-		{false, false, false, false, false, false, false, false, false},
-		{false, false, false, false, false, false, false, false, false},
-		{false, false, false, false, false, false, false, false, false},
-		{false, false, false, false, false, false, false, false, false},
-		{false, false, false, false, false, false, true, false, false},
-		{false, false, false, false, false, false, false, true, false},
-		{false, false, false, false, false, false, false, false, false},
-	}
-
-	ks := &vindexes.Keyspace{}
-	lRoute := &route{}
-	rRoute := &route{}
-	for left, vals := range testcases {
-		lRoute.eroute = engine.NewSimpleRoute(engine.RouteOpcode(left), ks)
-		for right, val := range vals {
-			rRoute.eroute = engine.NewSimpleRoute(engine.RouteOpcode(right), ks)
-			assert.Equal(t, val, lRoute.UnionCanMerge(rRoute), fmt.Sprintf("%v:%v", lRoute.eroute.RouteType(), rRoute.eroute.RouteType()))
-		}
-	}
-}
diff --git a/go/vt/vtgate/planbuilder/symtab_test.go b/go/vt/vtgate/planbuilder/symtab_test.go
index bd9ac8a6a9..81e85e889b 100644
--- a/go/vt/vtgate/planbuilder/symtab_test.go
+++ b/go/vt/vtgate/planbuilder/symtab_test.go
@@ -16,7 +16,14 @@ limitations under the License.
 
 package planbuilder
 
-/*
+import (
+	"encoding/json"
+	"testing"
+
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vtgate/vindexes"
+)
+
 func TestSymtabAddVSchemaTable(t *testing.T) {
 	tname := sqlparser.TableName{Name: sqlparser.NewTableIdent("t")}
 	rb := &route{}
@@ -24,24 +31,24 @@ func TestSymtabAddVSchemaTable(t *testing.T) {
 	null, _ := vindexes.CreateVindex("null", "null", nil)
 
 	tcases := []struct {
-		in            *vindexes.Table
+		in            []*vindexes.Table
 		authoritative bool
-		vindex        []string
+		vindexes      [][]string
 		err           string
 	}{{
 		// Single table.
-		in: &vindexes.Table{
+		in: []*vindexes.Table{{
 			Columns: []vindexes.Column{{
 				Name: sqlparser.NewColIdent("C1"),
 			}, {
 				Name: sqlparser.NewColIdent("C2"),
 			}},
-		},
+		}},
 		authoritative: false,
-		vindex:        []string{},
+		vindexes:      [][]string{{}},
 	}, {
 		// Column vindex specified.
-		in: &vindexes.Table{
+		in: []*vindexes.Table{{
 			ColumnVindexes: []*vindexes.ColumnVindex{{
 				Columns: []sqlparser.ColIdent{sqlparser.NewColIdent("C1")},
 				Vindex:  null,
@@ -51,12 +58,12 @@ func TestSymtabAddVSchemaTable(t *testing.T) {
 			}, {
 				Name: sqlparser.NewColIdent("C2"),
 			}},
-		},
+		}},
 		authoritative: false,
-		vindex:        []string{"c1"},
+		vindexes:      [][]string{{"c1"}},
 	}, {
 		// Multi-column vindex.
-		in: &vindexes.Table{
+		in: []*vindexes.Table{{
 			ColumnVindexes: []*vindexes.ColumnVindex{{
 				Columns: []sqlparser.ColIdent{
 					sqlparser.NewColIdent("C1"),
@@ -69,12 +76,12 @@ func TestSymtabAddVSchemaTable(t *testing.T) {
 			}, {
 				Name: sqlparser.NewColIdent("C2"),
 			}},
-		},
+		}},
 		authoritative: false,
-		vindex:        []string{"c1"},
+		vindexes:      [][]string{{"c1"}},
 	}, {
 		// AutoIncrement.
-		in: &vindexes.Table{
+		in: []*vindexes.Table{{
 			AutoIncrement: &vindexes.AutoIncrement{
 				Column: sqlparser.NewColIdent("C1"),
 			},
@@ -83,12 +90,12 @@ func TestSymtabAddVSchemaTable(t *testing.T) {
 			}, {
 				Name: sqlparser.NewColIdent("C2"),
 			}},
-		},
+		}},
 		authoritative: false,
-		vindex:        []string{},
+		vindexes:      [][]string{{}},
 	}, {
 		// Column vindex specifies a column not in list.
-		in: &vindexes.Table{
+		in: []*vindexes.Table{{
 			ColumnVindexes: []*vindexes.ColumnVindex{{
 				Columns: []sqlparser.ColIdent{sqlparser.NewColIdent("C1")},
 				Vindex:  null,
@@ -96,12 +103,12 @@ func TestSymtabAddVSchemaTable(t *testing.T) {
 			Columns: []vindexes.Column{{
 				Name: sqlparser.NewColIdent("C2"),
 			}},
-		},
+		}},
 		authoritative: false,
-		vindex:        []string{"c1"},
+		vindexes:      [][]string{{"c1"}},
 	}, {
 		// Column vindex specifies columns with none in list.
-		in: &vindexes.Table{
+		in: []*vindexes.Table{{
 			ColumnVindexes: []*vindexes.ColumnVindex{{
 				Columns: []sqlparser.ColIdent{
 					sqlparser.NewColIdent("C1"),
@@ -109,24 +116,56 @@ func TestSymtabAddVSchemaTable(t *testing.T) {
 				},
 				Vindex: null,
 			}},
-		},
+		}},
 		authoritative: false,
-		vindex:        []string{"c1"},
+		vindexes:      [][]string{{"c1"}},
 	}, {
 		// AutoIncrement specifies a column not in list.
-		in: &vindexes.Table{
+		in: []*vindexes.Table{{
 			AutoIncrement: &vindexes.AutoIncrement{
 				Column: sqlparser.NewColIdent("C1"),
 			},
 			Columns: []vindexes.Column{{
 				Name: sqlparser.NewColIdent("C2"),
 			}},
-		},
+		}},
+		authoritative: false,
+		vindexes:      [][]string{{}},
+	}, {
+		// Two tables.
+		in: []*vindexes.Table{{
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C2"),
+			}},
+		}, {
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}},
+		}},
 		authoritative: false,
-		vindex:        []string{},
+		vindexes:      [][]string{{}, {}},
 	}, {
-		// Two column vindexes.
-		in: &vindexes.Table{
+		// Two tables, with column vindexes.
+		in: []*vindexes.Table{{
+			ColumnVindexes: []*vindexes.ColumnVindex{{
+				Columns: []sqlparser.ColIdent{
+					sqlparser.NewColIdent("C1"),
+				},
+				Vindex: null,
+			}},
+		}, {
+			ColumnVindexes: []*vindexes.ColumnVindex{{
+				Columns: []sqlparser.ColIdent{
+					sqlparser.NewColIdent("C2"),
+				},
+				Vindex: null,
+			}},
+		}},
+		authoritative: false,
+		vindexes:      [][]string{{"c1"}, {"c2"}},
+	}, {
+		// One table with two column vindexes.
+		in: []*vindexes.Table{{
 			ColumnVindexes: []*vindexes.ColumnVindex{{
 				Columns: []sqlparser.ColIdent{
 					sqlparser.NewColIdent("C1"),
@@ -138,15 +177,108 @@ func TestSymtabAddVSchemaTable(t *testing.T) {
 				},
 				Vindex: null,
 			}},
-		},
+		}},
 		authoritative: false,
-		vindex:        []string{"c1", "c2"},
+		vindexes:      [][]string{{"c1", "c2"}, {}},
+	}, {
+		// First table is authoritative.
+		in: []*vindexes.Table{{
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}, {
+				Name: sqlparser.NewColIdent("C2"),
+			}},
+			ColumnListAuthoritative: true,
+		}, {
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}},
+		}},
+		authoritative: true,
+		vindexes:      [][]string{{}, {}},
+	}, {
+		// Both tables are authoritative.
+		in: []*vindexes.Table{{
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}, {
+				Name: sqlparser.NewColIdent("C2"),
+			}},
+			ColumnListAuthoritative: true,
+		}, {
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}},
+			ColumnListAuthoritative: true,
+		}},
+		authoritative: true,
+		vindexes:      [][]string{{}, {}},
+	}, {
+		// Second table is authoritative.
+		in: []*vindexes.Table{{
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}, {
+				Name: sqlparser.NewColIdent("C2"),
+			}},
+		}, {
+			Name: sqlparser.NewTableIdent("t1"),
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}},
+			ColumnListAuthoritative: true,
+		}},
+		authoritative: true,
+		vindexes:      [][]string{{}, {}},
+		err:           "intermixing of authoritative and non-authoritative tables not allowed: t1",
+	}, {
+		// Cannot add to authoritative table (column list).
+		in: []*vindexes.Table{{
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}},
+			ColumnListAuthoritative: true,
+		}, {
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C2"),
+			}},
+		}},
+		err: "column C2 not found in t",
+	}, {
+		// Cannot add to authoritative table (column vindex).
+		in: []*vindexes.Table{{
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}},
+			ColumnListAuthoritative: true,
+		}, {
+			ColumnVindexes: []*vindexes.ColumnVindex{{
+				Columns: []sqlparser.ColIdent{
+					sqlparser.NewColIdent("C2"),
+				},
+				Vindex: null,
+			}},
+		}},
+		err: "column C2 not found in t",
+	}, {
+		// Cannot add to authoritative table (autoinc).
+		in: []*vindexes.Table{{
+			Columns: []vindexes.Column{{
+				Name: sqlparser.NewColIdent("C1"),
+			}},
+			ColumnListAuthoritative: true,
+		}, {
+			AutoIncrement: &vindexes.AutoIncrement{
+				Column: sqlparser.NewColIdent("C2"),
+			},
+		}},
+		err: "column C2 not found in t",
 	}}
 
 	out := []string{"c1", "c2"}
 	for _, tcase := range tcases {
 		st := newSymtab()
-		vindexMap, err := st.AddVSchemaTable(tname, tcase.in, rb)
+		vindexMaps, err := st.AddVSchemaTable(tname, tcase.in, rb)
 		tcasein, _ := json.Marshal(tcase.in)
 		if err != nil {
 			if err.Error() != tcase.err {
@@ -163,13 +295,15 @@ func TestSymtabAddVSchemaTable(t *testing.T) {
 				t.Errorf("st.AddVSchemaTable(%s): column %s not found", tcasein, col)
 			}
 		}
-		for _, col := range tcase.vindex {
-			c := tab.columns[col]
-			if c == nil {
-				t.Errorf("st.AddVSchemaTable(%s): column %s not found", tcasein, col)
-			}
-			if _, ok := vindexMap[c]; !ok {
-				t.Errorf("st.AddVSchemaTable(%s).vindexMap: column %s not found", tcasein, col)
+		for i, cols := range tcase.vindexes {
+			for _, col := range cols {
+				c := tab.columns[col]
+				if c == nil {
+					t.Errorf("st.AddVSchemaTable(%s): column %s not found", tcasein, col)
+				}
+				if _, ok := vindexMaps[i][c]; !ok {
+					t.Errorf("st.AddVSchemaTable(%s).vindexMap[%d]: column %s not found", tcasein, i, col)
+				}
 			}
 		}
 		if tab.isAuthoritative != tcase.authoritative {
@@ -177,4 +311,3 @@ func TestSymtabAddVSchemaTable(t *testing.T) {
 		}
 	}
 }
-*/
diff --git a/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt b/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt
index 1172ea4dcf..58414285a9 100644
--- a/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt
@@ -1143,7 +1143,7 @@
   }
 }
 
-# routing rules for aggregates
+# routing rules for aggregates where sharded and unsharded match. Unsharded always wins.
 "select id, count(*) from route2 group by id"
 {
   "QueryType": "SELECT",
@@ -1161,7 +1161,25 @@
   }
 }
 
-# order by on a reference table
+# routing rules for select distinct where sharded and unsharded match. Unsharded always wins.
+"select distinct id, a from route2"
+{
+  "QueryType": "SELECT",
+  "Original": "select distinct id, a from route2",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectUnsharded",
+    "Keyspace": {
+      "Name": "main",
+      "Sharded": false
+    },
+    "FieldQuery": "select id, a from unsharded as route2 where 1 != 1",
+    "Query": "select distinct id, a from unsharded as route2",
+    "Table": "unsharded"
+  }
+}
+
+# oreder by on a reference table
 "select col from ref order by col"
 {
   "QueryType": "SELECT",
diff --git a/go/vt/vtgate/planbuilder/testdata/dml_cases.txt b/go/vt/vtgate/planbuilder/testdata/dml_cases.txt
index 4531dc693c..d2b6d94659 100644
--- a/go/vt/vtgate/planbuilder/testdata/dml_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/dml_cases.txt
@@ -137,7 +137,7 @@
   }
 }
 
-# update: routing rules for subquery.
+# routing rules: subquery chooses unsharded route for update
 "update unsharded_a set a=(select a from route2)"
 {
   "QueryType": "UPDATE",
@@ -504,7 +504,7 @@
   }
 }
 
-# delete: routing rules for subquery
+# routing rules: subquery chooses unsharded route for delete
 "delete from  unsharded_a where a=(select a from route2)"
 {
   "QueryType": "DELETE",
@@ -2150,67 +2150,3 @@
     "Table": "user"
   }
 }
-
-# delete with shard targeting
-"delete from `user[-]`.user"
-{
-  "QueryType": "DELETE",
-  "Original": "delete from `user[-]`.user",
-  "Instructions": {
-    "OperatorType": "Delete",
-    "Variant": "ByDestination",
-    "Keyspace": {
-      "Name": "user",
-      "Sharded": true
-    },
-    "TargetTabletType": "MASTER",
-    "KsidVindex": "user_index",
-    "MultiShardAutocommit": false,
-    "OwnedVindexQuery": "select Id, Name, Costly from user for update",
-    "Query": "delete from user",
-    "Table": "user"
-  }
-}
-
-# update with shard targeting
-"update `user[-]`.user set name = 'myname'"
-{
-  "QueryType": "UPDATE",
-  "Original": "update `user[-]`.user set name = 'myname'",
-  "Instructions": {
-    "OperatorType": "Update",
-    "Variant": "ByDestination",
-    "Keyspace": {
-      "Name": "user",
-      "Sharded": true
-    },
-    "TargetTabletType": "MASTER",
-    "ChangedVindexValues": [
-      "name_user_map"
-    ],
-    "KsidVindex": "user_index",
-    "MultiShardAutocommit": false,
-    "OwnedVindexQuery": "select Id, Name, Costly from user for update",
-    "Query": "update user set name = 'myname'",
-    "Table": "user"
-  }
-}
-
-# update with shard targeting without vindex
-"update `user[-]`.user_extra set val = 1"
-{
-  "QueryType": "UPDATE",
-  "Original": "update `user[-]`.user_extra set val = 1",
-  "Instructions": {
-    "OperatorType": "Update",
-    "Variant": "ByDestination",
-    "Keyspace": {
-      "Name": "user",
-      "Sharded": true
-    },
-    "TargetTabletType": "MASTER",
-    "MultiShardAutocommit": false,
-    "Query": "update user_extra set val = 1",
-    "Table": "user_extra"
-  }
-}
diff --git a/go/vt/vtgate/planbuilder/testdata/filter_cases.txt b/go/vt/vtgate/planbuilder/testdata/filter_cases.txt
index c368362ad1..6ad9e9fc09 100644
--- a/go/vt/vtgate/planbuilder/testdata/filter_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/filter_cases.txt
@@ -672,7 +672,7 @@
   }
 }
 
-# routing rules: choose the redirected table
+# routing rules: choose first table due to better plan
 "select col from route1 where id = 1"
 {
   "QueryType": "SELECT",
@@ -694,6 +694,28 @@
   }
 }
 
+# routing rules: choose second table due to better plan
+"select col from route1 where email = 'aaa'"
+{
+  "QueryType": "SELECT",
+  "Original": "select col from route1 where email = 'aaa'",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectEqualUnique",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select col from user_metadata as route1 where 1 != 1",
+    "Query": "select col from user_metadata as route1 where email = 'aaa'",
+    "Table": "user_metadata",
+    "Values": [
+      "aaa"
+    ],
+    "Vindex": "email_user_map"
+  }
+}
+
 # subquery of information_schema with itself
 "select * from information_schema.a where id in (select * from information_schema.b)"
 {
@@ -1152,10 +1174,10 @@
 }
 
 # routing rules subquery merge
-"select col from user where id = (select id from route1 where route1.id = user.id)"
+"select col from user where id = (select id from route2 where route2.id = user.id)"
 {
   "QueryType": "SELECT",
-  "Original": "select col from user where id = (select id from route1 where route1.id = user.id)",
+  "Original": "select col from user where id = (select id from route2 where route2.id = user.id)",
   "Instructions": {
     "OperatorType": "Route",
     "Variant": "SelectScatter",
@@ -1164,7 +1186,7 @@
       "Sharded": true
     },
     "FieldQuery": "select col from user where 1 != 1",
-    "Query": "select col from user where id = (select id from user as route1 where route1.id = user.id)",
+    "Query": "select col from user where id = (select id from user as route2 where route2.id = user.id)",
     "Table": "user"
   }
 }
diff --git a/go/vt/vtgate/planbuilder/testdata/from_cases.txt b/go/vt/vtgate/planbuilder/testdata/from_cases.txt
index bdf90d1151..933fe1bf3d 100644
--- a/go/vt/vtgate/planbuilder/testdata/from_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/from_cases.txt
@@ -1061,27 +1061,21 @@
   }
 }
 
-
-# reference table can merge with other opcodes left to right and vindex value is in the plan.
-# This tests that route.Merge also copies the condition to the LHS.
-"select ref.col from ref join (select aa from user where user.id=1) user"
+# routing rules for join, merged route wins
+"select route2.col from route2 join user_extra on route2.id = user_extra.user_id"
 {
   "QueryType": "SELECT",
-  "Original": "select ref.col from ref join (select aa from user where user.id=1) user",
+  "Original": "select route2.col from route2 join user_extra on route2.id = user_extra.user_id",
   "Instructions": {
     "OperatorType": "Route",
-    "Variant": "SelectEqualUnique",
+    "Variant": "SelectScatter",
     "Keyspace": {
       "Name": "user",
       "Sharded": true
     },
-    "FieldQuery": "select ref.col from ref join (select aa from user where 1 != 1) as user where 1 != 1",
-    "Query": "select ref.col from ref join (select aa from user where user.id = 1) as user",
-    "Table": "user",
-    "Values": [
-      1
-    ],
-    "Vindex": "user_index"
+    "FieldQuery": "select route2.col from user as route2 join user_extra on route2.id = user_extra.user_id where 1 != 1",
+    "Query": "select route2.col from user as route2 join user_extra on route2.id = user_extra.user_id",
+    "Table": "user"
   }
 }
 
@@ -1251,7 +1245,7 @@
   }
 }
 
-# routing rules for subquery
+# routing rules for subquery where the first route wins
 "select id from (select id, col from route1 where id = 5) as t"
 {
   "QueryType": "SELECT",
@@ -1273,6 +1267,28 @@
   }
 }
 
+# routing rules for subquery where the second route wins
+"select id from (select id, col from route1 where email = 'aaa') as t"
+{
+  "QueryType": "SELECT",
+  "Original": "select id from (select id, col from route1 where email = 'aaa') as t",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectEqualUnique",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select id from (select id, col from user_metadata as route1 where 1 != 1) as t where 1 != 1",
+    "Query": "select id from (select id, col from user_metadata as route1 where email = 'aaa') as t",
+    "Table": "user_metadata",
+    "Values": [
+      "aaa"
+    ],
+    "Vindex": "email_user_map"
+  }
+}
+
 # routing rules for subquery where the constraint is in the outer query
 "select id from (select id, col from route1) as t where id = 5"
 {
diff --git a/go/vt/vtgate/planbuilder/testdata/postprocess_cases.txt b/go/vt/vtgate/planbuilder/testdata/postprocess_cases.txt
index a2352cfd2e..58c435c515 100644
--- a/go/vt/vtgate/planbuilder/testdata/postprocess_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/postprocess_cases.txt
@@ -839,7 +839,7 @@
   }
 }
 
-# routing rules: order by gets pushed for routes
+# routing rules: order by test pushed to all options. table 1 is chosen.
 "select col from route1 where id = 1 order by col"
 {
   "QueryType": "SELECT",
@@ -861,6 +861,28 @@
   }
 }
 
+# routing rules: order by test pushed to all options. table 2 is chosen.
+"select col from route1 where email = 'aaa' order by col"
+{
+  "QueryType": "SELECT",
+  "Original": "select col from route1 where email = 'aaa' order by col",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectEqualUnique",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select col from user_metadata as route1 where 1 != 1",
+    "Query": "select col from user_metadata as route1 where email = 'aaa' order by col asc",
+    "Table": "user_metadata",
+    "Values": [
+      "aaa"
+    ],
+    "Vindex": "email_user_map"
+  }
+}
+
 # LIMIT
 "select col1 from user where id = 1 limit 1"
 {
diff --git a/go/vt/vtgate/planbuilder/testdata/schema_test.json b/go/vt/vtgate/planbuilder/testdata/schema_test.json
index c078fe5287..e39c602caa 100644
--- a/go/vt/vtgate/planbuilder/testdata/schema_test.json
+++ b/go/vt/vtgate/planbuilder/testdata/schema_test.json
@@ -2,10 +2,10 @@
   "routing_rules": {
     "rules": [{
       "from_table": "route1",
-      "to_tables": ["user.user"]
+      "to_tables": ["user.user", "user.user_metadata"]
     }, {
       "from_table": "route2",
-      "to_tables": ["main.unsharded"]
+      "to_tables": ["user.user", "main.unsharded"]
     }, {
       "from_table": "second_user.user",
       "to_tables": ["user.user"]
diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.txt b/go/vt/vtgate/planbuilder/testdata/select_cases.txt
index 17aa01ebe0..e167d388a9 100644
--- a/go/vt/vtgate/planbuilder/testdata/select_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/select_cases.txt
@@ -1310,6 +1310,28 @@
   }
 }
 
+# routing rules: union finds the correct option to merge, and renames the table
+"select a from route1 where email = 'aaa' union select b from route1 where email = 'aaa'"
+{
+  "QueryType": "SELECT",
+  "Original": "select a from route1 where email = 'aaa' union select b from route1 where email = 'aaa'",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectEqualUnique",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select a from user_metadata as route1 where 1 != 1 union select b from user_metadata as route1 where 1 != 1",
+    "Query": "select a from user_metadata as route1 where email = 'aaa' union select b from user_metadata as route1 where email = 'aaa'",
+    "Table": "user_metadata",
+    "Values": [
+      "aaa"
+    ],
+    "Vindex": "email_user_map"
+  }
+}
+
 # routing rules: ensure directives are not lost
 "select /*vt+ QUERY_TIMEOUT_MS=1000 */ * from route2"
 {
diff --git a/go/vt/vtgate/vindexes/vschema_test.go b/go/vt/vtgate/vindexes/vschema_test.go
index 7455a8e1be..b90250cc52 100644
--- a/go/vt/vtgate/vindexes/vschema_test.go
+++ b/go/vt/vtgate/vindexes/vschema_test.go
@@ -679,6 +679,9 @@ func TestVSchemaRoutingRules(t *testing.T) {
 			}, {
 				FromTable: "notfound",
 				ToTables:  []string{"ks1.t2"},
+			}, {
+				FromTable: "doubletable",
+				ToTables:  []string{"ks1.t1", "ks1.t1"},
 			}},
 		},
 		Keyspaces: map[string]*vschemapb.Keyspace{
@@ -748,7 +751,7 @@ func TestVSchemaRoutingRules(t *testing.T) {
 	want := &VSchema{
 		RoutingRules: map[string]*RoutingRule{
 			"rt1": {
-				Error: errors.New("table rt1 has more than one target: [ks1.t1 ks2.t2]"),
+				Tables: []*Table{t1, t2},
 			},
 			"rt2": {
 				Tables: []*Table{t2},
@@ -765,6 +768,9 @@ func TestVSchemaRoutingRules(t *testing.T) {
 			"notfound": {
 				Error: errors.New("table t2 not found"),
 			},
+			"doubletable": {
+				Error: errors.New("table ks1.t1 specified more than once"),
+			},
 		},
 		uniqueTables: map[string]*Table{
 			"t1":   t1,
@@ -795,9 +801,11 @@ func TestVSchemaRoutingRules(t *testing.T) {
 			},
 		},
 	}
-	gotb, _ := json.MarshalIndent(got, "", "  ")
-	wantb, _ := json.MarshalIndent(want, "", "  ")
-	assert.Equal(t, string(wantb), string(gotb), string(gotb))
+	if !reflect.DeepEqual(got, want) {
+		gotb, _ := json.Marshal(got)
+		wantb, _ := json.Marshal(want)
+		t.Errorf("BuildVSchema:\n%s, want\n%s", gotb, wantb)
+	}
 }
 
 func TestChooseVindexForType(t *testing.T) {
@@ -2193,15 +2201,15 @@ func TestFindTable(t *testing.T) {
 	}
 }
 
-func TestFindTableOrVindex(t *testing.T) {
+func TestFindTablesOrVindex(t *testing.T) {
 	input := vschemapb.SrvVSchema{
 		RoutingRules: &vschemapb.RoutingRules{
 			Rules: []*vschemapb.RoutingRule{{
 				FromTable: "unqualified",
-				ToTables:  []string{"ksa.ta"},
+				ToTables:  []string{"ksa.ta", "ksb.t1"},
 			}, {
 				FromTable: "unqualified@replica",
-				ToTables:  []string{"ksb.t1"},
+				ToTables:  []string{"ksb.t1", "ksa.ta"},
 			}, {
 				FromTable: "newks.qualified",
 				ToTables:  []string{"ksa.ta"},
@@ -2259,27 +2267,27 @@ func TestFindTableOrVindex(t *testing.T) {
 	ta := vschema.Keyspaces["ksa"].Tables["ta"]
 	t1 := vschema.Keyspaces["ksb"].Tables["t1"]
 
-	_, _, err := vschema.FindTableOrVindex("", "t1", topodatapb.TabletType_MASTER)
+	_, _, err := vschema.FindTablesOrVindex("", "t1", topodatapb.TabletType_MASTER)
 	wantErr := "ambiguous table reference: t1"
 	if err == nil || err.Error() != wantErr {
-		t.Errorf("FindTableOrVindex(\"\"): %v, want %s", err, wantErr)
+		t.Errorf("FindTablesOrVindex(\"\"): %v, want %s", err, wantErr)
 	}
 
-	_, _, err = vschema.FindTableOrVindex("", "none", topodatapb.TabletType_MASTER)
+	_, _, err = vschema.FindTablesOrVindex("", "none", topodatapb.TabletType_MASTER)
 	wantErr = "table none not found"
 	if err == nil || err.Error() != wantErr {
-		t.Errorf("FindTableOrVindex(\"\"): %v, want %s", err, wantErr)
+		t.Errorf("FindTablesOrVindex(\"\"): %v, want %s", err, wantErr)
 	}
 
-	got, _, err := vschema.FindTableOrVindex("", "ta", topodatapb.TabletType_MASTER)
+	got, _, err := vschema.FindTablesOrVindex("", "ta", topodatapb.TabletType_MASTER)
 	if err != nil {
 		t.Fatal(err)
 	}
-	if !reflect.DeepEqual(got, ta) {
-		t.Errorf("FindTableOrVindex(\"t1a\"): %+v, want %+v", got, ta)
+	if !reflect.DeepEqual(got, []*Table{ta}) {
+		t.Errorf("FindTablesOrVindex(\"t1a\"): %+v, want %+v", got, ta)
 	}
 
-	_, vindex, err := vschema.FindTableOrVindex("", "stfu1", topodatapb.TabletType_MASTER)
+	_, vindex, err := vschema.FindTablesOrVindex("", "stfu1", topodatapb.TabletType_MASTER)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -2287,10 +2295,10 @@ func TestFindTableOrVindex(t *testing.T) {
 		name: "stfu1",
 	}
 	if !reflect.DeepEqual(vindex, wantVindex) {
-		t.Errorf("FindTableOrVindex(\"stfu1\"): %+v, want %+v", vindex, wantVindex)
+		t.Errorf("FindTablesOrVindex(\"stfu1\"): %+v, want %+v", vindex, wantVindex)
 	}
 
-	_, vindex, err = vschema.FindTableOrVindex("ksc", "ta", topodatapb.TabletType_MASTER)
+	_, vindex, err = vschema.FindTablesOrVindex("ksc", "ta", topodatapb.TabletType_MASTER)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -2298,51 +2306,51 @@ func TestFindTableOrVindex(t *testing.T) {
 		name: "ta",
 	}
 	if !reflect.DeepEqual(vindex, wantVindex) {
-		t.Errorf("FindTableOrVindex(\"stfu1\"): %+v, want %+v", vindex, wantVindex)
+		t.Errorf("FindTablesOrVindex(\"stfu1\"): %+v, want %+v", vindex, wantVindex)
 	}
 
-	_, _, err = vschema.FindTableOrVindex("", "dup", topodatapb.TabletType_MASTER)
+	_, _, err = vschema.FindTablesOrVindex("", "dup", topodatapb.TabletType_MASTER)
 	wantErr = "ambiguous vindex reference: dup"
 	if err == nil || err.Error() != wantErr {
-		t.Errorf("FindTableOrVindex(\"\"): %v, want %s", err, wantErr)
+		t.Errorf("FindTablesOrVindex(\"\"): %v, want %s", err, wantErr)
 	}
 
-	got, _, err = vschema.FindTableOrVindex("", "unqualified", topodatapb.TabletType_MASTER)
+	got, _, err = vschema.FindTablesOrVindex("", "unqualified", topodatapb.TabletType_MASTER)
 	if err != nil {
 		t.Fatal(err)
 	}
-	if want := ta; !reflect.DeepEqual(got, want) {
-		t.Errorf("FindTableOrVindex(unqualified): %+v, want %+v", got, want)
+	if want := []*Table{ta, t1}; !reflect.DeepEqual(got, want) {
+		t.Errorf("FindTablesOrVindex(unqualified): %+v, want %+v", got, want)
 	}
 
-	got, _, err = vschema.FindTableOrVindex("", "unqualified", topodatapb.TabletType_REPLICA)
+	got, _, err = vschema.FindTablesOrVindex("", "unqualified", topodatapb.TabletType_REPLICA)
 	if err != nil {
 		t.Fatal(err)
 	}
-	if want := t1; !reflect.DeepEqual(got, want) {
-		t.Errorf("FindTableOrVindex(unqualified): %+v, want %+v", got, want)
+	if want := []*Table{t1, ta}; !reflect.DeepEqual(got, want) {
+		t.Errorf("FindTablesOrVindex(unqualified): %+v, want %+v", got, want)
 	}
 
-	got, _, err = vschema.FindTableOrVindex("newks", "qualified", topodatapb.TabletType_MASTER)
+	got, _, err = vschema.FindTablesOrVindex("newks", "qualified", topodatapb.TabletType_MASTER)
 	if err != nil {
 		t.Fatal(err)
 	}
-	if want := ta; !reflect.DeepEqual(got, want) {
-		t.Errorf("FindTableOrVindex(unqualified): %+v, want %+v", got, want)
+	if want := []*Table{ta}; !reflect.DeepEqual(got, want) {
+		t.Errorf("FindTablesOrVindex(unqualified): %+v, want %+v", got, want)
 	}
 
-	got, _, err = vschema.FindTableOrVindex("newks", "qualified", topodatapb.TabletType_REPLICA)
+	got, _, err = vschema.FindTablesOrVindex("newks", "qualified", topodatapb.TabletType_REPLICA)
 	if err != nil {
 		t.Fatal(err)
 	}
-	if want := t1; !reflect.DeepEqual(got, want) {
-		t.Errorf("FindTableOrVindex(unqualified): %+v, want %+v", got, want)
+	if want := []*Table{t1}; !reflect.DeepEqual(got, want) {
+		t.Errorf("FindTablesOrVindex(unqualified): %+v, want %+v", got, want)
 	}
 
-	_, _, err = vschema.FindTableOrVindex("", "notarget", topodatapb.TabletType_MASTER)
+	_, _, err = vschema.FindTablesOrVindex("", "notarget", topodatapb.TabletType_MASTER)
 	wantErr = "table notarget has been disabled"
 	if err == nil || err.Error() != wantErr {
-		t.Errorf("FindTableOrVindex(\"\"): %v, want %s", err, wantErr)
+		t.Errorf("FindTablesOrVindex(\"\"): %v, want %s", err, wantErr)
 	}
 }
 
diff --git a/go/vt/vttablet/endtoend/connkilling/connkiller_test.go b/go/vt/vttablet/endtoend/connkilling/connkiller_test.go
deleted file mode 100644
index d94051f3c5..0000000000
--- a/go/vt/vttablet/endtoend/connkilling/connkiller_test.go
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
-Copyright 2020 The Vitess Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-/*
-All tests in this package come with a three second time out for OLTP session
-*/
-package connkilling
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/require"
-	"vitess.io/vitess/go/vt/vttablet/endtoend/framework"
-)
-
-func TestTxKillerKillsTransactionsInReservedConnections(t *testing.T) {
-	client := framework.NewClient()
-	defer client.Release()
-
-	_, err := client.ReserveBeginExecute("select 42", nil, nil)
-	require.NoError(t, err)
-
-	assertIsKilledWithin5Seconds(t, client)
-}
-
-func TestTxKillerDoesNotKillReservedConnectionsInUse(t *testing.T) {
-	client := framework.NewClient()
-	defer client.Release()
-
-	_, err := client.ReserveExecute("select 42", nil, nil)
-	require.NoError(t, err)
-
-	assertIsNotKilledOver5Second(t, client)
-}
-
-func TestTxKillerCountsTimeFromTxStartedNotStatefulConnCreated(t *testing.T) {
-	client := framework.NewClient()
-	defer client.Release()
-
-	// reserve connection at 0th second
-	_, err := client.ReserveExecute("select 42", nil, nil)
-	require.NoError(t, err)
-
-	// elapsed 2 seconds
-	time.Sleep(2 * time.Second)
-
-	// update the timer on tx start - new tx timer starts
-	_, err = client.BeginExecute("select 44", nil, nil)
-	require.NoError(t, err)
-
-	// elapsed 1 second from tx and 3 second from reserved conn.
-	time.Sleep(1 * time.Second)
-	_, err = client.Execute("select 43", nil)
-	require.NoError(t, err)
-
-	// elapsed 2 second from tx and 4 second from reserved conn. It does not fail.
-	time.Sleep(1 * time.Second)
-	_, err = client.Execute("select 43", nil)
-	require.NoError(t, err)
-
-	assertIsKilledWithin5Seconds(t, client)
-}
-
-func TestTxKillerKillsTransactionThreeSecondsAfterCreation(t *testing.T) {
-	client := framework.NewClient()
-	defer client.Release()
-
-	_, err := client.BeginExecute("select 42", nil, nil)
-	require.NoError(t, err)
-
-	assertIsKilledWithin5Seconds(t, client)
-}
-
-func assertIsNotKilledOver5Second(t *testing.T, client *framework.QueryClient) {
-	for i := 0; i < 5; i++ {
-		_, err := client.Execute("select 43", nil)
-		require.NoError(t, err)
-		time.Sleep(1 * time.Second)
-	}
-}
-
-func assertIsKilledWithin5Seconds(t *testing.T, client *framework.QueryClient) {
-	var err error
-	// when it is used once per second
-	for i := 0; i < 5; i++ {
-		_, err = client.Execute("select 43", nil)
-		if err != nil {
-			break
-		}
-		time.Sleep(1 * time.Second)
-	}
-
-	// then it should still be killed. transactions are tracked per tx-creation time and not last-used time
-	require.Error(t, err)
-	require.Contains(t, err.Error(), "exceeded timeout: 3s")
-}
diff --git a/go/vt/vttablet/endtoend/connkilling/main_test.go b/go/vt/vttablet/endtoend/connkilling/main_test.go
deleted file mode 100644
index 9e4aa6c2e5..0000000000
--- a/go/vt/vttablet/endtoend/connkilling/main_test.go
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
-Copyright 2019 The Vitess Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package connkilling
-
-import (
-	"errors"
-	"flag"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"testing"
-
-	"vitess.io/vitess/go/mysql"
-	"vitess.io/vitess/go/vt/tableacl"
-	"vitess.io/vitess/go/vt/tableacl/simpleacl"
-	"vitess.io/vitess/go/vt/vttablet/endtoend/framework"
-	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
-	"vitess.io/vitess/go/vt/vttest"
-
-	vttestpb "vitess.io/vitess/go/vt/proto/vttest"
-)
-
-var (
-	connParams         mysql.ConnParams
-	connAppDebugParams mysql.ConnParams
-)
-
-func TestMain(m *testing.M) {
-	flag.Parse() // Do not remove this comment, import into google3 depends on it
-	tabletenv.Init()
-
-	exitCode := func() int {
-		// Launch MySQL.
-		// We need a Keyspace in the topology, so the DbName is set.
-		// We need a Shard too, so the database 'vttest' is created.
-		cfg := vttest.Config{
-			Topology: &vttestpb.VTTestTopology{
-				Keyspaces: []*vttestpb.Keyspace{
-					{
-						Name: "vttest",
-						Shards: []*vttestpb.Shard{
-							{
-								Name:           "0",
-								DbNameOverride: "vttest",
-							},
-						},
-					},
-				},
-			},
-			OnlyMySQL: true,
-		}
-		if err := cfg.InitSchemas("vttest", testSchema, nil); err != nil {
-			fmt.Fprintf(os.Stderr, "InitSchemas failed: %v\n", err)
-			return 1
-		}
-		defer os.RemoveAll(cfg.SchemaDir)
-		cluster := vttest.LocalCluster{
-			Config: cfg,
-		}
-		if err := cluster.Setup(); err != nil {
-			fmt.Fprintf(os.Stderr, "could not launch mysql: %v\n", err)
-			return 1
-		}
-		defer cluster.TearDown()
-
-		connParams = cluster.MySQLConnParams()
-		connAppDebugParams = cluster.MySQLAppDebugConnParams()
-		config := tabletenv.NewDefaultConfig()
-		config.Oltp.TxTimeoutSeconds = tabletenv.Seconds(3)
-		err := framework.StartCustomServer(connParams, connAppDebugParams, cluster.DbName(), config)
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "%v", err)
-			return 1
-		}
-		defer framework.StopServer()
-
-		err = initTableACL()
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "%v", err)
-			return 1
-		}
-
-		return m.Run()
-	}()
-	os.Exit(exitCode)
-}
-
-func initTableACL() error {
-	file, err := ioutil.TempFile("", "tableacl.json")
-	if err != nil {
-		return err
-	}
-	defer os.Remove(file.Name())
-
-	n, err := file.WriteString(tableACLConfig)
-	if err != nil {
-		return err
-	}
-	if n != len(tableACLConfig) {
-		return errors.New("table acl: short write")
-	}
-	file.Close()
-	tableacl.Register("simpleacl", &simpleacl.Factory{})
-	tableacl.Init(file.Name(), func() { framework.Server.ClearQueryPlanCache() })
-	return nil
-}
-
-var testSchema = `create table vitess_test(intval int default 0, floatval float default null, charval varchar(10) default null, binval varbinary(256) default null, primary key(intval));
-create table vitess_test_debuguser(intval int default 0, floatval float default null, charval varchar(256) default null, binval varbinary(256) default null, primary key(intval));
-grant select, show databases, process on *.* to 'vt_appdebug'@'localhost';
-revoke select on *.* from 'vt_appdebug'@'localhost';
-insert into vitess_test_debuguser values(1, 1.12345, 0xC2A2, 0x00FF), (2, null, '', null), (3, null, null, null);
-insert into vitess_test values(1, 1.12345, 0xC2A2, 0x00FF), (2, null, '', null), (3, null, null, null);
-
-create table vitess_a(eid bigint default 0, id int default 1, name varchar(128) default null, foo varbinary(128) default null, primary key(eid, id));
-create table vitess_b(eid bigint default 0, id int default 0, primary key(eid, id));
-create table vitess_c(eid bigint default 0, name varchar(128) default 'name', foo varbinary(128) default null, primary key(eid, name));
-create table vitess_d(eid bigint default null, id int default null);
-create table vitess_e(eid bigint auto_increment, id int default 1, name varchar(128) default 'name', foo varchar(128) default null, primary key(eid, id, name));
-create table vitess_f(vb varbinary(16) default 'ab', id int default null, primary key(vb));
-create table upsert_test(id1 int default 0, id2 int default null, primary key (id1));
-create unique index id2_idx on upsert_test(id2);
-insert into vitess_a(eid, id, name, foo) values(1, 1, 'abcd', 'efgh'), (1, 2, 'bcde', 'fghi');
-insert into vitess_b(eid, id) values(1, 1), (1, 2);
-insert into vitess_c(eid, name, foo) values(10, 'abcd', '20'), (11, 'bcde', '30');
-create table vitess_mixed_case(Col1 int default 0, COL2 int default null, primary key(col1));
-
-CREATE TABLE vitess_autoinc_seq (
-  id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
-  name varchar(255) NOT NULL,
-  sequence bigint(20) unsigned NOT NULL DEFAULT '0',
-  PRIMARY KEY (id),
-  UNIQUE KEY name (name)
-);
-
-create table vitess_big(id int default 0, string1 varchar(128) default null, string2 varchar(100) default null, string3 char(1) default null, string4 varchar(50) default null, string5 varchar(50) default null, string6 varchar(16) default null, string7 varchar(120) default null, bigint1 bigint(20) default null, bigint2 bigint(20) default null, integer1 int default null, tinyint1 tinyint(4) default null, primary key(id));
-
-create table vitess_ints(tiny tinyint default 0, tinyu tinyint unsigned default null, small smallint default null, smallu smallint unsigned default null, medium mediumint default null, mediumu mediumint unsigned default null, normal int default null, normalu int unsigned default null, big bigint default null, bigu bigint unsigned default null, y year default null, primary key(tiny));
-create table vitess_fracts(id int default 0, deci decimal(5,2) default null, num numeric(5,2) default null, f float default null, d double default null, primary key(id));
-create table vitess_strings(vb varbinary(16) default 'vb', c char(16) default null, vc varchar(16) default null, b binary(4) default null, tb tinyblob default null, bl blob default null, ttx tinytext default null, tx text default null, en enum('a','b') default null, s set('a','b') default null, primary key(vb));
-create table vitess_misc(id int default 0, b bit(8) default null, d date default null, dt datetime default null, t time default null, g geometry default null, primary key(id));
-create table vitess_bit_default(id bit(8) default b'101', primary key(id));
-
-create table vitess_bool(auto int auto_increment, bval tinyint(1) default 0, sval varchar(16) default '', ival int default null, primary key (auto));
-
-create table vitess_seq(id int default 0, next_id bigint default null, cache bigint default null, increment bigint default null, primary key(id)) comment 'vitess_sequence';
-insert into vitess_seq(id, next_id, cache) values(0, 1, 3);
-
-create table vitess_reset_seq(id int default 0, next_id bigint default null, cache bigint default null, increment bigint default null, primary key(id)) comment 'vitess_sequence';
-insert into vitess_reset_seq(id, next_id, cache) values(0, 1, 3);
-
-create table vitess_part(id int, data varchar(16), primary key(id));
-alter table vitess_part partition by range (id) (partition p0 values less than (10), partition p1 values less than (maxvalue));
-
-create table vitess_acl_no_access(key1 bigint default 0, key2 bigint default null, primary key(key1));
-create table vitess_acl_read_only(key1 bigint default 0, key2 bigint default null, primary key(key1));
-create table vitess_acl_read_write(key1 bigint default 0, key2 bigint default null, primary key(key1));
-create table vitess_acl_admin(key1 bigint default 0, key2 bigint default null, primary key(key1));
-create table vitess_acl_unmatched(key1 bigint default 0, key2 bigint default null, primary key(key1));
-create table vitess_acl_all_user_read_only(key1 bigint default 0, key2 bigint default null, primary key(key1));`
-
-var tableACLConfig = `{
-  "table_groups": [
-    {
-      "name": "mysql",
-      "table_names_or_prefixes": [""],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_cached",
-      "table_names_or_prefixes": ["vitess_nocache", "vitess_cached%"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_renamed",
-      "table_names_or_prefixes": ["vitess_renamed%"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_part",
-      "table_names_or_prefixes": ["vitess_part%"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess",
-      "table_names_or_prefixes": ["vitess_a", "vitess_b", "vitess_c", "dual", "vitess_d", "vitess_temp", "vitess_e", "vitess_f", "vitess_mixed_case", "upsert_test", "vitess_strings", "vitess_fracts", "vitess_ints", "vitess_misc", "vitess_bit_default", "vitess_big", "vitess_view", "vitess_json", "vitess_bool", "vitess_autoinc_seq"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_test",
-      "table_names_or_prefixes": ["vitess_test"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_test_ddl",
-      "table_names_or_prefixes": ["vitess_test_ddl"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_seq",
-      "table_names_or_prefixes": ["vitess_seq"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_reset_seq",
-      "table_names_or_prefixes": ["vitess_reset_seq"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_acl_unmatched",
-      "table_names_or_prefixes": ["vitess_acl_unmatched"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_acl_no_access",
-      "table_names_or_prefixes": ["vitess_acl_no_access"]
-    },
-    {
-      "name": "vitess_acl_read_only",
-      "table_names_or_prefixes": ["vitess_acl_read_only"],
-      "readers": ["dev"]
-    },
-    {
-      "name": "vitess_acl_read_write",
-      "table_names_or_prefixes": ["vitess_acl_read_write"],
-      "readers": ["dev"],
-      "writers": ["dev"]
-    },
-    {
-      "name": "vitess_acl_admin",
-      "table_names_or_prefixes": ["vitess_acl_admin"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "vitess_acl_all_user_read_only",
-      "table_names_or_prefixes": ["vitess_acl_all_user_read_only"],
-      "readers": ["dev"]
-    },
-    {
-      "name": "vitess_acl_appdebug",
-      "table_names_or_prefixes": ["vitess_test_debuguser"],
-      "readers": ["dev", "vt_appdebug"],
-      "writers": ["dev", "vt_appdebug"]
-    },
-    {
-      "name": "version",
-      "table_names_or_prefixes": ["vitess_version"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "schema_version",
-      "table_names_or_prefixes": ["schema_version"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    },
-    {
-      "name": "historian_test1",
-      "table_names_or_prefixes": ["historian_test1"],
-      "readers": ["dev"],
-      "writers": ["dev"],
-      "admins": ["dev"]
-    }
-  ]
-}`
diff --git a/go/vt/vttablet/tabletserver/schema/historian_test.go b/go/vt/vttablet/tabletserver/schema/historian_test.go
index 40268fd628..643b5397a8 100644
--- a/go/vt/vttablet/tabletserver/schema/historian_test.go
+++ b/go/vt/vttablet/tabletserver/schema/historian_test.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 	"github.com/stretchr/testify/require"
 	"vitess.io/vitess/go/sqltypes"
 	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
diff --git a/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go b/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go
index 4e85193473..72082fd351 100644
--- a/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go
+++ b/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go
@@ -31,7 +31,7 @@ import (
 	"vitess.io/vitess/go/vt/log"
 	"vitess.io/vitess/go/vt/sqlparser"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"vitess.io/vitess/go/mysql"
diff --git a/go/vt/wrangler/materializer_test.go b/go/vt/wrangler/materializer_test.go
index 6adb25b3e5..2b7f88f665 100644
--- a/go/vt/wrangler/materializer_test.go
+++ b/go/vt/wrangler/materializer_test.go
@@ -21,7 +21,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/net/context"
diff --git a/go/vt/wrangler/traffic_switcher_env_test.go b/go/vt/wrangler/traffic_switcher_env_test.go
index 81f8b38b7d..e24040f34d 100644
--- a/go/vt/wrangler/traffic_switcher_env_test.go
+++ b/go/vt/wrangler/traffic_switcher_env_test.go
@@ -40,8 +40,8 @@ import (
 	"vitess.io/vitess/go/vt/vttablet/tmclient"
 )
 
-const vreplQueryks = "select id, source, message, cell, tablet_types from _vt.vreplication where workflow='test' and db_name='vt_ks'"
-const vreplQueryks2 = "select id, source, message, cell, tablet_types from _vt.vreplication where workflow='test' and db_name='vt_ks2'"
+const vreplQueryks = "select id, source, message from _vt.vreplication where workflow='test' and db_name='vt_ks'"
+const vreplQueryks2 = "select id, source, message from _vt.vreplication where workflow='test' and db_name='vt_ks2'"
 
 type testMigraterEnv struct {
 	ts              *topo.Server
@@ -182,11 +182,11 @@ func newTestTableMigraterCustom(ctx context.Context, t *testing.T, sourceShards,
 					}},
 				},
 			}
-			rows = append(rows, fmt.Sprintf("%d|%v|||", j+1, bls))
+			rows = append(rows, fmt.Sprintf("%d|%v|", j+1, bls))
 		}
 		tme.dbTargetClients[i].addInvariant(vreplQueryks2, sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-			"id|source|message|cell|tablet_types",
-			"int64|varchar|varchar|varchar|varchar"),
+			"id|source|message",
+			"int64|varchar|varchar"),
 			rows...),
 		)
 	}
@@ -301,11 +301,11 @@ func newTestShardMigrater(ctx context.Context, t *testing.T, sourceShards, targe
 					}},
 				},
 			}
-			rows = append(rows, fmt.Sprintf("%d|%v|||", j+1, bls))
+			rows = append(rows, fmt.Sprintf("%d|%v|", j+1, bls))
 		}
 		tme.dbTargetClients[i].addInvariant(vreplQueryks, sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-			"id|source|message|cell|tablet_types",
-			"int64|varchar|varchar|varchar|varchar"),
+			"id|source|message",
+			"int64|varchar|varchar"),
 			rows...),
 		)
 	}
diff --git a/go/vt/wrangler/traffic_switcher_test.go b/go/vt/wrangler/traffic_switcher_test.go
index 70c45a261c..b506aba212 100644
--- a/go/vt/wrangler/traffic_switcher_test.go
+++ b/go/vt/wrangler/traffic_switcher_test.go
@@ -1455,9 +1455,9 @@ func TestMigrateFrozen(t *testing.T) {
 		},
 	}
 	tme.dbTargetClients[0].addQuery(vreplQueryks2, sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-		"id|source|message|cell|tablet_types",
-		"int64|varchar|varchar|varchar|varchar"),
-		fmt.Sprintf("1|%v|FROZEN||", bls1),
+		"id|source|message",
+		"int64|varchar|varchar"),
+		fmt.Sprintf("1|%v|FROZEN", bls1),
 	), nil)
 	tme.dbTargetClients[1].addQuery(vreplQueryks2, &sqltypes.Result{}, nil)
 
@@ -1468,9 +1468,9 @@ func TestMigrateFrozen(t *testing.T) {
 	}
 
 	tme.dbTargetClients[0].addQuery(vreplQueryks2, sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-		"id|source|message|cell|tablet_type",
-		"int64|varchar|varchar|varchar|varchar"),
-		fmt.Sprintf("1|%v|FROZEN||", bls1),
+		"id|source|message",
+		"int64|varchar|varchar"),
+		fmt.Sprintf("1|%v|FROZEN", bls1),
 	), nil)
 	tme.dbTargetClients[1].addQuery(vreplQueryks2, &sqltypes.Result{}, nil)
 
@@ -1515,9 +1515,9 @@ func TestMigrateDistinctSources(t *testing.T) {
 		},
 	}
 	tme.dbTargetClients[0].addQuery(vreplQueryks2, sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-		"id|source|message|cell|tablet_types",
-		"int64|varchar|varchar|varchar|varchar"),
-		fmt.Sprintf("1|%v|||", bls),
+		"id|source|message",
+		"int64|varchar|varchar"),
+		fmt.Sprintf("1|%v|", bls),
 	), nil)
 
 	_, err := tme.wr.SwitchReads(ctx, tme.targetKeyspace, "test", topodatapb.TabletType_RDONLY, nil, DirectionForward, false)
@@ -1543,9 +1543,9 @@ func TestMigrateMismatchedTables(t *testing.T) {
 		},
 	}
 	tme.dbTargetClients[0].addQuery(vreplQueryks2, sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-		"id|source|message|cell|tablet_types",
-		"int64|varchar|varchar|varchar|varchar"),
-		fmt.Sprintf("1|%v|||", bls)),
+		"id|source|message",
+		"int64|varchar|varchar"),
+		fmt.Sprintf("1|%v|", bls)),
 		nil,
 	)
 
@@ -1596,10 +1596,10 @@ func TestMigrateNoTableWildcards(t *testing.T) {
 		},
 	}
 	tme.dbTargetClients[0].addQuery(vreplQueryks2, sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-		"id|source|message|cell|tablet_types",
-		"int64|varchar|varchar|varchar|varchar"),
-		fmt.Sprintf("1|%v|||", bls1),
-		fmt.Sprintf("2|%v|||", bls2),
+		"id|source|message",
+		"int64|varchar|varchar"),
+		fmt.Sprintf("1|%v|", bls1),
+		fmt.Sprintf("2|%v|", bls2),
 	), nil)
 	bls3 := &binlogdatapb.BinlogSource{
 		Keyspace: "ks1",
@@ -1612,9 +1612,9 @@ func TestMigrateNoTableWildcards(t *testing.T) {
 		},
 	}
 	tme.dbTargetClients[1].addQuery(vreplQueryks2, sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-		"id|source|message|cell|tablet_types",
-		"int64|varchar|varchar|varchar|varchar"),
-		fmt.Sprintf("1|%v|||", bls3),
+		"id|source|message",
+		"int64|varchar|varchar"),
+		fmt.Sprintf("1|%v|", bls3),
 	), nil)
 
 	_, err := tme.wr.SwitchReads(ctx, tme.targetKeyspace, "test", topodatapb.TabletType_RDONLY, nil, DirectionForward, false)
@@ -1641,51 +1641,6 @@ func TestReverseName(t *testing.T) {
 	}
 }
 
-func TestReverseVReplicationUpdateQuery(t *testing.T) {
-	ts := &trafficSwitcher{
-		reverseWorkflow: "wf",
-	}
-	dbname := "db"
-	type tCase struct {
-		optCells       string
-		optTabletTypes string
-		targetCell     string
-		sourceCell     string
-		want           string
-	}
-	updateQuery := "update _vt.vreplication set cell = '%s', tablet_types = '%s' where workflow = 'wf' and db_name = 'db'"
-	tCases := []tCase{
-		{
-			targetCell: "cell1", sourceCell: "cell1", optCells: "cell1", optTabletTypes: "",
-			want: fmt.Sprintf(updateQuery, "cell1", ""),
-		},
-		{
-			targetCell: "cell1", sourceCell: "cell2", optCells: "cell1", optTabletTypes: "",
-			want: fmt.Sprintf(updateQuery, "cell2", ""),
-		},
-		{
-			targetCell: "cell1", sourceCell: "cell2", optCells: "cell2", optTabletTypes: "",
-			want: fmt.Sprintf(updateQuery, "cell2", ""),
-		},
-		{
-			targetCell: "cell1", sourceCell: "cell1", optCells: "cell1,cell2", optTabletTypes: "replica,master",
-			want: fmt.Sprintf(updateQuery, "cell1,cell2", "replica,master"),
-		},
-		{
-			targetCell: "cell1", sourceCell: "cell1", optCells: "", optTabletTypes: "replica,master",
-			want: fmt.Sprintf(updateQuery, "", "replica,master"),
-		},
-	}
-	for _, tc := range tCases {
-		t.Run("", func(t *testing.T) {
-			ts.optCells = tc.optCells
-			ts.optTabletTypes = tc.optTabletTypes
-			got := ts.getReverseVReplicationUpdateQuery(tc.targetCell, tc.sourceCell, dbname)
-			require.Equal(t, tc.want, got)
-		})
-	}
-}
-
 func checkRouting(t *testing.T, wr *Wrangler, want map[string][]string) {
 	t.Helper()
 	ctx := context.Background()
diff --git a/go/vt/wrangler/vdiff_env_test.go b/go/vt/wrangler/vdiff_env_test.go
index fa0991a432..95c780e4eb 100644
--- a/go/vt/wrangler/vdiff_env_test.go
+++ b/go/vt/wrangler/vdiff_env_test.go
@@ -116,7 +116,7 @@ func newTestVDiffEnv(sourceShards, targetShards []string, query string, position
 					}},
 				},
 			}
-			rows = append(rows, fmt.Sprintf("%d|%v|||", j+1, bls))
+			rows = append(rows, fmt.Sprintf("%d|%v|", j+1, bls))
 			position := vdiffStopPosition
 			if pos := positions[sourceShard+shard]; pos != "" {
 				position = pos
@@ -134,10 +134,10 @@ func newTestVDiffEnv(sourceShards, targetShards []string, query string, position
 		// migrater buildMigrationTargets
 		env.tmc.setVRResults(
 			master.tablet,
-			"select id, source, message, cell, tablet_types from _vt.vreplication where workflow='vdiffTest' and db_name='vt_target'",
+			"select id, source, message from _vt.vreplication where workflow='vdiffTest' and db_name='vt_target'",
 			sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-				"id|source|message|cell|tablet_types",
-				"int64|varchar|varchar|varchar|varchar"),
+				"id|source|message",
+				"int64|varchar|varchar"),
 				rows...,
 			),
 		)
diff --git a/go/vt/wrangler/vexec_test.go b/go/vt/wrangler/vexec_test.go
index 8a85b11dc2..754bb2f041 100644
--- a/go/vt/wrangler/vexec_test.go
+++ b/go/vt/wrangler/vexec_test.go
@@ -95,13 +95,13 @@ func TestVExec(t *testing.T) {
 	var result *sqltypes.Result
 	var testCases []*TestCase
 	result = sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-		"id|source|message|cell|tablet_types",
-		"int64|varchar|varchar|varchar|varchar"),
-		"1|keyspace:\"source\" shard:\"0\" filter:<rules:<match:\"t1\" > >|||",
+		"id|source|message",
+		"int64|varchar|varchar"),
+		"1|keyspace:\"source\" shard:\"0\" filter:<rules:<match:\"t1\" > >|",
 	)
 	testCases = append(testCases, &TestCase{
 		name:   "select",
-		query:  "select id, source, message, cell, tablet_types from _vt.vreplication",
+		query:  "select id, source, message from _vt.vreplication",
 		result: result,
 	})
 	result = &sqltypes.Result{
diff --git a/go/vt/wrangler/wrangler_env_test.go b/go/vt/wrangler/wrangler_env_test.go
index 497af49f33..92cd904e88 100644
--- a/go/vt/wrangler/wrangler_env_test.go
+++ b/go/vt/wrangler/wrangler_env_test.go
@@ -114,7 +114,7 @@ func newWranglerTestEnv(sourceShards, targetShards []string, query string, posit
 					}},
 				},
 			}
-			rows = append(rows, fmt.Sprintf("%d|%v|||", j+1, bls))
+			rows = append(rows, fmt.Sprintf("%d|%v|", j+1, bls))
 			position := testStopPosition
 			if pos := positions[sourceShard+shard]; pos != "" {
 				position = pos
@@ -130,10 +130,10 @@ func newWranglerTestEnv(sourceShards, targetShards []string, query string, posit
 		// migrater buildMigrationTargets
 		env.tmc.setVRResults(
 			master.tablet,
-			"select id, source, message, cell, tablet_types from _vt.vreplication where db_name = 'vt_target' and workflow = 'wrWorkflow'",
+			"select id, source, message from _vt.vreplication where db_name = 'vt_target' and workflow = 'wrWorkflow'",
 			sqltypes.MakeTestResult(sqltypes.MakeTestFields(
-				"id|source|message|cell|tablet_types",
-				"int64|varchar|varchar|varchar|varchar"),
+				"id|source|message",
+				"int64|varchar|varchar"),
 				rows...,
 			),
 		)
diff --git a/test/region_example.sh b/test/region_example.sh
index d02d3203d5..2be46a597b 100755
--- a/test/region_example.sh
+++ b/test/region_example.sh
@@ -32,28 +32,6 @@ source ./env.sh # Required so that "mysql" works from alias
 sleep 5 # Give vtgate time to really start.
 
 mysql < insert_customers.sql
-mysql --table < show_initial_data.sql
+mysql --table < show_data.sql
 
-# create schema and vschema for sharding (+lookup vindex)
-./201_main_sharded.sh
-
-# bring up shards and tablets
-./202_new_tablets.sh
-
-# reshard
-./203_reshard.sh
-
-# SwitchReads
-./204_switch_reads.sh
-
-# SwitchWrites
-./205_switch_writes.sh
-
-# down shard
-./206_down_shard_0.sh
-
-# delete shard 0
-./207_delete_shard_0.sh
-
-# Down cluster
-./301_teardown.sh
+./201_teardown.sh
\ No newline at end of file