Repository: vitessio/vitess
Pull request #8312 (2021-06-11 12:14:07)

gen4: outer joins
## Description
Teaches gen4 how to plan outer joins.

The big change is that we had to introduce a new abstraction - the operator tree.
Before this PR, gen4 turned queries into query graphs for planning. That is not enough to handle outer joins well, so we introduced the operator tree, which can be or contain a querygraph to represent the query.

## Related Issue(s)
#7280

## Checklist
- [x] Tests were added or are not required
- [x] Documentation was added or is not required


Patch:
diff --git a/docker/bootstrap/Dockerfile.mysql56 b/docker/bootstrap/Dockerfile.mysql56
index 76c9b26706..9905e5b099 100644
--- a/docker/bootstrap/Dockerfile.mysql56
+++ b/docker/bootstrap/Dockerfile.mysql56
@@ -10,7 +10,7 @@ FROM "${image}"
 #
 # I think it's fine as MySQL 5.6 will be EOL pretty soon (February 5, 2021)
 #
-RUN for i in $(seq 1 10); do apt-key adv --no-tty --recv-keys --keyserver keyserver.ubuntu.com 8C718D3B5072E1F5 && break; done && \
+RUN for i in $(seq 1 10); do apt-key adv --no-tty --recv-keys --keyserver pool.sks-keyservers.net 8C718D3B5072E1F5 && break; done && \
     add-apt-repository 'deb http://repo.mysql.com/apt/debian/ stretch mysql-5.6' && \
     for i in $(seq 1 10); do apt-key adv --no-tty --keyserver keys.gnupg.net --recv-keys 9334A25F8507EFA5 && break; done && \
     echo 'deb http://repo.percona.com/apt buster main' > /etc/apt/sources.list.d/percona.list && \
diff --git a/docker/bootstrap/Dockerfile.mysql57 b/docker/bootstrap/Dockerfile.mysql57
index 5c0f6d0cfc..3ebae48a4d 100644
--- a/docker/bootstrap/Dockerfile.mysql57
+++ b/docker/bootstrap/Dockerfile.mysql57
@@ -5,7 +5,7 @@ FROM "${image}"
 
 # Install MySQL 5.7
 RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends gnupg dirmngr ca-certificates && \
-    for i in $(seq 1 10); do apt-key adv --no-tty --recv-keys --keyserver keyserver.ubuntu.com 8C718D3B5072E1F5 && break; done && \
+    for i in $(seq 1 10); do apt-key adv --no-tty --recv-keys --keyserver ha.pool.sks-keyservers.net 8C718D3B5072E1F5 && break; done && \
     add-apt-repository 'deb http://repo.mysql.com/apt/debian/ buster mysql-5.7' && \
     for i in $(seq 1 10); do apt-key adv --no-tty --keyserver keys.gnupg.net --recv-keys 9334A25F8507EFA5 && break; done && \
     echo 'deb http://repo.percona.com/apt buster main' > /etc/apt/sources.list.d/percona.list && \
diff --git a/docker/bootstrap/Dockerfile.mysql80 b/docker/bootstrap/Dockerfile.mysql80
index 2d84949f30..b2a60cdf5c 100644
--- a/docker/bootstrap/Dockerfile.mysql80
+++ b/docker/bootstrap/Dockerfile.mysql80
@@ -4,7 +4,7 @@ ARG image="vitess/bootstrap:${bootstrap_version}-common"
 FROM "${image}"
 
 # Install MySQL 8.0
-RUN for i in $(seq 1 10); do apt-key adv --no-tty --recv-keys --keyserver keyserver.ubuntu.com 8C718D3B5072E1F5 && break; done && \
+RUN for i in $(seq 1 10); do apt-key adv --no-tty --recv-keys --keyserver ha.pool.sks-keyservers.net 8C718D3B5072E1F5 && break; done && \
     add-apt-repository 'deb http://repo.mysql.com/apt/debian/ buster mysql-8.0' && \
     for i in $(seq 1 10); do apt-key adv --no-tty --keyserver keys.gnupg.net --recv-keys 9334A25F8507EFA5 && break; done && \
     echo 'deb http://repo.percona.com/apt buster main' > /etc/apt/sources.list.d/percona.list && \
diff --git a/docker/lite/Dockerfile.ubi7.mysql57 b/docker/lite/Dockerfile.ubi7.mysql57
index 1cbcca6558..a98bda4838 100644
--- a/docker/lite/Dockerfile.ubi7.mysql57
+++ b/docker/lite/Dockerfile.ubi7.mysql57
@@ -38,7 +38,8 @@ FROM registry.access.redhat.com/ubi7/ubi:latest
 # Install keys and dependencies
 RUN mkdir /tmp/gpg && chmod 700 /tmp/gpg && export GNUPGHOME=/tmp/gpg \
  && yum install -y --setopt=alwaysprompt=no gnupg \
- && ( gpg --keyserver keyserver.ubuntu.com --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 A4A9406876FCBD3C456770C88C718D3B5072E1F5 ) \
+ && ( gpg --keyserver keyserver.ubuntu.com --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 A4A9406876FCBD3C456770C88C718D3B5072E1F5 \
+ ||   gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 A4A9406876FCBD3C456770C88C718D3B5072E1F5 ) \
  && gpg --export --armor 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A > ${GNUPGHOME}/RPM-GPG-KEY-Percona.1 \
  && gpg --export --armor 4D1BB29D63D98E422B2113B19334A25F8507EFA5 > ${GNUPGHOME}/RPM-GPG-KEY-Percona.2 \
  && gpg --export --armor 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 > ${GNUPGHOME}/RPM-GPG-KEY-CentOS-7 \
diff --git a/docker/lite/Dockerfile.ubi7.mysql80 b/docker/lite/Dockerfile.ubi7.mysql80
index dcc2c76ab7..fce90776a2 100644
--- a/docker/lite/Dockerfile.ubi7.mysql80
+++ b/docker/lite/Dockerfile.ubi7.mysql80
@@ -38,7 +38,8 @@ FROM registry.access.redhat.com/ubi7/ubi:latest
 # Install keys and dependencies
 RUN mkdir /tmp/gpg && chmod 700 /tmp/gpg && export GNUPGHOME=/tmp/gpg \
  && yum install -y --setopt=alwaysprompt=no gnupg \
- && ( gpg --keyserver keyserver.ubuntu.com --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 A4A9406876FCBD3C456770C88C718D3B5072E1F5 ) \
+ && ( gpg --keyserver keyserver.ubuntu.com --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 A4A9406876FCBD3C456770C88C718D3B5072E1F5 \
+ ||   gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 A4A9406876FCBD3C456770C88C718D3B5072E1F5 ) \
  && gpg --export --armor 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A > ${GNUPGHOME}/RPM-GPG-KEY-Percona.1 \
  && gpg --export --armor 4D1BB29D63D98E422B2113B19334A25F8507EFA5 > ${GNUPGHOME}/RPM-GPG-KEY-Percona.2 \
  && gpg --export --armor 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 > ${GNUPGHOME}/RPM-GPG-KEY-CentOS-7 \
diff --git a/docker/lite/Dockerfile.ubi7.percona57 b/docker/lite/Dockerfile.ubi7.percona57
index 7fd612295c..a8911e23a9 100644
--- a/docker/lite/Dockerfile.ubi7.percona57
+++ b/docker/lite/Dockerfile.ubi7.percona57
@@ -38,7 +38,8 @@ FROM registry.access.redhat.com/ubi7/ubi:latest
 # Install keys and dependencies
 RUN mkdir /tmp/gpg && chmod 700 /tmp/gpg && export GNUPGHOME=/tmp/gpg \
  && yum install -y --setopt=alwaysprompt=no gnupg \
- && ( gpg --keyserver keyserver.ubuntu.com --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 ) \
+ && ( gpg --keyserver keyserver.ubuntu.com --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 \
+ ||   gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 ) \
  && gpg --export --armor 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A > ${GNUPGHOME}/RPM-GPG-KEY-Percona.1 \
  && gpg --export --armor 4D1BB29D63D98E422B2113B19334A25F8507EFA5 > ${GNUPGHOME}/RPM-GPG-KEY-Percona.2 \
  && gpg --export --armor 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 > ${GNUPGHOME}/RPM-GPG-KEY-CentOS-7 \
diff --git a/docker/lite/Dockerfile.ubi7.percona80 b/docker/lite/Dockerfile.ubi7.percona80
index 916843684f..e1ed4c89a1 100644
--- a/docker/lite/Dockerfile.ubi7.percona80
+++ b/docker/lite/Dockerfile.ubi7.percona80
@@ -38,7 +38,8 @@ FROM registry.access.redhat.com/ubi7/ubi:latest
 # Install keys and dependencies
 RUN mkdir /tmp/gpg && chmod 700 /tmp/gpg && export GNUPGHOME=/tmp/gpg \
  && yum install -y --setopt=alwaysprompt=no gnupg \
- && ( gpg --keyserver keyserver.ubuntu.com --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 ) \
+ && ( gpg --keyserver keyserver.ubuntu.com --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 \
+ ||   gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A 4D1BB29D63D98E422B2113B19334A25F8507EFA5 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 ) \
  && gpg --export --armor 430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A > ${GNUPGHOME}/RPM-GPG-KEY-Percona.1 \
  && gpg --export --armor 4D1BB29D63D98E422B2113B19334A25F8507EFA5 > ${GNUPGHOME}/RPM-GPG-KEY-Percona.2 \
  && gpg --export --armor 6341AB2753D78A78A7C27BB124C6A8A7F4A80EB5 > ${GNUPGHOME}/RPM-GPG-KEY-CentOS-7 \
diff --git a/docker/lite/install_dependencies.sh b/docker/lite/install_dependencies.sh
index ac257d871f..347d76767b 100755
--- a/docker/lite/install_dependencies.sh
+++ b/docker/lite/install_dependencies.sh
@@ -11,7 +11,9 @@ FLAVOR="$1"
 export DEBIAN_FRONTEND=noninteractive
 
 KEYSERVERS=(
+    ha.pool.sks-keyservers.net
     keyserver.ubuntu.com
+    hkp://p80.pool.sks-keyservers.net:80
     hkp://keyserver.ubuntu.com:80
 )
 
diff --git a/go.mod b/go.mod
index 89782e04f8..20ca8a320f 100644
--- a/go.mod
+++ b/go.mod
@@ -73,8 +73,8 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/planetscale/pargzip v0.0.0-20201116224723-90c7fc03ea8a
 	github.com/planetscale/vtprotobuf v0.0.0-20210521163914-5a02622d1e2a
-	github.com/prometheus/client_golang v1.11.0
-	github.com/prometheus/common v0.26.0
+	github.com/prometheus/client_golang v1.5.1
+	github.com/prometheus/common v0.9.1
 	github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0
 	github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e
 	github.com/satori/go.uuid v1.2.0 // indirect
