From 9843c0dbae0490cc497faa4462aa0b9795707070 Mon Sep 17 00:00:00 2001 From: Alex Lunev Date: Mon, 1 Nov 2021 15:35:06 -0700 Subject: [PATCH] sql: add ALTER RANGE RELOCATE This commit introduces a new ALTER RANGE RELOCATE command, which will allow an admin to move a lease or replica for a specific range. Unlike ALTER TABLE RELOCATE this command works on a range_id, which makes it a lot easier to use since the user does not have to worry about range keys, which are difficult to deal with in an emergncy situation. Release note (sql change): Introduce new SQL syntax ALTER RANGE RELOCATE to move a lease or replica between stores. This is helpful in an emergency situation to relocate data in the cluster. --- docs/generated/sql/bnf/alter_index_stmt.bnf | 1 + .../bnf/alter_range_relocate_lease_stmt.bnf | 2 + docs/generated/sql/bnf/alter_range_stmt.bnf | 1 + .../sql/bnf/alter_relocate_index_stmt.bnf | 3 + docs/generated/sql/bnf/stmt_block.bnf | 19 +++ pkg/sql/catalog/colinfo/result_columns.go | 7 + pkg/sql/distsql_spec_exec_factory.go | 6 + pkg/sql/opt/exec/execbuilder/relational.go | 3 + pkg/sql/opt/exec/execbuilder/statement.go | 14 ++ pkg/sql/opt/exec/explain/emit.go | 2 + pkg/sql/opt/exec/explain/result_columns.go | 3 + pkg/sql/opt/exec/factory.opt | 9 ++ pkg/sql/opt/memo/expr_format.go | 2 +- pkg/sql/opt/memo/logical_props_builder.go | 6 + pkg/sql/opt/ops/statement.opt | 21 +++ pkg/sql/opt/optbuilder/alter_range.go | 43 ++++++ pkg/sql/opt/optbuilder/builder.go | 5 +- pkg/sql/opt/ordering/ordering.go | 5 + pkg/sql/opt/ordering/statement.go | 9 ++ pkg/sql/opt/xform/physical_props.go | 2 + pkg/sql/opt_exec_factory.go | 17 +++ pkg/sql/parser/sql.y | 41 +++++- pkg/sql/plan.go | 1 + pkg/sql/plan_columns.go | 2 + pkg/sql/relocate.go | 24 ++-- pkg/sql/relocate_range.go | 130 ++++++++++++++++++ pkg/sql/sem/tree/alter_range.go | 43 ++++++ pkg/sql/sem/tree/stmt.go | 17 +++ pkg/sql/walk.go | 3 + 29 files changed, 430 insertions(+), 11 deletions(-) create mode 100644 docs/generated/sql/bnf/alter_range_relocate_lease_stmt.bnf create mode 100644 docs/generated/sql/bnf/alter_relocate_index_stmt.bnf create mode 100644 pkg/sql/opt/optbuilder/alter_range.go create mode 100644 pkg/sql/relocate_range.go create mode 100644 pkg/sql/sem/tree/alter_range.go diff --git a/docs/generated/sql/bnf/alter_index_stmt.bnf b/docs/generated/sql/bnf/alter_index_stmt.bnf index 56fedf088399..c4dd13b9fecc 100644 --- a/docs/generated/sql/bnf/alter_index_stmt.bnf +++ b/docs/generated/sql/bnf/alter_index_stmt.bnf @@ -1,5 +1,6 @@ alter_index_stmt ::= alter_oneindex_stmt + | alter_relocate_index_stmt | alter_split_index_stmt | alter_unsplit_index_stmt | alter_scatter_index_stmt diff --git a/docs/generated/sql/bnf/alter_range_relocate_lease_stmt.bnf b/docs/generated/sql/bnf/alter_range_relocate_lease_stmt.bnf new file mode 100644 index 000000000000..1291b568e46a --- /dev/null +++ b/docs/generated/sql/bnf/alter_range_relocate_lease_stmt.bnf @@ -0,0 +1,2 @@ +alter_range_relocate_lease_stmt ::= + 'ALTER' 'RANGE' iconst64 relocate_kw 'LEASE' 'TO' iconst64 diff --git a/docs/generated/sql/bnf/alter_range_stmt.bnf b/docs/generated/sql/bnf/alter_range_stmt.bnf index ee71f8482572..d5b92f5e5dbf 100644 --- a/docs/generated/sql/bnf/alter_range_stmt.bnf +++ b/docs/generated/sql/bnf/alter_range_stmt.bnf @@ -1,2 +1,3 @@ alter_range_stmt ::= alter_zone_range_stmt + | alter_range_relocate_lease_stmt diff --git a/docs/generated/sql/bnf/alter_relocate_index_stmt.bnf b/docs/generated/sql/bnf/alter_relocate_index_stmt.bnf new file mode 100644 index 000000000000..0cc221abfdca --- /dev/null +++ b/docs/generated/sql/bnf/alter_relocate_index_stmt.bnf @@ -0,0 +1,3 @@ +alter_relocate_index_stmt ::= + 'ALTER' 'RANGE' iconst64 relocate_kw voters_kw 'FROM' iconst64 'TO' iconst64 + | 'ALTER' 'RANGE' iconst64 relocate_kw voters_kw 'NON_VOTERS' iconst64 'TO' iconst64 diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index d6065ff85b72..362c0dd5b508 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -1091,6 +1091,7 @@ unreserved_keyword ::= | 'REGIONS' | 'REINDEX' | 'RELEASE' + | 'RELOCATE' | 'RENAME' | 'REPEATABLE' | 'REPLACE' @@ -1320,6 +1321,7 @@ alter_table_stmt ::= alter_index_stmt ::= alter_oneindex_stmt + | alter_relocate_index_stmt | alter_split_index_stmt | alter_unsplit_index_stmt | alter_scatter_index_stmt @@ -1349,6 +1351,7 @@ alter_database_stmt ::= alter_range_stmt ::= alter_zone_range_stmt + | alter_range_relocate_lease_stmt alter_partition_stmt ::= alter_zone_partition_stmt @@ -1760,6 +1763,10 @@ alter_oneindex_stmt ::= 'ALTER' 'INDEX' table_index_name alter_index_cmds | 'ALTER' 'INDEX' 'IF' 'EXISTS' table_index_name alter_index_cmds +alter_relocate_index_stmt ::= + 'ALTER' 'RANGE' iconst64 relocate_kw voters_kw 'FROM' iconst64 'TO' iconst64 + | 'ALTER' 'RANGE' iconst64 relocate_kw voters_kw 'NON_VOTERS' iconst64 'TO' iconst64 + alter_split_index_stmt ::= 'ALTER' 'INDEX' table_index_name 'SPLIT' 'AT' select_stmt | 'ALTER' 'INDEX' table_index_name 'SPLIT' 'AT' select_stmt 'WITH' 'EXPIRATION' a_expr @@ -1843,6 +1850,9 @@ alter_database_primary_region_stmt ::= alter_zone_range_stmt ::= 'ALTER' 'RANGE' zone_name set_zone_config +alter_range_relocate_lease_stmt ::= + 'ALTER' 'RANGE' iconst64 relocate_kw 'LEASE' 'TO' iconst64 + alter_zone_partition_stmt ::= 'ALTER' 'PARTITION' partition_name 'OF' 'TABLE' table_name set_zone_config | 'ALTER' 'PARTITION' partition_name 'OF' 'INDEX' table_index_name set_zone_config @@ -2328,6 +2338,15 @@ locality ::= alter_index_cmds ::= ( alter_index_cmd ) ( ( ',' alter_index_cmd ) )* +relocate_kw ::= + 'TESTING_RELOCATE' + | 'EXPERIMENTAL_RELOCATE' + | 'RELOCATE' + +voters_kw ::= + 'VOTERS' + | + sequence_option_list ::= ( sequence_option_elem ) ( ( sequence_option_elem ) )* diff --git a/pkg/sql/catalog/colinfo/result_columns.go b/pkg/sql/catalog/colinfo/result_columns.go index 18004ae197a0..3cc6d083e058 100644 --- a/pkg/sql/catalog/colinfo/result_columns.go +++ b/pkg/sql/catalog/colinfo/result_columns.go @@ -222,6 +222,13 @@ var AlterTableScatterColumns = ResultColumns{ {Name: "pretty", Typ: types.String}, } +// AlterRangeRelocateColumns are the result columns of an +// ALTER RANGE .. RELOCATE statement. +var AlterRangeRelocateColumns = ResultColumns{ + {Name: "key", Typ: types.Bytes}, + {Name: "pretty", Typ: types.String}, +} + // ScrubColumns are the result columns of a SCRUB statement. var ScrubColumns = ResultColumns{ {Name: "job_uuid", Typ: types.Uuid}, diff --git a/pkg/sql/distsql_spec_exec_factory.go b/pkg/sql/distsql_spec_exec_factory.go index f3d5d1882384..90832fc1e7ad 100644 --- a/pkg/sql/distsql_spec_exec_factory.go +++ b/pkg/sql/distsql_spec_exec_factory.go @@ -1005,6 +1005,12 @@ func (e *distSQLSpecExecFactory) ConstructAlterTableRelocate( return nil, unimplemented.NewWithIssue(47473, "experimental opt-driven distsql planning: alter table relocate") } +func (e *distSQLSpecExecFactory) ConstructAlterRangeRelocate( + relocateLease bool, relocateNonVoters bool, rangeID int64, toStoreID int64, fromStoreID int64, +) (exec.Node, error) { + return nil, unimplemented.NewWithIssue(47473, "experimental opt-driven distsql planning: alter range relocate") +} + func (e *distSQLSpecExecFactory) ConstructBuffer(input exec.Node, label string) (exec.Node, error) { return nil, unimplemented.NewWithIssue(47473, "experimental opt-driven distsql planning: buffer") } diff --git a/pkg/sql/opt/exec/execbuilder/relational.go b/pkg/sql/opt/exec/execbuilder/relational.go index 4a807483fa26..f7c3e8f25eed 100644 --- a/pkg/sql/opt/exec/execbuilder/relational.go +++ b/pkg/sql/opt/exec/execbuilder/relational.go @@ -312,6 +312,9 @@ func (b *Builder) buildRelational(e memo.RelExpr) (execPlan, error) { case *memo.AlterTableRelocateExpr: ep, err = b.buildAlterTableRelocate(t) + case *memo.AlterRangeRelocateExpr: + ep, err = b.buildAlterRangeRelocate(t) + case *memo.ControlJobsExpr: ep, err = b.buildControlJobs(t) diff --git a/pkg/sql/opt/exec/execbuilder/statement.go b/pkg/sql/opt/exec/execbuilder/statement.go index 83ef2910f1fa..594576303671 100644 --- a/pkg/sql/opt/exec/execbuilder/statement.go +++ b/pkg/sql/opt/exec/execbuilder/statement.go @@ -217,6 +217,20 @@ func (b *Builder) buildAlterTableRelocate(relocate *memo.AlterTableRelocateExpr) return planWithColumns(node, relocate.Columns), nil } +func (b *Builder) buildAlterRangeRelocate(relocate *memo.AlterRangeRelocateExpr) (execPlan, error) { + node, err := b.factory.ConstructAlterRangeRelocate( + relocate.RelocateLease, + relocate.RelocateNonVoters, + relocate.RangeID, + relocate.ToStoreID, + relocate.FromStoreID, + ) + if err != nil { + return execPlan{}, err + } + return planWithColumns(node, relocate.Columns), nil +} + func (b *Builder) buildControlJobs(ctl *memo.ControlJobsExpr) (execPlan, error) { input, err := b.buildRelational(ctl.Input) if err != nil { diff --git a/pkg/sql/opt/exec/explain/emit.go b/pkg/sql/opt/exec/explain/emit.go index e7ba86f05511..bbd1b2322cfd 100644 --- a/pkg/sql/opt/exec/explain/emit.go +++ b/pkg/sql/opt/exec/explain/emit.go @@ -252,6 +252,7 @@ func (e *emitter) nodeName(n *Node) (string, error) { } var nodeNames = [...]string{ + alterRangeRelocateOp: "relocate", alterTableRelocateOp: "relocate", alterTableSplitOp: "split", alterTableUnsplitAllOp: "unsplit all", @@ -815,6 +816,7 @@ func (e *emitter) emitNodeAttributes(n *Node) error { alterTableUnsplitOp, alterTableUnsplitAllOp, alterTableRelocateOp, + alterRangeRelocateOp, controlJobsOp, controlSchedulesOp, cancelQueriesOp, diff --git a/pkg/sql/opt/exec/explain/result_columns.go b/pkg/sql/opt/exec/explain/result_columns.go index 88eac1b64ff1..1523259857ed 100644 --- a/pkg/sql/opt/exec/explain/result_columns.go +++ b/pkg/sql/opt/exec/explain/result_columns.go @@ -155,6 +155,9 @@ func getResultColumns( case alterTableRelocateOp: return colinfo.AlterTableRelocateColumns, nil + case alterRangeRelocateOp: + return colinfo.AlterRangeRelocateColumns, nil + case exportOp: return colinfo.ExportColumns, nil diff --git a/pkg/sql/opt/exec/factory.opt b/pkg/sql/opt/exec/factory.opt index e2fd76ffa632..e1ed9c8c9f81 100644 --- a/pkg/sql/opt/exec/factory.opt +++ b/pkg/sql/opt/exec/factory.opt @@ -666,6 +666,15 @@ define AlterTableRelocate { relocateNonVoters bool } +# AlterTableRelocate implements ALTER RANGE RELOCATE. +define AlterRangeRelocate { + relocateLease bool + relocateNonVoters bool + rangeId int64 + toStoreId int64 + fromStoreId int64 +} + # Buffer passes through the input rows but also saves them in a buffer, which # can be referenced from elsewhere in the query (using ScanBuffer). define Buffer { diff --git a/pkg/sql/opt/memo/expr_format.go b/pkg/sql/opt/memo/expr_format.go index 2eeb034061cb..1ba915cd4880 100644 --- a/pkg/sql/opt/memo/expr_format.go +++ b/pkg/sql/opt/memo/expr_format.go @@ -211,7 +211,7 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) { *InsertExpr, *UpdateExpr, *UpsertExpr, *DeleteExpr, *SequenceSelectExpr, *WindowExpr, *OpaqueRelExpr, *OpaqueMutationExpr, *OpaqueDDLExpr, *AlterTableSplitExpr, *AlterTableUnsplitExpr, *AlterTableUnsplitAllExpr, - *AlterTableRelocateExpr, *ControlJobsExpr, *CancelQueriesExpr, + *AlterTableRelocateExpr, *AlterRangeRelocateExpr, *ControlJobsExpr, *CancelQueriesExpr, *CancelSessionsExpr, *CreateViewExpr, *ExportExpr: fmt.Fprintf(f.Buffer, "%v", e.Op()) FormatPrivate(f, e.Private(), required) diff --git a/pkg/sql/opt/memo/logical_props_builder.go b/pkg/sql/opt/memo/logical_props_builder.go index 1367102adac8..5b8ba1fd9b18 100644 --- a/pkg/sql/opt/memo/logical_props_builder.go +++ b/pkg/sql/opt/memo/logical_props_builder.go @@ -1033,6 +1033,12 @@ func (b *logicalPropsBuilder) buildAlterTableRelocateProps( b.buildBasicProps(relocate, relocate.Columns, rel) } +func (b *logicalPropsBuilder) buildAlterRangeRelocateProps( + relocate *AlterRangeRelocateExpr, rel *props.Relational, +) { + b.buildBasicProps(relocate, relocate.Columns, rel) +} + func (b *logicalPropsBuilder) buildControlJobsProps(ctl *ControlJobsExpr, rel *props.Relational) { b.buildBasicProps(ctl, opt.ColList{}, rel) } diff --git a/pkg/sql/opt/ops/statement.opt b/pkg/sql/opt/ops/statement.opt index 015914d251ed..27d0bb52e53c 100644 --- a/pkg/sql/opt/ops/statement.opt +++ b/pkg/sql/opt/ops/statement.opt @@ -292,3 +292,24 @@ define CreateStatisticsPrivate { # Syntax is the tree.CreateStats AST node. Syntax CreateStats } + +# AlterTableRelocateRange represents an `ALTER RANGE .. RELOCATE ..` statement. +[Relational, Mutation] +define AlterRangeRelocate { + _ AlterRangeRelocatePrivate +} + +[Private] +define AlterRangeRelocatePrivate { + RelocateLease bool + RelocateNonVoters bool + RangeID int64 + ToStoreID int64 + FromStoreID int64 + + # Columns stores the column IDs for the statement result columns. + Columns ColList + + # Props stores the required physical properties for the input expression. + Props PhysProps +} diff --git a/pkg/sql/opt/optbuilder/alter_range.go b/pkg/sql/opt/optbuilder/alter_range.go new file mode 100644 index 000000000000..2eb1fd05e583 --- /dev/null +++ b/pkg/sql/opt/optbuilder/alter_range.go @@ -0,0 +1,43 @@ +// Copyright 2018 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package optbuilder + +import ( + "github.com/cockroachdb/cockroach/pkg/sql/catalog/colinfo" + "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" + "github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical" + "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" +) + +// buildAlterTableRelocate builds an ALTER RANGE RELOCATE (LEASE). +func (b *Builder) buildAlterRangeRelocate( + relocate *tree.RelocateRange, inScope *scope, +) (outScope *scope) { + //TODO(lunevalex) we need to go from a rangeId to a tableOrIndex name to get perms check to work + + b.DisableMemoReuse = true + + outScope = inScope.push() + b.synthesizeResultColumns(outScope, colinfo.AlterTableRelocateColumns) + + outScope.expr = b.factory.ConstructAlterRangeRelocate( + &memo.AlterRangeRelocatePrivate{ + RelocateLease: relocate.RelocateLease, + RelocateNonVoters: relocate.RelocateNonVoters, + RangeID: relocate.RangeID, + ToStoreID: relocate.ToStoreID, + FromStoreID: relocate.FromStoreID, + Columns: colsToColList(outScope.cols), + Props: physical.MinRequired, + }, + ) + return outScope +} diff --git a/pkg/sql/opt/optbuilder/builder.go b/pkg/sql/opt/optbuilder/builder.go index 6c6a3ab94993..870eeea8b71b 100644 --- a/pkg/sql/opt/optbuilder/builder.go +++ b/pkg/sql/opt/optbuilder/builder.go @@ -268,7 +268,7 @@ func (b *Builder) buildStmt( // A blocklist of statements that can't be used from inside a view. switch stmt := stmt.(type) { case *tree.Delete, *tree.Insert, *tree.Update, *tree.CreateTable, *tree.CreateView, - *tree.Split, *tree.Unsplit, *tree.Relocate, + *tree.Split, *tree.Unsplit, *tree.Relocate, *tree.RelocateRange, *tree.ControlJobs, *tree.ControlSchedules, *tree.CancelQueries, *tree.CancelSessions: panic(pgerror.Newf( pgcode.Syntax, "%s cannot be used inside a view definition", stmt.StatementTag(), @@ -323,6 +323,9 @@ func (b *Builder) buildStmt( case *tree.Relocate: return b.buildAlterTableRelocate(stmt, inScope) + case *tree.RelocateRange: + return b.buildAlterRangeRelocate(stmt, inScope) + case *tree.ControlJobs: return b.buildControlJobs(stmt, inScope) diff --git a/pkg/sql/opt/ordering/ordering.go b/pkg/sql/opt/ordering/ordering.go index 53898367d64e..3de6c19182b4 100644 --- a/pkg/sql/opt/ordering/ordering.go +++ b/pkg/sql/opt/ordering/ordering.go @@ -256,6 +256,11 @@ func init() { buildChildReqOrdering: alterTableRelocateBuildChildReqOrdering, buildProvidedOrdering: noProvidedOrdering, } + funcMap[opt.AlterRangeRelocateOp] = funcs{ + canProvideOrdering: canNeverProvideOrdering, + buildChildReqOrdering: alterRangeRelocateBuildChildReqOrdering, + buildProvidedOrdering: noProvidedOrdering, + } funcMap[opt.ControlJobsOp] = funcs{ canProvideOrdering: canNeverProvideOrdering, buildChildReqOrdering: controlJobsBuildChildReqOrdering, diff --git a/pkg/sql/opt/ordering/statement.go b/pkg/sql/opt/ordering/statement.go index 235f880ce4ed..57b1e73a126e 100644 --- a/pkg/sql/opt/ordering/statement.go +++ b/pkg/sql/opt/ordering/statement.go @@ -48,6 +48,15 @@ func alterTableRelocateBuildChildReqOrdering( return parent.(*memo.AlterTableRelocateExpr).Props.Ordering } +func alterRangeRelocateBuildChildReqOrdering( + parent memo.RelExpr, required *props.OrderingChoice, childIdx int, +) props.OrderingChoice { + if childIdx != 0 { + return props.OrderingChoice{} + } + return parent.(*memo.AlterRangeRelocateExpr).Props.Ordering +} + func controlJobsBuildChildReqOrdering( parent memo.RelExpr, required *props.OrderingChoice, childIdx int, ) props.OrderingChoice { diff --git a/pkg/sql/opt/xform/physical_props.go b/pkg/sql/opt/xform/physical_props.go index abf73e952455..d635ba731cc5 100644 --- a/pkg/sql/opt/xform/physical_props.go +++ b/pkg/sql/opt/xform/physical_props.go @@ -67,6 +67,8 @@ func BuildChildPhysicalProps( childProps.Presentation = parent.(*memo.AlterTableUnsplitExpr).Props.Presentation case opt.AlterTableRelocateOp: childProps.Presentation = parent.(*memo.AlterTableRelocateExpr).Props.Presentation + case opt.AlterRangeRelocateOp: + childProps.Presentation = parent.(*memo.AlterRangeRelocateExpr).Props.Presentation case opt.ControlJobsOp: childProps.Presentation = parent.(*memo.ControlJobsExpr).Props.Presentation case opt.CancelQueriesOp: diff --git a/pkg/sql/opt_exec_factory.go b/pkg/sql/opt_exec_factory.go index a8c349e600ca..66a6a7bd403a 100644 --- a/pkg/sql/opt_exec_factory.go +++ b/pkg/sql/opt_exec_factory.go @@ -1978,6 +1978,23 @@ func (ef *execFactory) ConstructAlterTableRelocate( }, nil } +// ConstructAlterRangeRelocate is part of the exec.Factory interface. +func (ef *execFactory) ConstructAlterRangeRelocate( + relocateLease bool, relocateNonVoters bool, rangeID int64, toStoreID int64, fromStoreID int64, +) (exec.Node, error) { + if !ef.planner.ExecCfg().Codec.ForSystemTenant() { + return nil, errorutil.UnsupportedWithMultiTenancy(54250) + } + + return &relocateRange{ + relocateLease: relocateLease, + relocateNonVoters: relocateNonVoters, + rangeID: roachpb.RangeID(rangeID), + toStoreID: roachpb.StoreID(toStoreID), + fromStoreID: roachpb.StoreID(fromStoreID), + }, nil +} + // ConstructControlJobs is part of the exec.Factory interface. func (ef *execFactory) ConstructControlJobs( command tree.JobCommand, input exec.Node, reason tree.TypedExpr, diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index f6873872e4ed..8c6e5c1da895 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -844,7 +844,7 @@ func (u *sqlSymUnion) setVar() *tree.SetVar { %token START STATISTICS STATUS STDIN STREAM STRICT STRING STORAGE STORE STORED STORING SUBSTRING %token SURVIVE SURVIVAL SYMMETRIC SYNTAX SYSTEM SQRT SUBSCRIPTION STATEMENTS -%token TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TENANT TESTING_RELOCATE EXPERIMENTAL_RELOCATE TEXT THEN +%token TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TENANT TESTING_RELOCATE EXPERIMENTAL_RELOCATE RELOCATE TEXT THEN %token TIES TIME TIMETZ TIMESTAMP TIMESTAMPTZ TO THROTTLING TRAILING TRACE %token TRANSACTION TRANSACTIONS TREAT TRIGGER TRIM TRUE %token TRUNCATE TRUSTED TYPE TYPES @@ -905,6 +905,7 @@ func (u *sqlSymUnion) setVar() *tree.SetVar { // ALTER RANGE %type alter_zone_range_stmt +%type alter_range_relocate_lease_stmt // ALTER TABLE %type alter_onetable_stmt @@ -1781,6 +1782,8 @@ alter_database_primary_region_stmt: // // Commands: // ALTER RANGE ... CONFIGURE ZONE +// ALTER RANGE r RELOCATE from to +// ALTER RANGE r RELOCATE LEASE to // // Zone configurations: // DISCARD @@ -1791,6 +1794,7 @@ alter_database_primary_region_stmt: // %SeeAlso: ALTER TABLE alter_range_stmt: alter_zone_range_stmt +| alter_range_relocate_lease_stmt | ALTER RANGE error // SHOW HELP: ALTER RANGE // %Help: ALTER INDEX - change the definition of an index @@ -1906,6 +1910,7 @@ alter_unsplit_index_stmt: relocate_kw: TESTING_RELOCATE | EXPERIMENTAL_RELOCATE +| RELOCATE voters_kw: VOTERS {} @@ -1971,6 +1976,39 @@ alter_zone_range_stmt: $$.val = s } +alter_range_relocate_lease_stmt: + ALTER RANGE iconst64 relocate_kw LEASE TO iconst64 + { + $$.val = &tree.RelocateRange{ + RangeID: $3.int64(), + ToStoreID: $7.int64(), + RelocateLease: true, + RelocateNonVoters: false, + } + } + +alter_relocate_index_stmt: + ALTER RANGE iconst64 relocate_kw voters_kw FROM iconst64 TO iconst64 + { + $$.val = &tree.RelocateRange{ + RangeID: $3.int64(), + FromStoreID: $7.int64(), + ToStoreID: $9.int64(), + RelocateLease: false, + RelocateNonVoters: false, + } + } +| ALTER RANGE iconst64 relocate_kw voters_kw NON_VOTERS iconst64 TO iconst64 + { + $$.val = &tree.RelocateRange{ + RangeID: $3.int64(), + FromStoreID: $7.int64(), + ToStoreID: $9.int64(), + RelocateLease: false, + RelocateNonVoters: true, + } + } + set_zone_config: CONFIGURE ZONE to_or_eq a_expr { @@ -13380,6 +13418,7 @@ unreserved_keyword: | REGIONS | REINDEX | RELEASE +| RELOCATE | RENAME | REPEATABLE | REPLACE diff --git a/pkg/sql/plan.go b/pkg/sql/plan.go index 32c1f486606c..0c02251090db 100644 --- a/pkg/sql/plan.go +++ b/pkg/sql/plan.go @@ -195,6 +195,7 @@ var _ planNode = &reassignOwnedByNode{} var _ planNode = &refreshMaterializedViewNode{} var _ planNode = &recursiveCTENode{} var _ planNode = &relocateNode{} +var _ planNode = &relocateRange{} var _ planNode = &renameColumnNode{} var _ planNode = &renameDatabaseNode{} var _ planNode = &renameIndexNode{} diff --git a/pkg/sql/plan_columns.go b/pkg/sql/plan_columns.go index da28ac210883..3fc2f7818d30 100644 --- a/pkg/sql/plan_columns.go +++ b/pkg/sql/plan_columns.go @@ -108,6 +108,8 @@ func getPlanColumns(plan planNode, mut bool) colinfo.ResultColumns { return n.getColumns(mut, colinfo.ExplainPlanColumns) case *relocateNode: return n.getColumns(mut, colinfo.AlterTableRelocateColumns) + case *relocateRange: + return n.getColumns(mut, colinfo.AlterRangeRelocateColumns) case *scatterNode: return n.getColumns(mut, colinfo.AlterTableScatterColumns) case *showFingerprintsNode: diff --git a/pkg/sql/relocate.go b/pkg/sql/relocate.go index 7720db1941f2..989e7dc6ee86 100644 --- a/pkg/sql/relocate.go +++ b/pkg/sql/relocate.go @@ -91,17 +91,10 @@ func (n *relocateNode) Next(params runParams) (bool, error) { nodeID, ok := n.run.storeMap[storeID] if !ok { // Lookup the store in gossip. - var storeDesc roachpb.StoreDescriptor - gossipStoreKey := gossip.MakeStoreKey(storeID) - g, err := params.extendedEvalCtx.ExecCfg.Gossip.OptionalErr(54250) + storeDesc, err := lookupStoreDesc(storeID, params) if err != nil { return false, err } - if err := g.GetInfoProto( - gossipStoreKey, &storeDesc, - ); err != nil { - return false, errors.Wrapf(err, "error looking up store %d", storeID) - } nodeID = storeDesc.Node.NodeID n.run.storeMap[storeID] = nodeID } @@ -161,6 +154,21 @@ func (n *relocateNode) Close(ctx context.Context) { n.rows.Close(ctx) } +func lookupStoreDesc(storeID roachpb.StoreID, params runParams) (*roachpb.StoreDescriptor, error) { + var storeDesc roachpb.StoreDescriptor + gossipStoreKey := gossip.MakeStoreKey(storeID) + g, err := params.extendedEvalCtx.ExecCfg.Gossip.OptionalErr(54250) + if err != nil { + return nil, err + } + if err := g.GetInfoProto( + gossipStoreKey, &storeDesc, + ); err != nil { + return nil, errors.Wrapf(err, "error looking up store %d", storeID) + } + return &storeDesc, nil +} + func lookupRangeDescriptor( ctx context.Context, db *kv.DB, rowKey []byte, ) (roachpb.RangeDescriptor, error) { diff --git a/pkg/sql/relocate_range.go b/pkg/sql/relocate_range.go new file mode 100644 index 000000000000..41abb0024530 --- /dev/null +++ b/pkg/sql/relocate_range.go @@ -0,0 +1,130 @@ +// Copyright 2017 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package sql + +import ( + "context" + + "github.com/cockroachdb/cockroach/pkg/keys" + "github.com/cockroachdb/cockroach/pkg/kv" + "github.com/cockroachdb/cockroach/pkg/roachpb" + "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" + "github.com/cockroachdb/errors" +) + +type relocateRange struct { + optColumnsSlot + + relocateLease bool + relocateNonVoters bool + rangeID roachpb.RangeID + toStoreID roachpb.StoreID + fromStoreID roachpb.StoreID +} + +func (n *relocateRange) startExec(params runParams) error { + if n.toStoreID <= 0 { + return errors.Errorf("invalid target to store ID %d for RELOCATE", n.toStoreID) + } + if n.rangeID <= 0 { + return errors.Errorf("invalid range for ID %d for RELOCATE", n.rangeID) + } + if !n.relocateLease { + if n.fromStoreID <= 0 { + return errors.Errorf("invalid target from store ID %d for RELOCATE", n.fromStoreID) + } + } + + toStoreDesc, err := lookupStoreDesc(n.toStoreID, params) + if err != nil { + return err + } + rangeDesc, err := lookupRangeDescriptorByRangeID(params.ctx, params.extendedEvalCtx.ExecCfg.DB, n.rangeID) + if err != nil { + return errors.Wrapf(err, "error looking up range descriptor") + } + + if n.relocateLease { + if err := params.p.ExecCfg().DB.AdminTransferLease(params.ctx, rangeDesc.StartKey, n.toStoreID); err != nil { + return err + } + } else { + fromStoreDesc, err := lookupStoreDesc(n.fromStoreID, params) + if err != nil { + return err + } + toTarget := roachpb.ReplicationTarget{NodeID: toStoreDesc.Node.NodeID, StoreID: toStoreDesc.StoreID} + fromTarget := roachpb.ReplicationTarget{NodeID: fromStoreDesc.Node.NodeID, StoreID: fromStoreDesc.StoreID} + if n.relocateNonVoters { + if _, err := params.p.ExecCfg().DB.AdminChangeReplicas( + params.ctx, rangeDesc.StartKey, rangeDesc, []roachpb.ReplicationChange{ + {ChangeType: roachpb.ADD_NON_VOTER, Target: toTarget}, + {ChangeType: roachpb.REMOVE_NON_VOTER, Target: fromTarget}, + }, + ); err != nil { + return err + } + } else { + if _, err := params.p.ExecCfg().DB.AdminChangeReplicas( + params.ctx, rangeDesc.StartKey, rangeDesc, []roachpb.ReplicationChange{ + {ChangeType: roachpb.ADD_VOTER, Target: toTarget}, + {ChangeType: roachpb.REMOVE_VOTER, Target: fromTarget}, + }, + ); err != nil { + return err + } + } + } + return nil +} + +func (n *relocateRange) Next(runParams) (bool, error) { return false, nil } +func (n *relocateRange) Values() tree.Datums { return tree.Datums{} } +func (n *relocateRange) Close(context.Context) {} + +func lookupRangeDescriptorByRangeID( + ctx context.Context, db *kv.DB, rangeID roachpb.RangeID, +) (roachpb.RangeDescriptor, error) { + var descriptor roachpb.RangeDescriptor + sentinelErr := errors.Errorf("sentinel") + err := db.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error { + return txn.Iterate(ctx, keys.MetaMin, keys.MetaMax, 100, + func(rows []kv.KeyValue) error { + var desc roachpb.RangeDescriptor + for _, row := range rows { + err := row.ValueProto(&desc) + if err != nil { + return errors.Wrapf(err, "unable to unmarshal range descriptor from %s", row.Key) + } + // In small enough clusters it's possible for the same range + // descriptor to be stored in both meta1 and meta2. This + // happens when some range spans both the meta and the user + // keyspace. Consider when r1 is [/Min, + // /System/NodeLiveness); we'll store the range descriptor + // in both /Meta2/ and in /Meta1/KeyMax[1]. + // + // [1]: See kvserver.rangeAddressing. + if desc.RangeID == rangeID { + descriptor = desc + return sentinelErr + } + } + return nil + }) + }) + if errors.Is(err, sentinelErr) { + return descriptor, nil + } + if err != nil { + return roachpb.RangeDescriptor{}, err + } + return roachpb.RangeDescriptor{}, errors.Errorf("Descriptor for range %d is not found", rangeID) +} diff --git a/pkg/sql/sem/tree/alter_range.go b/pkg/sql/sem/tree/alter_range.go new file mode 100644 index 000000000000..938bfb91a496 --- /dev/null +++ b/pkg/sql/sem/tree/alter_range.go @@ -0,0 +1,43 @@ +// Copyright 2021 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package tree + +import "strconv" + +// RelocateRange represents an `ALTER RANGE .. RELOCATE ..` +// statement. +type RelocateRange struct { + RangeID int64 + ToStoreID int64 + FromStoreID int64 + RelocateLease bool + RelocateNonVoters bool +} + +// Format implements the NodeFormatter interface. +func (node *RelocateRange) Format(ctx *FmtCtx) { + ctx.WriteString("ALTER RANGE ") + ctx.WriteString(strconv.FormatInt(node.RangeID, 10)) + ctx.WriteString(" RELOCATE ") + if node.RelocateLease { + ctx.WriteString("LEASE ") + } else if node.RelocateNonVoters { + ctx.WriteString("NON_VOTERS ") + } else { + ctx.WriteString("VOTERS ") + } + if !node.RelocateLease { + ctx.WriteString("FROM ") + ctx.WriteString(strconv.FormatInt(node.FromStoreID, 10)) + } + ctx.WriteString("TO ") + ctx.WriteString(strconv.FormatInt(node.ToStoreID, 10)) +} diff --git a/pkg/sql/sem/tree/stmt.go b/pkg/sql/sem/tree/stmt.go index c1b85268f69e..83795bf105a0 100644 --- a/pkg/sql/sem/tree/stmt.go +++ b/pkg/sql/sem/tree/stmt.go @@ -977,6 +977,22 @@ func (n *Relocate) StatementTag() string { return "EXPERIMENTAL_RELOCATE VOTERS" } +// StatementReturnType implements the Statement interface. +func (*RelocateRange) StatementReturnType() StatementReturnType { return Rows } + +// StatementType implements the Statement interface. +func (*RelocateRange) StatementType() StatementType { return TypeDML } + +// StatementTag returns a short string identifying the type of statement. +func (n *RelocateRange) StatementTag() string { + if n.RelocateLease { + return "RELOCATE LEASE" + } else if n.RelocateNonVoters { + return "RELOCATE NON_VOTERS" + } + return "RELOCATE VOTERS" +} + // StatementReturnType implements the Statement interface. func (*ReplicationStream) StatementReturnType() StatementReturnType { return Rows } @@ -1709,6 +1725,7 @@ func (n *Prepare) String() string { return AsString(n) } func (n *ReassignOwnedBy) String() string { return AsString(n) } func (n *ReleaseSavepoint) String() string { return AsString(n) } func (n *Relocate) String() string { return AsString(n) } +func (n *RelocateRange) String() string { return AsString(n) } func (n *RefreshMaterializedView) String() string { return AsString(n) } func (n *RenameColumn) String() string { return AsString(n) } func (n *RenameDatabase) String() string { return AsString(n) } diff --git a/pkg/sql/walk.go b/pkg/sql/walk.go index 45ce835c184f..9ed634608c18 100644 --- a/pkg/sql/walk.go +++ b/pkg/sql/walk.go @@ -185,6 +185,8 @@ func (v *planVisitor) visitInternal(plan planNode, name string) { case *relocateNode: n.rows = v.visit(n.rows) + case *relocateRange: + case *insertNode, *insertFastPathNode: if ins, ok := n.(*insertNode); ok { ins.source = v.visit(ins.source) @@ -406,6 +408,7 @@ var planNodeNames = map[reflect.Type]string{ reflect.TypeOf(&recursiveCTENode{}): "recursive cte", reflect.TypeOf(&refreshMaterializedViewNode{}): "refresh materialized view", reflect.TypeOf(&relocateNode{}): "relocate", + reflect.TypeOf(&relocateRange{}): "relocate range", reflect.TypeOf(&renameColumnNode{}): "rename column", reflect.TypeOf(&renameDatabaseNode{}): "rename database", reflect.TypeOf(&renameIndexNode{}): "rename index",