diff --git a/pkg/kv/kvserver/client_replica_test.go b/pkg/kv/kvserver/client_replica_test.go index 71c9b0a0e844..a3d12df4fc79 100644 --- a/pkg/kv/kvserver/client_replica_test.go +++ b/pkg/kv/kvserver/client_replica_test.go @@ -3356,15 +3356,15 @@ func TestProposalOverhead(t *testing.T) { defer tc.Stopper().Stop(ctx) db := tc.Server(0).DB() - // NB: the expected overhead reflects the space overhead currently - // present in Raft commands. This test will fail if that overhead - // changes. Try to make this number go down and not up. It slightly - // undercounts because our proposal filter is called before - // maxLeaseIndex is filled in. The difference between the user and system - // overhead is that users ranges do not have rangefeeds on by default whereas - // system ranges do. + // NB: the expected overhead reflects the space overhead currently present + // in Raft commands. This test will fail if that overhead changes. Try to + // make this number go down and not up. It slightly undercounts because our + // proposal filter is called before MaxLeaseIndex or ClosedTimestamp are + // filled in. The difference between the user and system overhead is that + // users ranges do not have rangefeeds on by default whereas system ranges + // do. const ( - expectedUserOverhead uint32 = 45 + expectedUserOverhead uint32 = 42 ) t.Run("user-key overhead", func(t *testing.T) { userKey := tc.ScratchRange(t) diff --git a/pkg/kv/kvserver/kvserverpb/proposer_kv.go b/pkg/kv/kvserver/kvserverpb/proposer_kv.go index 14fc1ca2d064..801c05ceb86d 100644 --- a/pkg/kv/kvserver/kvserverpb/proposer_kv.go +++ b/pkg/kv/kvserver/kvserverpb/proposer_kv.go @@ -16,7 +16,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/hlc" ) -var maxRaftCommandFooterSize = (&RaftCommandFooter{ +var maxMaxLeaseFooterSize = (&MaxLeaseFooter{ MaxLeaseIndex: math.MaxUint64, }).Size() @@ -28,10 +28,10 @@ var maxClosedTimestampFooterSize = (&ClosedTimestampFooter{ }, }).Size() -// MaxRaftCommandFooterSize returns the maximum possible size of an -// encoded RaftCommandFooter proto. -func MaxRaftCommandFooterSize() int { - return maxRaftCommandFooterSize +// MaxMaxLeaseFooterSize returns the maximum possible size of an encoded +// MaxLeaseFooter proto. +func MaxMaxLeaseFooterSize() int { + return maxMaxLeaseFooterSize } // MaxClosedTimestampFooterSize returns the maximmum possible size of an encoded diff --git a/pkg/kv/kvserver/kvserverpb/proposer_kv.pb.go b/pkg/kv/kvserver/kvserverpb/proposer_kv.pb.go index ae64003650e3..6e5a6185c5a2 100644 --- a/pkg/kv/kvserver/kvserverpb/proposer_kv.pb.go +++ b/pkg/kv/kvserver/kvserverpb/proposer_kv.pb.go @@ -48,7 +48,7 @@ func (m *Split) Reset() { *m = Split{} } func (m *Split) String() string { return proto.CompactTextString(m) } func (*Split) ProtoMessage() {} func (*Split) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{0} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{0} } func (m *Split) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -83,7 +83,7 @@ func (m *Merge) Reset() { *m = Merge{} } func (m *Merge) String() string { return proto.CompactTextString(m) } func (*Merge) ProtoMessage() {} func (*Merge) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{1} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{1} } func (m *Merge) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -117,7 +117,7 @@ type ChangeReplicas struct { func (m *ChangeReplicas) Reset() { *m = ChangeReplicas{} } func (*ChangeReplicas) ProtoMessage() {} func (*ChangeReplicas) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{2} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{2} } func (m *ChangeReplicas) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -169,7 +169,7 @@ func (m *ComputeChecksum) Reset() { *m = ComputeChecksum{} } func (m *ComputeChecksum) String() string { return proto.CompactTextString(m) } func (*ComputeChecksum) ProtoMessage() {} func (*ComputeChecksum) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{3} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{3} } func (m *ComputeChecksum) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -206,7 +206,7 @@ func (m *Compaction) Reset() { *m = Compaction{} } func (m *Compaction) String() string { return proto.CompactTextString(m) } func (*Compaction) ProtoMessage() {} func (*Compaction) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{4} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{4} } func (m *Compaction) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -243,7 +243,7 @@ func (m *SuggestedCompaction) Reset() { *m = SuggestedCompaction{} } func (m *SuggestedCompaction) String() string { return proto.CompactTextString(m) } func (*SuggestedCompaction) ProtoMessage() {} func (*SuggestedCompaction) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{5} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{5} } func (m *SuggestedCompaction) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -305,7 +305,7 @@ func (m *ReplicatedEvalResult) Reset() { *m = ReplicatedEvalResult{} } func (m *ReplicatedEvalResult) String() string { return proto.CompactTextString(m) } func (*ReplicatedEvalResult) ProtoMessage() {} func (*ReplicatedEvalResult) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{6} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{6} } func (m *ReplicatedEvalResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -349,7 +349,7 @@ func (m *ReplicatedEvalResult_AddSSTable) Reset() { *m = ReplicatedEvalR func (m *ReplicatedEvalResult_AddSSTable) String() string { return proto.CompactTextString(m) } func (*ReplicatedEvalResult_AddSSTable) ProtoMessage() {} func (*ReplicatedEvalResult_AddSSTable) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{6, 0} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{6, 0} } func (m *ReplicatedEvalResult_AddSSTable) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -386,7 +386,7 @@ func (m *WriteBatch) Reset() { *m = WriteBatch{} } func (m *WriteBatch) String() string { return proto.CompactTextString(m) } func (*WriteBatch) ProtoMessage() {} func (*WriteBatch) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{7} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{7} } func (m *WriteBatch) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -423,7 +423,7 @@ func (m *LogicalOpLog) Reset() { *m = LogicalOpLog{} } func (m *LogicalOpLog) String() string { return proto.CompactTextString(m) } func (*LogicalOpLog) ProtoMessage() {} func (*LogicalOpLog) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{8} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{8} } func (m *LogicalOpLog) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -506,7 +506,12 @@ type RaftCommand struct { // updated accordingly. Managing retry of proposals becomes trickier as // well as that uproots whatever ordering was originally envisioned. // - // This field is set through RaftCommandFooter hackery. + // This field is set through MaxLeaseFooter hackery. Unlike with the + // ClosedTimestamp, which needs to be nullable in this proto (see comment), + // there are no nullability concerns with this field. This is because + // max_lease_index is a primitive type, so it does not get encoded when zero. + // This alone ensures that the field is not encoded twice in the combined + // RaftCommand+MaxLeaseFooter proto. MaxLeaseIndex uint64 `protobuf:"varint,4,opt,name=max_lease_index,json=maxLeaseIndex,proto3" json:"max_lease_index,omitempty"` // The closed timestamp carried by this command. Once a follower is told to // apply this command, it knows that there will be no further writes at @@ -519,8 +524,11 @@ type RaftCommand struct { // in a command-specific way. If the value is not zero, the value is greater // or equal to that of the previous commands (and all before it). // - // This field is set through ClosedTimestampFooter hackery. - ClosedTimestamp hlc.Timestamp `protobuf:"bytes,17,opt,name=closed_timestamp,json=closedTimestamp,proto3" json:"closed_timestamp"` + // This field is set through ClosedTimestampFooter hackery. Unlike in the + // ClosedTimestampFooter, the field is nullable here so that it does not get + // encoded when empty. This prevents the field from being encoded twice in the + // combined RaftCommand+ClosedTimestampFooter proto. + ClosedTimestamp *hlc.Timestamp `protobuf:"bytes,17,opt,name=closed_timestamp,json=closedTimestamp,proto3" json:"closed_timestamp,omitempty"` // replicated_eval_result is a set of structured information that instructs // replicated state changes to the part of a Range's replicated state machine // that exists outside of RocksDB. @@ -544,7 +552,7 @@ func (m *RaftCommand) Reset() { *m = RaftCommand{} } func (m *RaftCommand) String() string { return proto.CompactTextString(m) } func (*RaftCommand) ProtoMessage() {} func (*RaftCommand) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{9} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{9} } func (m *RaftCommand) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -569,26 +577,26 @@ func (m *RaftCommand) XXX_DiscardUnknown() { var xxx_messageInfo_RaftCommand proto.InternalMessageInfo -// RaftCommandFooter contains a subset of the fields in RaftCommand. It is used +// MaxLeaseFooter contains a subset of the fields in RaftCommand. It is used // to optimize a pattern where most of the fields in RaftCommand are marshaled // outside of a heavily contended critical section, except for the fields in the // footer, which are assigned and marhsaled inside of the critical section and // appended to the marshaled byte buffer. This minimizes the memory allocation // and marshaling work performed under lock. -type RaftCommandFooter struct { +type MaxLeaseFooter struct { MaxLeaseIndex uint64 `protobuf:"varint,4,opt,name=max_lease_index,json=maxLeaseIndex,proto3" json:"max_lease_index,omitempty"` } -func (m *RaftCommandFooter) Reset() { *m = RaftCommandFooter{} } -func (m *RaftCommandFooter) String() string { return proto.CompactTextString(m) } -func (*RaftCommandFooter) ProtoMessage() {} -func (*RaftCommandFooter) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{10} +func (m *MaxLeaseFooter) Reset() { *m = MaxLeaseFooter{} } +func (m *MaxLeaseFooter) String() string { return proto.CompactTextString(m) } +func (*MaxLeaseFooter) ProtoMessage() {} +func (*MaxLeaseFooter) Descriptor() ([]byte, []int) { + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{10} } -func (m *RaftCommandFooter) XXX_Unmarshal(b []byte) error { +func (m *MaxLeaseFooter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *RaftCommandFooter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MaxLeaseFooter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { b = b[:cap(b)] n, err := m.MarshalTo(b) if err != nil { @@ -596,22 +604,25 @@ func (m *RaftCommandFooter) XXX_Marshal(b []byte, deterministic bool) ([]byte, e } return b[:n], nil } -func (dst *RaftCommandFooter) XXX_Merge(src proto.Message) { - xxx_messageInfo_RaftCommandFooter.Merge(dst, src) +func (dst *MaxLeaseFooter) XXX_Merge(src proto.Message) { + xxx_messageInfo_MaxLeaseFooter.Merge(dst, src) } -func (m *RaftCommandFooter) XXX_Size() int { +func (m *MaxLeaseFooter) XXX_Size() int { return m.Size() } -func (m *RaftCommandFooter) XXX_DiscardUnknown() { - xxx_messageInfo_RaftCommandFooter.DiscardUnknown(m) +func (m *MaxLeaseFooter) XXX_DiscardUnknown() { + xxx_messageInfo_MaxLeaseFooter.DiscardUnknown(m) } -var xxx_messageInfo_RaftCommandFooter proto.InternalMessageInfo +var xxx_messageInfo_MaxLeaseFooter proto.InternalMessageInfo -// ClosedTimestampFooter is similar to RaftCommandFooter, allowing the proposal +// ClosedTimestampFooter is similar to MaxLeaseFooter, allowing the proposal // buffer to fill in the closed_timestamp field after most of the proto has been // marshaled already. type ClosedTimestampFooter struct { + // NOTE: unlike in RaftCommand, there's no reason to make this field nullable. + // If we don't want to include the field, we don't need to append the encoded + // footer to an encoded RaftCommand buffer. ClosedTimestamp hlc.Timestamp `protobuf:"bytes,17,opt,name=closed_timestamp,json=closedTimestamp,proto3" json:"closed_timestamp"` } @@ -619,7 +630,7 @@ func (m *ClosedTimestampFooter) Reset() { *m = ClosedTimestampFooter{} } func (m *ClosedTimestampFooter) String() string { return proto.CompactTextString(m) } func (*ClosedTimestampFooter) ProtoMessage() {} func (*ClosedTimestampFooter) Descriptor() ([]byte, []int) { - return fileDescriptor_proposer_kv_0b3536bd0bf3d98c, []int{11} + return fileDescriptor_proposer_kv_7221db15568d8c0d, []int{11} } func (m *ClosedTimestampFooter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -657,7 +668,7 @@ func init() { proto.RegisterType((*LogicalOpLog)(nil), "cockroach.kv.kvserver.storagepb.LogicalOpLog") proto.RegisterType((*RaftCommand)(nil), "cockroach.kv.kvserver.storagepb.RaftCommand") proto.RegisterMapType((map[string]string)(nil), "cockroach.kv.kvserver.storagepb.RaftCommand.TraceDataEntry") - proto.RegisterType((*RaftCommandFooter)(nil), "cockroach.kv.kvserver.storagepb.RaftCommandFooter") + proto.RegisterType((*MaxLeaseFooter)(nil), "cockroach.kv.kvserver.storagepb.MaxLeaseFooter") proto.RegisterType((*ClosedTimestampFooter)(nil), "cockroach.kv.kvserver.storagepb.ClosedTimestampFooter") } func (this *Split) Equal(that interface{}) bool { @@ -1366,20 +1377,22 @@ func (m *RaftCommand) MarshalTo(dAtA []byte) (int, error) { i += copy(dAtA[i:], v) } } - dAtA[i] = 0x8a - i++ - dAtA[i] = 0x1 - i++ - i = encodeVarintProposerKv(dAtA, i, uint64(m.ClosedTimestamp.Size())) - n21, err := m.ClosedTimestamp.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + if m.ClosedTimestamp != nil { + dAtA[i] = 0x8a + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintProposerKv(dAtA, i, uint64(m.ClosedTimestamp.Size())) + n21, err := m.ClosedTimestamp.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n21 } - i += n21 return i, nil } -func (m *RaftCommandFooter) Marshal() (dAtA []byte, err error) { +func (m *MaxLeaseFooter) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalTo(dAtA) @@ -1389,7 +1402,7 @@ func (m *RaftCommandFooter) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *RaftCommandFooter) MarshalTo(dAtA []byte) (int, error) { +func (m *MaxLeaseFooter) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -1666,12 +1679,14 @@ func (m *RaftCommand) Size() (n int) { n += mapEntrySize + 2 + sovProposerKv(uint64(mapEntrySize)) } } - l = m.ClosedTimestamp.Size() - n += 2 + l + sovProposerKv(uint64(l)) + if m.ClosedTimestamp != nil { + l = m.ClosedTimestamp.Size() + n += 2 + l + sovProposerKv(uint64(l)) + } return n } -func (m *RaftCommandFooter) Size() (n int) { +func (m *MaxLeaseFooter) Size() (n int) { if m == nil { return 0 } @@ -3411,6 +3426,9 @@ func (m *RaftCommand) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } + if m.ClosedTimestamp == nil { + m.ClosedTimestamp = &hlc.Timestamp{} + } if err := m.ClosedTimestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -3436,7 +3454,7 @@ func (m *RaftCommand) Unmarshal(dAtA []byte) error { } return nil } -func (m *RaftCommandFooter) Unmarshal(dAtA []byte) error { +func (m *MaxLeaseFooter) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3459,10 +3477,10 @@ func (m *RaftCommandFooter) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: RaftCommandFooter: wiretype end group for non-group") + return fmt.Errorf("proto: MaxLeaseFooter: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: RaftCommandFooter: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MaxLeaseFooter: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 4: @@ -3691,100 +3709,101 @@ var ( ) func init() { - proto.RegisterFile("kv/kvserver/kvserverpb/proposer_kv.proto", fileDescriptor_proposer_kv_0b3536bd0bf3d98c) -} - -var fileDescriptor_proposer_kv_0b3536bd0bf3d98c = []byte{ - // 1453 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x4f, 0x73, 0x13, 0xc7, - 0x12, 0xb7, 0x2c, 0xc9, 0x5e, 0xb5, 0x6c, 0x69, 0x3d, 0x18, 0xd8, 0xe7, 0xf7, 0x9e, 0xe4, 0xd2, - 0xe3, 0x51, 0x4e, 0x42, 0x56, 0x94, 0x9d, 0x54, 0xa5, 0x80, 0x4a, 0x61, 0xc9, 0x10, 0x2c, 0x6c, - 0x07, 0x46, 0x86, 0xa4, 0xc8, 0x61, 0x6b, 0xb4, 0x3b, 0xac, 0x36, 0x5a, 0x69, 0x97, 0x9d, 0x91, - 0xc0, 0x9f, 0x22, 0x49, 0x55, 0x0e, 0xb9, 0x24, 0xe1, 0x98, 0xaf, 0x91, 0x1b, 0x47, 0x8e, 0x54, - 0x0e, 0xaa, 0x60, 0x2e, 0xf9, 0x0c, 0x9c, 0x52, 0x33, 0x3b, 0xab, 0x3f, 0x29, 0x13, 0x8b, 0x24, - 0xb7, 0xd9, 0x9e, 0xe9, 0x5f, 0xf7, 0xf4, 0x9f, 0x5f, 0xcf, 0xc2, 0x46, 0x67, 0x50, 0xed, 0x0c, - 0x18, 0x8d, 0x06, 0x34, 0x1a, 0x2d, 0xc2, 0x56, 0x35, 0x8c, 0x82, 0x30, 0x60, 0x34, 0xb2, 0x3a, - 0x03, 0x33, 0x8c, 0x02, 0x1e, 0xa0, 0xb2, 0x1d, 0xd8, 0x9d, 0x28, 0x20, 0x76, 0xdb, 0xec, 0x0c, - 0xcc, 0xe4, 0xa8, 0xc9, 0x78, 0x10, 0x11, 0x97, 0x86, 0xad, 0xb5, 0x15, 0xb9, 0x19, 0xb6, 0xaa, - 0x24, 0xf4, 0x62, 0x9d, 0x35, 0x94, 0x88, 0x1c, 0xc2, 0x89, 0x92, 0x9d, 0x4b, 0x64, 0x5d, 0xca, - 0xc9, 0x84, 0xfc, 0xdf, 0x0a, 0xa9, 0x4a, 0x7b, 0xae, 0xd7, 0xa3, 0xe2, 0xc0, 0xc0, 0xb6, 0xd5, - 0xe6, 0x7f, 0x4e, 0xdc, 0xdc, 0x52, 0xbb, 0x95, 0x37, 0x5c, 0x82, 0x71, 0xc2, 0xa9, 0x3a, 0x63, - 0xf4, 0xb9, 0xe7, 0x57, 0xdb, 0xbe, 0x5d, 0xe5, 0x5e, 0x97, 0x32, 0x4e, 0xba, 0xa1, 0xda, 0x59, - 0x75, 0x03, 0x37, 0x90, 0xcb, 0xaa, 0x58, 0xc5, 0xd2, 0xca, 0x4f, 0x29, 0xc8, 0x36, 0x43, 0xdf, - 0xe3, 0xa8, 0x0e, 0x8b, 0x3c, 0xf2, 0x5c, 0x97, 0x46, 0x46, 0x6a, 0x3d, 0xb5, 0x91, 0xdf, 0x2c, - 0x9b, 0xe3, 0x50, 0xa8, 0xcb, 0x98, 0xf2, 0xe8, 0x61, 0x7c, 0xac, 0xa6, 0x3d, 0x1b, 0x96, 0xe7, - 0x9e, 0x0f, 0xcb, 0x29, 0x9c, 0x68, 0xa2, 0x43, 0xc8, 0x45, 0x6d, 0x66, 0x39, 0xd4, 0xe7, 0xc4, - 0x98, 0x97, 0x30, 0xff, 0x9f, 0x80, 0x51, 0xd7, 0x33, 0x93, 0xeb, 0x99, 0xfb, 0xf7, 0xeb, 0xf5, - 0x26, 0x27, 0x9c, 0xd5, 0x74, 0x01, 0x76, 0x3c, 0x2c, 0x6b, 0xf8, 0x56, 0x73, 0x47, 0xa8, 0x63, - 0x2d, 0x6a, 0x33, 0xb9, 0xba, 0x92, 0xf9, 0xed, 0x69, 0x39, 0x55, 0xc1, 0x90, 0xdd, 0xa7, 0x91, - 0x4b, 0x67, 0xf3, 0x54, 0x1e, 0x7d, 0xb3, 0xa7, 0x0a, 0xd3, 0x81, 0x42, 0xbd, 0x4d, 0x7a, 0x2e, - 0xc5, 0x34, 0xf4, 0x3d, 0x9b, 0x30, 0xb4, 0xf7, 0x47, 0xf0, 0x8d, 0x13, 0xc0, 0xa7, 0x75, 0xfe, - 0xcc, 0xca, 0x77, 0x4f, 0xcb, 0x73, 0x95, 0x97, 0xf3, 0x50, 0xac, 0x07, 0xdd, 0xb0, 0xcf, 0x69, - 0xbd, 0x4d, 0xed, 0x0e, 0xeb, 0x77, 0xd1, 0x97, 0x90, 0xb7, 0xd5, 0xda, 0xf2, 0x1c, 0x69, 0x6b, - 0xa9, 0xb6, 0x2b, 0x10, 0x7e, 0x19, 0x96, 0xb7, 0x5c, 0x8f, 0xb7, 0xfb, 0x2d, 0xd3, 0x0e, 0xba, - 0xd5, 0x91, 0x75, 0xa7, 0x35, 0x5e, 0x57, 0xc3, 0x8e, 0x5b, 0x95, 0xa9, 0xee, 0xf7, 0x3d, 0xc7, - 0xbc, 0x77, 0x6f, 0x77, 0xe7, 0x78, 0x58, 0x86, 0x04, 0x7d, 0x77, 0x07, 0x43, 0x82, 0xbe, 0xeb, - 0xa0, 0xff, 0xc1, 0x32, 0x23, 0x03, 0x6a, 0xb1, 0x1e, 0x09, 0x59, 0x3b, 0xe0, 0x32, 0x33, 0x1a, - 0x5e, 0x12, 0xc2, 0xa6, 0x92, 0xa1, 0x2d, 0xc8, 0x74, 0x03, 0x87, 0x1a, 0xe9, 0xf5, 0xd4, 0x46, - 0xe1, 0xc4, 0x90, 0x26, 0xe8, 0xfb, 0x81, 0x43, 0xb1, 0x3c, 0x8c, 0x4a, 0x10, 0xdb, 0x09, 0x03, - 0xaf, 0xc7, 0x8d, 0x8c, 0x84, 0x9d, 0x90, 0x20, 0x03, 0x16, 0x07, 0x34, 0x62, 0x5e, 0xd0, 0x33, - 0xb2, 0xeb, 0xa9, 0x8d, 0x65, 0x9c, 0x7c, 0xa2, 0x5b, 0x90, 0xe3, 0x34, 0xea, 0x7a, 0x3d, 0xc2, - 0xa9, 0xb1, 0xb0, 0x9e, 0xde, 0xc8, 0x6f, 0x5e, 0x38, 0xc1, 0xa6, 0x8a, 0xf1, 0x0e, 0x65, 0x76, - 0xe4, 0x85, 0x3c, 0x88, 0x6a, 0x19, 0x11, 0x23, 0x3c, 0x56, 0x56, 0x99, 0xbc, 0x0f, 0x20, 0x42, - 0x4c, 0x6c, 0x2e, 0xd0, 0x57, 0x21, 0xdb, 0x3a, 0xe2, 0x94, 0xc9, 0xb8, 0xa6, 0x71, 0xfc, 0x81, - 0x2e, 0x01, 0x62, 0x7d, 0xd7, 0xa5, 0x8c, 0x53, 0xc7, 0x22, 0xdc, 0xea, 0x91, 0x5e, 0xc0, 0x64, - 0x30, 0xd2, 0x58, 0x1f, 0xed, 0x6c, 0xf3, 0x03, 0x21, 0x57, 0xb8, 0xdf, 0xce, 0xc3, 0x99, 0x66, - 0xb2, 0x35, 0x61, 0xe1, 0x2e, 0xe4, 0x18, 0x27, 0x11, 0xb7, 0x3a, 0xf4, 0x48, 0x65, 0xef, 0x83, - 0xd7, 0xc3, 0xf2, 0xe5, 0x99, 0x32, 0x97, 0xdc, 0xee, 0x36, 0x3d, 0xc2, 0x9a, 0x84, 0xb9, 0x4d, - 0x8f, 0xd0, 0x3e, 0x2c, 0xd2, 0x9e, 0x23, 0x01, 0xe7, 0xff, 0x06, 0xe0, 0x02, 0xed, 0x39, 0x02, - 0xee, 0x1e, 0x80, 0x3d, 0xf2, 0x57, 0xa6, 0x35, 0xbf, 0xf9, 0x9e, 0x79, 0x0a, 0xbd, 0x99, 0xe3, - 0x2b, 0x4e, 0xd4, 0xf3, 0x04, 0x90, 0x0a, 0xcb, 0xcf, 0x1a, 0xac, 0xaa, 0xdc, 0x70, 0xea, 0xdc, - 0x18, 0x10, 0x1f, 0x53, 0xd6, 0xf7, 0x05, 0x8d, 0x64, 0x25, 0x1f, 0xa9, 0xee, 0x7f, 0xff, 0x54, - 0x83, 0x0a, 0x45, 0xb0, 0x00, 0xc5, 0xb1, 0x2e, 0xba, 0x06, 0x59, 0x26, 0x98, 0x46, 0x79, 0x7d, - 0xf1, 0x54, 0x10, 0xc9, 0x4b, 0x38, 0x56, 0x12, 0xda, 0x5d, 0xd1, 0xfd, 0xb2, 0x1e, 0x67, 0xd1, - 0x96, 0x5c, 0x81, 0x63, 0x25, 0xb4, 0x01, 0xba, 0xc7, 0x2c, 0x9f, 0x12, 0x46, 0xad, 0x88, 0x3e, - 0xea, 0x53, 0xc6, 0x8d, 0x05, 0x59, 0xd8, 0x05, 0x8f, 0xed, 0x09, 0x31, 0x8e, 0xa5, 0x68, 0x1b, - 0x72, 0x23, 0x92, 0x35, 0x34, 0x69, 0xeb, 0xbf, 0x13, 0xb6, 0x44, 0x7b, 0x9a, 0x6d, 0xdf, 0x36, - 0x0f, 0x93, 0x43, 0xa3, 0xda, 0x4d, 0x04, 0xe8, 0x0e, 0xe8, 0x0e, 0x0d, 0x23, 0x2a, 0xa3, 0xa8, - 0x68, 0x13, 0xde, 0x82, 0x36, 0x71, 0x71, 0xac, 0x2e, 0xb9, 0x12, 0x7d, 0x0e, 0x45, 0x5b, 0xb2, - 0x93, 0x15, 0x29, 0x7a, 0x32, 0x96, 0x24, 0x60, 0xf5, 0xf4, 0xd4, 0x4f, 0xb1, 0x1a, 0x2e, 0xd8, - 0xd3, 0xcc, 0x78, 0x01, 0x0a, 0x11, 0x79, 0xc8, 0x2d, 0x3f, 0x70, 0x95, 0xa7, 0xcb, 0xb2, 0x73, - 0x96, 0x84, 0x74, 0x2f, 0x70, 0x63, 0xfb, 0x8f, 0x20, 0x4f, 0x1c, 0xc7, 0x62, 0x8c, 0x93, 0x96, - 0x4f, 0x8d, 0x15, 0x69, 0xfb, 0xfa, 0xac, 0x55, 0x30, 0x55, 0x4b, 0xe6, 0xb6, 0xe3, 0x34, 0x9b, - 0x87, 0x02, 0xa7, 0x56, 0x10, 0xf4, 0x36, 0xfe, 0xc6, 0x40, 0x1c, 0xa7, 0x19, 0xdb, 0x40, 0x37, - 0x21, 0x1b, 0xfb, 0x83, 0xa4, 0xb1, 0x77, 0x67, 0x8a, 0x9c, 0xf4, 0x56, 0x25, 0x24, 0x56, 0x47, - 0x5f, 0xa5, 0xe0, 0x4c, 0x18, 0xd1, 0x81, 0x4a, 0x7e, 0xfc, 0x36, 0x20, 0xbe, 0xb1, 0x3a, 0x4b, - 0x6a, 0xaf, 0xbf, 0x1e, 0x96, 0xaf, 0xcd, 0x4e, 0xdb, 0x42, 0xb9, 0xee, 0x07, 0x76, 0x67, 0x84, - 0x80, 0x57, 0x84, 0x6d, 0x59, 0x60, 0x77, 0x94, 0x65, 0xf4, 0x05, 0xe8, 0x76, 0x3c, 0x37, 0xac, - 0x84, 0xce, 0x8d, 0xb3, 0xd2, 0x9b, 0xcb, 0x33, 0x35, 0xf2, 0xc4, 0xc0, 0xc1, 0x45, 0x7b, 0x5a, - 0xb0, 0xf6, 0x09, 0x4c, 0x04, 0x14, 0x21, 0xc8, 0x88, 0x57, 0x4a, 0x4c, 0x65, 0x58, 0xae, 0x51, - 0x19, 0xb2, 0x76, 0x64, 0x6f, 0x6d, 0xca, 0x5e, 0x5e, 0xae, 0xe5, 0x8e, 0x87, 0xe5, 0x6c, 0x1d, - 0xd7, 0xb7, 0x36, 0x71, 0x2c, 0x8f, 0xb9, 0xa0, 0x91, 0xd1, 0x52, 0xfa, 0x7c, 0x23, 0xa3, 0x65, - 0xf5, 0x85, 0x46, 0x46, 0x5b, 0xd4, 0xb5, 0x46, 0x46, 0xcb, 0xe9, 0xd0, 0xc8, 0x68, 0x05, 0xbd, - 0xd8, 0xc8, 0x68, 0x45, 0x5d, 0x6f, 0x64, 0x34, 0x5d, 0x5f, 0x69, 0x64, 0xb4, 0x33, 0xfa, 0x6a, - 0x63, 0x41, 0xfb, 0xe6, 0x40, 0xff, 0xe1, 0xa0, 0xb2, 0x0e, 0xf0, 0x59, 0xe4, 0x71, 0x5a, 0x23, - 0xdc, 0x6e, 0x9f, 0xe4, 0x40, 0xe5, 0x2e, 0x2c, 0xed, 0x05, 0xae, 0x67, 0x13, 0xff, 0xd3, 0x70, - 0x2f, 0x70, 0xd1, 0x36, 0xa4, 0x83, 0x50, 0x90, 0xba, 0x18, 0x17, 0xef, 0x9c, 0x96, 0xe7, 0x91, - 0xaa, 0x4a, 0xb3, 0xd0, 0xad, 0x7c, 0xbf, 0x00, 0x79, 0x4c, 0x1e, 0xf2, 0x7a, 0xd0, 0xed, 0x92, + proto.RegisterFile("kv/kvserver/kvserverpb/proposer_kv.proto", fileDescriptor_proposer_kv_7221db15568d8c0d) +} + +var fileDescriptor_proposer_kv_7221db15568d8c0d = []byte{ + // 1460 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x57, 0x5f, 0x6f, 0x13, 0xc7, + 0x16, 0x8f, 0x63, 0x3b, 0x59, 0x1f, 0x27, 0xf6, 0x66, 0x08, 0xb0, 0x37, 0xf7, 0x5e, 0x3b, 0xf2, + 0xe5, 0xa2, 0xdc, 0x5b, 0xba, 0x46, 0x49, 0x2b, 0x21, 0x8a, 0x2a, 0x62, 0x07, 0x4a, 0x4c, 0x92, + 0xc2, 0x38, 0xd0, 0x8a, 0x3e, 0xac, 0xc6, 0xbb, 0xc3, 0x7a, 0xeb, 0xb5, 0x77, 0xd9, 0x19, 0x1b, + 0xf2, 0x29, 0xda, 0x4a, 0x95, 0xda, 0xa7, 0x96, 0xc7, 0x7e, 0x8d, 0xbe, 0xf1, 0xc8, 0x23, 0xea, + 0x83, 0x55, 0xc2, 0x4b, 0x3f, 0x03, 0x4f, 0xd5, 0xcc, 0xce, 0xfa, 0x0f, 0x0a, 0x8d, 0xa1, 0x6f, + 0xb3, 0x67, 0xe6, 0xfc, 0xce, 0x99, 0xf3, 0xe7, 0x77, 0x66, 0x61, 0xa3, 0x33, 0xa8, 0x76, 0x06, + 0x8c, 0x46, 0x03, 0x1a, 0x8d, 0x16, 0x61, 0xab, 0x1a, 0x46, 0x41, 0x18, 0x30, 0x1a, 0x59, 0x9d, + 0x81, 0x19, 0x46, 0x01, 0x0f, 0x50, 0xd9, 0x0e, 0xec, 0x4e, 0x14, 0x10, 0xbb, 0x6d, 0x76, 0x06, + 0x66, 0x72, 0xd4, 0x64, 0x3c, 0x88, 0x88, 0x4b, 0xc3, 0xd6, 0xda, 0x8a, 0xdc, 0x0c, 0x5b, 0x55, + 0x12, 0x7a, 0xb1, 0xce, 0x1a, 0x4a, 0x44, 0x0e, 0xe1, 0x44, 0xc9, 0xce, 0x25, 0xb2, 0x2e, 0xe5, + 0x64, 0x42, 0xfe, 0x4f, 0x85, 0x54, 0xa5, 0x3d, 0xd7, 0xeb, 0x51, 0x71, 0x60, 0x60, 0xdb, 0x6a, + 0xf3, 0x5f, 0x27, 0x6e, 0x6e, 0xa9, 0xdd, 0xca, 0x5b, 0x2e, 0xc1, 0x38, 0xe1, 0x54, 0x9d, 0x31, + 0xfa, 0xdc, 0xf3, 0xab, 0x6d, 0xdf, 0xae, 0x72, 0xaf, 0x4b, 0x19, 0x27, 0xdd, 0x50, 0xed, 0xac, + 0xba, 0x81, 0x1b, 0xc8, 0x65, 0x55, 0xac, 0x62, 0x69, 0xe5, 0x97, 0x14, 0x64, 0x9b, 0xa1, 0xef, + 0x71, 0x54, 0x87, 0x45, 0x1e, 0x79, 0xae, 0x4b, 0x23, 0x23, 0xb5, 0x9e, 0xda, 0xc8, 0x6f, 0x96, + 0xcd, 0x71, 0x28, 0xd4, 0x65, 0x4c, 0x79, 0xf4, 0x30, 0x3e, 0x56, 0xd3, 0x9e, 0x0d, 0xcb, 0x73, + 0xcf, 0x87, 0xe5, 0x14, 0x4e, 0x34, 0xd1, 0x21, 0xe4, 0xa2, 0x36, 0xb3, 0x1c, 0xea, 0x73, 0x62, + 0xcc, 0x4b, 0x98, 0xff, 0x4e, 0xc0, 0xa8, 0xeb, 0x99, 0xc9, 0xf5, 0xcc, 0xfd, 0xfb, 0xf5, 0x7a, + 0x93, 0x13, 0xce, 0x6a, 0xba, 0x00, 0x3b, 0x1e, 0x96, 0x35, 0x7c, 0xab, 0xb9, 0x23, 0xd4, 0xb1, + 0x16, 0xb5, 0x99, 0x5c, 0x5d, 0xcd, 0xfc, 0xf1, 0xb4, 0x9c, 0xaa, 0x60, 0xc8, 0xee, 0xd3, 0xc8, + 0xa5, 0xb3, 0x79, 0x2a, 0x8f, 0xbe, 0xdd, 0x53, 0x85, 0xe9, 0x40, 0xa1, 0xde, 0x26, 0x3d, 0x97, + 0x62, 0x1a, 0xfa, 0x9e, 0x4d, 0x18, 0xda, 0x7b, 0x13, 0x7c, 0xe3, 0x04, 0xf0, 0x69, 0x9d, 0xbf, + 0xb2, 0xf2, 0xe3, 0xd3, 0xf2, 0x5c, 0xe5, 0xe5, 0x3c, 0x14, 0xeb, 0x41, 0x37, 0xec, 0x73, 0x5a, + 0x6f, 0x53, 0xbb, 0xc3, 0xfa, 0x5d, 0xf4, 0x35, 0xe4, 0x6d, 0xb5, 0xb6, 0x3c, 0x47, 0xda, 0x5a, + 0xaa, 0xed, 0x0a, 0x84, 0xdf, 0x86, 0xe5, 0x2d, 0xd7, 0xe3, 0xed, 0x7e, 0xcb, 0xb4, 0x83, 0x6e, + 0x75, 0x64, 0xdd, 0x69, 0x8d, 0xd7, 0xd5, 0xb0, 0xe3, 0x56, 0x65, 0xaa, 0xfb, 0x7d, 0xcf, 0x31, + 0xef, 0xdd, 0xdb, 0xdd, 0x39, 0x1e, 0x96, 0x21, 0x41, 0xdf, 0xdd, 0xc1, 0x90, 0xa0, 0xef, 0x3a, + 0xe8, 0x3f, 0xb0, 0xcc, 0xc8, 0x80, 0x5a, 0xac, 0x47, 0x42, 0xd6, 0x0e, 0xb8, 0xcc, 0x8c, 0x86, + 0x97, 0x84, 0xb0, 0xa9, 0x64, 0x68, 0x0b, 0x32, 0xdd, 0xc0, 0xa1, 0x46, 0x7a, 0x3d, 0xb5, 0x51, + 0x38, 0x31, 0xa4, 0x09, 0xfa, 0x7e, 0xe0, 0x50, 0x2c, 0x0f, 0xa3, 0x12, 0xc4, 0x76, 0xc2, 0xc0, + 0xeb, 0x71, 0x23, 0x23, 0x61, 0x27, 0x24, 0xc8, 0x80, 0xc5, 0x01, 0x8d, 0x98, 0x17, 0xf4, 0x8c, + 0xec, 0x7a, 0x6a, 0x63, 0x19, 0x27, 0x9f, 0xe8, 0x16, 0xe4, 0x38, 0x8d, 0xba, 0x5e, 0x8f, 0x70, + 0x6a, 0x2c, 0xac, 0xa7, 0x37, 0xf2, 0x9b, 0x17, 0x4e, 0xb0, 0xa9, 0x62, 0xbc, 0x43, 0x99, 0x1d, + 0x79, 0x21, 0x0f, 0xa2, 0x5a, 0x46, 0xc4, 0x08, 0x8f, 0x95, 0x55, 0x26, 0xef, 0x03, 0x88, 0x10, + 0x13, 0x9b, 0x0b, 0xf4, 0x55, 0xc8, 0xb6, 0x8e, 0x38, 0x65, 0x32, 0xae, 0x69, 0x1c, 0x7f, 0xa0, + 0x4b, 0x80, 0x58, 0xdf, 0x75, 0x29, 0xe3, 0xd4, 0xb1, 0x08, 0xb7, 0x7a, 0xa4, 0x17, 0x30, 0x19, + 0x8c, 0x34, 0xd6, 0x47, 0x3b, 0xdb, 0xfc, 0x40, 0xc8, 0x15, 0xee, 0xf7, 0xf3, 0x70, 0xa6, 0x99, + 0x6c, 0x4d, 0x58, 0xb8, 0x0b, 0x39, 0xc6, 0x49, 0xc4, 0xad, 0x0e, 0x3d, 0x52, 0xd9, 0xfb, 0xe8, + 0xf5, 0xb0, 0x7c, 0x79, 0xa6, 0xcc, 0x25, 0xb7, 0xbb, 0x4d, 0x8f, 0xb0, 0x26, 0x61, 0x6e, 0xd3, + 0x23, 0xb4, 0x0f, 0x8b, 0xb4, 0xe7, 0x48, 0xc0, 0xf9, 0xbf, 0x01, 0xb8, 0x40, 0x7b, 0x8e, 0x80, + 0xbb, 0x07, 0x60, 0x8f, 0xfc, 0x95, 0x69, 0xcd, 0x6f, 0x7e, 0x60, 0x9e, 0x42, 0x6f, 0xe6, 0xf8, + 0x8a, 0x13, 0xf5, 0x3c, 0x01, 0xa4, 0xc2, 0xf2, 0xab, 0x06, 0xab, 0x2a, 0x37, 0x9c, 0x3a, 0x37, + 0x06, 0xc4, 0xc7, 0x94, 0xf5, 0x7d, 0x41, 0x23, 0x59, 0xc9, 0x47, 0xaa, 0xfb, 0x3f, 0x3c, 0xd5, + 0xa0, 0x42, 0x11, 0x2c, 0x40, 0x71, 0xac, 0x8b, 0xae, 0x41, 0x96, 0x09, 0xa6, 0x51, 0x5e, 0x5f, + 0x3c, 0x15, 0x44, 0xf2, 0x12, 0x8e, 0x95, 0x84, 0x76, 0x57, 0x74, 0xbf, 0xac, 0xc7, 0x59, 0xb4, + 0x25, 0x57, 0xe0, 0x58, 0x09, 0x6d, 0x80, 0xee, 0x31, 0xcb, 0xa7, 0x84, 0x51, 0x2b, 0xa2, 0x8f, + 0xfa, 0x94, 0x71, 0x63, 0x41, 0x16, 0x76, 0xc1, 0x63, 0x7b, 0x42, 0x8c, 0x63, 0x29, 0xda, 0x86, + 0xdc, 0x88, 0x64, 0x0d, 0x4d, 0xda, 0xfa, 0xf7, 0x84, 0x2d, 0xd1, 0x9e, 0x66, 0xdb, 0xb7, 0xcd, + 0xc3, 0xe4, 0xd0, 0xa8, 0x76, 0x13, 0x01, 0xba, 0x03, 0xba, 0x43, 0xc3, 0x88, 0xca, 0x28, 0x2a, + 0xda, 0x84, 0x77, 0xa0, 0x4d, 0x5c, 0x1c, 0xab, 0x4b, 0xae, 0x44, 0x5f, 0x42, 0xd1, 0x96, 0xec, + 0x64, 0x45, 0x8a, 0x9e, 0x8c, 0x25, 0x09, 0x58, 0x3d, 0x3d, 0xf5, 0x53, 0xac, 0x86, 0x0b, 0xf6, + 0x34, 0x33, 0x5e, 0x80, 0x42, 0x44, 0x1e, 0x72, 0xcb, 0x0f, 0x5c, 0xe5, 0xe9, 0xb2, 0xec, 0x9c, + 0x25, 0x21, 0xdd, 0x0b, 0xdc, 0xd8, 0xfe, 0x23, 0xc8, 0x13, 0xc7, 0xb1, 0x18, 0xe3, 0xa4, 0xe5, + 0x53, 0x63, 0x45, 0xda, 0xbe, 0x3e, 0x6b, 0x15, 0x4c, 0xd5, 0x92, 0xb9, 0xed, 0x38, 0xcd, 0xe6, + 0xa1, 0xc0, 0xa9, 0x15, 0x04, 0xbd, 0x8d, 0xbf, 0x31, 0x10, 0xc7, 0x69, 0xc6, 0x36, 0xd0, 0x4d, + 0xc8, 0xc6, 0xfe, 0x20, 0x69, 0xec, 0xff, 0x33, 0x45, 0x4e, 0x7a, 0xab, 0x12, 0x12, 0xab, 0xa3, + 0x6f, 0x52, 0x70, 0x26, 0x8c, 0xe8, 0x40, 0x25, 0x3f, 0x7e, 0x1b, 0x10, 0xdf, 0x58, 0x9d, 0x25, + 0xb5, 0xd7, 0x5f, 0x0f, 0xcb, 0xd7, 0x66, 0xa7, 0x6d, 0xa1, 0x5c, 0xf7, 0x03, 0xbb, 0x33, 0x42, + 0xc0, 0x2b, 0xc2, 0xb6, 0x2c, 0xb0, 0x3b, 0xca, 0x32, 0xfa, 0x0a, 0x74, 0x3b, 0x9e, 0x1b, 0x56, + 0x42, 0xe7, 0xc6, 0x59, 0xe9, 0xcd, 0xe5, 0x99, 0x1a, 0x79, 0x62, 0xe0, 0xe0, 0xa2, 0x3d, 0x2d, + 0x58, 0xfb, 0x0c, 0x26, 0x02, 0x8a, 0x10, 0x64, 0xc4, 0x2b, 0x25, 0xa6, 0x32, 0x2c, 0xd7, 0xa8, + 0x0c, 0x59, 0x3b, 0xb2, 0xb7, 0x36, 0x65, 0x2f, 0x2f, 0xd7, 0x72, 0xc7, 0xc3, 0x72, 0xb6, 0x8e, + 0xeb, 0x5b, 0x9b, 0x38, 0x96, 0xc7, 0x5c, 0xd0, 0xc8, 0x68, 0x29, 0x7d, 0xbe, 0x91, 0xd1, 0xb2, + 0xfa, 0x42, 0x23, 0xa3, 0x2d, 0xea, 0x5a, 0x23, 0xa3, 0xe5, 0x74, 0x68, 0x64, 0xb4, 0x82, 0x5e, + 0x6c, 0x64, 0xb4, 0xa2, 0xae, 0x37, 0x32, 0x9a, 0xae, 0xaf, 0x34, 0x32, 0xda, 0x19, 0x7d, 0xb5, + 0xb1, 0xa0, 0x7d, 0x77, 0xa0, 0xff, 0x74, 0x50, 0x59, 0x07, 0xf8, 0x22, 0xf2, 0x38, 0xad, 0x11, + 0x6e, 0xb7, 0x4f, 0x72, 0xa0, 0x72, 0x17, 0x96, 0xf6, 0x02, 0xd7, 0xb3, 0x89, 0xff, 0x79, 0xb8, + 0x17, 0xb8, 0x68, 0x1b, 0xd2, 0x41, 0x28, 0x48, 0x5d, 0x8c, 0x8b, 0xff, 0x9d, 0x96, 0xe7, 0x91, + 0xaa, 0x4a, 0xb3, 0xd0, 0xad, 0xfc, 0xb0, 0x00, 0x79, 0x4c, 0x1e, 0xf2, 0x7a, 0xd0, 0xed, 0x92, 0x9e, 0x83, 0x2e, 0x42, 0xb1, 0x4b, 0x9e, 0xa8, 0x94, 0x7b, 0x3d, 0x87, 0x3e, 0x91, 0xb4, 0x91, - 0xc1, 0xcb, 0x5d, 0xf2, 0x44, 0x66, 0x63, 0x57, 0x08, 0xd1, 0x21, 0xfc, 0x6b, 0xa2, 0x53, 0x47, + 0xc1, 0xcb, 0x5d, 0xf2, 0x44, 0x66, 0x63, 0x57, 0x08, 0xd1, 0x21, 0xfc, 0x63, 0xa2, 0x53, 0x47, 0xef, 0x46, 0xa9, 0x27, 0x67, 0x5b, 0x7e, 0xd3, 0x38, 0x61, 0x7e, 0xc5, 0x84, 0x71, 0x7e, 0xac, 0x7a, 0x47, 0x69, 0xca, 0x0d, 0x34, 0x80, 0xf3, 0xd3, 0x50, 0x16, 0x13, 0xe4, 0xd2, 0xb3, 0xa9, - 0xe4, 0x9c, 0x74, 0xed, 0xe3, 0xd7, 0xc3, 0xf2, 0x95, 0xb7, 0x1a, 0x01, 0x12, 0xb8, 0xa9, 0x50, + 0xe4, 0x9c, 0x74, 0xed, 0xd3, 0xd7, 0xc3, 0xf2, 0xd5, 0x77, 0x1a, 0x01, 0x12, 0xb8, 0xa9, 0x50, 0xf0, 0xd9, 0x70, 0xd2, 0x5e, 0x22, 0x46, 0x8f, 0xe0, 0x5c, 0x34, 0xea, 0x38, 0x8b, 0x0e, 0x88, - 0x6f, 0x45, 0xb2, 0xe7, 0x64, 0x4f, 0xe7, 0x37, 0x3f, 0xfc, 0x4b, 0x0d, 0xab, 0xe2, 0xbc, 0x1a, - 0x9d, 0x34, 0x18, 0xf6, 0x20, 0xff, 0x58, 0x64, 0xdb, 0x6a, 0x89, 0x74, 0x1b, 0x85, 0x19, 0xe7, - 0xd1, 0xb8, 0x42, 0x30, 0x3c, 0x1e, 0x57, 0x4b, 0x13, 0x0a, 0x7e, 0x9c, 0x5e, 0x2b, 0x08, 0x05, - 0x25, 0x19, 0xc5, 0x19, 0xe7, 0xcd, 0x64, 0x41, 0xe1, 0x25, 0x7f, 0xb2, 0xbc, 0x1e, 0x00, 0xf0, - 0x88, 0xd8, 0xd4, 0x92, 0x85, 0xa8, 0xcb, 0x2a, 0xbb, 0x7a, 0x7a, 0x24, 0xc6, 0xd5, 0x64, 0x1e, - 0x0a, 0xf5, 0x1d, 0xc2, 0xc9, 0x8d, 0x1e, 0x8f, 0x8e, 0x70, 0x8e, 0x27, 0xdf, 0xe8, 0x00, 0x74, - 0xdb, 0x0f, 0x18, 0x75, 0xac, 0xf1, 0xcc, 0x58, 0x99, 0x7d, 0x66, 0x14, 0x63, 0xe5, 0x91, 0x78, - 0xed, 0x1a, 0x14, 0xa6, 0x8d, 0x21, 0x1d, 0xd2, 0xc9, 0x5b, 0x24, 0x87, 0xc5, 0x52, 0xbc, 0x82, - 0x06, 0xc4, 0xef, 0xc7, 0xb3, 0x38, 0x87, 0xe3, 0x8f, 0x2b, 0xf3, 0x1f, 0x89, 0x96, 0x4d, 0xeb, - 0x99, 0x51, 0xe3, 0xce, 0xeb, 0xe9, 0xb8, 0x29, 0x7f, 0x3c, 0xa8, 0x5c, 0x85, 0x95, 0x89, 0x0b, - 0xdd, 0x0c, 0x02, 0x4e, 0xa3, 0x59, 0x9b, 0xa4, 0xe2, 0xc2, 0xd9, 0xfa, 0xb4, 0x9f, 0x0a, 0xe0, - 0x1f, 0xbe, 0x7d, 0xed, 0xd2, 0xb3, 0x97, 0xa5, 0xb9, 0x67, 0xc7, 0xa5, 0xd4, 0xf3, 0xe3, 0x52, - 0xea, 0xc5, 0x71, 0x29, 0xf5, 0xeb, 0x71, 0x29, 0xf5, 0xf5, 0xab, 0xd2, 0xdc, 0xf3, 0x57, 0xa5, - 0xb9, 0x17, 0xaf, 0x4a, 0x73, 0x0f, 0x60, 0xfc, 0x83, 0xd4, 0x5a, 0x90, 0xff, 0x3a, 0x5b, 0xbf, - 0x07, 0x00, 0x00, 0xff, 0xff, 0x38, 0xb4, 0xb7, 0xb4, 0x06, 0x0e, 0x00, 0x00, + 0x6f, 0x45, 0xb2, 0xe7, 0x64, 0x4f, 0xe7, 0x37, 0x3f, 0x7e, 0xaf, 0x86, 0x55, 0x71, 0x5e, 0x8d, + 0x4e, 0x1a, 0x0c, 0x7b, 0x90, 0x7f, 0x2c, 0xb2, 0x6d, 0xb5, 0x44, 0xba, 0x8d, 0xc2, 0x8c, 0xf3, + 0x68, 0x5c, 0x21, 0x18, 0x1e, 0x8f, 0xab, 0xa5, 0x09, 0x05, 0x3f, 0x4e, 0xaf, 0x15, 0x84, 0x82, + 0x92, 0x8c, 0xe2, 0x8c, 0xf3, 0x66, 0xb2, 0xa0, 0xf0, 0x92, 0x3f, 0x59, 0x5e, 0x0f, 0x00, 0x78, + 0x44, 0x6c, 0x6a, 0xc9, 0x42, 0xd4, 0x65, 0x95, 0x7d, 0x72, 0x7a, 0x24, 0xc6, 0xd5, 0x64, 0x1e, + 0x0a, 0xf5, 0x1d, 0xc2, 0xc9, 0x8d, 0x1e, 0x8f, 0x8e, 0x70, 0x8e, 0x27, 0xdf, 0xe8, 0x16, 0xe8, + 0xb6, 0x1f, 0x30, 0xea, 0x58, 0xe3, 0x99, 0xb1, 0x32, 0x03, 0xb1, 0xe0, 0x62, 0xac, 0x36, 0x12, + 0xac, 0x5d, 0x83, 0xc2, 0xb4, 0x19, 0xa4, 0x43, 0x3a, 0x79, 0x85, 0xe4, 0xb0, 0x58, 0x8a, 0xf7, + 0xcf, 0x80, 0xf8, 0xfd, 0x78, 0x0a, 0xe7, 0x70, 0xfc, 0x71, 0x75, 0xfe, 0x8a, 0x68, 0xd6, 0xb4, + 0x9e, 0x19, 0xb5, 0xec, 0xbc, 0x9e, 0x8e, 0xdb, 0xf1, 0xe7, 0x83, 0xca, 0x15, 0x28, 0xec, 0xab, + 0x92, 0xbf, 0x19, 0x04, 0x9c, 0x46, 0xb3, 0xf6, 0x46, 0xc5, 0x85, 0xb3, 0xf5, 0x69, 0x27, 0x15, + 0xc0, 0xc1, 0x7b, 0x5e, 0x5a, 0x15, 0xd2, 0x9b, 0x57, 0xaf, 0x5d, 0x7a, 0xf6, 0xb2, 0x34, 0xf7, + 0xec, 0xb8, 0x94, 0x7a, 0x7e, 0x5c, 0x4a, 0xbd, 0x38, 0x2e, 0xa5, 0x7e, 0x3f, 0x2e, 0xa5, 0xbe, + 0x7d, 0x55, 0x9a, 0x7b, 0xfe, 0xaa, 0x34, 0xf7, 0xe2, 0x55, 0x69, 0xee, 0x01, 0x8c, 0xff, 0x8b, + 0x5a, 0x0b, 0xf2, 0x17, 0x67, 0xeb, 0xcf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x05, 0x7f, 0x93, 0xe3, + 0xfd, 0x0d, 0x00, 0x00, } diff --git a/pkg/kv/kvserver/kvserverpb/proposer_kv.proto b/pkg/kv/kvserver/kvserverpb/proposer_kv.proto index 249b4e45ca58..f0a7e355cc90 100644 --- a/pkg/kv/kvserver/kvserverpb/proposer_kv.proto +++ b/pkg/kv/kvserver/kvserverpb/proposer_kv.proto @@ -241,7 +241,12 @@ message RaftCommand { // updated accordingly. Managing retry of proposals becomes trickier as // well as that uproots whatever ordering was originally envisioned. // - // This field is set through RaftCommandFooter hackery. + // This field is set through MaxLeaseFooter hackery. Unlike with the + // ClosedTimestamp, which needs to be nullable in this proto (see comment), + // there are no nullability concerns with this field. This is because + // max_lease_index is a primitive type, so it does not get encoded when zero. + // This alone ensures that the field is not encoded twice in the combined + // RaftCommand+MaxLeaseFooter proto. uint64 max_lease_index = 4; // The closed timestamp carried by this command. Once a follower is told to @@ -255,8 +260,11 @@ message RaftCommand { // in a command-specific way. If the value is not zero, the value is greater // or equal to that of the previous commands (and all before it). // - // This field is set through ClosedTimestampFooter hackery. - util.hlc.Timestamp closed_timestamp = 17 [(gogoproto.nullable) = false]; + // This field is set through ClosedTimestampFooter hackery. Unlike in the + // ClosedTimestampFooter, the field is nullable here so that it does not get + // encoded when empty. This prevents the field from being encoded twice in the + // combined RaftCommand+ClosedTimestampFooter proto. + util.hlc.Timestamp closed_timestamp = 17; reserved 3; @@ -284,19 +292,22 @@ message RaftCommand { reserved 1, 2, 10001 to 10014; } -// RaftCommandFooter contains a subset of the fields in RaftCommand. It is used +// MaxLeaseFooter contains a subset of the fields in RaftCommand. It is used // to optimize a pattern where most of the fields in RaftCommand are marshaled // outside of a heavily contended critical section, except for the fields in the // footer, which are assigned and marhsaled inside of the critical section and // appended to the marshaled byte buffer. This minimizes the memory allocation // and marshaling work performed under lock. -message RaftCommandFooter { +message MaxLeaseFooter { uint64 max_lease_index = 4; } -// ClosedTimestampFooter is similar to RaftCommandFooter, allowing the proposal +// ClosedTimestampFooter is similar to MaxLeaseFooter, allowing the proposal // buffer to fill in the closed_timestamp field after most of the proto has been // marshaled already. message ClosedTimestampFooter { + // NOTE: unlike in RaftCommand, there's no reason to make this field nullable. + // If we don't want to include the field, we don't need to append the encoded + // footer to an encoded RaftCommand buffer. util.hlc.Timestamp closed_timestamp = 17 [(gogoproto.nullable) = false]; } diff --git a/pkg/kv/kvserver/replica_application_state_machine.go b/pkg/kv/kvserver/replica_application_state_machine.go index 7793ca520fdd..69130255ea85 100644 --- a/pkg/kv/kvserver/replica_application_state_machine.go +++ b/pkg/kv/kvserver/replica_application_state_machine.go @@ -445,7 +445,7 @@ func (b *replicaAppBatch) Stage(cmdI apply.Command) (apply.CheckedCommand, error cmd.raftCmd.ReplicatedEvalResult = kvserverpb.ReplicatedEvalResult{} cmd.raftCmd.WriteBatch = nil cmd.raftCmd.LogicalOpLog = nil - cmd.raftCmd.ClosedTimestamp.Reset() + cmd.raftCmd.ClosedTimestamp = nil } else { // Assert that we're not writing under the closed timestamp. We can only do // these checks on IsIntentWrite requests, since others (for example, @@ -639,7 +639,7 @@ func (b *replicaAppBatch) runPreApplyTriggersAfterStagingWriteBatch( // // Alternatively if we discover that the RHS has already been removed // from this store, clean up its data. - splitPreApply(ctx, b.batch, res.Split.SplitTrigger, b.r, cmd.raftCmd.ClosedTimestamp) + splitPreApply(ctx, b.r, b.batch, res.Split.SplitTrigger, cmd.raftCmd.ClosedTimestamp) // The rangefeed processor will no longer be provided logical ops for // its entire range, so it needs to be shut down and all registrations @@ -823,13 +823,13 @@ func (b *replicaAppBatch) stageTrivialReplicatedEvalResult( if leaseAppliedIndex := cmd.leaseIndex; leaseAppliedIndex != 0 { b.state.LeaseAppliedIndex = leaseAppliedIndex } - if cts := cmd.raftCmd.ClosedTimestamp; !cts.IsEmpty() { + if cts := cmd.raftCmd.ClosedTimestamp; cts != nil && !cts.IsEmpty() { if cts.Less(b.state.ClosedTimestamp) { log.Fatalf(ctx, "closed timestamp regressing from %s to %s when applying command %x", b.state.ClosedTimestamp, cts, cmd.idKey) } - b.state.ClosedTimestamp = cts + b.state.ClosedTimestamp = *cts if clockTS, ok := cts.TryToClockTimestamp(); ok { b.maxTS.Forward(clockTS) } diff --git a/pkg/kv/kvserver/replica_proposal.go b/pkg/kv/kvserver/replica_proposal.go index 5ccc84b1025f..c170a2c6cc17 100644 --- a/pkg/kv/kvserver/replica_proposal.go +++ b/pkg/kv/kvserver/replica_proposal.go @@ -81,7 +81,7 @@ type ProposalData struct { quotaAlloc *quotapool.IntAlloc // tmpFooter is used to avoid an allocation. - tmpFooter kvserverpb.RaftCommandFooter + tmpFooter kvserverpb.MaxLeaseFooter // ec.done is called after command application to update the timestamp // cache and optionally release latches and exits lock wait-queues. diff --git a/pkg/kv/kvserver/replica_proposal_buf_test.go b/pkg/kv/kvserver/replica_proposal_buf_test.go index 8b7df4c5c981..62de4020aeae 100644 --- a/pkg/kv/kvserver/replica_proposal_buf_test.go +++ b/pkg/kv/kvserver/replica_proposal_buf_test.go @@ -219,7 +219,7 @@ func (pc proposalCreator) newProposal(ba roachpb.BatchRequest) (*ProposalData, [ func (pc proposalCreator) encodeProposal(p *ProposalData) []byte { cmdLen := p.command.Size() needed := raftCommandPrefixLen + cmdLen + - kvserverpb.MaxRaftCommandFooterSize() + + kvserverpb.MaxMaxLeaseFooterSize() + kvserverpb.MaxClosedTimestampFooterSize() data := make([]byte, raftCommandPrefixLen, needed) encodeRaftCommandPrefix(data, raftVersionStandard, p.idKey) @@ -716,7 +716,12 @@ func TestProposalBufferClosedTimestamp(t *testing.T) { type reqType int checkClosedTS := func(t *testing.T, r *testProposerRaft, exp hlc.Timestamp) { require.Len(t, r.lastProps, 1) - require.Equal(t, exp, r.lastProps[0].ClosedTimestamp) + if exp.IsEmpty() { + require.Nil(t, r.lastProps[0].ClosedTimestamp) + } else { + require.NotNil(t, r.lastProps[0].ClosedTimestamp) + require.Equal(t, exp, *r.lastProps[0].ClosedTimestamp) + } } // The lease that the proposals are made under. diff --git a/pkg/kv/kvserver/replica_raft.go b/pkg/kv/kvserver/replica_raft.go index 2568c9b44158..e1ffbafea801 100644 --- a/pkg/kv/kvserver/replica_raft.go +++ b/pkg/kv/kvserver/replica_raft.go @@ -261,7 +261,7 @@ func (r *Replica) propose( // Make sure the maximum lease index is unset. This field will be set in // propBuf.Insert and its encoded bytes will be appended to the encoding - // buffer as a RaftCommandFooter. + // buffer as a MaxLeaseFooter. p.command.MaxLeaseIndex = 0 // Determine the encoding style for the Raft command. @@ -320,7 +320,7 @@ func (r *Replica) propose( // Allocate the data slice with enough capacity to eventually hold the two // "footers" that are filled later. needed := preLen + cmdLen + - kvserverpb.MaxRaftCommandFooterSize() + + kvserverpb.MaxMaxLeaseFooterSize() + kvserverpb.MaxClosedTimestampFooterSize() data := make([]byte, preLen, needed) // Encode prefix with command ID, if necessary. diff --git a/pkg/kv/kvserver/stateloader/stateloader.go b/pkg/kv/kvserver/stateloader/stateloader.go index a6610a807d39..a0da9c082c76 100644 --- a/pkg/kv/kvserver/stateloader/stateloader.go +++ b/pkg/kv/kvserver/stateloader/stateloader.go @@ -298,24 +298,23 @@ func (rsl StateLoader) LoadMVCCStats( // keys. We now deem those keys to be "legacy" because they have been replaced // by the range applied state key. // -// TODO(andrei): closedTimestamp is a pointer to avoid an allocation when -// putting it in RangeAppliedState. RangeAppliedState.ClosedTimestamp is made -// non-nullable (see comments on the field), this argument should be taken by -// value. +// TODO(andrei): closedTS is a pointer to avoid an allocation when putting it in +// RangeAppliedState. RangeAppliedState.ClosedTimestamp is made non-nullable +// (see comments on the field), this argument should be taken by value. func (rsl StateLoader) SetRangeAppliedState( ctx context.Context, readWriter storage.ReadWriter, appliedIndex, leaseAppliedIndex uint64, newMS *enginepb.MVCCStats, - closedTimestamp *hlc.Timestamp, + closedTS *hlc.Timestamp, ) error { as := enginepb.RangeAppliedState{ RaftAppliedIndex: appliedIndex, LeaseAppliedIndex: leaseAppliedIndex, RangeStats: newMS.ToPersistentStats(), } - if closedTimestamp != nil && !closedTimestamp.IsEmpty() { - as.ClosedTimestamp = closedTimestamp + if closedTS != nil && !closedTS.IsEmpty() { + as.ClosedTimestamp = closedTS } // The RangeAppliedStateKey is not included in stats. This is also reflected // in C.MVCCComputeStats and ComputeStatsForRange. @@ -498,7 +497,7 @@ func (rsl StateLoader) SetMVCCStats( // SetClosedTimestamp overwrites the closed timestamp. func (rsl StateLoader) SetClosedTimestamp( - ctx context.Context, readWriter storage.ReadWriter, closedTS hlc.Timestamp, + ctx context.Context, readWriter storage.ReadWriter, closedTS *hlc.Timestamp, ) error { as, err := rsl.LoadRangeAppliedState(ctx, readWriter) if err != nil { @@ -506,7 +505,7 @@ func (rsl StateLoader) SetClosedTimestamp( } return rsl.SetRangeAppliedState( ctx, readWriter, as.RaftAppliedIndex, as.LeaseAppliedIndex, - as.RangeStats.ToStatsPtr(), &closedTS) + as.RangeStats.ToStatsPtr(), closedTS) } // SetLegacyRaftTruncatedState overwrites the truncated state. diff --git a/pkg/kv/kvserver/store_split.go b/pkg/kv/kvserver/store_split.go index 7f8dccbfa3c4..9ec16989e7f7 100644 --- a/pkg/kv/kvserver/store_split.go +++ b/pkg/kv/kvserver/store_split.go @@ -30,11 +30,10 @@ import ( // split commit. func splitPreApply( ctx context.Context, + r *Replica, readWriter storage.ReadWriter, split roachpb.SplitTrigger, - r *Replica, - // The closed timestamp used to initialize the RHS. - closedTS hlc.Timestamp, + initClosedTS *hlc.Timestamp, ) { // Sanity check that the store is in the split. // @@ -123,7 +122,7 @@ func splitPreApply( } // Persist the closed timestamp. - if err := rsl.SetClosedTimestamp(ctx, readWriter, closedTS); err != nil { + if err := rsl.SetClosedTimestamp(ctx, readWriter, initClosedTS); err != nil { log.Fatalf(ctx, "%s", err) } diff --git a/pkg/sql/logictest/testdata/logic_test/unique b/pkg/sql/logictest/testdata/logic_test/unique index 6abbf7863fa7..3de7a80548ba 100644 --- a/pkg/sql/logictest/testdata/logic_test/unique +++ b/pkg/sql/logictest/testdata/logic_test/unique @@ -86,13 +86,6 @@ CREATE TABLE uniq_enum ( UNIQUE WITHOUT INDEX (s, j) ) -statement ok -CREATE TABLE uniq_partial_index ( - i INT, - UNIQUE WITHOUT INDEX (i), - UNIQUE INDEX (i) WHERE i > 0 -) - statement ok CREATE TABLE other (k INT, v INT, w INT NOT NULL, x INT, y INT) @@ -437,6 +430,51 @@ r s i j eu-west bar 2 2 us-west foo 1 1 +# Set a to the same value it already has. +statement ok +UPDATE uniq_partial SET a = 1 WHERE a = 1 AND b = 1 + +# Set a to an existing value. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +UPDATE uniq_partial SET a = 1 WHERE a = 2 + +# Make b of (1, -1) positive so that it conflicts with (1, 1) +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +UPDATE uniq_partial SET b = 10 WHERE a = 1 AND b = -1 + +# Set a to NULL. +statement ok +UPDATE uniq_partial SET a = NULL, b = 10 WHERE a = 1 AND b = -1 + +# Update two existing, non-conflicting rows resulting in a conflict. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(10\) already exists\. +UPDATE uniq_partial SET a = 10 WHERE a IS NULL AND b = 5 + +# Set a to a non-existing value. +statement ok +UPDATE uniq_partial SET a = 10 WHERE a = 9 AND b = 9 + +# Set a to a value that would conflict if it was a non-partial unique constraint. +statement ok +UPDATE uniq_partial SET a = 1 WHERE b = -7 + +query II colnames,rowsort +SELECT * FROM uniq_partial +---- +a b +1 1 +1 -3 +1 -7 +2 2 +5 5 +6 6 +7 7 +9 -9 +10 9 +NULL 5 +NULL 5 +NULL 10 + # -- Tests with UPSERT -- subtest Upsert @@ -616,11 +654,18 @@ eu-west bar 2 2 # Ensure that we do not choose a partial index as the arbiter when there is a # UNIQUE WITHOUT INDEX constraint. statement ok -INSERT INTO uniq_partial_index VALUES (-1) ON CONFLICT (i) WHERE i > 0 DO UPDATE SET i = 1; -INSERT INTO uniq_partial_index VALUES (-1) ON CONFLICT (i) WHERE i > 0 DO UPDATE SET i = 2 +CREATE TABLE uniq_partial_index_and_constraint ( + i INT, + UNIQUE WITHOUT INDEX (i), + UNIQUE INDEX (i) WHERE i > 0 +) + +statement ok +INSERT INTO uniq_partial_index_and_constraint VALUES (-1) ON CONFLICT (i) WHERE i > 0 DO UPDATE SET i = 1; +INSERT INTO uniq_partial_index_and_constraint VALUES (-1) ON CONFLICT (i) WHERE i > 0 DO UPDATE SET i = 2 query I colnames -SELECT * FROM uniq_partial_index +SELECT * FROM uniq_partial_index_and_constraint ---- i 2 diff --git a/pkg/sql/opt/exec/execbuilder/testdata/unique b/pkg/sql/opt/exec/execbuilder/testdata/unique index d5bb809c5ac6..539b288c00ed 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/unique +++ b/pkg/sql/opt/exec/execbuilder/testdata/unique @@ -131,16 +131,77 @@ CREATE TABLE uniq_enum ( statement ok CREATE TABLE uniq_partial_enum ( r region DEFAULT CASE (random()*3)::int WHEN 0 THEN 'us-east' WHEN 1 THEN 'us-west' ELSE 'eu-west' END, - i INT, - s STRING, - PRIMARY KEY (r, i), - UNIQUE WITHOUT INDEX (i) WHERE s IN ('foo', 'bar', 'baz'), - INDEX (r, i) WHERE s IN ('foo', 'bar', 'baz'), + a INT, + b INT, + c STRING, + PRIMARY KEY (r, a), + UNIQUE WITHOUT INDEX (b) WHERE c IN ('foo', 'bar', 'baz'), + INDEX (r, b) WHERE c IN ('foo', 'bar', 'baz'), FAMILY (r), - FAMILY (s), - FAMILY (i) + FAMILY (a), + FAMILY (b), + FAMILY (c) ) +statement ok +ALTER TABLE uniq_partial_enum INJECT STATISTICS '[ + { + "columns": ["r"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 3, + "histo_col_type": "region", + "histo_buckets": [ + {"num_eq": 333, "num_range": 0, "distinct_range": 0, "upper_bound": "eu-west"}, + {"num_eq": 333, "num_range": 0, "distinct_range": 0, "upper_bound": "us-east"}, + {"num_eq": 334, "num_range": 0, "distinct_range": 0, "upper_bound": "us-west"} + ] + }, + { + "columns": ["a"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 1000, + "histo_col_type": "int", + "histo_buckets": [ + {"num_eq": 1, "num_range": 0, "distinct_range": 0, "upper_bound": "0"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "200"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "400"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "600"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "800"}, + {"num_eq": 1, "num_range": 198, "distinct_range": 198, "upper_bound": "999"} + ] + }, + { + "columns": ["b"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 1000, + "histo_col_type": "int", + "histo_buckets": [ + {"num_eq": 1, "num_range": 0, "distinct_range": 0, "upper_bound": "0"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "200"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "400"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "600"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "800"}, + {"num_eq": 1, "num_range": 198, "distinct_range": 198, "upper_bound": "999"} + ] + }, + { + "columns": ["c"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 4, + "histo_col_type": "string", + "histo_buckets": [ + {"num_eq": 200, "num_range": 0, "distinct_range": 0, "upper_bound": "bar"}, + {"num_eq": 200, "num_range": 0, "distinct_range": 0, "upper_bound": "baz"}, + {"num_eq": 200, "num_range": 0, "distinct_range": 0, "upper_bound": "foo"}, + {"num_eq": 400, "num_range": 0, "distinct_range": 0, "upper_bound": "fud"} + ] + } +]' + statement ok CREATE TABLE other (k INT, v INT, w INT NOT NULL, x INT, y INT) @@ -1389,7 +1450,7 @@ vectorized: true # Test that we use the partial index when available for the insert checks. query T -EXPLAIN (VERBOSE) INSERT INTO uniq_partial_enum VALUES ('us-west', 1, 'foo'), ('us-east', 2, 'bar') +EXPLAIN (VERBOSE) INSERT INTO uniq_partial_enum VALUES ('us-west', 1, 1, 'foo'), ('us-east', 2, 2, 'bar') ---- distribution: local vectorized: true @@ -1400,30 +1461,33 @@ vectorized: true ├── • insert │ │ columns: () │ │ estimated row count: 0 (missing stats) -│ │ into: uniq_partial_enum(r, i, s) +│ │ into: uniq_partial_enum(r, a, b, c) │ │ │ └── • buffer -│ │ columns: (column1, column2, column3, check1, partial_index_put1) +│ │ columns: (column1, column2, column3, column4, check1, partial_index_put1) │ │ label: buffer 1 │ │ │ └── • render -│ │ columns: (column1, column2, column3, check1, partial_index_put1) +│ │ columns: (column1, column2, column3, column4, check1, partial_index_put1) │ │ estimated row count: 2 -│ │ render partial_index_put1: column3 IN ('bar', 'baz', 'foo') +│ │ render partial_index_put1: column4 IN ('bar', 'baz', 'foo') │ │ render check1: column1 IN ('us-east', 'us-west', 'eu-west') │ │ render column1: column1 │ │ render column2: column2 │ │ render column3: column3 +│ │ render column4: column4 │ │ │ └── • values -│ columns: (column1, column2, column3) -│ size: 3 columns, 2 rows +│ columns: (column1, column2, column3, column4) +│ size: 4 columns, 2 rows │ row 0, expr 0: 'us-west' │ row 0, expr 1: 1 -│ row 0, expr 2: 'foo' +│ row 0, expr 2: 1 +│ row 0, expr 3: 'foo' │ row 1, expr 0: 'us-east' │ row 1, expr 1: 2 -│ row 1, expr 2: 'bar' +│ row 1, expr 2: 2 +│ row 1, expr 3: 'bar' │ └── • constraint-check │ @@ -1431,44 +1495,43 @@ vectorized: true │ columns: () │ └── • project - │ columns: (column1, column2, column3) - │ estimated row count: 1 (missing stats) + │ columns: (column1, column2, column3, column4) + │ estimated row count: 1 │ └── • lookup join (semi) - │ columns: ("lookup_join_const_col_@11", column1, column2, column3) - │ table: uniq_partial_enum@uniq_partial_enum_r_i_idx (partial index) - │ equality: (lookup_join_const_col_@11, column2) = (r,i) - │ equality cols are key - │ pred: column1 != r + │ columns: ("lookup_join_const_col_@13", column1, column2, column3, column4) + │ table: uniq_partial_enum@uniq_partial_enum_r_b_idx (partial index) + │ equality: (lookup_join_const_col_@13, column3) = (r,b) + │ pred: (column1 != r) OR (column2 != a) │ └── • cross join (inner) - │ columns: ("lookup_join_const_col_@11", column1, column2, column3) + │ columns: ("lookup_join_const_col_@13", column1, column2, column3, column4) │ estimated row count: 6 │ ├── • values - │ columns: ("lookup_join_const_col_@11") + │ columns: ("lookup_join_const_col_@13") │ size: 1 column, 3 rows │ row 0, expr 0: 'us-east' │ row 1, expr 0: 'us-west' │ row 2, expr 0: 'eu-west' │ └── • filter - │ columns: (column1, column2, column3) + │ columns: (column1, column2, column3, column4) │ estimated row count: 2 - │ filter: column3 IN ('bar', 'baz', 'foo') + │ filter: column4 IN ('bar', 'baz', 'foo') │ └── • project - │ columns: (column1, column2, column3) + │ columns: (column1, column2, column3, column4) │ estimated row count: 2 │ └── • scan buffer - columns: (column1, column2, column3, check1, partial_index_put1) + columns: (column1, column2, column3, column4, check1, partial_index_put1) label: buffer 1 # Test that we use the partial index when available for de-duplicating INSERT ON # CONFLICT DO NOTHING rows before inserting. query T -EXPLAIN (VERBOSE) INSERT INTO uniq_partial_enum VALUES ('us-west', 1, 'foo'), ('us-east', 2, 'bar') +EXPLAIN (VERBOSE) INSERT INTO uniq_partial_enum VALUES ('us-west', 1, 1, 'foo'), ('us-east', 2, 2, 'bar') ON CONFLICT DO NOTHING ---- distribution: local @@ -1480,60 +1543,64 @@ vectorized: true ├── • insert │ │ columns: () │ │ estimated row count: 0 (missing stats) -│ │ into: uniq_partial_enum(r, i, s) +│ │ into: uniq_partial_enum(r, a, b, c) │ │ arbiter indexes: primary -│ │ arbiter constraints: unique_i +│ │ arbiter constraints: unique_b │ │ │ └── • buffer -│ │ columns: (column1, column2, column3, check1, partial_index_put1) +│ │ columns: (column1, column2, column3, column4, check1, partial_index_put1) │ │ label: buffer 1 │ │ │ └── • render -│ │ columns: (column1, column2, column3, check1, partial_index_put1) -│ │ estimated row count: 0 (missing stats) -│ │ render partial_index_put1: column3 IN ('bar', 'baz', 'foo') +│ │ columns: (column1, column2, column3, column4, check1, partial_index_put1) +│ │ estimated row count: 0 +│ │ render partial_index_put1: column4 IN ('bar', 'baz', 'foo') │ │ render check1: column1 IN ('us-east', 'us-west', 'eu-west') │ │ render column1: column1 │ │ render column2: column2 │ │ render column3: column3 +│ │ render column4: column4 │ │ │ └── • distinct -│ │ columns: (arbiter_unique_i_distinct, column1, column2, column3) -│ │ estimated row count: 0 (missing stats) -│ │ distinct on: arbiter_unique_i_distinct, column2 +│ │ columns: (arbiter_unique_b_distinct, column1, column2, column3, column4) +│ │ estimated row count: 0 +│ │ distinct on: arbiter_unique_b_distinct, column3 │ │ nulls are distinct │ │ │ └── • render -│ │ columns: (arbiter_unique_i_distinct, column1, column2, column3) -│ │ estimated row count: 0 (missing stats) -│ │ render arbiter_unique_i_distinct: (column3 IN ('bar', 'baz', 'foo')) OR CAST(NULL AS BOOL) +│ │ columns: (arbiter_unique_b_distinct, column1, column2, column3, column4) +│ │ estimated row count: 0 +│ │ render arbiter_unique_b_distinct: (column4 IN ('bar', 'baz', 'foo')) OR CAST(NULL AS BOOL) │ │ render column1: column1 │ │ render column2: column2 │ │ render column3: column3 +│ │ render column4: column4 │ │ │ └── • lookup join (anti) -│ │ columns: (column1, column2, column3) -│ │ estimated row count: 0 (missing stats) -│ │ table: uniq_partial_enum@uniq_partial_enum_r_i_idx (partial index) -│ │ lookup condition: (column2 = i) AND (r IN ('us-east', 'us-west', 'eu-west')) -│ │ pred: column3 IN ('bar', 'baz', 'foo') +│ │ columns: (column1, column2, column3, column4) +│ │ estimated row count: 0 +│ │ table: uniq_partial_enum@uniq_partial_enum_r_b_idx (partial index) +│ │ lookup condition: (column3 = b) AND (r IN ('us-east', 'us-west', 'eu-west')) +│ │ pred: column4 IN ('bar', 'baz', 'foo') │ │ │ └── • lookup join (anti) -│ │ columns: (column1, column2, column3) -│ │ estimated row count: 0 (missing stats) +│ │ columns: (column1, column2, column3, column4) +│ │ estimated row count: 0 │ │ table: uniq_partial_enum@primary -│ │ equality: (column1, column2) = (r,i) +│ │ equality: (column1, column2) = (r,a) │ │ equality cols are key │ │ │ └── • values -│ columns: (column1, column2, column3) -│ size: 3 columns, 2 rows +│ columns: (column1, column2, column3, column4) +│ size: 4 columns, 2 rows │ row 0, expr 0: 'us-west' │ row 0, expr 1: 1 -│ row 0, expr 2: 'foo' +│ row 0, expr 2: 1 +│ row 0, expr 3: 'foo' │ row 1, expr 0: 'us-east' │ row 1, expr 1: 2 -│ row 1, expr 2: 'bar' +│ row 1, expr 2: 2 +│ row 1, expr 3: 'bar' │ └── • constraint-check │ @@ -1541,38 +1608,37 @@ vectorized: true │ columns: () │ └── • project - │ columns: (column1, column2, column3) - │ estimated row count: 0 (missing stats) + │ columns: (column1, column2, column3, column4) + │ estimated row count: 0 │ └── • lookup join (semi) - │ columns: ("lookup_join_const_col_@22", column1, column2, column3) - │ table: uniq_partial_enum@uniq_partial_enum_r_i_idx (partial index) - │ equality: (lookup_join_const_col_@22, column2) = (r,i) - │ equality cols are key - │ pred: column1 != r + │ columns: ("lookup_join_const_col_@26", column1, column2, column3, column4) + │ table: uniq_partial_enum@uniq_partial_enum_r_b_idx (partial index) + │ equality: (lookup_join_const_col_@26, column3) = (r,b) + │ pred: (column1 != r) OR (column2 != a) │ └── • cross join (inner) - │ columns: ("lookup_join_const_col_@22", column1, column2, column3) - │ estimated row count: 0 (missing stats) + │ columns: ("lookup_join_const_col_@26", column1, column2, column3, column4) + │ estimated row count: 0 │ ├── • values - │ columns: ("lookup_join_const_col_@22") + │ columns: ("lookup_join_const_col_@26") │ size: 1 column, 3 rows │ row 0, expr 0: 'us-east' │ row 1, expr 0: 'us-west' │ row 2, expr 0: 'eu-west' │ └── • filter - │ columns: (column1, column2, column3) - │ estimated row count: 0 (missing stats) - │ filter: column3 IN ('bar', 'baz', 'foo') + │ columns: (column1, column2, column3, column4) + │ estimated row count: 0 + │ filter: column4 IN ('bar', 'baz', 'foo') │ └── • project - │ columns: (column1, column2, column3) - │ estimated row count: 0 (missing stats) + │ columns: (column1, column2, column3, column4) + │ estimated row count: 0 │ └── • scan buffer - columns: (column1, column2, column3, check1, partial_index_put1) + columns: (column1, column2, column3, column4, check1, partial_index_put1) label: buffer 1 # -- Tests with UPDATE -- @@ -1946,7 +2012,7 @@ vectorized: true spans: FULL SCAN # Combine unique checks with foreign keys. -# There is no uniquness check since column c is not updated. +# There is no uniqueness check since column c is not updated. query T EXPLAIN UPDATE uniq_fk_child SET a = 1, b = 2 ---- @@ -2179,6 +2245,290 @@ vectorized: true row 1, expr 0: 'us-west' row 2, expr 0: 'eu-west' +# None of the updated values have nulls. +query T +EXPLAIN UPDATE uniq_partial SET a = 1, b = 2 +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq_partial +│ │ set: a, b +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq_partial@primary +│ spans: FULL SCAN +│ locking strength: for update +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (semi) +│ │ equality: (a_new) = (a) +│ │ pred: k != k +│ │ +│ ├── • filter +│ │ │ filter: b_new > 0 +│ │ │ +│ │ └── • scan buffer +│ │ label: buffer 1 +│ │ +│ └── • filter +│ │ filter: b > 0 +│ │ +│ └── • scan +│ missing stats +│ table: uniq_partial@primary +│ spans: FULL SCAN +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (semi) + │ equality: (b_new) = (b) + │ pred: k != k + │ + ├── • filter + │ │ filter: b_new > 0 + │ │ + │ └── • scan buffer + │ label: buffer 1 + │ + └── • filter + │ filter: b > 0 + │ + └── • scan + missing stats + table: uniq_partial@primary + spans: FULL SCAN + +# No need to plan checks for a since a is always null. +# Also update the primary key. +query T +EXPLAIN UPDATE uniq_partial SET k = 1, a = NULL, b = 2 +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq_partial +│ │ set: k, a, b +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq_partial@primary +│ spans: FULL SCAN +│ locking strength: for update +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (semi) + │ equality: (b_new) = (b) + │ pred: k_new != k + │ + ├── • filter + │ │ filter: b_new > 0 + │ │ + │ └── • scan buffer + │ label: buffer 1 + │ + └── • filter + │ filter: b > 0 + │ + └── • scan + missing stats + table: uniq_partial@primary + spans: FULL SCAN + +# No need to plan checks since none of the columns requiring checks are updated. +query T +EXPLAIN UPDATE uniq_partial SET k = 1 +---- +distribution: local +vectorized: true +· +• update +│ table: uniq_partial +│ set: k +│ auto commit +│ +└── • render + │ + └── • scan + missing stats + table: uniq_partial@primary + spans: FULL SCAN + locking strength: for update + +# Plan checks for a since b is in the partial predicate and is updated. +query T +EXPLAIN UPDATE uniq_partial SET b = 2 +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq_partial +│ │ set: b +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq_partial@primary +│ spans: FULL SCAN +│ locking strength: for update +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (semi) +│ │ equality: (a) = (a) +│ │ pred: k != k +│ │ +│ ├── • filter +│ │ │ filter: b_new > 0 +│ │ │ +│ │ └── • scan buffer +│ │ label: buffer 1 +│ │ +│ └── • filter +│ │ filter: b > 0 +│ │ +│ └── • scan +│ missing stats +│ table: uniq_partial@primary +│ spans: FULL SCAN +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (semi) + │ equality: (b_new) = (b) + │ pred: k != k + │ + ├── • filter + │ │ filter: b_new > 0 + │ │ + │ └── • scan buffer + │ label: buffer 1 + │ + └── • filter + │ filter: b > 0 + │ + └── • scan + missing stats + table: uniq_partial@primary + spans: FULL SCAN + +# Test that we use the index when available for the update checks. +query T +EXPLAIN (VERBOSE) UPDATE uniq_partial_enum SET b = 20 WHERE a = 2 +---- +distribution: local +vectorized: true +· +• root +│ columns: () +│ +├── • update +│ │ columns: () +│ │ estimated row count: 0 (missing stats) +│ │ table: uniq_partial_enum +│ │ set: b +│ │ +│ └── • buffer +│ │ columns: (r, a, b, b_new, partial_index_put1, partial_index_put1, c) +│ │ label: buffer 1 +│ │ +│ └── • project +│ │ columns: (r, a, b, b_new, partial_index_put1, partial_index_put1, c) +│ │ +│ └── • render +│ │ columns: (partial_index_put1, b_new, r, a, b, c) +│ │ estimated row count: 1 +│ │ render partial_index_put1: c IN ('bar', 'baz', 'foo') +│ │ render b_new: 20 +│ │ render r: r +│ │ render a: a +│ │ render b: b +│ │ render c: c +│ │ +│ └── • scan +│ columns: (r, a, b, c) +│ estimated row count: 1 (0.10% of the table) +│ table: uniq_partial_enum@primary +│ spans: /"@"/2/0-/"@"/2/1 /"@"/2/2/1-/"@"/2/3/2 /"\x80"/2/0-/"\x80"/2/1 /"\x80"/2/2/1-/"\x80"/2/3/2 /"\xc0"/2/0-/"\xc0"/2/1 /"\xc0"/2/2/1-/"\xc0"/2/3/2 +│ parallel +│ locking strength: for update +│ +└── • constraint-check + │ + └── • error if rows + │ columns: () + │ + └── • project + │ columns: (r, a, b_new, c) + │ estimated row count: 0 + │ + └── • lookup join (semi) + │ columns: ("lookup_join_const_col_@16", r, a, b_new, c) + │ table: uniq_partial_enum@uniq_partial_enum_r_b_idx (partial index) + │ equality: (lookup_join_const_col_@16, b_new) = (r,b) + │ pred: (r != r) OR (a != a) + │ + └── • cross join (inner) + │ columns: ("lookup_join_const_col_@16", r, a, b_new, c) + │ estimated row count: 3 + │ + ├── • values + │ columns: ("lookup_join_const_col_@16") + │ size: 1 column, 3 rows + │ row 0, expr 0: 'us-east' + │ row 1, expr 0: 'us-west' + │ row 2, expr 0: 'eu-west' + │ + └── • filter + │ columns: (r, a, b_new, c) + │ estimated row count: 1 + │ filter: c IN ('bar', 'baz', 'foo') + │ + └── • project + │ columns: (r, a, b_new, c) + │ estimated row count: 1 + │ + └── • scan buffer + columns: (r, a, b, b_new, partial_index_put1, partial_index_put1, c) + label: buffer 1 + + # -- Tests with UPSERT -- subtest Upsert diff --git a/pkg/sql/opt/norm/testdata/rules/prune_cols b/pkg/sql/opt/norm/testdata/rules/prune_cols index 6431de0c0f3f..6d4d935295a6 100644 --- a/pkg/sql/opt/norm/testdata/rules/prune_cols +++ b/pkg/sql/opt/norm/testdata/rules/prune_cols @@ -89,6 +89,20 @@ CREATE TABLE uniq ( ) ---- +exec-ddl +CREATE TABLE uniq_partial ( + k INT PRIMARY KEY, + v INT, + w INT, + x INT, + UNIQUE WITHOUT INDEX (v) WHERE w > 0, + FAMILY (k), + FAMILY (v), + FAMILY (w), + FAMILY (x) +) +---- + exec-ddl CREATE TABLE uniq_fk_parent ( k INT PRIMARY KEY, @@ -3578,6 +3592,8 @@ upsert checks └── column2:7 > upsert_b:16 [as=check3:19, outer=(7,16)] # Do not prune columns from updates that are needed for unique checks. +# TODO(mgartner): v and z can be pruned because they are not updated and not +# needed for uniqueness checks. norm expect=PruneMutationInputCols UPDATE uniq SET w = 1, x = 2 WHERE k = 3 ---- @@ -3662,6 +3678,76 @@ update uniq ├── y:41 = uniq.y:34 [outer=(34,41), constraints=(/34: (/NULL - ]; /41: (/NULL - ]), fd=(34)==(41), (41)==(34)] └── k:37 != uniq.k:30 [outer=(30,37), constraints=(/30: (/NULL - ]; /37: (/NULL - ])] +# Do not prune columns from updates that are needed for partial unique checks. +# TODO(mgartner): x can be pruned because it is not updated and not needed for +# uniqueness checks. +norm expect=PruneMutationInputCols +UPDATE uniq_partial SET v = 1 WHERE k = 3 +---- +update uniq_partial + ├── columns: + ├── fetch columns: uniq_partial.k:6 uniq_partial.v:7 + ├── update-mapping: + │ └── v_new:11 => uniq_partial.v:2 + ├── input binding: &1 + ├── cardinality: [0 - 0] + ├── volatile, mutations + ├── project + │ ├── columns: v_new:11!null uniq_partial.k:6!null uniq_partial.v:7 uniq_partial.w:8 uniq_partial.x:9 + │ ├── cardinality: [0 - 1] + │ ├── key: () + │ ├── fd: ()-->(6-9,11) + │ ├── select + │ │ ├── columns: uniq_partial.k:6!null uniq_partial.v:7 uniq_partial.w:8 uniq_partial.x:9 + │ │ ├── cardinality: [0 - 1] + │ │ ├── key: () + │ │ ├── fd: ()-->(6-9) + │ │ ├── scan uniq_partial + │ │ │ ├── columns: uniq_partial.k:6!null uniq_partial.v:7 uniq_partial.w:8 uniq_partial.x:9 + │ │ │ ├── key: (6) + │ │ │ └── fd: (6)-->(7-9) + │ │ └── filters + │ │ └── uniq_partial.k:6 = 3 [outer=(6), constraints=(/6: [/3 - /3]; tight), fd=()-->(6)] + │ └── projections + │ └── 1 [as=v_new:11] + └── unique-checks + └── unique-checks-item: uniq_partial(v) + └── semi-join (hash) + ├── columns: k:17!null v:18!null w:19!null x:20 + ├── cardinality: [0 - 1] + ├── key: () + ├── fd: ()-->(17-20) + ├── select + │ ├── columns: k:17!null v:18!null w:19!null x:20 + │ ├── cardinality: [0 - 1] + │ ├── key: () + │ ├── fd: ()-->(17-20) + │ ├── with-scan &1 + │ │ ├── columns: k:17!null v:18!null w:19 x:20 + │ │ ├── mapping: + │ │ │ ├── uniq_partial.k:6 => k:17 + │ │ │ ├── v_new:11 => v:18 + │ │ │ ├── uniq_partial.w:8 => w:19 + │ │ │ └── uniq_partial.x:9 => x:20 + │ │ ├── cardinality: [0 - 1] + │ │ ├── key: () + │ │ └── fd: ()-->(17-20) + │ └── filters + │ └── w:19 > 0 [outer=(19), constraints=(/19: [/1 - ]; tight)] + ├── select + │ ├── columns: uniq_partial.k:12!null uniq_partial.v:13 uniq_partial.w:14!null + │ ├── key: (12) + │ ├── fd: (12)-->(13,14) + │ ├── scan uniq_partial + │ │ ├── columns: uniq_partial.k:12!null uniq_partial.v:13 uniq_partial.w:14 + │ │ ├── key: (12) + │ │ └── fd: (12)-->(13,14) + │ └── filters + │ └── uniq_partial.w:14 > 0 [outer=(14), constraints=(/14: [/1 - ]; tight)] + └── filters + ├── v:18 = uniq_partial.v:13 [outer=(13,18), constraints=(/13: (/NULL - ]; /18: (/NULL - ]), fd=(13)==(18), (18)==(13)] + └── k:17 != uniq_partial.k:12 [outer=(12,17), constraints=(/12: (/NULL - ]; /17: (/NULL - ])] + # Do not prune columns that are needed for foreign key checks or cascades. norm expect=PruneMutationInputCols INSERT INTO uniq_fk_parent VALUES (2, 1) ON CONFLICT (k) DO UPDATE SET c = 1 diff --git a/pkg/sql/opt/optbuilder/mutation_builder.go b/pkg/sql/opt/optbuilder/mutation_builder.go index f4bc19a7c608..109e84646dc7 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder.go +++ b/pkg/sql/opt/optbuilder/mutation_builder.go @@ -1201,12 +1201,12 @@ func (mb *mutationBuilder) parsePartialIndexPredicateExpr(idx cat.IndexOrdinal) // parseUniqueConstraintPredicateExpr parses the predicate of the given partial // unique constraint and caches it for reuse. This function panics if the unique // constraint at the given ordinal is not partial. -func (mb *mutationBuilder) parseUniqueConstraintPredicateExpr(idx cat.UniqueOrdinal) tree.Expr { - uniqueConstraint := mb.tab.Unique(idx) +func (mb *mutationBuilder) parseUniqueConstraintPredicateExpr(uniq cat.UniqueOrdinal) tree.Expr { + uniqueConstraint := mb.tab.Unique(uniq) predStr, isPartial := uniqueConstraint.Predicate() if !isPartial { - panic(errors.AssertionFailedf("unique constraint at ordinal %d is not a partial unique constraint", idx)) + panic(errors.AssertionFailedf("unique constraint at ordinal %d is not a partial unique constraint", uniq)) } if mb.parsedUniqueConstraintExprs == nil { @@ -1214,8 +1214,8 @@ func (mb *mutationBuilder) parseUniqueConstraintPredicateExpr(idx cat.UniqueOrdi } // Return expression from the cache, if it was already parsed previously. - if mb.parsedUniqueConstraintExprs[idx] != nil { - return mb.parsedUniqueConstraintExprs[idx] + if mb.parsedUniqueConstraintExprs[uniq] != nil { + return mb.parsedUniqueConstraintExprs[uniq] } expr, err := parser.ParseExpr(predStr) @@ -1223,7 +1223,7 @@ func (mb *mutationBuilder) parseUniqueConstraintPredicateExpr(idx cat.UniqueOrdi panic(err) } - mb.parsedUniqueConstraintExprs[idx] = expr + mb.parsedUniqueConstraintExprs[uniq] = expr return expr } diff --git a/pkg/sql/opt/optbuilder/mutation_builder_unique.go b/pkg/sql/opt/optbuilder/mutation_builder_unique.go index 5cedbbc68253..492875cff950 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder_unique.go +++ b/pkg/sql/opt/optbuilder/mutation_builder_unique.go @@ -107,14 +107,32 @@ func (mb *mutationBuilder) hasUniqueWithoutIndexConstraints() bool { } // uniqueColsUpdated returns true if any of the columns for a unique -// constraint are being updated (according to updateColIDs). +// constraint are being updated (according to updateColIDs). When the unique +// constraint has a partial predicate, it also returns true if the predicate +// references any of the columns being updated. func (mb *mutationBuilder) uniqueColsUpdated(uniqueOrdinal int) bool { uc := mb.tab.Unique(uniqueOrdinal) + for i, n := 0, uc.ColumnCount(); i < n; i++ { if ord := uc.ColumnOrdinal(mb.tab, i); mb.updateColIDs[ord] != 0 { return true } } + + if _, isPartial := uc.Predicate(); isPartial { + pred := mb.parseUniqueConstraintPredicateExpr(uniqueOrdinal) + typedPred := mb.fetchScope.resolveAndRequireType(pred, types.Bool) + + var predCols opt.ColSet + mb.b.buildScalar(typedPred, mb.fetchScope, nil, nil, &predCols) + for colID, ok := predCols.Next(0); ok; colID, ok = predCols.Next(colID + 1) { + ord := mb.md.ColumnMeta(colID).Table.ColumnOrdinal(colID) + if mb.updateColIDs[ord] != 0 { + return true + } + } + } + return false } diff --git a/pkg/sql/opt/optbuilder/testdata/unique-checks-update b/pkg/sql/opt/optbuilder/testdata/unique-checks-update index 77ced5f530b6..a1627c60cd21 100644 --- a/pkg/sql/opt/optbuilder/testdata/unique-checks-update +++ b/pkg/sql/opt/optbuilder/testdata/unique-checks-update @@ -62,7 +62,7 @@ update uniq ├── y:36 = uniq.y:30 └── k:32 != uniq.k:26 -# No need to plan checks for w since it's aways null. +# No need to plan checks for w since it's always null. build UPDATE uniq SET w = NULL, x = 1 ---- @@ -99,7 +99,7 @@ update uniq ├── y:25 = uniq.y:19 └── k:21 != uniq.k:15 -# No need to plan checks for x,y since x is aways null. +# No need to plan checks for x,y since x is always null. # Also update the primary key. build UPDATE uniq SET k = 1, w = 2, x = NULL @@ -138,7 +138,7 @@ update uniq ├── w:24 = uniq.w:18 └── k:22 != uniq.k:16 -# No need to plan checks for x,y since y is aways null. +# No need to plan checks for x,y since y is always null. build UPDATE uniq SET w = 1, y = NULL WHERE k = 1 ---- @@ -570,3 +570,444 @@ update uniq_hidden_pk └── filters ├── a:37 = uniq_hidden_pk.a:31 └── rowid:41 != uniq_hidden_pk.rowid:35 + +exec-ddl +CREATE TABLE uniq_partial ( + k INT PRIMARY KEY, + a INT, + b INT, + c INT, + UNIQUE WITHOUT INDEX (a) WHERE b > 0 +) +---- + +# None of the updated values have nulls. +build +UPDATE uniq_partial SET a = 1 +---- +update uniq_partial + ├── columns: + ├── fetch columns: uniq_partial.k:6 uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 + ├── update-mapping: + │ └── a_new:11 => uniq_partial.a:2 + ├── input binding: &1 + ├── project + │ ├── columns: a_new:11!null uniq_partial.k:6!null uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 crdb_internal_mvcc_timestamp:10 + │ ├── scan uniq_partial + │ │ └── columns: uniq_partial.k:6!null uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 crdb_internal_mvcc_timestamp:10 + │ └── projections + │ └── 1 [as=a_new:11] + └── unique-checks + └── unique-checks-item: uniq_partial(a) + └── semi-join (hash) + ├── columns: k:17!null a:18!null b:19 c:20 + ├── with-scan &1 + │ ├── columns: k:17!null a:18!null b:19 c:20 + │ └── mapping: + │ ├── uniq_partial.k:6 => k:17 + │ ├── a_new:11 => a:18 + │ ├── uniq_partial.b:8 => b:19 + │ └── uniq_partial.c:9 => c:20 + ├── scan uniq_partial + │ └── columns: uniq_partial.k:12!null uniq_partial.a:13 uniq_partial.b:14 uniq_partial.c:15 + └── filters + ├── a:18 = uniq_partial.a:13 + ├── b:19 > 0 + ├── uniq_partial.b:14 > 0 + └── k:17 != uniq_partial.k:12 + +# Plan a check when a column in the predicate is updated. +build +UPDATE uniq_partial SET b = 1 +---- +update uniq_partial + ├── columns: + ├── fetch columns: uniq_partial.k:6 uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 + ├── update-mapping: + │ └── b_new:11 => uniq_partial.b:3 + ├── input binding: &1 + ├── project + │ ├── columns: b_new:11!null uniq_partial.k:6!null uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 crdb_internal_mvcc_timestamp:10 + │ ├── scan uniq_partial + │ │ └── columns: uniq_partial.k:6!null uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 crdb_internal_mvcc_timestamp:10 + │ └── projections + │ └── 1 [as=b_new:11] + └── unique-checks + └── unique-checks-item: uniq_partial(a) + └── semi-join (hash) + ├── columns: k:17!null a:18 b:19!null c:20 + ├── with-scan &1 + │ ├── columns: k:17!null a:18 b:19!null c:20 + │ └── mapping: + │ ├── uniq_partial.k:6 => k:17 + │ ├── uniq_partial.a:7 => a:18 + │ ├── b_new:11 => b:19 + │ └── uniq_partial.c:9 => c:20 + ├── scan uniq_partial + │ └── columns: uniq_partial.k:12!null uniq_partial.a:13 uniq_partial.b:14 uniq_partial.c:15 + └── filters + ├── a:18 = uniq_partial.a:13 + ├── b:19 > 0 + ├── uniq_partial.b:14 > 0 + └── k:17 != uniq_partial.k:12 + +# No need to plan checks for a since it's always null. +build +UPDATE uniq_partial SET a = NULL, b = 1 +---- +update uniq_partial + ├── columns: + ├── fetch columns: k:6 a:7 b:8 c:9 + ├── update-mapping: + │ ├── a_new:11 => a:2 + │ └── b_new:12 => b:3 + └── project + ├── columns: a_new:11 b_new:12!null k:6!null a:7 b:8 c:9 crdb_internal_mvcc_timestamp:10 + ├── scan uniq_partial + │ └── columns: k:6!null a:7 b:8 c:9 crdb_internal_mvcc_timestamp:10 + └── projections + ├── NULL::INT8 [as=a_new:11] + └── 1 [as=b_new:12] + +# No need to plan checks for a since it's always null. +# Also update the primary key. +build +UPDATE uniq_partial SET k = 1, a = NULL, b = 1 +---- +update uniq_partial + ├── columns: + ├── fetch columns: k:6 a:7 b:8 c:9 + ├── update-mapping: + │ ├── k_new:11 => k:1 + │ ├── a_new:12 => a:2 + │ └── k_new:11 => b:3 + └── project + ├── columns: k_new:11!null a_new:12 k:6!null a:7 b:8 c:9 crdb_internal_mvcc_timestamp:10 + ├── scan uniq_partial + │ └── columns: k:6!null a:7 b:8 c:9 crdb_internal_mvcc_timestamp:10 + └── projections + ├── 1 [as=k_new:11] + └── NULL::INT8 [as=a_new:12] + +# No need to plan checks since none of the columns in the unique constraint or +# its predicate are updated. +build +UPDATE uniq_partial SET c = 2 +---- +update uniq_partial + ├── columns: + ├── fetch columns: k:6 a:7 b:8 c:9 + ├── update-mapping: + │ └── c_new:11 => c:4 + └── project + ├── columns: c_new:11!null k:6!null a:7 b:8 c:9 crdb_internal_mvcc_timestamp:10 + ├── scan uniq_partial + │ └── columns: k:6!null a:7 b:8 c:9 crdb_internal_mvcc_timestamp:10 + └── projections + └── 2 [as=c_new:11] + +# Update with non-constant input. +build +UPDATE uniq_partial SET a = other.w, b = other.x FROM other +---- +update uniq_partial + ├── columns: + ├── fetch columns: uniq_partial.k:6 uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 + ├── update-mapping: + │ ├── w:13 => uniq_partial.a:2 + │ └── x:14 => uniq_partial.b:3 + ├── input binding: &1 + ├── distinct-on + │ ├── columns: uniq_partial.k:6!null uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 uniq_partial.crdb_internal_mvcc_timestamp:10 other.k:11 v:12 w:13!null x:14 y:15 rowid:16!null other.crdb_internal_mvcc_timestamp:17 + │ ├── grouping columns: uniq_partial.k:6!null + │ ├── inner-join (cross) + │ │ ├── columns: uniq_partial.k:6!null uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 uniq_partial.crdb_internal_mvcc_timestamp:10 other.k:11 v:12 w:13!null x:14 y:15 rowid:16!null other.crdb_internal_mvcc_timestamp:17 + │ │ ├── scan uniq_partial + │ │ │ └── columns: uniq_partial.k:6!null uniq_partial.a:7 uniq_partial.b:8 uniq_partial.c:9 uniq_partial.crdb_internal_mvcc_timestamp:10 + │ │ ├── scan other + │ │ │ └── columns: other.k:11 v:12 w:13!null x:14 y:15 rowid:16!null other.crdb_internal_mvcc_timestamp:17 + │ │ └── filters (true) + │ └── aggregations + │ ├── first-agg [as=uniq_partial.a:7] + │ │ └── uniq_partial.a:7 + │ ├── first-agg [as=uniq_partial.b:8] + │ │ └── uniq_partial.b:8 + │ ├── first-agg [as=uniq_partial.c:9] + │ │ └── uniq_partial.c:9 + │ ├── first-agg [as=uniq_partial.crdb_internal_mvcc_timestamp:10] + │ │ └── uniq_partial.crdb_internal_mvcc_timestamp:10 + │ ├── first-agg [as=other.k:11] + │ │ └── other.k:11 + │ ├── first-agg [as=v:12] + │ │ └── v:12 + │ ├── first-agg [as=w:13] + │ │ └── w:13 + │ ├── first-agg [as=x:14] + │ │ └── x:14 + │ ├── first-agg [as=y:15] + │ │ └── y:15 + │ ├── first-agg [as=rowid:16] + │ │ └── rowid:16 + │ └── first-agg [as=other.crdb_internal_mvcc_timestamp:17] + │ └── other.crdb_internal_mvcc_timestamp:17 + └── unique-checks + └── unique-checks-item: uniq_partial(a) + └── semi-join (hash) + ├── columns: k:23!null a:24!null b:25 c:26 + ├── with-scan &1 + │ ├── columns: k:23!null a:24!null b:25 c:26 + │ └── mapping: + │ ├── uniq_partial.k:6 => k:23 + │ ├── w:13 => a:24 + │ ├── x:14 => b:25 + │ └── uniq_partial.c:9 => c:26 + ├── scan uniq_partial + │ └── columns: uniq_partial.k:18!null uniq_partial.a:19 uniq_partial.b:20 uniq_partial.c:21 + └── filters + ├── a:24 = uniq_partial.a:19 + ├── b:25 > 0 + ├── uniq_partial.b:20 > 0 + └── k:23 != uniq_partial.k:18 + +exec-ddl +CREATE TABLE uniq_partial_overlaps_pk ( + a INT, + b INT, + c INT, + d INT, + PRIMARY KEY (a, b), + UNIQUE WITHOUT INDEX (c) WHERE d > 0, + UNIQUE WITHOUT INDEX (a) WHERE d > 0, + UNIQUE WITHOUT INDEX (a, b) WHERE d > 0, + UNIQUE WITHOUT INDEX (b, c) WHERE d > 0, + UNIQUE WITHOUT INDEX (a, b, c) WHERE d > 0 +) +---- + +# Update with constant input. +# Do not build uniqueness checks when the primary key columns are a subset of +# the partial unique constraint columns. +# Add inequality filters for the primary key columns that are not part of each +# unique constraint to prevent rows from matching themselves in the semi join. +build +UPDATE uniq_partial_overlaps_pk SET a = 1, b = 2, c = 3, d = 4 WHERE a = 5 +---- +update uniq_partial_overlaps_pk + ├── columns: + ├── fetch columns: uniq_partial_overlaps_pk.a:6 uniq_partial_overlaps_pk.b:7 uniq_partial_overlaps_pk.c:8 uniq_partial_overlaps_pk.d:9 + ├── update-mapping: + │ ├── a_new:11 => uniq_partial_overlaps_pk.a:1 + │ ├── b_new:12 => uniq_partial_overlaps_pk.b:2 + │ ├── c_new:13 => uniq_partial_overlaps_pk.c:3 + │ └── d_new:14 => uniq_partial_overlaps_pk.d:4 + ├── input binding: &1 + ├── project + │ ├── columns: a_new:11!null b_new:12!null c_new:13!null d_new:14!null uniq_partial_overlaps_pk.a:6!null uniq_partial_overlaps_pk.b:7!null uniq_partial_overlaps_pk.c:8 uniq_partial_overlaps_pk.d:9 crdb_internal_mvcc_timestamp:10 + │ ├── select + │ │ ├── columns: uniq_partial_overlaps_pk.a:6!null uniq_partial_overlaps_pk.b:7!null uniq_partial_overlaps_pk.c:8 uniq_partial_overlaps_pk.d:9 crdb_internal_mvcc_timestamp:10 + │ │ ├── scan uniq_partial_overlaps_pk + │ │ │ └── columns: uniq_partial_overlaps_pk.a:6!null uniq_partial_overlaps_pk.b:7!null uniq_partial_overlaps_pk.c:8 uniq_partial_overlaps_pk.d:9 crdb_internal_mvcc_timestamp:10 + │ │ └── filters + │ │ └── uniq_partial_overlaps_pk.a:6 = 5 + │ └── projections + │ ├── 1 [as=a_new:11] + │ ├── 2 [as=b_new:12] + │ ├── 3 [as=c_new:13] + │ └── 4 [as=d_new:14] + └── unique-checks + ├── unique-checks-item: uniq_partial_overlaps_pk(c) + │ └── semi-join (hash) + │ ├── columns: a:20!null b:21!null c:22!null d:23!null + │ ├── with-scan &1 + │ │ ├── columns: a:20!null b:21!null c:22!null d:23!null + │ │ └── mapping: + │ │ ├── a_new:11 => a:20 + │ │ ├── b_new:12 => b:21 + │ │ ├── c_new:13 => c:22 + │ │ └── d_new:14 => d:23 + │ ├── scan uniq_partial_overlaps_pk + │ │ └── columns: uniq_partial_overlaps_pk.a:15!null uniq_partial_overlaps_pk.b:16!null uniq_partial_overlaps_pk.c:17 uniq_partial_overlaps_pk.d:18 + │ └── filters + │ ├── c:22 = uniq_partial_overlaps_pk.c:17 + │ ├── d:23 > 0 + │ ├── uniq_partial_overlaps_pk.d:18 > 0 + │ └── (a:20 != uniq_partial_overlaps_pk.a:15) OR (b:21 != uniq_partial_overlaps_pk.b:16) + ├── unique-checks-item: uniq_partial_overlaps_pk(a) + │ └── semi-join (hash) + │ ├── columns: a:29!null b:30!null c:31!null d:32!null + │ ├── with-scan &1 + │ │ ├── columns: a:29!null b:30!null c:31!null d:32!null + │ │ └── mapping: + │ │ ├── a_new:11 => a:29 + │ │ ├── b_new:12 => b:30 + │ │ ├── c_new:13 => c:31 + │ │ └── d_new:14 => d:32 + │ ├── scan uniq_partial_overlaps_pk + │ │ └── columns: uniq_partial_overlaps_pk.a:24!null uniq_partial_overlaps_pk.b:25!null uniq_partial_overlaps_pk.c:26 uniq_partial_overlaps_pk.d:27 + │ └── filters + │ ├── a:29 = uniq_partial_overlaps_pk.a:24 + │ ├── d:32 > 0 + │ ├── uniq_partial_overlaps_pk.d:27 > 0 + │ └── b:30 != uniq_partial_overlaps_pk.b:25 + └── unique-checks-item: uniq_partial_overlaps_pk(b,c) + └── semi-join (hash) + ├── columns: a:38!null b:39!null c:40!null d:41!null + ├── with-scan &1 + │ ├── columns: a:38!null b:39!null c:40!null d:41!null + │ └── mapping: + │ ├── a_new:11 => a:38 + │ ├── b_new:12 => b:39 + │ ├── c_new:13 => c:40 + │ └── d_new:14 => d:41 + ├── scan uniq_partial_overlaps_pk + │ └── columns: uniq_partial_overlaps_pk.a:33!null uniq_partial_overlaps_pk.b:34!null uniq_partial_overlaps_pk.c:35 uniq_partial_overlaps_pk.d:36 + └── filters + ├── b:39 = uniq_partial_overlaps_pk.b:34 + ├── c:40 = uniq_partial_overlaps_pk.c:35 + ├── d:41 > 0 + ├── uniq_partial_overlaps_pk.d:36 > 0 + └── a:38 != uniq_partial_overlaps_pk.a:33 + +# Update with non-constant input. +# Do not build uniqueness checks when the primary key columns are a subset of +# the partial unique constraint columns. +# No need to add a check for b,c since those columns weren't updated. +# Add inequality filters for the primary key columns that are not part of each +# unique constraint to prevent rows from matching themselves in the semi join. +build +UPDATE uniq_partial_overlaps_pk SET a = k FROM other +---- +update uniq_partial_overlaps_pk + ├── columns: + ├── fetch columns: uniq_partial_overlaps_pk.a:6 uniq_partial_overlaps_pk.b:7 uniq_partial_overlaps_pk.c:8 uniq_partial_overlaps_pk.d:9 + ├── update-mapping: + │ └── k:11 => uniq_partial_overlaps_pk.a:1 + ├── input binding: &1 + ├── distinct-on + │ ├── columns: uniq_partial_overlaps_pk.a:6!null uniq_partial_overlaps_pk.b:7!null uniq_partial_overlaps_pk.c:8 uniq_partial_overlaps_pk.d:9 uniq_partial_overlaps_pk.crdb_internal_mvcc_timestamp:10 k:11 v:12 w:13!null x:14 y:15 rowid:16!null other.crdb_internal_mvcc_timestamp:17 + │ ├── grouping columns: uniq_partial_overlaps_pk.a:6!null uniq_partial_overlaps_pk.b:7!null + │ ├── inner-join (cross) + │ │ ├── columns: uniq_partial_overlaps_pk.a:6!null uniq_partial_overlaps_pk.b:7!null uniq_partial_overlaps_pk.c:8 uniq_partial_overlaps_pk.d:9 uniq_partial_overlaps_pk.crdb_internal_mvcc_timestamp:10 k:11 v:12 w:13!null x:14 y:15 rowid:16!null other.crdb_internal_mvcc_timestamp:17 + │ │ ├── scan uniq_partial_overlaps_pk + │ │ │ └── columns: uniq_partial_overlaps_pk.a:6!null uniq_partial_overlaps_pk.b:7!null uniq_partial_overlaps_pk.c:8 uniq_partial_overlaps_pk.d:9 uniq_partial_overlaps_pk.crdb_internal_mvcc_timestamp:10 + │ │ ├── scan other + │ │ │ └── columns: k:11 v:12 w:13!null x:14 y:15 rowid:16!null other.crdb_internal_mvcc_timestamp:17 + │ │ └── filters (true) + │ └── aggregations + │ ├── first-agg [as=uniq_partial_overlaps_pk.c:8] + │ │ └── uniq_partial_overlaps_pk.c:8 + │ ├── first-agg [as=uniq_partial_overlaps_pk.d:9] + │ │ └── uniq_partial_overlaps_pk.d:9 + │ ├── first-agg [as=uniq_partial_overlaps_pk.crdb_internal_mvcc_timestamp:10] + │ │ └── uniq_partial_overlaps_pk.crdb_internal_mvcc_timestamp:10 + │ ├── first-agg [as=k:11] + │ │ └── k:11 + │ ├── first-agg [as=v:12] + │ │ └── v:12 + │ ├── first-agg [as=w:13] + │ │ └── w:13 + │ ├── first-agg [as=x:14] + │ │ └── x:14 + │ ├── first-agg [as=y:15] + │ │ └── y:15 + │ ├── first-agg [as=rowid:16] + │ │ └── rowid:16 + │ └── first-agg [as=other.crdb_internal_mvcc_timestamp:17] + │ └── other.crdb_internal_mvcc_timestamp:17 + └── unique-checks + └── unique-checks-item: uniq_partial_overlaps_pk(a) + └── semi-join (hash) + ├── columns: a:23 b:24!null c:25 d:26 + ├── with-scan &1 + │ ├── columns: a:23 b:24!null c:25 d:26 + │ └── mapping: + │ ├── k:11 => a:23 + │ ├── uniq_partial_overlaps_pk.b:7 => b:24 + │ ├── uniq_partial_overlaps_pk.c:8 => c:25 + │ └── uniq_partial_overlaps_pk.d:9 => d:26 + ├── scan uniq_partial_overlaps_pk + │ └── columns: uniq_partial_overlaps_pk.a:18!null uniq_partial_overlaps_pk.b:19!null uniq_partial_overlaps_pk.c:20 uniq_partial_overlaps_pk.d:21 + └── filters + ├── a:23 = uniq_partial_overlaps_pk.a:18 + ├── d:26 > 0 + ├── uniq_partial_overlaps_pk.d:21 > 0 + └── b:24 != uniq_partial_overlaps_pk.b:19 + +exec-ddl +CREATE TABLE uniq_partial_hidden_pk ( + a INT, + b INT, + UNIQUE WITHOUT INDEX (a) WHERE b > 0 +) +---- + +# Update with constant input. +# Add inequality filters for the hidden primary key column. +build +UPDATE uniq_partial_hidden_pk SET a = 1 +---- +update uniq_partial_hidden_pk + ├── columns: + ├── fetch columns: uniq_partial_hidden_pk.a:5 uniq_partial_hidden_pk.b:6 uniq_partial_hidden_pk.rowid:7 + ├── update-mapping: + │ └── a_new:9 => uniq_partial_hidden_pk.a:1 + ├── input binding: &1 + ├── project + │ ├── columns: a_new:9!null uniq_partial_hidden_pk.a:5 uniq_partial_hidden_pk.b:6 uniq_partial_hidden_pk.rowid:7!null crdb_internal_mvcc_timestamp:8 + │ ├── scan uniq_partial_hidden_pk + │ │ └── columns: uniq_partial_hidden_pk.a:5 uniq_partial_hidden_pk.b:6 uniq_partial_hidden_pk.rowid:7!null crdb_internal_mvcc_timestamp:8 + │ └── projections + │ └── 1 [as=a_new:9] + └── unique-checks + └── unique-checks-item: uniq_partial_hidden_pk(a) + └── semi-join (hash) + ├── columns: a:14!null b:15 rowid:16!null + ├── with-scan &1 + │ ├── columns: a:14!null b:15 rowid:16!null + │ └── mapping: + │ ├── a_new:9 => a:14 + │ ├── uniq_partial_hidden_pk.b:6 => b:15 + │ └── uniq_partial_hidden_pk.rowid:7 => rowid:16 + ├── scan uniq_partial_hidden_pk + │ └── columns: uniq_partial_hidden_pk.a:10 uniq_partial_hidden_pk.b:11 uniq_partial_hidden_pk.rowid:12!null + └── filters + ├── a:14 = uniq_partial_hidden_pk.a:10 + ├── b:15 > 0 + ├── uniq_partial_hidden_pk.b:11 > 0 + └── rowid:16 != uniq_partial_hidden_pk.rowid:12 + +# Update with non-constant input. +# Add inequality filters for the hidden primary key column. +build +UPDATE uniq_partial_hidden_pk SET a = k FROM other +---- +update uniq_partial_hidden_pk + ├── columns: + ├── fetch columns: uniq_partial_hidden_pk.a:5 uniq_partial_hidden_pk.b:6 uniq_partial_hidden_pk.rowid:7 + ├── update-mapping: + │ └── k:9 => uniq_partial_hidden_pk.a:1 + ├── input binding: &1 + ├── inner-join (cross) + │ ├── columns: uniq_partial_hidden_pk.a:5 uniq_partial_hidden_pk.b:6 uniq_partial_hidden_pk.rowid:7!null uniq_partial_hidden_pk.crdb_internal_mvcc_timestamp:8 k:9 v:10 w:11!null x:12 y:13 other.rowid:14!null other.crdb_internal_mvcc_timestamp:15 + │ ├── scan uniq_partial_hidden_pk + │ │ └── columns: uniq_partial_hidden_pk.a:5 uniq_partial_hidden_pk.b:6 uniq_partial_hidden_pk.rowid:7!null uniq_partial_hidden_pk.crdb_internal_mvcc_timestamp:8 + │ ├── scan other + │ │ └── columns: k:9 v:10 w:11!null x:12 y:13 other.rowid:14!null other.crdb_internal_mvcc_timestamp:15 + │ └── filters (true) + └── unique-checks + └── unique-checks-item: uniq_partial_hidden_pk(a) + └── semi-join (hash) + ├── columns: a:20 b:21 rowid:22!null + ├── with-scan &1 + │ ├── columns: a:20 b:21 rowid:22!null + │ └── mapping: + │ ├── k:9 => a:20 + │ ├── uniq_partial_hidden_pk.b:6 => b:21 + │ └── uniq_partial_hidden_pk.rowid:7 => rowid:22 + ├── scan uniq_partial_hidden_pk + │ └── columns: uniq_partial_hidden_pk.a:16 uniq_partial_hidden_pk.b:17 uniq_partial_hidden_pk.rowid:18!null + └── filters + ├── a:20 = uniq_partial_hidden_pk.a:16 + ├── b:21 > 0 + ├── uniq_partial_hidden_pk.b:17 > 0 + └── rowid:22 != uniq_partial_hidden_pk.rowid:18