@@ -84,7 +84,7 @@ require (
 	github.com/spf13/cobra v1.1.1
 	github.com/spf13/pflag v1.0.5
 	github.com/spyzhov/ajson v0.4.2
-	github.com/stretchr/testify v1.7.0
+	github.com/stretchr/testify v1.6.1
 	github.com/tchap/go-patricia v0.0.0-20160729071656-dd168db6051b
 	github.com/tebeka/selenium v0.9.9
 	github.com/tinylib/msgp v1.1.1 // indirect
@@ -92,19 +92,19 @@ require (
 	github.com/uber/jaeger-client-go v2.16.0+incompatible
 	github.com/uber/jaeger-lib v2.0.0+incompatible // indirect
 	github.com/z-division/go-zookeeper v0.0.0-20190128072838-6d7457066b9b
-	go.etcd.io/etcd/api/v3 v3.5.0
-	go.etcd.io/etcd/client/pkg/v3 v3.5.0
-	go.etcd.io/etcd/client/v3 v3.5.0
+	go.etcd.io/etcd/api/v3 v3.5.0-alpha.0
+	go.etcd.io/etcd/client/v3 v3.5.0-alpha.0
+	go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0
 	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
-	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
-	golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
+	golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5
+	golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
 	golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/text v0.3.5
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0
-	golang.org/x/tools v0.1.2
+	golang.org/x/tools v0.1.0
 	google.golang.org/api v0.45.0
-	google.golang.org/grpc v1.38.0
+	google.golang.org/grpc v1.37.0
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
 	google.golang.org/grpc/examples v0.0.0-20210430044426-28078834f35b
 	google.golang.org/protobuf v1.26.0
@@ -124,5 +124,3 @@ require (
 )
 
 replace github.com/skeema/tengo => github.com/planetscale/tengo v0.9.6-ps.v3
-
-replace google.golang.org/grpc => google.golang.org/grpc v1.37.0
diff --git a/go.sum b/go.sum
index f1b8fbd157..41e5864cac 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,4 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
 cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg=
@@ -24,17 +25,13 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY=
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
@@ -42,7 +39,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20210330150358-dbd898e17899 h1:Cm0cjER/2C+3BEuRBARZ+1HG+jwU5jbVkYysA7zE2H8=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20210330150358-dbd898e17899/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
@@ -50,7 +46,6 @@ github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot
 github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
 github.com/Azure/azure-storage-blob-go v0.10.0 h1:evCwGreYo3XLeBV4vSxLbLiYb6e0SzsJiXQVRGsRXxs=
 github.com/Azure/azure-storage-blob-go v0.10.0/go.mod h1:ep1edmW+kNQx4UfWM9heESNmQdijykocJ0YOxmMX8SE=
-github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
 github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
 github.com/Azure/go-autorest/autorest v0.10.0 h1:mvdtztBqcL8se7MdrUweNieTNi4kfNG6GOJuurQJpuY=
@@ -82,28 +77,20 @@ github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/
 github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
 github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
 github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
-github.com/Masterminds/glide v0.13.2 h1:M5MOH04TyRiMBVeWHbifqTpnauxWINIubTCOkhXh+2g=
 github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
-github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
 github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
-github.com/Masterminds/vcs v1.13.0 h1:USF5TvZGYgIpcbNAEMLfFhHqP08tFZVlUVrmTSpqnyA=
 github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
-github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
-github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
 github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
 github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0=
 github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0=
-github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
 github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
 github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
 github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
@@ -111,18 +98,14 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
 github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
-github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/aquarapid/vaultlib v0.5.1 h1:vuLWR6bZzLHybjJBSUYPgZlIp6KZ+SXeHLRRYTuk6d4=
 github.com/aquarapid/vaultlib v0.5.1/go.mod h1:yT7AlEXtuabkxylOc/+Ulyp18tff1+QjgNLTnFWTlOs=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@@ -130,12 +113,10 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
 github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
 github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
 github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
-github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/aws/aws-sdk-go v1.28.8 h1:kPGnElMdW0GDc54Giy1lcE/3gAr2Gzl6cMjYKoBNFhw=
 github.com/aws/aws-sdk-go v1.28.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@@ -143,49 +124,37 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c h1:+0HFd5KSZ/mm3JmhmrDukiId5iR6w4+BdFtfSy4yWIc=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/buger/jsonparser v0.0.0-20200322175846-f7e751efca13 h1:+qUNY4VRkEH46bLUwxCyUU+iOGJMQBVibAaYzWiwWcg=
 github.com/buger/jsonparser v0.0.0-20200322175846-f7e751efca13/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
-github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY=
 github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
-github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA=
 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
-github.com/codegangsta/cli v1.20.0 h1:iX1FXEgwzd5+XN6wk5cVHOGQj6Q3Dcp20lUeS4lHNTw=
 github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
-github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
-github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
 github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
@@ -193,20 +162,17 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
-github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/go-systemd/v22 v22.1.0 h1:kq/SbG2BCKLkDKkjQf5OWwKWUKj1lgs3lFI4PxnR5lg=
+github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U=
 github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=
-github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
 github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
 github.com/dave/jennifer v1.4.1 h1:XyqG6cn5RQsTj3qlWQTKlRGAyrTcsk1kUmWdZBzRjDw=
@@ -217,26 +183,22 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0 h1:w3NnFcKR5241cfmQU5ZZAsf0xcpId6mWOupTvJlUX2U=
 github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
 github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
 github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
 github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
@@ -249,26 +211,16 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
-github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ=
-github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
-github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
 github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
@@ -276,65 +228,54 @@ github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70t
 github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
-github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI=
 github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
 github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
-github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY=
 github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
 github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
 github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
-github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
 github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
 github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
 github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
 github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
-github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
 github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
 github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
-github.com/go-openapi/loads v0.19.4 h1:5I4CCSqoWzT+82bBkNIvmLc0UOsoKKQ4Fz+3VxOB7SY=
 github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
 github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
 github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
-github.com/go-openapi/runtime v0.19.4 h1:csnOgcgAiuGoM/Po7PEpKDoNulCcF3FGbSnbHfxgjMI=
 github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
 github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
 github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
-github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
 github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
 github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
-github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA=
 github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
 github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
 github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
 github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
 github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
-github.com/go-openapi/validate v0.19.5 h1:QhCBKRYqZR+SKo4gl1lPhPahope8/RLt6EVgY8X80w0=
 github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.1-0.20210202043019-fe2230a8b20c h1:yUT3Ygm3yXBD2qLPxYRDBcnEz0MHgQ4TJ/87C/wKnWA=
 github.com/go-sql-driver/mysql v1.5.1-0.20210202043019-fe2230a8b20c/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
-github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
@@ -345,6 +286,7 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
@@ -389,9 +331,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-github/v27 v27.0.4 h1:N/EEqsvJLgqTbepTiMBz+12KhwLovv6YvwpRezd+4Fg=
 github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0=
-github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
 github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
@@ -411,9 +351,7 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@@ -427,7 +365,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
 github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
-github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
 github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
@@ -440,7 +377,6 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -450,8 +386,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/api v1.5.0 h1:Yo2bneoGy68A7aNwmuETFnPhjyBEm7n3vzRacEVMjvI=
 github.com/hashicorp/consul/api v1.5.0/go.mod h1:LqwrLNW876eYSuUOo4ZLHBcdKc038txr/IMfbLPATa4=
@@ -474,7 +409,6 @@ github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
 github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
 github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
-github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s=
 github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
 github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
 github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
@@ -482,23 +416,18 @@ github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR3
 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
 github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
 github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
-github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/mdns v1.0.1 h1:XFSOubp8KWB+Jd2PDyaX5xUd5bhSP/+pTDZVDMzZJM8=
 github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
@@ -513,7 +442,6 @@ github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJ
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 h1:Mo9W14pwbO9VfRe+ygqZ8dFbPpoIK1HFrG/zjTuQ+nc=
 github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
@@ -528,17 +456,13 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5i
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
 github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
-github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
-github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
-github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -546,13 +470,9 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
-github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
-github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
 github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
@@ -560,16 +480,13 @@ github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE
 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A=
 github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4=
 github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -586,7 +503,6 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN
 github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
 github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
 github.com/martini-contrib/auth v0.0.0-20150219114609-fa62c19b7ae8 h1:1ded5x5QpCLsyTH5ct62Rh1RXPFnn0/dubCqAeh+stU=
 github.com/martini-contrib/auth v0.0.0-20150219114609-fa62c19b7ae8/go.mod h1:ahTFgV/NtzY/CALneRrC67m1dis5arHTQDfyIhKk69E=
@@ -622,7 +538,6 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju
 github.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1 h1:jw16EimP5oAEM/2wt+SiEUov/YDyTCTDuPtIKgQIvk0=
 github.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1/go.mod h1:vuvdOZLJuf5HmJAJrKV64MmozrSsk+or0PB5dzdfspg=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg=
 github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -630,11 +545,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI=
 github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
-github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
-github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc=
 github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -650,20 +562,14 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
 github.com/montanaflynn/stats v0.6.3 h1:F8446DrvIF5V5smZfZ8K9nrmmix0AFgevPdLruGOmzk=
 github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/ngdinhtoan/glide-cleanup v0.2.0 h1:kN4sV+0tp2F1BvwU+5SfNRMDndRmvIfnI3kZ7B8Yv4Y=
 github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
 github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=
 github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
 github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE=
 github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw=
-github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.5-0.20200416053754-163badb3bac6 h1:F721VBMijn0OBFZ5wUSuMVVLQj2IJiiupn6UNd7UbBE=
@@ -690,9 +596,7 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
 github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
-github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
@@ -712,17 +616,14 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
 github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
-github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM=
 github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
-github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA=
+github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -732,33 +633,24 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q
 github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
-github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
-github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
+github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 h1:/NRJ5vAYoqz+7sG51ubIDHXeWO8DlTSrToPu6q11ziA=
 github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
-github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e h1:CGjiMQ0wMH4wtNWrlj6kiTbkPt2F3rbYnhGX6TWLfco=
 github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
@@ -766,14 +658,11 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
-github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sjmudd/stopwatch v0.0.0-20170613150411-f380bf8a9be1 h1:acClJNSOjUrAUKW+ZneCZymCFDWtSaJG5YQl8FoOlyI=
 github.com/sjmudd/stopwatch v0.0.0-20170613150411-f380bf8a9be1/go.mod h1:Pgf1sZ2KrHK8vdRTV5UHGp80LT7HMUKuNAiKC402abY=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -784,18 +673,14 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
-github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
 github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
-github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -803,7 +688,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
-github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/spyzhov/ajson v0.4.2 h1:JMByd/jZApPKDvNsmO90X2WWGbmT2ahDFp73QhZbg3s=
 github.com/spyzhov/ajson v0.4.2/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
@@ -816,23 +700,18 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tchap/go-patricia v0.0.0-20160729071656-dd168db6051b h1:i3lm+BZX5fAaH95wJavMgsSYU95LhSxdNCMa8nLv2gk=
 github.com/tchap/go-patricia v0.0.0-20160729071656-dd168db6051b/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
 github.com/tebeka/selenium v0.9.9 h1:cNziB+etNgyH/7KlNI7RMC1ua5aH1+5wUlFQyzeMh+w=
 github.com/tebeka/selenium v0.9.9/go.mod h1:5Fr8+pUvU6B1OiPfkdCKdXZyr5znvVkxuPd0NOdZCQc=
-github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/tinylib/msgp v1.1.1 h1:TnCZ3FIuKeaIy+F45+Cnp+caqdXGy4z74HvwXN+570Y=
 github.com/tinylib/msgp v1.1.1/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
 github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o=
 github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
@@ -840,42 +719,33 @@ github.com/uber/jaeger-client-go v2.16.0+incompatible h1:Q2Pp6v3QYiocMxomCaJuwQG
 github.com/uber/jaeger-client-go v2.16.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v2.0.0+incompatible h1:iMSCV0rmXEogjNWPh2D0xk9YVKvrtGoHJNe9ebLu/pw=
 github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
-github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
-github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
 github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/z-division/go-zookeeper v0.0.0-20190128072838-6d7457066b9b h1:Itr7GbuXoM1PK/eCeNNia4Qd3ib9IgX9g9SpXgo8BwQ=
 github.com/z-division/go-zookeeper v0.0.0-20190128072838-6d7457066b9b/go.mod h1:JNALoWa+nCXR8SmgLluHcBNVJgyejzpKPZk9pX2yXXE=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
-go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw=
-go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
-go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU=
-go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
-go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
+go.etcd.io/etcd/api/v3 v3.5.0-alpha.0 h1:+e5nrluATIy3GP53znpkHMFzPTHGYyzvJGFCbuI6ZLc=
+go.etcd.io/etcd/api/v3 v3.5.0-alpha.0/go.mod h1:mPcW6aZJukV6Aa81LSKpBjQXTWlXB5r74ymPoSWa3Sw=
+go.etcd.io/etcd/client/v3 v3.5.0-alpha.0 h1:dr1EOILak2pu4Nf5XbRIOCNIBjcz6UmkQd7hHRXwxaM=
+go.etcd.io/etcd/client/v3 v3.5.0-alpha.0/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8=
+go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0 h1:3yLUEC0nFCxw/RArImOyRUI4OAFbg4PFpBbAhSNzKNY=
+go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0/go.mod h1:tV31atvwzcybuqejDoY3oaNRTtlD2l/Ot78Pc9w7DMY=
 go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
-go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA=
 go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -887,14 +757,16 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
-go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
-go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
-go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
+go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -923,11 +795,10 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -937,11 +808,9 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
@@ -950,12 +819,12 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -978,6 +847,7 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -999,9 +869,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1029,6 +898,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cO
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1057,7 +927,6 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1072,27 +941,20 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog=
 golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
-golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1112,6 +974,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -1133,6 +996,8 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1166,18 +1031,15 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
-golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485 h1:OB/uP/Puiu5vS5QMRPrXCDWUPb+kt8f1KW8oQzFejQw=
 gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
-gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e h1:jRyg0XfpwWlhEV8mDfdNGBeSJM2fuyh9Yjrnd8kF2Ts=
 gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -1253,9 +1115,28 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384 h1:z+j74wi4yV+P7EtK9gPLGukOk7mFOy9wMQaC0wNb7eY=
 google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
-google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
@@ -1285,9 +1166,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
-gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@@ -1300,11 +1179,8 @@ gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ldap.v2 v2.5.0 h1:1rO3ojzsHUk+gq4ZYhC4Pg+EzWaaKIV8+DJwExS5/QQ=
 gopkg.in/ldap.v2 v2.5.0/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
-gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
-gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
 gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -1317,12 +1193,10 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1339,13 +1213,11 @@ k8s.io/apiextensions-apiserver v0.17.3 h1:WDZWkPcbgvchEdDd7ysL21GGPx3UKZQLDZXEke
 k8s.io/apiextensions-apiserver v0.17.3/go.mod h1:CJbCyMfkKftAd/X/V6OTHYhVn7zXnDdnkUjS1h0GTeY=
 k8s.io/apimachinery v0.17.3 h1:f+uZV6rm4/tHE7xXgLyToprg6xWairaClGVkm2t8omg=
 k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
-k8s.io/apiserver v0.17.3 h1:faZbSuFtJ4dx09vctKZGHms/7bp3qFtbqb10Swswqfs=
 k8s.io/apiserver v0.17.3/go.mod h1:iJtsPpu1ZpEnHaNawpSV0nYTGBhhX2dUlnn7/QS7QiY=
 k8s.io/client-go v0.17.3 h1:deUna1Ksx05XeESH6XGCyONNFfiQmDdqeqUvicvP6nU=
 k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ=
 k8s.io/code-generator v0.17.3 h1:q/hDMk2cvFzSxol7k/VA1qCssR7VSMXHQHhzuX29VJ8=
 k8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=
-k8s.io/component-base v0.17.3 h1:hQzTSshY14aLSR6WGIYvmw+w+u6V4d+iDR2iDGMrlUg=
 k8s.io/component-base v0.17.3/go.mod h1:GeQf4BrgelWm64PXkIXiPh/XS0hnO42d9gx9BtbZRp8=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20190822140433-26a664648505 h1:ZY6yclUKVbZ+SdWnkfY+Je5vrMpKOxmGeKRbsXVmqYM=
@@ -1358,24 +1230,15 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLy
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
-modernc.org/cc v1.0.0 h1:nPibNuDEx6tvYrUAtvDTTw98rx5juGsa5zuDnKwEEQQ=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
-modernc.org/golex v1.0.0 h1:wWpDlbK8ejRfSyi0frMyhilD3JBvtcx2AdGDnU+JtsE=
 modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
-modernc.org/mathutil v1.0.0 h1:93vKjrJopTPrtTNpZ8XIovER7iCIH1QU7wNbOQXC60I=
 modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
-modernc.org/strutil v1.0.0 h1:XVFtQwFVwc02Wk+0L/Z/zDDXO81r5Lhe6iMKmGX3KhE=
 modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
-modernc.org/xc v1.0.0 h1:7ccXrupWZIS3twbUGrtKmHS2DXY6xegFua+6O3xgAFU=
 modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
-rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
-sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
 sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
diff --git a/go/vt/discovery/replicationlag.go b/go/vt/discovery/replicationlag.go
index a78765e1d4..4840bd6205 100644
--- a/go/vt/discovery/replicationlag.go
+++ b/go/vt/discovery/replicationlag.go
@@ -31,36 +31,6 @@ var (
 	legacyReplicationLagAlgorithm = flag.Bool("legacy_replication_lag_algorithm", true, "use the legacy algorithm when selecting the vttablets for serving")
 )
 
-// GetLowReplicationLag getter for use by debugenv
-func GetLowReplicationLag() time.Duration {
-	return *lowReplicationLag
-}
-
-// SetLowReplicationLag setter for use by debugenv
-func SetLowReplicationLag(lag time.Duration) {
-	lowReplicationLag = &lag
-}
-
-// GetHighReplicationLagMinServing getter for use by debugenv
-func GetHighReplicationLagMinServing() time.Duration {
-	return *highReplicationLagMinServing
-}
-
-// SetHighReplicationLagMinServing setter for use by debugenv
-func SetHighReplicationLagMinServing(lag time.Duration) {
-	highReplicationLagMinServing = &lag
-}
-
-// GetMinNumTablets getter for use by debugenv
-func GetMinNumTablets() int {
-	return *minNumTablets
-}
-
-// SetMinNumTablets setter for use by debugenv
-func SetMinNumTablets(numTablets int) {
-	minNumTablets = &numTablets
-}
-
 // IsReplicationLagHigh verifies that the given LegacytabletHealth refers to a tablet with high
 // replication lag, i.e. higher than the configured discovery_low_replication_lag flag.
 func IsReplicationLagHigh(tabletHealth *TabletHealth) bool {
diff --git a/go/vt/sqlparser/analyzer.go b/go/vt/sqlparser/analyzer.go
index 29c62ca68c..f06dc5485c 100644
--- a/go/vt/sqlparser/analyzer.go
+++ b/go/vt/sqlparser/analyzer.go
@@ -323,6 +323,35 @@ func SplitAndExpression(filters []Expr, node Expr) []Expr {
 	return append(filters, node)
 }
 
+// AndExpressions ands together two expression, minimising the expr when possible
+func AndExpressions(exprs ...Expr) Expr {
+	switch len(exprs) {
+	case 0:
+		return nil
+	case 1:
+		return exprs[0]
+	default:
+		result := (Expr)(nil)
+		for i, expr := range exprs {
+			if result == nil {
+				result = expr
+			} else {
+				found := false
+				for j := 0; j < i; j++ {
+					if EqualsExpr(expr, exprs[j]) {
+						found = true
+						break
+					}
+				}
+				if !found {
+					result = &AndExpr{Left: result, Right: expr}
+				}
+			}
+		}
+		return result
+	}
+}
+
 // TableFromStatement returns the qualified table name for the query.
 // This works only for select statements.
 func TableFromStatement(sql string) (TableName, error) {
diff --git a/go/vt/topo/etcd2topo/server.go b/go/vt/topo/etcd2topo/server.go
index 062fe0bcec..aeb7c4691a 100644
--- a/go/vt/topo/etcd2topo/server.go
+++ b/go/vt/topo/etcd2topo/server.go
@@ -42,8 +42,8 @@ import (
 
 	"google.golang.org/grpc"
 
-	"go.etcd.io/etcd/client/pkg/v3/tlsutil"
 	clientv3 "go.etcd.io/etcd/client/v3"
+	"go.etcd.io/etcd/pkg/v3/tlsutil"
 
 	"vitess.io/vitess/go/vt/topo"
 )
diff --git a/go/vt/vtadmin/vtctldclient/proxy.go b/go/vt/vtadmin/vtctldclient/proxy.go
index 4626d25d28..fce4c78676 100644
--- a/go/vt/vtadmin/vtctldclient/proxy.go
+++ b/go/vt/vtadmin/vtctldclient/proxy.go
@@ -25,7 +25,6 @@ import (
 	"vitess.io/vitess/go/trace"
 	"vitess.io/vitess/go/vt/grpcclient"
 	"vitess.io/vitess/go/vt/vtadmin/cluster/discovery"
-	"vitess.io/vitess/go/vt/vtadmin/vtadminproto"
 	"vitess.io/vitess/go/vt/vtctl/grpcvtctldclient"
 	"vitess.io/vitess/go/vt/vtctl/vtctldclient"
 
@@ -91,12 +90,9 @@ func (vtctld *ClientProxy) Dial(ctx context.Context) error {
 	span, ctx := trace.NewSpan(ctx, "VtctldClientProxy.Dial")
 	defer span.Finish()
 
-	vtadminproto.AnnotateClusterSpan(vtctld.cluster, span)
-
 	if vtctld.VtctldClient != nil {
 		if !vtctld.closed {
 			span.Annotate("is_noop", true)
-			span.Annotate("vtctld_host", vtctld.host)
 
 			return nil
 		}
diff --git a/go/vt/vtgate/debugenv.go b/go/vt/vtgate/debugenv.go
deleted file mode 100644
index 6fce306bf3..0000000000
--- a/go/vt/vtgate/debugenv.go
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
-Copyright 2021 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 vtgate
-
-import (
-	"encoding/json"
-	"fmt"
-	"html"
-	"net/http"
-	"strconv"
-	"text/template"
-	"time"
-
-	"vitess.io/vitess/go/acl"
-	"vitess.io/vitess/go/vt/discovery"
-	"vitess.io/vitess/go/vt/log"
-)
-
-var (
-	debugEnvHeader = []byte(`
-	<thead><tr>
-		<th>Variable Name</th>
-		<th>Value</th>
-		<th>Action</th>
-	</tr></thead>
-	`)
-	debugEnvRow = template.Must(template.New("debugenv").Parse(`
-	<tr><form method="POST">
-		<td>{{.VarName}}</td>
-		<td>
-			<input type="hidden" name="varname" value="{{.VarName}}"></input>
-			<input type="text" name="value" value="{{.Value}}"></input>
-		</td>
-		<td><input type="submit" name="Action" value="Modify"></input></td>
-	</form></tr>
-	`))
-	gridTable = []byte(`<!DOCTYPE html>
-	<style type="text/css">
-			table.gridtable {
-				font-family: verdana,arial,sans-serif;
-				font-size: 11px;
-				border-width: 1px;
-				border-collapse: collapse; table-layout:fixed; overflow: hidden;
-			}
-			table.gridtable th {
-				border-width: 1px;
-				padding: 8px;
-				border-style: solid;
-				background-color: #dedede;
-				white-space: nowrap;
-			}
-			table.gridtable td {
-				border-width: 1px;
-				padding: 5px;
-				border-style: solid;
-			}
-			table.gridtable th {
-				padding-left: 2em;
-				padding-right: 2em;
-			}
-	</style>
-	`)
-	startTable = []byte(`
-	<table class="gridtable">
-	`)
-	endTable = []byte(`
-	</table>
-	`)
-)
-
-type envValue struct {
-	VarName string
-	Value   string
-}
-
-func debugEnvHandler(vtg *VTGate, w http.ResponseWriter, r *http.Request) {
-	if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil {
-		acl.SendError(w, err)
-		return
-	}
-
-	var msg string
-	if r.Method == "POST" {
-		varname := r.FormValue("varname")
-		value := r.FormValue("value")
-		setIntVal := func(f func(int)) {
-			ival, err := strconv.Atoi(value)
-			if err != nil {
-				msg = fmt.Sprintf("Failed setting value for %v: %v", varname, err)
-				return
-			}
-			f(ival)
-			msg = fmt.Sprintf("Setting %v to: %v", varname, value)
-		}
-		setDurationVal := func(f func(time.Duration)) {
-			durationVal, err := time.ParseDuration(value)
-			if err != nil {
-				msg = fmt.Sprintf("Failed setting value for %v: %v", varname, err)
-				return
-			}
-			f(durationVal)
-			msg = fmt.Sprintf("Setting %v to: %v", varname, value)
-		}
-		switch varname {
-		case "discovery_low_replication_lag":
-			setDurationVal(discovery.SetLowReplicationLag)
-		case "discovery_high_replication_lag_minimum_serving":
-			setDurationVal(discovery.SetHighReplicationLagMinServing)
-		case "min_num_tablets":
-			setIntVal(discovery.SetMinNumTablets)
-		}
-	}
-
-	var vars []envValue
-	addIntVar := func(varname string, f func() int) {
-		vars = append(vars, envValue{
-			VarName: varname,
-			Value:   fmt.Sprintf("%v", f()),
-		})
-	}
-	addDurationVar := func(varname string, f func() time.Duration) {
-		vars = append(vars, envValue{
-			VarName: varname,
-			Value:   fmt.Sprintf("%v", f()),
-		})
-	}
-	addDurationVar("discovery_low_replication_lag", discovery.GetLowReplicationLag)
-	addDurationVar("discovery_high_replication_lag_minimum_serving", discovery.GetHighReplicationLagMinServing)
-	addIntVar("min_num_tablets", discovery.GetMinNumTablets)
-
-	format := r.FormValue("format")
-	if format == "json" {
-		mvars := make(map[string]string)
-		for _, v := range vars {
-			mvars[v.VarName] = v.Value
-		}
-		w.Header().Set("Content-Type", "application/json")
-		_ = json.NewEncoder(w).Encode(mvars)
-		return
-	}
-
-	w.Write(gridTable)
-	w.Write([]byte("<h3>Internal Variables</h3>\n"))
-	if msg != "" {
-		w.Write([]byte(fmt.Sprintf("<b>%s</b><br /><br />\n", html.EscapeString(msg))))
-	}
-	w.Write(startTable)
-	w.Write(debugEnvHeader)
-	for _, v := range vars {
-		if err := debugEnvRow.Execute(w, v); err != nil {
-			log.Errorf("debugenv: couldn't execute template: %v", err)
-		}
-	}
-	w.Write(endTable)
-}
diff --git a/go/vt/vtgate/engine/join.go b/go/vt/vtgate/engine/join.go
index 22b472ff47..cbdb8d5815 100644
--- a/go/vt/vtgate/engine/join.go
+++ b/go/vt/vtgate/engine/join.go
@@ -206,12 +206,12 @@ type JoinOpcode int
 
 // This is the list of JoinOpcode values.
 const (
-	NormalJoin = JoinOpcode(iota)
+	InnerJoin = JoinOpcode(iota)
 	LeftJoin
 )
 
 func (code JoinOpcode) String() string {
-	if code == NormalJoin {
+	if code == InnerJoin {
 		return "Join"
 	}
 	return "LeftJoin"
diff --git a/go/vt/vtgate/executor_scatter_stats.go b/go/vt/vtgate/executor_scatter_stats.go
index 2c5e8e8960..74641f11de 100644
--- a/go/vt/vtgate/executor_scatter_stats.go
+++ b/go/vt/vtgate/executor_scatter_stats.go
@@ -134,7 +134,7 @@ func (e *Executor) WriteScatterStats(w http.ResponseWriter) {
 	}
 
 	t := template.New("template")
-	t, err = t.Parse(statsHTML)
+	t, err = t.Parse(html)
 	if err != nil {
 		http.Error(w, err.Error(), 500)
 		return
@@ -153,7 +153,7 @@ func (e *Executor) WriteScatterStats(w http.ResponseWriter) {
 
 }
 
-const statsHTML = `
+const html = `
 <thead>
 	<tr>
 		<th>Query</th>
diff --git a/go/vt/vtgate/planbuilder/fuzz.go b/go/vt/vtgate/planbuilder/abstract/fuzz.go
similarity index 96%
rename from go/vt/vtgate/planbuilder/fuzz.go
rename to go/vt/vtgate/planbuilder/abstract/fuzz.go
index ff8d496831..0a032ade2a 100644
--- a/go/vt/vtgate/planbuilder/fuzz.go
+++ b/go/vt/vtgate/planbuilder/abstract/fuzz.go
@@ -15,7 +15,7 @@ limitations under the License.
 */
 // +build gofuzz
 
-package planbuilder
+package abstract
 
 import (
 	"fmt"
@@ -54,7 +54,8 @@ func FuzzAnalyse(data []byte) int {
 		if err != nil {
 			return 0
 		}
-		_, _ = createQGFromSelect(stmt, semTable)
+		_, _ = CreateOperatorFromSelect(stmt, semTable)
+
 	default:
 		return 0
 	}
diff --git a/go/vt/vtgate/planbuilder/fuzzdata/clusterfuzz-testcase-planbuilder_fuzzer-1 b/go/vt/vtgate/planbuilder/abstract/fuzzdata/clusterfuzz-testcase-planbuilder_fuzzer-1
similarity index 100%
rename from go/vt/vtgate/planbuilder/fuzzdata/clusterfuzz-testcase-planbuilder_fuzzer-1
rename to go/vt/vtgate/planbuilder/abstract/fuzzdata/clusterfuzz-testcase-planbuilder_fuzzer-1
diff --git a/go/vt/vtgate/planbuilder/fuzzdata/clusterfuzz-testcase-planbuilder_fuzzer-5577761986052096 b/go/vt/vtgate/planbuilder/abstract/fuzzdata/clusterfuzz-testcase-planbuilder_fuzzer-5577761986052096
similarity index 100%
rename from go/vt/vtgate/planbuilder/fuzzdata/clusterfuzz-testcase-planbuilder_fuzzer-5577761986052096
rename to go/vt/vtgate/planbuilder/abstract/fuzzdata/clusterfuzz-testcase-planbuilder_fuzzer-5577761986052096
diff --git a/go/vt/vtgate/planbuilder/abstract/join.go b/go/vt/vtgate/planbuilder/abstract/join.go
new file mode 100644
index 0000000000..1ca5586800
--- /dev/null
+++ b/go/vt/vtgate/planbuilder/abstract/join.go
@@ -0,0 +1,49 @@
+/*
+Copyright 2021 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 abstract
+
+import (
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vtgate/semantics"
+)
+
+// Join represents an join. If we have a predicate, this is an inner join. If no predicate exists, it is a cross join
+type Join struct {
+	LHS, RHS Operator
+	Exp      sqlparser.Expr
+}
+
+// PushPredicate implements the Operator interface
+func (j *Join) PushPredicate(expr sqlparser.Expr, semTable *semantics.SemTable) error {
+	deps := semTable.Dependencies(expr)
+	switch {
+	case deps.IsSolvedBy(j.LHS.TableID()):
+		return j.LHS.PushPredicate(expr, semTable)
+	case deps.IsSolvedBy(j.RHS.TableID()):
+		return j.RHS.PushPredicate(expr, semTable)
+	case deps.IsSolvedBy(j.LHS.TableID().Merge(j.RHS.TableID())):
+		j.Exp = sqlparser.AndExpressions(j.Exp, expr)
+		return nil
+	}
+
+	return semantics.Gen4NotSupportedF("still not sure what to do with this predicate")
+}
+
+// TableID implements the Operator interface
+func (j *Join) TableID() semantics.TableSet {
+	return j.RHS.TableID().Merge(j.LHS.TableID())
+}
diff --git a/go/vt/vtgate/planbuilder/abstract/operator.go b/go/vt/vtgate/planbuilder/abstract/operator.go
new file mode 100644
index 0000000000..137a19394d
--- /dev/null
+++ b/go/vt/vtgate/planbuilder/abstract/operator.go
@@ -0,0 +1,145 @@
+/*
+Copyright 2021 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 abstract
+
+import (
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vtgate/semantics"
+)
+
+type (
+	// Operator forms the tree of operators, representing the declarative query provided.
+	// An operator can be:
+	//  *  QueryGraph - which represents a group of tables and predicates that can be evaluated in any order
+	//     while still preserving the results
+	//	*  LeftJoin - A left join. These can't be evaluated in any order, so we keep them separate
+	//	*  Join - A join represents inner join.
+	Operator interface {
+		// TableID returns a TableSet of the tables contained within
+		TableID() semantics.TableSet
+
+		// PushPredicate pushes a predicate to the closest possible operator
+		PushPredicate(expr sqlparser.Expr, semTable *semantics.SemTable) error
+	}
+)
+
+func getOperatorFromTableExpr(tableExpr sqlparser.TableExpr, semTable *semantics.SemTable) (Operator, error) {
+	switch tableExpr := tableExpr.(type) {
+	case *sqlparser.AliasedTableExpr:
+		qg := newQueryGraph()
+		tableName := tableExpr.Expr.(sqlparser.TableName)
+		qt := &QueryTable{Alias: tableExpr, Table: tableName, TableID: semTable.TableSetFor(tableExpr)}
+		qg.Tables = append(qg.Tables, qt)
+		return qg, nil
+	case *sqlparser.JoinTableExpr:
+		switch tableExpr.Join {
+		case sqlparser.NormalJoinType:
+			lhs, err := getOperatorFromTableExpr(tableExpr.LeftExpr, semTable)
+			if err != nil {
+				return nil, err
+			}
+			rhs, err := getOperatorFromTableExpr(tableExpr.RightExpr, semTable)
+			if err != nil {
+				return nil, err
+			}
+			op := createJoin(lhs, rhs)
+			err = op.PushPredicate(tableExpr.Condition.On, semTable)
+			if err != nil {
+				return nil, err
+			}
+			return op, nil
+		case sqlparser.LeftJoinType, sqlparser.RightJoinType:
+			inner, err := getOperatorFromTableExpr(tableExpr.LeftExpr, semTable)
+			if err != nil {
+				return nil, err
+			}
+			outer, err := getOperatorFromTableExpr(tableExpr.RightExpr, semTable)
+			if err != nil {
+				return nil, err
+			}
+			if tableExpr.Join == sqlparser.RightJoinType {
+				inner, outer = outer, inner
+			}
+			op := &LeftJoin{
+				Left:      inner,
+				Right:     outer,
+				Predicate: tableExpr.Condition.On,
+			}
+			return op, nil
+		default:
+			return nil, semantics.Gen4NotSupportedF("%s joins", tableExpr.Join.ToString())
+		}
+	case *sqlparser.ParenTableExpr:
+		return crossJoin(tableExpr.Exprs, semTable)
+	default:
+		return nil, semantics.Gen4NotSupportedF("%T table type", tableExpr)
+	}
+}
+
+func crossJoin(exprs sqlparser.TableExprs, semTable *semantics.SemTable) (Operator, error) {
+	var output Operator
+	for _, tableExpr := range exprs {
+		op, err := getOperatorFromTableExpr(tableExpr, semTable)
+		if err != nil {
+			return nil, err
+		}
+		if output == nil {
+			output = op
+		} else {
+			output = createJoin(output, op)
+		}
+	}
+	return output, nil
+}
+
+// CreateOperatorFromSelect creates an operator tree that represents the input SELECT query
+func CreateOperatorFromSelect(sel *sqlparser.Select, semTable *semantics.SemTable) (Operator, error) {
+	op, err := crossJoin(sel.From, semTable)
+	if err != nil {
+		return nil, err
+	}
+	if sel.Where != nil {
+		exprs := sqlparser.SplitAndExpression(nil, sel.Where.Expr)
+		for _, expr := range exprs {
+			err := op.PushPredicate(expr, semTable)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	return op, nil
+}
+
+func createJoin(LHS, RHS Operator) Operator {
+	lqg, lok := LHS.(*QueryGraph)
+	rqg, rok := RHS.(*QueryGraph)
+	if lok && rok {
+		op := &QueryGraph{
+			Tables:     append(lqg.Tables, rqg.Tables...),
+			innerJoins: map[semantics.TableSet][]sqlparser.Expr{},
+			NoDeps:     sqlparser.AndExpressions(lqg.NoDeps, rqg.NoDeps),
+		}
+		for k, v := range lqg.innerJoins {
+			op.innerJoins[k] = v
+		}
+		for k, v := range rqg.innerJoins {
+			op.innerJoins[k] = v
+		}
+		return op
+	}
+	return &Join{LHS: LHS, RHS: RHS}
+}
diff --git a/go/vt/vtgate/planbuilder/abstract/outerjoin.go b/go/vt/vtgate/planbuilder/abstract/outerjoin.go
new file mode 100644
index 0000000000..ba75e40c83
--- /dev/null
+++ b/go/vt/vtgate/planbuilder/abstract/outerjoin.go
@@ -0,0 +1,43 @@
+/*
+Copyright 2021 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 abstract
+
+import (
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vtgate/semantics"
+)
+
+// LeftJoin represents an outerjoin.
+type LeftJoin struct {
+	Left, Right Operator
+	Predicate   sqlparser.Expr
+}
+
+// PushPredicate implements the Operator interface
+func (oj *LeftJoin) PushPredicate(expr sqlparser.Expr, semTable *semantics.SemTable) error {
+	deps := semTable.Dependencies(expr)
+	if deps.IsSolvedBy(oj.Left.TableID()) {
+		return oj.Left.PushPredicate(expr, semTable)
+	}
+
+	return semantics.Gen4NotSupportedF("cannot push predicates to the RHS of an outer join")
+}
+
+// TableID implements the Operator interface
+func (oj *LeftJoin) TableID() semantics.TableSet {
+	return oj.Right.TableID().Merge(oj.Left.TableID())
+}
diff --git a/go/vt/vtgate/planbuilder/abstract/querygraph.go b/go/vt/vtgate/planbuilder/abstract/querygraph.go
new file mode 100644
index 0000000000..5d75d1f472
--- /dev/null
+++ b/go/vt/vtgate/planbuilder/abstract/querygraph.go
@@ -0,0 +1,217 @@
+/*
+Copyright 2021 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 abstract
+
+import (
+	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/semantics"
+)
+
+type (
+	/*QueryGraph represents the FROM and WHERE parts of a query.
+	It is an intermediate representation of the query that makes it easier for the planner
+	to find all possible join combinations. Instead of storing the query information in a form that is close
+	to the syntax (AST), we extract the interesting parts into a graph form with the nodes being tables in the FROM
+	clause and the edges between them being predicates. We keep predicates in a hash map keyed by the dependencies of
+	the predicate. This makes it very fast to look up connections between tables in the query.
+	*/
+	QueryGraph struct {
+		// the Tables, including predicates that only depend on this particular table
+		Tables []*QueryTable
+
+		// innerJoins contains the predicates that need multiple Tables
+		innerJoins map[semantics.TableSet][]sqlparser.Expr
+
+		// NoDeps contains the predicates that can be evaluated anywhere.
+		NoDeps sqlparser.Expr
+	}
+
+	// QueryTable is a single FROM table, including all predicates particular to this table
+	QueryTable struct {
+		TableID    semantics.TableSet
+		Alias      *sqlparser.AliasedTableExpr
+		Table      sqlparser.TableName
+		Predicates []sqlparser.Expr
+	}
+)
+
+// PushPredicate implements the Operator interface
+func (qg *QueryGraph) PushPredicate(expr sqlparser.Expr, semTable *semantics.SemTable) error {
+	for _, e := range sqlparser.SplitAndExpression(nil, expr) {
+		err := qg.collectPredicate(e, semTable)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// TableID implements the Operator interface
+func (qg *QueryGraph) TableID() semantics.TableSet {
+	var ts semantics.TableSet
+	for _, table := range qg.Tables {
+		ts = ts.Merge(table.TableID)
+	}
+	return ts
+}
+
+// GetPredicates returns the predicates that are applicable for the two given TableSets
+func (qg *QueryGraph) GetPredicates(lhs, rhs semantics.TableSet) []sqlparser.Expr {
+	var allExprs []sqlparser.Expr
+	for tableSet, exprs := range qg.innerJoins {
+		if tableSet.IsSolvedBy(lhs|rhs) &&
+			tableSet.IsOverlapping(rhs) &&
+			tableSet.IsOverlapping(lhs) {
+			allExprs = append(allExprs, exprs...)
+		}
+	}
+	return allExprs
+}
+
+func newQueryGraph() *QueryGraph {
+	return &QueryGraph{
+		innerJoins: map[semantics.TableSet][]sqlparser.Expr{},
+	}
+}
+
+func (qg *QueryGraph) collectTable(t sqlparser.TableExpr, semTable *semantics.SemTable) error {
+	switch table := t.(type) {
+	case *sqlparser.AliasedTableExpr:
+		tableName := table.Expr.(sqlparser.TableName)
+		qt := &QueryTable{Alias: table, Table: tableName, TableID: semTable.TableSetFor(table)}
+		qg.Tables = append(qg.Tables, qt)
+	case *sqlparser.JoinTableExpr:
+		if err := qg.collectTable(table.LeftExpr, semTable); err != nil {
+			return err
+		}
+		if err := qg.collectTable(table.RightExpr, semTable); err != nil {
+			return err
+		}
+		if table.Condition.On != nil {
+			for _, predicate := range sqlparser.SplitAndExpression(nil, table.Condition.On) {
+				err := qg.collectPredicateTable(t, predicate, semTable)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	case *sqlparser.ParenTableExpr:
+		for _, expr := range table.Exprs {
+			if err := qg.collectTable(expr, semTable); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (qg *QueryGraph) collectTables(t sqlparser.TableExprs, semTable *semantics.SemTable) error {
+	for _, expr := range t {
+		if err := qg.collectTable(expr, semTable); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (qg *QueryGraph) collectPredicates(sel *sqlparser.Select, semTable *semantics.SemTable) error {
+	predicates := sqlparser.SplitAndExpression(nil, sel.Where.Expr)
+
+	for _, predicate := range predicates {
+		err := qg.collectPredicate(predicate, semTable)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (qg *QueryGraph) collectPredicateTable(t sqlparser.TableExpr, predicate sqlparser.Expr, semTable *semantics.SemTable) error {
+	deps := semTable.Dependencies(predicate)
+	switch deps.NumberOfTables() {
+	case 0:
+		qg.addNoDepsPredicate(predicate)
+	case 1:
+		found := qg.addToSingleTable(deps, predicate)
+		if !found {
+			return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "table %v for predicate %v not found", deps, sqlparser.String(predicate))
+		}
+	default:
+		switch table := t.(type) {
+		case *sqlparser.JoinTableExpr:
+			switch table.Join {
+			case sqlparser.NormalJoinType:
+				allPredicates, found := qg.innerJoins[deps]
+				if found {
+					allPredicates = append(allPredicates, predicate)
+				} else {
+					allPredicates = []sqlparser.Expr{predicate}
+				}
+				qg.innerJoins[deps] = allPredicates
+			case sqlparser.LeftJoinType, sqlparser.RightJoinType:
+				break
+			}
+		}
+	}
+
+	return nil
+}
+
+func (qg *QueryGraph) collectPredicate(predicate sqlparser.Expr, semTable *semantics.SemTable) error {
+	deps := semTable.Dependencies(predicate)
+	switch deps.NumberOfTables() {
+	case 0:
+		qg.addNoDepsPredicate(predicate)
+	case 1:
+		found := qg.addToSingleTable(deps, predicate)
+		if !found {
+			return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "table %v for predicate %v not found", deps, sqlparser.String(predicate))
+		}
+	default:
+		allPredicates, found := qg.innerJoins[deps]
+		if found {
+			allPredicates = append(allPredicates, predicate)
+		} else {
+			allPredicates = []sqlparser.Expr{predicate}
+		}
+		qg.innerJoins[deps] = allPredicates
+	}
+	return nil
+}
+
+func (qg *QueryGraph) addToSingleTable(table semantics.TableSet, predicate sqlparser.Expr) bool {
+	for _, t := range qg.Tables {
+		if table == t.TableID {
+			t.Predicates = append(t.Predicates, predicate)
+			return true
+		}
+	}
+	return false
+}
+
+func (qg *QueryGraph) addNoDepsPredicate(predicate sqlparser.Expr) {
+	if qg.NoDeps == nil {
+		qg.NoDeps = predicate
+	} else {
+		qg.NoDeps = &sqlparser.AndExpr{
+			Left:  qg.NoDeps,
+			Right: predicate,
+		}
+	}
+}
diff --git a/go/vt/vtgate/planbuilder/dml.go b/go/vt/vtgate/planbuilder/dml.go
index cbd1654273..06d87df428 100644
--- a/go/vt/vtgate/planbuilder/dml.go
+++ b/go/vt/vtgate/planbuilder/dml.go
@@ -65,7 +65,7 @@ func getDMLRouting(where *sqlparser.Where, table *vindexes.Table) (engine.DMLOpc
 // constraint on the specified column that can be used to
 // decide on a route.
 func getMatch(node sqlparser.Expr, col sqlparser.ColIdent) (pv sqltypes.PlanValue, ok bool) {
-	filters := splitAndExpression(nil, node)
+	filters := sqlparser.SplitAndExpression(nil, node)
 	for _, filter := range filters {
 		comparison, ok := filter.(*sqlparser.ComparisonExpr)
 		if !ok {
diff --git a/go/vt/vtgate/planbuilder/expr.go b/go/vt/vtgate/planbuilder/expr.go
index 5adbf2c4fa..82ed900864 100644
--- a/go/vt/vtgate/planbuilder/expr.go
+++ b/go/vt/vtgate/planbuilder/expr.go
@@ -25,21 +25,6 @@ import (
 	"vitess.io/vitess/go/vt/vtgate/engine"
 )
 
-// splitAndExpression breaks up the Expr into AND-separated conditions
-// and appends them to filters, which can be shuffled and recombined
-// as needed.
-func splitAndExpression(filters []sqlparser.Expr, node sqlparser.Expr) []sqlparser.Expr {
-	if node == nil {
-		return filters
-	}
-	switch node := node.(type) {
-	case *sqlparser.AndExpr:
-		filters = splitAndExpression(filters, node.Left)
-		return splitAndExpression(filters, node.Right)
-	}
-	return append(filters, node)
-}
-
 type subqueryInfo struct {
 	ast    *sqlparser.Subquery
 	plan   logicalPlan
diff --git a/go/vt/vtgate/planbuilder/from.go b/go/vt/vtgate/planbuilder/from.go
index f1b4ae5905..026010585a 100644
--- a/go/vt/vtgate/planbuilder/from.go
+++ b/go/vt/vtgate/planbuilder/from.go
@@ -356,7 +356,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) {
+	for _, filter := range sqlparser.SplitAndExpression(nil, ajoin.Condition.On) {
 		lRoute.UpdatePlan(pb, filter)
 	}
 	return nil
diff --git a/go/vt/vtgate/planbuilder/join.go b/go/vt/vtgate/planbuilder/join.go
index f8bcbabeb2..3da977b2c1 100644
--- a/go/vt/vtgate/planbuilder/join.go
+++ b/go/vt/vtgate/planbuilder/join.go
@@ -81,7 +81,7 @@ func newJoin(lpb, rpb *primitiveBuilder, ajoin *sqlparser.JoinTableExpr, reserve
 	// external references, and the FROM clause doesn't allow duplicates,
 	// it's safe to perform this conversion and still expect the same behavior.
 
-	opcode := engine.NormalJoin
+	opcode := engine.InnerJoin
 	if ajoin != nil {
 		switch {
 		case ajoin.Join == sqlparser.LeftJoinType:
diff --git a/go/vt/vtgate/planbuilder/joinGen4.go b/go/vt/vtgate/planbuilder/joinGen4.go
index 5f406f517d..67f26ff341 100644
--- a/go/vt/vtgate/planbuilder/joinGen4.go
+++ b/go/vt/vtgate/planbuilder/joinGen4.go
@@ -29,6 +29,7 @@ var _ logicalPlan = (*joinGen4)(nil)
 type joinGen4 struct {
 	// Left and Right are the nodes for the join.
 	Left, Right logicalPlan
+	Opcode      engine.JoinOpcode
 	Cols        []int
 	Vars        map[string]int
 }
@@ -53,7 +54,7 @@ func (j *joinGen4) Wireup(lp logicalPlan, jt *jointab) error {
 	panic("implement me")
 }
 
-// Wireup2 implements the logicalPlan interface
+// WireupGen4 implements the logicalPlan interface
 func (j *joinGen4) WireupGen4(semTable *semantics.SemTable) error {
 	err := j.Left.WireupGen4(semTable)
 	if err != nil {
@@ -80,10 +81,11 @@ func (j *joinGen4) SupplyWeightString(colNumber int) (weightcolNumber int, err e
 // Primitive implements the logicalPlan interface
 func (j *joinGen4) Primitive() engine.Primitive {
 	return &engine.Join{
-		Left:  j.Left.Primitive(),
-		Right: j.Right.Primitive(),
-		Cols:  j.Cols,
-		Vars:  j.Vars,
+		Left:   j.Left.Primitive(),
+		Right:  j.Right.Primitive(),
+		Cols:   j.Cols,
+		Vars:   j.Vars,
+		Opcode: j.Opcode,
 	}
 }
 
@@ -97,7 +99,7 @@ func (j *joinGen4) Rewrite(inputs ...logicalPlan) error {
 	panic("implement me")
 }
 
-// Solves implements the logicalPlan interface
+// ContainsTables implements the logicalPlan interface
 func (j *joinGen4) ContainsTables() semantics.TableSet {
 	return j.Left.ContainsTables().Merge(j.Right.ContainsTables())
 }
diff --git a/go/vt/vtgate/planbuilder/jointree.go b/go/vt/vtgate/planbuilder/jointree.go
new file mode 100644
index 0000000000..49295e96c4
--- /dev/null
+++ b/go/vt/vtgate/planbuilder/jointree.go
@@ -0,0 +1,549 @@
+/*
+Copyright 2021 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 (
+	"strings"
+
+	"vitess.io/vitess/go/sqltypes"
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vtgate/engine"
+	"vitess.io/vitess/go/vt/vtgate/planbuilder/abstract"
+	"vitess.io/vitess/go/vt/vtgate/semantics"
+	"vitess.io/vitess/go/vt/vtgate/vindexes"
+)
+
+type (
+	joinTree interface {
+		// tableID returns the table identifiers that are solved by this plan
+		tableID() semantics.TableSet
+
+		// cost is simply the number of routes in the joinTree
+		cost() int
+
+		// creates a copy of the joinTree that can be updated without changing the original
+		clone() joinTree
+
+		pushOutputColumns([]*sqlparser.ColName, *semantics.SemTable) []int
+	}
+
+	relation interface {
+		tableID() semantics.TableSet
+		tableNames() []string
+	}
+
+	leJoin struct {
+		lhs, rhs relation
+		pred     sqlparser.Expr
+	}
+
+	routeTable struct {
+		qtable *abstract.QueryTable
+		vtable *vindexes.Table
+	}
+
+	outerTable struct {
+		right relation
+		pred  sqlparser.Expr
+	}
+
+	routePlan struct {
+		routeOpCode engine.RouteOpcode
+		solved      semantics.TableSet
+		keyspace    *vindexes.Keyspace
+
+		// tables contains inner tables that are solved by this plan.
+		// the tables also contain any predicates that only depend on that particular table
+		tables parenTables
+
+		// predicates are the predicates evaluated by this plan
+		predicates []sqlparser.Expr
+
+		// leftJoins are the join conditions evaluated by this plan
+		leftJoins []*outerTable
+
+		// vindex and vindexValues is set if a vindex will be used for this route.
+		vindex           vindexes.Vindex
+		vindexValues     []sqltypes.PlanValue
+		vindexPredicates []sqlparser.Expr
+
+		// here we store the possible vindexes we can use so that when we add predicates to the plan,
+		// we can quickly check if the new predicates enables any new vindex options
+		vindexPreds []*vindexPlusPredicates
+
+		// columns needed to feed other plans`
+		columns []*sqlparser.ColName
+	}
+
+	joinPlan struct {
+		// columns needed to feed other plans
+		columns []int
+
+		// arguments that need to be copied from the LHS/RHS
+		vars map[string]int
+
+		// the children of this plan
+		lhs, rhs joinTree
+
+		outer bool
+	}
+
+	parenTables []relation
+)
+
+// type assertions
+var _ joinTree = (*routePlan)(nil)
+var _ joinTree = (*joinPlan)(nil)
+var _ relation = (*routeTable)(nil)
+var _ relation = (*leJoin)(nil)
+var _ relation = (parenTables)(nil)
+
+func (rp *routeTable) tableID() semantics.TableSet { return rp.qtable.TableID }
+
+func (rp *leJoin) tableID() semantics.TableSet { return rp.lhs.tableID().Merge(rp.rhs.tableID()) }
+
+func (rp *leJoin) tableNames() []string {
+	return append(rp.lhs.tableNames(), rp.rhs.tableNames()...)
+}
+
+func (rp *routeTable) tableNames() []string {
+	return []string{sqlparser.String(rp.qtable.Table.Name)}
+}
+
+func (p parenTables) tableNames() []string {
+	var result []string
+	for _, r := range p {
+		result = append(result, r.tableNames()...)
+	}
+	return result
+}
+
+func (p parenTables) tableID() semantics.TableSet {
+	res := semantics.TableSet(0)
+	for _, r := range p {
+		res = res.Merge(r.tableID())
+	}
+	return res
+}
+
+// visit will traverse the route tables, going inside parenTables and visiting all routeTables
+func (p parenTables) visit(f func(tbl *routeTable) error) error {
+	for _, r := range p {
+		switch r := r.(type) {
+		case *routeTable:
+			err := f(r)
+			if err != nil {
+				return err
+			}
+		case parenTables:
+			err := r.visit(f)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+// clone returns a copy of the struct with copies of slices,
+// so changing the the contents of them will not be reflected in the original
+func (rp *routePlan) clone() joinTree {
+	result := *rp
+	result.vindexPreds = make([]*vindexPlusPredicates, len(rp.vindexPreds))
+	for i, pred := range rp.vindexPreds {
+		// we do this to create a copy of the struct
+		p := *pred
+		result.vindexPreds[i] = &p
+	}
+	return &result
+}
+
+// tables implements the joinTree interface
+func (rp *routePlan) tableID() semantics.TableSet {
+	return rp.solved
+}
+
+func (rp *routePlan) hasOuterjoins() bool {
+	return len(rp.leftJoins) > 0
+}
+
+// cost implements the joinTree interface
+func (rp *routePlan) cost() int {
+	switch rp.routeOpCode {
+	case // these op codes will never be compared with each other - they are assigned by a rule and not a comparison
+		engine.SelectDBA,
+		engine.SelectNext,
+		engine.SelectNone,
+		engine.SelectReference,
+		engine.SelectUnsharded:
+		return 0
+	// TODO revisit these costs when more of the gen4 planner is done
+	case engine.SelectEqualUnique:
+		return 1
+	case engine.SelectEqual:
+		return 5
+	case engine.SelectIN:
+		return 10
+	case engine.SelectMultiEqual:
+		return 10
+	case engine.SelectScatter:
+		return 20
+	}
+	return 1
+}
+
+// vindexPlusPredicates is a struct used to store all the predicates that the vindex can be used to query
+type vindexPlusPredicates struct {
+	colVindex *vindexes.ColumnVindex
+	values    []sqltypes.PlanValue
+
+	// when we have the predicates found, we also know how to interact with this vindex
+	foundVindex vindexes.Vindex
+	opcode      engine.RouteOpcode
+	predicates  []sqlparser.Expr
+}
+
+// addPredicate adds these predicates added to it. if the predicates can help,
+// they will improve the routeOpCode
+func (rp *routePlan) addPredicate(predicates ...sqlparser.Expr) error {
+	if rp.canImprove() {
+		newVindexFound, err := rp.searchForNewVindexes(predicates)
+		if err != nil {
+			return err
+		}
+
+		// if we didn't open up any new vindex options, no need to enter here
+		if newVindexFound {
+			rp.pickBestAvailableVindex()
+		}
+	}
+
+	// any predicates that cover more than a single table need to be added here
+	rp.predicates = append(rp.predicates, predicates...)
+
+	return nil
+}
+
+// canImprove returns true if additional predicates could help improving this plan
+func (rp *routePlan) canImprove() bool {
+	return rp.routeOpCode != engine.SelectNone
+}
+
+func (rp *routePlan) isImpossibleIN(node *sqlparser.ComparisonExpr) bool {
+	switch nodeR := node.Right.(type) {
+	case sqlparser.ValTuple:
+		// WHERE col IN (null)
+		if len(nodeR) == 1 && sqlparser.IsNull(nodeR[0]) {
+			rp.routeOpCode = engine.SelectNone
+			return true
+		}
+	}
+	return false
+}
+
+func (rp *routePlan) isImpossibleNotIN(node *sqlparser.ComparisonExpr) bool {
+	switch node := node.Right.(type) {
+	case sqlparser.ValTuple:
+		for _, n := range node {
+			if sqlparser.IsNull(n) {
+				rp.routeOpCode = engine.SelectNone
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func (rp *routePlan) searchForNewVindexes(predicates []sqlparser.Expr) (bool, error) {
+	newVindexFound := false
+	for _, filter := range predicates {
+		switch node := filter.(type) {
+		case *sqlparser.ComparisonExpr:
+			if sqlparser.IsNull(node.Left) || sqlparser.IsNull(node.Right) {
+				// we are looking at ANDed predicates in the WHERE clause.
+				// since we know that nothing returns true when compared to NULL,
+				// so we can safely bail out here
+				rp.routeOpCode = engine.SelectNone
+				return false, nil
+			}
+
+			switch node.Operator {
+			case sqlparser.EqualOp:
+				found, err := rp.planEqualOp(node)
+				if err != nil {
+					return false, err
+				}
+				newVindexFound = newVindexFound || found
+			case sqlparser.InOp:
+				if rp.isImpossibleIN(node) {
+					return false, nil
+				}
+				found, err := rp.planInOp(node)
+				if err != nil {
+					return false, err
+				}
+				newVindexFound = newVindexFound || found
+			case sqlparser.NotInOp:
+				// NOT IN is always a scatter, except when we can be sure it would return nothing
+				if rp.isImpossibleNotIN(node) {
+					return false, nil
+				}
+			case sqlparser.LikeOp:
+				found, err := rp.planLikeOp(node)
+				if err != nil {
+					return false, err
+				}
+				newVindexFound = newVindexFound || found
+
+			default:
+				return false, semantics.Gen4NotSupportedF("%s", sqlparser.String(filter))
+			}
+		case *sqlparser.IsExpr:
+			found, err := rp.planIsExpr(node)
+			if err != nil {
+				return false, err
+			}
+			newVindexFound = newVindexFound || found
+		}
+	}
+	return newVindexFound, nil
+}
+
+func justTheVindex(vindex *vindexes.ColumnVindex) vindexes.Vindex {
+	return vindex.Vindex
+}
+
+func equalOrEqualUnique(vindex *vindexes.ColumnVindex) engine.RouteOpcode {
+	if vindex.Vindex.IsUnique() {
+		return engine.SelectEqualUnique
+	}
+
+	return engine.SelectEqual
+}
+
+func (rp *routePlan) planEqualOp(node *sqlparser.ComparisonExpr) (bool, error) {
+	column, ok := node.Left.(*sqlparser.ColName)
+	other := node.Right
+	if !ok {
+		column, ok = node.Right.(*sqlparser.ColName)
+		if !ok {
+			// either the LHS or RHS have to be a column to be useful for the vindex
+			return false, nil
+		}
+		other = node.Left
+	}
+	val, err := makePlanValue(other)
+	if err != nil || val == nil {
+		return false, err
+	}
+
+	return rp.haveMatchingVindex(node, column, *val, equalOrEqualUnique, justTheVindex), err
+}
+
+func (rp *routePlan) planInOp(node *sqlparser.ComparisonExpr) (bool, error) {
+	column, ok := node.Left.(*sqlparser.ColName)
+	if !ok {
+		return false, nil
+	}
+	value, err := sqlparser.NewPlanValue(node.Right)
+	if err != nil {
+		// if we are unable to create a PlanValue, we can't use a vindex, but we don't have to fail
+		if strings.Contains(err.Error(), "expression is too complex") {
+			return false, nil
+		}
+		// something else went wrong, return the error
+		return false, err
+	}
+	switch nodeR := node.Right.(type) {
+	case sqlparser.ValTuple:
+		if len(nodeR) == 1 && sqlparser.IsNull(nodeR[0]) {
+			rp.routeOpCode = engine.SelectNone
+			return false, nil
+		}
+	}
+	opcode := func(*vindexes.ColumnVindex) engine.RouteOpcode { return engine.SelectIN }
+	return rp.haveMatchingVindex(node, column, value, opcode, justTheVindex), err
+}
+
+func (rp *routePlan) planLikeOp(node *sqlparser.ComparisonExpr) (bool, error) {
+	column, ok := node.Left.(*sqlparser.ColName)
+	if !ok {
+		return false, nil
+	}
+
+	val, err := makePlanValue(node.Right)
+	if err != nil || val == nil {
+		return false, err
+	}
+
+	selectEqual := func(*vindexes.ColumnVindex) engine.RouteOpcode { return engine.SelectEqual }
+	vdx := func(vindex *vindexes.ColumnVindex) vindexes.Vindex {
+		if prefixable, ok := vindex.Vindex.(vindexes.Prefixable); ok {
+			return prefixable.PrefixVindex()
+		}
+
+		// if we can't use the vindex as a prefix-vindex, we can't use this vindex at all
+		return nil
+	}
+
+	return rp.haveMatchingVindex(node, column, *val, selectEqual, vdx), err
+}
+
+func (rp *routePlan) planIsExpr(node *sqlparser.IsExpr) (bool, error) {
+	// we only handle IS NULL correct. IsExpr can contain other expressions as well
+	if node.Right != sqlparser.IsNullOp {
+		return false, nil
+	}
+	column, ok := node.Left.(*sqlparser.ColName)
+	if !ok {
+		return false, nil
+	}
+	val, err := makePlanValue(&sqlparser.NullVal{})
+	if err != nil || val == nil {
+		return false, err
+	}
+
+	return rp.haveMatchingVindex(node, column, *val, equalOrEqualUnique, justTheVindex), err
+}
+
+func makePlanValue(n sqlparser.Expr) (*sqltypes.PlanValue, error) {
+	value, err := sqlparser.NewPlanValue(n)
+	if err != nil {
+		// if we are unable to create a PlanValue, we can't use a vindex, but we don't have to fail
+		if strings.Contains(err.Error(), "expression is too complex") {
+			return nil, nil
+		}
+		// something else went wrong, return the error
+		return nil, err
+	}
+	return &value, nil
+}
+
+func (rp *routePlan) haveMatchingVindex(
+	node sqlparser.Expr,
+	column *sqlparser.ColName,
+	value sqltypes.PlanValue,
+	opcode func(*vindexes.ColumnVindex) engine.RouteOpcode,
+	vfunc func(*vindexes.ColumnVindex) vindexes.Vindex,
+) bool {
+	newVindexFound := false
+	for _, v := range rp.vindexPreds {
+		if v.foundVindex != nil {
+			continue
+		}
+		for _, col := range v.colVindex.Columns {
+			// If the column for the predicate matches any column in the vindex add it to the list
+			if column.Name.Equal(col) {
+				v.values = append(v.values, value)
+				v.predicates = append(v.predicates, node)
+				// Vindex is covered if all the columns in the vindex have a associated predicate
+				covered := len(v.values) == len(v.colVindex.Columns)
+				if covered {
+					v.opcode = opcode(v.colVindex)
+					v.foundVindex = vfunc(v.colVindex)
+				}
+				newVindexFound = newVindexFound || covered
+			}
+		}
+	}
+	return newVindexFound
+}
+
+// pickBestAvailableVindex goes over the available vindexes for this route and picks the best one available.
+func (rp *routePlan) pickBestAvailableVindex() {
+	for _, v := range rp.vindexPreds {
+		if v.foundVindex == nil {
+			continue
+		}
+		// Choose the minimum cost vindex from the ones which are covered
+		if rp.vindex == nil || v.colVindex.Vindex.Cost() < rp.vindex.Cost() {
+			rp.routeOpCode = v.opcode
+			rp.vindex = v.foundVindex
+			rp.vindexValues = v.values
+			rp.vindexPredicates = v.predicates
+		}
+	}
+}
+
+// Predicates takes all known predicates for this route and ANDs them together
+func (rp *routePlan) Predicates() sqlparser.Expr {
+	return sqlparser.AndExpressions(rp.predicates...)
+}
+
+func (rp *routePlan) pushOutputColumns(col []*sqlparser.ColName, _ *semantics.SemTable) []int {
+	idxs := make([]int, len(col))
+outer:
+	for i, newCol := range col {
+		for j, existingCol := range rp.columns {
+			if sqlparser.EqualsExpr(newCol, existingCol) {
+				idxs[i] = j
+				continue outer
+			}
+		}
+		idxs[i] = len(rp.columns)
+		rp.columns = append(rp.columns, newCol)
+	}
+	return idxs
+}
+
+func (jp *joinPlan) tableID() semantics.TableSet {
+	return jp.lhs.tableID() | jp.rhs.tableID()
+}
+
+func (jp *joinPlan) cost() int {
+	return jp.lhs.cost() + jp.rhs.cost()
+}
+
+func (jp *joinPlan) clone() joinTree {
+	result := &joinPlan{
+		lhs:   jp.lhs.clone(),
+		rhs:   jp.rhs.clone(),
+		outer: jp.outer,
+	}
+	return result
+}
+
+func (jp *joinPlan) pushOutputColumns(columns []*sqlparser.ColName, semTable *semantics.SemTable) []int {
+	var toTheLeft []bool
+	var lhs, rhs []*sqlparser.ColName
+	for _, col := range columns {
+		col.Qualifier.Qualifier = sqlparser.NewTableIdent("")
+		if semTable.Dependencies(col).IsSolvedBy(jp.lhs.tableID()) {
+			lhs = append(lhs, col)
+			toTheLeft = append(toTheLeft, true)
+		} else {
+			rhs = append(rhs, col)
+			toTheLeft = append(toTheLeft, false)
+		}
+	}
+	lhsOffset := jp.lhs.pushOutputColumns(lhs, semTable)
+	rhsOffset := jp.rhs.pushOutputColumns(rhs, semTable)
+	outputColumns := make([]int, len(toTheLeft))
+	var l, r int
+	for i, isLeft := range toTheLeft {
+		outputColumns[i] = i
+		if isLeft {
+			jp.columns = append(jp.columns, -lhsOffset[l]-1)
+			l++
+		} else {
+			jp.columns = append(jp.columns, rhsOffset[r]+1)
+			r++
+		}
+	}
+	return outputColumns
+}
diff --git a/go/vt/vtgate/planbuilder/jointree_transformers.go b/go/vt/vtgate/planbuilder/jointree_transformers.go
index 4583f2ea73..b2f1b93d38 100644
--- a/go/vt/vtgate/planbuilder/jointree_transformers.go
+++ b/go/vt/vtgate/planbuilder/jointree_transformers.go
@@ -50,11 +50,16 @@ func transformJoinPlan(n *joinPlan, semTable *semantics.SemTable) (logicalPlan,
 	if err != nil {
 		return nil, err
 	}
+	opCode := engine.InnerJoin
+	if n.outer {
+		opCode = engine.LeftJoin
+	}
 	return &joinGen4{
-		Left:  lhs,
-		Right: rhs,
-		Cols:  n.columns,
-		Vars:  n.vars,
+		Left:   lhs,
+		Right:  rhs,
+		Cols:   n.columns,
+		Vars:   n.vars,
+		Opcode: opCode,
 	}, nil
 }
 
@@ -62,18 +67,16 @@ func transformRoutePlan(n *routePlan) (*route, error) {
 	var tablesForSelect sqlparser.TableExprs
 	tableNameMap := map[string]interface{}{}
 
-	sort.Sort(n._tables)
-	for _, t := range n._tables {
-		alias := sqlparser.AliasedTableExpr{
-			Expr: sqlparser.TableName{
-				Name: t.vtable.Name,
-			},
-			Partitions: nil,
-			As:         t.qtable.alias.As,
-			Hints:      nil,
+	sort.Sort(n.tables)
+	for _, t := range n.tables {
+		tableExpr, err := relToTableExpr(t)
+		if err != nil {
+			return nil, err
+		}
+		tablesForSelect = append(tablesForSelect, tableExpr)
+		for _, name := range t.tableNames() {
+			tableNameMap[name] = nil
 		}
-		tablesForSelect = append(tablesForSelect, &alias)
-		tableNameMap[sqlparser.String(t.qtable.table.Name)] = nil
 	}
 
 	for _, predicate := range n.vindexPredicates {
@@ -85,6 +88,31 @@ func transformRoutePlan(n *routePlan) (*route, error) {
 		}
 	}
 
+	for _, leftJoin := range n.leftJoins {
+		var lft sqlparser.TableExpr
+		if len(tablesForSelect) == 1 {
+			lft = tablesForSelect[0]
+		} else {
+			lft = &sqlparser.ParenTableExpr{Exprs: tablesForSelect}
+		}
+
+		rightExpr, err := relToTableExpr(leftJoin.right)
+		if err != nil {
+			return nil, err
+		}
+		joinExpr := &sqlparser.JoinTableExpr{
+			Join: sqlparser.LeftJoinType,
+			Condition: sqlparser.JoinCondition{
+				On: leftJoin.pred,
+			},
+			RightExpr: rightExpr,
+			LeftExpr:  lft,
+		}
+		tablesForSelect = sqlparser.TableExprs{joinExpr}
+		// todo: add table
+		// tableNameMap[sqlparser.String()] = nil
+	}
+
 	predicates := n.Predicates()
 	var where *sqlparser.Where
 	if predicates != nil {
@@ -98,7 +126,10 @@ func transformRoutePlan(n *routePlan) (*route, error) {
 
 	var expressions sqlparser.SelectExprs
 	for _, col := range n.columns {
-		expressions = append(expressions, &sqlparser.AliasedExpr{Expr: col})
+		expressions = append(expressions, &sqlparser.AliasedExpr{
+			Expr: col,
+			As:   sqlparser.ColIdent{},
+		})
 	}
 
 	var tableNames []string
@@ -123,3 +154,46 @@ func transformRoutePlan(n *routePlan) (*route, error) {
 		tables: n.solved,
 	}, nil
 }
+
+func relToTableExpr(t relation) (sqlparser.TableExpr, error) {
+	switch t := t.(type) {
+	case *routeTable:
+		return &sqlparser.AliasedTableExpr{
+			Expr: sqlparser.TableName{
+				Name: t.vtable.Name,
+			},
+			Partitions: nil,
+			As:         t.qtable.Alias.As,
+			Hints:      nil,
+		}, nil
+	case parenTables:
+		tables := sqlparser.TableExprs{}
+		for _, t := range t {
+			tableExpr, err := relToTableExpr(t)
+			if err != nil {
+				return nil, err
+			}
+			tables = append(tables, tableExpr)
+		}
+		return &sqlparser.ParenTableExpr{Exprs: tables}, nil
+	case *leJoin:
+		lExpr, err := relToTableExpr(t.lhs)
+		if err != nil {
+			return nil, err
+		}
+		rExpr, err := relToTableExpr(t.rhs)
+		if err != nil {
+			return nil, err
+		}
+		return &sqlparser.JoinTableExpr{
+			LeftExpr:  lExpr,
+			Join:      sqlparser.NormalJoinType,
+			RightExpr: rExpr,
+			Condition: sqlparser.JoinCondition{
+				On: t.pred,
+			},
+		}, nil
+	default:
+		return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] unknown relation type: %T", t)
+	}
+}
diff --git a/go/vt/vtgate/planbuilder/querygraph.go b/go/vt/vtgate/planbuilder/querygraph.go
deleted file mode 100644
index 608825b565..0000000000
--- a/go/vt/vtgate/planbuilder/querygraph.go
+++ /dev/null
@@ -1,226 +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 (
-	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/semantics"
-)
-
-type (
-	/*
-	 queryGraph represents the FROM and WHERE parts of a query.
-	 It is an intermediate representation of the query that makes it easier for the planner
-	 to find all possible join combinations. Instead of storing the query information in a form that is close
-	 to the syntax (AST), we extract the interesting parts into a graph form with the nodes being tables in the FROM
-	 clause and the edges between them being predicates. We keep predicates in a hash map keyed by the dependencies of
-	 the predicate. This makes it very fast to look up connections between tables in the query.
-	*/
-	queryGraph struct {
-		// the tables, including predicates that only depend on this particular table
-		tables []*queryTable
-
-		// crossTable contains the predicates that need multiple tables
-		crossTable map[semantics.TableSet][]sqlparser.Expr
-
-		// noDeps contains the predicates that can be evaluated anywhere.
-		noDeps sqlparser.Expr
-
-		// subqueries contains the subqueries that depend on this query graph
-		subqueries map[*sqlparser.Subquery][]*queryGraph
-	}
-
-	// queryTable is a single FROM table, including all predicates particular to this table
-	queryTable struct {
-		tableID    semantics.TableSet
-		alias      *sqlparser.AliasedTableExpr
-		table      sqlparser.TableName
-		predicates []sqlparser.Expr
-	}
-)
-
-func (qg *queryGraph) getPredicates(lhs, rhs semantics.TableSet) []sqlparser.Expr {
-	var allExprs []sqlparser.Expr
-	for tableSet, exprs := range qg.crossTable {
-		if tableSet.IsSolvedBy(lhs|rhs) &&
-			tableSet.IsOverlapping(rhs) &&
-			tableSet.IsOverlapping(lhs) {
-			allExprs = append(allExprs, exprs...)
-		}
-	}
-	return allExprs
-}
-
-func createQGFromSelect(sel *sqlparser.Select, semTable *semantics.SemTable) (*queryGraph, error) {
-	qg := newQueryGraph()
-	if err := qg.collectTables(sel.From, semTable); err != nil {
-		return nil, err
-	}
-
-	if sel.Where != nil {
-		err := qg.collectPredicates(sel, semTable)
-		if err != nil {
-			return nil, err
-		}
-	}
-	return qg, nil
-}
-
-func createQGFromSelectStatement(selStmt sqlparser.SelectStatement, semTable *semantics.SemTable) ([]*queryGraph, error) {
-	switch stmt := selStmt.(type) {
-	case *sqlparser.Select:
-		qg, err := createQGFromSelect(stmt, semTable)
-		if err != nil {
-			return nil, err
-		}
-		return []*queryGraph{qg}, err
-	case *sqlparser.Union:
-		qg, err := createQGFromSelectStatement(stmt.FirstStatement, semTable)
-		if err != nil {
-			return nil, err
-		}
-		for _, sel := range stmt.UnionSelects {
-			qgr, err := createQGFromSelectStatement(sel.Statement, semTable)
-			if err != nil {
-				return nil, err
-			}
-			qg = append(qg, qgr...)
-		}
-		return qg, nil
-	case *sqlparser.ParenSelect:
-		return createQGFromSelectStatement(stmt.Select, semTable)
-	}
-
-	return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] not reachable %T", selStmt)
-}
-
-func newQueryGraph() *queryGraph {
-	return &queryGraph{
-		crossTable: map[semantics.TableSet][]sqlparser.Expr{},
-		subqueries: map[*sqlparser.Subquery][]*queryGraph{},
-	}
-}
-
-func (qg *queryGraph) collectTable(t sqlparser.TableExpr, semTable *semantics.SemTable) error {
-	switch table := t.(type) {
-	case *sqlparser.AliasedTableExpr:
-		tableName := table.Expr.(sqlparser.TableName)
-		qt := &queryTable{alias: table, table: tableName, tableID: semTable.TableSetFor(table)}
-		qg.tables = append(qg.tables, qt)
-	case *sqlparser.JoinTableExpr:
-		if err := qg.collectTable(table.LeftExpr, semTable); err != nil {
-			return err
-		}
-		if err := qg.collectTable(table.RightExpr, semTable); err != nil {
-			return err
-		}
-		if table.Condition.On != nil {
-			for _, predicate := range splitAndExpression(nil, table.Condition.On) {
-				err := qg.collectPredicate(predicate, semTable)
-				if err != nil {
-					return err
-				}
-			}
-		}
-	case *sqlparser.ParenTableExpr:
-		for _, expr := range table.Exprs {
-			if err := qg.collectTable(expr, semTable); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
-func (qg *queryGraph) collectTables(t sqlparser.TableExprs, semTable *semantics.SemTable) error {
-	for _, expr := range t {
-		if err := qg.collectTable(expr, semTable); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (qg *queryGraph) collectPredicates(sel *sqlparser.Select, semTable *semantics.SemTable) error {
-	predicates := splitAndExpression(nil, sel.Where.Expr)
-
-	for _, predicate := range predicates {
-		err := qg.collectPredicate(predicate, semTable)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (qg *queryGraph) collectPredicate(predicate sqlparser.Expr, semTable *semantics.SemTable) error {
-	deps := semTable.Dependencies(predicate)
-	switch deps.NumberOfTables() {
-	case 0:
-		qg.addNoDepsPredicate(predicate)
-	case 1:
-		found := qg.addToSingleTable(deps, predicate)
-		if !found {
-			return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "table %v for predicate %v not found", deps, sqlparser.String(predicate))
-		}
-	default:
-		allPredicates, found := qg.crossTable[deps]
-		if found {
-			allPredicates = append(allPredicates, predicate)
-		} else {
-			allPredicates = []sqlparser.Expr{predicate}
-		}
-		qg.crossTable[deps] = allPredicates
-	}
-	err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
-		switch subQuery := node.(type) {
-		case *sqlparser.Subquery:
-
-			qgr, err := createQGFromSelectStatement(subQuery.Select, semTable)
-			if err != nil {
-				return false, err
-			}
-			qg.subqueries[subQuery] = qgr
-		}
-		return true, nil
-	}, predicate)
-
-	return err
-}
-
-func (qg *queryGraph) addToSingleTable(table semantics.TableSet, predicate sqlparser.Expr) bool {
-	for _, t := range qg.tables {
-		if table == t.tableID {
-			t.predicates = append(t.predicates, predicate)
-			return true
-		}
-	}
-	return false
-}
-
-func (qg *queryGraph) addNoDepsPredicate(predicate sqlparser.Expr) {
-	if qg.noDeps == nil {
-		qg.noDeps = predicate
-	} else {
-		qg.noDeps = &sqlparser.AndExpr{
-			Left:  qg.noDeps,
-			Right: predicate,
-		}
-	}
-}
diff --git a/go/vt/vtgate/planbuilder/route.go b/go/vt/vtgate/planbuilder/route.go
index 56615be537..504b73c682 100644
--- a/go/vt/vtgate/planbuilder/route.go
+++ b/go/vt/vtgate/planbuilder/route.go
@@ -445,7 +445,7 @@ func (rb *route) JoinCanMerge(pb *primitiveBuilder, rrb *route, ajoin *sqlparser
 	if ajoin == nil {
 		return false
 	}
-	for _, filter := range splitAndExpression(nil, ajoin.Condition.On) {
+	for _, filter := range sqlparser.SplitAndExpression(nil, ajoin.Condition.On) {
 		if rb.canMergeOnFilter(pb, rrb, filter) {
 			return true
 		}
diff --git a/go/vt/vtgate/planbuilder/route_planning.go b/go/vt/vtgate/planbuilder/route_planning.go
index 3caaca2039..a955caf09d 100644
--- a/go/vt/vtgate/planbuilder/route_planning.go
+++ b/go/vt/vtgate/planbuilder/route_planning.go
@@ -18,10 +18,10 @@ package planbuilder
 
 import (
 	"fmt"
+	"io"
 	"sort"
-	"strings"
 
-	"vitess.io/vitess/go/sqltypes"
+	"vitess.io/vitess/go/vt/vtgate/planbuilder/abstract"
 
 	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
 	"vitess.io/vitess/go/vt/vtgate/semantics"
@@ -60,23 +60,12 @@ func newBuildSelectPlan(sel *sqlparser.Select, vschema ContextVSchema) (engine.P
 		return nil, err
 	}
 
-	qgraph, err := createQGFromSelect(sel, semTable)
+	opTree, err := abstract.CreateOperatorFromSelect(sel, semTable)
 	if err != nil {
 		return nil, err
 	}
-	if len(qgraph.subqueries) > 0 {
-		return nil, semantics.Gen4NotSupportedF("subquery")
-	}
-
-	var tree joinTree
-
-	switch {
-	case vschema.Planner() == Gen4Left2Right:
-		tree, err = leftToRightSolve(qgraph, semTable, vschema)
-	default:
-		tree, err = greedySolve(qgraph, semTable, vschema)
-	}
 
+	tree, err := optimizeQuery(opTree, semTable, vschema)
 	if err != nil {
 		return nil, err
 	}
@@ -102,6 +91,41 @@ func newBuildSelectPlan(sel *sqlparser.Select, vschema ContextVSchema) (engine.P
 	return plan.Primitive(), nil
 }
 
+func optimizeQuery(opTree abstract.Operator, semTable *semantics.SemTable, vschema ContextVSchema) (joinTree, error) {
+	switch op := opTree.(type) {
+	case *abstract.QueryGraph:
+		switch {
+		case vschema.Planner() == Gen4Left2Right:
+			return leftToRightSolve(op, semTable, vschema)
+		default:
+			return greedySolve(op, semTable, vschema)
+		}
+	case *abstract.LeftJoin:
+		treeInner, err := optimizeQuery(op.Left, semTable, vschema)
+		if err != nil {
+			return nil, err
+		}
+		treeOuter, err := optimizeQuery(op.Right, semTable, vschema)
+		if err != nil {
+			return nil, err
+		}
+		return mergeOrJoin(treeInner, treeOuter, []sqlparser.Expr{op.Predicate}, semTable, false)
+	case *abstract.Join:
+		treeInner, err := optimizeQuery(op.LHS, semTable, vschema)
+		if err != nil {
+			return nil, err
+		}
+		treeOuter, err := optimizeQuery(op.RHS, semTable, vschema)
+		if err != nil {
+			return nil, err
+		}
+		return mergeOrJoin(treeInner, treeOuter, []sqlparser.Expr{op.Exp}, semTable, true)
+
+	default:
+		return nil, semantics.Gen4NotSupportedF("optimizeQuery")
+	}
+}
+
 type starRewriter struct {
 	err      error
 	semTable *semantics.SemTable
@@ -233,12 +257,7 @@ func planLimit(limit *sqlparser.Limit, plan logicalPlan) (logicalPlan, error) {
 func planHorizon(sel *sqlparser.Select, plan logicalPlan, semTable *semantics.SemTable) (logicalPlan, error) {
 	rb, ok := plan.(*route)
 	if ok && rb.isSingleShard() {
-		ast := rb.Select.(*sqlparser.Select)
-		ast.Distinct = sel.Distinct
-		ast.GroupBy = sel.GroupBy
-		ast.OrderBy = sel.OrderBy
-		ast.SelectExprs = sel.SelectExprs
-		ast.Comments = sel.Comments
+		createSingleShardRoutePlan(sel, rb)
 		return plan, nil
 	}
 
@@ -275,449 +294,21 @@ func planHorizon(sel *sqlparser.Select, plan logicalPlan, semTable *semantics.Se
 	return plan, nil
 }
 
-type (
-	joinTree interface {
-		// tables returns the table identifiers that are solved by this plan
-		tables() semantics.TableSet
-
-		// cost is simply the number of routes in the joinTree
-		cost() int
-
-		// creates a copy of the joinTree that can be updated without changing the original
-		clone() joinTree
-
-		pushOutputColumns([]*sqlparser.ColName, *semantics.SemTable) int
-	}
-	routeTable struct {
-		qtable *queryTable
-		vtable *vindexes.Table
-	}
-	routePlan struct {
-		routeOpCode engine.RouteOpcode
-		solved      semantics.TableSet
-		keyspace    *vindexes.Keyspace
-
-		// _tables contains all the tables that are solved by this plan.
-		// the tables also contain any predicates that only depend on that particular table
-		_tables routeTables
-
-		// predicates are the predicates evaluated by this plan
-		predicates []sqlparser.Expr
-
-		// vindex and vindexValues is set if a vindex will be used for this route.
-		vindex           vindexes.Vindex
-		vindexValues     []sqltypes.PlanValue
-		vindexPredicates []sqlparser.Expr
-
-		// here we store the possible vindexes we can use so that when we add predicates to the plan,
-		// we can quickly check if the new predicates enables any new vindex options
-		vindexPreds []*vindexPlusPredicates
-
-		// columns needed to feed other plans
-		columns []*sqlparser.ColName
-	}
-	joinPlan struct {
-		// columns needed to feed other plans
-		columns []int
-
-		// arguments that need to be copied from the LHS/RHS
-		vars map[string]int
-
-		lhs, rhs joinTree
-	}
-	routeTables []*routeTable
-)
-
-var _ joinTree = (*routePlan)(nil)
-var _ joinTree = (*joinPlan)(nil)
-
-// clone returns a copy of the struct with copies of slices,
-// so changing the the contents of them will not be reflected in the original
-func (rp *routePlan) clone() joinTree {
-	result := *rp
-	result.vindexPreds = make([]*vindexPlusPredicates, len(rp.vindexPreds))
-	for i, pred := range rp.vindexPreds {
-		// we do this to create a copy of the struct
-		p := *pred
-		result.vindexPreds[i] = &p
-	}
-	return &result
-}
-
-// tables implements the joinTree interface
-func (rp *routePlan) tables() semantics.TableSet {
-	return rp.solved
-}
-
-// cost implements the joinTree interface
-func (rp *routePlan) cost() int {
-	switch rp.routeOpCode {
-	case // these op codes will never be compared with each other - they are assigned by a rule and not a comparison
-		engine.SelectDBA,
-		engine.SelectNext,
-		engine.SelectNone,
-		engine.SelectReference,
-		engine.SelectUnsharded:
-		return 0
-	// TODO revisit these costs when more of the gen4 planner is done
-	case engine.SelectEqualUnique:
-		return 1
-	case engine.SelectEqual:
-		return 5
-	case engine.SelectIN:
-		return 10
-	case engine.SelectMultiEqual:
-		return 10
-	case engine.SelectScatter:
-		return 20
-	}
-	return 1
-}
-
-// vindexPlusPredicates is a struct used to store all the predicates that the vindex can be used to query
-type vindexPlusPredicates struct {
-	colVindex *vindexes.ColumnVindex
-	values    []sqltypes.PlanValue
-
-	// when we have the predicates found, we also know how to interact with this vindex
-	foundVindex vindexes.Vindex
-	opcode      engine.RouteOpcode
-	predicates  []sqlparser.Expr
-}
-
-// addPredicate adds these predicates added to it. if the predicates can help,
-// they will improve the routeOpCode
-func (rp *routePlan) addPredicate(predicates ...sqlparser.Expr) error {
-	if rp.canImprove() {
-		newVindexFound, err := rp.searchForNewVindexes(predicates)
-		if err != nil {
-			return err
-		}
-
-		// if we didn't open up any new vindex options, no need to enter here
-		if newVindexFound {
-			rp.pickBestAvailableVindex()
-		}
-	}
-
-	// any predicates that cover more than a single table need to be added here
-	rp.predicates = append(rp.predicates, predicates...)
-
-	return nil
-}
-
-// canImprove returns true if additional predicates could help improving this plan
-func (rp *routePlan) canImprove() bool {
-	return rp.routeOpCode != engine.SelectNone
-}
-
-func (rp *routePlan) isImpossibleIN(node *sqlparser.ComparisonExpr) bool {
-	switch nodeR := node.Right.(type) {
-	case sqlparser.ValTuple:
-		// WHERE col IN (null)
-		if len(nodeR) == 1 && sqlparser.IsNull(nodeR[0]) {
-			rp.routeOpCode = engine.SelectNone
-			return true
+func createSingleShardRoutePlan(sel *sqlparser.Select, rb *route) {
+	ast := rb.Select.(*sqlparser.Select)
+	ast.Distinct = sel.Distinct
+	ast.GroupBy = sel.GroupBy
+	ast.OrderBy = sel.OrderBy
+	ast.Comments = sel.Comments
+	ast.SelectExprs = sel.SelectExprs
+	for i, expr := range ast.SelectExprs {
+		if aliasedExpr, ok := expr.(*sqlparser.AliasedExpr); ok {
+			ast.SelectExprs[i] = removeQualifierFromColName(aliasedExpr)
 		}
 	}
-	return false
-}
-
-func (rp *routePlan) isImpossibleNotIN(node *sqlparser.ComparisonExpr) bool {
-	switch node := node.Right.(type) {
-	case sqlparser.ValTuple:
-		for _, n := range node {
-			if sqlparser.IsNull(n) {
-				rp.routeOpCode = engine.SelectNone
-				return true
-			}
-		}
-	}
-
-	return false
-}
-
-func (rp *routePlan) searchForNewVindexes(predicates []sqlparser.Expr) (bool, error) {
-	newVindexFound := false
-	for _, filter := range predicates {
-		switch node := filter.(type) {
-		case *sqlparser.ComparisonExpr:
-			if sqlparser.IsNull(node.Left) || sqlparser.IsNull(node.Right) {
-				// we are looking at ANDed predicates in the WHERE clause.
-				// since we know that nothing returns true when compared to NULL,
-				// so we can safely bail out here
-				rp.routeOpCode = engine.SelectNone
-				return false, nil
-			}
-
-			switch node.Operator {
-			case sqlparser.EqualOp:
-				found, err := rp.planEqualOp(node)
-				if err != nil {
-					return false, err
-				}
-				newVindexFound = newVindexFound || found
-			case sqlparser.InOp:
-				if rp.isImpossibleIN(node) {
-					return false, nil
-				}
-				found, err := rp.planInOp(node)
-				if err != nil {
-					return false, err
-				}
-				newVindexFound = newVindexFound || found
-			case sqlparser.NotInOp:
-				// NOT IN is always a scatter, except when we can be sure it would return nothing
-				if rp.isImpossibleNotIN(node) {
-					return false, nil
-				}
-			case sqlparser.LikeOp:
-				found, err := rp.planLikeOp(node)
-				if err != nil {
-					return false, err
-				}
-				newVindexFound = newVindexFound || found
-
-			default:
-				return false, semantics.Gen4NotSupportedF("%s", sqlparser.String(filter))
-			}
-		case *sqlparser.IsExpr:
-			found, err := rp.planIsExpr(node)
-			if err != nil {
-				return false, err
-			}
-			newVindexFound = newVindexFound || found
-		}
-	}
-	return newVindexFound, nil
 }
 
-func justTheVindex(vindex *vindexes.ColumnVindex) vindexes.Vindex {
-	return vindex.Vindex
-}
-
-func equalOrEqualUnique(vindex *vindexes.ColumnVindex) engine.RouteOpcode {
-	if vindex.Vindex.IsUnique() {
-		return engine.SelectEqualUnique
-	}
-
-	return engine.SelectEqual
-}
-
-func (rp *routePlan) planEqualOp(node *sqlparser.ComparisonExpr) (bool, error) {
-	column, ok := node.Left.(*sqlparser.ColName)
-	other := node.Right
-	if !ok {
-		column, ok = node.Right.(*sqlparser.ColName)
-		if !ok {
-			// either the LHS or RHS have to be a column to be useful for the vindex
-			return false, nil
-		}
-		other = node.Left
-	}
-	val, err := makePlanValue(other)
-	if err != nil || val == nil {
-		return false, err
-	}
-
-	return rp.haveMatchingVindex(node, column, *val, equalOrEqualUnique, justTheVindex), err
-}
-
-func (rp *routePlan) planInOp(node *sqlparser.ComparisonExpr) (bool, error) {
-	column, ok := node.Left.(*sqlparser.ColName)
-	if !ok {
-		return false, nil
-	}
-	value, err := sqlparser.NewPlanValue(node.Right)
-	if err != nil {
-		// if we are unable to create a PlanValue, we can't use a vindex, but we don't have to fail
-		if strings.Contains(err.Error(), "expression is too complex") {
-			return false, nil
-		}
-		// something else went wrong, return the error
-		return false, err
-	}
-	switch nodeR := node.Right.(type) {
-	case sqlparser.ValTuple:
-		if len(nodeR) == 1 && sqlparser.IsNull(nodeR[0]) {
-			rp.routeOpCode = engine.SelectNone
-			return false, nil
-		}
-	}
-	opcode := func(*vindexes.ColumnVindex) engine.RouteOpcode { return engine.SelectIN }
-	return rp.haveMatchingVindex(node, column, value, opcode, justTheVindex), err
-}
-
-func (rp *routePlan) planLikeOp(node *sqlparser.ComparisonExpr) (bool, error) {
-	column, ok := node.Left.(*sqlparser.ColName)
-	if !ok {
-		return false, nil
-	}
-
-	val, err := makePlanValue(node.Right)
-	if err != nil || val == nil {
-		return false, err
-	}
-
-	selectEqual := func(*vindexes.ColumnVindex) engine.RouteOpcode { return engine.SelectEqual }
-	vdx := func(vindex *vindexes.ColumnVindex) vindexes.Vindex {
-		if prefixable, ok := vindex.Vindex.(vindexes.Prefixable); ok {
-			return prefixable.PrefixVindex()
-		}
-
-		// if we can't use the vindex as a prefix-vindex, we can't use this vindex at all
-		return nil
-	}
-
-	return rp.haveMatchingVindex(node, column, *val, selectEqual, vdx), err
-}
-
-func (rp *routePlan) planIsExpr(node *sqlparser.IsExpr) (bool, error) {
-	// we only handle IS NULL correct. IsExpr can contain other expressions as well
-	if node.Right != sqlparser.IsNullOp {
-		return false, nil
-	}
-	column, ok := node.Left.(*sqlparser.ColName)
-	if !ok {
-		return false, nil
-	}
-	val, err := makePlanValue(&sqlparser.NullVal{})
-	if err != nil || val == nil {
-		return false, err
-	}
-
-	return rp.haveMatchingVindex(node, column, *val, equalOrEqualUnique, justTheVindex), err
-}
-
-func makePlanValue(n sqlparser.Expr) (*sqltypes.PlanValue, error) {
-	value, err := sqlparser.NewPlanValue(n)
-	if err != nil {
-		// if we are unable to create a PlanValue, we can't use a vindex, but we don't have to fail
-		if strings.Contains(err.Error(), "expression is too complex") {
-			return nil, nil
-		}
-		// something else went wrong, return the error
-		return nil, err
-	}
-	return &value, nil
-}
-
-func (rp *routePlan) haveMatchingVindex(
-	node sqlparser.Expr,
-	column *sqlparser.ColName,
-	value sqltypes.PlanValue,
-	opcode func(*vindexes.ColumnVindex) engine.RouteOpcode,
-	vfunc func(*vindexes.ColumnVindex) vindexes.Vindex,
-) bool {
-	newVindexFound := false
-	for _, v := range rp.vindexPreds {
-		if v.foundVindex != nil {
-			continue
-		}
-		for _, col := range v.colVindex.Columns {
-			// If the column for the predicate matches any column in the vindex add it to the list
-			if column.Name.Equal(col) {
-				v.values = append(v.values, value)
-				v.predicates = append(v.predicates, node)
-				// Vindex is covered if all the columns in the vindex have a associated predicate
-				covered := len(v.values) == len(v.colVindex.Columns)
-				if covered {
-					v.opcode = opcode(v.colVindex)
-					v.foundVindex = vfunc(v.colVindex)
-				}
-				newVindexFound = newVindexFound || covered
-			}
-		}
-	}
-	return newVindexFound
-}
-
-// pickBestAvailableVindex goes over the available vindexes for this route and picks the best one available.
-func (rp *routePlan) pickBestAvailableVindex() {
-	for _, v := range rp.vindexPreds {
-		if v.foundVindex == nil {
-			continue
-		}
-		// Choose the minimum cost vindex from the ones which are covered
-		if rp.vindex == nil || v.colVindex.Vindex.Cost() < rp.vindex.Cost() {
-			rp.routeOpCode = v.opcode
-			rp.vindex = v.foundVindex
-			rp.vindexValues = v.values
-			rp.vindexPredicates = v.predicates
-		}
-	}
-}
-
-// Predicates takes all known predicates for this route and ANDs them together
-func (rp *routePlan) Predicates() sqlparser.Expr {
-	var result sqlparser.Expr
-	add := func(e sqlparser.Expr) {
-		if result == nil {
-			result = e
-			return
-		}
-		result = &sqlparser.AndExpr{
-			Left:  result,
-			Right: e,
-		}
-	}
-	for _, p := range rp.predicates {
-		add(p)
-	}
-	return result
-}
-
-func (rp *routePlan) pushOutputColumns(col []*sqlparser.ColName, _ *semantics.SemTable) int {
-	newCol := len(rp.columns)
-	rp.columns = append(rp.columns, col...)
-	return newCol
-}
-
-func (jp *joinPlan) tables() semantics.TableSet {
-	return jp.lhs.tables() | jp.rhs.tables()
-}
-
-func (jp *joinPlan) cost() int {
-	return jp.lhs.cost() + jp.rhs.cost()
-}
-
-func (jp *joinPlan) clone() joinTree {
-	result := &joinPlan{
-		lhs: jp.lhs.clone(),
-		rhs: jp.rhs.clone(),
-	}
-	return result
-}
-
-func (jp *joinPlan) pushOutputColumns(columns []*sqlparser.ColName, semTable *semantics.SemTable) int {
-	resultIdx := len(jp.columns)
-	var toTheLeft []bool
-	var lhs, rhs []*sqlparser.ColName
-	for _, col := range columns {
-		if semTable.Dependencies(col).IsSolvedBy(jp.lhs.tables()) {
-			lhs = append(lhs, col)
-			toTheLeft = append(toTheLeft, true)
-		} else {
-			rhs = append(rhs, col)
-			toTheLeft = append(toTheLeft, false)
-		}
-	}
-	lhsOffset := jp.lhs.pushOutputColumns(lhs, semTable)
-	rhsOffset := -jp.rhs.pushOutputColumns(rhs, semTable)
-
-	for _, left := range toTheLeft {
-		if left {
-			jp.columns = append(jp.columns, lhsOffset)
-			lhsOffset++
-		} else {
-			jp.columns = append(jp.columns, rhsOffset)
-			rhsOffset--
-		}
-	}
-	return resultIdx
-}
-
-func pushPredicate2(exprs []sqlparser.Expr, tree joinTree, semTable *semantics.SemTable) (joinTree, error) {
+func pushJoinPredicate(exprs []sqlparser.Expr, tree joinTree, semTable *semantics.SemTable) (joinTree, error) {
 	switch node := tree.(type) {
 	case *routePlan:
 		plan := node.clone().(*routePlan)
@@ -728,10 +319,12 @@ func pushPredicate2(exprs []sqlparser.Expr, tree joinTree, semTable *semantics.S
 		return plan, nil
 
 	case *joinPlan:
+		node = node.clone().(*joinPlan)
+
 		// we break up the predicates so that colnames from the LHS are replaced by arguments
 		var rhsPreds []sqlparser.Expr
 		var lhsColumns []*sqlparser.ColName
-		lhsSolves := node.lhs.tables()
+		lhsSolves := node.lhs.tableID()
 		for _, expr := range exprs {
 			cols, predicate, err := breakPredicateInLHSandRHS(expr, semTable, lhsSolves)
 			if err != nil {
@@ -741,13 +334,15 @@ func pushPredicate2(exprs []sqlparser.Expr, tree joinTree, semTable *semantics.S
 			rhsPreds = append(rhsPreds, predicate)
 		}
 		node.pushOutputColumns(lhsColumns, semTable)
-		rhsPlan, err := pushPredicate2(rhsPreds, node.rhs, semTable)
+		rhsPlan, err := pushJoinPredicate(rhsPreds, node.rhs, semTable)
 		if err != nil {
 			return nil, err
 		}
+
 		return &joinPlan{
-			lhs: node.lhs,
-			rhs: rhsPlan,
+			lhs:   node.lhs,
+			rhs:   rhsPlan,
+			outer: node.outer,
 		}, nil
 	default:
 		panic(fmt.Sprintf("BUG: unknown type %T", node))
@@ -765,6 +360,7 @@ func breakPredicateInLHSandRHS(expr sqlparser.Expr, semTable *semantics.SemTable
 				return false
 			}
 			if deps.IsSolvedBy(lhs) {
+				node.Qualifier.Qualifier = sqlparser.NewTableIdent("")
 				columns = append(columns, node)
 				arg := sqlparser.NewArgument(node.CompliantName())
 				cursor.Replace(arg)
@@ -778,14 +374,18 @@ func breakPredicateInLHSandRHS(expr sqlparser.Expr, semTable *semantics.SemTable
 	return
 }
 
-func mergeOrJoin(lhs, rhs joinTree, joinPredicates []sqlparser.Expr, semTable *semantics.SemTable) (joinTree, error) {
-	newPlan := tryMerge(lhs, rhs, joinPredicates, semTable)
+func mergeOrJoinInner(lhs, rhs joinTree, joinPredicates []sqlparser.Expr, semTable *semantics.SemTable) (joinTree, error) {
+	return mergeOrJoin(lhs, rhs, joinPredicates, semTable, true)
+}
+
+func mergeOrJoin(lhs, rhs joinTree, joinPredicates []sqlparser.Expr, semTable *semantics.SemTable, inner bool) (joinTree, error) {
+	newPlan := tryMerge(lhs, rhs, joinPredicates, semTable, inner)
 	if newPlan != nil {
 		return newPlan, nil
 	}
 
-	tree := &joinPlan{lhs: lhs.clone(), rhs: rhs.clone()}
-	return pushPredicate2(joinPredicates, tree, semTable)
+	tree := &joinPlan{lhs: lhs.clone(), rhs: rhs.clone(), outer: !inner}
+	return pushJoinPredicate(joinPredicates, tree, semTable)
 }
 
 type (
@@ -801,14 +401,24 @@ type (
 	and removes the two inputs to this cheapest plan and instead adds the join.
 	As an optimization, it first only considers joining tables that have predicates defined between them
 */
-func greedySolve(qg *queryGraph, semTable *semantics.SemTable, vschema ContextVSchema) (joinTree, error) {
+func greedySolve(qg *abstract.QueryGraph, semTable *semantics.SemTable, vschema ContextVSchema) (joinTree, error) {
 	joinTrees, err := seedPlanList(qg, semTable, vschema)
 	planCache := cacheMap{}
 	if err != nil {
 		return nil, err
 	}
 
-	crossJoinsOK := false
+	tree, err := mergeJoinTrees(qg, semTable, joinTrees, planCache, false)
+	if err != nil {
+		return nil, err
+	}
+	return tree, nil
+}
+
+func mergeJoinTrees(qg *abstract.QueryGraph, semTable *semantics.SemTable, joinTrees []joinTree, planCache cacheMap, crossJoinsOK bool) (joinTree, error) {
+	if len(joinTrees) == 0 {
+		return nil, nil
+	}
 	for len(joinTrees) > 1 {
 		bestTree, lIdx, rIdx, err := findBestJoinTree(qg, semTable, joinTrees, planCache, crossJoinsOK)
 		if err != nil {
@@ -832,18 +442,17 @@ func greedySolve(qg *queryGraph, semTable *semantics.SemTable, vschema ContextVS
 			crossJoinsOK = true
 		}
 	}
-
 	return joinTrees[0], nil
 }
 
 func (cm cacheMap) getJoinTreeFor(lhs, rhs joinTree, joinPredicates []sqlparser.Expr, semTable *semantics.SemTable) (joinTree, error) {
-	solves := tableSetPair{left: lhs.tables(), right: rhs.tables()}
+	solves := tableSetPair{left: lhs.tableID(), right: rhs.tableID()}
 	cachedPlan := cm[solves]
 	if cachedPlan != nil {
 		return cachedPlan, nil
 	}
 
-	join, err := mergeOrJoin(lhs, rhs, joinPredicates, semTable)
+	join, err := mergeOrJoinInner(lhs, rhs, joinPredicates, semTable)
 	if err != nil {
 		return nil, err
 	}
@@ -852,7 +461,7 @@ func (cm cacheMap) getJoinTreeFor(lhs, rhs joinTree, joinPredicates []sqlparser.
 }
 
 func findBestJoinTree(
-	qg *queryGraph,
+	qg *abstract.QueryGraph,
 	semTable *semantics.SemTable,
 	plans []joinTree,
 	planCache cacheMap,
@@ -863,7 +472,7 @@ func findBestJoinTree(
 			if i == j {
 				continue
 			}
-			joinPredicates := qg.getPredicates(lhs.tables(), rhs.tables())
+			joinPredicates := qg.GetPredicates(lhs.tableID(), rhs.tableID())
 			if len(joinPredicates) == 0 && !crossJoinsOK {
 				// if there are no predicates joining the two tables,
 				// creating a join between them would produce a
@@ -885,7 +494,7 @@ func findBestJoinTree(
 	return bestPlan, lIdx, rIdx, nil
 }
 
-func leftToRightSolve(qg *queryGraph, semTable *semantics.SemTable, vschema ContextVSchema) (joinTree, error) {
+func leftToRightSolve(qg *abstract.QueryGraph, semTable *semantics.SemTable, vschema ContextVSchema) (joinTree, error) {
 	plans, err := seedPlanList(qg, semTable, vschema)
 	if err != nil {
 		return nil, err
@@ -897,8 +506,8 @@ func leftToRightSolve(qg *queryGraph, semTable *semantics.SemTable, vschema Cont
 			acc = plan
 			continue
 		}
-		joinPredicates := qg.getPredicates(acc.tables(), plan.tables())
-		acc, err = mergeOrJoin(acc, plan, joinPredicates, semTable)
+		joinPredicates := qg.GetPredicates(acc.tableID(), plan.tableID())
+		acc, err = mergeOrJoinInner(acc, plan, joinPredicates, semTable)
 		if err != nil {
 			return nil, err
 		}
@@ -908,16 +517,19 @@ func leftToRightSolve(qg *queryGraph, semTable *semantics.SemTable, vschema Cont
 }
 
 // seedPlanList returns a routePlan for each table in the qg
-func seedPlanList(qg *queryGraph, semTable *semantics.SemTable, vschema ContextVSchema) ([]joinTree, error) {
-	plans := make([]joinTree, len(qg.tables))
+func seedPlanList(qg *abstract.QueryGraph, semTable *semantics.SemTable, vschema ContextVSchema) ([]joinTree, error) {
+	plans := make([]joinTree, len(qg.Tables))
 
 	// we start by seeding the table with the single routes
-	for i, table := range qg.tables {
-		solves := semTable.TableSetFor(table.alias)
+	for i, table := range qg.Tables {
+		solves := semTable.TableSetFor(table.Alias)
 		plan, err := createRoutePlan(table, solves, vschema)
 		if err != nil {
 			return nil, err
 		}
+		if qg.NoDeps != nil {
+			plan.predicates = append(plan.predicates, sqlparser.SplitAndExpression(nil, qg.NoDeps)...)
+		}
 		plans[i] = plan
 	}
 	return plans, nil
@@ -927,29 +539,29 @@ func removeAt(plans []joinTree, idx int) []joinTree {
 	return append(plans[:idx], plans[idx+1:]...)
 }
 
-func createRoutePlan(table *queryTable, solves semantics.TableSet, vschema ContextVSchema) (*routePlan, error) {
-	vschemaTable, _, _, _, _, err := vschema.FindTableOrVindex(table.table)
+func createRoutePlan(table *abstract.QueryTable, solves semantics.TableSet, vschema ContextVSchema) (*routePlan, error) {
+	vschemaTable, _, _, _, _, err := vschema.FindTableOrVindex(table.Table)
 	if err != nil {
 		return nil, err
 	}
-	if vschemaTable.Name.String() != table.table.Name.String() {
+	if vschemaTable.Name.String() != table.Table.Name.String() {
 		// we are dealing with a routed table
-		name := table.table.Name
-		table.table.Name = vschemaTable.Name
-		astTable, ok := table.alias.Expr.(sqlparser.TableName)
+		name := table.Table.Name
+		table.Table.Name = vschemaTable.Name
+		astTable, ok := table.Alias.Expr.(sqlparser.TableName)
 		if !ok {
 			return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] a derived table should never be a routed table")
 		}
 		realTableName := sqlparser.NewTableIdent(vschemaTable.Name.String())
 		astTable.Name = realTableName
-		if table.alias.As.IsEmpty() {
+		if table.Alias.As.IsEmpty() {
 			// if the user hasn't specified an alias, we'll insert one here so the old table name still works
-			table.alias.As = sqlparser.NewTableIdent(name.String())
+			table.Alias.As = sqlparser.NewTableIdent(name.String())
 		}
 	}
 	plan := &routePlan{
 		solved: solves,
-		_tables: []*routeTable{{
+		tables: []relation{&routeTable{
 			qtable: table,
 			vtable: vschemaTable,
 		}},
@@ -976,7 +588,7 @@ func createRoutePlan(table *queryTable, solves semantics.TableSet, vschema Conte
 	default:
 		plan.routeOpCode = engine.SelectScatter
 	}
-	err = plan.addPredicate(table.predicates...)
+	err = plan.addPredicate(table.Predicates...)
 	if err != nil {
 		return nil, err
 	}
@@ -990,17 +602,23 @@ func findColumnVindex(a *routePlan, exp sqlparser.Expr, sem *semantics.SemTable)
 		return nil
 	}
 	leftDep := sem.Dependencies(left)
-	for _, table := range a._tables {
-		if leftDep.IsSolvedBy(table.qtable.tableID) {
+
+	var singCol vindexes.SingleColumn
+
+	_ = a.tables.visit(func(table *routeTable) error {
+		if leftDep.IsSolvedBy(table.qtable.TableID) {
 			for _, vindex := range table.vtable.ColumnVindexes {
-				singCol, isSingle := vindex.Vindex.(vindexes.SingleColumn)
+				sC, isSingle := vindex.Vindex.(vindexes.SingleColumn)
 				if isSingle && vindex.Columns[0].Equal(left.Name) {
-					return singCol
+					singCol = sC
+					return io.EOF
 				}
 			}
 		}
-	}
-	return nil
+		return nil
+	})
+
+	return singCol
 }
 
 func canMergeOnFilter(a, b *routePlan, predicate sqlparser.Expr, sem *semantics.SemTable) bool {
@@ -1031,36 +649,28 @@ func canMergeOnFilter(a, b *routePlan, predicate sqlparser.Expr, sem *semantics.
 
 func canMergeOnFilters(a, b *routePlan, joinPredicates []sqlparser.Expr, semTable *semantics.SemTable) bool {
 	for _, predicate := range joinPredicates {
-		if canMergeOnFilter(a, b, predicate, semTable) {
-			return true
+		for _, expr := range sqlparser.SplitAndExpression(nil, predicate) {
+			if canMergeOnFilter(a, b, expr, semTable) {
+				return true
+			}
 		}
 	}
 	return false
 }
 
-func tryMerge(a, b joinTree, joinPredicates []sqlparser.Expr, semTable *semantics.SemTable) joinTree {
-	aRoute, ok := a.(*routePlan)
-	if !ok {
-		return nil
-	}
-	bRoute, ok := b.(*routePlan)
-	if !ok {
-		return nil
-	}
-	if aRoute.keyspace != bRoute.keyspace {
+func tryMerge(a, b joinTree, joinPredicates []sqlparser.Expr, semTable *semantics.SemTable, inner bool) joinTree {
+	aRoute, bRoute := joinTreesToRoutes(a, b)
+	if aRoute == nil || bRoute == nil {
 		return nil
 	}
 
 	newTabletSet := aRoute.solved | bRoute.solved
-	r := &routePlan{
-		routeOpCode: aRoute.routeOpCode,
-		solved:      newTabletSet,
-		_tables:     append(aRoute._tables, bRoute._tables...),
-		predicates: append(
-			append(aRoute.predicates, bRoute.predicates...),
-			joinPredicates...),
-		keyspace:    aRoute.keyspace,
-		vindexPreds: append(aRoute.vindexPreds, bRoute.vindexPreds...),
+
+	var r *routePlan
+	if inner {
+		r = createRoutePlanForInner(aRoute, bRoute, newTabletSet, joinPredicates)
+	} else {
+		r = createRoutePlanForOuter(aRoute, bRoute, semTable, newTabletSet, joinPredicates)
 	}
 
 	switch aRoute.routeOpCode {
@@ -1082,20 +692,109 @@ func tryMerge(a, b joinTree, joinPredicates []sqlparser.Expr, semTable *semantic
 		}
 		r.pickBestAvailableVindex()
 	}
-
 	return r
 }
 
-var _ sort.Interface = (routeTables)(nil)
+func joinTreesToRoutes(a, b joinTree) (*routePlan, *routePlan) {
+	aRoute, ok := a.(*routePlan)
+	if !ok {
+		return nil, nil
+	}
+	bRoute, ok := b.(*routePlan)
+	if !ok {
+		return nil, nil
+	}
+	if aRoute.keyspace != bRoute.keyspace {
+		return nil, nil
+	}
+	return aRoute, bRoute
+}
+
+func createRoutePlanForInner(aRoute *routePlan, bRoute *routePlan, newTabletSet semantics.TableSet, joinPredicates []sqlparser.Expr) *routePlan {
+	var tables parenTables
+	if !aRoute.hasOuterjoins() {
+		tables = append(aRoute.tables, bRoute.tables...)
+	} else {
+		tables = append(parenTables{aRoute.tables}, bRoute.tables...)
+	}
+	return &routePlan{
+		routeOpCode: aRoute.routeOpCode,
+		solved:      newTabletSet,
+		tables:      tables,
+		predicates: append(
+			append(aRoute.predicates, bRoute.predicates...),
+			joinPredicates...),
+		keyspace:    aRoute.keyspace,
+		vindexPreds: append(aRoute.vindexPreds, bRoute.vindexPreds...),
+		leftJoins:   append(aRoute.leftJoins, bRoute.leftJoins...),
+	}
+}
+
+func findTables(deps semantics.TableSet, tables parenTables) (relation, relation, parenTables) {
+	foundTables := parenTables{}
+	newTables := parenTables{}
+
+	for i, t := range tables {
+		if t.tableID().IsSolvedBy(deps) {
+			foundTables = append(foundTables, t)
+			if len(foundTables) == 2 {
+				return foundTables[0], foundTables[1], append(newTables, tables[i:])
+			}
+		} else {
+			newTables = append(newTables, t)
+		}
+	}
+	return nil, nil, tables
+}
+
+func createRoutePlanForOuter(aRoute, bRoute *routePlan, semTable *semantics.SemTable, newTabletSet semantics.TableSet, joinPredicates []sqlparser.Expr) *routePlan {
+	// create relation slice with all tables
+	tables := bRoute.tables
+	// we are doing an outer join where the outer part contains multiple tables - we have to turn the outer part into a join or two
+	for _, predicate := range bRoute.predicates {
+		deps := semTable.Dependencies(predicate)
+		aTbl, bTbl, newTables := findTables(deps, tables)
+		tables = newTables
+		if aTbl != nil && bTbl != nil {
+			tables = append(tables, &leJoin{
+				lhs:  aTbl,
+				rhs:  bTbl,
+				pred: predicate,
+			})
+		}
+	}
+
+	var outer relation
+	if len(tables) == 1 {
+		// if we have a single relation, no need to put it inside parens
+		outer = tables[0]
+	} else {
+		outer = tables
+	}
+
+	return &routePlan{
+		routeOpCode: aRoute.routeOpCode,
+		solved:      newTabletSet,
+		tables:      aRoute.tables,
+		leftJoins: append(aRoute.leftJoins, &outerTable{
+			right: outer,
+			pred:  sqlparser.AndExpressions(joinPredicates...),
+		}),
+		keyspace:    aRoute.keyspace,
+		vindexPreds: append(aRoute.vindexPreds, bRoute.vindexPreds...),
+	}
+}
+
+var _ sort.Interface = (parenTables)(nil)
 
-func (r routeTables) Len() int {
-	return len(r)
+func (p parenTables) Len() int {
+	return len(p)
 }
 
-func (r routeTables) Less(i, j int) bool {
-	return r[i].qtable.tableID < r[j].qtable.tableID
+func (p parenTables) Less(i, j int) bool {
+	return p[i].tableID() < p[j].tableID()
 }
 
-func (r routeTables) Swap(i, j int) {
-	r[i], r[j] = r[j], r[i]
+func (p parenTables) Swap(i, j int) {
+	p[i], p[j] = p[j], p[i]
 }
diff --git a/go/vt/vtgate/planbuilder/select.go b/go/vt/vtgate/planbuilder/select.go
index b9d1f730f3..3a7cba1cd5 100644
--- a/go/vt/vtgate/planbuilder/select.go
+++ b/go/vt/vtgate/planbuilder/select.go
@@ -362,7 +362,7 @@ func isOnlyDual(sel *sqlparser.Select) bool {
 // pushes it down, and updates the route info if the new constraint improves
 // the primitive. This function can push to a WHERE or HAVING clause.
 func (pb *primitiveBuilder) pushFilter(in sqlparser.Expr, whereType string, reservedVars *sqlparser.ReservedVars) error {
-	filters := splitAndExpression(nil, in)
+	filters := sqlparser.SplitAndExpression(nil, in)
 	reorderBySubquery(filters)
 	for _, filter := range filters {
 		pullouts, origin, expr, err := pb.findOrigin(filter, reservedVars)
@@ -377,7 +377,7 @@ func (pb *primitiveBuilder) pushFilter(in sqlparser.Expr, whereType string, rese
 			}
 		}
 		// The returned expression may be complex. Resplit before pushing.
-		for _, subexpr := range splitAndExpression(nil, expr) {
+		for _, subexpr := range sqlparser.SplitAndExpression(nil, expr) {
 			pb.plan, err = planFilter(pb, pb.plan, subexpr, whereType, origin)
 			if err != nil {
 				return err
diff --git a/go/vt/vtgate/planbuilder/selectGen4.go b/go/vt/vtgate/planbuilder/selectGen4.go
index bfa765917c..aa31244a48 100644
--- a/go/vt/vtgate/planbuilder/selectGen4.go
+++ b/go/vt/vtgate/planbuilder/selectGen4.go
@@ -32,6 +32,12 @@ func pushProjection(expr *sqlparser.AliasedExpr, plan logicalPlan, semTable *sem
 	switch node := plan.(type) {
 	case *route:
 		sel := node.Select.(*sqlparser.Select)
+		i := checkIfAlreadyExists(expr, sel)
+		if i != -1 {
+			return i, nil
+		}
+		expr = removeQualifierFromColName(expr)
+
 		offset := len(sel.SelectExprs)
 		sel.SelectExprs = append(sel.SelectExprs, expr)
 		return offset, nil
@@ -61,6 +67,26 @@ func pushProjection(expr *sqlparser.AliasedExpr, plan logicalPlan, semTable *sem
 	}
 }
 
+func removeQualifierFromColName(expr *sqlparser.AliasedExpr) *sqlparser.AliasedExpr {
+	if _, ok := expr.Expr.(*sqlparser.ColName); ok {
+		expr = sqlparser.CloneRefOfAliasedExpr(expr)
+		col := expr.Expr.(*sqlparser.ColName)
+		col.Qualifier.Qualifier = sqlparser.NewTableIdent("")
+	}
+	return expr
+}
+
+func checkIfAlreadyExists(expr *sqlparser.AliasedExpr, sel *sqlparser.Select) int {
+	for i, selectExpr := range sel.SelectExprs {
+		if selectExpr, ok := selectExpr.(*sqlparser.AliasedExpr); ok {
+			if sqlparser.EqualsExpr(selectExpr.Expr, expr.Expr) {
+				return i
+			}
+		}
+	}
+	return -1
+}
+
 func planAggregations(qp *queryProjection, plan logicalPlan, semTable *semantics.SemTable) (logicalPlan, error) {
 	eaggr := &engine.OrderedAggregate{}
 	oa := &orderedAggregate{
diff --git a/go/vt/vtgate/semantics/FakeSI.go b/go/vt/vtgate/semantics/FakeSI.go
new file mode 100644
index 0000000000..41f54b4ba2
--- /dev/null
+++ b/go/vt/vtgate/semantics/FakeSI.go
@@ -0,0 +1,36 @@
+/*
+Copyright 2021 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 semantics
+
+import (
+	"vitess.io/vitess/go/vt/key"
+	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vtgate/vindexes"
+)
+
+var _ SchemaInformation = (*FakeSI)(nil)
+
+// FakeSI is a fake SchemaInformation for testing
+type FakeSI struct {
+	Tables map[string]*vindexes.Table
+}
+
+// FindTableOrVindex implements the SchemaInformation interface
+func (s *FakeSI) FindTableOrVindex(tablename sqlparser.TableName) (*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) {
+	return s.Tables[sqlparser.String(tablename)], nil, "", 0, nil, nil
+}
diff --git a/go/vt/vtgate/semantics/analyzer.go b/go/vt/vtgate/semantics/analyzer.go
index 0594b86309..3bb8d2bcd0 100644
--- a/go/vt/vtgate/semantics/analyzer.go
+++ b/go/vt/vtgate/semantics/analyzer.go
@@ -126,17 +126,24 @@ func (a *analyzer) analyzeTableExpr(tableExpr sqlparser.TableExpr) error {
 	case *sqlparser.AliasedTableExpr:
 		return a.bindTable(table, table.Expr)
 	case *sqlparser.JoinTableExpr:
-		if table.Join != sqlparser.NormalJoinType {
-			return Gen4NotSupportedF("join type %s", table.Join.ToString())
-		}
+		return a.analyzeJoinTableExpr(table)
+	case *sqlparser.ParenTableExpr:
+		return a.analyzeTableExprs(table.Exprs)
+	}
+	return nil
+}
+
+func (a *analyzer) analyzeJoinTableExpr(table *sqlparser.JoinTableExpr) error {
+	switch table.Join {
+	case sqlparser.NormalJoinType, sqlparser.LeftJoinType, sqlparser.RightJoinType:
 		if err := a.analyzeTableExpr(table.LeftExpr); err != nil {
 			return err
 		}
 		if err := a.analyzeTableExpr(table.RightExpr); err != nil {
 			return err
 		}
-	case *sqlparser.ParenTableExpr:
-		return a.analyzeTableExprs(table.Exprs)
+	default:
+		return Gen4NotSupportedF("join type %s", table.Join.ToString())
 	}
 	return nil
 }
@@ -197,6 +204,9 @@ func (a *analyzer) bindTable(alias *sqlparser.AliasedTableExpr, expr sqlparser.S
 	case *sqlparser.DerivedTable:
 		return Gen4NotSupportedF("derived table")
 	case sqlparser.TableName:
+		if sqlparser.SystemSchema(t.Qualifier.String()) {
+			return Gen4NotSupportedF("system tables")
+		}
 		tbl, vdx, _, _, _, err := a.si.FindTableOrVindex(t)
 		if err != nil {
 			return err
diff --git a/go/vt/vtgate/vtgate.go b/go/vt/vtgate/vtgate.go
index e3aaf49fd1..f751d7fc85 100644
--- a/go/vt/vtgate/vtgate.go
+++ b/go/vt/vtgate/vtgate.go
@@ -273,7 +273,6 @@ func Init(ctx context.Context, serv srvtopo.Server, cell string, tabletTypesToWa
 		}
 	})
 	rpcVTGate.registerDebugHealthHandler()
-	rpcVTGate.registerDebugEnvHandler()
 	err := initQueryLogger(rpcVTGate)
 	if err != nil {
 		log.Fatalf("error initializing query logger: %v", err)
@@ -322,12 +321,6 @@ func resolveAndLoadKeyspace(ctx context.Context, srvResolver *srvtopo.Resolver,
 	}
 }
 
-func (vtg *VTGate) registerDebugEnvHandler() {
-	http.HandleFunc("/debug/env", func(w http.ResponseWriter, r *http.Request) {
-		debugEnvHandler(vtg, w, r)
-	})
-}
-
 func (vtg *VTGate) registerDebugHealthHandler() {
 	http.HandleFunc("/debug/health", func(w http.ResponseWriter, r *http.Request) {
 		if err := acl.CheckAccessHTTP(r, acl.MONITORING); err != nil {
diff --git a/go/vt/vttablet/tabletmanager/tm_state.go b/go/vt/vttablet/tabletmanager/tm_state.go
index 124b9f020e..be10b40db6 100644
--- a/go/vt/vttablet/tabletmanager/tm_state.go
+++ b/go/vt/vttablet/tabletmanager/tm_state.go
@@ -239,6 +239,7 @@ func (ts *tmState) updateLocked(ctx context.Context) {
 	// before other services are shutdown.
 	reason := ts.canServe(ts.tablet.Type)
 	if reason != "" {
+		ts.populateLocalMetadataLocked()
 		log.Infof("Disabling query service: %v", reason)
 		if err := ts.tm.QueryServiceControl.SetServingType(ts.tablet.Type, terTime, false, reason); err != nil {
 			log.Errorf("SetServingType(serving=false) failed: %v", err)
@@ -280,6 +281,8 @@ func (ts *tmState) updateLocked(ctx context.Context) {
 		if err := ts.tm.QueryServiceControl.SetServingType(ts.tablet.Type, terTime, true, ""); err != nil {
 			log.Errorf("Cannot start query service: %v", err)
 		}
+
+		ts.populateLocalMetadataLocked()
 	}
 }
 
diff --git a/go/vt/vttablet/tabletserver/debugenv.go b/go/vt/vttablet/tabletserver/debugenv.go
index 21c140be98..314d8d21fa 100644
--- a/go/vt/vttablet/tabletserver/debugenv.go
+++ b/go/vt/vttablet/tabletserver/debugenv.go
@@ -169,7 +169,7 @@ func debugEnvHandler(tsv *TabletServer, w http.ResponseWriter, r *http.Request)
 	w.Write(debugEnvHeader)
 	for _, v := range vars {
 		if err := debugEnvRow.Execute(w, v); err != nil {
-			log.Errorf("debugenv: couldn't execute template: %v", err)
+			log.Errorf("queryz: couldn't execute template: %v", err)
 		}
 	}
 	w.Write(endTable)


Test patch:
diff --git a/go/test/endtoend/reparent/utils_test.go b/go/test/endtoend/reparent/utils_test.go
index 44bf8ec918..fa97ebef4f 100644
--- a/go/test/endtoend/reparent/utils_test.go
+++ b/go/test/endtoend/reparent/utils_test.go
@@ -116,7 +116,6 @@ func setupCluster(ctx context.Context, t *testing.T, shardName string, cells []s
 	clusterInstance.VtTabletExtraArgs = []string{
 		"-lock_tables_timeout", "5s",
 		"-enable_semi_sync",
-		"-init_populate_metadata",
 		"-track_schema_versions=true",
 	}
 
diff --git a/go/vt/sqlparser/analyzer_test.go b/go/vt/sqlparser/analyzer_test.go
index cf755b8241..8fed503421 100644
--- a/go/vt/sqlparser/analyzer_test.go
+++ b/go/vt/sqlparser/analyzer_test.go
@@ -167,6 +167,76 @@ func TestSplitAndExpression(t *testing.T) {
 	}
 }
 
+func TestAndExpressions(t *testing.T) {
+	greaterThanExpr := &ComparisonExpr{
+		Operator: GreaterThanOp,
+		Left: &ColName{
+			Name: NewColIdent("val"),
+			Qualifier: TableName{
+				Name: NewTableIdent("a"),
+			},
+		},
+		Right: &ColName{
+			Name: NewColIdent("val"),
+			Qualifier: TableName{
+				Name: NewTableIdent("b"),
+			},
+		},
+	}
+	equalExpr := &ComparisonExpr{
+		Operator: EqualOp,
+		Left: &ColName{
+			Name: NewColIdent("id"),
+			Qualifier: TableName{
+				Name: NewTableIdent("a"),
+			},
+		},
+		Right: &ColName{
+			Name: NewColIdent("id"),
+			Qualifier: TableName{
+				Name: NewTableIdent("b"),
+			},
+		},
+	}
+	testcases := []struct {
+		name           string
+		expressions    Exprs
+		expectedOutput Expr
+	}{
+		{
+			name:           "empty input",
+			expressions:    nil,
+			expectedOutput: nil,
+		}, {
+			name: "two equal inputs",
+			expressions: Exprs{
+				greaterThanExpr,
+				equalExpr,
+				equalExpr,
+			},
+			expectedOutput: &AndExpr{
+				Left:  greaterThanExpr,
+				Right: equalExpr,
+			},
+		},
+		{
+			name: "two equal inputs",
+			expressions: Exprs{
+				equalExpr,
+				equalExpr,
+			},
+			expectedOutput: equalExpr,
+		},
+	}
+
+	for _, testcase := range testcases {
+		t.Run(testcase.name, func(t *testing.T) {
+			output := AndExpressions(testcase.expressions...)
+			assert.Equal(t, String(testcase.expectedOutput), String(output))
+		})
+	}
+}
+
 func TestTableFromStatement(t *testing.T) {
 	testcases := []struct {
 		in, out string
diff --git a/go/vt/vtgate/engine/join_test.go b/go/vt/vtgate/engine/join_test.go
index 4695ad978d..67c0973b9d 100644
--- a/go/vt/vtgate/engine/join_test.go
+++ b/go/vt/vtgate/engine/join_test.go
@@ -68,7 +68,7 @@ func TestJoinExecute(t *testing.T) {
 
 	// Normal join
 	jn := &Join{
-		Opcode: NormalJoin,
+		Opcode: InnerJoin,
 		Left:   leftPrim,
 		Right:  rightPrim,
 		Cols:   []int{-1, -2, 1, 2},
@@ -185,7 +185,7 @@ func TestJoinExecuteMaxMemoryRows(t *testing.T) {
 
 		// Normal join
 		jn := &Join{
-			Opcode: NormalJoin,
+			Opcode: InnerJoin,
 			Left:   leftPrim,
 			Right:  rightPrim,
 			Cols:   []int{-1, -2, 1, 2},
@@ -227,7 +227,7 @@ func TestJoinExecuteNoResult(t *testing.T) {
 	}
 
 	jn := &Join{
-		Opcode: NormalJoin,
+		Opcode: InnerJoin,
 		Left:   leftPrim,
 		Right:  rightPrim,
 		Cols:   []int{-1, -2, 1, 2},
@@ -262,7 +262,7 @@ func TestJoinExecuteErrors(t *testing.T) {
 	}
 
 	jn := &Join{
-		Opcode: NormalJoin,
+		Opcode: InnerJoin,
 		Left:   leftPrim,
 	}
 	_, err := jn.Execute(&noopVCursor{}, map[string]*querypb.BindVariable{}, true)
@@ -287,7 +287,7 @@ func TestJoinExecuteErrors(t *testing.T) {
 	}
 
 	jn = &Join{
-		Opcode: NormalJoin,
+		Opcode: InnerJoin,
 		Left:   leftPrim,
 		Right:  rightPrim,
 		Cols:   []int{-1, -2, 1, 2},
@@ -314,7 +314,7 @@ func TestJoinExecuteErrors(t *testing.T) {
 	}
 
 	jn = &Join{
-		Opcode: NormalJoin,
+		Opcode: InnerJoin,
 		Left:   leftPrim,
 		Right:  rightPrim,
 		Cols:   []int{-1, -2, 1, 2},
@@ -368,7 +368,7 @@ func TestJoinStreamExecute(t *testing.T) {
 
 	// Normal join
 	jn := &Join{
-		Opcode: NormalJoin,
+		Opcode: InnerJoin,
 		Left:   leftPrim,
 		Right:  rightPrim,
 		Cols:   []int{-1, -2, 1, 2},
@@ -456,7 +456,7 @@ func TestGetFields(t *testing.T) {
 	}
 
 	jn := &Join{
-		Opcode: NormalJoin,
+		Opcode: InnerJoin,
 		Left:   leftPrim,
 		Right:  rightPrim,
 		Cols:   []int{-1, -2, 1, 2},
@@ -493,7 +493,7 @@ func TestGetFieldsErrors(t *testing.T) {
 	}
 
 	jn := &Join{
-		Opcode: NormalJoin,
+		Opcode: InnerJoin,
 		Left:   leftPrim,
 		Right:  rightPrim,
 		Cols:   []int{-1, -2, 1, 2},
diff --git a/go/vt/vtgate/planbuilder/fuzz_test.go b/go/vt/vtgate/planbuilder/abstract/fuzz_test.go
similarity index 98%
rename from go/vt/vtgate/planbuilder/fuzz_test.go
rename to go/vt/vtgate/planbuilder/abstract/fuzz_test.go
index 7413c0a405..14b00dc94c 100644
--- a/go/vt/vtgate/planbuilder/fuzz_test.go
+++ b/go/vt/vtgate/planbuilder/abstract/fuzz_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package planbuilder
+package abstract
 
 import (
 	"io/ioutil"
diff --git a/go/vt/vtgate/planbuilder/abstract/operator_test.go b/go/vt/vtgate/planbuilder/abstract/operator_test.go
new file mode 100644
index 0000000000..5eedf851c5
--- /dev/null
+++ b/go/vt/vtgate/planbuilder/abstract/operator_test.go
@@ -0,0 +1,260 @@
+/*
+Copyright 2021 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 abstract
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"vitess.io/vitess/go/vt/sqlparser"
+	"vitess.io/vitess/go/vt/vtgate/semantics"
+)
+
+func TestOperator(t *testing.T) {
+	type tcase struct {
+		input, output string
+	}
+	tcases := []tcase{{
+		input: "select * from t",
+		output: `QueryGraph: {
+Tables:
+	1:t
+}`,
+	}, {
+		input: "select t.c from t,y,z where t.c = y.c and (t.a = z.a or t.a = y.a) and 1 < 2",
+		output: `QueryGraph: {
+Tables:
+	1:t
+	2:y
+	4:z
+JoinPredicates:
+	1:2 - t.c = y.c
+	1:2:4 - t.a = z.a or t.a = y.a
+ForAll: 1 < 2
+}`,
+	}, {
+		input: "select t.c from t join y on t.id = y.t_id join z on t.id = z.t_id where t.name = 'foo' and y.col = 42 and z.baz = 101",
+		output: `QueryGraph: {
+Tables:
+	1:t where t.` + "`name`" + ` = 'foo'
+	2:y where y.col = 42
+	4:z where z.baz = 101
+JoinPredicates:
+	1:2 - t.id = y.t_id
+	1:4 - t.id = z.t_id
+}`,
+	}, {
+		input: "select t.c from t,y,z where t.name = 'foo' and y.col = 42 and z.baz = 101 and t.id = y.t_id and t.id = z.t_id",
+		output: `QueryGraph: {
+Tables:
+	1:t where t.` + "`name`" + ` = 'foo'
+	2:y where y.col = 42
+	4:z where z.baz = 101
+JoinPredicates:
+	1:2 - t.id = y.t_id
+	1:4 - t.id = z.t_id
+}`,
+	}, {
+		input: "select 1 from t where '1' = 1 and 12 = '12'",
+		output: `QueryGraph: {
+Tables:
+	1:t
+ForAll: '1' = 1 and 12 = '12'
+}`,
+	}, {
+		input: "select 1 from t left join s on t.id = s.id",
+		output: `OuterJoin: {
+	Inner: 	QueryGraph: {
+	Tables:
+		1:t
+	}
+	Outer: 	QueryGraph: {
+	Tables:
+		2:s
+	}
+	Predicate: t.id = s.id
+}`,
+	}, {
+		input: "select 1 from t join s on t.id = s.id and t.name = s.name",
+		output: `QueryGraph: {
+Tables:
+	1:t
+	2:s
+JoinPredicates:
+	1:2 - t.id = s.id and t.` + "`name`" + ` = s.` + "`name`" + `
+}`,
+	}, {
+		input: "select 1 from t left join s on t.id = s.id where t.name = 'Mister'",
+		output: `OuterJoin: {
+	Inner: 	QueryGraph: {
+	Tables:
+		1:t where t.` + "`name`" + ` = 'Mister'
+	}
+	Outer: 	QueryGraph: {
+	Tables:
+		2:s
+	}
+	Predicate: t.id = s.id
+}`,
+	}, {
+		input: "select 1 from t right join s on t.id = s.id",
+		output: `OuterJoin: {
+	Inner: 	QueryGraph: {
+	Tables:
+		2:s
+	}
+	Outer: 	QueryGraph: {
+	Tables:
+		1:t
+	}
+	Predicate: t.id = s.id
+}`,
+	}, {
+		input: "select 1 from (a left join b on a.id = b.id) join (c left join d on c.id = d.id) on a.id = c.id",
+		output: `Join: {
+	LHS: 	OuterJoin: {
+		Inner: 	QueryGraph: {
+		Tables:
+			1:a
+		}
+		Outer: 	QueryGraph: {
+		Tables:
+			2:b
+		}
+		Predicate: a.id = b.id
+	}
+	RHS: 	OuterJoin: {
+		Inner: 	QueryGraph: {
+		Tables:
+			4:c
+		}
+		Outer: 	QueryGraph: {
+		Tables:
+			8:d
+		}
+		Predicate: c.id = d.id
+	}
+	Predicate: a.id = c.id
+}`,
+	}}
+
+	for i, tc := range tcases {
+		sql := tc.input
+		t.Run(fmt.Sprintf("%d %s", i, sql), func(t *testing.T) {
+			tree, err := sqlparser.Parse(sql)
+			require.NoError(t, err)
+			semTable, err := semantics.Analyze(tree, "", &semantics.FakeSI{})
+			require.NoError(t, err)
+			optree, err := CreateOperatorFromSelect(tree.(*sqlparser.Select), semTable)
+			require.NoError(t, err)
+			assert.Equal(t, tc.output, testString(optree))
+			if t.Failed() {
+				fmt.Println(testString(optree))
+			}
+		})
+	}
+}
+
+func testString(op Operator) string {
+	switch op := op.(type) {
+	case *QueryGraph:
+		return fmt.Sprintf("QueryGraph: %s", op.testString())
+	case *Join:
+		leftStr := indent(testString(op.LHS))
+		rightStr := indent(testString(op.RHS))
+		return fmt.Sprintf("Join: {\n\tLHS: %s\n\tRHS: %s\n\tPredicate: %s\n}", leftStr, rightStr, sqlparser.String(op.Exp))
+	case *LeftJoin:
+		leftStr := indent(testString(op.Left))
+		rightStr := indent(testString(op.Right))
+		return fmt.Sprintf("OuterJoin: {\n\tInner: %s\n\tOuter: %s\n\tPredicate: %s\n}", leftStr, rightStr, sqlparser.String(op.Predicate))
+	}
+	return "implement me"
+}
+
+func indent(s string) string {
+	lines := strings.Split(s, "\n")
+	for i, line := range lines {
+		lines[i] = "\t" + line
+	}
+	return strings.Join(lines, "\n")
+}
+
+func (qt *QueryTable) testString() string {
+	var alias string
+	if !qt.Alias.As.IsEmpty() {
+		alias = " AS " + sqlparser.String(qt.Alias.As)
+	}
+	var preds []string
+	for _, predicate := range qt.Predicates {
+		preds = append(preds, sqlparser.String(predicate))
+	}
+	var where string
+	if len(preds) > 0 {
+		where = " where " + strings.Join(preds, " and ")
+	}
+
+	return fmt.Sprintf("\t%d:%s%s%s", qt.TableID, sqlparser.String(qt.Table), alias, where)
+}
+
+func (qg *QueryGraph) testString() string {
+	return fmt.Sprintf(`{
+Tables:
+%s%s%s
+}`, strings.Join(qg.tableNames(), "\n"), qg.crossPredicateString(), qg.noDepsString())
+}
+
+func (qg *QueryGraph) crossPredicateString() string {
+	if len(qg.innerJoins) == 0 {
+		return ""
+	}
+	var joinPreds []string
+	for deps, predicates := range qg.innerJoins {
+		var tables []string
+		for _, id := range deps.Constituents() {
+			tables = append(tables, fmt.Sprintf("%d", id))
+		}
+		var expressions []string
+		for _, expr := range predicates {
+			expressions = append(expressions, sqlparser.String(expr))
+		}
+		tableConcat := strings.Join(tables, ":")
+		exprConcat := strings.Join(expressions, " and ")
+		joinPreds = append(joinPreds, fmt.Sprintf("\t%s - %s", tableConcat, exprConcat))
+	}
+	sort.Strings(joinPreds)
+	return fmt.Sprintf("\nJoinPredicates:\n%s", strings.Join(joinPreds, "\n"))
+}
+
+func (qg *QueryGraph) tableNames() []string {
+	var tables []string
+	for _, t := range qg.Tables {
+		tables = append(tables, t.testString())
+	}
+	return tables
+}
+
+func (qg *QueryGraph) noDepsString() string {
+	if qg.NoDeps == nil {
+		return ""
+	}
+	return fmt.Sprintf("\nForAll: %s", sqlparser.String(qg.NoDeps))
+}
diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go
index 073a02846b..bb4978f424 100644
--- a/go/vt/vtgate/planbuilder/plan_test.go
+++ b/go/vt/vtgate/planbuilder/plan_test.go
@@ -472,6 +472,9 @@ func testFile(t *testing.T, filename, tempDir string, vschema *vschemaWrapper, c
 
 			vschema.version = Gen4
 			out, err := getPlanOutput(tcase, vschema)
+			if err != nil && tcase.output2ndPlanner == "" && strings.HasPrefix(err.Error(), "gen4 does not yet support") {
+				continue
+			}
 
 			// our expectation for the new planner on this query is one of three
 			//  - it produces the same plan as V3 - this is shown using empty brackets: {\n}
diff --git a/go/vt/vtgate/planbuilder/querygraph_test.go b/go/vt/vtgate/planbuilder/querygraph_test.go
deleted file mode 100644
index 76a6052756..0000000000
--- a/go/vt/vtgate/planbuilder/querygraph_test.go
+++ /dev/null
@@ -1,248 +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"
-	"sort"
-	"strings"
-	"testing"
-
-	"vitess.io/vitess/go/vt/key"
-	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
-	"vitess.io/vitess/go/vt/vtgate/vindexes"
-
-	"github.com/stretchr/testify/assert"
-
-	"vitess.io/vitess/go/test/utils"
-
-	"github.com/stretchr/testify/require"
-
-	"vitess.io/vitess/go/vt/sqlparser"
-	"vitess.io/vitess/go/vt/vtgate/semantics"
-)
-
-type tcase struct {
-	input, output string
-}
-
-var tcases = []tcase{{
-	input: "select * from t",
-	output: `{
-Tables:
-	1:t
-}`,
-}, {
-	input: "select t.c from t,y,z where t.c = y.c and (t.a = z.a or t.a = y.a) and 1 < 2",
-	output: `{
-Tables:
-	1:t
-	2:y
-	4:z
-JoinPredicates:
-	1:2 - t.c = y.c
-	1:2:4 - t.a = z.a or t.a = y.a
-ForAll: 1 < 2
-}`,
-}, {
-	input: "select t.c from t join y on t.id = y.t_id join z on t.id = z.t_id where t.name = 'foo' and y.col = 42 and z.baz = 101",
-	output: `{
-Tables:
-	1:t where t.` + "`name`" + ` = 'foo'
-	2:y where y.col = 42
-	4:z where z.baz = 101
-JoinPredicates:
-	1:2 - t.id = y.t_id
-	1:4 - t.id = z.t_id
-}`,
-}, {
-	input: "select t.c from t,y,z where t.name = 'foo' and y.col = 42 and z.baz = 101 and t.id = y.t_id and t.id = z.t_id",
-	output: `{
-Tables:
-	1:t where t.` + "`name`" + ` = 'foo'
-	2:y where y.col = 42
-	4:z where z.baz = 101
-JoinPredicates:
-	1:2 - t.id = y.t_id
-	1:4 - t.id = z.t_id
-}`,
-}, {
-	input: "select 1 from t where '1' = 1 and 12 = '12'",
-	output: `{
-Tables:
-	1:t
-ForAll: '1' = 1 and 12 = '12'
-}`,
-}, {
-	input: "select 1 from t where exists (select 1)",
-	output: `{
-Tables:
-	1:t
-ForAll: exists (select 1 from dual)
-SubQueries:
-(select 1 from dual) - 	{
-	Tables:
-		2:dual
-	}
-}`,
-}}
-
-func equals(left, right sqlparser.Expr) sqlparser.Expr {
-	return &sqlparser.ComparisonExpr{
-		Operator: sqlparser.EqualOp,
-		Left:     left,
-		Right:    right,
-	}
-}
-
-func colName(table, column string) *sqlparser.ColName {
-	return &sqlparser.ColName{Name: sqlparser.NewColIdent(column), Qualifier: tableName(table)}
-}
-
-func tableName(name string) sqlparser.TableName {
-	return sqlparser.TableName{Name: sqlparser.NewTableIdent(name)}
-}
-
-func TestQueryGraph(t *testing.T) {
-	for i, tc := range tcases {
-		sql := tc.input
-		t.Run(fmt.Sprintf("%d %s", i, sql), func(t *testing.T) {
-			tree, err := sqlparser.Parse(sql)
-			require.NoError(t, err)
-			semTable, err := semantics.Analyze(tree, "", &fakeSI{})
-			require.NoError(t, err)
-			qgraph, err := createQGFromSelect(tree.(*sqlparser.Select), semTable)
-			require.NoError(t, err)
-			assert.Equal(t, tc.output, qgraph.testString())
-			utils.MustMatch(t, tc.output, qgraph.testString(), "incorrect query graph")
-		})
-	}
-}
-
-func TestString(t *testing.T) {
-	tree, err := sqlparser.Parse("select * from a,b join c on b.id = c.id where a.id = b.id and b.col IN (select 42) and func() = 'foo'")
-	require.NoError(t, err)
-	semTable, err := semantics.Analyze(tree, "", &fakeSI{})
-	require.NoError(t, err)
-	qgraph, err := createQGFromSelect(tree.(*sqlparser.Select), semTable)
-	require.NoError(t, err)
-	utils.MustMatch(t, `{
-Tables:
-	1:a
-	2:b where b.col in (select 42 from dual)
-	4:c
-JoinPredicates:
-	1:2 - a.id = b.id
-	2:4 - b.id = c.id
-ForAll: func() = 'foo'
-SubQueries:
-(select 42 from dual) - 	{
-	Tables:
-		8:dual
-	}
-}`, qgraph.testString())
-}
-
-func (qt *queryTable) testString() string {
-	var alias string
-	if !qt.alias.As.IsEmpty() {
-		alias = " AS " + sqlparser.String(qt.alias.As)
-	}
-	var preds []string
-	for _, predicate := range qt.predicates {
-		preds = append(preds, sqlparser.String(predicate))
-	}
-	var where string
-	if len(preds) > 0 {
-		where = " where " + strings.Join(preds, " and ")
-	}
-
-	return fmt.Sprintf("\t%d:%s%s%s", qt.tableID, sqlparser.String(qt.table), alias, where)
-}
-
-func (qg *queryGraph) testString() string {
-	return fmt.Sprintf(`{
-Tables:
-%s%s%s%s
-}`, strings.Join(qg.tableNames(), "\n"), qg.crossPredicateString(), qg.noDepsString(), qg.subqueriesString())
-}
-
-func (qg *queryGraph) crossPredicateString() string {
-	if len(qg.crossTable) == 0 {
-		return ""
-	}
-	var joinPreds []string
-	for deps, predicates := range qg.crossTable {
-		var tables []string
-		for _, id := range deps.Constituents() {
-			tables = append(tables, fmt.Sprintf("%d", id))
-		}
-		var expressions []string
-		for _, expr := range predicates {
-			expressions = append(expressions, sqlparser.String(expr))
-		}
-		tableConcat := strings.Join(tables, ":")
-		exprConcat := strings.Join(expressions, " and ")
-		joinPreds = append(joinPreds, fmt.Sprintf("\t%s - %s", tableConcat, exprConcat))
-	}
-	sort.Strings(joinPreds)
-	return fmt.Sprintf("\nJoinPredicates:\n%s", strings.Join(joinPreds, "\n"))
-}
-
-func (qg *queryGraph) tableNames() []string {
-	var tables []string
-	for _, t := range qg.tables {
-		tables = append(tables, t.testString())
-	}
-	return tables
-}
-
-func (qg *queryGraph) subqueriesString() string {
-	if len(qg.subqueries) == 0 {
-		return ""
-	}
-	var graphs []string
-	for sq, qgraphs := range qg.subqueries {
-		key := sqlparser.String(sq)
-		for _, inner := range qgraphs {
-			str := inner.testString()
-			splitInner := strings.Split(str, "\n")
-			for i, s := range splitInner {
-				splitInner[i] = "\t" + s
-			}
-			graphs = append(graphs, fmt.Sprintf("%s - %s", key, strings.Join(splitInner, "\n")))
-		}
-	}
-	return fmt.Sprintf("\nSubQueries:\n%s", strings.Join(graphs, "\n"))
-}
-
-func (qg *queryGraph) noDepsString() string {
-	if qg.noDeps == nil {
-		return ""
-	}
-	return fmt.Sprintf("\nForAll: %s", sqlparser.String(qg.noDeps))
-}
-
-var _ semantics.SchemaInformation = (*fakeSI)(nil)
-
-type fakeSI struct {
-	tables map[string]*vindexes.Table
-}
-
-func (s *fakeSI) FindTableOrVindex(tablename sqlparser.TableName) (*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) {
-	return s.tables[sqlparser.String(tablename)], nil, "", 0, nil, nil
-}
diff --git a/go/vt/vtgate/planbuilder/route_planning_test.go b/go/vt/vtgate/planbuilder/route_planning_test.go
index 0def30a8d0..e3d8517c7f 100644
--- a/go/vt/vtgate/planbuilder/route_planning_test.go
+++ b/go/vt/vtgate/planbuilder/route_planning_test.go
@@ -20,6 +20,8 @@ import (
 	"fmt"
 	"testing"
 
+	"vitess.io/vitess/go/vt/vtgate/planbuilder/abstract"
+
 	"github.com/stretchr/testify/require"
 
 	"vitess.io/vitess/go/sqltypes"
@@ -103,7 +105,7 @@ func TestMergeJoins(t *testing.T) {
 	}}
 	for i, tc := range tests {
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
-			result := tryMerge(tc.l, tc.r, tc.predicates, semantics.NewSemTable())
+			result := tryMerge(tc.l, tc.r, tc.predicates, semantics.NewSemTable(), true)
 			assert.Equal(t, tc.expected, result)
 		})
 	}
@@ -127,9 +129,70 @@ func TestClone(t *testing.T) {
 	assert.Nil(t, original.vindexPreds[0].foundVindex)
 }
 
+func TestCreateRoutePlanForOuter(t *testing.T) {
+	assert := assert.New(t)
+	m1 := &routeTable{
+		qtable: &abstract.QueryTable{
+			TableID: semantics.TableSet(1),
+			Table:   sqlparser.TableName{},
+		},
+		vtable: &vindexes.Table{},
+	}
+	m2 := &routeTable{
+		qtable: &abstract.QueryTable{
+			TableID: semantics.TableSet(2),
+			Table:   sqlparser.TableName{},
+		},
+		vtable: &vindexes.Table{},
+	}
+	m3 := &routeTable{
+		qtable: &abstract.QueryTable{
+			TableID: semantics.TableSet(4),
+			Table:   sqlparser.TableName{},
+		},
+		vtable: &vindexes.Table{},
+	}
+	a := &routePlan{
+		routeOpCode: engine.SelectUnsharded,
+		solved:      semantics.TableSet(1),
+		tables:      []relation{m1},
+	}
+	col1 := sqlparser.NewColNameWithQualifier("id", sqlparser.TableName{
+		Name: sqlparser.NewTableIdent("m1"),
+	})
+	col2 := sqlparser.NewColNameWithQualifier("id", sqlparser.TableName{
+		Name: sqlparser.NewTableIdent("m2"),
+	})
+	b := &routePlan{
+		routeOpCode: engine.SelectUnsharded,
+		solved:      semantics.TableSet(6),
+		tables:      []relation{m2, m3},
+		predicates:  []sqlparser.Expr{equals(col1, col2)},
+	}
+	semTable := semantics.NewSemTable()
+	merge := tryMerge(a, b, []sqlparser.Expr{}, semTable, false)
+	assert.NotNil(merge)
+}
+
+func equals(left, right sqlparser.Expr) sqlparser.Expr {
+	return &sqlparser.ComparisonExpr{
+		Operator: sqlparser.EqualOp,
+		Left:     left,
+		Right:    right,
+	}
+}
+
+func colName(table, column string) *sqlparser.ColName {
+	return &sqlparser.ColName{Name: sqlparser.NewColIdent(column), Qualifier: tableName(table)}
+}
+
+func tableName(name string) sqlparser.TableName {
+	return sqlparser.TableName{Name: sqlparser.NewTableIdent(name)}
+}
+
 func TestExpandStar(t *testing.T) {
-	schemaInfo := &fakeSI{
-		tables: map[string]*vindexes.Table{
+	schemaInfo := &semantics.FakeSI{
+		Tables: map[string]*vindexes.Table{
 			"t1": {
 				Name: sqlparser.NewTableIdent("t1"),
 				Columns: []vindexes.Column{{
@@ -225,7 +288,7 @@ func TestExpandStar(t *testing.T) {
 }
 
 func TestSemTableDependenciesAfterExpandStar(t *testing.T) {
-	schemaInfo := &fakeSI{tables: map[string]*vindexes.Table{
+	schemaInfo := &semantics.FakeSI{Tables: map[string]*vindexes.Table{
 		"t1": {
 			Name: sqlparser.NewTableIdent("t1"),
 			Columns: []vindexes.Column{{
diff --git a/go/vt/vtgate/planbuilder/testdata/filter_cases.txt b/go/vt/vtgate/planbuilder/testdata/filter_cases.txt
index 480314df07..c8370b7562 100644
--- a/go/vt/vtgate/planbuilder/testdata/filter_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/filter_cases.txt
@@ -445,6 +445,7 @@ Gen4 plan same as above
     "Table": "`user`"
   }
 }
+Gen4 plan same as above
 
 # Single table complex in clause
 "select id from user where name in (col, 'bb')"
@@ -749,6 +750,7 @@ Gen4 plan same as above
     ]
   }
 }
+Gen4 plan same as above
 
 # Multi-route with non-route constraint, should use first route.
 "select user_extra.id from user join user_extra on user.col = user_extra.col where 1 = 1"
@@ -989,7 +991,7 @@ Gen4 plan same as above
   "Instructions": {
     "OperatorType": "Join",
     "Variant": "Join",
-    "JoinColumnIndexes": "-2",
+    "JoinColumnIndexes": "-1",
     "TableName": "unsharded_`user`",
     "Inputs": [
       {
@@ -999,8 +1001,8 @@ Gen4 plan same as above
           "Name": "main",
           "Sharded": false
         },
-        "FieldQuery": "select unsharded.id, unsharded.id from unsharded where 1 != 1",
-        "Query": "select unsharded.id, unsharded.id from unsharded",
+        "FieldQuery": "select unsharded.id from unsharded where 1 != 1",
+        "Query": "select unsharded.id from unsharded",
         "Table": "unsharded"
       },
       {
@@ -1617,6 +1619,7 @@ Gen4 plan same as above
     "Table": "`user`"
   }
 }
+Gen4 plan same as above
 
 # outer and inner subquery route reference the same "uu.id" name
 # but they refer to different things. The first reference is to the outermost query,
diff --git a/go/vt/vtgate/planbuilder/testdata/from_cases.txt b/go/vt/vtgate/planbuilder/testdata/from_cases.txt
index b872815cac..a90000dcea 100644
--- a/go/vt/vtgate/planbuilder/testdata/from_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/from_cases.txt
@@ -108,6 +108,21 @@ Gen4 plan same as above
     "Table": "unsharded"
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select m1.col from unsharded as m1 join unsharded as m2",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectUnsharded",
+    "Keyspace": {
+      "Name": "main",
+      "Sharded": false
+    },
+    "FieldQuery": "select m1.col from unsharded as m1, unsharded as m2 where 1 != 1",
+    "Query": "select m1.col from unsharded as m1, unsharded as m2",
+    "Table": "unsharded"
+  }
+}
 
 # Multi-table, multi-chunk
 "select music.col from user join music"
@@ -357,6 +372,7 @@ Gen4 plan same as above
     "Table": "unsharded"
   }
 }
+Gen4 plan same as above
 
 # Left join, multi-chunk
 "select u.col from user u left join unsharded m on u.a = m.b"
@@ -394,16 +410,50 @@ Gen4 plan same as above
     ]
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select u.col from user u left join unsharded m on u.a = m.b",
+  "Instructions": {
+    "OperatorType": "Join",
+    "Variant": "LeftJoin",
+    "JoinColumnIndexes": "-2",
+    "TableName": "`user`_unsharded",
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectScatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select u.a, u.col from `user` as u where 1 != 1",
+        "Query": "select u.a, u.col from `user` as u",
+        "Table": "`user`"
+      },
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectUnsharded",
+        "Keyspace": {
+          "Name": "main",
+          "Sharded": false
+        },
+        "FieldQuery": "select 1 from unsharded as m where 1 != 1",
+        "Query": "select 1 from unsharded as m where m.b = :u_a",
+        "Table": "unsharded"
+      }
+    ]
+  }
+}
 
 # Three-way left join
-"select user.col from user left join unsharded as m1 on user.col = m1.co left join unsharded as m2 on m1.col = m2.col"
+"select user.col, m2.foo from user left join unsharded as m1 on user.col = m1.col left join unsharded as m2 on m1.col = m2.col"
 {
   "QueryType": "SELECT",
-  "Original": "select user.col from user left join unsharded as m1 on user.col = m1.co left join unsharded as m2 on m1.col = m2.col",
+  "Original": "select user.col, m2.foo from user left join unsharded as m1 on user.col = m1.col left join unsharded as m2 on m1.col = m2.col",
   "Instructions": {
     "OperatorType": "Join",
     "Variant": "LeftJoin",
-    "JoinColumnIndexes": "-1",
+    "JoinColumnIndexes": "-1,1",
     "TableName": "`user`_unsharded_unsharded",
     "Inputs": [
       {
@@ -431,7 +481,7 @@ Gen4 plan same as above
               "Sharded": false
             },
             "FieldQuery": "select m1.col from unsharded as m1 where 1 != 1",
-            "Query": "select m1.col from unsharded as m1 where m1.co = :user_col",
+            "Query": "select m1.col from unsharded as m1 where m1.col = :user_col",
             "Table": "unsharded"
           }
         ]
@@ -443,8 +493,61 @@ Gen4 plan same as above
           "Name": "main",
           "Sharded": false
         },
-        "FieldQuery": "select 1 from unsharded as m2 where 1 != 1",
-        "Query": "select 1 from unsharded as m2 where m2.col = :m1_col",
+        "FieldQuery": "select m2.foo from unsharded as m2 where 1 != 1",
+        "Query": "select m2.foo from unsharded as m2 where m2.col = :m1_col",
+        "Table": "unsharded"
+      }
+    ]
+  }
+}
+{
+  "QueryType": "SELECT",
+  "Original": "select user.col, m2.foo from user left join unsharded as m1 on user.col = m1.col left join unsharded as m2 on m1.col = m2.col",
+  "Instructions": {
+    "OperatorType": "Join",
+    "Variant": "LeftJoin",
+    "JoinColumnIndexes": "-2,1",
+    "TableName": "`user`_unsharded_unsharded",
+    "Inputs": [
+      {
+        "OperatorType": "Join",
+        "Variant": "LeftJoin",
+        "JoinColumnIndexes": "1,-1",
+        "TableName": "`user`_unsharded",
+        "Inputs": [
+          {
+            "OperatorType": "Route",
+            "Variant": "SelectScatter",
+            "Keyspace": {
+              "Name": "user",
+              "Sharded": true
+            },
+            "FieldQuery": "select `user`.col from `user` where 1 != 1",
+            "Query": "select `user`.col from `user`",
+            "Table": "`user`"
+          },
+          {
+            "OperatorType": "Route",
+            "Variant": "SelectUnsharded",
+            "Keyspace": {
+              "Name": "main",
+              "Sharded": false
+            },
+            "FieldQuery": "select m1.col from unsharded as m1 where 1 != 1",
+            "Query": "select m1.col from unsharded as m1 where m1.col = :user_col",
+            "Table": "unsharded"
+          }
+        ]
+      },
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectUnsharded",
+        "Keyspace": {
+          "Name": "main",
+          "Sharded": false
+        },
+        "FieldQuery": "select m2.foo from unsharded as m2 where 1 != 1",
+        "Query": "select m2.foo from unsharded as m2 where m2.col = :m1_col",
         "Table": "unsharded"
       }
     ]
@@ -505,6 +608,58 @@ Gen4 plan same as above
     ]
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select user.col from user left join user_extra as e left join unsharded as m1 on m1.col = e.col on user.col = e.col",
+  "Instructions": {
+    "OperatorType": "Join",
+    "Variant": "LeftJoin",
+    "JoinColumnIndexes": "-1",
+    "TableName": "`user`_user_extra_unsharded",
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectScatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select `user`.col from `user` where 1 != 1",
+        "Query": "select `user`.col from `user`",
+        "Table": "`user`"
+      },
+      {
+        "OperatorType": "Join",
+        "Variant": "LeftJoin",
+        "TableName": "user_extra_unsharded",
+        "Inputs": [
+          {
+            "OperatorType": "Route",
+            "Variant": "SelectScatter",
+            "Keyspace": {
+              "Name": "user",
+              "Sharded": true
+            },
+            "FieldQuery": "select e.col from user_extra as e where 1 != 1",
+            "Query": "select e.col from user_extra as e",
+            "Table": "user_extra"
+          },
+          {
+            "OperatorType": "Route",
+            "Variant": "SelectUnsharded",
+            "Keyspace": {
+              "Name": "main",
+              "Sharded": false
+            },
+            "FieldQuery": "select 1 from unsharded as m1 where 1 != 1",
+            "Query": "select 1 from unsharded as m1 where m1.col = :e_col and :user_col = :e_col",
+            "Table": "unsharded"
+          }
+        ]
+      }
+    ]
+  }
+}
 
 # Right join
 "select m1.col from unsharded as m1 right join unsharded as m2 on m1.a=m2.b"
@@ -523,6 +678,7 @@ Gen4 plan same as above
     "Table": "unsharded"
   }
 }
+Gen4 plan same as above
 
 # Right join with a join LHS
 "select m1.col from unsharded as m1 join unsharded as m2 right join unsharded as m3 on m1.a=m2.b"
@@ -541,6 +697,21 @@ Gen4 plan same as above
     "Table": "unsharded"
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select m1.col from unsharded as m1 join unsharded as m2 right join unsharded as m3 on m1.a=m2.b",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectUnsharded",
+    "Keyspace": {
+      "Name": "main",
+      "Sharded": false
+    },
+    "FieldQuery": "select m1.col from unsharded as m3 left join (unsharded as m1, unsharded as m2) on m1.a = m2.b where 1 != 1",
+    "Query": "select m1.col from unsharded as m3 left join (unsharded as m1, unsharded as m2) on m1.a = m2.b",
+    "Table": "unsharded"
+  }
+}
 
 # Straight-join
 "select m1.col from unsharded as m1 straight_join unsharded as m2"
@@ -615,6 +786,41 @@ Gen4 plan same as above
     ]
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select user.col from user join unsharded as m1 join unsharded as m2",
+  "Instructions": {
+    "OperatorType": "Join",
+    "Variant": "Join",
+    "JoinColumnIndexes": "-1",
+    "TableName": "`user`_unsharded",
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectScatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select `user`.col from `user` where 1 != 1",
+        "Query": "select `user`.col from `user`",
+        "Table": "`user`"
+      },
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectUnsharded",
+        "Keyspace": {
+          "Name": "main",
+          "Sharded": false
+        },
+        "FieldQuery": "select 1 from unsharded as m1, unsharded as m2 where 1 != 1",
+        "Query": "select 1 from unsharded as m1, unsharded as m2",
+        "Table": "unsharded"
+      }
+    ]
+  }
+}
+
 
 # Parenthesized, single chunk
 "select user.col from user join (unsharded as m1 join unsharded as m2)"
@@ -652,6 +858,40 @@ Gen4 plan same as above
     ]
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select user.col from user join (unsharded as m1 join unsharded as m2)",
+  "Instructions": {
+    "OperatorType": "Join",
+    "Variant": "Join",
+    "JoinColumnIndexes": "-1",
+    "TableName": "`user`_unsharded",
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectScatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select `user`.col from `user` where 1 != 1",
+        "Query": "select `user`.col from `user`",
+        "Table": "`user`"
+      },
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectUnsharded",
+        "Keyspace": {
+          "Name": "main",
+          "Sharded": false
+        },
+        "FieldQuery": "select 1 from unsharded as m1, unsharded as m2 where 1 != 1",
+        "Query": "select 1 from unsharded as m1, unsharded as m2",
+        "Table": "unsharded"
+      }
+    ]
+  }
+}
 
 # Parenthesized, multi-chunk
 "select user.col from user join (user as u1 join unsharded)"
@@ -707,6 +947,59 @@ Gen4 plan same as above
     ]
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select user.col from user join (user as u1 join unsharded)",
+  "Instructions": {
+    "OperatorType": "Join",
+    "Variant": "Join",
+    "JoinColumnIndexes": "1",
+    "TableName": "`user`_`user`_unsharded",
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectScatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select 1 from `user` as u1 where 1 != 1",
+        "Query": "select 1 from `user` as u1",
+        "Table": "`user`"
+      },
+      {
+        "OperatorType": "Join",
+        "Variant": "Join",
+        "JoinColumnIndexes": "-1",
+        "TableName": "`user`_unsharded",
+        "Inputs": [
+          {
+            "OperatorType": "Route",
+            "Variant": "SelectScatter",
+            "Keyspace": {
+              "Name": "user",
+              "Sharded": true
+            },
+            "FieldQuery": "select `user`.col from `user` where 1 != 1",
+            "Query": "select `user`.col from `user`",
+            "Table": "`user`"
+          },
+          {
+            "OperatorType": "Route",
+            "Variant": "SelectUnsharded",
+            "Keyspace": {
+              "Name": "main",
+              "Sharded": false
+            },
+            "FieldQuery": "select 1 from unsharded where 1 != 1",
+            "Query": "select 1 from unsharded",
+            "Table": "unsharded"
+          }
+        ]
+      }
+    ]
+  }
+}
 
 # index hints, make sure they are not stripped.
 "select user.col from user use index(a)"
@@ -1135,6 +1428,44 @@ Gen4 plan same as above
     ]
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select user.col from user_extra join user on user_extra.user_id = user.name",
+  "Instructions": {
+    "OperatorType": "Join",
+    "Variant": "Join",
+    "JoinColumnIndexes": "-2",
+    "TableName": "`user`_user_extra",
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectScatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select `user`.`name`, `user`.col from `user` where 1 != 1",
+        "Query": "select `user`.`name`, `user`.col from `user`",
+        "Table": "`user`"
+      },
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectEqualUnique",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select 1 from user_extra where 1 != 1",
+        "Query": "select 1 from user_extra where user_extra.user_id = :user_name",
+        "Table": "user_extra",
+        "Values": [
+          ":user_name"
+        ],
+        "Vindex": "user_index"
+      }
+    ]
+  }
+}
 
 # join with reference table
 "select user.col from user join ref"
@@ -2050,6 +2381,40 @@ Gen4 plan same as above
     ]
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select user.user.col1, main.unsharded.col1 from user.user join main.unsharded where main.unsharded.col2 = user.user.col2",
+  "Instructions": {
+    "OperatorType": "Join",
+    "Variant": "Join",
+    "JoinColumnIndexes": "-2,1",
+    "TableName": "`user`_unsharded",
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectScatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select `user`.col2, `user`.col1 from `user` where 1 != 1",
+        "Query": "select `user`.col2, `user`.col1 from `user`",
+        "Table": "`user`"
+      },
+      {
+        "OperatorType": "Route",
+        "Variant": "SelectUnsharded",
+        "Keyspace": {
+          "Name": "main",
+          "Sharded": false
+        },
+        "FieldQuery": "select unsharded.col1 from unsharded where 1 != 1",
+        "Query": "select unsharded.col1 from unsharded where unsharded.col2 = :user_col2",
+        "Table": "unsharded"
+      }
+    ]
+  }
+}
 
 # implicit table reference for unsharded keyspace
 "select main.foo.col from main.foo"
@@ -2068,6 +2433,7 @@ Gen4 plan same as above
     "Table": "foo"
   }
 }
+Gen4 plan same as above
 
 # col refs should be case-insensitive
 "select user.col from user join user_extra on user.ID = user_extra.User_Id"
@@ -2086,6 +2452,21 @@ Gen4 plan same as above
     "Table": "`user`"
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select user.col from user join user_extra on user.ID = user_extra.User_Id",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectScatter",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select `user`.col from `user`, user_extra where 1 != 1",
+    "Query": "select `user`.col from `user`, user_extra where `user`.ID = user_extra.User_Id",
+    "Table": "`user`, user_extra"
+  }
+}
 
 # subquery with join primitive (FROM)
 "select id, t.id from (select user.id from user join user_extra) as t"
@@ -2151,6 +2532,21 @@ Gen4 plan same as above
     "Table": "unsharded"
   }
 }
+{
+  "QueryType": "SELECT",
+  "Original": "select u1.a from unsharded u1 join unsharded u2 on database()",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectUnsharded",
+    "Keyspace": {
+      "Name": "main",
+      "Sharded": false
+    },
+    "FieldQuery": "select u1.a from unsharded as u1, unsharded as u2 where 1 != 1",
+    "Query": "select u1.a from unsharded as u1, unsharded as u2 where database()",
+    "Table": "unsharded"
+  }
+}
 
 # last_insert_id for dual
 "select last_insert_id()"
@@ -2582,3 +2978,36 @@ Gen4 plan same as above
     "SysTableTableSchema": "[VARBINARY(\"performance_schema\")]"
   }
 }
+
+# query with parens is planned correctly
+"select m1.col from (unsharded as m1, unsharded as m2)"
+{
+  "QueryType": "SELECT",
+  "Original": "select m1.col from (unsharded as m1, unsharded as m2)",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectUnsharded",
+    "Keyspace": {
+      "Name": "main",
+      "Sharded": false
+    },
+    "FieldQuery": "select m1.col from (unsharded as m1, unsharded as m2) where 1 != 1",
+    "Query": "select m1.col from (unsharded as m1, unsharded as m2)",
+    "Table": "unsharded"
+  }
+}
+{
+  "QueryType": "SELECT",
+  "Original": "select m1.col from (unsharded as m1, unsharded as m2)",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "SelectUnsharded",
+    "Keyspace": {
+      "Name": "main",
+      "Sharded": false
+    },
+    "FieldQuery": "select m1.col from unsharded as m1, unsharded as m2 where 1 != 1",
+    "Query": "select m1.col from unsharded as m1, unsharded as m2",
+    "Table": "unsharded"
+  }
+}
diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.txt b/go/vt/vtgate/planbuilder/testdata/select_cases.txt
index 2b82ab9f81..ea44d9954a 100644
--- a/go/vt/vtgate/planbuilder/testdata/select_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/select_cases.txt
@@ -1396,6 +1396,7 @@ Gen4 plan same as above
     "Table": "dual"
   }
 }
+Gen4 plan same as above
 
 # testing SingleRow Projection with arithmetics
 "select 42+2"
diff --git a/go/vt/vtgate/semantics/analyzer_test.go b/go/vt/vtgate/semantics/analyzer_test.go
index e9d2b95ea2..418f9e273e 100644
--- a/go/vt/vtgate/semantics/analyzer_test.go
+++ b/go/vt/vtgate/semantics/analyzer_test.go
@@ -22,8 +22,6 @@ import (
 
 	querypb "vitess.io/vitess/go/vt/proto/query"
 
-	"vitess.io/vitess/go/vt/key"
-	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
 	"vitess.io/vitess/go/vt/vtgate/vindexes"
 
 	"github.com/stretchr/testify/assert"
@@ -99,7 +97,7 @@ func TestBindingSingleTable(t *testing.T) {
 			t.Run(query, func(t *testing.T) {
 				parse, err := sqlparser.Parse(query)
 				require.NoError(t, err)
-				_, err = Analyze(parse, "d", &fakeSI{})
+				_, err = Analyze(parse, "d", &FakeSI{})
 				require.Error(t, err)
 			})
 		}
@@ -139,8 +137,8 @@ func TestBindingSingleAliasedTable(t *testing.T) {
 			t.Run(query, func(t *testing.T) {
 				parse, err := sqlparser.Parse(query)
 				require.NoError(t, err)
-				_, err = Analyze(parse, "", &fakeSI{
-					tables: map[string]*vindexes.Table{
+				_, err = Analyze(parse, "", &FakeSI{
+					Tables: map[string]*vindexes.Table{
 						"t": {Name: sqlparser.NewTableIdent("t")},
 					},
 				})
@@ -235,8 +233,8 @@ func TestBindingMultiTable(t *testing.T) {
 			t.Run(query, func(t *testing.T) {
 				parse, err := sqlparser.Parse(query)
 				require.NoError(t, err)
-				_, err = Analyze(parse, "d", &fakeSI{
-					tables: map[string]*vindexes.Table{
+				_, err = Analyze(parse, "d", &FakeSI{
+					Tables: map[string]*vindexes.Table{
 						"tabl": {Name: sqlparser.NewTableIdent("tabl")},
 						"foo":  {Name: sqlparser.NewTableIdent("foo")},
 					},
@@ -271,7 +269,7 @@ func TestNotUniqueTableName(t *testing.T) {
 				t.Skip("derived tables not implemented")
 			}
 			parse, _ := sqlparser.Parse(query)
-			_, err := Analyze(parse, "test", &fakeSI{})
+			_, err := Analyze(parse, "test", &FakeSI{})
 			require.Error(t, err)
 			require.Contains(t, err.Error(), "Not unique table/alias")
 		})
@@ -286,7 +284,7 @@ func TestMissingTable(t *testing.T) {
 	for _, query := range queries {
 		t.Run(query, func(t *testing.T) {
 			parse, _ := sqlparser.Parse(query)
-			_, err := Analyze(parse, "", &fakeSI{})
+			_, err := Analyze(parse, "", &FakeSI{})
 			require.Error(t, err)
 			require.Contains(t, err.Error(), "Unknown table")
 		})
@@ -364,7 +362,7 @@ func TestUnknownColumnMap2(t *testing.T) {
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			si := &fakeSI{tables: test.schema}
+			si := &FakeSI{Tables: test.schema}
 			_, err := Analyze(parse, "", si)
 			if test.err {
 				require.Error(t, err)
@@ -379,21 +377,11 @@ func parseAndAnalyze(t *testing.T, query, dbName string) (sqlparser.Statement, *
 	t.Helper()
 	parse, err := sqlparser.Parse(query)
 	require.NoError(t, err)
-	semTable, err := Analyze(parse, dbName, &fakeSI{
-		tables: map[string]*vindexes.Table{
+	semTable, err := Analyze(parse, dbName, &FakeSI{
+		Tables: map[string]*vindexes.Table{
 			"t": {Name: sqlparser.NewTableIdent("t")},
 		},
 	})
 	require.NoError(t, err)
 	return parse, semTable
 }
-
-var _ SchemaInformation = (*fakeSI)(nil)
-
-type fakeSI struct {
-	tables map[string]*vindexes.Table
-}
-
-func (s *fakeSI) FindTableOrVindex(tablename sqlparser.TableName) (*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) {
-	return s.tables[sqlparser.String(tablename)], nil, "", 0, nil, nil
-}