diff --git a/pkg/sql/opt/bench/stub_factory.go b/pkg/sql/opt/bench/stub_factory.go index fb49f6eeddaa..70aef32ac837 100644 --- a/pkg/sql/opt/bench/stub_factory.go +++ b/pkg/sql/opt/bench/stub_factory.go @@ -305,3 +305,9 @@ func (f *stubFactory) ConstructAlterTableUnsplit( func (f *stubFactory) ConstructAlterTableUnsplitAll(index cat.Index) (exec.Node, error) { return struct{}{}, nil } + +func (f *stubFactory) ConstructAlterTableRelocate( + index cat.Index, input exec.Node, relocateLease bool, +) (exec.Node, error) { + return struct{}{}, nil +} diff --git a/pkg/sql/opt/exec/execbuilder/relational.go b/pkg/sql/opt/exec/execbuilder/relational.go index f88bd7b47b93..502a2ddc9a85 100644 --- a/pkg/sql/opt/exec/execbuilder/relational.go +++ b/pkg/sql/opt/exec/execbuilder/relational.go @@ -253,6 +253,9 @@ func (b *Builder) buildRelational(e memo.RelExpr) (execPlan, error) { case *memo.AlterTableUnsplitAllExpr: ep, err = b.buildAlterTableUnsplitAll(t) + case *memo.AlterTableRelocateExpr: + ep, err = b.buildAlterTableRelocate(t) + default: if opt.IsSetOp(e) { ep, err = b.buildSetOp(e) diff --git a/pkg/sql/opt/exec/execbuilder/statement.go b/pkg/sql/opt/exec/execbuilder/statement.go index b076affb1e71..2052be9ed241 100644 --- a/pkg/sql/opt/exec/execbuilder/statement.go +++ b/pkg/sql/opt/exec/execbuilder/statement.go @@ -176,3 +176,24 @@ func (b *Builder) buildAlterTableUnsplitAll( } return ep, nil } + +func (b *Builder) buildAlterTableRelocate(relocate *memo.AlterTableRelocateExpr) (execPlan, error) { + input, err := b.buildRelational(relocate.Input) + if err != nil { + return execPlan{}, err + } + table := b.mem.Metadata().Table(relocate.Table) + node, err := b.factory.ConstructAlterTableRelocate( + table.Index(relocate.Index), + input.root, + relocate.RelocateLease, + ) + if err != nil { + return execPlan{}, err + } + ep := execPlan{root: node} + for i, c := range relocate.Columns { + ep.outputCols.Set(int(c), i) + } + return ep, nil +} diff --git a/pkg/sql/opt/exec/factory.go b/pkg/sql/opt/exec/factory.go index 9676bb56b04b..f1f86c1b2360 100644 --- a/pkg/sql/opt/exec/factory.go +++ b/pkg/sql/opt/exec/factory.go @@ -416,6 +416,10 @@ type Factory interface { // ConstructAlterTableUnsplitAll creates a node that implements ALTER TABLE/INDEX // UNSPLIT ALL. ConstructAlterTableUnsplitAll(index cat.Index) (Node, error) + + // ConstructAlterTableRelocate creates a node that implements ALTER TABLE/INDEX + // UNSPLIT AT. + ConstructAlterTableRelocate(index cat.Index, input Node, relocateLease bool) (Node, error) } // OutputOrdering indicates the required output ordering on a Node that is being diff --git a/pkg/sql/opt/memo/expr_format.go b/pkg/sql/opt/memo/expr_format.go index 7cabac993a99..f182e4deb4f7 100644 --- a/pkg/sql/opt/memo/expr_format.go +++ b/pkg/sql/opt/memo/expr_format.go @@ -187,7 +187,8 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) { case *ScanExpr, *VirtualScanExpr, *IndexJoinExpr, *ShowTraceForSessionExpr, *InsertExpr, *UpdateExpr, *UpsertExpr, *DeleteExpr, *SequenceSelectExpr, - *WindowExpr, *OpaqueRelExpr: + *WindowExpr, *OpaqueRelExpr, *AlterTableSplitExpr, *AlterTableUnsplitExpr, + *AlterTableUnsplitAllExpr, *AlterTableRelocateExpr: fmt.Fprintf(f.Buffer, "%v", e.Op()) FormatPrivate(f, e.Private(), required) @@ -981,6 +982,12 @@ func FormatPrivate(f *ExprFmtCtx, private interface{}, physProps *physical.Requi fmt.Fprintf(f.Buffer, " %s@%s", tableAlias(f, t.Table), tab.Index(t.Index).Name()) } + case *AlterTableRelocatePrivate: + FormatPrivate(f, &t.AlterTableSplitPrivate, nil) + if t.RelocateLease { + f.Buffer.WriteString(" [lease]") + } + case *JoinPrivate: // Nothing to show; flags are shown separately. diff --git a/pkg/sql/opt/memo/logical_props_builder.go b/pkg/sql/opt/memo/logical_props_builder.go index 745de9ce28eb..c729ac2e98e0 100644 --- a/pkg/sql/opt/memo/logical_props_builder.go +++ b/pkg/sql/opt/memo/logical_props_builder.go @@ -755,6 +755,14 @@ func (b *logicalPropsBuilder) buildAlterTableUnsplitAllProps( rel.CanMutate = true } +func (b *logicalPropsBuilder) buildAlterTableRelocateProps( + relocate *AlterTableRelocateExpr, rel *props.Relational, +) { + b.buildBasicProps(relocate, relocate.Columns, rel) + rel.CanHaveSideEffects = true + rel.CanMutate = true +} + func (b *logicalPropsBuilder) buildLimitProps(limit *LimitExpr, rel *props.Relational) { BuildSharedProps(b.mem, limit, &rel.Shared) diff --git a/pkg/sql/opt/ops/statement.opt b/pkg/sql/opt/ops/statement.opt index dd9891db6596..4d43dbc5ab56 100644 --- a/pkg/sql/opt/ops/statement.opt +++ b/pkg/sql/opt/ops/statement.opt @@ -138,3 +138,20 @@ define AlterTableUnsplit { define AlterTableUnsplitAll { _ AlterTableSplitPrivate } + +# AlterTableRelocate represents an `ALTER TABLE/INDEX .. SPLIT AT ..` statement. +[Relational, DDL] +define AlterTableRelocate { + # The input expression provides values for the index columns (or a prefix of + # them). + Input RelExpr + + _ AlterTableRelocatePrivate +} + +[Private] +define AlterTableRelocatePrivate { + RelocateLease bool + + _ AlterTableSplitPrivate +} diff --git a/pkg/sql/opt/optbuilder/alter_table.go b/pkg/sql/opt/optbuilder/alter_table.go index 4ff1f99af517..e51824df9dc7 100644 --- a/pkg/sql/opt/optbuilder/alter_table.go +++ b/pkg/sql/opt/optbuilder/alter_table.go @@ -40,8 +40,8 @@ func (b *Builder) buildAlterTableSplit(split *tree.Split, inScope *scope) (outSc b.DisableMemoReuse = true - // Calculate the desired types for the select statement. It is OK if the - // select statement returns fewer columns (the relevant prefix is used). + // Calculate the desired types for the input expression. It is OK if it + // returns fewer columns (the relevant prefix is used). colNames, colTypes := getIndexColumnNamesAndTypes(index) // We don't allow the input statement to reference outer columns, so we @@ -112,8 +112,8 @@ func (b *Builder) buildAlterTableUnsplit(unsplit *tree.Unsplit, inScope *scope) return outScope } - // Calculate the desired types for the select statement. It is OK if the - // select statement returns fewer columns (the relevant prefix is used). + // Calculate the desired types for the input expression. It is OK if it + // returns fewer columns (the relevant prefix is used). colNames, colTypes := getIndexColumnNamesAndTypes(index) // We don't allow the input statement to reference outer columns, so we @@ -129,6 +129,64 @@ func (b *Builder) buildAlterTableUnsplit(unsplit *tree.Unsplit, inScope *scope) return outScope } +// buildAlterTableRelocate builds an ALTER TABLE/INDEX .. UNSPLIT AT/ALL .. statement. +func (b *Builder) buildAlterTableRelocate( + relocate *tree.Relocate, inScope *scope, +) (outScope *scope) { + flags := cat.Flags{ + AvoidDescriptorCaches: true, + NoTableStats: true, + } + index, err := cat.ResolveTableIndex(b.ctx, b.catalog, flags, &relocate.TableOrIndex) + if err != nil { + panic(builderError{err}) + } + table := index.Table() + if err := b.catalog.CheckPrivilege(b.ctx, table, privilege.INSERT); err != nil { + panic(builderError{err}) + } + + b.DisableMemoReuse = true + + outScope = inScope.push() + b.synthesizeResultColumns(outScope, sqlbase.AlterTableRelocateColumns) + + // Calculate the desired types for the input expression. It is OK if it + // returns fewer columns (the relevant prefix is used). + colNames, colTypes := getIndexColumnNamesAndTypes(index) + + // The first column is the target leaseholder or the relocation array, + // depending on variant. + cmdName := "EXPERIMENTAL_RELOCATE" + if relocate.RelocateLease { + cmdName += " LEASE" + colNames = append([]string{"target leaseholder"}, colNames...) + colTypes = append([]*types.T{types.Int}, colTypes...) + } else { + colNames = append([]string{"relocation array"}, colNames...) + colTypes = append([]*types.T{types.IntArray}, colTypes...) + } + + // We don't allow the input statement to reference outer columns, so we + // pass a "blank" scope rather than inScope. + inputScope := b.buildStmt(relocate.Rows, colTypes, &scope{builder: b}) + checkInputColumns(cmdName, inputScope, colNames, colTypes, 2) + + outScope.expr = b.factory.ConstructAlterTableRelocate( + inputScope.expr.(memo.RelExpr), + &memo.AlterTableRelocatePrivate{ + RelocateLease: relocate.RelocateLease, + AlterTableSplitPrivate: memo.AlterTableSplitPrivate{ + Table: b.factory.Metadata().AddTable(table), + Index: index.Ordinal(), + Columns: colsToColList(outScope.cols), + Props: inputScope.makePhysicalProps(), + }, + }, + ) + return outScope +} + // getIndexColumnNamesAndTypes returns the names and types of the index columns. func getIndexColumnNamesAndTypes(index cat.Index) (colNames []string, colTypes []*types.T) { colNames = make([]string, index.LaxKeyColumnCount()) @@ -151,7 +209,7 @@ func checkInputColumns( if len(inputScope.cols) == 0 { panic(pgerror.Newf(pgcode.Syntax, "no columns in %s data", context)) } - panic(pgerror.Newf(pgcode.Syntax, "too few columns in %s data", context)) + panic(pgerror.Newf(pgcode.Syntax, "less than %d columns in %s data", minPrefix, context)) } if len(inputScope.cols) > len(colTypes) { panic(pgerror.Newf(pgcode.Syntax, "too many columns in %s data", context)) diff --git a/pkg/sql/opt/optbuilder/builder.go b/pkg/sql/opt/optbuilder/builder.go index dc3cf28c57af..1762488b877d 100644 --- a/pkg/sql/opt/optbuilder/builder.go +++ b/pkg/sql/opt/optbuilder/builder.go @@ -243,6 +243,9 @@ func (b *Builder) buildStmt( case *tree.Unsplit: return b.buildAlterTableUnsplit(stmt, inScope) + case *tree.Relocate: + return b.buildAlterTableRelocate(stmt, inScope) + default: // See if this statement can be rewritten to another statement using the // delegate functionality. diff --git a/pkg/sql/opt/optbuilder/testdata/alter_table b/pkg/sql/opt/optbuilder/testdata/alter_table index 2a5874228d1f..3e3188576d82 100644 --- a/pkg/sql/opt/optbuilder/testdata/alter_table +++ b/pkg/sql/opt/optbuilder/testdata/alter_table @@ -2,10 +2,11 @@ exec-ddl CREATE TABLE abc (a INT PRIMARY KEY, b INT, c STRING, INDEX b (b), UNIQUE INDEX bc (b,c)) ---- +# Tests for ALTER TABLE SPLIT AT. build ALTER TABLE abc SPLIT AT VALUES (1), (2) ---- -alter-table-split +alter-table-split t.public.abc ├── columns: key:2(bytes) pretty:3(string) split_enforced_until:4(timestamp) ├── values │ ├── columns: column1:1(int!null) @@ -18,7 +19,7 @@ alter-table-split build ALTER TABLE abc SPLIT AT VALUES (1), (2) WITH EXPIRATION '2200-01-01 00:00:00.0' ---- -alter-table-split +alter-table-split t.public.abc ├── columns: key:2(bytes) pretty:3(string) split_enforced_until:4(timestamp) ├── values │ ├── columns: column1:1(int!null) @@ -36,7 +37,7 @@ error (42601): too many columns in SPLIT AT data build ALTER INDEX abc@bc SPLIT AT VALUES (1), (2) WITH EXPIRATION '2200-01-01 00:00:00.0' ---- -alter-table-split +alter-table-split t.public.abc@bc ├── columns: key:2(bytes) pretty:3(string) split_enforced_until:4(timestamp) ├── values │ ├── columns: column1:1(int!null) @@ -49,7 +50,7 @@ alter-table-split build ALTER INDEX abc@bc SPLIT AT VALUES (1, 'foo'), (2, 'bar') ---- -alter-table-split +alter-table-split t.public.abc@bc ├── columns: key:3(bytes) pretty:4(string) split_enforced_until:5(timestamp) ├── values │ ├── columns: column1:1(int!null) column2:2(string!null) @@ -69,7 +70,7 @@ error (42601): SPLIT AT data column 2 (c) must be of type string, not type int build ALTER INDEX abc@bc SPLIT AT SELECT b FROM abc ORDER BY a ---- -alter-table-split +alter-table-split t.public.abc@bc ├── columns: key:4(bytes) pretty:5(string) split_enforced_until:6(timestamp) ├── project │ ├── columns: b:2(int) [hidden: abc.a:1(int!null)] @@ -79,10 +80,11 @@ alter-table-split │ └── ordering: +1 └── null [type=string] +# Tests for ALTER TABLE UNSPLIT. build ALTER TABLE abc UNSPLIT AT VALUES (1), (2) ---- -alter-table-unsplit +alter-table-unsplit t.public.abc ├── columns: key:1(bytes) pretty:2(string) └── values ├── columns: column1:6(int!null) @@ -94,7 +96,7 @@ alter-table-unsplit build ALTER TABLE abc UNSPLIT ALL ---- -alter-table-unsplit-all +alter-table-unsplit-all t.public.abc └── columns: key:1(bytes) pretty:2(string) build @@ -105,13 +107,13 @@ error (42601): too many columns in UNSPLIT AT data build ALTER INDEX abc@bc UNSPLIT ALL ---- -alter-table-unsplit-all +alter-table-unsplit-all t.public.abc@bc └── columns: key:1(bytes) pretty:2(string) build ALTER INDEX abc@bc UNSPLIT AT VALUES (1, 'foo'), (2, 'bar') ---- -alter-table-unsplit +alter-table-unsplit t.public.abc@bc ├── columns: key:1(bytes) pretty:2(string) └── values ├── columns: column1:6(int!null) column2:7(string!null) @@ -130,7 +132,7 @@ error (42601): UNSPLIT AT data column 2 (c) must be of type string, not type int build ALTER INDEX abc@bc UNSPLIT AT SELECT b FROM abc ORDER BY a ---- -alter-table-unsplit +alter-table-unsplit t.public.abc@bc ├── columns: key:1(bytes) pretty:2(string) └── project ├── columns: b:7(int) [hidden: abc.a:6(int!null)] @@ -138,3 +140,88 @@ alter-table-unsplit └── scan abc ├── columns: abc.a:6(int!null) abc.b:7(int) abc.c:8(string) └── ordering: +6 + +# Tests for ALTER TABLE EXPERIMENTAL_RELOCATE. +build +ALTER TABLE abc EXPERIMENTAL_RELOCATE VALUES (ARRAY[1,2,3], 1), (ARRAY[4], 2) +---- +alter-table-relocate t.public.abc + ├── columns: key:1(bytes) pretty:2(string) + └── values + ├── columns: column1:3(int[]) column2:4(int!null) + ├── tuple [type=tuple{int[], int}] + │ ├── array: [type=int[]] + │ │ ├── const: 1 [type=int] + │ │ ├── const: 2 [type=int] + │ │ └── const: 3 [type=int] + │ └── const: 1 [type=int] + └── tuple [type=tuple{int[], int}] + ├── array: [type=int[]] + │ └── const: 4 [type=int] + └── const: 2 [type=int] + +build +ALTER TABLE abc EXPERIMENTAL_RELOCATE LEASE VALUES (10), (11) +---- +error (42601): less than 2 columns in EXPERIMENTAL_RELOCATE LEASE data + +build +ALTER TABLE abc EXPERIMENTAL_RELOCATE LEASE VALUES (10, 1, 2), (11, 3, 4) +---- +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 t.public.abc@bc + ├── columns: key:1(bytes) pretty:2(string) + └── values + ├── columns: column1:3(int[]) column2:4(int!null) column3:5(string!null) + ├── tuple [type=tuple{int[], int, string}] + │ ├── array: [type=int[]] + │ │ └── const: 5 [type=int] + │ ├── const: 1 [type=int] + │ └── const: 'foo' [type=string] + └── tuple [type=tuple{int[], int, string}] + ├── array: [type=int[]] + │ ├── const: 6 [type=int] + │ ├── const: 7 [type=int] + │ └── const: 8 [type=int] + ├── const: 2 [type=int] + └── const: 'bar' [type=string] + +build +ALTER INDEX abc@bc EXPERIMENTAL_RELOCATE VALUES (5, 1, 'foo'), (6, 2, 'bar') +---- +error (42601): EXPERIMENTAL_RELOCATE data column 1 (relocation array) must be of type int[], not type int + +build +ALTER INDEX abc@bc EXPERIMENTAL_RELOCATE LEASE VALUES (ARRAY[5], 1, 'foo'), (ARRAY[6,7,8], 2, 'bar') +---- +error (42601): EXPERIMENTAL_RELOCATE LEASE data column 1 (target leaseholder) must be of type int, not type int[] + +build +ALTER INDEX abc@bc EXPERIMENTAL_RELOCATE VALUES (1, 2), (3, 4) +---- +error (42601): EXPERIMENTAL_RELOCATE data column 1 (relocation array) must be of type int[], not type int + +build +ALTER INDEX abc@bc EXPERIMENTAL_RELOCATE VALUES (ARRAY[1,2], 1, 2), (ARRAY[3,4], 3, 4) +---- +error (42601): EXPERIMENTAL_RELOCATE data column 3 (c) must be of type string, not type int + +build +ALTER INDEX abc@bc EXPERIMENTAL_RELOCATE LEASE VALUES (10, 1, 'foo'), (11, 3, 'bar') +---- +alter-table-relocate t.public.abc@bc [lease] + ├── columns: key:1(bytes) pretty:2(string) + └── values + ├── columns: column1:3(int!null) column2:4(int!null) column3:5(string!null) + ├── tuple [type=tuple{int, int, string}] + │ ├── const: 10 [type=int] + │ ├── const: 1 [type=int] + │ └── const: 'foo' [type=string] + └── tuple [type=tuple{int, int, string}] + ├── const: 11 [type=int] + ├── const: 3 [type=int] + └── const: 'bar' [type=string] diff --git a/pkg/sql/opt/ordering/ordering.go b/pkg/sql/opt/ordering/ordering.go index 0fa84c47ec61..b1e6b3449d53 100644 --- a/pkg/sql/opt/ordering/ordering.go +++ b/pkg/sql/opt/ordering/ordering.go @@ -197,6 +197,11 @@ func init() { buildChildReqOrdering: alterTableUnsplitBuildChildReqOrdering, buildProvidedOrdering: noProvidedOrdering, } + funcMap[opt.AlterTableRelocateOp] = funcs{ + canProvideOrdering: canNeverProvideOrdering, + buildChildReqOrdering: alterTableRelocateBuildChildReqOrdering, + buildProvidedOrdering: noProvidedOrdering, + } } func canNeverProvideOrdering(expr memo.RelExpr, required *physical.OrderingChoice) bool { diff --git a/pkg/sql/opt/ordering/statement.go b/pkg/sql/opt/ordering/statement.go index eb7fa62d9ab7..aab2611ca949 100644 --- a/pkg/sql/opt/ordering/statement.go +++ b/pkg/sql/opt/ordering/statement.go @@ -38,3 +38,12 @@ func alterTableUnsplitBuildChildReqOrdering( } return parent.(*memo.AlterTableUnsplitExpr).Props.Ordering } + +func alterTableRelocateBuildChildReqOrdering( + parent memo.RelExpr, required *physical.OrderingChoice, childIdx int, +) physical.OrderingChoice { + if childIdx != 0 { + return physical.OrderingChoice{} + } + return parent.(*memo.AlterTableRelocateExpr).Props.Ordering +} diff --git a/pkg/sql/opt/xform/physical_props.go b/pkg/sql/opt/xform/physical_props.go index e2f506e4b22f..4e135d63e60e 100644 --- a/pkg/sql/opt/xform/physical_props.go +++ b/pkg/sql/opt/xform/physical_props.go @@ -55,6 +55,8 @@ func BuildChildPhysicalProps( childProps.Presentation = parent.(*memo.AlterTableSplitExpr).Props.Presentation case opt.AlterTableUnsplitOp: childProps.Presentation = parent.(*memo.AlterTableUnsplitExpr).Props.Presentation + case opt.AlterTableRelocateOp: + childProps.Presentation = parent.(*memo.AlterTableRelocateExpr).Props.Presentation } childProps.Ordering = ordering.BuildChildRequired(parent, &parentProps.Ordering, nth) diff --git a/pkg/sql/opt_exec_factory.go b/pkg/sql/opt_exec_factory.go index 3515b5b74c6e..92ca196dd79b 100644 --- a/pkg/sql/opt_exec_factory.go +++ b/pkg/sql/opt_exec_factory.go @@ -1629,6 +1629,18 @@ func (ef *execFactory) ConstructAlterTableUnsplitAll(index cat.Index) (exec.Node }, nil } +// ConstructAlterTableRelocate is part of the exec.Factory interface. +func (ef *execFactory) ConstructAlterTableRelocate( + index cat.Index, input exec.Node, relocateLease bool, +) (exec.Node, error) { + return &relocateNode{ + relocateLease: relocateLease, + tableDesc: &index.Table().(*optTable).desc.TableDescriptor, + index: index.(*optIndex).desc, + rows: input.(planNode), + }, nil +} + // renderBuilder encapsulates the code to build a renderNode. type renderBuilder struct { r *renderNode diff --git a/pkg/sql/relocate.go b/pkg/sql/relocate.go index 562ca5b98957..d8c3140fd055 100644 --- a/pkg/sql/relocate.go +++ b/pkg/sql/relocate.go @@ -76,7 +76,7 @@ func (p *planner) Relocate(ctx context.Context, n *tree.Relocate) (planNode, err } cols := planColumns(rows) if len(cols) < 2 { - return nil, errors.Errorf("less than two columns in %s data", cmdName) + return nil, errors.Errorf("less than 2 columns in %s data", cmdName) } if len(cols) > len(index.ColumnIDs)+1 { return nil, errors.Errorf("too many columns in %s data", cmdName) @@ -102,9 +102,6 @@ func (p *planner) Relocate(ctx context.Context, n *tree.Relocate) (planNode, err tableDesc: tableDesc.TableDesc(), index: index, rows: rows, - run: relocateRun{ - storeMap: make(map[roachpb.StoreID]roachpb.NodeID), - }, }, nil } @@ -119,6 +116,7 @@ type relocateRun struct { } func (n *relocateNode) startExec(runParams) error { + n.run.storeMap = make(map[roachpb.StoreID]roachpb.NodeID) return nil }