Repository: vitessio/vitess (Go)

Issue:
RFC: View Support
# Introduction

MySQL supports read views and updatable views. A View acts as a virtual table that is stored as a query and produces a result set when invoked. There are multiple use cases of why an application wants to use views but in this RFC we will focus on how we will add support for views in Vitess.

# Problem Statement

## Current Support
View support exists for unsharded keyspace via creating it on the underlying database.
For sharded keyspace, the support is limited to a single table as the user would be able to define sharding key for that view in the VSchema. This is useful to users who want to restrict the viewable columns for a table.

## New Support
There are limitations to the current support, like for sharded keyspace the current version is barely usable as it is very restrictive. Also, the users who are currently having views in unsharded keyspace cannot move to sharded keyspace.

This enhancement over current support will add complete support for **_readable views_** in the **_sharded keyspace_**.

# Proposal

This proposed document has its scope limited to only readable views. In the next iteration, we might extend it for updatable views, which will be a separate RFC.

## Query Serving
In the query serving path, the view definition will be cached at VTGates. The view table will be rewritten as a derived table containing the view definition. The normal query planning will follow thereafter.

An example is always helpful. For a view like:

```sql
create view user_view as select id, name from user
```

The query:

```sql
select * from user_view
```

would be rewritten to:

```sql
select * from (select id, name from user) as user_view
```

For the information_schema queries, describe queries, show queries, etc. that return the metadata, the view information would need to be injected to return the expected resultset back to the client.

## Storage / DDL Support
There are two ways we can store the view definition. Based on the location of the storage of views, there will be differences in how DDLs will be supported.

### 1: In the Topology Server (TopoServer)
The views will be stored as part of Vschema like how vindexes and tables information is stored.
Example for a keyspace:
```json
{
  "sharded": true,
  "vindexes": {...}
  "tables": {
    "user": {...},
    "user_extra": {...}
  },
  "views": {
    "user_view": "select id, name from user",
    "user_details_view": "select user.id, user_extra.col from user join user_extra on user.id = user_extra.user_id"
  }
}
```

#### DDL Support
The view definition can be added/removed using the vtctld `ApplyVSchema` command by providing the complete VSchema containing the view definition.
It can also be modified using VSchema DDL
```sql
alter vschema create view <view_name> as <select_statement>
alter vschema drop view <view_name>
```
Create View/Alter View/Drop View statements will be re-rewritten by vitess into VSchema DDLs.

Pros:
1. Atomic Update of Views definition.
2. One or more shard primary non-availability does not impact updating views definition.
3. Query will continue to serve when the TopoServer is not available.

Cons:
1. Non-availability of the Toposerver will impact updating view definition.
2. There is a size limitation on the object size that can be stored in the TopoServer. This can impact the number of views that can be created.
    * Zookeeper: 1MB hard limit
    * Etcd: 1.5MB soft limit, can request more with `--max-request-bytes`
    * K8s uses Etcd behind but has put restrictions on the sizes like 3MB and different Kubernetes cloud providers have their own limits defined.


### 2: In MySQL
The views will be stored in `_vt` the vitess internal schema where vreplication and/or online schema change metadata are stored.
View table inside `_vt` will look like:
```sql
CREATE TABLE `VIEWS` (
  `TABLE_CATALOG` varchar(512) NOT NULL DEFAULT '',
  `TABLE_SCHEMA` varchar(64) NOT NULL DEFAULT '',
  `TABLE_NAME` varchar(64) NOT NULL DEFAULT '',
  `VIEW_DEFINITION` longtext NOT NULL,
  `CHECK_OPTION` varchar(8) NOT NULL DEFAULT '',
  `IS_UPDATABLE` varchar(3) NOT NULL DEFAULT '',
  `DEFINER` varchar(93) NOT NULL DEFAULT '',
  `SECURITY_TYPE` varchar(7) NOT NULL DEFAULT '',
  `CHARACTER_SET_CLIENT` varchar(32) NOT NULL DEFAULT '',
  `COLLATION_CONNECTION` varchar(32) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8

```

#### DDL Support
Create View/Alter View/Drop View statements will be interpreted and executed as per the table definition in `_vt`.`views`.

Pros:
1. TopoServer non-availability does not impact updating views definition.
2. Query will continue to serve/buffer when one/more shards is unavailable.

Cons:
1. No ACID guarantee for updating view definition.
2. User have to manually fix the shards which have diverged view definitions.

# Decision
With the limitation on TopoServer object size, this will impose as a blocker for the users as with multiple view definition this limit might be breached and hence will make it unusable.
While storing in `_vt` has its own challenges, it does not have an achievable size limitation which makes it a viable option.

