Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sql/row: fix updates of single-composite-column families #131869

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/sql/colenc/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ func (b *BatchEncoder) skipColumnNotInPrimaryIndexValue(
colID catid.ColumnID, vec *coldata.Vec, row int,
) bool {
// Reuse this function but fake out the value and handle composites here.
if skip := b.rh.SkipColumnNotInPrimaryIndexValue(colID, tree.DNull); skip {
if skip, _ := b.rh.SkipColumnNotInPrimaryIndexValue(colID, tree.DNull); skip {
if !b.compositeColumnIDs.Contains(int(colID)) {
return true
}
Expand Down
27 changes: 27 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/column_families
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,30 @@ ORDER BY message
fetched: /t/t_pkey/1/2.00/x -> /1.00
fetched: /t/t_pkey/1/2/y -> /2.00
fetched: /t/t_pkey/1/2/z -> /1

# Regression test for #131860.

statement ok
CREATE TABLE abc (a INT NOT NULL, b FLOAT NOT NULL, c INT, FAMILY (a), FAMILY (b), FAMILY (c))

statement ok
INSERT INTO abc VALUES (4, -0, 6)

statement ok
ALTER TABLE abc ADD PRIMARY KEY (a, b)

statement ok
UPDATE abc SET c = NULL WHERE a = 4 AND b = -0

query IFI
SELECT * FROM abc
----
4 -0 NULL

statement ok
UPDATE abc SET b = 0 WHERE a = 4 AND b = -0;

query IFI
SELECT * FROM abc
----
4 0 NULL
4 changes: 2 additions & 2 deletions pkg/sql/row/deleter.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func (rd *Deleter) encodeValueForPrimaryIndexFamily(
if !ok {
return roachpb.Value{}, nil
}
if rd.Helper.SkipColumnNotInPrimaryIndexValue(family.DefaultColumnID, values[idx]) {
if skip, _ := rd.Helper.SkipColumnNotInPrimaryIndexValue(family.DefaultColumnID, values[idx]); skip {
return roachpb.Value{}, nil
}
typ := rd.FetchCols[idx].GetType()
Expand All @@ -218,7 +218,7 @@ func (rd *Deleter) encodeValueForPrimaryIndexFamily(
continue
}

if skip := rd.Helper.SkipColumnNotInPrimaryIndexValue(colID, values[idx]); skip {
if skip, _ := rd.Helper.SkipColumnNotInPrimaryIndexValue(colID, values[idx]); skip {
continue
}

Expand Down
11 changes: 6 additions & 5 deletions pkg/sql/row/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,25 +210,26 @@ func (rh *RowHelper) encodeSecondaryIndexes(
// SkipColumnNotInPrimaryIndexValue returns true if the value at column colID
// does not need to be encoded, either because it is already part of the primary
// key, or because it is not part of the primary index altogether. Composite
// datums are considered too, so a composite datum in a PK will return false.
// datums are considered too, so a composite datum in a PK will return false
// (but will return true for couldBeComposite).
func (rh *RowHelper) SkipColumnNotInPrimaryIndexValue(
colID descpb.ColumnID, value tree.Datum,
) bool {
) (skip, couldBeComposite bool) {
if rh.primaryIndexKeyCols.Empty() {
rh.primaryIndexKeyCols = rh.TableDesc.GetPrimaryIndex().CollectKeyColumnIDs()
rh.primaryIndexValueCols = rh.TableDesc.GetPrimaryIndex().CollectPrimaryStoredColumnIDs()
}
if !rh.primaryIndexKeyCols.Contains(colID) {
return !rh.primaryIndexValueCols.Contains(colID)
return !rh.primaryIndexValueCols.Contains(colID), false
}
if cdatum, ok := value.(tree.CompositeDatum); ok {
// Composite columns are encoded in both the key and the value.
return !cdatum.IsComposite()
return !cdatum.IsComposite(), true
}
// Skip primary key columns as their values are encoded in the key of
// each family. Family 0 is guaranteed to exist and acts as a
// sentinel.
return true
return true, false
}

func (rh *RowHelper) SortedColumnFamily(famID descpb.FamilyID) ([]descpb.ColumnID, bool) {
Expand Down
54 changes: 35 additions & 19 deletions pkg/sql/row/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,38 +136,54 @@ func prepareInsertOrUpdateBatch(
if !ok {
continue
}

var marshaled roachpb.Value
var err error
typ := fetchedCols[idx].GetType()

// Skip any values with a default ID not stored in the primary index,
// which can happen if we are adding new columns.
if skip := helper.SkipColumnNotInPrimaryIndexValue(family.DefaultColumnID, values[idx]); skip {
continue
}
typ := fetchedCols[idx].GetType()
marshaled, err := valueside.MarshalLegacy(typ, values[idx])
if err != nil {
return nil, err
skip, couldBeComposite := helper.SkipColumnNotInPrimaryIndexValue(family.DefaultColumnID, values[idx])
if skip {
// If the column could be composite, there could be a previous KV, so we
// still need to issue a Delete.
if !couldBeComposite {
continue
}
} else {
marshaled, err = valueside.MarshalLegacy(typ, values[idx])
if err != nil {
return nil, err
}
}

// TODO(ssd): Here and below investigate reducing the
// number of allocations required to marshal the old
// value.
var oldVal []byte
if oth.IsSet() && len(oldValues) > 0 {
old, err := valueside.MarshalLegacy(typ, oldValues[idx])
if err != nil {
return nil, err
// If the column could be composite, we only encode the old value if it
// was a composite value.
if !couldBeComposite || oldValues[idx].(tree.CompositeDatum).IsComposite() {
Copy link
Collaborator

@stevendanna stevendanna Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can't go wrong with a comment here, because to be honest I'm finding it hard to follow. Let me try to think this through in realtime here in this comment:

We are encoding a single-column, non-family-0 column family. So, our expected bytes should be:

  1. Nil if the value is NULL.
  2. Nil is the value is non-NULL but exists complete in the primary key (i.e. is in the primary key and is not a composite value).
  3. Non nil otherwise.

Unless I'm reading wrongly, I think this code probably is not quite right for case 1. since I think TagAndDataBytes will explode if old was encoded with just nil, so we might have to wrap that in an 'IsPresent()' call.

EDIT: I'm thinking that we should fix this up if possible but that we also err towards getting some of these fixes merged an perhaps putting an assertion at the top of this function that returns an error if we call it the OriginTimestampCPutHelper set and > 1 column families just to reflect the fact that we clearly need some follow-up testing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a little comment, and wrapped the call to TagAndDataBytes in a check for IsPresent.

(I'll leave the assertions up to you in your PR.)

old, err := valueside.MarshalLegacy(typ, oldValues[idx])
if err != nil {
return nil, err
}
if old.IsPresent() {
oldVal = old.TagAndDataBytes()
}
}
oldVal = old.TagAndDataBytes()
}

if marshaled.RawBytes == nil {
if overwrite {
if !marshaled.IsPresent() {
if oth.IsSet() {
// If using OriginTimestamp'd CPuts, we _always_ want to issue a Delete
// so that we can confirm our expected bytes were correct.
oth.DelWithCPut(ctx, batch, kvKey, oldVal, traceKV)
} else if overwrite {
// If the new family contains a NULL value, then we must
// delete any pre-existing row.
if oth.IsSet() {
oth.DelWithCPut(ctx, batch, kvKey, oldVal, traceKV)
} else {
insertDelFn(ctx, batch, kvKey, traceKV)
}
insertDelFn(ctx, batch, kvKey, traceKV)
}
} else {
// We only output non-NULL values. Non-existent column keys are
Expand Down Expand Up @@ -203,7 +219,7 @@ func prepareInsertOrUpdateBatch(
continue
}

if skip := helper.SkipColumnNotInPrimaryIndexValue(colID, values[idx]); skip {
if skip, _ := helper.SkipColumnNotInPrimaryIndexValue(colID, values[idx]); skip {
continue
}

Expand Down
Loading