Repository: vitessio/vitess
Pull request #10388 (2022-05-30 11:07:07)

feat: do not use lookup vindexes for IS NULL predicates
## Description
When a query is using a `IS NULL` predicate against a lookup vindex column, we should have a very hight cost for these, since they will always need to do a scatter query. The lookup table is keyed by the column and so does not support `null` values.

## Related Issue(s)
#10346

## Checklist
-   [x] "Backport me!" label has been added if this change should be backported
-   [x] Tests were added or are not required
-   [x] Documentation was added or is not required


github-actions[bot]: ### Review Checklist

Hello reviewers! :wave: Please follow this checklist when reviewing this Pull Request.

#### General
- [ ] Ensure that the Pull Request has the correct `release notes` label. `release notes none` should only be used for PRs that are so trivial that they need not be included.
- [ ] If a new flag is being introduced, review whether it is really needed. The flag names should be clear and intuitive (as far as possible), and the flag's help should be descriptive.

#### Bug fixes
- [ ] There should be at least one unit or end-to-end test.
- [ ] The Pull Request description should either include a link to an issue that describes the bug OR an actual description of the bug and how to reproduce, along with a description of the fix.

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

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

#### Backward compatibility            
- [ ] Protobuf changes should be wire-compatible.
- [ ] Changes to `_vt` tables and RPCs need to be backward compatible.
- [ ] `vtctl` command output order should be stable and `awk`-able.

Patch:
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 90fc71a23a..1a30a71175 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -35,6 +35,7 @@ bootstrap.sh @deepthi
 /go/vt/vtctl/grpcvtctldserver @ajm188 @doeg @notfelineit
 /go/vt/vtctl/vtctldclient @ajm188 @doeg @notfelineit
 /go/vt/vtctld @ajm188 @deepthi @doeg @notfelineit @rohit-nayak-ps
+/go/vt/vtexplain @systay
 /go/vt/vtgate @harshit-gangal @systay @frouioui
 /go/vt/vttablet/tabletmanager @deepthi @shlomi-noach
 /go/vt/vttablet/tabletmanager/vreplication @rohit-nayak-ps @mattlord
diff --git a/examples/are-you-alive/schemas/README.md b/examples/are-you-alive/schemas/README.md
index e119ab3350..25a0db86c4 100644
--- a/examples/are-you-alive/schemas/README.md
+++ b/examples/are-you-alive/schemas/README.md
@@ -4,6 +4,6 @@ MySQL schemas and Vitess schemas that must be applied to your cluster to use
 this test helper.  They should be applied using `vtctlclient` like this:
 
 ```
-vtctlclient -server "<vtctld_server>" ApplySchema -sql "$(cat create_test_table.sql)" <database_name>
-vtctlclient -server "<vtctld_server>" ApplyVSchema -vschema "$(cat vschema.json)" <database_name>
+vtctlclient --server "<vtctld_server>" ApplySchema -- --sql "$(cat create_test_table.sql)" <database_name>
+vtctlclient --server "<vtctld_server>" ApplyVSchema -- --vschema "$(cat vschema.json)" <database_name>
 ```
diff --git a/examples/compose/lvtctl.sh b/examples/compose/lvtctl.sh
index 6411074307..e8047a0edf 100755
--- a/examples/compose/lvtctl.sh
+++ b/examples/compose/lvtctl.sh
@@ -21,4 +21,4 @@ if [[ "$OSTYPE" == "msys" ]]; then
 fi
 
 # This is a convenience script to run vtctlclient against the local example.
-exec $tty docker-compose exec ${CS:-vtctld} vtctlclient -server vtctld:15999 "$@"
+exec $tty docker-compose exec ${CS:-vtctld} vtctlclient --server vtctld:15999 "$@"
diff --git a/examples/local/backups/start_cluster.sh b/examples/local/backups/start_cluster.sh
index d1c8261c69..ff4fdd2334 100755
--- a/examples/local/backups/start_cluster.sh
+++ b/examples/local/backups/start_cluster.sh
@@ -42,8 +42,8 @@ done
 vtctldclient InitShardPrimary --force commerce/0 zone1-100
 
 # create the schema for commerce
-vtctlclient ApplySchema -sql-file backups/create_commerce_schema.sql commerce
-vtctlclient ApplyVSchema -vschema_file ./vschema_commerce_seq.json commerce
+vtctlclient ApplySchema -- --sql-file backups/create_commerce_schema.sql commerce
+vtctlclient ApplyVSchema -- --vschema_file ./vschema_commerce_seq.json commerce
 
 
 # start vttablets for keyspace customer
@@ -61,8 +61,8 @@ vtctldclient InitShardPrimary --force customer/-80 zone1-200
 vtctldclient InitShardPrimary --force customer/80- zone1-300
 
 # create the schema for customer
-vtctlclient ApplySchema -sql-file backups/create_customer_schema.sql customer
-vtctlclient ApplyVSchema -vschema_file ./vschema_customer_sharded.json customer
+vtctlclient ApplySchema -- --sql-file backups/create_customer_schema.sql customer
+vtctlclient ApplyVSchema -- --vschema_file ./vschema_customer_sharded.json customer
 
 
 # start vtgate
diff --git a/examples/local/backups/stop_tablets.sh b/examples/local/backups/stop_tablets.sh
index f3359bff55..25470ac549 100755
--- a/examples/local/backups/stop_tablets.sh
+++ b/examples/local/backups/stop_tablets.sh
@@ -20,7 +20,7 @@
 source ./env.sh
 
 for tablet in 100 200 300; do
-  if vtctlclient -action_timeout 1s -server localhost:15999 GetTablet zone1-$tablet >/dev/null 2>&1; then
+  if vtctlclient --action_timeout 1s --server localhost:15999 GetTablet zone1-$tablet >/dev/null 2>&1; then
     # The zero tablet is up. Try to shutdown 0-2 tablet + mysqlctl
     for i in 0 1 2; do
       uid=$(($tablet + $i))
@@ -29,8 +29,8 @@ for tablet in 100 200 300; do
       echo "Shutting down mysql zone1-$uid"
       CELL=zone1 TABLET_UID=$uid ./scripts/mysqlctl-down.sh
       echo "Removing tablet directory zone1-$uid"
-      vtctlclient DeleteTablet -allow_primary=true zone1-$uid
+      vtctlclient DeleteTablet -- --allow_primary=true zone1-$uid
       rm -Rf $VTDATAROOT/vt_0000000$uid
     done
   fi
-done
\ No newline at end of file
+done
diff --git a/examples/operator/pf.sh b/examples/operator/pf.sh
index c0b267155f..77cc80fe07 100755
--- a/examples/operator/pf.sh
+++ b/examples/operator/pf.sh
@@ -6,7 +6,7 @@ kubectl port-forward --address localhost "$(kubectl get service --selector="plan
 process_id2=$!
 sleep 2
 echo "You may point your browser to http://localhost:15000, use the following aliases as shortcuts:"
-echo 'alias vtctlclient="vtctlclient -server=localhost:15999 -logtostderr"'
+echo 'alias vtctlclient="vtctlclient --server=localhost:15999 --logtostderr"'
 echo 'alias mysql="mysql -h 127.0.0.1 -P 15306 -u user"'
 echo "Hit Ctrl-C to stop the port forwards"
 wait $process_id1
diff --git a/examples/region_sharding/201_main_sharded.sh b/examples/region_sharding/201_main_sharded.sh
index 7885021d78..b8a589c0d4 100755
--- a/examples/region_sharding/201_main_sharded.sh
+++ b/examples/region_sharding/201_main_sharded.sh
@@ -20,7 +20,7 @@ source ./env.sh
 vtctldclient ApplyVSchema --vschema-file main_vschema_sharded.json main
 
 # optional: create the schema needed for lookup vindex
-#vtctlclient ApplySchema -sql-file create_lookup_schema.sql main
+#vtctlclient ApplySchema --sql-file create_lookup_schema.sql main
 
 # create the lookup vindex
 vtctlclient CreateLookupVindex -- --tablet_types=PRIMARY main "$(cat lookup_vindex.json)"
diff --git a/go/mysql/flavor.go b/go/mysql/flavor.go
index c96bc15d33..89564b4617 100644
--- a/go/mysql/flavor.go
+++ b/go/mysql/flavor.go
@@ -38,6 +38,17 @@ var (
 	ErrNoPrimaryStatus = errors.New("no master status")
 )
 