# Task
- [x] View Query Rewriting https://github.com/vitessio/vitess/pull/11195
- [x] View Storage https://github.com/vitessio/vitess/pull/11896
- [x] View DDL support https://github.com/vitessio/vitess/pull/11896
- [x] View Change Notify from VTTablet https://github.com/vitessio/vitess/pull/12115
- [x] View Definition caching in VTGates https://github.com/vitessio/vitess/pull/12124
- [x] Add Website Document https://github.com/vitessio/website/pull/1390
- [x] Add Release Notes https://github.com/vitessio/vitess/pull/12422
- [x] View Information Schema query support https://github.com/vitessio/vitess/pull/12409
- [x] View Show query support https://github.com/vitessio/vitess/pull/12409
- [x] View Describe query support https://github.com/vitessio/vitess/pull/12409
- [x] Global Routing on views
- [x] Tables and Views should be unique in a keyspace https://github.com/vitessio/vitess/pull/12409
- [x] #12448
- [x] expand star expression in views query with column list before storing - Works with gen4 planner
- [x] restrict views support to a keyspace https://github.com/vitessio/vitess/pull/12409
- [x] Use of Schema RPC to retrieve view definition over using query at VTGate https://github.com/vitessio/vitess/pull/12375

# Call for feedback
We are looking for the community’s feedback on the above design implementation. Thank you for taking the time to read and respond.



Discussion: I like the feature but I'm iffy about the implementation.
Here are the concerns:
- One of the principles we want to maintain is that queries can still be served if topo is unreachable. How do we accomplish that if the view definition is stored in the topo? Perhaps you are thinking we'll consult the cache within vtgate. What is the TTL for that cache? How long can we operate in that mode?
- Topo object size is also a concern. We still support consul where objects can only be up to 512K. There will be people who hit this limit with their view definitions
- We are saying that we won't support information_schema, describe, show. This seems to be a restriction that is derived from the implementation decision of storing the view in topo. If we reverse that decision order, and decide that at least in future we want to support IS, describe and show, what will such a design look like?
> One of the principles we want to maintain is that queries can still be served if topo is unreachable. How do we accomplish that if the view definition is stored in the topo? Perhaps you are thinking we'll consult the cache within vtgate. What is the TTL for that cache? How long can we operate in that mode?

There is no impact this is similar to having the table sharding information. If there is no sharding information available a sharded keyspace cannot be served. If VSchema is cached once, then VTGate will continue to serve the query and does not remove it from the cache till it is not able to acquire/fetch the new value.

> Topo object size is also a concern. We still support consul where objects can only be up to 512K. There will be people who hit this limit with their view definition

To my knowledge Consul support is deprecated from all the current supported versions of Vitess. So, my opinion would be that any future decision should not rely on the limitation of the consul server.

> We are saying that we won't support information_schema, describe, show. This seems to be a restriction that is derived from the implementation decision of storing the view in topo. If we reverse that decision order, and decide that at least in future we want to support IS, describe and show, what will such a design look like?

(*On Top Of My Mind*) To support these we have to most likely run a sidecar in-memory database along with each VTGate where we can store the whole schema of the system, where we can actually create the schema/tables/views, this can help in performing any information_schema query or any schema related / metadata-related query.

The current implementation or storing the views in `_vt` will continue to have the same limitation.
We can add some additional logic in the query planner and/or query execution that can overcome these limitations temporarily.

SGTM! A couple of points/questions:
1.   IMO it's worth clarifying what impact views created directly in mysqld may have on those in the vschema/vtgate, precedence, conflicts, etc. — keeping in mind sharded and unsharded keyspaces
2. Will the user also need to do `RebuildVSchemaGraph`?
> SGTM! A couple of points/questions:
> 
> 1. IMO it's worth clarifying what impact views created directly in mysqld may have on those in the vschema/vtgate, precedence, conflicts, etc. — keeping in mind sharded and unsharded keyspaces

* In Unsharded case: Vschema definition of views will take higher precedence over views present in MySQL. The reason is vschema declaration is optional.
* In Sharded case: Views has be declared in Vschema to be discoverable. The only way to bypass this is by using shard targeting which also means that your views are written in such a way that it does not need cross-shard query planning.

> 2. Will the user also need to do `RebuildVSchemaGraph`?

I believe this should not be the case as `ApplyVschema` and `Vschema DDL` does not require it.
I am not completely sure about this, but I can add a test and validate it.


> To my knowledge Consul support is deprecated from all the current supported versions of Vitess. So, my opinion would be that any future decision should not rely on the limitation of the consul server.

This is correct and was documented in 7.0 release notes. We need to document this properly on the website so that new adopters don't use it.

One last question:
What will be the command to list the current views? and to see their definitions?
GetVSchema? If yes, let's write that down.

