From 5add46733c791c021bbc78eeeb6f13d2b8132885 Mon Sep 17 00:00:00 2001 From: Andrew Kimball Date: Mon, 28 Jan 2019 10:30:22 -0800 Subject: [PATCH] opt: Prune Delete operator input columns Prune input columns that are not needed by a Delete operator. Needed columns include returned columns and index key columns. All other columns can be pruned. Pruning Delete columns causes new interactions with the Delete "fast path" that uses range delete. When multiple column families are in use, the range delete needs to cover all column families. The "forDelete" flag is used to construct spans that cover all columns, rather than just "needed" columns. A new ConstructDeleteRange exec factory function sets this flag correctly. After this change, optimizer deletes no longer need to stay behind a feature flag, as all fast paths should now work at least as well as they do with the heuristic planner. Release note: None --- pkg/sql/delete.go | 8 ++ pkg/sql/logictest/testdata/logic_test/delete | 39 +++++- .../logictest/testdata/logic_test/optimizer | 6 - pkg/sql/opt/bench/stub_factory.go | 18 ++- pkg/sql/opt/cat/table.go | 8 ++ .../exec/execbuilder/relational_builder.go | 69 ++++++++- pkg/sql/opt/exec/execbuilder/testdata/delete | 35 +++-- pkg/sql/opt/exec/factory.go | 11 ++ pkg/sql/opt/memo/expr_format.go | 4 +- pkg/sql/opt/norm/prune_cols.go | 58 ++++++++ pkg/sql/opt/norm/rules/prune_cols.opt | 15 ++ pkg/sql/opt/norm/testdata/rules/decorrelate | 8 +- pkg/sql/opt/norm/testdata/rules/prune_cols | 132 ++++++++++++++++++ pkg/sql/opt/optbuilder/delete.go | 4 - pkg/sql/opt/table_meta.go | 24 +++- pkg/sql/opt/testutils/testcat/create_table.go | 7 + pkg/sql/opt/testutils/testcat/test_catalog.go | 18 +++ pkg/sql/opt/xform/testdata/physprops/ordering | 4 +- pkg/sql/opt_catalog.go | 15 ++ pkg/sql/opt_exec_factory.go | 57 +++++++- 20 files changed, 493 insertions(+), 47 deletions(-) diff --git a/pkg/sql/delete.go b/pkg/sql/delete.go index 4b5aad9e4106..19c53006513c 100644 --- a/pkg/sql/delete.go +++ b/pkg/sql/delete.go @@ -463,6 +463,9 @@ func canDeleteFastInterleaved(table *ImmutableTableDescriptor, fkTables row.FkTa // canDeleteFast determines if the deletion of `rows` can be done // without actually scanning them. // This should be called after plan simplification for optimal results. +// +// This logic should be kept in sync with exec.Builder.canUseDeleteRange. +// TODO(andyk): Remove when the heuristic planner code is removed. func canDeleteFast(ctx context.Context, source planNode, r *deleteRun) (*scanNode, bool) { // Check that there are no secondary indexes, interleaving, FK // references checks, etc., ie. there is no extra work to be done @@ -505,6 +508,11 @@ func canDeleteFast(ctx context.Context, source planNode, r *deleteRun) (*scanNod return nil, false } + // Delete range does not support limits. + if scan.hardLimit != 0 { + return nil, false + } + return scan, true } diff --git a/pkg/sql/logictest/testdata/logic_test/delete b/pkg/sql/logictest/testdata/logic_test/delete index fe98b59e5f92..a0a790776d97 100644 --- a/pkg/sql/logictest/testdata/logic_test/delete +++ b/pkg/sql/logictest/testdata/logic_test/delete @@ -124,16 +124,25 @@ k v 5 6 7 8 +statement count 4 +DELETE FROM unindexed + +# Delete of range with limit. +statement count 4 +INSERT INTO unindexed VALUES (1, 2), (3, 4), (5, 6), (7, 8) + +statement count 1 +DELETE FROM unindexed WHERE k >= 4 ORDER BY k LIMIT 1 + query II colnames,rowsort SELECT k, v FROM unindexed ---- k v 1 2 3 4 -5 6 7 8 -statement count 4 +statement count 3 DELETE FROM unindexed query II colnames @@ -241,3 +250,29 @@ query II DELETE FROM t33361 RETURNING *; COMMIT ---- 1 3 + +# Test that delete works with column families (no indexes, so fast path). +statement ok +CREATE TABLE family ( + x INT PRIMARY KEY, + y INT, + FAMILY (x), + FAMILY (y) +); +INSERT INTO family VALUES (1, 1), (2, 2), (3, 3) + +statement ok +BEGIN; ALTER TABLE family ADD COLUMN z INT CREATE FAMILY + +# Check that the new column is not usable in RETURNING +statement ok +DELETE FROM family WHERE x=2 + +statement ok +COMMIT + +query III rowsort +SELECT x, y, z FROM family +---- +1 1 NULL +3 3 NULL diff --git a/pkg/sql/logictest/testdata/logic_test/optimizer b/pkg/sql/logictest/testdata/logic_test/optimizer index 95f4301568e7..b70a7576d727 100644 --- a/pkg/sql/logictest/testdata/logic_test/optimizer +++ b/pkg/sql/logictest/testdata/logic_test/optimizer @@ -123,14 +123,8 @@ SET experimental_optimizer_mutations = false statement error pq: no data source matches prefix: t UPDATE t SET v=(SELECT v+1 FROM t AS t2 WHERE t2.k=t.k) -statement error pq: no data source matches prefix: t -DELETE FROM t WHERE EXISTS(SELECT * FROM t AS t2 WHERE t2.k=t.k) - statement ok SET experimental_optimizer_mutations = true statement ok UPDATE t SET v=(SELECT v+1 FROM t AS t2 WHERE t2.k=t.k) - -statement ok -DELETE FROM t WHERE EXISTS(SELECT * FROM t AS t2 WHERE t2.k=t.k) diff --git a/pkg/sql/opt/bench/stub_factory.go b/pkg/sql/opt/bench/stub_factory.go index 0045e9a7e53d..95974b9702b2 100644 --- a/pkg/sql/opt/bench/stub_factory.go +++ b/pkg/sql/opt/bench/stub_factory.go @@ -214,12 +214,6 @@ func (f *stubFactory) ConstructUpdate( return struct{}{}, nil } -func (f *stubFactory) ConstructCreateTable( - input exec.Node, schema cat.Schema, ct *tree.CreateTable, -) (exec.Node, error) { - return struct{}{}, nil -} - func (f *stubFactory) ConstructUpsert( input exec.Node, table cat.Table, @@ -239,6 +233,18 @@ func (f *stubFactory) ConstructDelete( return struct{}{}, nil } +func (f *stubFactory) ConstructDeleteRange( + table cat.Table, needed exec.ColumnOrdinalSet, indexConstraint *constraint.Constraint, +) (exec.Node, error) { + return struct{}{}, nil +} + +func (f *stubFactory) ConstructCreateTable( + input exec.Node, schema cat.Schema, ct *tree.CreateTable, +) (exec.Node, error) { + return struct{}{}, nil +} + func (f *stubFactory) ConstructSequenceSelect(seq cat.Sequence) (exec.Node, error) { return struct{}{}, nil } diff --git a/pkg/sql/opt/cat/table.go b/pkg/sql/opt/cat/table.go index ac0b2e87934e..a3150ec01ebb 100644 --- a/pkg/sql/opt/cat/table.go +++ b/pkg/sql/opt/cat/table.go @@ -34,6 +34,14 @@ type Table interface { // information_schema tables. IsVirtualTable() bool + // IsInterleaved returns true if any of this table's indexes are interleaved + // with index(es) from other table(s). + IsInterleaved() bool + + // IsReferenced returns true if this table is referenced by at least one + // foreign key defined on another table (or this one if self-referential). + IsReferenced() bool + // ColumnCount returns the number of public columns in the table. Public // columns are not currently being added or dropped from the table. This // method should be used when mutation columns can be ignored (the common diff --git a/pkg/sql/opt/exec/execbuilder/relational_builder.go b/pkg/sql/opt/exec/execbuilder/relational_builder.go index d370b12d0c0a..9af2f6a079ad 100644 --- a/pkg/sql/opt/exec/execbuilder/relational_builder.go +++ b/pkg/sql/opt/exec/execbuilder/relational_builder.go @@ -1298,6 +1298,11 @@ func (b *Builder) buildUpsert(ups *memo.UpsertExpr) (execPlan, error) { } func (b *Builder) buildDelete(del *memo.DeleteExpr) (execPlan, error) { + // Check for the fast-path delete case that can use a range delete. + if b.canUseDeleteRange(del) { + return b.buildDeleteRange(del) + } + // Build the input query and ensure that the fetch columns are projected. input, err := b.buildRelational(del.Input) if err != nil { @@ -1308,7 +1313,9 @@ func (b *Builder) buildDelete(del *memo.DeleteExpr) (execPlan, error) { // // TODO(andyk): Using ensureColumns here can result in an extra Render. // Upgrade execution engine to not require this. - input, err = b.ensureColumns(input, del.FetchCols, nil, del.ProvidedPhysical().Ordering) + colList := make(opt.ColList, 0, len(del.FetchCols)) + colList = appendColsWhenPresent(colList, del.FetchCols) + input, err = b.ensureColumns(input, colList, nil, del.ProvidedPhysical().Ordering) if err != nil { return execPlan{}, err } @@ -1330,6 +1337,58 @@ func (b *Builder) buildDelete(del *memo.DeleteExpr) (execPlan, error) { return ep, nil } +// canUseDeleteRange checks whether a logical Delete operator can be implemented +// by a fast delete range execution operator. This logic should be kept in sync +// with canDeleteFast. +func (b *Builder) canUseDeleteRange(del *memo.DeleteExpr) bool { + // If rows need to be returned from the Delete operator (i.e. RETURNING + // clause), no fast path is possible, because row values must be fetched. + if del.NeedResults { + return false + } + + tab := b.mem.Metadata().Table(del.Table) + if tab.DeletableIndexCount() > 1 { + // Any secondary index prevents fast path, because separate delete batches + // must be formulated to delete rows from them. + return false + } + if tab.IsInterleaved() { + // There is a separate fast path for interleaved tables in sql/delete.go. + return false + } + if tab.IsReferenced() { + // If the table is referenced by other tables' foreign keys, no fast path + // is possible, because the integrity of those references must be checked. + return false + } + + // Check for simple Scan input operator without a limit; anything else is not + // supported by a range delete. + if scan, ok := del.Input.(*memo.ScanExpr); !ok || scan.HardLimit != 0 { + return false + } + + return true +} + +// buildDeleteRange constructs a DeleteRange operator that deletes contiguous +// rows in the primary index. canUseDeleteRange should have already been called. +func (b *Builder) buildDeleteRange(del *memo.DeleteExpr) (execPlan, error) { + // canUseDeleteRange has already validated that input is a Scan operator. + scan := del.Input.(*memo.ScanExpr) + tab := b.mem.Metadata().Table(scan.Table) + needed, output := b.getColumns(scan.Cols, scan.Table) + res := execPlan{outputCols: output} + + root, err := b.factory.ConstructDeleteRange(tab, needed, scan.Constraint) + if err != nil { + return execPlan{}, err + } + res.root = root + return res, nil +} + func (b *Builder) buildCreateTable(ct *memo.CreateTableExpr) (execPlan, error) { var root exec.Node if ct.Syntax.As() { @@ -1385,9 +1444,11 @@ func (b *Builder) needProjection( return nil, false } } - cols := make([]exec.ColumnOrdinal, len(colList)) - for i, col := range colList { - cols[i] = input.getColumnOrdinal(col) + cols := make([]exec.ColumnOrdinal, 0, len(colList)) + for _, col := range colList { + if col != 0 { + cols = append(cols, input.getColumnOrdinal(col)) + } } return cols, true } diff --git a/pkg/sql/opt/exec/execbuilder/testdata/delete b/pkg/sql/opt/exec/execbuilder/testdata/delete index 9e4c4392a6c7..b4bd9e0b723b 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/delete +++ b/pkg/sql/opt/exec/execbuilder/testdata/delete @@ -95,20 +95,31 @@ count · · · spans ALL · filter v = 5 -# TODO(andyk): Prune columns so that index-join is not necessary. +# Check DELETE with LIMIT clause that gets pushed into scan. +# The fast deleter should not be used, since it can't handle LIMIT. +query TTT +EXPLAIN DELETE FROM unindexed WHERE k > 5 LIMIT 10 +---- +count · · + └── delete · · + │ from unindexed + │ strategy deleter + └── scan · · +· table unindexed@primary +· spans /6- +· limit 10 + query TTT EXPLAIN DELETE FROM indexed WHERE value = 5 LIMIT 10 ---- -count · · - └── delete · · - │ from indexed - │ strategy deleter - └── index-join · · - │ table indexed@primary - └── scan · · -· table indexed@indexed_value_idx -· spans /5-/6 -· limit 10 +count · · + └── delete · · + │ from indexed + │ strategy deleter + └── scan · · +· table indexed@indexed_value_idx +· spans /5-/6 +· limit 10 query TTT EXPLAIN DELETE FROM indexed LIMIT 10 @@ -118,7 +129,7 @@ count · · │ from indexed │ strategy deleter └── scan · · -· table indexed@primary +· table indexed@indexed_value_idx · spans ALL · limit 10 diff --git a/pkg/sql/opt/exec/factory.go b/pkg/sql/opt/exec/factory.go index 699f59ee9c29..76d387d5ef0c 100644 --- a/pkg/sql/opt/exec/factory.go +++ b/pkg/sql/opt/exec/factory.go @@ -319,6 +319,17 @@ type Factory interface { input Node, table cat.Table, fetchCols ColumnOrdinalSet, rowsNeeded bool, ) (Node, error) + // ConstructDeleteRange creates a node that efficiently deletes contiguous + // rows stored in the given table's primary index. This fast path is only + // possible when certain conditions hold true (see canUseDeleteRange for more + // details). See the comment for ConstructScan for descriptions of the + // parameters, since FastDelete combines Delete + Scan into a single operator. + ConstructDeleteRange( + table cat.Table, + needed ColumnOrdinalSet, + indexConstraint *constraint.Constraint, + ) (Node, error) + // ConstructCreateTable returns a node that implements a CREATE TABLE // statement. ConstructCreateTable(input Node, schema cat.Schema, ct *tree.CreateTable) (Node, error) diff --git a/pkg/sql/opt/memo/expr_format.go b/pkg/sql/opt/memo/expr_format.go index 35872ab2b166..98677056327c 100644 --- a/pkg/sql/opt/memo/expr_format.go +++ b/pkg/sql/opt/memo/expr_format.go @@ -655,7 +655,9 @@ func (f *ExprFmtCtx) formatColList( f.Buffer.Reset() f.Buffer.WriteString(heading) for _, col := range colList { - formatCol(f, "" /* label */, col, notNullCols, false /* omitType */) + if col != 0 { + formatCol(f, "" /* label */, col, notNullCols, false /* omitType */) + } } tp.Child(f.Buffer.String()) } diff --git a/pkg/sql/opt/norm/prune_cols.go b/pkg/sql/opt/norm/prune_cols.go index 58bdd1c59bfd..d3df71d8702c 100644 --- a/pkg/sql/opt/norm/prune_cols.go +++ b/pkg/sql/opt/norm/prune_cols.go @@ -40,6 +40,30 @@ func (c *CustomFuncs) NeededColsExplain(private *memo.ExplainPrivate) opt.ColSet return private.Props.ColSet() } +// NeededColsMutation returns the columns needed by a mutation operator. +func (c *CustomFuncs) NeededColsMutation(private *memo.MutationPrivate) opt.ColSet { + var cols opt.ColSet + tabMeta := c.mem.Metadata().TableMeta(private.Table) + + // If the operator returns results, then include all non-mutation columns in + // the table. + // TODO(andyk): The returned columns need to be pruned as well. + if private.NeedResults { + cols = tabMeta.Columns() + } + + // Add in all strict key columns from all indexes, including mutation indexes, + // since it is necessary to delete rows even from indexes that are being + // added/dropped. + for i, n := 0, tabMeta.Table.DeletableIndexCount(); i < n; i++ { + cols.UnionWith(tabMeta.IndexKeyColumns(i)) + } + + // Map to mutation input columns. + cols = private.MapToInputCols(cols) + return cols +} + // CanPruneCols returns true if the target expression has extra columns that are // not needed at this level of the tree, and can be eliminated by one of the // PruneCols rules. CanPruneCols uses the PruneCols property to determine the @@ -109,6 +133,40 @@ func (c *CustomFuncs) PruneAggCols( return aggs } +// PruneMutationCols rewrites the given mutation private to no longer reference +// InsertCols, FetchCols, UpdateCols, or CheckCols that are not part of the +// neededCols set. The caller must have already done the analysis to prove that +// these columns are not needed. +// TODO(andyk): Add support for pruning column lists other than FetchCols. +func (c *CustomFuncs) PruneMutationCols( + private *memo.MutationPrivate, neededCols opt.ColSet, +) *memo.MutationPrivate { + newPrivate := *private + newPrivate.FetchCols = filterMutationList(newPrivate.FetchCols, neededCols) + return &newPrivate +} + +// filterMutationList filters the given mutation list by setting any columns +// that are not in the neededCols set to zero. This indicates that those columns +// are not present in the input. +func filterMutationList(inList opt.ColList, neededCols opt.ColSet) opt.ColList { + var newList opt.ColList + for i, c := range inList { + if !neededCols.Contains(int(c)) { + // Copy-on-write the list for efficiency. + if newList == nil { + newList = make(opt.ColList, len(inList)) + copy(newList, inList) + } + newList[i] = 0 + } + } + if newList != nil { + return newList + } + return inList +} + // pruneScanCols constructs a new Scan operator based on the given existing Scan // operator, but projecting only the needed columns. func (c *CustomFuncs) pruneScanCols(scan *memo.ScanExpr, neededCols opt.ColSet) memo.RelExpr { diff --git a/pkg/sql/opt/norm/rules/prune_cols.opt b/pkg/sql/opt/norm/rules/prune_cols.opt index 20082e5c0f07..a239f8e7385f 100644 --- a/pkg/sql/opt/norm/rules/prune_cols.opt +++ b/pkg/sql/opt/norm/rules/prune_cols.opt @@ -363,3 +363,18 @@ $projections $passthrough ) + +# PruneMutationCols discards mutation operator columns that are never used. This +# includes input columns as well as corresponding InsertCols, FetchCols, +# UpdateCols, and CheckCols in the mutation private. +# TODO(andyk): Add support for other mutation operators. +[PruneMutationCols, Normalize] +(Delete + $input:* + $mutationPrivate:* & (CanPruneCols $input $needed:(NeededColsMutation $mutationPrivate)) +) +=> +(Delete + (PruneCols $input $needed) + (PruneMutationCols $mutationPrivate $needed) +) diff --git a/pkg/sql/opt/norm/testdata/rules/decorrelate b/pkg/sql/opt/norm/testdata/rules/decorrelate index e6eefde7d12d..91c843aae87b 100644 --- a/pkg/sql/opt/norm/testdata/rules/decorrelate +++ b/pkg/sql/opt/norm/testdata/rules/decorrelate @@ -161,19 +161,17 @@ DELETE FROM xy WHERE EXISTS(SELECT * FROM uv WHERE u=x) ---- delete xy ├── columns: - ├── fetch columns: x:3(int) y:4(int) + ├── fetch columns: x:3(int) ├── cardinality: [0 - 0] ├── side-effects, mutations └── semi-join (merge) - ├── columns: x:3(int!null) y:4(int) + ├── columns: x:3(int!null) ├── left ordering: +3 ├── right ordering: +5 ├── key: (3) - ├── fd: (3)-->(4) ├── scan xy - │ ├── columns: x:3(int!null) y:4(int) + │ ├── columns: x:3(int!null) │ ├── key: (3) - │ ├── fd: (3)-->(4) │ └── ordering: +3 ├── scan uv │ ├── columns: u:5(int!null) v:6(int) diff --git a/pkg/sql/opt/norm/testdata/rules/prune_cols b/pkg/sql/opt/norm/testdata/rules/prune_cols index 1c51c5eb7406..8e598ba8f8ee 100644 --- a/pkg/sql/opt/norm/testdata/rules/prune_cols +++ b/pkg/sql/opt/norm/testdata/rules/prune_cols @@ -41,6 +41,33 @@ TABLE abcde ├── c int └── a int not null (storing) +exec-ddl +CREATE TABLE mutation ( + a INT PRIMARY KEY, + b INT, + c INT, + "d:write-only" INT, + "e:delete-only" INT, + UNIQUE INDEX "idx1:write-only" (b, d), + INDEX "idx2:delete-only" (e) +) +---- +TABLE mutation + ├── a int not null + ├── b int + ├── c int + ├── d int (mutation) + ├── e int (mutation) + ├── INDEX primary + │ └── a int not null + ├── INDEX idx1 (mutation) + │ ├── b int + │ ├── d int + │ └── a int not null (storing) + └── INDEX idx2 (mutation) + ├── e int + └── a int not null + # -------------------------------------------------- # PruneProjectCols # -------------------------------------------------- @@ -1604,3 +1631,108 @@ distinct-on │ └── variable: k [type=int] └── function: length [type=int, outer=(4)] └── variable: s [type=string] + +# -------------------------------------------------- +# PruneMutationCols +# -------------------------------------------------- + +# Prune all but the key column. +opt expect=PruneMutationCols +DELETE FROM a +---- +delete a + ├── columns: + ├── fetch columns: k:5(int) + ├── cardinality: [0 - 0] + ├── side-effects, mutations + └── scan a + ├── columns: k:5(int!null) + └── key: (5) + +# Prune when computed ordering column is present. +opt +DELETE FROM a WHERE i > 0 ORDER BY i*2 LIMIT 10 +---- +delete a + ├── columns: + ├── fetch columns: k:5(int) + ├── cardinality: [0 - 0] + ├── side-effects, mutations + └── limit + ├── columns: k:5(int!null) column9:9(int) + ├── internal-ordering: +9 + ├── cardinality: [0 - 10] + ├── key: (5) + ├── fd: (5)-->(9) + ├── sort + │ ├── columns: k:5(int!null) column9:9(int) + │ ├── key: (5) + │ ├── fd: (5)-->(9) + │ ├── ordering: +9 + │ └── project + │ ├── columns: column9:9(int) k:5(int!null) + │ ├── key: (5) + │ ├── fd: (5)-->(9) + │ ├── select + │ │ ├── columns: k:5(int!null) i:6(int!null) + │ │ ├── key: (5) + │ │ ├── fd: (5)-->(6) + │ │ ├── scan a + │ │ │ ├── columns: k:5(int!null) i:6(int) + │ │ │ ├── key: (5) + │ │ │ └── fd: (5)-->(6) + │ │ └── filters + │ │ └── i > 0 [type=bool, outer=(6), constraints=(/6: [/1 - ]; tight)] + │ └── projections + │ └── i * 2 [type=int, outer=(6)] + └── const: 10 [type=int] + +# Prune when a secondary index is present on the table. +opt expect=PruneMutationCols +DELETE FROM abcde WHERE a > 0 +---- +delete abcde + ├── columns: + ├── fetch columns: a:6(int) b:7(int) c:8(int) + ├── cardinality: [0 - 0] + ├── side-effects, mutations + └── scan abcde + ├── columns: a:6(int!null) b:7(int) c:8(int) + ├── constraint: /6: [/1 - ] + ├── key: (6) + └── fd: (6)-->(7,8), (7,8)~~>(6) + +# Prune when mutation columns/indexes exist. +opt +DELETE FROM mutation +---- +delete mutation + ├── columns: + ├── fetch columns: a:6(int) b:7(int) d:9(int) e:10(int) + ├── cardinality: [0 - 0] + ├── side-effects, mutations + └── scan mutation + ├── columns: a:6(int!null) b:7(int) d:9(int) e:10(int) + ├── key: (6) + └── fd: (6)-->(7,9,10) + +# No pruning when RETURNING clause is present. +# TODO(andyk): Need to prune output columns. +opt expect-not=PruneMutationCols +DELETE FROM a RETURNING k, s +---- +project + ├── columns: k:1(int!null) s:4(string) + ├── side-effects, mutations + ├── key: (1) + ├── fd: (1)-->(4) + └── delete a + ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) + ├── fetch columns: k:5(int) i:6(int) f:7(float) s:8(string) + ├── side-effects, mutations + ├── key: (1) + ├── fd: (1)-->(2-4) + └── scan a + ├── columns: k:5(int!null) i:6(int) f:7(float) s:8(string) + ├── key: (5) + └── fd: (5)-->(6-8) diff --git a/pkg/sql/opt/optbuilder/delete.go b/pkg/sql/opt/optbuilder/delete.go index 20d0eb4a697f..d1b70b04eb84 100644 --- a/pkg/sql/opt/optbuilder/delete.go +++ b/pkg/sql/opt/optbuilder/delete.go @@ -34,10 +34,6 @@ import ( // mutations are applied, or the order of any returned rows (i.e. it won't // become a physical property required of the Delete operator). func (b *Builder) buildDelete(del *tree.Delete, inScope *scope) (outScope *scope) { - if !b.evalCtx.SessionData.OptimizerMutations { - panic(unimplementedf("cost-based optimizer is not planning DELETE statements")) - } - // UX friendliness safeguard. if del.Where == nil && b.evalCtx.SessionData.SafeUpdates { panic(builderError{pgerror.NewDangerousStatementErrorf("DELETE without WHERE clause")}) diff --git a/pkg/sql/opt/table_meta.go b/pkg/sql/opt/table_meta.go index 4cb69b85920d..82e0c1daf498 100644 --- a/pkg/sql/opt/table_meta.go +++ b/pkg/sql/opt/table_meta.go @@ -128,6 +128,15 @@ type TableMeta struct { anns [maxTableAnnIDCount]interface{} } +// Columns returns the metadata IDs for all non-mutation columns in the table. +func (tm *TableMeta) Columns() ColSet { + var cols ColSet + for i, n := 0, tm.Table.ColumnCount(); i < n; i++ { + cols.Add(int(tm.MetaID.ColumnID(i))) + } + return cols +} + // IndexColumns returns the metadata IDs for the set of columns in the given // index. // TODO(justin): cache this value in the table metadata. @@ -135,7 +144,20 @@ func (tm *TableMeta) IndexColumns(indexOrd int) ColSet { index := tm.Table.Index(indexOrd) var indexCols ColSet - for i, cnt := 0, index.ColumnCount(); i < cnt; i++ { + for i, n := 0, index.ColumnCount(); i < n; i++ { + ord := index.Column(i).Ordinal + indexCols.Add(int(tm.MetaID.ColumnID(ord))) + } + return indexCols +} + +// IndexKeyColumns returns the metadata IDs for the set of strict key columns in +// the given index. +func (tm *TableMeta) IndexKeyColumns(indexOrd int) ColSet { + index := tm.Table.Index(indexOrd) + + var indexCols ColSet + for i, n := 0, index.KeyColumnCount(); i < n; i++ { ord := index.Column(i).Ordinal indexCols.Add(int(tm.MetaID.ColumnID(ord))) } diff --git a/pkg/sql/opt/testutils/testcat/create_table.go b/pkg/sql/opt/testutils/testcat/create_table.go index 6674673c6038..308da14e5789 100644 --- a/pkg/sql/opt/testutils/testcat/create_table.go +++ b/pkg/sql/opt/testutils/testcat/create_table.go @@ -63,6 +63,12 @@ func (tc *Catalog) CreateTable(stmt *tree.CreateTable) *Table { tab.IsVirtual = true } + // TODO(andyk): For now, just remember that the table was interleaved. In the + // future, it may be necessary to extract additional metadata. + if stmt.Interleave != nil { + tab.interleaved = true + } + // Add non-mutation columns. for _, def := range stmt.Defs { switch def := def.(type) { @@ -152,6 +158,7 @@ func (tc *Catalog) resolveFK(tab *Table, d *tree.ForeignKeyConstraintTableDef) { } targetTable := tc.Table(&d.Table) + targetTable.referenced = true toCols := make([]int, len(d.ToCols)) for i, c := range d.ToCols { diff --git a/pkg/sql/opt/testutils/testcat/test_catalog.go b/pkg/sql/opt/testutils/testcat/test_catalog.go index f487e246d58e..055ac0cc3db3 100644 --- a/pkg/sql/opt/testutils/testcat/test_catalog.go +++ b/pkg/sql/opt/testutils/testcat/test_catalog.go @@ -442,6 +442,14 @@ type Table struct { deleteOnlyColCount int writeOnlyIdxCount int deleteOnlyIdxCount int + + // interleaved is true if the table's rows are interleaved with rows from + // other table(s). + interleaved bool + + // referenced is set to true when another table has referenced this table + // via a foreign key. + referenced bool } var _ cat.Table = &Table{} @@ -476,6 +484,16 @@ func (tt *Table) IsVirtualTable() bool { return tt.IsVirtual } +// IsInterleaved is part of the cat.Table interface. +func (tt *Table) IsInterleaved() bool { + return false +} + +// IsReferenced is part of the cat.Table interface. +func (tt *Table) IsReferenced() bool { + return tt.referenced +} + // ColumnCount is part of the cat.Table interface. func (tt *Table) ColumnCount() int { return len(tt.Columns) - tt.writeOnlyColCount - tt.deleteOnlyColCount diff --git a/pkg/sql/opt/xform/testdata/physprops/ordering b/pkg/sql/opt/xform/testdata/physprops/ordering index 258b60d01615..f80934165c56 100644 --- a/pkg/sql/opt/xform/testdata/physprops/ordering +++ b/pkg/sql/opt/xform/testdata/physprops/ordering @@ -1501,7 +1501,7 @@ project ├── key: (10) ├── fd: (10)-->(6-9) ├── ordering: +8,+9 - ├── prune: (6-10) + ├── prune: (6,7,9,10) └── interesting orderings: (+10) (+6,+7,+10) (+8,+9,+10) # Verify that provided orderings are derived correctly with equivalence FD. @@ -1543,7 +1543,7 @@ sort │ ├── limit: 10 │ ├── key: (10) │ ├── fd: (10)-->(6-9) - │ ├── prune: (6-10) + │ ├── prune: (6,7,10) │ └── interesting orderings: (+10) (+6,+7,+10) (+8,+9,+10) └── filters └── b = c [type=bool, outer=(2,3), fd=(2)==(3), (3)==(2)] diff --git a/pkg/sql/opt_catalog.go b/pkg/sql/opt_catalog.go index 5c59be7ef545..956b3c5d9ea4 100644 --- a/pkg/sql/opt_catalog.go +++ b/pkg/sql/opt_catalog.go @@ -390,6 +390,21 @@ func (ot *optTable) IsVirtualTable() bool { return ot.desc.IsVirtualTable() } +// IsInterleaved is part of the cat.Table interface. +func (ot *optTable) IsInterleaved() bool { + return ot.desc.IsInterleaved() +} + +// IsReferenced is part of the cat.Table interface. +func (ot *optTable) IsReferenced() bool { + for i, n := 0, ot.DeletableIndexCount(); i < n; i++ { + if len(ot.Index(i).(*optIndex).desc.ReferencedBy) != 0 { + return true + } + } + return false +} + // ColumnCount is part of the cat.Table interface. func (ot *optTable) ColumnCount() int { return len(ot.desc.Columns) diff --git a/pkg/sql/opt_exec_factory.go b/pkg/sql/opt_exec_factory.go index 526a52734252..58165413217f 100644 --- a/pkg/sql/opt_exec_factory.go +++ b/pkg/sql/opt_exec_factory.go @@ -65,18 +65,44 @@ func (ef *execFactory) ConstructValues( func (ef *execFactory) ConstructScan( table cat.Table, index cat.Index, - cols exec.ColumnOrdinalSet, + needed exec.ColumnOrdinalSet, + indexConstraint *constraint.Constraint, + hardLimit int64, + reverse bool, + maxResults uint64, + reqOrdering exec.OutputOrdering, +) (exec.Node, error) { + return ef.constructScan( + table, + index, + needed, + indexConstraint, + hardLimit, + reverse, + maxResults, + reqOrdering, + false, /* forDelete */ + ) +} + +// constructScan is a private helper method used by ConstructScan and +// ConstructDeleteRange. +func (ef *execFactory) constructScan( + table cat.Table, + index cat.Index, + needed exec.ColumnOrdinalSet, indexConstraint *constraint.Constraint, hardLimit int64, reverse bool, maxResults uint64, reqOrdering exec.OutputOrdering, + forDelete bool, ) (exec.Node, error) { tabDesc := table.(*optTable).desc indexDesc := index.(*optIndex).desc // Create a scanNode. scan := ef.planner.Scan() - colCfg := makeScanColumnsConfig(table, cols) + colCfg := makeScanColumnsConfig(table, needed) // initTable checks that the current user has the correct privilege to access // the table. However, the privilege has already been checked in optbuilder, @@ -99,8 +125,7 @@ func (ef *execFactory) ConstructScan( scan.maxResults = maxResults scan.parallelScansEnabled = sqlbase.ParallelScans.Get(&ef.planner.extendedEvalCtx.Settings.SV) var err error - scan.spans, err = spansFromConstraint( - tabDesc, indexDesc, indexConstraint, cols, scan.isDeleteSource) + scan.spans, err = spansFromConstraint(tabDesc, indexDesc, indexConstraint, needed, forDelete) if err != nil { return nil, err } @@ -1242,6 +1267,30 @@ func (ef *execFactory) ConstructDelete( return &rowCountNode{source: del}, nil } +func (ef *execFactory) ConstructDeleteRange( + table cat.Table, needed exec.ColumnOrdinalSet, indexConstraint *constraint.Constraint, +) (exec.Node, error) { + // Setting the "forDelete" flag instructs the scan to include all column + // families in case where a single record is deleted. + input, err := ef.constructScan( + table, + table.Index(cat.PrimaryIndex), + needed, + indexConstraint, + 0, /* hardLimit */ + false, /* reverse */ + 0, /* maxResults */ + nil, /* reqOrdering */ + true, /* forDelete */ + ) + if err != nil { + return input, err + } + + // TODO(andyk): Construct new DeleteRange operator once it's been merged. + return ef.ConstructDelete(input, table, needed, false /* rowsNeeded */) +} + func (ef *execFactory) ConstructCreateTable( input exec.Node, schema cat.Schema, ct *tree.CreateTable, ) (exec.Node, error) {