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 ¶ms.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 }