Commit 59b79642 authored by craig[bot]'s avatar craig[bot]

Merge #49671

49671: sql: handle inserts for partial indexes r=mgartner a=mgartner

This commit makes INSERTs only write new entries to partial indexes when
the new row satisfies the predicate expression of the partial index.

In order to do this, the optimizer synthesizes a column for each partial
index defined on the table that evaluates to true if the respective
partial index should be written to. These columns are interpretted by the
execution engine into a set of IndexIDs to not write to.

Release note: None
Co-authored-by: default avatarMarcus Gartner <[email protected]>
parents c87535a0 1c3404a6
......@@ -33,6 +33,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
"github.com/cockroachdb/cockroach/pkg/storage"
"github.com/cockroachdb/cockroach/pkg/storage/cloud"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/cockroach/pkg/util/hlc"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
......@@ -365,7 +366,10 @@ func insertStmtToKVs(
}
// TODO(bram): Is the checking of FKs here required? If not, turning them
// off may provide a speed boost.
if err := ri.InsertRow(ctx, b, insertRow, true, row.CheckFKs, false /* traceKV */); err != nil {
// TODO(mgartner): Add partial index IDs to ignoreIndexes that we should
// not add entries to.
var ignoreIndexes util.FastIntSet
if err := ri.InsertRow(ctx, b, insertRow, ignoreIndexes, true, row.CheckFKs, false /* traceKV */); err != nil {
return errors.Wrapf(err, "insert %q", insertRow)
}
}
......
......@@ -39,7 +39,7 @@ import (
// mysqldumpReader reads the default output of `mysqldump`, which consists of
// SQL statements, in MySQL-dialect, namely CREATE TABLE and INSERT statements,
// with some additional statements that contorl the loading process like LOCK or
// with some additional statements that control the loading process like LOCK or
// UNLOCK (which are simply ignored for the purposed of this reader). Data for
// tables with names that appear in the `tables` map is converted to Cockroach
// KVs using the mapped converter and sent to kvCh.
......
......@@ -332,10 +332,9 @@ type checkSet = util.FastIntSet
//
// It is allowed to check only a subset of the active checks (for some, we could
// determine that they can't fail because they statically evaluate to true for
// the entire input); checkSet contains the set of checks for which we have
// the entire input); checkOrds contains the set of checks for which we have
// values, as ordinals into ActiveChecks(). There must be exactly one value in
// checkVals for each element in checkSet.
//
func checkMutationInput(
tabDesc *sqlbase.ImmutableTableDescriptor, checkOrds checkSet, checkVals tree.Datums,
) error {
......
......@@ -469,7 +469,10 @@ func (n *createTableNode) startExec(params runParams) error {
}
}
if err := tw.row(params.ctx, rowBuffer, params.extendedEvalCtx.Tracing.KVTracingEnabled()); err != nil {
// TODO(mgartner): Add partial index IDs to ignoreIndexes that we should
// not add entries to.
var ignoreIndexes util.FastIntSet
if err := tw.row(params.ctx, rowBuffer, ignoreIndexes, params.extendedEvalCtx.Tracing.KVTracingEnabled()); err != nil {
return err
}
}
......
......@@ -17,6 +17,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/rowcontainer"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/cockroach/pkg/util/tracing"
)
......@@ -169,7 +170,10 @@ func (d *deleteNode) BatchedNext(params runParams) (bool, error) {
// result rows are needed, saves it in the result row container
func (d *deleteNode) processSourceRow(params runParams, sourceVals tree.Datums) error {
// Queue the deletion in the KV batch.
if err := d.run.td.row(params.ctx, sourceVals, d.run.traceKV); err != nil {
// TODO(mgartner): Add partial index IDs to ignoreIndexes that we should
// not delete entries from.
var ignoreIndexes util.FastIntSet
if err := d.run.td.row(params.ctx, sourceVals, ignoreIndexes, d.run.traceKV); err != nil {
return err
}
......
......@@ -17,6 +17,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/rowcontainer"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/cockroach/pkg/util/tracing"
)
......@@ -138,6 +139,37 @@ func (r *insertRun) processSourceRow(params runParams, rowVals tree.Datums) erro
return err
}
// Create a set of index IDs to not write to. Indexes should not be written
// to when they are partial indexes and the row does not satisfy the
// predicate. This set is passed as a parameter to tableInserter.row below.
var ignoreIndexes util.FastIntSet
indexPredicateVals := rowVals[len(r.insertCols)+r.checkOrds.Len():]
colIdx := 0
indexes := r.ti.tableDesc().Indexes
for i := range indexes {
if colIdx >= len(indexPredicateVals) {
break
}
index := indexes[i]
if index.IsPartial() {
val, err := tree.GetBool(indexPredicateVals[colIdx])
if err != nil {
return err
}
if !val {
// If the value of the column for the index predicate expression
// is false, the row should not be added to the partial index.
ignoreIndexes.Add(int(index.ID))
}
colIdx++
}
}
// Truncate rowVals so that it no longer includes partial index predicate
// values.
rowVals = rowVals[:len(r.insertCols)+r.checkOrds.Len()]
// Verify the CHECK constraint results, if any.
if !r.checkOrds.Empty() {
checkVals := rowVals[len(r.insertCols):]
......@@ -148,7 +180,7 @@ func (r *insertRun) processSourceRow(params runParams, rowVals tree.Datums) erro
}
// Queue the insert in the KV batch.
if err := r.ti.row(params.ctx, rowVals, r.traceKV); err != nil {
if err := r.ti.row(params.ctx, rowVals, ignoreIndexes, r.traceKV); err != nil {
return err
}
......
......@@ -56,6 +56,11 @@ type Index interface {
// clause), as well as implicitly added primary key columns.
ColumnCount() int
// Predicate returns the partial index predicate expression and true if the
// index is a partial index. If it is not a partial index, the empty string
// and false are returned.
Predicate() (string, bool)
// KeyColumnCount returns the number of columns in the index that are part
// of its unique key. No two rows in the index will have the same values for
// those columns (where NULL values are treated as equal). Every index has a
......
......@@ -78,9 +78,10 @@ func (b *Builder) buildInsert(ins *memo.InsertExpr) (execPlan, error) {
}
// Construct list of columns that only contains columns that need to be
// inserted (e.g. delete-only mutation columns don't need to be inserted).
colList := make(opt.ColList, 0, len(ins.InsertCols)+len(ins.CheckCols))
colList := make(opt.ColList, 0, len(ins.InsertCols)+len(ins.CheckCols)+len(ins.IndexPredicateCols))
colList = appendColsWhenPresent(colList, ins.InsertCols)
colList = appendColsWhenPresent(colList, ins.CheckCols)
colList = appendColsWhenPresent(colList, ins.IndexPredicateCols)
input, err := b.buildMutationInput(ins, ins.Input, colList, &ins.MutationPrivate)
if err != nil {
return execPlan{}, err
......@@ -223,9 +224,10 @@ func (b *Builder) tryBuildFastPathInsert(ins *memo.InsertExpr) (_ execPlan, ok b
}
}
colList := make(opt.ColList, 0, len(ins.InsertCols)+len(ins.CheckCols))
colList := make(opt.ColList, 0, len(ins.InsertCols)+len(ins.CheckCols)+len(ins.IndexPredicateCols))
colList = appendColsWhenPresent(colList, ins.InsertCols)
colList = appendColsWhenPresent(colList, ins.CheckCols)
colList = appendColsWhenPresent(colList, ins.IndexPredicateCols)
if !colList.Equals(values.Cols) {
// We have a Values input, but the columns are not in the right order. For
// example:
......
# LogicTest: local
# TODO(mgartner): remove this once partial indexes are fully supported.
statement ok
SET experimental_partial_indexes=on
statement ok
CREATE TABLE t (
a INT PRIMARY KEY,
b INT,
c STRING,
FAMILY (a, b, c),
INDEX (b) WHERE b > 10,
INDEX (c) WHERE a > b AND c = 'foo'
)
# Inserted row matches no partial index.
query T kvtrace
INSERT INTO t VALUES(5, 4, 'bar')
----
CPut /Table/53/1/5/0 -> /TUPLE/2:2:Int/4/1:3:Bytes/bar
# Inserted row matches the first partial index.
query T kvtrace
INSERT INTO t VALUES(6, 11, 'bar')
----
CPut /Table/53/1/6/0 -> /TUPLE/2:2:Int/11/1:3:Bytes/bar
InitPut /Table/53/2/11/6/0 -> /BYTES/
# Inserted row matches both partial indexes.
query T kvtrace
INSERT INTO t VALUES(12, 11, 'foo')
----
CPut /Table/53/1/12/0 -> /TUPLE/2:2:Int/11/1:3:Bytes/foo
InitPut /Table/53/2/11/12/0 -> /BYTES/
InitPut /Table/53/3/"foo"/12/0 -> /BYTES/
......@@ -467,6 +467,7 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) {
}
f.formatMutationCols(e, tp, "insert-mapping:", t.InsertCols, t.Table)
f.formatColList(e, tp, "check columns:", t.CheckCols)
f.formatColList(e, tp, "partial index pred columns:", t.IndexPredicateCols)
f.formatMutationCommon(tp, &t.MutationPrivate)
}
......
......@@ -60,6 +60,7 @@ func (c *CustomFuncs) NeededMutationCols(
addCols(private.FetchCols)
addCols(private.UpdateCols)
addCols(private.CheckCols)
addCols(private.IndexPredicateCols)
addCols(private.ReturnCols)
addCols(private.PassthroughCols)
if private.CanaryCol != 0 {
......
......@@ -104,6 +104,28 @@ define MutationPrivate {
# TODO(radu): we don't actually implement this optimization currently.
CheckCols ColList
# IndexPredicateCols are columns from the Input expression containing the
# results of evaluating each partial index predicate from the target table
# for the mutation. Evaluating a partial index predicate produces a boolean
# value which is projected as a column and used during execution to
# determine whether or not to write a row to the partial index. The count
# and order of columns corresponds to the count and order of the target
# table's partial indexes. For example:
#
# CREATE TABLE abc (
# a INT, b INT, c INT,
# INDEX (a) WHERE a > 0,
# INDEX (b),
# INDEX (c) WHERE c > 5
# )
#
# In this case there are two columns. The first is the result of evaluating
# the predicate expression of the index on a. The second is the result of
# evaluating the predicate of the index on c. The index on b is not a
# partial index, because it has no predicate, so it is not included in
# IndexPredicateCols.
IndexPredicateCols ColList
# CanaryCol is used only with the Upsert operator. It identifies the column
# that the execution engine uses to decide whether to insert or to update.
# If the canary column value is null for a particular input row, then a new
......
......@@ -632,9 +632,16 @@ func (mb *mutationBuilder) addSynthesizedColsForInsert() {
// buildInsert constructs an Insert operator, possibly wrapped by a Project
// operator that corresponds to the given RETURNING clause.
func (mb *mutationBuilder) buildInsert(returning tree.ReturningExprs) {
// Disambiguate names so that references in any expressions, such as a
// check constraint, refer to the correct columns.
mb.disambiguateColumns()
// Add any check constraint boolean columns to the input.
mb.addCheckConstraintCols()
// Add any partial index boolean columns to the input.
mb.addPartialIndexPredicateCols()
mb.buildFKChecksForInsert()
private := mb.makeMutationPrivate(returning != nil)
......@@ -921,6 +928,10 @@ func (mb *mutationBuilder) buildUpsert(returning tree.ReturningExprs) {
// Merge input insert and update columns using CASE expressions.
mb.projectUpsertColumns()
// Disambiguate names so that references in any expressions, such as a
// check constraint, refer to the correct columns.
mb.disambiguateColumns()
// Add any check constraint boolean columns to the input.
mb.addCheckConstraintCols()
......
......@@ -55,9 +55,9 @@ type mutationBuilder struct {
// expression is completed, it will be contained in outScope.expr. Columns,
// when present, are arranged in this order:
//
// +--------+-------+--------+--------+-------+
// | Insert | Fetch | Update | Upsert | Check |
// +--------+-------+--------+--------+-------+
// +--------+-------+--------+--------+-------+------------------+
// | Insert | Fetch | Update | Upsert | Check | Index Predicates |
// +--------+-------+--------+--------+-------+------------------+
//
// Each column is identified by its ordinal position in outScope, and those
// ordinals are stored in the corresponding ScopeOrds fields (see below).
......@@ -110,6 +110,16 @@ type mutationBuilder struct {
// (see opt.Table.CheckCount).
checkOrds []scopeOrdinal
// indexPredicateOrds lists the outScope columns storing the boolean results
// of evaluating partial index predicate expressions defined on the indexes
// of the target table. Its length is always equal to the number of partial
// indexes on the table.
// TODO(mgartner): We will likely need two sets of columns to support
// updates for partial indexes; one set to indicate current rows that need
// to be removed from the index, and the other to indicate updated rows that
// need to be added to the index.
indexPredicateOrds []scopeOrdinal
// canaryColID is the ID of the column that is used to decide whether to
// insert or update each row. If the canary column's value is null, then it's
// an insert; otherwise it's an update.
......@@ -157,7 +167,7 @@ func (mb *mutationBuilder) init(b *Builder, opName string, tab cat.Table, alias
// Allocate segmented array of scope column ordinals.
n := tab.DeletableColumnCount()
scopeOrds := make([]scopeOrdinal, n*4+tab.CheckCount())
scopeOrds := make([]scopeOrdinal, n*4+tab.CheckCount()+partialIndexCount(tab))
for i := range scopeOrds {
scopeOrds[i] = -1
}
......@@ -165,7 +175,8 @@ func (mb *mutationBuilder) init(b *Builder, opName string, tab cat.Table, alias
mb.fetchOrds = scopeOrds[n : n*2]
mb.updateOrds = scopeOrds[n*2 : n*3]
mb.upsertOrds = scopeOrds[n*3 : n*4]
mb.checkOrds = scopeOrds[n*4:]
mb.checkOrds = scopeOrds[n*4 : n*4+tab.CheckCount()]
mb.indexPredicateOrds = scopeOrds[n*4+tab.CheckCount():]
// Add the table and its columns (including mutation columns) to metadata.
mb.tabID = mb.md.AddTable(tab, &mb.alias)
......@@ -676,10 +687,6 @@ func findRoundingFunction(typ *types.T, precision int) (*tree.FunctionProperties
// a constraint violation error if the value of the column is false.
func (mb *mutationBuilder) addCheckConstraintCols() {
if mb.tab.CheckCount() != 0 {
// Disambiguate names so that references in the constraint expression refer
// to the correct columns.
mb.disambiguateColumns()
projectionsScope := mb.outScope.replace()
projectionsScope.appendColumnsFromScope(mb.outScope)
......@@ -704,6 +711,41 @@ func (mb *mutationBuilder) addCheckConstraintCols() {
}
}
// addPartialIndexPredicateCols synthesizes a boolean output column for each
// partial index defined on the target table. The execution code uses these
// booleans to determine whether or not to insert or delete a row in the partial
// index.
func (mb *mutationBuilder) addPartialIndexPredicateCols() {
projectionsScope := mb.outScope.replace()
projectionsScope.appendColumnsFromScope(mb.outScope)
ord := 0
for i, n := 0, mb.tab.IndexCount(); i < n; i++ {
index := mb.tab.Index(i)
predicate, ok := index.Predicate()
if !ok {
continue
}
expr, err := parser.ParseExpr(predicate)
if err != nil {
panic(err)
}
alias := fmt.Sprintf("indexpred%d", ord+1)
texpr := mb.outScope.resolveAndRequireType(expr, types.Bool)
scopeCol := mb.b.addColumn(projectionsScope, alias, texpr)
mb.b.buildScalar(texpr, mb.outScope, projectionsScope, scopeCol, nil)
mb.indexPredicateOrds[ord] = scopeOrdinal(len(projectionsScope.cols) - 1)
ord++
}
mb.b.constructProjectForScope(mb.outScope, projectionsScope)
mb.outScope = projectionsScope
}
// disambiguateColumns ranges over the scope and ensures that at most one column
// has each table column name, and that name refers to the column with the final
// value that the mutation applies.
......@@ -743,14 +785,15 @@ func (mb *mutationBuilder) makeMutationPrivate(needResults bool) *memo.MutationP
}
private := &memo.MutationPrivate{
Table: mb.tabID,
InsertCols: makeColList(mb.insertOrds),
FetchCols: makeColList(mb.fetchOrds),
UpdateCols: makeColList(mb.updateOrds),
CanaryCol: mb.canaryColID,
CheckCols: makeColList(mb.checkOrds),
FKCascades: mb.cascades,
FKFallback: mb.fkFallback,
Table: mb.tabID,
InsertCols: makeColList(mb.insertOrds),
FetchCols: makeColList(mb.fetchOrds),
UpdateCols: makeColList(mb.updateOrds),
CanaryCol: mb.canaryColID,
CheckCols: makeColList(mb.checkOrds),
IndexPredicateCols: makeColList(mb.indexPredicateOrds),
FKCascades: mb.cascades,
FKFallback: mb.fkFallback,
}
// If we didn't actually plan any checks or cascades, don't buffer the input.
......@@ -965,3 +1008,15 @@ func checkDatumTypeFitsColumnType(col cat.Column, typ *types.T) {
"value type %s doesn't match type %s of column %q",
typ, col.DatumType(), tree.ErrNameString(colName)))
}
// partialIndexCount returns the number of public, write-only, and delete-only
// partial indexes defined on the table.
func partialIndexCount(tab cat.Table) int {
count := 0
for i, n := 0, tab.DeletableIndexCount(); i < n; i++ {
if _, ok := tab.Index(i).Predicate(); ok {
count++
}
}
return count
}
......@@ -1215,3 +1215,37 @@ insert defvals2
└── projections
├── ARRAY[NULL] [as=column5:5]
└── ARRAY[NULL] [as=column6:6]
# ------------------------------------------------------------------------------
# Test partial index column values.
# ------------------------------------------------------------------------------
exec-ddl
CREATE TABLE partial_indexes (
a INT PRIMARY KEY,
b INT,
c STRING,
INDEX (b),
INDEX (b) WHERE c = 'foo',
INDEX (c) WHERE a > b AND c = 'bar'
)
----
build
INSERT INTO partial_indexes VALUES (2, 1, 'bar')
----
insert partial_indexes
├── columns: <none>
├── insert-mapping:
│ ├── column1:4 => a:1
│ ├── column2:5 => b:2
│ └── column3:6 => c:3
├── partial index pred columns: indexpred1:7 indexpred2:8
└── project
├── columns: indexpred1:7!null indexpred2:8!null column1:4!null column2:5!null column3:6!null
├── values
│ ├── columns: column1:4!null column2:5!null column3:6!null
│ └── (2, 1, 'bar')
└── projections
├── column3:6 = 'foo' [as=indexpred1:7]
└── (column1:4 > column2:5) AND (column3:6 = 'bar') [as=indexpred2:8]
......@@ -276,7 +276,7 @@ func (mb *mutationBuilder) addUpdateCols(exprs tree.UpdateExprs) {
mb.addSynthesizedColsForUpdate()
}
// addComputedColsForUpdate wraps an Update input expression with a Project
// addSynthesizedColsForUpdate wraps an Update input expression with a Project
// operator containing any computed columns that need to be updated. This
// includes write-only mutation columns that are computed.
func (mb *mutationBuilder) addSynthesizedColsForUpdate() {
......@@ -321,6 +321,10 @@ func (mb *mutationBuilder) addSynthesizedColsForUpdate() {
// buildUpdate constructs an Update operator, possibly wrapped by a Project
// operator that corresponds to the given RETURNING clause.
func (mb *mutationBuilder) buildUpdate(returning tree.ReturningExprs) {
// Disambiguate names so that references in any expressions, such as a
// check constraint, refer to the correct columns.
mb.disambiguateColumns()
mb.addCheckConstraintCols()
mb.buildFKChecksForUpdate()
......
......@@ -507,6 +507,11 @@ func (tt *Table) addIndex(def *tree.IndexTableDef, typ indexType) *Index {
}
}
// Add partial index predicate.
if def.Predicate != nil {
idx.predicate = tree.Serialize(def.Predicate)
}
idx.ordinal = len(tt.Indexes)
tt.Indexes = append(tt.Indexes, idx)
......
......@@ -733,6 +733,9 @@ type Index struct {
// partitionBy is the partitioning clause that corresponds to this index. Used
// to implement PartitionByListPrefixes.
partitionBy *tree.PartitionBy
// predicate is the partial index predicate expression, if it exists.
predicate string
}
// ID is part of the cat.Index interface.
......@@ -795,6 +798,13 @@ func (ti *Index) Span() roachpb.Span {
panic("not implemented")
}
// Predicate is part of the cat.Index interface. It returns the predicate
// expression and true if the index is a partial index. If the index is not
// partial, the empty string and false is returned.
func (ti *Index) Predicate() (string, bool) {
return ti.predicate, ti.predicate != ""
}
// PartitionByListPrefixes is part of the cat.Index interface.
func (ti *Index) PartitionByListPrefixes() []tree.Datums {
ctx := context.Background()
......
......@@ -941,6 +941,13 @@ func (oi *optIndex) ColumnCount() int {
return oi.numCols
}
// Predicate is part of the cat.Index interface. It returns the predicate
// expression and true if the index is a partial index. If the index is not
// partial, the empty string and false is returned.
func (oi *optIndex) Predicate() (string, bool) {
return oi.desc.Predicate, oi.desc.Predicate != ""
}
// KeyColumnCount is part of the cat.Index interface.
func (oi *optIndex) KeyColumnCount() int {
return oi.numKeyCols
......@@ -1616,6 +1623,11 @@ func (oi *optVirtualIndex) ColumnCount() int {
return oi.numCols
}
// Predicate is part of the cat.Index interface.
func (oi *optVirtualIndex) Predicate() (string, bool) {
return "", false
}
// KeyColumnCount is part of the cat.Index interface.
func (oi *optVirtualIndex) KeyColumnCount() int {
return 1
......
......@@ -16,6 +16,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/keys"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/cockroach/pkg/util/encoding"
)
......@@ -60,13 +61,16 @@ func newRowHelper(
// encodeSecondaryIndexes. includeEmpty details whether the results should
// include empty secondary index k/v pairs.
func (rh *rowHelper) encodeIndexes(
colIDtoRowIndex map[sqlbase.ColumnID]int, values []tree.Datum, includeEmpty bool,
colIDtoRowIndex map[sqlbase.ColumnID]int,
values []tree.Datum,
ignoreIndexes util.FastIntSet,
includeEmpty bool,
) (primaryIndexKey []byte, secondaryIndexEntries []sqlbase.IndexEntry, err error) {
primaryIndexKey, err = rh.encodePrimaryIndex(colIDtoRowIndex, values)
if err != nil {
return nil, nil, err
}
secondaryIndexEntries, err = rh.encodeSecondaryIndexes(colIDtoRowIndex, values, includeEmpty)
secondaryIndexEntries, err = rh.encodeSecondaryIndexes(colIDtoRowIndex, values, ignoreIndexes, includeEmpty)
if err != nil {
return nil, nil, err
}
......@@ -86,28 +90,40 @@ func (rh *rowHelper) encodePrimaryIndex(
return primaryIndexKey, err
}
// encodeSecondaryIndexes encodes the secondary index keys. The
// secondaryIndexEntries are only valid until the next call to encodeIndexes or
// encodeSecondaryIndexes. includeEmpty details whether the results
// should include empty secondary index k/v pairs.
// encodeSecondaryIndexes encodes the secondary index keys based on a row's
// values.
//
// The secondaryIndexEntries are only valid until the next call to encodeIndexes
// or encodeSecondaryIndexes, when they are overwritten.
//
// This function will not encode index entries for any index with an ID in
// ignoreIndexes.
//
// includeEmpty details whether the results should include empty secondary index
// k/v pairs.
func (rh *rowHelper) encodeSecondaryIndexes(
colIDtoRowIndex map[sqlbase.ColumnID]int, values []tree.Datum, includeEmpty bool,
colIDtoRowIndex map[sqlbase.ColumnID]int,
values []tree.Datum,
ignoreIndexes util.FastIntSet,
includeEmpty bool,
) (secondaryIndexEntries []sqlbase.IndexEntry, err error) {
if cap(rh.indexEntries) < len(rh.Indexes) {
rh.indexEntries = make([]sqlbase.IndexEntry, 0, len(rh.Indexes))
}
rh.indexEntries, err = sqlbase.EncodeSecondaryIndexes(
rh.Codec,
rh.TableDesc.TableDesc(),
rh.Indexes,
colIDtoRowIndex,
values,
rh.indexEntries[:0],
includeEmpty,
)
if err != nil {
return nil, err
rh.indexEntries = rh.indexEntries[:0]
for i := range rh.Indexes {
index := &rh.Indexes[i]
if !ignoreIndexes.Contains(int(index.ID)) {
entries, err := sqlbase.EncodeSecondaryIndex(rh.Codec, rh.TableDesc.TableDesc(), index, colIDtoRowIndex, values, includeEmpty)
if err != nil {
return nil, err
}
rh.indexEntries = append(rh.indexEntries, entries...)
}
}
return rh.indexEntries, nil
}
......
......@@ -19,6 +19,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/errors"
)
......@@ -127,6 +128,7 @@ func (ri *Inserter) InsertRow(
ctx context.Context,
b putter,
values []tree.Datum,
ignoreIndexes util.FastIntSet,
overwrite bool,
checkFKs checkFKConstraints,
traceKV bool,
......@@ -173,7 +175,7 @@ func (ri *Inserter) InsertRow(
// We don't want to insert empty k/v's like this, so we
// set includeEmpty to false.
primaryIndexKey, secondaryIndexEntries, err := ri.Helper.encodeIndexes(
ri.InsertColIDtoRowIndex, values, false /* includeEmpty */)
ri.InsertColIDtoRowIndex, values, ignoreIndexes, false /* includeEmpty */)
if err != nil {
return err
}
......
......@@ -19,6 +19,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/errors"
)
......@@ -373,6 +374,9 @@ func (c *DatumRowConverter) Row(ctx context.Context, sourceID int32, rowIndex in
if err != nil {
return errors.Wrap(err, "generate insert row")
}
// TODO(mgartner): Add partial index IDs to ignoreIndexes that we should
// not delete entries from.
var ignoreIndexes util.FastIntSet
if err := c.ri.InsertRow(
ctx,
KVInserter(func(kv roachpb.KeyValue) {
......@@ -380,6 +384,7 @@ func (c *DatumRowConverter) Row(ctx context.Context, sourceID int32, rowIndex in
c.KvBatch.KVs = append(c.KvBatch.KVs, kv)
}),
insertRow,
ignoreIndexes,
true, /* ignoreConflicts */
SkipFKs,
false, /* traceKV */
......
......@@ -20,6 +20,7 @@ import (