github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/drop_view.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package sql
    12  
    13  import (
    14  	"context"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/server/telemetry"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/catalog/resolver"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/privilege"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry"
    22  	"github.com/cockroachdb/cockroach/pkg/util/log"
    23  	"github.com/cockroachdb/errors"
    24  )
    25  
    26  type dropViewNode struct {
    27  	n  *tree.DropView
    28  	td []toDelete
    29  }
    30  
    31  // DropView drops a view.
    32  // Privileges: DROP on view.
    33  //   Notes: postgres allows only the view owner to DROP a view.
    34  //          mysql requires the DROP privilege on the view.
    35  func (p *planner) DropView(ctx context.Context, n *tree.DropView) (planNode, error) {
    36  	td := make([]toDelete, 0, len(n.Names))
    37  	for i := range n.Names {
    38  		tn := &n.Names[i]
    39  		droppedDesc, err := p.prepareDrop(ctx, tn, !n.IfExists, resolver.ResolveRequireViewDesc)
    40  		if err != nil {
    41  			return nil, err
    42  		}
    43  		if droppedDesc == nil {
    44  			// IfExists specified and the view did not exist.
    45  			continue
    46  		}
    47  
    48  		td = append(td, toDelete{tn, droppedDesc})
    49  	}
    50  
    51  	// Ensure this view isn't depended on by any other views, or that if it is
    52  	// then `cascade` was specified or it was also explicitly specified in the
    53  	// DROP VIEW command.
    54  	for _, toDel := range td {
    55  		droppedDesc := toDel.desc
    56  		for _, ref := range droppedDesc.DependedOnBy {
    57  			// Don't verify that we can remove a dependent view if that dependent
    58  			// view was explicitly specified in the DROP VIEW command.
    59  			if descInSlice(ref.ID, td) {
    60  				continue
    61  			}
    62  			if err := p.canRemoveDependentView(ctx, droppedDesc, ref, n.DropBehavior); err != nil {
    63  				return nil, err
    64  			}
    65  		}
    66  	}
    67  
    68  	if len(td) == 0 {
    69  		return newZeroNode(nil /* columns */), nil
    70  	}
    71  	return &dropViewNode{n: n, td: td}, nil
    72  }
    73  
    74  // ReadingOwnWrites implements the planNodeReadingOwnWrites interface.
    75  // This is because DROP VIEW performs multiple KV operations on descriptors
    76  // and expects to see its own writes.
    77  func (n *dropViewNode) ReadingOwnWrites() {}
    78  
    79  func (n *dropViewNode) startExec(params runParams) error {
    80  	telemetry.Inc(sqltelemetry.SchemaChangeDropCounter("view"))
    81  
    82  	ctx := params.ctx
    83  	for _, toDel := range n.td {
    84  		droppedDesc := toDel.desc
    85  		if droppedDesc == nil {
    86  			continue
    87  		}
    88  
    89  		cascadeDroppedViews, err := params.p.dropViewImpl(
    90  			ctx, droppedDesc, true /* queueJob */, tree.AsStringWithFQNames(n.n, params.Ann()), n.n.DropBehavior,
    91  		)
    92  		if err != nil {
    93  			return err
    94  		}
    95  		// Log a Drop View event for this table. This is an auditable log event
    96  		// and is recorded in the same transaction as the table descriptor
    97  		// update.
    98  		if err := MakeEventLogger(params.extendedEvalCtx.ExecCfg).InsertEventRecord(
    99  			ctx,
   100  			params.p.txn,
   101  			EventLogDropView,
   102  			int32(droppedDesc.ID),
   103  			int32(params.extendedEvalCtx.NodeID.SQLInstanceID()),
   104  			struct {
   105  				ViewName            string
   106  				Statement           string
   107  				User                string
   108  				CascadeDroppedViews []string
   109  			}{toDel.tn.FQString(), n.n.String(), params.SessionData().User, cascadeDroppedViews},
   110  		); err != nil {
   111  			return err
   112  		}
   113  	}
   114  	return nil
   115  }
   116  
   117  func (*dropViewNode) Next(runParams) (bool, error) { return false, nil }
   118  func (*dropViewNode) Values() tree.Datums          { return tree.Datums{} }
   119  func (*dropViewNode) Close(context.Context)        {}
   120  
   121  func descInSlice(descID sqlbase.ID, td []toDelete) bool {
   122  	for _, toDel := range td {
   123  		if descID == toDel.desc.ID {
   124  			return true
   125  		}
   126  	}
   127  	return false
   128  }
   129  
   130  func (p *planner) canRemoveDependentView(
   131  	ctx context.Context,
   132  	from *sqlbase.MutableTableDescriptor,
   133  	ref sqlbase.TableDescriptor_Reference,
   134  	behavior tree.DropBehavior,
   135  ) error {
   136  	return p.canRemoveDependentViewGeneric(ctx, from.TypeName(), from.Name, from.ParentID, ref, behavior)
   137  }
   138  
   139  func (p *planner) canRemoveDependentViewGeneric(
   140  	ctx context.Context,
   141  	typeName string,
   142  	objName string,
   143  	parentID sqlbase.ID,
   144  	ref sqlbase.TableDescriptor_Reference,
   145  	behavior tree.DropBehavior,
   146  ) error {
   147  	viewDesc, err := p.getViewDescForCascade(ctx, typeName, objName, parentID, ref.ID, behavior)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	if err := p.CheckPrivilege(ctx, viewDesc, privilege.DROP); err != nil {
   152  		return err
   153  	}
   154  	// If this view is depended on by other views, we have to check them as well.
   155  	for _, ref := range viewDesc.DependedOnBy {
   156  		if err := p.canRemoveDependentView(ctx, viewDesc, ref, behavior); err != nil {
   157  			return err
   158  		}
   159  	}
   160  	return nil
   161  }
   162  
   163  // Drops the view and any additional views that depend on it.
   164  // Returns the names of any additional views that were also dropped
   165  // due to `cascade` behavior.
   166  func (p *planner) removeDependentView(
   167  	ctx context.Context, tableDesc, viewDesc *sqlbase.MutableTableDescriptor, jobDesc string,
   168  ) ([]string, error) {
   169  	// In the table whose index is being removed, filter out all back-references
   170  	// that refer to the view that's being removed.
   171  	tableDesc.DependedOnBy = removeMatchingReferences(tableDesc.DependedOnBy, viewDesc.ID)
   172  	// Then proceed to actually drop the view and log an event for it.
   173  	return p.dropViewImpl(ctx, viewDesc, true /* queueJob */, jobDesc, tree.DropCascade)
   174  }
   175  
   176  // dropViewImpl does the work of dropping a view (and views that depend on it
   177  // if `cascade is specified`). Returns the names of any additional views that
   178  // were also dropped due to `cascade` behavior.
   179  func (p *planner) dropViewImpl(
   180  	ctx context.Context,
   181  	viewDesc *sqlbase.MutableTableDescriptor,
   182  	queueJob bool,
   183  	jobDesc string,
   184  	behavior tree.DropBehavior,
   185  ) ([]string, error) {
   186  	var cascadeDroppedViews []string
   187  
   188  	// Remove back-references from the tables/views this view depends on.
   189  	for _, depID := range viewDesc.DependsOn {
   190  		dependencyDesc, err := p.Tables().GetMutableTableVersionByID(ctx, depID, p.txn)
   191  		if err != nil {
   192  			return cascadeDroppedViews,
   193  				errors.Errorf("error resolving dependency relation ID %d: %v", depID, err)
   194  		}
   195  		// The dependency is also being deleted, so we don't have to remove the
   196  		// references.
   197  		if dependencyDesc.Dropped() {
   198  			continue
   199  		}
   200  		dependencyDesc.DependedOnBy = removeMatchingReferences(dependencyDesc.DependedOnBy, viewDesc.ID)
   201  		// TODO (lucy): Have more consistent/informative names for dependent jobs.
   202  		if err := p.writeSchemaChange(
   203  			ctx, dependencyDesc, sqlbase.InvalidMutationID, "removing references for view",
   204  		); err != nil {
   205  			return cascadeDroppedViews, err
   206  		}
   207  	}
   208  	viewDesc.DependsOn = nil
   209  
   210  	if behavior == tree.DropCascade {
   211  		for _, ref := range viewDesc.DependedOnBy {
   212  			dependentDesc, err := p.getViewDescForCascade(
   213  				ctx, viewDesc.TypeName(), viewDesc.Name, viewDesc.ParentID, ref.ID, behavior,
   214  			)
   215  			if err != nil {
   216  				return cascadeDroppedViews, err
   217  			}
   218  			// TODO (lucy): Have more consistent/informative names for dependent jobs.
   219  			cascadedViews, err := p.dropViewImpl(ctx, dependentDesc, queueJob, "dropping dependent view", behavior)
   220  			if err != nil {
   221  				return cascadeDroppedViews, err
   222  			}
   223  			cascadeDroppedViews = append(cascadeDroppedViews, cascadedViews...)
   224  			cascadeDroppedViews = append(cascadeDroppedViews, dependentDesc.Name)
   225  		}
   226  	}
   227  
   228  	if err := p.initiateDropTable(ctx, viewDesc, queueJob, jobDesc, true /* drainName */); err != nil {
   229  		return cascadeDroppedViews, err
   230  	}
   231  
   232  	return cascadeDroppedViews, nil
   233  }
   234  
   235  func (p *planner) getViewDescForCascade(
   236  	ctx context.Context,
   237  	typeName string,
   238  	objName string,
   239  	parentID, viewID sqlbase.ID,
   240  	behavior tree.DropBehavior,
   241  ) (*sqlbase.MutableTableDescriptor, error) {
   242  	viewDesc, err := p.Tables().GetMutableTableVersionByID(ctx, viewID, p.txn)
   243  	if err != nil {
   244  		log.Warningf(ctx, "unable to retrieve descriptor for view %d: %v", viewID, err)
   245  		return nil, errors.Wrapf(err, "error resolving dependent view ID %d", viewID)
   246  	}
   247  	if behavior != tree.DropCascade {
   248  		viewName := viewDesc.Name
   249  		if viewDesc.ParentID != parentID {
   250  			var err error
   251  			viewName, err = p.getQualifiedTableName(ctx, viewDesc.TableDesc())
   252  			if err != nil {
   253  				log.Warningf(ctx, "unable to retrieve qualified name of view %d: %v", viewID, err)
   254  				return nil, sqlbase.NewDependentObjectErrorf(
   255  					"cannot drop %s %q because a view depends on it", typeName, objName)
   256  			}
   257  		}
   258  		return nil, errors.WithHintf(
   259  			sqlbase.NewDependentObjectErrorf("cannot drop %s %q because view %q depends on it",
   260  				typeName, objName, viewName),
   261  			"you can drop %s instead.", viewName)
   262  	}
   263  	return viewDesc, nil
   264  }