diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index 30fe5e1e99ee..5412a4387075 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -968,6 +968,7 @@ unreserved_keyword ::= | 'NOCONTROLJOB' | 'NOLOGIN' | 'NOMODIFYCLUSTERSETTING' + | 'NON_VOTERS' | 'NOVIEWACTIVITY' | 'NOWAIT' | 'NULLS' diff --git a/pkg/ccl/kvccl/kvfollowerreadsccl/followerreads_test.go b/pkg/ccl/kvccl/kvfollowerreadsccl/followerreads_test.go index 4224f60736cf..0ee1190a5eaf 100644 --- a/pkg/ccl/kvccl/kvfollowerreadsccl/followerreads_test.go +++ b/pkg/ccl/kvccl/kvfollowerreadsccl/followerreads_test.go @@ -540,7 +540,8 @@ func TestFollowerReadsWithStaleDescriptor(t *testing.T) { n1 := sqlutils.MakeSQLRunner(tc.Conns[0]) n1.Exec(t, `CREATE DATABASE t`) n1.Exec(t, `CREATE TABLE test (k INT PRIMARY KEY)`) - n1.Exec(t, `ALTER TABLE test EXPERIMENTAL_RELOCATE VOTERS VALUES (ARRAY[1,2], 1)`) + n1.Exec(t, `ALTER TABLE test EXPERIMENTAL_RELOCATE VOTERS VALUES (ARRAY[1], 1)`) + n1.Exec(t, `ALTER TABLE test EXPERIMENTAL_RELOCATE NON_VOTERS VALUES (ARRAY[2], 1)`) // Speed up closing of timestamps, as we'll in order to be able to use // follower_read_timestamp(). // Every 0.2s we'll close the timestamp from 0.4s ago. We'll attempt follower reads @@ -570,11 +571,12 @@ func TestFollowerReadsWithStaleDescriptor(t *testing.T) { require.Equal(t, roachpb.StoreID(1), entry.Lease().Replica.StoreID) require.Equal(t, []roachpb.ReplicaDescriptor{ {NodeID: 1, StoreID: 1, ReplicaID: 1}, - {NodeID: 2, StoreID: 2, ReplicaID: 2}, + {NodeID: 2, StoreID: 2, ReplicaID: 2, Type: roachpb.ReplicaTypeNonVoter()}, }, entry.Desc().Replicas().Descriptors()) // Relocate the follower. n2 will no longer have a replica. n1.Exec(t, `ALTER TABLE test EXPERIMENTAL_RELOCATE VOTERS VALUES (ARRAY[1,3], 1)`) + n1.Exec(t, `ALTER TABLE test EXPERIMENTAL_RELOCATE NON_VOTERS VALUES (ARRAY[], 1)`) // Execute the query again and assert the cache is updated. This query will // not be executed as a follower read since it attempts to use n2 which diff --git a/pkg/sql/distsql_spec_exec_factory.go b/pkg/sql/distsql_spec_exec_factory.go index 36b4d52ece90..6140f821c794 100644 --- a/pkg/sql/distsql_spec_exec_factory.go +++ b/pkg/sql/distsql_spec_exec_factory.go @@ -952,7 +952,7 @@ func (e *distSQLSpecExecFactory) ConstructAlterTableUnsplitAll(index cat.Index) } func (e *distSQLSpecExecFactory) ConstructAlterTableRelocate( - index cat.Index, input exec.Node, relocateLease bool, + index cat.Index, input exec.Node, relocateLease bool, relocateNonVoters bool, ) (exec.Node, error) { return nil, unimplemented.NewWithIssue(47473, "experimental opt-driven distsql planning: alter table relocate") } diff --git a/pkg/sql/opt/exec/execbuilder/statement.go b/pkg/sql/opt/exec/execbuilder/statement.go index 7996807cc9e0..5220f3f0ed25 100644 --- a/pkg/sql/opt/exec/execbuilder/statement.go +++ b/pkg/sql/opt/exec/execbuilder/statement.go @@ -201,6 +201,7 @@ func (b *Builder) buildAlterTableRelocate(relocate *memo.AlterTableRelocateExpr) table.Index(relocate.Index), input.root, relocate.RelocateLease, + relocate.RelocateNonVoters, ) if err != nil { return execPlan{}, err diff --git a/pkg/sql/opt/exec/factory.opt b/pkg/sql/opt/exec/factory.opt index 1abade811b6b..e4cf0d55f349 100644 --- a/pkg/sql/opt/exec/factory.opt +++ b/pkg/sql/opt/exec/factory.opt @@ -609,6 +609,7 @@ define AlterTableRelocate { Index cat.Index input exec.Node relocateLease bool + relocateNonVoters bool } # Buffer passes through the input rows but also saves them in a buffer, which diff --git a/pkg/sql/opt/memo/expr_format.go b/pkg/sql/opt/memo/expr_format.go index 77078d856504..ed3258c18542 100644 --- a/pkg/sql/opt/memo/expr_format.go +++ b/pkg/sql/opt/memo/expr_format.go @@ -1427,6 +1427,10 @@ func FormatPrivate(f *ExprFmtCtx, private interface{}, physProps *physical.Requi FormatPrivate(f, &t.AlterTableSplitPrivate, nil) if t.RelocateLease { f.Buffer.WriteString(" [lease]") + } else if t.RelocateNonVoters { + f.Buffer.WriteString(" [non-voters]") + } else { + f.Buffer.WriteString(" [voters]") } case *ControlJobsPrivate: diff --git a/pkg/sql/opt/ops/statement.opt b/pkg/sql/opt/ops/statement.opt index eca590bed1cd..ebc74d8ea3b2 100644 --- a/pkg/sql/opt/ops/statement.opt +++ b/pkg/sql/opt/ops/statement.opt @@ -192,6 +192,7 @@ define AlterTableRelocate { [Private] define AlterTableRelocatePrivate { RelocateLease bool + RelocateNonVoters bool _ AlterTableSplitPrivate } diff --git a/pkg/sql/opt/optbuilder/alter_table.go b/pkg/sql/opt/optbuilder/alter_table.go index 73652bab5f88..b504be322001 100644 --- a/pkg/sql/opt/optbuilder/alter_table.go +++ b/pkg/sql/opt/optbuilder/alter_table.go @@ -61,7 +61,13 @@ func (b *Builder) buildAlterTableSplit(split *tree.Split, inScope *scope) (outSc b.semaCtx.Properties.Require(emptyScope.context.String(), tree.RejectSpecial) texpr := emptyScope.resolveType(split.ExpireExpr, types.String) - expiration = b.buildScalar(texpr, emptyScope, nil /* outScope */, nil /* outCol */, nil /* colRefs */) + expiration = b.buildScalar( + texpr, + emptyScope, + nil, /* outScope */ + nil, /* outCol */ + nil, /* colRefs */ + ) } else { expiration = b.factory.ConstructNull(types.String) } @@ -175,7 +181,8 @@ func (b *Builder) buildAlterTableRelocate( outScope.expr = b.factory.ConstructAlterTableRelocate( inputScope.expr.(memo.RelExpr), &memo.AlterTableRelocatePrivate{ - RelocateLease: relocate.RelocateLease, + RelocateLease: relocate.RelocateLease, + RelocateNonVoters: relocate.RelocateNonVoters, AlterTableSplitPrivate: memo.AlterTableSplitPrivate{ Table: b.factory.Metadata().AddTable(table, &tn), Index: index.Ordinal(), diff --git a/pkg/sql/opt/optbuilder/testdata/alter_table b/pkg/sql/opt/optbuilder/testdata/alter_table index e87a2ed4d776..67fbd9c5200f 100644 --- a/pkg/sql/opt/optbuilder/testdata/alter_table +++ b/pkg/sql/opt/optbuilder/testdata/alter_table @@ -129,7 +129,17 @@ alter-table-unsplit abc@bc build ALTER TABLE abc EXPERIMENTAL_RELOCATE VALUES (ARRAY[1,2,3], 1), (ARRAY[4], 2) ---- -alter-table-relocate abc +alter-table-relocate abc [voters] + ├── columns: key:1 pretty:2 + └── values + ├── columns: column1:3 column2:4!null + ├── (ARRAY[1,2,3], 1) + └── (ARRAY[4], 2) + +build +ALTER TABLE abc EXPERIMENTAL_RELOCATE NON_VOTERS VALUES (ARRAY[1,2,3], 1), (ARRAY[4], 2) +---- +alter-table-relocate abc [non-voters] ├── columns: key:1 pretty:2 └── values ├── columns: column1:3 column2:4!null @@ -149,7 +159,17 @@ error (42601): too many columns in EXPERIMENTAL_RELOCATE LEASE data build ALTER INDEX abc@bc EXPERIMENTAL_RELOCATE VALUES (ARRAY[5], 1, 'foo'), (ARRAY[6,7,8], 2, 'bar') ---- -alter-table-relocate abc@bc +alter-table-relocate abc@bc [voters] + ├── columns: key:1 pretty:2 + └── values + ├── columns: column1:3 column2:4!null column3:5!null + ├── (ARRAY[5], 1, 'foo') + └── (ARRAY[6,7,8], 2, 'bar') + +build +ALTER INDEX abc@bc EXPERIMENTAL_RELOCATE NON_VOTERS VALUES (ARRAY[5], 1, 'foo'), (ARRAY[6,7,8], 2, 'bar') +---- +alter-table-relocate abc@bc [non-voters] ├── columns: key:1 pretty:2 └── values ├── columns: column1:3 column2:4!null column3:5!null diff --git a/pkg/sql/opt_exec_factory.go b/pkg/sql/opt_exec_factory.go index 42f1afc6ea95..e3f7d040bb98 100644 --- a/pkg/sql/opt_exec_factory.go +++ b/pkg/sql/opt_exec_factory.go @@ -1825,17 +1825,18 @@ func (ef *execFactory) ConstructAlterTableUnsplitAll(index cat.Index) (exec.Node // ConstructAlterTableRelocate is part of the exec.Factory interface. func (ef *execFactory) ConstructAlterTableRelocate( - index cat.Index, input exec.Node, relocateLease bool, + index cat.Index, input exec.Node, relocateLease bool, relocateNonVoters bool, ) (exec.Node, error) { if !ef.planner.ExecCfg().Codec.ForSystemTenant() { return nil, errorutil.UnsupportedWithMultiTenancy(54250) } return &relocateNode{ - relocateLease: relocateLease, - tableDesc: index.Table().(*optTable).desc, - index: index.(*optIndex).desc, - rows: input.(planNode), + relocateLease: relocateLease, + relocateNonVoters: relocateNonVoters, + tableDesc: index.Table().(*optTable).desc, + index: index.(*optIndex).desc, + rows: input.(planNode), }, nil } diff --git a/pkg/sql/parser/parse_test.go b/pkg/sql/parser/parse_test.go index c2bbe1b7967f..d484fb0b60e0 100644 --- a/pkg/sql/parser/parse_test.go +++ b/pkg/sql/parser/parse_test.go @@ -1524,6 +1524,12 @@ func TestParse(t *testing.T) { {`ALTER TABLE d.a EXPERIMENTAL_RELOCATE VOTERS VALUES (ARRAY[1, 2, 3], 'b', 2)`}, {`ALTER INDEX d.i EXPERIMENTAL_RELOCATE VOTERS VALUES (ARRAY[1], 2)`}, + {`ALTER TABLE a EXPERIMENTAL_RELOCATE NON_VOTERS VALUES (ARRAY[1], 1)`}, + {`EXPLAIN ALTER TABLE a EXPERIMENTAL_RELOCATE NON_VOTERS TABLE b`}, + {`ALTER TABLE a EXPERIMENTAL_RELOCATE NON_VOTERS SELECT * FROM t`}, + {`ALTER TABLE d.a EXPERIMENTAL_RELOCATE NON_VOTERS VALUES (ARRAY[1, 2, 3], 'b', 2)`}, + {`ALTER INDEX d.i EXPERIMENTAL_RELOCATE NON_VOTERS VALUES (ARRAY[1], 2)`}, + {`ALTER TABLE a EXPERIMENTAL_RELOCATE LEASE VALUES (1, 1)`}, {`ALTER TABLE a EXPERIMENTAL_RELOCATE LEASE SELECT * FROM t`}, {`ALTER TABLE d.a EXPERIMENTAL_RELOCATE LEASE VALUES (1, 'b', 2)`}, diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index 5dc8ddc6387c..5104ad850967 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -666,7 +666,7 @@ func (u *sqlSymUnion) objectNamePrefixList() tree.ObjectNamePrefixList { %token NAN NAME NAMES NATURAL NEVER NEXT NO NOCANCELQUERY NOCONTROLCHANGEFEED NOCONTROLJOB %token NOCREATEDB NOCREATELOGIN NOCREATEROLE NOLOGIN NOMODIFYCLUSTERSETTING NO_INDEX_JOIN -%token NONE NORMAL NOT NOTHING NOTNULL NOVIEWACTIVITY NOWAIT NULL NULLIF NULLS NUMERIC +%token NONE NON_VOTERS NORMAL NOT NOTHING NOTNULL NOVIEWACTIVITY NOWAIT NULL NULLIF NULLS NUMERIC %token OF OFF OFFSET OID OIDS OIDVECTOR ON ONLY OPT OPTION OPTIONS OR %token ORDER ORDINALITY OTHERS OUT OUTER OVER OVERLAPS OVERLAY OWNED OWNER OPERATOR @@ -1698,6 +1698,16 @@ alter_relocate_stmt: Rows: $6.slct(), } } +| ALTER TABLE table_name relocate_kw NON_VOTERS select_stmt + { + /* SKIP DOC */ + name := $3.unresolvedObjectName().ToTableName() + $$.val = &tree.Relocate{ + TableOrIndex: tree.TableIndexName{Table: name}, + Rows: $6.slct(), + RelocateNonVoters: true, + } + } alter_relocate_index_stmt: ALTER INDEX table_index_name relocate_kw voters_kw select_stmt @@ -1705,6 +1715,11 @@ alter_relocate_index_stmt: /* SKIP DOC */ $$.val = &tree.Relocate{TableOrIndex: $3.tableIndexName(), Rows: $6.slct()} } +| ALTER INDEX table_index_name relocate_kw NON_VOTERS select_stmt + { + /* SKIP DOC */ + $$.val = &tree.Relocate{TableOrIndex: $3.tableIndexName(), Rows: $6.slct(), RelocateNonVoters: true} + } alter_relocate_lease_stmt: ALTER TABLE table_name relocate_kw LEASE select_stmt @@ -12457,6 +12472,7 @@ unreserved_keyword: | NOCONTROLJOB | NOLOGIN | NOMODIFYCLUSTERSETTING +| NON_VOTERS | NOVIEWACTIVITY | NOWAIT | NULLS diff --git a/pkg/sql/relocate.go b/pkg/sql/relocate.go index 5b2f8f30fbf6..630eb0547298 100644 --- a/pkg/sql/relocate.go +++ b/pkg/sql/relocate.go @@ -29,10 +29,11 @@ import ( type relocateNode struct { optColumnsSlot - relocateLease bool - tableDesc catalog.TableDescriptor - index *descpb.IndexDescriptor - rows planNode + relocateLease bool + relocateNonVoters bool + tableDesc catalog.TableDescriptor + index *descpb.IndexDescriptor + rows planNode run relocateRun } @@ -52,9 +53,6 @@ func (n *relocateNode) startExec(runParams) error { return nil } -// TODO(aayush): Extend EXPERIMENTAL_RELOCATE syntax to support relocating -// non-voting replicas. - func (n *relocateNode) Next(params runParams) (bool, error) { // Each Next call relocates one range (corresponding to one row from n.rows). // TODO(radu): perform multiple relocations in parallel. @@ -82,7 +80,8 @@ func (n *relocateNode) Next(params runParams) (bool, error) { ) } relocation := data[0].(*tree.DArray) - if len(relocation.Array) == 0 { + if !n.relocateNonVoters && len(relocation.Array) == 0 { + // We cannot remove all voters. return false, errors.Errorf("empty relocation array for EXPERIMENTAL_RELOCATE") } @@ -129,13 +128,21 @@ func (n *relocateNode) Next(params runParams) (bool, error) { } n.run.lastRangeStartKey = rangeDesc.StartKey.AsRawKey() + existingVoters := rangeDesc.Replicas().Voters().ReplicationTargets() + existingNonVoters := rangeDesc.Replicas().NonVoters().ReplicationTargets() if n.relocateLease { if err := params.p.ExecCfg().DB.AdminTransferLease(params.ctx, rowKey, leaseStoreID); err != nil { return false, err } + } else if n.relocateNonVoters { + if err := params.p.ExecCfg().DB.AdminRelocateRange( + params.ctx, rowKey, existingVoters, relocationTargets, + ); err != nil { + return false, err + } } else { if err := params.p.ExecCfg().DB.AdminRelocateRange( - params.ctx, rowKey, relocationTargets, rangeDesc.Replicas().NonVoters().ReplicationTargets(), + params.ctx, rowKey, relocationTargets, existingNonVoters, ); err != nil { return false, err } diff --git a/pkg/sql/sem/tree/split.go b/pkg/sql/sem/tree/split.go index 21f25219d02a..ace7a804a2d8 100644 --- a/pkg/sql/sem/tree/split.go +++ b/pkg/sql/sem/tree/split.go @@ -73,8 +73,9 @@ type Relocate struct { // Each row contains an array with store ids and values for the columns in the // PK or index (or a prefix of the columns). // See docs/RFCS/sql_split_syntax.md. - Rows *Select - RelocateLease bool + Rows *Select + RelocateLease bool + RelocateNonVoters bool } // Format implements the NodeFormatter interface. @@ -86,10 +87,13 @@ func (node *Relocate) Format(ctx *FmtCtx) { ctx.WriteString("TABLE ") } ctx.FormatNode(&node.TableOrIndex) + ctx.WriteString(" EXPERIMENTAL_RELOCATE ") if node.RelocateLease { - ctx.WriteString(" EXPERIMENTAL_RELOCATE LEASE ") + ctx.WriteString("LEASE ") + } else if node.RelocateNonVoters { + ctx.WriteString("NON_VOTERS ") } else { - ctx.WriteString(" EXPERIMENTAL_RELOCATE VOTERS ") + ctx.WriteString("VOTERS ") } ctx.FormatNode(node.Rows) } diff --git a/pkg/sql/sem/tree/stmt.go b/pkg/sql/sem/tree/stmt.go index 2eb2f0c1e302..b352e22e0e44 100644 --- a/pkg/sql/sem/tree/stmt.go +++ b/pkg/sql/sem/tree/stmt.go @@ -673,8 +673,10 @@ func (*Relocate) StatementType() StatementType { return Rows } func (n *Relocate) StatementTag() string { if n.RelocateLease { return "EXPERIMENTAL_RELOCATE LEASE" + } else if n.RelocateNonVoters { + return "EXPERIMENTAL_RELOCATE NON_VOTERS" } - return "EXPERIMENTAL_RELOCATE" + return "EXPERIMENTAL_RELOCATE VOTERS" } // StatementType implements the Statement interface.