github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/create_index.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/clusterversion" 17 "github.com/cockroachdb/cockroach/pkg/geo/geoindex" 18 "github.com/cockroachdb/cockroach/pkg/server/telemetry" 19 "github.com/cockroachdb/cockroach/pkg/sql/catalog/resolver" 20 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 21 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 22 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgnotice" 23 "github.com/cockroachdb/cockroach/pkg/sql/privilege" 24 "github.com/cockroachdb/cockroach/pkg/sql/schemaexpr" 25 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 26 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 27 "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" 28 "github.com/cockroachdb/cockroach/pkg/sql/types" 29 "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" 30 "github.com/cockroachdb/errors" 31 ) 32 33 type createIndexNode struct { 34 n *tree.CreateIndex 35 tableDesc *sqlbase.MutableTableDescriptor 36 } 37 38 // CreateIndex creates an index. 39 // Privileges: CREATE on table. 40 // notes: postgres requires CREATE on the table. 41 // mysql requires INDEX on the table. 42 func (p *planner) CreateIndex(ctx context.Context, n *tree.CreateIndex) (planNode, error) { 43 tableDesc, err := p.ResolveMutableTableDescriptor( 44 ctx, &n.Table, true /*required*/, resolver.ResolveRequireTableDesc, 45 ) 46 if err != nil { 47 return nil, err 48 } 49 50 if err := p.CheckPrivilege(ctx, tableDesc, privilege.CREATE); err != nil { 51 return nil, err 52 } 53 54 return &createIndexNode{tableDesc: tableDesc, n: n}, nil 55 } 56 57 // setupFamilyAndConstraintForShard adds a newly-created shard column into its appropriate 58 // family (see comment above GetColumnFamilyForShard) and adds a check constraint ensuring 59 // that the shard column's value is within [0..ShardBuckets-1]. This method is called when 60 // a `CREATE INDEX` statement is issued for the creation of a sharded index that *does 61 // not* re-use a pre-existing shard column. 62 func (p *planner) setupFamilyAndConstraintForShard( 63 ctx context.Context, 64 tableDesc *MutableTableDescriptor, 65 shardCol *sqlbase.ColumnDescriptor, 66 idxColumns []string, 67 buckets int32, 68 ) error { 69 family := sqlbase.GetColumnFamilyForShard(tableDesc, idxColumns) 70 if family == "" { 71 return errors.AssertionFailedf("could not find column family for the first column in the index column set") 72 } 73 // Assign shard column to the family of the first column in its index set, and do it 74 // before `AllocateIDs()` assigns it to the primary column family. 75 if err := tableDesc.AddColumnToFamilyMaybeCreate(shardCol.Name, family, false, false); err != nil { 76 return err 77 } 78 // Assign an ID to the newly-added shard column, which is needed for the creation 79 // of a valid check constraint. 80 if err := tableDesc.AllocateIDs(); err != nil { 81 return err 82 } 83 84 ckDef, err := makeShardCheckConstraintDef(tableDesc, int(buckets), shardCol) 85 if err != nil { 86 return err 87 } 88 info, err := tableDesc.GetConstraintInfo(ctx, nil, p.ExecCfg().Codec) 89 if err != nil { 90 return err 91 } 92 93 inuseNames := make(map[string]struct{}, len(info)) 94 for k := range info { 95 inuseNames[k] = struct{}{} 96 } 97 98 ckBuilder := schemaexpr.NewCheckConstraintBuilder(ctx, p.tableName, tableDesc, &p.semaCtx) 99 ckName, err := ckBuilder.DefaultName(ckDef.Expr) 100 if err != nil { 101 return err 102 } 103 104 // Avoid creating duplicate check constraints. 105 if _, ok := inuseNames[ckName]; !ok { 106 ck, err := ckBuilder.Build(ckDef) 107 if err != nil { 108 return err 109 } 110 ck.Validity = sqlbase.ConstraintValidity_Validating 111 tableDesc.AddCheckMutation(ck, sqlbase.DescriptorMutation_ADD) 112 } 113 return nil 114 } 115 116 // MakeIndexDescriptor creates an index descriptor from a CreateIndex node and optionally 117 // adds a hidden computed shard column (along with its check constraint) in case the index 118 // is hash sharded. Note that `tableDesc` will be modified when this method is called for 119 // a hash sharded index. 120 func MakeIndexDescriptor( 121 params runParams, n *tree.CreateIndex, tableDesc *sqlbase.MutableTableDescriptor, 122 ) (*sqlbase.IndexDescriptor, error) { 123 // Ensure that the columns we want to index exist before trying to create the 124 // index. 125 if err := validateIndexColumnsExist(tableDesc, n.Columns); err != nil { 126 return nil, err 127 } 128 129 // Ensure that the index name does not exist before trying to create the index. 130 if err := tableDesc.ValidateIndexNameIsUnique(string(n.Name)); err != nil { 131 return nil, err 132 } 133 indexDesc := sqlbase.IndexDescriptor{ 134 Name: string(n.Name), 135 Unique: n.Unique, 136 StoreColumnNames: n.Storing.ToStrings(), 137 CreatedExplicitly: true, 138 } 139 140 if n.Inverted { 141 if n.Interleave != nil { 142 return nil, pgerror.New(pgcode.InvalidSQLStatementName, "inverted indexes don't support interleaved tables") 143 } 144 145 if n.PartitionBy != nil { 146 return nil, pgerror.New(pgcode.InvalidSQLStatementName, "inverted indexes don't support partitioning") 147 } 148 149 if n.Sharded != nil { 150 return nil, pgerror.New(pgcode.InvalidSQLStatementName, "inverted indexes don't support hash sharding") 151 } 152 153 if len(indexDesc.StoreColumnNames) > 0 { 154 return nil, pgerror.New(pgcode.InvalidSQLStatementName, "inverted indexes don't support stored columns") 155 } 156 157 if n.Unique { 158 return nil, pgerror.New(pgcode.InvalidSQLStatementName, "inverted indexes can't be unique") 159 } 160 indexDesc.Type = sqlbase.IndexDescriptor_INVERTED 161 columnDesc, _, err := tableDesc.FindColumnByName(n.Columns[0].Column) 162 if err != nil { 163 return nil, err 164 } 165 if columnDesc.Type.InternalType.Family == types.GeometryFamily { 166 indexDesc.GeoConfig = *geoindex.DefaultGeometryIndexConfig() 167 telemetry.Inc(sqltelemetry.GeometryInvertedIndexCounter) 168 } 169 if columnDesc.Type.InternalType.Family == types.GeographyFamily { 170 indexDesc.GeoConfig = *geoindex.DefaultGeographyIndexConfig() 171 telemetry.Inc(sqltelemetry.GeographyInvertedIndexCounter) 172 } 173 telemetry.Inc(sqltelemetry.InvertedIndexCounter) 174 } 175 176 if n.Sharded != nil { 177 if n.PartitionBy != nil { 178 return nil, pgerror.New(pgcode.FeatureNotSupported, "sharded indexes don't support partitioning") 179 } 180 if n.Interleave != nil { 181 return nil, pgerror.New(pgcode.FeatureNotSupported, "interleaved indexes cannot also be hash sharded") 182 } 183 shardCol, newColumn, err := setupShardedIndex( 184 params.ctx, 185 params.EvalContext(), 186 ¶ms.p.semaCtx, 187 params.SessionData().HashShardedIndexesEnabled, 188 &n.Columns, 189 n.Sharded.ShardBuckets, 190 tableDesc, 191 &indexDesc, 192 false /* isNewTable */) 193 if err != nil { 194 return nil, err 195 } 196 if newColumn { 197 if err := params.p.setupFamilyAndConstraintForShard(params.ctx, tableDesc, shardCol, 198 indexDesc.Sharded.ColumnNames, indexDesc.Sharded.ShardBuckets); err != nil { 199 return nil, err 200 } 201 } 202 telemetry.Inc(sqltelemetry.HashShardedIndexCounter) 203 } 204 205 if n.Predicate != nil { 206 // TODO(mgartner): remove this once partial indexes are fully supported. 207 if !params.SessionData().PartialIndexes { 208 return nil, unimplemented.NewWithIssue(9683, "partial indexes are not supported") 209 } 210 211 idxValidator := schemaexpr.NewIndexPredicateValidator(params.ctx, n.Table, tableDesc, ¶ms.p.semaCtx) 212 expr, err := idxValidator.Validate(n.Predicate) 213 if err != nil { 214 return nil, err 215 } 216 217 // Store the serialized predicate expression in the IndexDescriptor. 218 indexDesc.Predicate = tree.Serialize(expr) 219 } 220 221 if err := indexDesc.FillColumns(n.Columns); err != nil { 222 return nil, err 223 } 224 return &indexDesc, nil 225 } 226 227 // validateIndexColumnsExists validates that the columns for an index exist 228 // in the table and are not being dropped prior to attempting to add the index. 229 func validateIndexColumnsExist( 230 desc *sqlbase.MutableTableDescriptor, columns tree.IndexElemList, 231 ) error { 232 for _, column := range columns { 233 _, dropping, err := desc.FindColumnByName(column.Column) 234 if err != nil { 235 return err 236 } 237 if dropping { 238 return sqlbase.NewUndefinedColumnError(string(column.Column)) 239 } 240 } 241 return nil 242 } 243 244 // ReadingOwnWrites implements the planNodeReadingOwnWrites interface. 245 // This is because CREATE INDEX performs multiple KV operations on descriptors 246 // and expects to see its own writes. 247 func (n *createIndexNode) ReadingOwnWrites() {} 248 249 var invalidClusterForShardedIndexError = pgerror.Newf(pgcode.FeatureNotSupported, 250 "hash sharded indexes can only be created on a cluster that has fully migrated to version 20.1") 251 252 var hashShardedIndexesDisabledError = pgerror.Newf(pgcode.FeatureNotSupported, 253 "hash sharded indexes require the experimental_enable_hash_sharded_indexes cluster setting") 254 255 func setupShardedIndex( 256 ctx context.Context, 257 evalCtx *tree.EvalContext, 258 semaCtx *tree.SemaContext, 259 shardedIndexEnabled bool, 260 columns *tree.IndexElemList, 261 bucketsExpr tree.Expr, 262 tableDesc *sqlbase.MutableTableDescriptor, 263 indexDesc *sqlbase.IndexDescriptor, 264 isNewTable bool, 265 ) (shard *sqlbase.ColumnDescriptor, newColumn bool, err error) { 266 st := evalCtx.Settings 267 if !st.Version.IsActive(ctx, clusterversion.VersionHashShardedIndexes) { 268 return nil, false, invalidClusterForShardedIndexError 269 } 270 if !shardedIndexEnabled { 271 return nil, false, hashShardedIndexesDisabledError 272 } 273 274 colNames := make([]string, 0, len(*columns)) 275 for _, c := range *columns { 276 colNames = append(colNames, string(c.Column)) 277 } 278 buckets, err := sqlbase.EvalShardBucketCount(ctx, semaCtx, evalCtx, bucketsExpr) 279 if err != nil { 280 return nil, false, err 281 } 282 shardCol, newColumn, err := maybeCreateAndAddShardCol(int(buckets), tableDesc, 283 colNames, isNewTable) 284 if err != nil { 285 return nil, false, err 286 } 287 shardIdxElem := tree.IndexElem{ 288 Column: tree.Name(shardCol.Name), 289 Direction: tree.Ascending, 290 } 291 *columns = append(tree.IndexElemList{shardIdxElem}, *columns...) 292 indexDesc.Sharded = sqlbase.ShardedDescriptor{ 293 IsSharded: true, 294 Name: shardCol.Name, 295 ShardBuckets: buckets, 296 ColumnNames: colNames, 297 } 298 return shardCol, newColumn, nil 299 } 300 301 // maybeCreateAndAddShardCol adds a new hidden computed shard column (or its mutation) to 302 // `desc`, if one doesn't already exist for the given index column set and number of shard 303 // buckets. 304 func maybeCreateAndAddShardCol( 305 shardBuckets int, desc *sqlbase.MutableTableDescriptor, colNames []string, isNewTable bool, 306 ) (col *sqlbase.ColumnDescriptor, created bool, err error) { 307 shardCol, err := makeShardColumnDesc(colNames, shardBuckets) 308 if err != nil { 309 return nil, false, err 310 } 311 existingShardCol, dropped, err := desc.FindColumnByName(tree.Name(shardCol.Name)) 312 if err == nil && !dropped { 313 // TODO(ajwerner): In what ways is existingShardCol allowed to differ from 314 // the newly made shardCol? Should there be some validation of 315 // existingShardCol? 316 if !existingShardCol.Hidden { 317 // The user managed to reverse-engineer our crazy shard column name, so 318 // we'll return an error here rather than try to be tricky. 319 return nil, false, pgerror.Newf(pgcode.DuplicateColumn, 320 "column %s already specified; can't be used for sharding", shardCol.Name) 321 } 322 return existingShardCol, false, nil 323 } 324 columnIsUndefined := sqlbase.IsUndefinedColumnError(err) 325 if err != nil && !columnIsUndefined { 326 return nil, false, err 327 } 328 if columnIsUndefined || dropped { 329 if isNewTable { 330 desc.AddColumn(shardCol) 331 } else { 332 desc.AddColumnMutation(shardCol, sqlbase.DescriptorMutation_ADD) 333 } 334 created = true 335 } 336 return shardCol, created, nil 337 } 338 339 func (n *createIndexNode) startExec(params runParams) error { 340 telemetry.Inc(sqltelemetry.SchemaChangeCreateCounter("index")) 341 _, dropped, err := n.tableDesc.FindIndexByName(string(n.n.Name)) 342 if err == nil { 343 if dropped { 344 return pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, 345 "index %q being dropped, try again later", string(n.n.Name)) 346 } 347 if n.n.IfNotExists { 348 return nil 349 } 350 } 351 352 if n.n.Concurrently { 353 params.p.SendClientNotice( 354 params.ctx, 355 pgnotice.Newf("CONCURRENTLY is not required as all indexes are created concurrently"), 356 ) 357 } 358 359 // Warn against creating a non-partitioned index on a partitioned table, 360 // which is undesirable in most cases. 361 if n.n.PartitionBy == nil && n.tableDesc.PrimaryIndex.Partitioning.NumColumns > 0 { 362 params.p.SendClientNotice( 363 params.ctx, 364 errors.WithHint( 365 pgnotice.Newf("creating non-partitioned index on partitioned table may not be performant"), 366 "Consider modifying the index such that it is also partitioned.", 367 ), 368 ) 369 } 370 371 indexDesc, err := MakeIndexDescriptor(params, n.n, n.tableDesc) 372 if err != nil { 373 return err 374 } 375 376 // Increment the counter if this index could be storing data across multiple column families. 377 if len(indexDesc.StoreColumnNames) > 1 && len(n.tableDesc.Families) > 1 { 378 telemetry.Inc(sqltelemetry.SecondaryIndexColumnFamiliesCounter) 379 } 380 381 // If all nodes in the cluster know how to handle secondary indexes with column families, 382 // write the new version into the index descriptor. 383 encodingVersion := sqlbase.BaseIndexFormatVersion 384 if params.p.EvalContext().Settings.Version.IsActive(params.ctx, clusterversion.VersionSecondaryIndexColumnFamilies) { 385 encodingVersion = sqlbase.SecondaryIndexFamilyFormatVersion 386 } 387 indexDesc.Version = encodingVersion 388 389 if n.n.PartitionBy != nil { 390 partitioning, err := CreatePartitioning(params.ctx, params.p.ExecCfg().Settings, 391 params.EvalContext(), n.tableDesc, indexDesc, n.n.PartitionBy) 392 if err != nil { 393 return err 394 } 395 indexDesc.Partitioning = partitioning 396 } 397 398 mutationIdx := len(n.tableDesc.Mutations) 399 if err := n.tableDesc.AddIndexMutation(indexDesc, sqlbase.DescriptorMutation_ADD); err != nil { 400 return err 401 } 402 if err := n.tableDesc.AllocateIDs(); err != nil { 403 return err 404 } 405 // The index name may have changed as a result of 406 // AllocateIDs(). Retrieve it for the event log below. 407 index := n.tableDesc.Mutations[mutationIdx].GetIndex() 408 indexName := index.Name 409 410 if n.n.Interleave != nil { 411 if err := params.p.addInterleave(params.ctx, n.tableDesc, index, n.n.Interleave); err != nil { 412 return err 413 } 414 if err := params.p.finalizeInterleave(params.ctx, n.tableDesc, index); err != nil { 415 return err 416 } 417 } 418 419 mutationID := n.tableDesc.ClusterVersion.NextMutationID 420 if err := params.p.writeSchemaChange( 421 params.ctx, n.tableDesc, mutationID, tree.AsStringWithFQNames(n.n, params.Ann()), 422 ); err != nil { 423 return err 424 } 425 426 // Record index creation in the event log. This is an auditable log 427 // event and is recorded in the same transaction as the table descriptor 428 // update. 429 return MakeEventLogger(params.extendedEvalCtx.ExecCfg).InsertEventRecord( 430 params.ctx, 431 params.p.txn, 432 EventLogCreateIndex, 433 int32(n.tableDesc.ID), 434 int32(params.extendedEvalCtx.NodeID.SQLInstanceID()), 435 struct { 436 TableName string 437 IndexName string 438 Statement string 439 User string 440 MutationID uint32 441 }{ 442 n.n.Table.FQString(), indexName, n.n.String(), 443 params.SessionData().User, uint32(mutationID), 444 }, 445 ) 446 } 447 448 func (*createIndexNode) Next(runParams) (bool, error) { return false, nil } 449 func (*createIndexNode) Values() tree.Datums { return tree.Datums{} } 450 func (*createIndexNode) Close(context.Context) {}