+type FlavorFamilty int
+
+const (
+	UnknownFlavorFamily FlavorFamilty = iota
+	MySQL56FlavorFamily
+	MySQL57FlavorFamily
+	MySQL80FlavorFamily
+	MariaDB101FlavorFamily
+	MariaDB102FlavorFamily
+)
+
 const (
 	// mariaDBReplicationHackPrefix is the prefix of a version for MariaDB 10.0
 	// versions, to work around replication bugs.
@@ -139,9 +150,9 @@ type flavor interface {
 // connection parameters.
 var flavors = make(map[string]func() flavor)
 
-// serverVersionAtLeast returns true if current server is at least given value.
+// ServerVersionAtLeast returns true if current server is at least given value.
 // Example: if input is []int{8, 0, 23}... the function returns 'true' if we're on MySQL 8.0.23, 8.0.24, ...
-func serverVersionAtLeast(serverVersion string, parts ...int) (bool, error) {
+func ServerVersionAtLeast(serverVersion string, parts ...int) (bool, error) {
 	versionPrefix := strings.Split(serverVersion, "-")[0]
 	versionTokens := strings.Split(versionPrefix, ".")
 	for i, part := range parts {
@@ -162,7 +173,7 @@ func serverVersionAtLeast(serverVersion string, parts ...int) (bool, error) {
 	return true, nil
 }
 
-// fillFlavor fills in c.Flavor. If the params specify the flavor,
+// GetFlavor fills in c.Flavor. If the params specify the flavor,
 // that is used. Otherwise, we auto-detect.
 //
 // This is the same logic as the ConnectorJ java client. We try to recognize
@@ -174,34 +185,59 @@ func serverVersionAtLeast(serverVersion string, parts ...int) (bool, error) {
 // Note on such servers, 'select version()' would return 10.0.21-MariaDB-...
 // as well (not matching what c.ServerVersion is, but matching after we remove
 // the prefix).
-func (c *Conn) fillFlavor(params *ConnParams) {
-	flavorFunc := flavors[params.Flavor]
-
+func GetFlavor(serverVersion string, flavorFunc func() flavor) (f flavor, family FlavorFamilty, canonicalVersion string) {
+	family = UnknownFlavorFamily
+	canonicalVersion = serverVersion
 	switch {
 	case flavorFunc != nil:
-		c.flavor = flavorFunc()
-	case strings.HasPrefix(c.ServerVersion, mariaDBReplicationHackPrefix):
-		c.ServerVersion = c.ServerVersion[len(mariaDBReplicationHackPrefix):]
-		c.flavor = mariadbFlavor101{}
-	case strings.Contains(c.ServerVersion, mariaDBVersionString):
-		mariadbVersion, err := strconv.ParseFloat(c.ServerVersion[:4], 64)
+		f = flavorFunc()
+	case strings.HasPrefix(serverVersion, mariaDBReplicationHackPrefix):
+		canonicalVersion = serverVersion[len(mariaDBReplicationHackPrefix):]
+		f = mariadbFlavor101{}
+		family = MariaDB101FlavorFamily
+	case strings.Contains(serverVersion, mariaDBVersionString):
+		mariadbVersion, err := strconv.ParseFloat(serverVersion[:4], 64)
 		if err != nil || mariadbVersion < 10.2 {
-			c.flavor = mariadbFlavor101{}
+			f = mariadbFlavor101{}
+			family = MariaDB101FlavorFamily
+		} else {
+			f = mariadbFlavor102{}
+			family = MariaDB102FlavorFamily
 		}
-		c.flavor = mariadbFlavor102{}
-	case strings.HasPrefix(c.ServerVersion, mysql57VersionPrefix):
-		c.flavor = mysqlFlavor57{}
-	case strings.HasPrefix(c.ServerVersion, mysql80VersionPrefix):
-		c.flavor = mysqlFlavor80{}
+	case strings.HasPrefix(serverVersion, mysql57VersionPrefix):
+		f = mysqlFlavor57{}
+		family = MySQL57FlavorFamily
+	case strings.HasPrefix(serverVersion, mysql80VersionPrefix):
+		f = mysqlFlavor80{}
+		family = MySQL80FlavorFamily
 	default:
-		c.flavor = mysqlFlavor56{}
+		f = mysqlFlavor56{}
+		family = MySQL56FlavorFamily
 	}
+	return f, family, canonicalVersion
+}
+
+// fillFlavor fills in c.Flavor. If the params specify the flavor,
+// that is used. Otherwise, we auto-detect.
+//
+// This is the same logic as the ConnectorJ java client. We try to recognize
+// MariaDB as much as we can, but default to MySQL.
+//
+// MariaDB note: the server version returned here might look like:
+// 5.5.5-10.0.21-MariaDB-...
+// If that is the case, we are removing the 5.5.5- prefix.
+// Note on such servers, 'select version()' would return 10.0.21-MariaDB-...
+// as well (not matching what c.ServerVersion is, but matching after we remove
+// the prefix).
+func (c *Conn) fillFlavor(params *ConnParams) {
+	flavorFunc := flavors[params.Flavor]
+	c.flavor, _, c.ServerVersion = GetFlavor(c.ServerVersion, flavorFunc)
 }
 
 // ServerVersionAtLeast returns 'true' if server version is equal or greater than given parts. e.g.
 // "8.0.14-log" is at least [8, 0, 13] and [8, 0, 14], but not [8, 0, 15]
 func (c *Conn) ServerVersionAtLeast(parts ...int) (bool, error) {
-	return serverVersionAtLeast(c.ServerVersion, parts...)
+	return ServerVersionAtLeast(c.ServerVersion, parts...)
 }
 
 //
diff --git a/go/vt/vtctl/backup.go b/go/vt/vtctl/backup.go
index 00262ce569..eb03a1c039 100644
--- a/go/vt/vtctl/backup.go
+++ b/go/vt/vtctl/backup.go
@@ -46,7 +46,7 @@ func init() {
 	addCommand("Shards", command{
 		name:   "BackupShard",
 		method: commandBackupShard,
-		params: "[-allow_primary=false] <keyspace/shard>",
+		params: "[--allow_primary=false] <keyspace/shard>",
 		help:   "Chooses a tablet and creates a backup for a shard.",
 	})
 	addCommand("Shards", command{
@@ -59,13 +59,13 @@ func init() {
 	addCommand("Tablets", command{
 		name:   "Backup",
 		method: commandBackup,
-		params: "[-concurrency=4] [-allow_primary=false] <tablet alias>",
+		params: "[--concurrency=4] [--allow_primary=false] <tablet alias>",
 		help:   "Stops mysqld and uses the BackupStorage service to store a new backup. This function also remembers if the tablet was replicating so that it can restore the same state after the backup completes.",
 	})
 	addCommand("Tablets", command{
 		name:   "RestoreFromBackup",
 		method: commandRestoreFromBackup,
-		params: "[-backup_timestamp=yyyy-MM-dd.HHmmss] <tablet alias>",
+		params: "[--backup_timestamp=yyyy-MM-dd.HHmmss] <tablet alias>",
 		help:   "Stops mysqld and restores the data from the latest backup or if a timestamp is specified then the most recent backup at or before that time.",
 	})
 }
diff --git a/go/vt/vtctl/cell_info.go b/go/vt/vtctl/cell_info.go
index 7eda91e9ad..0478af4e50 100644
--- a/go/vt/vtctl/cell_info.go
+++ b/go/vt/vtctl/cell_info.go
@@ -38,21 +38,21 @@ func init() {
 	addCommand(cellsGroupName, command{
 		name:   "AddCellInfo",
 		method: commandAddCellInfo,
-		params: "[-server_address <addr>] [-root <root>] <cell>",
+		params: "[--server_address <addr>] [--root <root>] <cell>",
 		help:   "Registers a local topology service in a new cell by creating the CellInfo with the provided parameters. The address will be used to connect to the topology service, and we'll put Vitess data starting at the provided root.",
 	})
 
 	addCommand(cellsGroupName, command{
 		name:   "UpdateCellInfo",
 		method: commandUpdateCellInfo,
-		params: "[-server_address <addr>] [-root <root>] <cell>",
+		params: "[--server_address <addr>] [--root <root>] <cell>",
 		help:   "Updates the content of a CellInfo with the provided parameters. If a value is empty, it is not updated. The CellInfo will be created if it doesn't exist.",
 	})
 
 	addCommand(cellsGroupName, command{
 		name:   "DeleteCellInfo",
 		method: commandDeleteCellInfo,
-		params: "[-force] <cell>",
+		params: "[--force] <cell>",
 		help:   "Deletes the CellInfo for the provided cell. The cell cannot be referenced by any Shard record.",
 	})
 
diff --git a/go/vt/vtctl/cells_aliases.go b/go/vt/vtctl/cells_aliases.go
index f313cebce0..eaf016e1b2 100644
--- a/go/vt/vtctl/cells_aliases.go
+++ b/go/vt/vtctl/cells_aliases.go
@@ -38,14 +38,14 @@ func init() {
 	addCommand(cellsAliasesGroupName, command{
 		name:   "AddCellsAlias",
 		method: commandAddCellsAlias,
-		params: "[-cells <cell,cell2...>] <alias>",
+		params: "[--cells <cell,cell2...>] <alias>",
 		help:   "Defines a group of cells within which replica/rdonly traffic can be routed across cells. Between cells that are not in the same group (alias), only primary traffic can be routed.",
 	})
 
 	addCommand(cellsAliasesGroupName, command{
 		name:   "UpdateCellsAlias",
 		method: commandUpdateCellsAlias,
-		params: "[-cells <cell,cell2,...>] <alias>",
+		params: "[--cells <cell,cell2,...>] <alias>",
 		help:   "Updates the content of a CellsAlias with the provided parameters. If a value is empty, it is not updated. The CellsAlias will be created if it doesn't exist.",
 	})
 
diff --git a/go/vt/vtctl/query.go b/go/vt/vtctl/query.go
index d4404ef779..705f467b32 100644
--- a/go/vt/vtctl/query.go
+++ b/go/vt/vtctl/query.go
@@ -55,7 +55,7 @@ func init() {
 	addCommand(queriesGroupName, command{
 		name:       "VtGateExecute",
 		method:     commandVtGateExecute,
-		params:     "-server <vtgate> [-bind_variables <JSON map>] [-keyspace <default keyspace>] [-tablet_type <tablet type>] [-options <proto text options>] [-json] <sql>",
+		params:     "--server <vtgate> [--bind_variables <JSON map>] [--keyspace <default keyspace>] [--tablet_type <tablet type>] [--options <proto text options>] [--json] <sql>",
 		help:       "Executes the given SQL query with the provided bound variables against the vtgate server.",
 		deprecated: true,
 	})
@@ -64,36 +64,36 @@ func init() {
 	addCommand(queriesGroupName, command{
 		name:         "VtTabletExecute",
 		method:       commandVtTabletExecute,
-		params:       "[-username <TableACL user>] [-transaction_id <transaction_id>] [-options <proto text options>] [-json] <tablet alias> <sql>",
-		help:         "Executes the given query on the given tablet. -transaction_id is optional. Use VtTabletBegin to start a transaction.",
+		params:       "[--username <TableACL user>] [--transaction_id <transaction_id>] [--options <proto text options>] [--json] <tablet alias> <sql>",
+		help:         "Executes the given query on the given tablet. --transaction_id is optional. Use VtTabletBegin to start a transaction.",
 		deprecated:   true,
 		deprecatedBy: "ExecuteFetchAsApp",
 	})
 	addCommand(queriesGroupName, command{
 		name:       "VtTabletBegin",
 		method:     commandVtTabletBegin,
-		params:     "[-username <TableACL user>] <tablet alias>",
+		params:     "[--username <TableACL user>] <tablet alias>",
 		help:       "Starts a transaction on the provided server.",
 		deprecated: true,
 	})
 	addCommand(queriesGroupName, command{
 		name:       "VtTabletCommit",
 		method:     commandVtTabletCommit,
-		params:     "[-username <TableACL user>] <transaction_id>",
+		params:     "[--username <TableACL user>] <transaction_id>",
 		help:       "Commits the given transaction on the provided server.",
 		deprecated: true,
 	})
 	addCommand(queriesGroupName, command{
 		name:       "VtTabletRollback",
 		method:     commandVtTabletRollback,
-		params:     "[-username <TableACL user>] <tablet alias> <transaction_id>",
+		params:     "[--username <TableACL user>] <tablet alias> <transaction_id>",
 		help:       "Rollbacks the given transaction on the provided server.",
 		deprecated: true,
 	})
 	addCommand(queriesGroupName, command{
 		name:       "VtTabletStreamHealth",
 		method:     commandVtTabletStreamHealth,
-		params:     "[-count <count, default 1>] <tablet alias>",
+		params:     "[--count <count, default 1>] <tablet alias>",
 		help:       "Executes the StreamHealth streaming query to a vttablet process. Will stop after getting <count> answers.",
 		deprecated: true,
 	})
diff --git a/go/vt/vtctl/reparent.go b/go/vt/vtctl/reparent.go
index d910ded910..dec7f2ce64 100644
--- a/go/vt/vtctl/reparent.go
+++ b/go/vt/vtctl/reparent.go
@@ -39,19 +39,19 @@ func init() {
 	addCommand("Shards", command{
 		name:   "InitShardPrimary",
 		method: commandInitShardPrimary,
-		params: "[-force] [-wait_replicas_timeout=<duration>] <keyspace/shard> <tablet alias>",
+		params: "[--force] [--wait_replicas_timeout=<duration>] <keyspace/shard> <tablet alias>",
 		help:   "Sets the initial primary for a shard. Will make all other tablets in the shard replicas of the provided tablet. WARNING: this could cause data loss on an already replicating shard. PlannedReparentShard or EmergencyReparentShard should be used instead.",
 	})
 	addCommand("Shards", command{
 		name:   "PlannedReparentShard",
 		method: commandPlannedReparentShard,
-		params: "-keyspace_shard=<keyspace/shard> [-new_primary=<tablet alias>] [-avoid_tablet=<tablet alias>] [-wait_replicas_timeout=<duration>]",
+		params: "--keyspace_shard=<keyspace/shard> [--new_primary=<tablet alias>] [--avoid_tablet=<tablet alias>] [--wait_replicas_timeout=<duration>]",
 		help:   "Reparents the shard to the new primary, or away from old primary. Both old and new primary need to be up and running.",
 	})
 	addCommand("Shards", command{
 		name:   "EmergencyReparentShard",
 		method: commandEmergencyReparentShard,
-		params: "-keyspace_shard=<keyspace/shard> [-new_primary=<tablet alias>] [-wait_replicas_timeout=<duration>] [-ignore_replicas=<tablet alias list>] [-prevent_cross_cell_promotion=<true/false>]",
+		params: "--keyspace_shard=<keyspace/shard> [--new_primary=<tablet alias>] [--wait_replicas_timeout=<duration>] [--ignore_replicas=<tablet alias list>] [--prevent_cross_cell_promotion=<true/false>]",
 		help:   "Reparents the shard to the new primary. Assumes the old primary is dead and not responding.",
 	})
 	addCommand("Shards", command{
@@ -65,7 +65,7 @@ func init() {
 
 func commandReparentTablet(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
 	if *mysqlctl.DisableActiveReparents {
-		return fmt.Errorf("active reparent commands disabled (unset the -disable_active_reparents flag to enable)")
+		return fmt.Errorf("active reparent commands disabled (unset the --disable_active_reparents flag to enable)")
 	}
 
 	if err := subFlags.Parse(args); err != nil {
@@ -83,7 +83,7 @@ func commandReparentTablet(ctx context.Context, wr *wrangler.Wrangler, subFlags
 
 func commandInitShardPrimary(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
 	if *mysqlctl.DisableActiveReparents {
-		return fmt.Errorf("active reparent commands disabled (unset the -disable_active_reparents flag to enable)")
+		return fmt.Errorf("active reparent commands disabled (unset the --disable_active_reparents flag to enable)")
 	}
 
 	force := subFlags.Bool("force", false, "will force the reparent even if the provided tablet is not writable or the shard primary")
@@ -107,7 +107,7 @@ func commandInitShardPrimary(ctx context.Context, wr *wrangler.Wrangler, subFlag
 
 func commandPlannedReparentShard(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
 	if *mysqlctl.DisableActiveReparents {
-		return fmt.Errorf("active reparent commands disabled (unset the -disable_active_reparents flag to enable)")
+		return fmt.Errorf("active reparent commands disabled (unset the --disable_active_reparents flag to enable)")
 	}
 
 	waitReplicasTimeout := subFlags.Duration("wait_replicas_timeout", *topo.RemoteOperationTimeout, "time to wait for replicas to catch up on replication before and after reparenting")
@@ -121,12 +121,12 @@ func commandPlannedReparentShard(ctx context.Context, wr *wrangler.Wrangler, sub
 	if subFlags.NArg() == 2 {
 		// Legacy syntax: "<keyspace/shard> <tablet alias>".
 		if *keyspaceShard != "" || *newPrimary != "" {
-			return fmt.Errorf("cannot use legacy syntax and flags -keyspace_shard and -new_primary for action PlannedReparentShard at the same time")
+			return fmt.Errorf("cannot use legacy syntax and flags --keyspace_shard and --new_primary for action PlannedReparentShard at the same time")
 		}
 		*keyspaceShard = subFlags.Arg(0)
 		*newPrimary = subFlags.Arg(1)
 	} else if subFlags.NArg() != 0 {
-		return fmt.Errorf("action PlannedReparentShard requires -keyspace_shard=<keyspace/shard> [-new_primary=<tablet alias>] [-avoid_tablet=<tablet alias>]")
+		return fmt.Errorf("action PlannedReparentShard requires --keyspace_shard=<keyspace/shard> [--new_primary=<tablet alias>] [--avoid_tablet=<tablet alias>]")
 	}
 
 	keyspace, shard, err := topoproto.ParseKeyspaceShard(*keyspaceShard)
@@ -151,7 +151,7 @@ func commandPlannedReparentShard(ctx context.Context, wr *wrangler.Wrangler, sub
 
 func commandEmergencyReparentShard(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
 	if *mysqlctl.DisableActiveReparents {
-		return fmt.Errorf("active reparent commands disabled (unset the -disable_active_reparents flag to enable)")
+		return fmt.Errorf("active reparent commands disabled (unset the --disable_active_reparents flag to enable)")
 	}
 
 	waitReplicasTimeout := subFlags.Duration("wait_replicas_timeout", *topo.RemoteOperationTimeout, "time to wait for replicas to catch up in reparenting")
@@ -166,12 +166,12 @@ func commandEmergencyReparentShard(ctx context.Context, wr *wrangler.Wrangler, s
 	if subFlags.NArg() == 2 {
 		// Legacy syntax: "<keyspace/shard> <tablet alias>".
 		if *newPrimary != "" {
-			return fmt.Errorf("cannot use legacy syntax and flag -new_primary for action EmergencyReparentShard at the same time")
+			return fmt.Errorf("cannot use legacy syntax and flag --new_primary for action EmergencyReparentShard at the same time")
 		}
 		*keyspaceShard = subFlags.Arg(0)
 		*newPrimary = subFlags.Arg(1)
 	} else if subFlags.NArg() != 0 {
-		return fmt.Errorf("action EmergencyReparentShard requires -keyspace_shard=<keyspace/shard>")
+		return fmt.Errorf("action EmergencyReparentShard requires --keyspace_shard=<keyspace/shard>")
 	}
 
 	keyspace, shard, err := topoproto.ParseKeyspaceShard(*keyspaceShard)
diff --git a/go/vt/vtctl/throttler.go b/go/vt/vtctl/throttler.go
index fbde108bbd..e22d406fae 100644
--- a/go/vt/vtctl/throttler.go
+++ b/go/vt/vtctl/throttler.go
@@ -41,7 +41,7 @@ const (
 )
 
 // This file contains the commands to control the throttler which is used during
-// resharding (vtworker) and by filtered replication (vttablet).
+// resharding and by filtered replication (vttablet).
 
 func init() {
 	addCommandGroup(throttlerGroupName)
@@ -49,7 +49,7 @@ func init() {
 	addCommand(throttlerGroupName, command{
 		name:         "ThrottlerMaxRates",
 		method:       commandThrottlerMaxRates,
-		params:       "-server <vtworker or vttablet>",
+		params:       "--server <vttablet>",
 		help:         "Returns the current max rate of all active resharding throttlers on the server.",
 		deprecated:   true,
 		deprecatedBy: "the new Reshard/MoveTables workflows",
@@ -57,7 +57,7 @@ func init() {
 	addCommand(throttlerGroupName, command{
 		name:         "ThrottlerSetMaxRate",
 		method:       commandThrottlerSetMaxRate,
-		params:       "-server <vtworker or vttablet> <rate>",
+		params:       "--server <vttablet> <rate>",
 		help:         "Sets the max rate for all active resharding throttlers on the server.",
 		deprecated:   true,
 		deprecatedBy: "the new Reshard/MoveTables workflows",
@@ -66,7 +66,7 @@ func init() {
 	addCommand(throttlerGroupName, command{
 		name:         "GetThrottlerConfiguration",
 		method:       commandGetThrottlerConfiguration,
-		params:       "-server <vtworker or vttablet> [<throttler name>]",
+		params:       "--server <vttablet> [<throttler name>]",
 		help:         "Returns the current configuration of the MaxReplicationLag module. If no throttler name is specified, the configuration of all throttlers will be returned.",
 		deprecated:   true,
 		deprecatedBy: "the new Reshard/MoveTables workflows",
@@ -76,15 +76,15 @@ func init() {
 		method: commandUpdateThrottlerConfiguration,
 		// Note: <configuration protobuf text> is put in quotes to tell the user
 		// that the value must be quoted such that it's one argument only.
-		params:       `-server <vtworker or vttablet> [-copy_zero_values] "<configuration protobuf text>" [<throttler name>]`,
-		help:         "Updates the configuration of the MaxReplicationLag module. The configuration must be specified as protobuf text. If a field is omitted or has a zero value, it will be ignored unless -copy_zero_values is specified. If no throttler name is specified, all throttlers will be updated.",
+		params:       `--server <vttablet> [--copy_zero_values] "<configuration protobuf text>" [<throttler name>]`,
+		help:         "Updates the configuration of the MaxReplicationLag module. The configuration must be specified as protobuf text. If a field is omitted or has a zero value, it will be ignored unless --copy_zero_values is specified. If no throttler name is specified, all throttlers will be updated.",
 		deprecated:   true,
 		deprecatedBy: "the new Reshard/MoveTables workflows",
 	})
 	addCommand(throttlerGroupName, command{
 		name:         "ResetThrottlerConfiguration",
 		method:       commandResetThrottlerConfiguration,
-		params:       "-server <vtworker or vttablet> [<throttler name>]",
+		params:       "--server <vttablet> [<throttler name>]",
 		help:         "Resets the current configuration of the MaxReplicationLag module. If no throttler name is specified, the configuration of all throttlers will be reset.",
 		deprecated:   true,
 		deprecatedBy: "the new Reshard/MoveTables workflows",
@@ -92,7 +92,7 @@ func init() {
 }
 
 func commandThrottlerMaxRates(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
-	server := subFlags.String("server", "", "vtworker or vttablet to connect to")
+	server := subFlags.String("server", "", "vttablet to connect to")
 	if err := subFlags.Parse(args); err != nil {
 		return err
 	}
@@ -135,7 +135,7 @@ func commandThrottlerMaxRates(ctx context.Context, wr *wrangler.Wrangler, subFla
 }
 
 func commandThrottlerSetMaxRate(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
-	server := subFlags.String("server", "", "vtworker or vttablet to connect to")
+	server := subFlags.String("server", "", "vttablet to connect to")
 	if err := subFlags.Parse(args); err != nil {
 		return err
 	}
@@ -177,7 +177,7 @@ func commandThrottlerSetMaxRate(ctx context.Context, wr *wrangler.Wrangler, subF
 }
 
 func commandGetThrottlerConfiguration(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
-	server := subFlags.String("server", "", "vtworker or vttablet to connect to")
+	server := subFlags.String("server", "", "vttablet to connect to")
 	if err := subFlags.Parse(args); err != nil {
 		return err
 	}
@@ -225,7 +225,7 @@ func commandGetThrottlerConfiguration(ctx context.Context, wr *wrangler.Wrangler
 }
 
 func commandUpdateThrottlerConfiguration(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
-	server := subFlags.String("server", "", "vtworker or vttablet to connect to")
+	server := subFlags.String("server", "", "vttablet to connect to")
 	copyZeroValues := subFlags.Bool("copy_zero_values", false, "If true, fields with zero values will be copied as well")
 	if err := subFlags.Parse(args); err != nil {
 		return err
@@ -270,7 +270,7 @@ func commandUpdateThrottlerConfiguration(ctx context.Context, wr *wrangler.Wrang
 }
 
 func commandResetThrottlerConfiguration(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
-	server := subFlags.String("server", "", "vtworker or vttablet to connect to")
+	server := subFlags.String("server", "", "vttablet to connect to")
 	if err := subFlags.Parse(args); err != nil {
 		return err
 	}
diff --git a/go/vt/vtctl/topo.go b/go/vt/vtctl/topo.go
index c4b18c94a9..465e731f8a 100644
--- a/go/vt/vtctl/topo.go
+++ b/go/vt/vtctl/topo.go
@@ -45,14 +45,14 @@ func init() {
 	addCommand(topoGroupName, command{
 		name:   "TopoCat",
 		method: commandTopoCat,
-		params: "[-cell <cell>] [-decode_proto] [-decode_proto_json] [-long] <path> [<path>...]",
+		params: "[--cell <cell>] [--decode_proto] [--decode_proto_json] [--long] <path> [<path>...]",
 		help:   "Retrieves the file(s) at <path> from the topo service, and displays it. It can resolve wildcards, and decode the proto-encoded data.",
 	})
 
 	addCommand(topoGroupName, command{
 		name:   "TopoCp",
 		method: commandTopoCp,
-		params: "[-cell <cell>] [-to_topo] <src> <dst>",
+		params: "[--cell <cell>] [--to_topo] <src> <dst>",
 		help:   "Copies a file from topo to local file structure, or the other way around",
 	})
 }
diff --git a/go/vt/vtctl/vtctl.go b/go/vt/vtctl/vtctl.go
index 2c0846b3c8..2f1bf6e9c8 100644
--- a/go/vt/vtctl/vtctl.go
+++ b/go/vt/vtctl/vtctl.go
@@ -192,7 +192,7 @@ var commands = []commandGroup{
 			{
 				name:   "DeleteTablet",
 				method: commandDeleteTablet,
-				params: "[-allow_primary] <tablet alias> ...",
+				params: "[--allow_primary] <tablet alias> ...",
 				help:   "Deletes tablet(s) from the topology.",
 			},
 			{
diff --git a/go/vt/vtctl/workflow.go b/go/vt/vtctl/workflow.go
index 4d90511feb..5fe7d444ec 100644
--- a/go/vt/vtctl/workflow.go
+++ b/go/vt/vtctl/workflow.go
@@ -41,7 +41,7 @@ func init() {
 	addCommand(workflowsGroupName, command{
 		name:   "WorkflowCreate",
 		method: commandWorkflowCreate,
-		params: "[-skip_start] <factoryName> [parameters...]",
+		params: "[--skip_start] <factoryName> [parameters...]",
 		help:   "Creates the workflow with the provided parameters. The workflow is also started, unless -skip_start is specified.",
 	})
 	addCommand(workflowsGroupName, command{
diff --git a/go/vt/vtgate/errorsanitizer/errorSanitizer.go b/go/vt/vtgate/errorsanitizer/error_sanitizer.go
similarity index 100%
rename from go/vt/vtgate/errorsanitizer/errorSanitizer.go
rename to go/vt/vtgate/errorsanitizer/error_sanitizer.go
diff --git a/go/vt/vtgate/errorsanitizer/errorSanitizer_test.go b/go/vt/vtgate/errorsanitizer/error_sanitizer_test.go
similarity index 100%
rename from go/vt/vtgate/errorsanitizer/errorSanitizer_test.go
rename to go/vt/vtgate/errorsanitizer/error_sanitizer_test.go
diff --git a/go/vt/vtgate/planbuilder/physical/route.go b/go/vt/vtgate/planbuilder/physical/route.go
index bc529c1147..24b87caf4f 100644
--- a/go/vt/vtgate/planbuilder/physical/route.go
+++ b/go/vt/vtgate/planbuilder/physical/route.go
@@ -309,7 +309,7 @@ func (r *Route) haveMatchingVindex(
 				// single column vindex - just add the option
 				routeOpcode := opcode(v.ColVindex)
 				vindex := vfunc(v.ColVindex)
-				if vindex == nil {
+				if vindex == nil || routeOpcode == engine.Scatter {
 					continue
 				}
 				v.Options = append(v.Options, &VindexOption{
@@ -711,6 +711,12 @@ func (r *Route) planIsExpr(ctx *plancontext.PlanningContext, node *sqlparser.IsE
 	if val == nil {
 		return false
 	}
+	opcodeF := func(vindex *vindexes.ColumnVindex) engine.Opcode {
+		if _, ok := vindex.Vindex.(vindexes.Lookup); ok {
+			return engine.Scatter
+		}
+		return equalOrEqualUnique(vindex)
+	}
 
-	return r.haveMatchingVindex(ctx, node, vdValue, column, val, equalOrEqualUnique, justTheVindex)
+	return r.haveMatchingVindex(ctx, node, vdValue, column, val, opcodeF, justTheVindex)
 }
diff --git a/go/vt/vttablet/onlineddl/analysis.go b/go/vt/vttablet/onlineddl/analysis.go
index f3fd8f3d70..70a43aad51 100644
--- a/go/vt/vttablet/onlineddl/analysis.go
+++ b/go/vt/vttablet/onlineddl/analysis.go
@@ -19,7 +19,9 @@ package onlineddl
 import (
 	"context"
 	"encoding/json"
+	"strings"
 
+	"vitess.io/vitess/go/mysql"
 	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
 	"vitess.io/vitess/go/vt/schema"
 	"vitess.io/vitess/go/vt/sqlparser"
@@ -29,6 +31,7 @@ import (
 type specialAlterOperation string
 
 const (
+	instantDDLSpecialOperation         specialAlterOperation = "instant-ddl"
 	dropRangePartitionSpecialOperation specialAlterOperation = "drop-range-partition"
 	addRangePartitionSpecialOperation  specialAlterOperation = "add-range-partition"
 )
@@ -53,6 +56,7 @@ func (p *SpecialAlterPlan) SetDetail(key string, val string) *SpecialAlterPlan {
 	p.details[key] = val
 	return p
 }
+
 func (p *SpecialAlterPlan) Detail(key string) string {
 	return p.details[key]
 }
@@ -83,7 +87,7 @@ func (e *Executor) getCreateTableStatement(ctx context.Context, tableName string
 }
 
 // analyzeDropRangePartition sees if the online DDL drops a single partition in a range partitioned table
-func (e *Executor) analyzeDropRangePartition(alterTable *sqlparser.AlterTable, createTable *sqlparser.CreateTable) (*SpecialAlterPlan, error) {
+func analyzeDropRangePartition(alterTable *sqlparser.AlterTable, createTable *sqlparser.CreateTable) (*SpecialAlterPlan, error) {
 	// we are looking for a `ALTER TABLE <table> DROP PARTITION <name>` statement with nothing else
 	if len(alterTable.AlterOptions) > 0 {
 		return nil, nil
@@ -135,7 +139,7 @@ func (e *Executor) analyzeDropRangePartition(alterTable *sqlparser.AlterTable, c
 }
 
 // analyzeAddRangePartition sees if the online DDL adds a partition in a range partitioned table
-func (e *Executor) analyzeAddRangePartition(alterTable *sqlparser.AlterTable, createTable *sqlparser.CreateTable) *SpecialAlterPlan {
+func analyzeAddRangePartition(alterTable *sqlparser.AlterTable, createTable *sqlparser.CreateTable) *SpecialAlterPlan {
 	// we are looking for a `ALTER TABLE <table> ADD PARTITION (PARTITION ...)` statement with nothing else
 	if len(alterTable.AlterOptions) > 0 {
 		return nil
@@ -171,9 +175,70 @@ func (e *Executor) analyzeAddRangePartition(alterTable *sqlparser.AlterTable, cr
 	return op
 }
 
+func alterOptionAvailableViaInstantDDL(alterOption sqlparser.AlterOption, createTable *sqlparser.CreateTable, serverVersion string) (bool, error) {
+	isVirtualColumn := func(colName string) bool {
+		if createTable == nil {
+			return false
+		}
+		for _, col := range createTable.TableSpec.Columns {
+			if strings.EqualFold(colName, col.Name.String()) {
+				return col.Type.Options.As != nil
+			}
+		}
+		return false
+	}
+	// Up to 8.0.26 we could only ADD COLUMN as last column
+	switch opt := alterOption.(type) {
+	case *sqlparser.AddColumns:
+		if opt.First || opt.After != nil {
+			// not a "last" column. Only supported as of 8.0.29
+			return mysql.ServerVersionAtLeast(serverVersion, 8, 0, 29)
+		}
+		// Adding a *last* column is supported in 8.0
+		return true, nil
+	case *sqlparser.DropColumn:
+		if isVirtualColumn(opt.Name.Name.String()) {
+			// supported by all 8.0 versions
+			return true, nil
+		}
+		return mysql.ServerVersionAtLeast(serverVersion, 8, 0, 29)
+	default:
+		return false, nil
+	}
+}
+
+// AnalyzeInstantDDL takes declarative CreateTable and AlterTable, as well as a server version, and checks whether it is possible to run the ALTER
+// using ALGORITM=INSTANT for that version.
+// This function is INTENTIONALLY public, even though we do not guarantee that it will remain so.
+func AnalyzeInstantDDL(alterTable *sqlparser.AlterTable, createTable *sqlparser.CreateTable, serverVersion string) (*SpecialAlterPlan, error) {
+	_, family, _ := mysql.GetFlavor(serverVersion, nil)
+	if family != mysql.MySQL80FlavorFamily {
+		return nil, nil
+	}
+	if alterTable.PartitionOption != nil {
+		// no INSTANT for partitions
+		return nil, nil
+	}
+	if alterTable.PartitionSpec != nil {
+		// no INSTANT for partitions
+		return nil, nil
+	}
+	for _, alterOption := range alterTable.AlterOptions {
+		instantOK, err := alterOptionAvailableViaInstantDDL(alterOption, createTable, serverVersion)
+		if err != nil {
+			return nil, err
+		}
+		if !instantOK {
+			return nil, nil
+		}
+	}
+	op := NewSpecialAlterOperation(instantDDLSpecialOperation, alterTable, createTable)
+	return op, nil
+}
+
 // analyzeSpecialAlterPlan checks if the given ALTER onlineDDL, and for the current state of affected table,
 // can be executed in a special way. If so, it returns with a "special plan"
-func (e *Executor) analyzeSpecialAlterPlan(ctx context.Context, onlineDDL *schema.OnlineDDL) (*SpecialAlterPlan, error) {
+func (e *Executor) analyzeSpecialAlterPlan(ctx context.Context, onlineDDL *schema.OnlineDDL, serverVersion string) (*SpecialAlterPlan, error) {
 	ddlStmt, _, err := schema.ParseOnlineDDLStatement(onlineDDL.SQL)
 	if err != nil {
 		return nil, err
@@ -192,14 +257,23 @@ func (e *Executor) analyzeSpecialAlterPlan(ctx context.Context, onlineDDL *schem
 	// special plans which support reverts are trivially desired:
 	// special plans which do not support reverts are flag protected:
 	if onlineDDL.StrategySetting().IsFastRangeRotationFlag() {
-		op, err := e.analyzeDropRangePartition(alterTable, createTable)
+		op, err := analyzeDropRangePartition(alterTable, createTable)
 		if err != nil {
 			return nil, err
 		}
 		if op != nil {
 			return op, nil
 		}
-		if op := e.analyzeAddRangePartition(alterTable, createTable); op != nil {
+		if op := analyzeAddRangePartition(alterTable, createTable); op != nil {
+			return op, nil
+		}
+	}
+	if onlineDDL.StrategySetting().IsFastOverRevertibleFlag() {
+		op, err := AnalyzeInstantDDL(alterTable, createTable, serverVersion)
+		if err != nil {
+			return nil, err
+		}
+		if op != nil {
 			return op, nil
 		}
 	}
diff --git a/go/vt/vttablet/onlineddl/executor.go b/go/vt/vttablet/onlineddl/executor.go
index 9e628109d1..0f9ccc5aa0 100644
--- a/go/vt/vttablet/onlineddl/executor.go
+++ b/go/vt/vttablet/onlineddl/executor.go
@@ -2541,11 +2541,31 @@ func (e *Executor) executeAlterViewOnline(ctx context.Context, onlineDDL *schema
 	return nil
 }
 
+// addInstantAlgorithm adds or modifies the AlterTable's ALGORITHM to INSTANT
+func (e *Executor) addInstantAlgorithm(alterTable *sqlparser.AlterTable) {
+	instantOpt := sqlparser.AlgorithmValue("INSTANT")
+	for i, opt := range alterTable.AlterOptions {
+		if _, ok := opt.(sqlparser.AlgorithmValue); ok {
+			// replace an existing algorithm
+			alterTable.AlterOptions[i] = instantOpt
+			return
+		}
+	}
+	// append an algorithm
+	alterTable.AlterOptions = append(alterTable.AlterOptions, instantOpt)
+}
+
 // executeSpecialAlterDDLActionMigrationIfApplicable sees if the given migration can be executed via special execution path, that isn't a full blown online schema change process.
 func (e *Executor) executeSpecialAlterDDLActionMigrationIfApplicable(ctx context.Context, onlineDDL *schema.OnlineDDL) (specialMigrationExecuted bool, err error) {
 	// Before we jump on to strategies... Some ALTERs can be optimized without having to run through
 	// a full online schema change process. Let's find out if this is the case!
-	specialPlan, err := e.analyzeSpecialAlterPlan(ctx, onlineDDL)
+	conn, err := dbconnpool.NewDBConnection(ctx, e.env.Config().DB.DbaWithDB())
+	if err != nil {
+		return false, err
+	}
+	defer conn.Close()
+
+	specialPlan, err := e.analyzeSpecialAlterPlan(ctx, onlineDDL, conn.ServerVersion)
 	if err != nil {
 		return false, err
 	}
@@ -2553,6 +2573,12 @@ func (e *Executor) executeSpecialAlterDDLActionMigrationIfApplicable(ctx context
 		return false, nil
 	}
 	switch specialPlan.operation {
+	case instantDDLSpecialOperation:
+		e.addInstantAlgorithm(specialPlan.alterTable)
+		onlineDDL.SQL = sqlparser.CanonicalString(specialPlan.alterTable)
+		if _, err := e.executeDirectly(ctx, onlineDDL); err != nil {
+			return false, err
+		}
 	case dropRangePartitionSpecialOperation:
 		dropPartition := func() error {
 			artifactTableName, err := schema.GenerateGCTableName(schema.HoldTableGCState, newGCTableRetainTime())
@@ -2562,11 +2588,6 @@ func (e *Executor) executeSpecialAlterDDLActionMigrationIfApplicable(ctx context
 			if err := e.updateArtifacts(ctx, onlineDDL.UUID, artifactTableName); err != nil {
 				return err
 			}
-			conn, err := dbconnpool.NewDBConnection(ctx, e.env.Config().DB.DbaWithDB())
-			if err != nil {
-				return err
-			}
-			defer conn.Close()
 
 			// Apply CREATE TABLE for artifact table
 			if _, err := e.createTableLike(ctx, artifactTableName, onlineDDL, conn); err != nil {


Test patch:
diff --git a/examples/local/orc_test.sh b/examples/local/orc_test.sh
index c8139d228c..ea13eec013 100755
--- a/examples/local/orc_test.sh
+++ b/examples/local/orc_test.sh
@@ -31,7 +31,7 @@ fi
 # start vtctld
 CELL=zone1 ./scripts/vtctld-up.sh
 
-vtctlclient AddCellInfo -root /vitess/zone2 -server_address "${ETCD_SERVER}" zone2
+vtctlclient AddCellInfo -- --root /vitess/zone2 --server_address "${ETCD_SERVER}" zone2
 
 # start vttablets for keyspace commerce
 for i in 100 101 102; do
@@ -45,10 +45,10 @@ for i in 110 111; do
 done
 
 # create the schema
-#vtctlclient ApplySchema -sql-file create_commerce_schema.sql commerce
+#vtctlclient ApplySchema -- --sql-file create_commerce_schema.sql commerce
 
 # create the vschema
-#vtctlclient ApplyVSchema -vschema_file vschema_commerce_initial.json commerce
+#vtctlclient ApplyVSchema -- --vschema_file vschema_commerce_initial.json commerce
 
 # start vtgate
 CELL=zone1 ./scripts/vtgate-up.sh
diff --git a/go/mysql/flavor_test.go b/go/mysql/flavor_test.go
index 92ec69f735..6fe7cf603d 100644
--- a/go/mysql/flavor_test.go
+++ b/go/mysql/flavor_test.go
@@ -46,6 +46,11 @@ func TestServerVersionAtLeast(t *testing.T) {
 			parts:   []int{7, 5, 20},
 			expect:  true,
 		},
+		{
+			version: "8.0.14",
+			parts:   []int{7, 5},
+			expect:  true,
+		},
 		{
 			version: "8.0.14-log",
 			parts:   []int{7, 5, 20},
@@ -73,7 +78,7 @@ func TestServerVersionAtLeast(t *testing.T) {
 		},
 	}
 	for _, tc := range testcases {
-		result, err := serverVersionAtLeast(tc.version, tc.parts...)
+		result, err := ServerVersionAtLeast(tc.version, tc.parts...)
 		if tc.expectError {
 			assert.Error(t, err)
 		} else {
@@ -82,3 +87,33 @@ func TestServerVersionAtLeast(t *testing.T) {
 		}
 	}
 }
+
+func TestGetFlavor(t *testing.T) {
+	testcases := []struct {
+		version string
+		family  FlavorFamilty
+	}{
+		{
+			version: "8.0.14",
+			family:  MySQL80FlavorFamily,
+		},
+		{
+			version: "8.0.0",
+			family:  MySQL80FlavorFamily,
+		},
+		{
+			version: "5.6.7",
+			family:  MySQL56FlavorFamily,
+		},
+		{
+			version: "5.7.29",
+			family:  MySQL57FlavorFamily,
+		},
+	}
+	for _, tc := range testcases {
+		t.Run(tc.version, func(t *testing.T) {
+			_, family, _ := GetFlavor(tc.version, nil)
+			assert.Equal(t, tc.family, family)
+		})
+	}
+}
diff --git a/go/test/endtoend/keyspace/keyspace_test.go b/go/test/endtoend/keyspace/keyspace_test.go
index b336a49f53..700b01c400 100644
--- a/go/test/endtoend/keyspace/keyspace_test.go
+++ b/go/test/endtoend/keyspace/keyspace_test.go
@@ -284,10 +284,10 @@ func TestDeleteKeyspace(t *testing.T) {
 	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("CreateKeyspace", "test_delete_keyspace_removekscell")
 	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("CreateShard", "test_delete_keyspace_removekscell/0")
 	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("CreateShard", "test_delete_keyspace_removekscell/1")
-	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "-port=1234", "-keyspace=test_delete_keyspace_removekscell", "-shard=0", "zone1-0000000100", "primary")
-	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "-port=1234", "-keyspace=test_delete_keyspace_removekscell", "-shard=1", "zone1-0000000101", "primary")
-	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "-port=1234", "-keyspace=test_delete_keyspace_removekscell", "-shard=0", "zone2-0000000100", "replica")
-	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "-port=1234", "-keyspace=test_delete_keyspace_removekscell", "-shard=1", "zone2-0000000101", "replica")
+	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "--port=1234", "-keyspace=test_delete_keyspace_removekscell", "--shard=0", "zone1-0000000100", "primary")
+	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "--port=1234", "-keyspace=test_delete_keyspace_removekscell", "--shard=1", "zone1-0000000101", "primary")
+	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "--port=1234", "-keyspace=test_delete_keyspace_removekscell", "--shard=0", "zone2-0000000100", "replica")
+	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "--port=1234", "-keyspace=test_delete_keyspace_removekscell", "--shard=1", "zone2-0000000101", "replica")
 
 	// Create the serving/replication entries and check that they exist,  so we can later check they're deleted.
 	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("RebuildKeyspaceGraph", "test_delete_keyspace_removekscell")
@@ -298,7 +298,7 @@ func TestDeleteKeyspace(t *testing.T) {
 
 	// Just remove the shard from one cell (including tablets),
 	// but leaving the global records and other cells/shards alone.
-	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("RemoveShardCell", "-recursive", "test_delete_keyspace_removekscell/0", "zone2")
+	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("RemoveShardCell", "--recursive", "test_delete_keyspace_removekscell/0", "zone2")
 
 	//Check that the shard is gone from zone2.
 	srvKeyspaceZone2 := getSrvKeyspace(t, cell2, "test_delete_keyspace_removekscell")
@@ -331,7 +331,7 @@ func TestDeleteKeyspace(t *testing.T) {
 	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("GetSrvKeyspace", "zone2", "test_delete_keyspace_removekscell")
 
 	// Add it back to do another test.
-	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "-port=1234", "-keyspace=test_delete_keyspace_removekscell", "-shard=0", "zone2-0000000100", "replica")
+	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("InitTablet", "--port=1234", "--keyspace=test_delete_keyspace_removekscell", "--shard=0", "zone2-0000000100", "replica")
 	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("RebuildKeyspaceGraph", "test_delete_keyspace_removekscell")
 	_ = clusterForKSTest.VtctlclientProcess.ExecuteCommand("GetShardReplication", "zone2", "test_delete_keyspace_removekscell/0")
 
diff --git a/go/test/endtoend/onlineddl/revert/onlineddl_revert_test.go b/go/test/endtoend/onlineddl/revert/onlineddl_revert_test.go
index 48d8003df7..ff0850d351 100644
--- a/go/test/endtoend/onlineddl/revert/onlineddl_revert_test.go
+++ b/go/test/endtoend/onlineddl/revert/onlineddl_revert_test.go
@@ -85,6 +85,7 @@ var (
 	clusterInstance *cluster.LocalProcessCluster
 	shards          []cluster.Shard
 	vtParams        mysql.ConnParams
+	mysqlVersion    string
 
 	hostname              = "localhost"
 	keyspaceName          = "ks"
@@ -257,6 +258,12 @@ func TestSchemaChange(t *testing.T) {
 	shards = clusterInstance.Keyspaces[0].Shards
 	require.Equal(t, 1, len(shards))
 
+	mysqlVersion = onlineddl.GetMySQLVersion(t, clusterInstance.Keyspaces[0].Shards[0].PrimaryTablet())
+	require.NotEmpty(t, mysqlVersion)
+
+	_, flavorFamily, _ := mysql.GetFlavor(mysqlVersion, nil)
+	require.NotEqual(t, mysql.UnknownFlavorFamily, flavorFamily)
+
 	var uuids []string
 	ddlStrategy := "online"
 
@@ -644,6 +651,40 @@ func TestSchemaChange(t *testing.T) {
 		testSelectTableMetrics(t)
 	})
 
+	// INSTANT DDL
+	t.Run("INSTANT DDL: add column", func(t *testing.T) {
+		uuid := testOnlineDDLStatementForTable(t, "alter table stress_test add column i_instant int not null default 0", ddlStrategy+" --fast-over-revertible", "vtgate", "i_instant")
+		uuids = append(uuids, uuid)
+		onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
+		checkTable(t, tableName, true)
+
+		rs := onlineddl.ReadMigrations(t, &vtParams, uuid)
+		require.NotNil(t, rs)
+		row := rs.Named().Row()
+		require.NotNil(t, row)
+		specialPlan := row.AsString("special_plan", "")
+		artifacts := row.AsString("artifacts", "")
+		if flavorFamily == mysql.MySQL80FlavorFamily {
+			// instant DDL expected to apply in 8.0
+			assert.Contains(t, specialPlan, "instant-ddl")
+			assert.Empty(t, artifacts)
+		} else {
+			// instant DDL not possible, this is a normal vrepl migration
+			assert.Empty(t, specialPlan)
+			assert.NotEmpty(t, artifacts)
+		}
+	})
+	t.Run("INSTANT DDL: fail revert", func(t *testing.T) {
+		uuid := testRevertMigration(t, uuids[len(uuids)-1], ddlStrategy)
+		uuids = append(uuids, uuid)
+		if flavorFamily == mysql.MySQL80FlavorFamily {
+			// instant DDL expected to apply in 8.0, therefore revert is impossible
+			onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusFailed)
+		} else {
+			onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
+		}
+	})
+
 	// DROP
 	t.Run("online DROP TABLE", func(t *testing.T) {
 		uuid := testOnlineDDLStatementForTable(t, dropStatement, "online", "vtgate", "")
diff --git a/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go b/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go
index b0a69fcf57..7280231dd0 100644
--- a/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go
+++ b/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go
@@ -243,9 +243,9 @@ func TestSchemaChange(t *testing.T) {
 	defer cluster.PanicHandler(t)
 
 	shards = clusterInstance.Keyspaces[0].Shards
-	assert.Equal(t, 2, len(shards))
+	require.Equal(t, 2, len(shards))
 	for _, shard := range shards {
-		assert.Equal(t, 2, len(shard.Vttablets))
+		require.Equal(t, 2, len(shard.Vttablets))
 	}
 
 	providedUUID := ""
@@ -561,7 +561,23 @@ func TestSchemaChange(t *testing.T) {
 		onlineddl.CheckRetryMigration(t, &vtParams, shards, uuid, false)
 	})
 	t.Run("Online DROP TABLE IF EXISTS, vtgate", func(t *testing.T) {
-		uuid := testOnlineDDLStatement(t, onlineDDLDropTableIfExistsStatement, "online", providedUUID, providedMigrationContext, "vtgate", "", "", false)
+		uuid := testOnlineDDLStatement(t, onlineDDLDropTableIfExistsStatement, "online ", providedUUID, providedMigrationContext, "vtgate", "", "", false)
+		onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
+		onlineddl.CheckCancelMigration(t, &vtParams, shards, uuid, false)
+		onlineddl.CheckRetryMigration(t, &vtParams, shards, uuid, false)
+		// this table existed
+		checkTables(t, schema.OnlineDDLToGCUUID(uuid), 1)
+	})
+	t.Run("Online CREATE, vtctl, extra flags", func(t *testing.T) {
+		// the flags are meaningless to this migration. The test just validates that they don't get in the way.
+		uuid := testOnlineDDLStatement(t, onlineDDLCreateTableStatement, "vitess --fast-over-revertible --allow-zero-in-date", providedUUID, providedMigrationContext, "vtctl", "online_ddl_create_col", "", false)
+		onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
+		onlineddl.CheckCancelMigration(t, &vtParams, shards, uuid, false)
+		onlineddl.CheckRetryMigration(t, &vtParams, shards, uuid, false)
+	})
+	t.Run("Online DROP TABLE IF EXISTS, vtgate, extra flags", func(t *testing.T) {
+		// the flags are meaningless to this migration. The test just validates that they don't get in the way.
+		uuid := testOnlineDDLStatement(t, onlineDDLDropTableIfExistsStatement, "vitess --fast-over-revertible --allow-zero-in-date", providedUUID, providedMigrationContext, "vtgate", "", "", false)
 		onlineddl.CheckMigrationStatus(t, &vtParams, shards, uuid, schema.OnlineDDLStatusComplete)
 		onlineddl.CheckCancelMigration(t, &vtParams, shards, uuid, false)
 		onlineddl.CheckRetryMigration(t, &vtParams, shards, uuid, false)
diff --git a/go/test/endtoend/onlineddl/vttablet_util.go b/go/test/endtoend/onlineddl/vttablet_util.go
index ec0a1e9d8d..4d4e88b818 100644
--- a/go/test/endtoend/onlineddl/vttablet_util.go
+++ b/go/test/endtoend/onlineddl/vttablet_util.go
@@ -26,6 +26,7 @@ import (
 
 	"vitess.io/vitess/go/test/endtoend/cluster"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -64,3 +65,14 @@ func WaitForVReplicationStatus(t *testing.T, vtParams *mysql.ConnParams, shards
 	}
 	return lastKnownStatus
 }
+
+func GetMySQLVersion(t *testing.T, tablet *cluster.Vttablet) string {
+	query := `select @@version as version`
+	rs, err := tablet.VttabletProcess.QueryTablet(query, "", true)
+	assert.NoError(t, err)
+	row := rs.Named().Row()
+	assert.NotNil(t, row)
+	version := row["version"].ToString()
+	assert.NotEmpty(t, row)
+	return version
+}
diff --git a/go/test/endtoend/vtgate/reservedconn/reconnect3/main_test.go b/go/test/endtoend/vtgate/reservedconn/reconnect3/main_test.go
index fd0fc2df9b..ff0e675086 100644
--- a/go/test/endtoend/vtgate/reservedconn/reconnect3/main_test.go
+++ b/go/test/endtoend/vtgate/reservedconn/reconnect3/main_test.go
@@ -88,7 +88,7 @@ func TestMysqlDownServingChange(t *testing.T) {
 	require.NoError(t,
 		primaryTablet.MysqlctlProcess.Stop())
 	require.NoError(t,
-		clusterInstance.VtctlclientProcess.ExecuteCommand("EmergencyReparentShard", "-keyspace_shard", "ks/0"))
+		clusterInstance.VtctlclientProcess.ExecuteCommand("EmergencyReparentShard", "--", "--keyspace_shard", "ks/0"))
 
 	// This should work without any error.
 	_ = utils.Exec(t, conn, "select /*vt+ PLANNER=gen4 */ * from test")
diff --git a/go/test/endtoend/vtgate/reservedconn/reconnect4/main_test.go b/go/test/endtoend/vtgate/reservedconn/reconnect4/main_test.go
index 98934aed6b..d4e059c170 100644
--- a/go/test/endtoend/vtgate/reservedconn/reconnect4/main_test.go
+++ b/go/test/endtoend/vtgate/reservedconn/reconnect4/main_test.go
@@ -90,7 +90,7 @@ func TestVttabletDownServingChange(t *testing.T) {
 	// kill vttablet process
 	_ = primaryTablet.VttabletProcess.TearDown()
 	require.NoError(t,
-		clusterInstance.VtctlclientProcess.ExecuteCommand("EmergencyReparentShard", "-keyspace_shard", "ks/0"))
+		clusterInstance.VtctlclientProcess.ExecuteCommand("EmergencyReparentShard", "--", "--keyspace_shard", "ks/0"))
 
 	// This should work without any error.
 	_ = utils.Exec(t, conn, "select /*vt+ PLANNER=gen4 */ * from test")
diff --git a/go/vt/vtgate/planbuilder/testdata/filter_cases.txt b/go/vt/vtgate/planbuilder/testdata/filter_cases.txt
index 824d91d751..af8aede845 100644
--- a/go/vt/vtgate/planbuilder/testdata/filter_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/filter_cases.txt
@@ -2126,7 +2126,21 @@ Gen4 plan same as above
     "Vindex": "music_user_map"
   }
 }
-Gen4 plan same as above
+{
+  "QueryType": "SELECT",
+  "Original": "select id from music where id is null",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "Scatter",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select id from music where 1 != 1",
+    "Query": "select id from music where id is null",
+    "Table": "music"
+  }
+}
 
 # SELECT with IS NOT NULL
 "select id from music where id is not null"
@@ -4051,3 +4065,44 @@ Gen4 plan same as above
     "Vindex": "user_index"
   }
 }
+
+# Don't pick a vindex for an IS NULL predicate if it's a lookup vindex
+"select id from music where id is null and user_id in (1,2)"
+{
+  "QueryType": "SELECT",
+  "Original": "select id from music where id is null and user_id in (1,2)",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "EqualUnique",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select id from music where 1 != 1",
+    "Query": "select id from music where id is null and user_id in (1, 2)",
+    "Table": "music",
+    "Values": [
+      "NULL"
+    ],
+    "Vindex": "music_user_map"
+  }
+}
+{
+  "QueryType": "SELECT",
+  "Original": "select id from music where id is null and user_id in (1,2)",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "IN",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select id from music where 1 != 1",
+    "Query": "select id from music where id is null and user_id in ::__vals",
+    "Table": "music",
+    "Values": [
+      "(INT64(1), INT64(2))"
+    ],
+    "Vindex": "user_index"
+  }
+}
diff --git a/go/vt/vttablet/onlineddl/analysis_test.go b/go/vt/vttablet/onlineddl/analysis_test.go
new file mode 100644
index 0000000000..0705cefe02
--- /dev/null
+++ b/go/vt/vttablet/onlineddl/analysis_test.go
@@ -0,0 +1,128 @@
+/*
+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.
+*/
+
+/*
+Functionality of this Executor is tested in go/test/endtoend/onlineddl/...
+*/
+
+package onlineddl
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"vitess.io/vitess/go/vt/sqlparser"
+)
+
+func TestAnalyzeInstantDDL(t *testing.T) {
+	tt := []struct {
+		version     string
+		create      string
+		alter       string
+		expectError bool
+		instant     bool
+	}{
+		{
+			version: "5.7.28",
+			create:  "create table t(id int, i1 int not null, primary key(id))",
+			alter:   "alter table t add column i2 int not null",
+			instant: false,
+		},
+		{
+			version: "8.0.21",
+			create:  "create table t(id int, i1 int not null, primary key(id))",
+			alter:   "alter table t add column i2 int not null",
+			instant: true,
+		},
+		{
+			version: "8.0.21",
+			create:  "create table t(id int, i1 int not null, primary key(id))",
+			alter:   "alter table t add column i2 int not null, add column i3 int not null",
+			instant: true,
+		},
+		{
+			version: "8.0.21",
+			create:  "create table t(id int, i1 int not null, primary key(id))",
+			alter:   "alter table t add column i2 int not null after id",
+			instant: false,
+		},
+		{
+			version: "8.0.21",
+			create:  "create table t(id int, i1 int not null, primary key(id))",
+			alter:   "alter table t drop column i1",
+			instant: false,
+		},
+		{
+			version: "8.0.21",
+			create:  "create table t(id int, i1 int not null, i2 int generated always as (i1 + 1) virtual, primary key(id))",
+			alter:   "alter table t drop column i2",
+			instant: true,
+		},
+		{
+			version: "8.0.29",
+			create:  "create table t(id int, i1 int not null, primary key(id))",
+			alter:   "alter table t add column i2 int not null after id",
+			instant: true,
+		},
+		{
+			version: "8.0.29",
+			create:  "create table t(id int, i1 int not null, i2 int not null, primary key(id))",
+			alter:   "alter table t drop column i1",
+			instant: true,
+		},
+		{
+			version: "8.0.29",
+			create:  "create table t(id int, i1 int not null, primary key(id))",
+			alter:   "alter table t add column i2 int not null after id, add column i3 int not null",
+			instant: true,
+		},
+		{
+			version: "8.0.29",
+			create:  "create table t(id int, i1 int not null, primary key(id))",
+			alter:   "alter table t add column i2 int not null after id, add column i3 int not null, drop column i1",
+			instant: true,
+		},
+	}
+	for _, tc := range tt {
+		name := tc.version + " " + tc.create
+		t.Run(name, func(t *testing.T) {
+			stmt, err := sqlparser.ParseStrictDDL(tc.create)
+			require.NoError(t, err)
+			createTable, ok := stmt.(*sqlparser.CreateTable)
+			require.True(t, ok)
+
+			stmt, err = sqlparser.ParseStrictDDL(tc.alter)
+			require.NoError(t, err)
+			alterTable, ok := stmt.(*sqlparser.AlterTable)
+			require.True(t, ok)
+
+			plan, err := AnalyzeInstantDDL(alterTable, createTable, tc.version)
+			if tc.expectError {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+				if tc.instant {
+					require.NotNil(t, plan)
+					assert.Equal(t, instantDDLSpecialOperation, plan.operation)
+				} else {
+					require.Nil(t, plan)
+				}
+			}
+		})
+	}
+}
diff --git a/go/vt/vttablet/onlineddl/executor_test.go b/go/vt/vttablet/onlineddl/executor_test.go
index 81ec043922..2406650f93 100644
--- a/go/vt/vttablet/onlineddl/executor_test.go
+++ b/go/vt/vttablet/onlineddl/executor_test.go
@@ -97,3 +97,46 @@ func TestValidateAndEditCreateTableStatement(t *testing.T) {
 		})
 	}
 }
+
+func TestAddInstantAlgorithm(t *testing.T) {
+	e := Executor{}
+	tt := []struct {
+		alter  string
+		expect string
+	}{
+		{
+			alter:  "alter table t add column i2 int not null",
+			expect: "ALTER TABLE `t` ADD COLUMN `i2` int NOT NULL, ALGORITHM = INSTANT",
+		},
+		{
+			alter:  "alter table t add column i2 int not null, lock=none",
+			expect: "ALTER TABLE `t` ADD COLUMN `i2` int NOT NULL, LOCK NONE, ALGORITHM = INSTANT",
+		},
+		{
+			alter:  "alter table t add column i2 int not null, algorithm=inplace",
+			expect: "ALTER TABLE `t` ADD COLUMN `i2` int NOT NULL, ALGORITHM = INSTANT",
+		},
+		{
+			alter:  "alter table t add column i2 int not null, algorithm=inplace, lock=none",
+			expect: "ALTER TABLE `t` ADD COLUMN `i2` int NOT NULL, ALGORITHM = INSTANT, LOCK NONE",
+		},
+	}
+	for _, tc := range tt {
+		t.Run(tc.alter, func(t *testing.T) {
+			stmt, err := sqlparser.ParseStrictDDL(tc.alter)
+			require.NoError(t, err)
+			alterTable, ok := stmt.(*sqlparser.AlterTable)
+			require.True(t, ok)
+
+			e.addInstantAlgorithm(alterTable)
+			alterInstant := sqlparser.CanonicalString(alterTable)
+
+			assert.Equal(t, tc.expect, alterInstant)
+
+			stmt, err = sqlparser.ParseStrictDDL(alterInstant)
+			require.NoError(t, err)
+			_, ok = stmt.(*sqlparser.AlterTable)
+			require.True(t, ok)
+		})
+	}
+}
diff --git a/test/client_test.sh b/test/client_test.sh
index dc8def11d2..8624a51a44 100755
--- a/test/client_test.sh
+++ b/test/client_test.sh
@@ -30,10 +30,10 @@ for i in 100 101 102; do
  CELL=test KEYSPACE=test_keyspace TABLET_UID=$i ./scripts/vttablet-up.sh
 done
 
-vtctlclient -server localhost:15999 InitShardPrimary -force test_keyspace/0 test-100
+vtctlclient --server localhost:15999 InitShardPrimary -- --force test_keyspace/0 test-100
 
-vtctlclient -server localhost:15999 ApplySchema -sql-file create_test_table.sql test_keyspace
-vtctlclient -server localhost:15999 RebuildVSchemaGraph
+vtctlclient --server localhost:15999 ApplySchema -- --sql-file create_test_table.sql test_keyspace
+vtctlclient --server localhost:15999 RebuildVSchemaGraph
 
 CELL=test ./scripts/vtgate-up.sh