I don't like the fact that we are planning to claim views support without "show", that is non-intuitive. However, we anyway need to mark this as experimental. Proper documentation is going to be key, otherwise I bet the first bug report will be, I created a view and show create view doesn't work.
I have updated the RFC with the comments received so far.
I don't like the idea of using Topo as as full blown storage instead it should just be saving metadata (small piece of information). Topo should mainly be used as configuration information system. So my vote is for saving in MySQL. 

Also the RFC does not mention any detail around if we support cross shard views or scattered views. 

To provide consistent view or ACID guaranteed for our views is one of the many reason we should have Atomic broadcast or fault-tolerant replication instead of relying of mysql vanilla replication. But that is a separate topic to be discussed some other time. 
  
> I don't like the idea of using Topo as as full blown storage instead it should just be saving metadata (small piece of information). Topo should mainly be used as configuration information system. So my vote is for saving in MySQL.
> 
> Also the RFC does not mention any detail around if we support cross shard views or scattered views.
> 
> To provide consistent view or ACID guaranteed for our views is one of the many reason we should have Atomic broadcast or fault-tolerant replication instead of relying of mysql vanilla replication. But that is a separate topic to be discussed some other time.

Thank you for reading the RFC and providing input.

RFC is about adding support for Views in Vitess for sharded and unsharded keyspace to support all the cases which includes support for cross-shard/scatter view query.
I suggest a slight modification of how we store view definition.

The info that we'll store will be only the state that we need right now:

```sql
CREATE TABLE IF NOT EXISTS _vt.VIEWS (
  TABLE_NAME varchar(64) NOT NULL,
  VIEW_DEFINITION longtext NOT NULL,
  CREATE_STATEMENT longtext NOT NULL,
  PRIMARY KEY (TABLE_NAME))
```

By storing the original create view statement in `CREATE_STATEMENT`, if we later on want to extract more information, we can look at the original request from the user.

Additionally, I think we should store the view names in the topo, together with a view version. That way, when a view is updated, the topo subscription will tell the vtgates that they need to load the new view definition from the tablet. The alternative to this would have been to lean on the healthcheck, but I think using the topo for this is simpler and faster.

I'll update the description later today if no-one has strong opinions against this suggestion


I like this regarding the topo storing view name and version. I think the responsibility of storing that should still lie on to the vtgate that received the view DDL statement, once the results are returned from vttablet.
Storing the create_statement sounds like a good idea. Why `longtext` instead of some other type like VARBINARY or BLOB? Did you consider pros and cons before choosing the data type?

Storing the view version in the topo seems like inventing another way of keeping up with schema updates. Why is this not part of schema tracking? Are you trying to de-couple those and let views work in the absence of schema tracking?
In varchar we have to provide some max length which we do not know upfront. The benefit of varchar is that those columns can be indexed. I do not see in future we would index view_definition or create_statement column.
> Storing the view version in the topo seems like inventing another way of keeping up with schema updates. Why is his not part of schema tracking? Are you trying to de-couple those and let views work in the absence of schema tracking?

Schema tracking is about tracking the schema on the mysql instances. This is different - the views will not be actually created on the shards, so there is none of that schema to track. Additionally, we are trying to move away from the current schema tracking and move the to One Schema-tracker that listens on replication traffic.

I think adding more responsibilities to the existing query serving schema tracker is a step in the wrong direction given our current plans.
Additional Tasks:
- [ ] Global Routing on views
- [ ] Tables and Views should be unique in a keyspace
- [ ] Views should be moved to new shards on Resharding
- [ ] Add support for `show vschema views`
- [X] expand star expression in views query with column list before storing - Works with gen4 planner
- [ ] restrict views support to a keyspace

-- Moved them to the description.
I and @harshit-gangal discussed this approach for unsharded keyspaces recently.
Currently, Vitess supports views being created in an unsharded keyspace and vtgate treats them like any other table. 

With the changes suggested in this RFC, instead of creating a view, we will store its definition in the `_vt` table and vtgate will change the references to that table with a derived table.

The problem is that the Vitess planner isn't smart enough to always plan queries using a derived table in the best way possible. This means that we may end up with somewhat suboptimal plans for a use-case that we already supported. However, after looking at the code, we realized there is no way to support both the variants and keep the code maintainable.



