diff --git a/pkg/ccl/logictestccl/testdata/logic_test/zone b/pkg/ccl/logictestccl/testdata/logic_test/zone index 853026b3e277..0c322f4d4c75 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/zone +++ b/pkg/ccl/logictestccl/testdata/logic_test/zone @@ -593,3 +593,26 @@ PARTITION p1 OF INDEX "my database".public.t2@x1 ALTER PARTITION p1 OF IND num_replicas = 1 PARTITION p1 OF INDEX "my database".public.t2@x2 ALTER PARTITION p1 OF INDEX "my database".public.t2@x2 CONFIGURE ZONE USING num_replicas = 1 + +# regression for #40417 +statement ok +CREATE TABLE t40417 (x INT PRIMARY KEY) + +statement ok +ALTER TABLE t40417 PARTITION BY LIST (x) ( PARTITION p1 VALUES IN (1)); + +statement ok +ALTER PARTITION p1 OF TABLE t40417 CONFIGURE ZONE USING num_replicas = 1 + +query TT +SHOW CREATE TABLE t40417 +---- +t40417 CREATE TABLE t40417 ( + x INT8 NOT NULL, + CONSTRAINT "primary" PRIMARY KEY (x ASC), + FAMILY "primary" (x) +) PARTITION BY LIST (x) ( + PARTITION p1 VALUES IN ((1)) +); +ALTER PARTITION p1 OF INDEX "my database".public.t40417@primary CONFIGURE ZONE USING + num_replicas = 1 diff --git a/pkg/sql/crdb_internal.go b/pkg/sql/crdb_internal.go index 56262e7cf5d4..1490ab2857e1 100644 --- a/pkg/sql/crdb_internal.go +++ b/pkg/sql/crdb_internal.go @@ -1215,7 +1215,8 @@ CREATE TABLE crdb_internal.create_statements ( if err != nil { return err } - if len(zoneConfig.Constraints) > 0 { + // If all constraints are default, then don't show anything. + if !zoneConfig.Equal(config.ZoneConfig{}) { sqlString := string(tree.MustBeDString(row[2])) zoneConfigStmts[tableName] = append(zoneConfigStmts[tableName], sqlString) } diff --git a/pkg/sql/opt/memo/statistics_builder.go b/pkg/sql/opt/memo/statistics_builder.go index e051f2d2da88..228fe3456967 100644 --- a/pkg/sql/opt/memo/statistics_builder.go +++ b/pkg/sql/opt/memo/statistics_builder.go @@ -852,8 +852,10 @@ func (sb *statisticsBuilder) buildJoin( s.RowCount = leftStats.RowCount case opt.AntiJoinOp, opt.AntiJoinApplyOp: - s.RowCount = 0 - s.Selectivity = 0 + // Don't set the row count to 0 since we can't guarantee that the + // cardinality is 0. + s.RowCount = epsilon + s.Selectivity = epsilon } return } diff --git a/pkg/sql/opt/memo/testdata/logprops/join b/pkg/sql/opt/memo/testdata/logprops/join index 2ddb6f1168a8..73e7357ae177 100644 --- a/pkg/sql/opt/memo/testdata/logprops/join +++ b/pkg/sql/opt/memo/testdata/logprops/join @@ -1417,3 +1417,16 @@ project │ └── filters (true) └── projections └── variable: mn.m [type=int, outer=(13)] + +# Regression test #40456. +opt +SELECT NULL +FROM uv +WHERE NOT EXISTS(SELECT uv.u); +---- +values + ├── columns: "?column?":5(unknown!null) + ├── cardinality: [0 - 0] + ├── key: () + ├── fd: ()-->(5) + └── prune: (5) diff --git a/pkg/sql/opt/memo/testdata/stats/join b/pkg/sql/opt/memo/testdata/stats/join index 340a415c76fd..88205c7be857 100644 --- a/pkg/sql/opt/memo/testdata/stats/join +++ b/pkg/sql/opt/memo/testdata/stats/join @@ -1380,3 +1380,65 @@ full-join (hash) │ ├── fd: ()-->(7) │ └── (NULL,) [type=tuple{unknown}] └── false [type=bool] + +expr +(SemiJoin + (Values + [ (Tuple [ (Const 1) (Const 2) ] "tuple{int}" ) ] + [ (Cols [ (NewColumn "a" "int") (NewColumn "b" "int") ]) ] + ) + (Scan [ (Table "uv") (Cols "u,v,rowid") ]) + [] + [] +) +---- +semi-join (hash) + ├── columns: a:1(int!null) b:2(int!null) + ├── cardinality: [0 - 1] + ├── stats: [rows=1] + ├── key: () + ├── fd: ()-->(1,2) + ├── values + │ ├── columns: a:1(int!null) b:2(int!null) + │ ├── cardinality: [1 - 1] + │ ├── stats: [rows=1] + │ ├── key: () + │ ├── fd: ()-->(1,2) + │ └── (1, 2) [type=tuple{int}] + ├── scan uv + │ ├── columns: u:3(int) v:4(int!null) rowid:5(int!null) + │ ├── stats: [rows=10000] + │ ├── key: (5) + │ └── fd: (5)-->(3,4) + └── filters (true) + +expr +(AntiJoin + (Values + [ (Tuple [ (Const 1) (Const 2) ] "tuple{int}" ) ] + [ (Cols [ (NewColumn "a" "int") (NewColumn "b" "int") ]) ] + ) + (Scan [ (Table "uv") (Cols "u,v,rowid") ]) + [] + [] +) +---- +anti-join (hash) + ├── columns: a:1(int!null) b:2(int!null) + ├── cardinality: [0 - 1] + ├── stats: [rows=1e-10] + ├── key: () + ├── fd: ()-->(1,2) + ├── values + │ ├── columns: a:1(int!null) b:2(int!null) + │ ├── cardinality: [1 - 1] + │ ├── stats: [rows=1] + │ ├── key: () + │ ├── fd: ()-->(1,2) + │ └── (1, 2) [type=tuple{int}] + ├── scan uv + │ ├── columns: u:3(int) v:4(int!null) rowid:5(int!null) + │ ├── stats: [rows=10000] + │ ├── key: (5) + │ └── fd: (5)-->(3,4) + └── filters (true) diff --git a/pkg/sql/opt/norm/rules/join.opt b/pkg/sql/opt/norm/rules/join.opt index 293fde9b1070..5f4652b0b0bd 100644 --- a/pkg/sql/opt/norm/rules/join.opt +++ b/pkg/sql/opt/norm/rules/join.opt @@ -325,6 +325,16 @@ => $left +# SimplifyZeroCardinalitySemiJoin converts a SemiJoin operator to an empty +# Values when it's known that the right input never returns any rows. +[SimplifyZeroCardinalitySemiJoin, Normalize] +(SemiJoin | SemiJoinApply + $left:* + $right:* & (HasZeroRows $right) +) +=> +(ConstructEmptyValues (OutputCols $left)) + # EliminateAntiJoin discards an AntiJoin operator when it's known that the right # input never returns any rows. [EliminateAntiJoin, Normalize] @@ -335,6 +345,18 @@ $left => $left +# SimplifyZeroCardinalityAntiJoin converts an AntiJoin operator to an empty +# Values when it's known that the right input never returns zero rows, and +# there is no join condition. +[SimplifyZeroCardinalityAntiJoin, Normalize] +(AntiJoin | AntiJoinApply + $left:* + $right:* & ^(CanHaveZeroRows $right) + [] +) +=> +(ConstructEmptyValues (OutputCols $left)) + # EliminateJoinNoColsLeft eliminates an InnerJoin with a one row, zero column # left input set. These can be produced when a Values, scalar GroupBy, or other # one-row operator's columns are never used. diff --git a/pkg/sql/opt/norm/testdata/rules/join b/pkg/sql/opt/norm/testdata/rules/join index 6ba9b82e15f5..abcaff997678 100644 --- a/pkg/sql/opt/norm/testdata/rules/join +++ b/pkg/sql/opt/norm/testdata/rules/join @@ -1841,6 +1841,27 @@ scan a ├── key: (1) └── fd: (1)-->(2-5) +opt expect=EliminateSemiJoin +SELECT * FROM a WHERE EXISTS(VALUES (k)) +---- +scan a + ├── columns: k:1(int!null) i:2(int) f:3(float!null) s:4(string) j:5(jsonb) + ├── key: (1) + └── fd: (1)-->(2-5) + +# -------------------------------------------------- +# SimplifyZeroCardinalitySemiJoin +# -------------------------------------------------- +# TODO(justin): figure out if there's a good way to make this still apply. +opt disable=SimplifyZeroCardinalityGroup expect=SimplifyZeroCardinalitySemiJoin +SELECT * FROM a WHERE EXISTS(SELECT * FROM (VALUES (k)) OFFSET 1) +---- +values + ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) s:4(string!null) j:5(jsonb!null) + ├── cardinality: [0 - 0] + ├── key: () + └── fd: ()-->(1-5) + # -------------------------------------------------- # EliminateAntiJoin # -------------------------------------------------- @@ -1853,6 +1874,27 @@ scan a ├── key: (1) └── fd: (1)-->(2-5) +# -------------------------------------------------- +# SimplifyZeroCardinalityAntiJoin +# -------------------------------------------------- +opt expect=SimplifyZeroCardinalityAntiJoin +SELECT * FROM a WHERE NOT EXISTS(SELECT count(*) FROM b WHERE x=k) +---- +values + ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) s:4(string!null) j:5(jsonb!null) + ├── cardinality: [0 - 0] + ├── key: () + └── fd: ()-->(1-5) + +opt expect=SimplifyZeroCardinalityAntiJoin +SELECT * FROM a WHERE NOT EXISTS(VALUES (k)) +---- +values + ├── columns: k:1(int!null) i:2(int!null) f:3(float!null) s:4(string!null) j:5(jsonb!null) + ├── cardinality: [0 - 0] + ├── key: () + └── fd: ()-->(1-5) + # -------------------------------------------------- # EliminateJoinNoColsLeft # -------------------------------------------------- diff --git a/pkg/storage/stateloader/stateloader.go b/pkg/storage/stateloader/stateloader.go index b401dfffa657..9280d06446a6 100644 --- a/pkg/storage/stateloader/stateloader.go +++ b/pkg/storage/stateloader/stateloader.go @@ -574,7 +574,7 @@ func (rsl StateLoader) LoadHardState( // SetHardState overwrites the HardState. func (rsl StateLoader) SetHardState( - ctx context.Context, batch engine.Writer, st raftpb.HardState, + ctx context.Context, batch engine.Writer, hs raftpb.HardState, ) error { // "Blind" because ms == nil and timestamp == hlc.Timestamp{}. return engine.MVCCBlindPutProto( @@ -583,7 +583,7 @@ func (rsl StateLoader) SetHardState( nil, /* ms */ rsl.RaftHardStateKey(), hlc.Timestamp{}, /* timestamp */ - &st, + &hs, nil, /* txn */ ) } diff --git a/pkg/storage/store.go b/pkg/storage/store.go index 418c2cc53143..4d6bbb3a5c92 100644 --- a/pkg/storage/store.go +++ b/pkg/storage/store.go @@ -4006,6 +4006,17 @@ func (s *Store) tryGetOrCreateReplica( s.mu.uninitReplicas[repl.RangeID] = repl s.mu.Unlock() + // An uninitiazlied replica should have an empty HardState.Commit at + // all times. Failure to maintain this invariant indicates corruption. + // And yet, we have observed this in the wild. See #40213. + if hs, err := repl.mu.stateLoader.LoadHardState(ctx, s.Engine()); err != nil { + repl.mu.Unlock() + repl.raftMu.Unlock() + return nil, false, err + } else if hs.Commit != 0 { + log.Fatalf(ctx, "found non-zero HardState.Commit on uninitialized replica %s. HS=%+v", repl, hs) + } + desc := &roachpb.RangeDescriptor{ RangeID: rangeID, // TODO(bdarnell): other fields are unknown; need to populate them from diff --git a/pkg/storage/store_test.go b/pkg/storage/store_test.go index ffae42512628..8829fd7a15cb 100644 --- a/pkg/storage/store_test.go +++ b/pkg/storage/store_test.go @@ -2863,7 +2863,8 @@ func TestStoreRemovePlaceholderOnRaftIgnored(t *testing.T) { s := tc.store ctx := context.Background() - // Clobber the existing range so we can test nonoverlapping placeholders. + // Clobber the existing range and recreated it with an uninitialized + // descriptor so we can test nonoverlapping placeholders. repl1, err := s.GetReplica(1) if err != nil { t.Fatal(err) @@ -2874,13 +2875,21 @@ func TestStoreRemovePlaceholderOnRaftIgnored(t *testing.T) { t.Fatal(err) } + uninitDesc := roachpb.RangeDescriptor{RangeID: repl1.Desc().RangeID} cv := s.ClusterSettings().Version.Version().Version if _, err := stateloader.WriteInitialState( - ctx, s.Engine(), enginepb.MVCCStats{}, *repl1.Desc(), roachpb.Lease{}, + ctx, s.Engine(), enginepb.MVCCStats{}, uninitDesc, roachpb.Lease{}, hlc.Timestamp{}, cv, stateloader.TruncatedStateUnreplicated, ); err != nil { t.Fatal(err) } + uninitRepl1, err := NewReplica(&uninitDesc, s, 0) + if err != nil { + t.Fatal(err) + } + if err := s.addReplicaToRangeMapLocked(uninitRepl1); err != nil { + t.Fatal(err) + } // Generate a minimal fake snapshot. snapData := &roachpb.RaftSnapshotData{}