github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/create_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  	"fmt"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/server/telemetry"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/catalog/catalogkv"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgnotice"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/privilege"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry"
    26  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    27  	"github.com/cockroachdb/cockroach/pkg/util/log"
    28  )
    29  
    30  // createViewNode represents a CREATE VIEW statement.
    31  type createViewNode struct {
    32  	viewName tree.Name
    33  	// viewQuery contains the view definition, with all table names fully
    34  	// qualified.
    35  	viewQuery   string
    36  	ifNotExists bool
    37  	replace     bool
    38  	temporary   bool
    39  	dbDesc      *sqlbase.DatabaseDescriptor
    40  	columns     sqlbase.ResultColumns
    41  
    42  	// planDeps tracks which tables and views the view being created
    43  	// depends on. This is collected during the construction of
    44  	// the view query's logical plan.
    45  	planDeps planDependencies
    46  }
    47  
    48  // ReadingOwnWrites implements the planNodeReadingOwnWrites interface.
    49  // This is because CREATE VIEW performs multiple KV operations on descriptors
    50  // and expects to see its own writes.
    51  func (n *createViewNode) ReadingOwnWrites() {}
    52  
    53  func (n *createViewNode) startExec(params runParams) error {
    54  	telemetry.Inc(sqltelemetry.SchemaChangeCreateCounter("view"))
    55  
    56  	viewName := string(n.viewName)
    57  	isTemporary := n.temporary
    58  	log.VEventf(params.ctx, 2, "dependencies for view %s:\n%s", viewName, n.planDeps.String())
    59  
    60  	// First check the backrefs and see if any of them are temporary.
    61  	// If so, promote this view to temporary.
    62  	backRefMutables := make(map[sqlbase.ID]*sqlbase.MutableTableDescriptor, len(n.planDeps))
    63  	for id, updated := range n.planDeps {
    64  		backRefMutable := params.p.Tables().GetUncommittedTableByID(id).MutableTableDescriptor
    65  		if backRefMutable == nil {
    66  			backRefMutable = sqlbase.NewMutableExistingTableDescriptor(*updated.desc.TableDesc())
    67  		}
    68  		if !isTemporary && backRefMutable.Temporary {
    69  			// This notice is sent from pg, let's imitate.
    70  			params.p.SendClientNotice(
    71  				params.ctx,
    72  				pgnotice.Newf(`view "%s" will be a temporary view`, viewName),
    73  			)
    74  			isTemporary = true
    75  		}
    76  		backRefMutables[id] = backRefMutable
    77  	}
    78  
    79  	var replacingDesc *sqlbase.MutableTableDescriptor
    80  
    81  	tKey, schemaID, err := getTableCreateParams(params, n.dbDesc.ID, isTemporary, viewName)
    82  	if err != nil {
    83  		switch {
    84  		case !sqlbase.IsRelationAlreadyExistsError(err):
    85  			return err
    86  		case n.ifNotExists:
    87  			return nil
    88  		case n.replace:
    89  			// If we are replacing an existing view see if what we are
    90  			// replacing is actually a view.
    91  			id, err := catalogkv.GetDescriptorID(params.ctx, params.p.txn, params.ExecCfg().Codec, tKey)
    92  			if err != nil {
    93  				return err
    94  			}
    95  			desc, err := params.p.Tables().GetMutableTableVersionByID(params.ctx, id, params.p.txn)
    96  			if err != nil {
    97  				return err
    98  			}
    99  			if err := params.p.CheckPrivilege(params.ctx, desc, privilege.DROP); err != nil {
   100  				return err
   101  			}
   102  			if !desc.IsView() {
   103  				return pgerror.Newf(pgcode.WrongObjectType, `%q is not a view`, viewName)
   104  			}
   105  			replacingDesc = desc
   106  		default:
   107  			return err
   108  		}
   109  	}
   110  
   111  	schemaName := tree.PublicSchemaName
   112  	if isTemporary {
   113  		telemetry.Inc(sqltelemetry.CreateTempViewCounter)
   114  		schemaName = tree.Name(params.p.TemporarySchemaName())
   115  	}
   116  
   117  	// Inherit permissions from the database descriptor.
   118  	privs := n.dbDesc.GetPrivileges()
   119  
   120  	var newDesc *sqlbase.MutableTableDescriptor
   121  
   122  	// If replacingDesc != nil, we found an existing view while resolving
   123  	// the name for our view. So instead of creating a new view, replace
   124  	// the existing one.
   125  	if replacingDesc != nil {
   126  		newDesc, err = params.p.replaceViewDesc(params.ctx, n, replacingDesc, backRefMutables)
   127  		if err != nil {
   128  			return err
   129  		}
   130  	} else {
   131  		// If we aren't replacing anything, make a new table descriptor.
   132  		id, err := catalogkv.GenerateUniqueDescID(params.ctx, params.p.ExecCfg().DB, params.p.ExecCfg().Codec)
   133  		if err != nil {
   134  			return err
   135  		}
   136  		desc, err := makeViewTableDesc(
   137  			params.ctx,
   138  			viewName,
   139  			n.viewQuery,
   140  			n.dbDesc.ID,
   141  			schemaID,
   142  			id,
   143  			n.columns,
   144  			params.creationTimeForNewTableDescriptor(),
   145  			privs,
   146  			&params.p.semaCtx,
   147  			params.p.EvalContext(),
   148  			isTemporary,
   149  		)
   150  		if err != nil {
   151  			return err
   152  		}
   153  
   154  		// Collect all the tables/views this view depends on.
   155  		for backrefID := range n.planDeps {
   156  			desc.DependsOn = append(desc.DependsOn, backrefID)
   157  		}
   158  
   159  		// TODO (lucy): I think this needs a NodeFormatter implementation. For now,
   160  		// do some basic string formatting (not accurate in the general case).
   161  		if err = params.p.createDescriptorWithID(
   162  			params.ctx, tKey.Key(params.ExecCfg().Codec), id, &desc, params.EvalContext().Settings,
   163  			fmt.Sprintf("CREATE VIEW %q AS %q", n.viewName, n.viewQuery),
   164  		); err != nil {
   165  			return err
   166  		}
   167  		newDesc = &desc
   168  	}
   169  
   170  	// Persist the back-references in all referenced table descriptors.
   171  	for id, updated := range n.planDeps {
   172  		backRefMutable := backRefMutables[id]
   173  		// In case that we are replacing a view that already depends on
   174  		// this table, remove all existing references so that we don't leave
   175  		// any out of date references. Then, add the new references.
   176  		backRefMutable.DependedOnBy = removeMatchingReferences(
   177  			backRefMutable.DependedOnBy,
   178  			newDesc.ID,
   179  		)
   180  		for _, dep := range updated.deps {
   181  			// The logical plan constructor merely registered the dependencies.
   182  			// It did not populate the "ID" field of TableDescriptor_Reference,
   183  			// because the ID of the newly created view descriptor was not
   184  			// yet known.
   185  			// We need to do it here.
   186  			dep.ID = newDesc.ID
   187  			backRefMutable.DependedOnBy = append(backRefMutable.DependedOnBy, dep)
   188  		}
   189  		// TODO (lucy): Have more consistent/informative names for dependent jobs.
   190  		if err := params.p.writeSchemaChange(
   191  			params.ctx,
   192  			backRefMutable,
   193  			sqlbase.InvalidMutationID,
   194  			fmt.Sprintf("updating view reference %q", n.viewName),
   195  		); err != nil {
   196  			return err
   197  		}
   198  	}
   199  
   200  	if err := newDesc.Validate(params.ctx, params.p.txn, params.ExecCfg().Codec); err != nil {
   201  		return err
   202  	}
   203  
   204  	// Log Create View event. This is an auditable log event and is
   205  	// recorded in the same transaction as the table descriptor update.
   206  	tn := tree.MakeTableNameWithSchema(tree.Name(n.dbDesc.Name), schemaName, n.viewName)
   207  	return MakeEventLogger(params.extendedEvalCtx.ExecCfg).InsertEventRecord(
   208  		params.ctx,
   209  		params.p.txn,
   210  		EventLogCreateView,
   211  		int32(newDesc.ID),
   212  		int32(params.extendedEvalCtx.NodeID.SQLInstanceID()),
   213  		struct {
   214  			ViewName  string
   215  			ViewQuery string
   216  			User      string
   217  		}{
   218  			ViewName:  tn.FQString(),
   219  			ViewQuery: n.viewQuery,
   220  			User:      params.SessionData().User,
   221  		},
   222  	)
   223  }
   224  
   225  func (*createViewNode) Next(runParams) (bool, error) { return false, nil }
   226  func (*createViewNode) Values() tree.Datums          { return tree.Datums{} }
   227  func (n *createViewNode) Close(ctx context.Context)  {}
   228  
   229  // makeViewTableDesc returns the table descriptor for a new view.
   230  //
   231  // It creates the descriptor directly in the PUBLIC state rather than
   232  // the ADDING state because back-references are added to the view's
   233  // dependencies in the same transaction that the view is created and it
   234  // doesn't matter if reads/writes use a cached descriptor that doesn't
   235  // include the back-references.
   236  func makeViewTableDesc(
   237  	ctx context.Context,
   238  	viewName string,
   239  	viewQuery string,
   240  	parentID sqlbase.ID,
   241  	schemaID sqlbase.ID,
   242  	id sqlbase.ID,
   243  	resultColumns []sqlbase.ResultColumn,
   244  	creationTime hlc.Timestamp,
   245  	privileges *sqlbase.PrivilegeDescriptor,
   246  	semaCtx *tree.SemaContext,
   247  	evalCtx *tree.EvalContext,
   248  	temporary bool,
   249  ) (sqlbase.MutableTableDescriptor, error) {
   250  	desc := InitTableDescriptor(
   251  		id,
   252  		parentID,
   253  		schemaID,
   254  		viewName,
   255  		creationTime,
   256  		privileges,
   257  		temporary,
   258  	)
   259  	desc.ViewQuery = viewQuery
   260  	if err := addResultColumns(ctx, semaCtx, evalCtx, &desc, resultColumns); err != nil {
   261  		return sqlbase.MutableTableDescriptor{}, err
   262  	}
   263  	return desc, nil
   264  }
   265  
   266  // replaceViewDesc modifies and returns the input view descriptor changed
   267  // to hold the new view represented by n. Note that back references from
   268  // tables that the new view depends on still need to be added. This function
   269  // will additionally drop backreferences from tables the old view depended
   270  // on that the new view no longer depends on.
   271  func (p *planner) replaceViewDesc(
   272  	ctx context.Context,
   273  	n *createViewNode,
   274  	toReplace *sqlbase.MutableTableDescriptor,
   275  	backRefMutables map[sqlbase.ID]*sqlbase.MutableTableDescriptor,
   276  ) (*sqlbase.MutableTableDescriptor, error) {
   277  	// Set the query to the new query.
   278  	toReplace.ViewQuery = n.viewQuery
   279  	// Reset the columns to add the new result columns onto.
   280  	toReplace.Columns = make([]sqlbase.ColumnDescriptor, 0, len(n.columns))
   281  	toReplace.NextColumnID = 0
   282  	if err := addResultColumns(ctx, &p.semaCtx, p.EvalContext(), toReplace, n.columns); err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	// Compare toReplace against its ClusterVersion to verify if
   287  	// its new set of columns is valid for a replacement view.
   288  	if err := verifyReplacingViewColumns(
   289  		toReplace.ClusterVersion.Columns,
   290  		toReplace.Columns,
   291  	); err != nil {
   292  		return nil, err
   293  	}
   294  
   295  	// Remove the back reference from all tables that the view depended on.
   296  	for _, id := range toReplace.DependsOn {
   297  		desc, ok := backRefMutables[id]
   298  		if !ok {
   299  			var err error
   300  			desc, err = p.Tables().GetMutableTableVersionByID(ctx, id, p.txn)
   301  			if err != nil {
   302  				return nil, err
   303  			}
   304  			backRefMutables[id] = desc
   305  		}
   306  
   307  		// If n.planDeps doesn't contain id, then the new view definition doesn't
   308  		// reference this table anymore, so we can remove all existing references.
   309  		if _, ok := n.planDeps[id]; !ok {
   310  			desc.DependedOnBy = removeMatchingReferences(desc.DependedOnBy, toReplace.ID)
   311  			if err := p.writeSchemaChange(
   312  				ctx,
   313  				desc,
   314  				sqlbase.InvalidMutationID,
   315  				fmt.Sprintf("updating view reference %q", n.viewName),
   316  			); err != nil {
   317  				return nil, err
   318  			}
   319  		}
   320  	}
   321  
   322  	// Since the view query has been replaced, the dependencies that this
   323  	// table descriptor had are gone.
   324  	toReplace.DependsOn = make([]sqlbase.ID, 0, len(n.planDeps))
   325  	for backrefID := range n.planDeps {
   326  		toReplace.DependsOn = append(toReplace.DependsOn, backrefID)
   327  	}
   328  
   329  	// Since we are replacing an existing view here, we need to write the new
   330  	// descriptor into place.
   331  	if err := p.writeSchemaChange(ctx, toReplace, sqlbase.InvalidMutationID,
   332  		fmt.Sprintf("CREATE OR REPLACE VIEW %q AS %q", n.viewName, n.viewQuery),
   333  	); err != nil {
   334  		return nil, err
   335  	}
   336  	return toReplace, nil
   337  }
   338  
   339  // addResultColumns adds the resultColumns as actual column
   340  // descriptors onto desc.
   341  func addResultColumns(
   342  	ctx context.Context,
   343  	semaCtx *tree.SemaContext,
   344  	evalCtx *tree.EvalContext,
   345  	desc *sqlbase.MutableTableDescriptor,
   346  	resultColumns sqlbase.ResultColumns,
   347  ) error {
   348  	for _, colRes := range resultColumns {
   349  		columnTableDef := tree.ColumnTableDef{Name: tree.Name(colRes.Name), Type: colRes.Typ}
   350  		// The new types in the CREATE VIEW column specs never use
   351  		// SERIAL so we need not process SERIAL types here.
   352  		col, _, _, err := sqlbase.MakeColumnDefDescs(ctx, &columnTableDef, semaCtx, evalCtx)
   353  		if err != nil {
   354  			return err
   355  		}
   356  		desc.AddColumn(col)
   357  	}
   358  	if err := desc.AllocateIDs(); err != nil {
   359  		return err
   360  	}
   361  	return nil
   362  }
   363  
   364  // verifyReplacingViewColumns ensures that the new set of view columns must
   365  // have at least the same prefix of columns as the old view. We attempt to
   366  // match the postgres error message in each of the error cases below.
   367  func verifyReplacingViewColumns(oldColumns, newColumns []sqlbase.ColumnDescriptor) error {
   368  	if len(newColumns) < len(oldColumns) {
   369  		return pgerror.Newf(pgcode.InvalidTableDefinition, "cannot drop columns from view")
   370  	}
   371  	for i := range oldColumns {
   372  		oldCol, newCol := &oldColumns[i], &newColumns[i]
   373  		if oldCol.Name != newCol.Name {
   374  			return pgerror.Newf(
   375  				pgcode.InvalidTableDefinition,
   376  				`cannot change name of view column %q to %q`,
   377  				oldCol.Name,
   378  				newCol.Name,
   379  			)
   380  		}
   381  		if !newCol.Type.Identical(oldCol.Type) {
   382  			return pgerror.Newf(
   383  				pgcode.InvalidTableDefinition,
   384  				`cannot change type of view column %q from %s to %s`,
   385  				oldCol.Name,
   386  				oldCol.Type.String(),
   387  				newCol.Type.String(),
   388  			)
   389  		}
   390  		if newCol.Hidden != oldCol.Hidden {
   391  			return pgerror.Newf(
   392  				pgcode.InvalidTableDefinition,
   393  				`cannot change visibility of view column %q`,
   394  				oldCol.Name,
   395  			)
   396  		}
   397  		if newCol.Nullable != oldCol.Nullable {
   398  			return pgerror.Newf(
   399  				pgcode.InvalidTableDefinition,
   400  				`cannot change nullability of view column %q`,
   401  				oldCol.Name,
   402  			)
   403  		}
   404  	}
   405  	return nil
   406  }
   407  
   408  func overrideColumnNames(cols sqlbase.ResultColumns, newNames tree.NameList) sqlbase.ResultColumns {
   409  	res := append(sqlbase.ResultColumns(nil), cols...)
   410  	for i := range res {
   411  		res[i].Name = string(newNames[i])
   412  	}
   413  	return res
   414  }