Fix (patch):
diff --git a/go/mysql/schema.go b/go/mysql/schema.go
index cfc9af0c206..3a59c771f6e 100644
--- a/go/mysql/schema.go
+++ b/go/mysql/schema.go
@@ -99,26 +99,36 @@ order by table_name, ordinal_position`
 	// GetColumnNamesQueryPatternForTable is used for mocking queries in unit tests
 	GetColumnNamesQueryPatternForTable = `SELECT COLUMN_NAME.*TABLE_NAME.*%s.*`
 
-	// Views
-	InsertIntoViewsTable = `insert into %s.views (
-    table_schema,
-	table_name,
-	create_statement) values (database(), :table_name, :create_statement)`
+	// DetectViewChange query detects if there is any view change from previous copy.
+	DetectViewChange = `
+SELECT distinct table_name
+FROM (
+	SELECT table_name, view_definition
+	FROM information_schema.views
+	WHERE table_schema = database()
 
-	ReplaceIntoViewsTable = `replace into %s.views (
-	table_schema,
-	table_name,
-	create_statement) values (database(), :table_name, :create_statement)`
+	UNION ALL
 
-	UpdateViewsTable = `update %s.views
-	set create_statement = :create_statement 
-	where table_schema = database() and table_name = :table_name`
+	SELECT table_name, view_definition
+	FROM %s.views
+	WHERE table_schema = database()
+) _inner
+GROUP BY table_name, view_definition
+HAVING COUNT(*) = 1
+`
+	// FetchViewDefinition retrieves view definition from information_schema.views table.
+	FetchViewDefinition = `select table_name, view_definition from information_schema.views
+where table_schema = database() and table_name in ::tableNames`
 
-	DeleteFromViewsTable = `delete from %s.views where table_schema = database() and table_name in ::table_name`
+	// FetchCreateStatement retrieves create statement.
+	FetchCreateStatement = `show create table %s`
 
-	SelectFromViewsTable = `select table_name from %s.views where table_schema = database() and table_name in ::table_name`
+	// DeleteFromViewsTable removes the views from the table.
+	DeleteFromViewsTable = `delete from %s.views where table_schema = database() and table_name in ::tableNames`
 
-	SelectAllViews = `select table_name, updated_at from %s.views where table_schema = database()`
+	// InsertIntoViewsTable using information_schema.views.
+	InsertIntoViewsTable = `insert %s.views(table_schema, table_name, create_statement, view_definition)
+values (database(), :table_name, :create_statement, :view_definition)`
 
 	// FetchUpdatedViews queries fetches information about updated views
 	FetchUpdatedViews = `select table_name, create_statement from %s.views where table_schema = database() and table_name in ::viewnames`
diff --git a/go/vt/sidecardb/schema/misc/views.sql b/go/vt/sidecardb/schema/misc/views.sql
index 4ae1b51f226..36123fb0d85 100644
--- a/go/vt/sidecardb/schema/misc/views.sql
+++ b/go/vt/sidecardb/schema/misc/views.sql
@@ -18,7 +18,8 @@ CREATE TABLE IF NOT EXISTS views
 (
     TABLE_SCHEMA varchar(64) NOT NULL,
     TABLE_NAME varchar(64) NOT NULL,
-    CREATE_STATEMENT longtext NOT NULL,
+    CREATE_STATEMENT longtext,
+    VIEW_DEFINITION longtext NOT NULL,
     UPDATED_AT TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     PRIMARY KEY (TABLE_SCHEMA, TABLE_NAME)
 ) engine = InnoDB
diff --git a/go/vt/vttablet/endtoend/framework/client.go b/go/vt/vttablet/endtoend/framework/client.go
index ea72d2ee89d..b06c78645e8 100644
--- a/go/vt/vttablet/endtoend/framework/client.go
+++ b/go/vt/vttablet/endtoend/framework/client.go
@@ -405,6 +405,11 @@ func (client *QueryClient) StreamHealth(sendFunc func(*querypb.StreamHealthRespo
 	return client.server.StreamHealth(client.ctx, sendFunc)
 }
 
+// StreamHealthWithContext receives the health response
+func (client *QueryClient) StreamHealthWithContext(ctx context.Context, sendFunc func(*querypb.StreamHealthResponse) error) error {
+	return client.server.StreamHealth(ctx, sendFunc)
+}
+
 func (client *QueryClient) UpdateContext(ctx context.Context) {
 	client.ctx = ctx
 }
diff --git a/go/vt/vttablet/tabletserver/health_streamer.go b/go/vt/vttablet/tabletserver/health_streamer.go
index 297e5ebdd63..2bae9a17169 100644
--- a/go/vt/vttablet/tabletserver/health_streamer.go
+++ b/go/vt/vttablet/tabletserver/health_streamer.go
@@ -27,6 +27,8 @@ import (
 
 	"github.com/spf13/pflag"
 
+	"vitess.io/vitess/go/vt/vttablet/tabletserver/planbuilder"
+
 	"vitess.io/vitess/go/vt/servenv"
 	"vitess.io/vitess/go/vt/sidecardb"
 
@@ -91,7 +93,6 @@ type healthStreamer struct {
 	signalWhenSchemaChange bool
 
 	viewsEnabled bool
-	views        map[string]string
 }
 
 func newHealthStreamer(env tabletenv.Env, alias *topodatapb.TabletAlias) *healthStreamer {
@@ -124,7 +125,6 @@ func newHealthStreamer(env tabletenv.Env, alias *topodatapb.TabletAlias) *health
 		conns:                  pool,
 		signalWhenSchemaChange: env.Config().SignalWhenSchemaChange,
 		viewsEnabled:           env.Config().EnableViews,
-		views:                  map[string]string{},
 	}
 	hs.unhealthyThreshold.Store(env.Config().Healthcheck.UnhealthyThresholdSeconds.Get().Nanoseconds())
 	return hs
@@ -349,7 +349,12 @@ func (hs *healthStreamer) reload() error {
 
 	views, err := hs.getChangedViewNames(ctx, conn)
 	if err != nil {
-		return err
+		if len(tables) == 0 {
+			return err
+		}
+		// there are some tables, we need to send that in the health stream.
+		// making views to nil will prevent any views notification.
+		views = nil
 	}
 
 	// no change detected
@@ -428,54 +433,142 @@ func (hs *healthStreamer) getChangedTableNames(ctx context.Context, conn *connpo
 	return tables, nil
 }
 
-func (hs *healthStreamer) getChangedViewNames(ctx context.Context, conn *connpool.DBConn) ([]string, error) {
+type viewDefAndStmt struct {
+	def  string
+	stmt string
+}
+
+func (hs *healthStreamer) getChangedViewNames(ctx context.Context, conn *connpool.DBConn) (views []string, err error) {
 	if !hs.viewsEnabled {
 		return nil, nil
 	}
-	var changedViews []string
-	views := map[string]string{}
 
+	/* Retrieve changed views */
 	callback := func(qr *sqltypes.Result) error {
 		for _, row := range qr.Rows {
-			viewName := row[0].ToString()
-			lastUpdTime := row[1].ToString()
-			views[viewName] = lastUpdTime
+			view := row[0].ToString()
+			views = append(views, view)
 		}
-
 		return nil
 	}
 	alloc := func() *sqltypes.Result { return &sqltypes.Result{} }
 	bufferSize := 1000
-	err := conn.Stream(ctx, sqlparser.BuildParsedQuery(mysql.SelectAllViews, sidecardb.GetIdentifier()).Query,
-		callback, alloc, bufferSize, 0)
+
+	viewChangeQuery := sqlparser.BuildParsedQuery(mysql.DetectViewChange, sidecardb.GetIdentifier()).Query
+	err = conn.Stream(ctx, viewChangeQuery, callback, alloc, bufferSize, 0)
 	if err != nil {
-		return nil, err
+		return
 	}
 
 	// If no change detected, then return
-	if len(views) == 0 && len(hs.views) == 0 {
-		return nil, nil
+	if len(views) == 0 {
+		return
 	}
 
-	for viewName, lastUpdTime := range views {
-		t, exists := hs.views[viewName]
-		if !exists { // new view added
-			changedViews = append(changedViews, viewName)
-			continue
+	/* Retrieve changed views definition */
+	viewsDefStmt := map[string]*viewDefAndStmt{}
+	callback = func(qr *sqltypes.Result) error {
+		for _, row := range qr.Rows {
+			viewsDefStmt[row[0].ToString()] = &viewDefAndStmt{def: row[1].ToString()}
 		}
-		if t != lastUpdTime { // view updated
-			changedViews = append(changedViews, viewName)
+		return nil
+	}
+
+	var viewsBV *querypb.BindVariable
+	viewsBV, err = sqltypes.BuildBindVariable(views)
+	if err != nil {
+		return
+	}
+	bv := map[string]*querypb.BindVariable{"tableNames": viewsBV}
+
+	err = hs.getViewDefinition(ctx, conn, bv, callback, alloc, bufferSize)
+	if err != nil {
+		return
+	}
+
+	/* Retrieve create statement for views */
+	viewsDefStmt, err = hs.getCreateViewStatement(ctx, conn, viewsDefStmt)
+	if err != nil {
+		return
+	}
+
+	/* update the views copy table */
+	err = hs.updateViewsTable(ctx, conn, bv, viewsDefStmt)
+	return
+}
+
+func (hs *healthStreamer) getViewDefinition(ctx context.Context, conn *connpool.DBConn, bv map[string]*querypb.BindVariable, callback func(qr *sqltypes.Result) error, alloc func() *sqltypes.Result, bufferSize int) error {
+	var viewsDefQuery string
+	viewsDefQuery = sqlparser.BuildParsedQuery(mysql.FetchViewDefinition).Query
+
+	stmt, err := sqlparser.Parse(viewsDefQuery)
+	if err != nil {
+		return err
+	}
+	viewsDefQuery, err = planbuilder.GenerateFullQuery(stmt).GenerateQuery(bv, nil)
+	if err != nil {
+		return err
+	}
+	return conn.Stream(ctx, viewsDefQuery, callback, alloc, bufferSize, 0)
+}
+
+func (hs *healthStreamer) getCreateViewStatement(ctx context.Context, conn *connpool.DBConn, viewsDefStmt map[string]*viewDefAndStmt) (map[string]*viewDefAndStmt, error) {
+	for k, v := range viewsDefStmt {
+		res, err := conn.Exec(ctx, sqlparser.BuildParsedQuery(mysql.FetchCreateStatement, k).Query, 1, false)
+		if err != nil {
+			return nil, err
+		}
+		for _, row := range res.Rows {
+			v.stmt = row[1].ToString()
 		}
-		delete(hs.views, viewName)
 	}
+	return viewsDefStmt, nil
+}
 
-	// views deleted
-	for viewName := range hs.views {
-		changedViews = append(changedViews, viewName)
+func (hs *healthStreamer) updateViewsTable(ctx context.Context, conn *connpool.DBConn, bv map[string]*querypb.BindVariable, viewsDefStmt map[string]*viewDefAndStmt) error {
+	stmt, err := sqlparser.Parse(
+		sqlparser.BuildParsedQuery(mysql.DeleteFromViewsTable, sidecardb.GetIdentifier()).Query)
+	if err != nil {
+		return err
+	}
+	clearViewQuery, err := planbuilder.GenerateFullQuery(stmt).GenerateQuery(bv, nil)
+	if err != nil {
+		return err
 	}
 
-	// update hs.views with latest view info
-	hs.views = views
+	stmt, err = sqlparser.Parse(
+		sqlparser.BuildParsedQuery(mysql.InsertIntoViewsTable, sidecardb.GetIdentifier()).Query)
+	if err != nil {
+		return err
+	}
+	insertViewsParsedQuery := planbuilder.GenerateFullQuery(stmt)
 
-	return changedViews, nil
+	// Reload the views in a transaction.
+	_, err = conn.Exec(ctx, "begin", 1, false)
+	if err != nil {
+		return err
+	}
+	defer conn.Exec(ctx, "rollback", 1, false)
+
+	_, err = conn.Exec(ctx, clearViewQuery, 1, false)
+	if err != nil {
+		return err
+	}
+
+	for k, v := range viewsDefStmt {
+		bv["table_name"] = sqltypes.StringBindVariable(k)
+		bv["create_statement"] = sqltypes.StringBindVariable(v.stmt)
+		bv["view_definition"] = sqltypes.StringBindVariable(v.def)
+		insertViewQuery, err := insertViewsParsedQuery.GenerateQuery(bv, nil)
+		if err != nil {
+			return err
+		}
+		_, err = conn.Exec(ctx, insertViewQuery, 1, false)
+		if err != nil {
+			return err
+		}
+	}
+
+	_, err = conn.Exec(ctx, "commit", 1, false)
+	return err
 }
diff --git a/go/vt/vttablet/tabletserver/planbuilder/builder.go b/go/vt/vttablet/tabletserver/planbuilder/builder.go
index 8cb281b1e9e..a08b747e724 100644
--- a/go/vt/vttablet/tabletserver/planbuilder/builder.go
+++ b/go/vt/vttablet/tabletserver/planbuilder/builder.go
@@ -19,8 +19,6 @@ package planbuilder
 import (
 	"strings"
 
-	"vitess.io/vitess/go/mysql"
-	"vitess.io/vitess/go/vt/sidecardb"
 	"vitess.io/vitess/go/vt/sqlparser"
 	"vitess.io/vitess/go/vt/vterrors"
 	"vitess.io/vitess/go/vt/vtgate/evalengine"
@@ -207,13 +205,7 @@ func lookupSingleTable(tableExpr sqlparser.TableExpr, tables map[string]*schema.
 	return tables[tableName.String()]
 }
 
-func analyzeDDL(stmt sqlparser.DDLStatement, viewsEnabled bool) (*Plan, error) {
-	switch stmt.(type) {
-	case *sqlparser.AlterView, *sqlparser.DropView, *sqlparser.CreateView:
-		if viewsEnabled {
-			return analyzeViewsDDL(stmt)
-		}
-	}
+func analyzeDDL(stmt sqlparser.DDLStatement) (*Plan, error) {
 	// DDLs and some other statements below don't get fully parsed.
 	// We have to use the original query at the time of execution.
 	// We are in the process of changing this
@@ -224,31 +216,3 @@ func analyzeDDL(stmt sqlparser.DDLStatement, viewsEnabled bool) (*Plan, error) {
 	}
 	return &Plan{PlanID: PlanDDL, FullQuery: fullQuery, FullStmt: stmt, NeedsReservedConn: stmt.IsTemporary()}, nil
 }
-
-func analyzeViewsDDL(stmt sqlparser.DDLStatement) (*Plan, error) {
-	switch viewDDL := stmt.(type) {
-	case *sqlparser.CreateView:
-		query := sqlparser.BuildParsedQuery(mysql.InsertIntoViewsTable, sidecardb.GetIdentifier()).Query
-		if viewDDL.IsReplace {
-			query = sqlparser.BuildParsedQuery(mysql.ReplaceIntoViewsTable, sidecardb.GetIdentifier()).Query
-		}
-		insert, err := sqlparser.Parse(query)
-		if err != nil {
-			return nil, err
-		}
-		return &Plan{PlanID: PlanViewDDL, FullQuery: GenerateFullQuery(insert), FullStmt: viewDDL}, nil
-	case *sqlparser.AlterView:
-		update, err := sqlparser.Parse(sqlparser.BuildParsedQuery(mysql.UpdateViewsTable, sidecardb.GetIdentifier()).Query)
-		if err != nil {
-			return nil, err
-		}
-		return &Plan{PlanID: PlanViewDDL, FullQuery: GenerateFullQuery(update), FullStmt: viewDDL}, nil
-	case *sqlparser.DropView:
-		del, err := sqlparser.Parse(sqlparser.BuildParsedQuery(mysql.DeleteFromViewsTable, sidecardb.GetIdentifier()).Query)
-		if err != nil {
-			return nil, err
-		}
-		return &Plan{PlanID: PlanViewDDL, FullQuery: GenerateFullQuery(del), FullStmt: viewDDL}, nil
-	}
-	return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] unknown view DDL type: %T", stmt)
-}
diff --git a/go/vt/vttablet/tabletserver/planbuilder/plan.go b/go/vt/vttablet/tabletserver/planbuilder/plan.go
index 842f6349665..aca212d10cd 100644
--- a/go/vt/vttablet/tabletserver/planbuilder/plan.go
+++ b/go/vt/vttablet/tabletserver/planbuilder/plan.go
@@ -80,7 +80,6 @@ const (
 	PlanShowMigrationLogs
 	PlanShowThrottledApps
 	PlanShowThrottlerStatus
-	PlanViewDDL
 	NumPlans
 )
 
@@ -117,7 +116,6 @@ var planName = []string{
 	"ShowMigrationLogs",
 	"ShowThrottledApps",
 	"ShowThrottlerStatus",
-	"ViewDDL",
 }
 
 func (pt PlanType) String() string {
@@ -222,7 +220,7 @@ func Build(statement sqlparser.Statement, tables map[string]*schema.Table, dbNam
 	case *sqlparser.Set:
 		plan, err = analyzeSet(stmt), nil
 	case sqlparser.DDLStatement:
-		plan, err = analyzeDDL(stmt, viewsEnabled)
+		plan, err = analyzeDDL(stmt)
 	case *sqlparser.AlterMigration:
 		plan, err = &Plan{PlanID: PlanAlterMigration, FullStmt: stmt}, nil
 	case *sqlparser.RevertMigration:
diff --git a/go/vt/vttablet/tabletserver/query_executor.go b/go/vt/vttablet/tabletserver/query_executor.go
index 5429a7d5c9e..769254d333e 100644
--- a/go/vt/vttablet/tabletserver/query_executor.go
+++ b/go/vt/vttablet/tabletserver/query_executor.go
@@ -187,13 +187,6 @@ func (qre *QueryExecutor) Execute() (reply *sqltypes.Result, err error) {
 		return qre.execOther()
 	case p.PlanInsert, p.PlanUpdate, p.PlanDelete, p.PlanInsertMessage, p.PlanDDL, p.PlanLoad:
 		return qre.execAutocommit(qre.txConnExec)
-	case p.PlanViewDDL:
-		switch qre.plan.FullStmt.(type) {
-		case *sqlparser.DropView:
-			return qre.execAutocommit(qre.execDropViewDDL)
-		default:
-			return qre.execAsTransaction(qre.execViewDDL)
-		}
 	case p.PlanUpdateLimit, p.PlanDeleteLimit:
 		return qre.execAsTransaction(qre.txConnExec)
 	case p.PlanCallProc:
@@ -303,113 +296,6 @@ func (qre *QueryExecutor) txConnExec(conn *StatefulConnection) (*sqltypes.Result
 	return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] %s unexpected plan type", qre.plan.PlanID.String())
 }
 
-func (qre *QueryExecutor) execViewDDL(conn *StatefulConnection) (*sqltypes.Result, error) {
-	var err error
-	switch stmt := qre.plan.FullStmt.(type) {
-	case *sqlparser.CreateView:
-		_, err = qre.execCreateViewDDL(conn, stmt)
-	case *sqlparser.AlterView:
-		_, err = qre.execAlterViewDDL(conn, stmt)
-	default:
-		err = vterrors.Errorf(vtrpcpb.Code_INTERNAL, "BUG: unexpected view DDL type: %T", qre.plan.FullStmt)
-	}
-	if err != nil {
-		return nil, err
-	}
-	// We need to use a different connection for executing the DDL on MySQL
-	// because the previous DMLs are running in a transaction and we don't want to autocommit
-	// those changes.
-	ddlConn, err := qre.getConn()
-	if err != nil {
-		return nil, err
-	}
-	defer ddlConn.Recycle()
-	// If MySQL fails, then we will Rollback the changes.
-	return ddlConn.Exec(qre.ctx, sqlparser.String(qre.plan.FullStmt), 1000, true)
-}
-
-func (qre *QueryExecutor) execCreateViewDDL(conn *StatefulConnection, stmt *sqlparser.CreateView) (*sqltypes.Result, error) {
-	bindVars := generateBindVarsForViewDDLInsert(stmt)
-	sql, _, err := qre.generateFinalSQL(qre.plan.FullQuery, bindVars)
-	if err != nil {
-		return nil, err
-	}
-	qr, err := execWithDDLView(qre.ctx, conn, sql)
-	if err != nil {
-		sqlErr, isSQLErr := mysql.NewSQLErrorFromError(err).(*mysql.SQLError)
-		// If it is a MySQL error and its code is of duplicate entry,
-		// then we would return duplicate create view error.
-		if isSQLErr && sqlErr.Number() == mysql.ERDupEntry {
-			return nil, vterrors.Errorf(vtrpcpb.Code_ALREADY_EXISTS, "Table '%s' already exists", stmt.ViewName.Name.String())
-		}
-		return nil, err
-	}
-	return qr, nil
-}
-
-func (qre *QueryExecutor) execAlterViewDDL(conn *StatefulConnection, stmt *sqlparser.AlterView) (*sqltypes.Result, error) {
-	createViewDDL := &sqlparser.CreateView{
-		ViewName:    stmt.ViewName,
-		Algorithm:   stmt.Algorithm,
-		Definer:     stmt.Definer,
-		Security:    stmt.Security,
-		Columns:     stmt.Columns,
-		Select:      stmt.Select,
-		CheckOption: stmt.CheckOption,
-		Comments:    stmt.Comments,
-	}
-	bindVars := generateBindVarsForViewDDLInsert(createViewDDL)
-	sql, _, err := qre.generateFinalSQL(qre.plan.FullQuery, bindVars)
-	if err != nil {
-		return nil, err
-	}
-	qr, err := execWithDDLView(qre.ctx, conn, sql)
-	if err != nil {
-		return nil, err
-	}
-	if qr.RowsAffected == 0 {
-		return nil, vterrors.Errorf(vtrpcpb.Code_NOT_FOUND, "Table '%s' does not exist", stmt.ViewName.Name.String())
-	}
-	return qr, nil
-}
-
-func (qre *QueryExecutor) execDropViewDDL(conn *StatefulConnection) (*sqltypes.Result, error) {
-	viewsMap := make(map[string]int)
-	stmt := qre.plan.FullStmt.(*sqlparser.DropView)
-	var viewNames []string
-	for pos, view := range stmt.FromTables {
-		viewName := view.Name.String()
-		if _, exists := viewsMap[viewName]; exists {
-			return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "Not unique view: '%s'", viewName)
-		}
-		viewNames = append(viewNames, viewName)
-		viewsMap[viewName] = pos
-	}
-	viewNamesBV, err := sqltypes.BuildBindVariable(viewNames)
-	if err != nil {
-		return nil, err
-	}
-	bindVars := map[string]*querypb.BindVariable{
-		"table_name": viewNamesBV,
-	}
-
-	sql, _, err := qre.generateFinalSQL(qre.plan.FullQuery, bindVars)
-	if err != nil {
-		return nil, err
-	}
-	_, err = execWithDDLView(qre.ctx, conn, sql)
-	if err != nil {
-		return nil, err
-	}
-
-	// Drop the view on MySQL too.
-	return conn.Exec(qre.ctx, sqlparser.String(qre.plan.FullStmt), 1000, true)
-}
-
-func execWithDDLView(ctx context.Context, conn *StatefulConnection, sql string) (*sqltypes.Result, error) {
-	return conn.Exec(ctx, sql, 10000, true)
-}
-
 // Stream performs a streaming query execution.
 func (qre *QueryExecutor) Stream(callback StreamCallback) error {
 	qre.logStats.PlanType = qre.plan.PlanID.String()