diff --git a/pkg/sql/exec_util.go b/pkg/sql/exec_util.go index a4e98b64bc9a..5dfcca5a1fa4 100644 --- a/pkg/sql/exec_util.go +++ b/pkg/sql/exec_util.go @@ -3855,6 +3855,10 @@ func (m *sessionDataMutator) SetOptimizerPreferBoundedCardinality(b bool) { m.data.OptimizerPreferBoundedCardinality = b } +func (m *sessionDataMutator) SetOptimizerMinRowCount(val float64) { + m.data.OptimizerMinRowCount = val +} + // Utility functions related to scrubbing sensitive information on SQL Stats. // quantizeCounts ensures that the Count field in the diff --git a/pkg/sql/logictest/testdata/logic_test/information_schema b/pkg/sql/logictest/testdata/logic_test/information_schema index 54abae600726..b801a1c55700 100644 --- a/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/pkg/sql/logictest/testdata/logic_test/information_schema @@ -6172,6 +6172,7 @@ optimizer on optimizer_always_use_histograms on optimizer_hoist_uncorrelated_equality_subqueries on optimizer_merge_joins_enabled on +optimizer_min_row_count 0 optimizer_prefer_bounded_cardinality off optimizer_prove_implication_with_virtual_computed_columns on optimizer_push_limit_into_project_filtered_scan off diff --git a/pkg/sql/logictest/testdata/logic_test/pg_catalog b/pkg/sql/logictest/testdata/logic_test/pg_catalog index e448a57dcd53..2fa7872ed341 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -2947,6 +2947,7 @@ opt_split_scan_limit 2048 N optimizer_always_use_histograms on NULL NULL NULL string optimizer_hoist_uncorrelated_equality_subqueries on NULL NULL NULL string optimizer_merge_joins_enabled on NULL NULL NULL string +optimizer_min_row_count 0 NULL NULL NULL string optimizer_prefer_bounded_cardinality off NULL NULL NULL string optimizer_prove_implication_with_virtual_computed_columns on NULL NULL NULL string optimizer_push_limit_into_project_filtered_scan off NULL NULL NULL string @@ -3139,6 +3140,7 @@ opt_split_scan_limit 2048 N optimizer_always_use_histograms on NULL user NULL on on optimizer_hoist_uncorrelated_equality_subqueries on NULL user NULL on on optimizer_merge_joins_enabled on NULL user NULL on on +optimizer_min_row_count 0 NULL user NULL 0 0 optimizer_prefer_bounded_cardinality off NULL user NULL off off optimizer_prove_implication_with_virtual_computed_columns on NULL user NULL on on optimizer_push_limit_into_project_filtered_scan off NULL user NULL off off @@ -3330,6 +3332,7 @@ optimizer NULL NULL NULL optimizer_always_use_histograms NULL NULL NULL NULL NULL optimizer_hoist_uncorrelated_equality_subqueries NULL NULL NULL NULL NULL optimizer_merge_joins_enabled NULL NULL NULL NULL NULL +optimizer_min_row_count NULL NULL NULL NULL NULL optimizer_prefer_bounded_cardinality NULL NULL NULL NULL NULL optimizer_prove_implication_with_virtual_computed_columns NULL NULL NULL NULL NULL optimizer_push_limit_into_project_filtered_scan NULL NULL NULL NULL NULL diff --git a/pkg/sql/logictest/testdata/logic_test/show_source b/pkg/sql/logictest/testdata/logic_test/show_source index 0fde546acd0e..702d8d98bdd1 100644 --- a/pkg/sql/logictest/testdata/logic_test/show_source +++ b/pkg/sql/logictest/testdata/logic_test/show_source @@ -128,6 +128,7 @@ opt_split_scan_limit 2048 optimizer_always_use_histograms on optimizer_hoist_uncorrelated_equality_subqueries on optimizer_merge_joins_enabled on +optimizer_min_row_count 0 optimizer_prefer_bounded_cardinality off optimizer_prove_implication_with_virtual_computed_columns on optimizer_push_limit_into_project_filtered_scan off diff --git a/pkg/sql/opt/exec/execbuilder/testdata/explain_redact b/pkg/sql/opt/exec/execbuilder/testdata/explain_redact index 9297ab1c7f9c..8bf3bdfd7288 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/explain_redact +++ b/pkg/sql/opt/exec/execbuilder/testdata/explain_redact @@ -700,7 +700,7 @@ upsert bc query T EXPLAIN (OPT, MEMO, REDACT) INSERT INTO bc SELECT a::float + 1 FROM a ON CONFLICT (b) DO UPDATE SET b = bc.b + 100 ---- -memo (optimized, ~35KB, required=[presentation: info:25] [distribution: test]) +memo (optimized, ~37KB, required=[presentation: info:25] [distribution: test]) ├── G1: (explain G2 [distribution: test]) │ └── [presentation: info:19] [distribution: test] │ ├── best: (explain G2="[distribution: test]" [distribution: test]) @@ -1140,7 +1140,7 @@ update ab query T EXPLAIN (OPT, MEMO, REDACT) UPDATE ab SET a = a || 'ab' WHERE a > 'a' ---- -memo (optimized, ~14KB, required=[presentation: info:15] [distribution: test]) +memo (optimized, ~15KB, required=[presentation: info:15] [distribution: test]) ├── G1: (explain G2 [distribution: test]) │ └── [presentation: info:11] [distribution: test] │ ├── best: (explain G2="[distribution: test]" [distribution: test]) @@ -1366,7 +1366,7 @@ update e query T EXPLAIN (OPT, MEMO, REDACT) UPDATE e SET e = 'eee' WHERE e > 'a' ---- -memo (optimized, ~17KB, required=[presentation: info:17] [distribution: test]) +memo (optimized, ~18KB, required=[presentation: info:17] [distribution: test]) ├── G1: (explain G2 [distribution: test]) │ └── [presentation: info:13] [distribution: test] │ ├── best: (explain G2="[distribution: test]" [distribution: test]) @@ -1690,7 +1690,7 @@ project query T EXPLAIN (OPT, MEMO, REDACT) SELECT * FROM bc WHERE b >= 1.0 AND b < 2.0 ---- -memo (optimized, ~12KB, required=[presentation: info:5] [distribution: test]) +memo (optimized, ~13KB, required=[presentation: info:7] [distribution: test]) ├── G1: (explain G2 [presentation: b:1,c:2] [distribution: test]) │ └── [presentation: info:5] [distribution: test] │ ├── best: (explain G2="[presentation: b:1,c:2] [distribution: test]" [presentation: b:1,c:2] [distribution: test]) @@ -2439,10 +2439,10 @@ project query T EXPLAIN (OPT, MEMO, REDACT) SELECT * FROM bc JOIN f ON b = f + 1 ---- -memo (optimized, ~27KB, required=[presentation: info:10] [distribution: test]) - ├── G1: (explain G2 [presentation: b:1,c:2,f:5] [distribution: test]) - │ └── [presentation: info:10] [distribution: test] - │ ├── best: (explain G2="[presentation: b:1,c:2,f:5] [distribution: test]" [presentation: b:1,c:2,f:5] [distribution: test]) +memo (optimized, ~29KB, required=[presentation: info:14] [distribution: test]) + ├── G1: (explain G2 [presentation: b:1,c:2,f:7] [distribution: test]) + │ └── [presentation: info:14] [distribution: test] + │ ├── best: (explain G2="[presentation: b:1,c:2,f:7] [distribution: test]" [presentation: b:1,c:2,f:7] [distribution: test]) │ └── cost: 2247.29 ├── G2: (project G3 G4 b c f) │ ├── [presentation: b:1,c:2,f:5] [distribution: test] diff --git a/pkg/sql/opt/memo/memo.go b/pkg/sql/opt/memo/memo.go index accfcde8e66d..29ebf3faeeef 100644 --- a/pkg/sql/opt/memo/memo.go +++ b/pkg/sql/opt/memo/memo.go @@ -197,6 +197,7 @@ type Memo struct { pushLimitIntoProjectFilteredScan bool legacyVarcharTyping bool preferBoundedCardinality bool + minRowCount float64 // txnIsoLevel is the isolation level under which the plan was created. This // affects the planning of some locking operations, so it must be included in @@ -285,6 +286,7 @@ func (m *Memo) Init(ctx context.Context, evalCtx *eval.Context) { pushLimitIntoProjectFilteredScan: evalCtx.SessionData().OptimizerPushLimitIntoProjectFilteredScan, legacyVarcharTyping: evalCtx.SessionData().LegacyVarcharTyping, preferBoundedCardinality: evalCtx.SessionData().OptimizerPreferBoundedCardinality, + minRowCount: evalCtx.SessionData().OptimizerMinRowCount, txnIsoLevel: evalCtx.TxnIsoLevel, } m.metadata.Init() @@ -451,6 +453,7 @@ func (m *Memo) IsStale( m.pushLimitIntoProjectFilteredScan != evalCtx.SessionData().OptimizerPushLimitIntoProjectFilteredScan || m.legacyVarcharTyping != evalCtx.SessionData().LegacyVarcharTyping || m.preferBoundedCardinality != evalCtx.SessionData().OptimizerPreferBoundedCardinality || + m.minRowCount != evalCtx.SessionData().OptimizerMinRowCount || m.txnIsoLevel != evalCtx.TxnIsoLevel { return true, nil } diff --git a/pkg/sql/opt/memo/memo_test.go b/pkg/sql/opt/memo/memo_test.go index ed3c6a1c913d..9e3cb4c320af 100644 --- a/pkg/sql/opt/memo/memo_test.go +++ b/pkg/sql/opt/memo/memo_test.go @@ -512,6 +512,11 @@ func TestMemoIsStale(t *testing.T) { evalCtx.SessionData().OptimizerPreferBoundedCardinality = false notStale() + evalCtx.SessionData().OptimizerMinRowCount = 1.0 + stale() + evalCtx.SessionData().OptimizerMinRowCount = 0 + notStale() + // User no longer has access to view. catalog.View(tree.NewTableNameWithSchema("t", catconstants.PublicSchemaName, "abcview")).Revoked = true _, err = o.Memo().IsStale(ctx, &evalCtx, catalog) diff --git a/pkg/sql/opt/memo/statistics_builder.go b/pkg/sql/opt/memo/statistics_builder.go index fd2d02080da9..7acb6f6cfc54 100644 --- a/pkg/sql/opt/memo/statistics_builder.go +++ b/pkg/sql/opt/memo/statistics_builder.go @@ -228,18 +228,20 @@ const ( // // See props/statistics.go for more details. type statisticsBuilder struct { - ctx context.Context - evalCtx *eval.Context - md *opt.Metadata + ctx context.Context + evalCtx *eval.Context + md *opt.Metadata + minRowCount float64 } func (sb *statisticsBuilder) init(ctx context.Context, evalCtx *eval.Context, md *opt.Metadata) { // This initialization pattern ensures that fields are not unwittingly // reused. Field reuse must be explicit. *sb = statisticsBuilder{ - ctx: ctx, - evalCtx: evalCtx, - md: md, + ctx: ctx, + evalCtx: evalCtx, + md: md, + minRowCount: evalCtx.SessionData().OptimizerMinRowCount, } } @@ -826,7 +828,7 @@ func (sb *statisticsBuilder) colAvgSize(tabID opt.TableID, col opt.ColumnID) uin func (sb *statisticsBuilder) buildScan(scan *ScanExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -1075,7 +1077,7 @@ func (sb *statisticsBuilder) colStatScan(colSet opt.ColSet, scan *ScanExpr) *pro func (sb *statisticsBuilder) buildSelect(sel *SelectExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -1116,7 +1118,7 @@ func (sb *statisticsBuilder) colStatSelect( func (sb *statisticsBuilder) buildProject(prj *ProjectExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -1218,7 +1220,7 @@ func (sb *statisticsBuilder) buildInvertedFilter( invFilter *InvertedFilterExpr, relProps *props.Relational, ) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -1279,7 +1281,7 @@ func (sb *statisticsBuilder) buildJoin( } s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -1810,7 +1812,7 @@ func (sb *statisticsBuilder) colStatFromJoinRight( func (sb *statisticsBuilder) buildIndexJoin(indexJoin *IndexJoinExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -1891,7 +1893,7 @@ func (sb *statisticsBuilder) buildZigzagJoin( zigzag *ZigzagJoinExpr, relProps *props.Relational, h *joinPropsHelper, ) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -1992,7 +1994,7 @@ func (sb *statisticsBuilder) buildZigzagJoin( func (sb *statisticsBuilder) buildGroupBy(groupNode RelExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2104,7 +2106,7 @@ func (sb *statisticsBuilder) colStatGroupBy( func (sb *statisticsBuilder) buildSetNode(setNode RelExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2209,7 +2211,7 @@ func (sb *statisticsBuilder) colStatSetNodeImpl( // buildValues builds the statistics for a VALUES expression. func (sb *statisticsBuilder) buildValues(values ValuesContainer, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2314,7 +2316,7 @@ func (sb *statisticsBuilder) colStatLiteralValues( func (sb *statisticsBuilder) buildLimit(limit *LimitExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2357,7 +2359,7 @@ func (sb *statisticsBuilder) colStatLimit( func (sb *statisticsBuilder) buildTopK(topK *TopKExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2399,7 +2401,7 @@ func (sb *statisticsBuilder) colStatTopK(colSet opt.ColSet, topK *TopKExpr) *pro func (sb *statisticsBuilder) buildOffset(offset *OffsetExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2452,7 +2454,7 @@ func (sb *statisticsBuilder) colStatOffset( func (sb *statisticsBuilder) buildMax1Row(max1Row *Max1RowExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2482,7 +2484,7 @@ func (sb *statisticsBuilder) colStatMax1Row( func (sb *statisticsBuilder) buildOrdinality(ord *OrdinalityExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2527,7 +2529,7 @@ func (sb *statisticsBuilder) colStatOrdinality( func (sb *statisticsBuilder) buildWindow(window *WindowExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2595,7 +2597,7 @@ func (sb *statisticsBuilder) buildProjectSet( projectSet *ProjectSetExpr, relProps *props.Relational, ) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2728,7 +2730,7 @@ func (sb *statisticsBuilder) buildWithScan( withScan *WithScanExpr, relProps, bindingProps *props.Relational, ) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2770,7 +2772,7 @@ func (sb *statisticsBuilder) colStatWithScan( func (sb *statisticsBuilder) buildMutation(mutation RelExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2808,7 +2810,7 @@ func (sb *statisticsBuilder) colStatMutation( func (sb *statisticsBuilder) buildLock(lock *LockExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2840,7 +2842,7 @@ func (sb *statisticsBuilder) colStatLock(colSet opt.ColSet, lock *LockExpr) *pro func (sb *statisticsBuilder) buildBarrier(barrier *BarrierExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short cut if cardinality is 0. return } @@ -2874,7 +2876,7 @@ func (sb *statisticsBuilder) colStatBarrier( func (sb *statisticsBuilder) buildCall(call *CallExpr, relProps *props.Relational) { s := relProps.Statistics() - if zeroCardinality := s.Init(relProps); zeroCardinality { + if zeroCardinality := s.Init(relProps, sb.minRowCount); zeroCardinality { // Short-cut if cardinality is 0. return } diff --git a/pkg/sql/opt/memo/statistics_builder_test.go b/pkg/sql/opt/memo/statistics_builder_test.go index 9803a3fc1c0d..e439edbd14cd 100644 --- a/pkg/sql/opt/memo/statistics_builder_test.go +++ b/pkg/sql/opt/memo/statistics_builder_test.go @@ -112,7 +112,8 @@ func TestGetStatsFromConstraint(t *testing.T) { relProps := &props.Relational{Cardinality: props.AnyCardinality} relProps.NotNullCols = cs.ExtractNotNullCols(ctx, &evalCtx) s := relProps.Statistics() - s.Init(relProps) + const minRowCount = 0 + s.Init(relProps, minRowCount) // Calculate distinct counts. sb.applyConstraintSet(cs, true /* tight */, sel, relProps, relProps.Statistics()) diff --git a/pkg/sql/opt/memo/testdata/memo b/pkg/sql/opt/memo/testdata/memo index a27ba6177385..87c326726658 100644 --- a/pkg/sql/opt/memo/testdata/memo +++ b/pkg/sql/opt/memo/testdata/memo @@ -317,7 +317,7 @@ memo (optimized, ~6KB, required=[presentation: array_agg:6]) memo SELECT array_agg(x) FROM (SELECT * FROM a) GROUP BY y ---- -memo (optimized, ~7KB, required=[presentation: array_agg:6]) +memo (optimized, ~8KB, required=[presentation: array_agg:6]) ├── G1: (project G2 G3 array_agg) │ └── [presentation: array_agg:6] │ ├── best: (project G2 G3 array_agg) @@ -373,7 +373,7 @@ memo (optimized, ~6KB, required=[presentation: array_cat_agg:6]) memo SELECT array_cat_agg(arr) FROM (SELECT * FROM a) GROUP BY y ---- -memo (optimized, ~7KB, required=[presentation: array_cat_agg:6]) +memo (optimized, ~8KB, required=[presentation: array_cat_agg:6]) ├── G1: (project G2 G3 array_cat_agg) │ └── [presentation: array_cat_agg:6] │ ├── best: (project G2 G3 array_cat_agg) diff --git a/pkg/sql/opt/props/statistics.go b/pkg/sql/opt/props/statistics.go index a8628e279ca8..85abc2c7678e 100644 --- a/pkg/sql/opt/props/statistics.go +++ b/pkg/sql/opt/props/statistics.go @@ -54,6 +54,10 @@ type Statistics struct { // expressions with Cardinality.Max > 0, RowCount will be >= epsilon. RowCount float64 + // minRowCount, if greater than zero, limits the lower bound of RowCount + // when it is updated by ApplySelectivity. See Init for more details. + minRowCount float64 + // VirtualCols is the set of virtual computed columns produced by our input // that we have statistics on. Any of these could appear in ColStats. This set // is maintained separately from OutputCols to allow lookup of statistics on @@ -87,10 +91,19 @@ type Statistics struct { } // Init initializes the data members of Statistics. -func (s *Statistics) Init(relProps *Relational) (zeroCardinality bool) { +// +// minRowCount, if greater than zero, limits the lower bound of RowCount when it +// is updated by ApplySelectivity. If minRowCount is zero, then there is no +// lower bound (however, in practice there is some lower bound due to +// Selectivity being at least epsilon). Note that if minRowCount is non-zero, +// RowCount can still be zero if the cardinality of the expression is zero, +// e.g., for a contradictory filter. +func (s *Statistics) Init(relProps *Relational, minRowCount float64) (zeroCardinality bool) { // This initialization pattern ensures that fields are not unwittingly // reused. Reusing fields must be done explicitly. - *s = Statistics{} + *s = Statistics{ + minRowCount: minRowCount, + } if relProps.Cardinality.IsZero() { s.RowCount = 0 s.Selectivity = ZeroSelectivity @@ -126,12 +139,15 @@ func (s *Statistics) CopyFrom(other *Statistics) { // counts, and histograms. func (s *Statistics) ApplySelectivity(selectivity Selectivity) { if selectivity == ZeroSelectivity { - s.RowCount = 0 s.Selectivity = ZeroSelectivity - return + s.RowCount = 0 + } else if r := s.RowCount * selectivity.AsFloat(); r < s.minRowCount { + s.Selectivity.Multiply(MakeSelectivityFromFraction(s.minRowCount, s.RowCount)) + s.RowCount = s.minRowCount + } else { + s.Selectivity.Multiply(selectivity) + s.RowCount = r } - s.RowCount *= selectivity.AsFloat() - s.Selectivity.Multiply(selectivity) } // UnionWith unions this Statistics object with another Statistics object. It diff --git a/pkg/sql/opt/xform/testdata/coster/outside-histogram b/pkg/sql/opt/xform/testdata/coster/outside-histogram index 0acd1fe544f4..698d474b3bb0 100644 --- a/pkg/sql/opt/xform/testdata/coster/outside-histogram +++ b/pkg/sql/opt/xform/testdata/coster/outside-histogram @@ -70,7 +70,7 @@ ALTER TABLE t INJECT STATISTICS '[ # Q1 # -------------------------------------------------- -opt set=(optimizer_prefer_bounded_cardinality=false) +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=0) SELECT * FROM t WHERE k IN (110, 120, 130, 140) AND i = 500 ---- index-join t @@ -99,7 +99,7 @@ index-join t ├── key: (1) └── fd: ()-->(2) -opt set=(optimizer_prefer_bounded_cardinality=true) +opt set=(optimizer_prefer_bounded_cardinality=true,optimizer_min_row_count=0) SELECT * FROM t WHERE k IN (110, 120, 130, 140) AND i = 500 ---- index-join t @@ -128,11 +128,41 @@ index-join t ├── key: (1) └── fd: ()-->(2) +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=1) +SELECT * FROM t WHERE k IN (110, 120, 130, 140) AND i = 500 +---- +select + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 4] + ├── stats: [rows=1, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0, distinct(1,2)=1, null(1,2)=0] + │ histogram(1)= 0 0.25 0 0.25 0 0.25 0 0.25 + │ <--- 110 --- 120 --- 130 --- 140 + │ histogram(2)= + ├── cost: 24.21 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── scan t + │ ├── columns: k:1!null i:2 s:3 + │ ├── constraint: /1 + │ │ ├── [/110 - /110] + │ │ ├── [/120 - /120] + │ │ ├── [/130 - /130] + │ │ └── [/140 - /140] + │ ├── cardinality: [0 - 4] + │ ├── stats: [rows=4, distinct(1)=4, null(1)=0] + │ │ histogram(1)= 0 1 0 1 0 1 0 1 + │ │ <--- 110 --- 120 --- 130 --- 140 + │ ├── cost: 24.15 + │ ├── key: (1) + │ └── fd: (1)-->(2,3) + └── filters + └── i:2 = 500 [outer=(2), constraints=(/2: [/500 - /500]; tight), fd=()-->(2)] + # -------------------------------------------------- # Q2 # -------------------------------------------------- -opt set=(optimizer_prefer_bounded_cardinality=false) +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=0) SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i > 500 ---- index-join t @@ -167,7 +197,7 @@ index-join t └── filters └── k:1 IN (100, 110, 120, 130) [outer=(1), constraints=(/1: [/100 - /100] [/110 - /110] [/120 - /120] [/130 - /130]; tight)] -opt set=(optimizer_prefer_bounded_cardinality=true) +opt set=(optimizer_prefer_bounded_cardinality=true,optimizer_min_row_count=0) SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i > 500 ---- select @@ -197,11 +227,41 @@ select └── filters └── i:2 > 500 [outer=(2), constraints=(/2: [/501 - ]; tight)] +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=1) +SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i > 500 +---- +select + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 4] + ├── stats: [rows=1, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0, distinct(1,2)=1, null(1,2)=0] + │ histogram(1)= 0 0.25 0 0.25 0 0.25 0 0.25 + │ <--- 100 --- 110 --- 120 --- 130 + │ histogram(2)= + ├── cost: 24.21 + ├── key: (1) + ├── fd: (1)-->(2,3) + ├── scan t + │ ├── columns: k:1!null i:2 s:3 + │ ├── constraint: /1 + │ │ ├── [/100 - /100] + │ │ ├── [/110 - /110] + │ │ ├── [/120 - /120] + │ │ └── [/130 - /130] + │ ├── cardinality: [0 - 4] + │ ├── stats: [rows=4, distinct(1)=4, null(1)=0] + │ │ histogram(1)= 0 1 0 1 0 1 0 1 + │ │ <--- 100 --- 110 --- 120 --- 130 + │ ├── cost: 24.15 + │ ├── key: (1) + │ └── fd: (1)-->(2,3) + └── filters + └── i:2 > 500 [outer=(2), constraints=(/2: [/501 - ]; tight)] + # -------------------------------------------------- # Q3 # -------------------------------------------------- -opt set=(optimizer_prefer_bounded_cardinality=false) +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=0) SELECT * FROM t WHERE k IN (410, 420, 430) AND i > 500 ---- index-join t @@ -234,7 +294,7 @@ index-join t └── filters └── k:1 IN (410, 420, 430) [outer=(1), constraints=(/1: [/410 - /410] [/420 - /420] [/430 - /430]; tight)] -opt set=(optimizer_prefer_bounded_cardinality=true) +opt set=(optimizer_prefer_bounded_cardinality=true,optimizer_min_row_count=0) SELECT * FROM t WHERE k IN (410, 420, 430) AND i > 500 ---- select @@ -261,11 +321,38 @@ select └── filters └── i:2 > 500 [outer=(2), constraints=(/2: [/501 - ]; tight)] +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=1) +SELECT * FROM t WHERE k IN (410, 420, 430) AND i > 500 +---- +select + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 3] + ├── stats: [rows=1, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0] + │ histogram(1)= + │ histogram(2)= + ├── cost: 19.03 + ├── key: (1) + ├── fd: (1)-->(2,3) + ├── scan t + │ ├── columns: k:1!null i:2 s:3 + │ ├── constraint: /1 + │ │ ├── [/410 - /410] + │ │ ├── [/420 - /420] + │ │ └── [/430 - /430] + │ ├── cardinality: [0 - 3] + │ ├── stats: [rows=6e-07, distinct(1)=6e-07, null(1)=0] + │ │ histogram(1)= + │ ├── cost: 19.01 + │ ├── key: (1) + │ └── fd: (1)-->(2,3) + └── filters + └── i:2 > 500 [outer=(2), constraints=(/2: [/501 - ]; tight)] + # -------------------------------------------------- # Q4 # -------------------------------------------------- -opt set=(optimizer_prefer_bounded_cardinality=false) +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=0) SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i = 400 AND s < 'apple' ---- select @@ -311,7 +398,7 @@ select └── filters └── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] -opt set=(optimizer_prefer_bounded_cardinality=true) +opt set=(optimizer_prefer_bounded_cardinality=true,optimizer_min_row_count=0) SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i = 400 AND s < 'apple' ---- select @@ -344,11 +431,44 @@ select ├── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] └── s:3 < 'apple' [outer=(3), constraints=(/3: (/NULL - /'apple'); tight)] +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=1) +SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i = 400 AND s < 'apple' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── cardinality: [0 - 4] + ├── stats: [rows=1, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=1, null(2,3)=0, distinct(1-3)=1, null(1-3)=0] + │ histogram(1)= 0 0.25 0 0.25 0 0.25 0 0.25 + │ <--- 100 --- 110 --- 120 --- 130 + │ histogram(2)= 0 1 + │ <--- 400 + │ histogram(3)= + ├── cost: 24.22 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── scan t + │ ├── columns: k:1!null i:2 s:3 + │ ├── constraint: /1 + │ │ ├── [/100 - /100] + │ │ ├── [/110 - /110] + │ │ ├── [/120 - /120] + │ │ └── [/130 - /130] + │ ├── cardinality: [0 - 4] + │ ├── stats: [rows=4, distinct(1)=4, null(1)=0] + │ │ histogram(1)= 0 1 0 1 0 1 0 1 + │ │ <--- 100 --- 110 --- 120 --- 130 + │ ├── cost: 24.15 + │ ├── key: (1) + │ └── fd: (1)-->(2,3) + └── filters + ├── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] + └── s:3 < 'apple' [outer=(3), constraints=(/3: (/NULL - /'apple'); tight)] + # -------------------------------------------------- # Q5 # -------------------------------------------------- -opt set=(enable_zigzag_join=false,optimizer_prefer_bounded_cardinality=false) +opt set=(enable_zigzag_join=false,optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=0) SELECT * FROM t WHERE i = 400 AND s > 'z' ---- select @@ -377,7 +497,7 @@ select └── filters └── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] -opt set=(enable_zigzag_join=false,optimizer_prefer_bounded_cardinality=true) +opt set=(enable_zigzag_join=false,optimizer_prefer_bounded_cardinality=true,optimizer_min_row_count=0) SELECT * FROM t WHERE i = 400 AND s > 'z' ---- select @@ -409,11 +529,40 @@ select └── filters └── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] +opt set=(enable_zigzag_join=false,optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=1) +SELECT * FROM t WHERE i = 400 AND s > 'z' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── stats: [rows=1, distinct(2)=1, null(2)=0, distinct(3)=1, null(3)=0, distinct(2,3)=1, null(2,3)=0] + │ histogram(2)= 0 1 + │ <--- 400 + │ histogram(3)= + ├── cost: 25.1375 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── stats: [rows=1] + │ ├── cost: 25.0975 + │ ├── key: (1) + │ ├── fd: (1)-->(2,3) + │ └── scan t@t_s_idx + │ ├── columns: k:1!null s:3!null + │ ├── constraint: /3/1: [/e'z\x00' - ] + │ ├── stats: [rows=1, distinct(3)=1, null(3)=0] + │ │ histogram(3)= + │ ├── cost: 19.045 + │ ├── key: (1) + │ └── fd: (1)-->(3) + └── filters + └── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] + # -------------------------------------------------- # Q6 # -------------------------------------------------- -opt set=(optimizer_prefer_bounded_cardinality=false) +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=0) SELECT * FROM t WHERE k = 100 AND i = 500 AND s = 'zzz' ---- select @@ -448,7 +597,7 @@ select └── filters └── s:3 = 'zzz' [outer=(3), constraints=(/3: [/'zzz' - /'zzz']; tight), fd=()-->(3)] -opt set=(optimizer_prefer_bounded_cardinality=true) +opt set=(optimizer_prefer_bounded_cardinality=true,optimizer_min_row_count=0) SELECT * FROM t WHERE k = 100 AND i = 500 AND s = 'zzz' ---- select @@ -482,3 +631,31 @@ select │ └── fd: ()-->(1,2) └── filters └── s:3 = 'zzz' [outer=(3), constraints=(/3: [/'zzz' - /'zzz']; tight), fd=()-->(3)] + +opt set=(optimizer_prefer_bounded_cardinality=false,optimizer_min_row_count=1) +SELECT * FROM t WHERE k = 100 AND i = 500 AND s = 'zzz' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── cardinality: [0 - 1] + ├── stats: [rows=1, distinct(1)=1, null(1)=0, distinct(2)=1, null(2)=0, distinct(3)=1, null(3)=0] + │ histogram(1)= 0 1 + │ <--- 100 + │ histogram(2)= + │ histogram(3)= + ├── cost: 9.085 + ├── key: () + ├── fd: ()-->(1-3) + ├── scan t + │ ├── columns: k:1!null i:2 s:3 + │ ├── constraint: /1: [/100 - /100] + │ ├── cardinality: [0 - 1] + │ ├── stats: [rows=1, distinct(1)=1, null(1)=0] + │ │ histogram(1)= 0 1 + │ │ <--- 100 + │ ├── cost: 9.045 + │ ├── key: () + │ └── fd: ()-->(1-3) + └── filters + ├── i:2 = 500 [outer=(2), constraints=(/2: [/500 - /500]; tight), fd=()-->(2)] + └── s:3 = 'zzz' [outer=(3), constraints=(/3: [/'zzz' - /'zzz']; tight), fd=()-->(3)] diff --git a/pkg/sql/opt/xform/testdata/physprops/ordering b/pkg/sql/opt/xform/testdata/physprops/ordering index 6bfe3c1a1f4d..e54398ec958e 100644 --- a/pkg/sql/opt/xform/testdata/physprops/ordering +++ b/pkg/sql/opt/xform/testdata/physprops/ordering @@ -686,7 +686,7 @@ memo (optimized, ~4KB, required=[presentation: info:7]) memo SELECT y FROM a WITH ORDINALITY ORDER BY ordinality ---- -memo (optimized, ~5KB, required=[presentation: y:2] [ordering: +7]) +memo (optimized, ~6KB, required=[presentation: y:2] [ordering: +7]) ├── G1: (ordinality G2) │ ├── [presentation: y:2] [ordering: +7] │ │ ├── best: (ordinality G2) @@ -725,7 +725,7 @@ memo (optimized, ~7KB, required=[presentation: y:2] [ordering: +8]) memo SELECT y FROM a WITH ORDINALITY ORDER BY ordinality, x ---- -memo (optimized, ~8KB, required=[presentation: y:2] [ordering: +7]) +memo (optimized, ~9KB, required=[presentation: y:2] [ordering: +7]) ├── G1: (ordinality G2) │ ├── [presentation: y:2] [ordering: +7] │ │ ├── best: (ordinality G2) @@ -779,7 +779,7 @@ memo (optimized, ~7KB, required=[presentation: y:2] [ordering: +7]) memo SELECT y FROM a WITH ORDINALITY ORDER BY ordinality DESC ---- -memo (optimized, ~5KB, required=[presentation: y:2] [ordering: -7]) +memo (optimized, ~6KB, required=[presentation: y:2] [ordering: -7]) ├── G1: (ordinality G2) │ ├── [presentation: y:2] [ordering: -7] │ │ ├── best: (sort G1) diff --git a/pkg/sql/opt/xform/testdata/rules/groupby b/pkg/sql/opt/xform/testdata/rules/groupby index e3ee50826d72..e7ebf694f013 100644 --- a/pkg/sql/opt/xform/testdata/rules/groupby +++ b/pkg/sql/opt/xform/testdata/rules/groupby @@ -2298,7 +2298,7 @@ memo (optimized, ~6KB, required=[presentation: u:2,v:3,w:4] [ordering: +4]) memo SELECT (SELECT w FROM kuvw WHERE v=1 AND x=u) FROM xyz ORDER BY x+1, x ---- -memo (optimized, ~26KB, required=[presentation: w:12] [ordering: +13,+1]) +memo (optimized, ~28KB, required=[presentation: w:12] [ordering: +13,+1]) ├── G1: (project G2 G3 x) │ ├── [presentation: w:12] [ordering: +13,+1] │ │ ├── best: (sort G1) diff --git a/pkg/sql/opt/xform/testdata/rules/join b/pkg/sql/opt/xform/testdata/rules/join index 0ba7b06f0074..092933f96b96 100644 --- a/pkg/sql/opt/xform/testdata/rules/join +++ b/pkg/sql/opt/xform/testdata/rules/join @@ -235,7 +235,7 @@ inner-join (merge) memo expect=ReorderJoins SELECT * FROM abc, stu, xyz WHERE abc.a=stu.s AND stu.s=xyz.x ---- -memo (optimized, ~46KB, required=[presentation: a:1,b:2,c:3,s:7,t:8,u:9,x:12,y:13,z:14]) +memo (optimized, ~48KB, required=[presentation: a:1,b:2,c:3,s:7,t:8,u:9,x:12,y:13,z:14]) ├── G1: (inner-join G2 G3 G4) (inner-join G3 G2 G4) (inner-join G5 G6 G7) (inner-join G6 G5 G7) (inner-join G8 G9 G7) (inner-join G9 G8 G7) (merge-join G2 G3 G10 inner-join,+1,+7) (merge-join G3 G2 G10 inner-join,+7,+1) (lookup-join G3 G10 abc@ab,keyCols=[7],outCols=(1-3,7-9,12-14)) (merge-join G5 G6 G10 inner-join,+7,+12) (merge-join G6 G5 G10 inner-join,+12,+7) (lookup-join G6 G10 stu,keyCols=[12],outCols=(1-3,7-9,12-14)) (merge-join G8 G9 G10 inner-join,+7,+12) (lookup-join G8 G10 xyz@xy,keyCols=[7],outCols=(1-3,7-9,12-14)) (merge-join G9 G8 G10 inner-join,+12,+7) │ └── [presentation: a:1,b:2,c:3,s:7,t:8,u:9,x:12,y:13,z:14] │ ├── best: (merge-join G5="[ordering: +7]" G6="[ordering: +(1|12)]" G10 inner-join,+7,+12) @@ -343,7 +343,7 @@ SELECT * FROM stu, abc, xyz, pqr WHERE u = a AND a = x AND x = p ---- -memo (optimized, ~40KB, required=[presentation: s:1,t:2,u:3,a:6,b:7,c:8,x:12,y:13,z:14,p:18,q:19,r:20,s:21,t:22]) +memo (optimized, ~42KB, required=[presentation: s:1,t:2,u:3,a:6,b:7,c:8,x:12,y:13,z:14,p:18,q:19,r:20,s:21,t:22]) ├── G1: (inner-join G2 G3 G4) (inner-join G3 G2 G4) (merge-join G2 G3 G5 inner-join,+3,+6) (merge-join G3 G2 G5 inner-join,+6,+3) (lookup-join G3 G5 stu@uts,keyCols=[6],outCols=(1-3,6-8,12-14,18-22)) │ └── [presentation: s:1,t:2,u:3,a:6,b:7,c:8,x:12,y:13,z:14,p:18,q:19,r:20,s:21,t:22] │ ├── best: (merge-join G2="[ordering: +3]" G3="[ordering: +(6|12|18)]" G5 inner-join,+3,+6) diff --git a/pkg/sql/opt/xform/testdata/rules/join_order b/pkg/sql/opt/xform/testdata/rules/join_order index 7c36dafebc42..75b2eb8a4bdc 100644 --- a/pkg/sql/opt/xform/testdata/rules/join_order +++ b/pkg/sql/opt/xform/testdata/rules/join_order @@ -360,7 +360,7 @@ memo (optimized, ~24KB, required=[presentation: b:1,x:2,c:5,y:6,a:9,b:10,c:11,d: memo set=reorder_joins_limit=2 SELECT * FROM bx, cy, abc WHERE a = 1 AND abc.b = bx.b AND abc.c = cy.c ---- -memo (optimized, ~35KB, required=[presentation: b:1,x:2,c:5,y:6,a:9,b:10,c:11,d:12]) +memo (optimized, ~37KB, required=[presentation: b:1,x:2,c:5,y:6,a:9,b:10,c:11,d:12]) ├── G1: (inner-join G2 G3 G4) (inner-join G3 G2 G4) (inner-join G5 G6 G7) (inner-join G6 G5 G7) (merge-join G2 G3 G8 inner-join,+1,+10) (merge-join G3 G2 G8 inner-join,+10,+1) (lookup-join G3 G8 bx,keyCols=[10],outCols=(1,2,5,6,9-12)) (merge-join G5 G6 G8 inner-join,+5,+11) (merge-join G6 G5 G8 inner-join,+11,+5) (lookup-join G6 G8 cy,keyCols=[11],outCols=(1,2,5,6,9-12)) │ └── [presentation: b:1,x:2,c:5,y:6,a:9,b:10,c:11,d:12] │ ├── best: (lookup-join G3 G8 bx,keyCols=[10],outCols=(1,2,5,6,9-12)) @@ -521,7 +521,7 @@ inner-join (cross) memo set=reorder_joins_limit=0 SELECT * FROM bx, cy, dz, abc WHERE x = y AND y = z AND z = a ---- -memo (optimized, ~31KB, required=[presentation: b:1,x:2,c:5,y:6,d:9,z:10,a:13,b:14,c:15,d:16]) +memo (optimized, ~33KB, required=[presentation: b:1,x:2,c:5,y:6,d:9,z:10,a:13,b:14,c:15,d:16]) ├── G1: (inner-join G2 G3 G4) (merge-join G2 G3 G5 inner-join,+2,+6) │ └── [presentation: b:1,x:2,c:5,y:6,d:9,z:10,a:13,b:14,c:15,d:16] │ ├── best: (inner-join G2 G3 G4) @@ -587,7 +587,7 @@ memo (optimized, ~31KB, required=[presentation: b:1,x:2,c:5,y:6,d:9,z:10,a:13,b: memo set=reorder_joins_limit=3 SELECT * FROM bx, cy, dz, abc WHERE x = y AND y = z AND z = a ---- -memo (optimized, ~67KB, required=[presentation: b:1,x:2,c:5,y:6,d:9,z:10,a:13,b:14,c:15,d:16]) +memo (optimized, ~69KB, required=[presentation: b:1,x:2,c:5,y:6,d:9,z:10,a:13,b:14,c:15,d:16]) ├── G1: (inner-join G2 G3 G4) (inner-join G3 G2 G4) (inner-join G5 G6 G7) (inner-join G6 G5 G7) (inner-join G8 G9 G7) (inner-join G9 G8 G7) (inner-join G10 G11 G12) (inner-join G11 G10 G12) (inner-join G13 G14 G12) (inner-join G14 G13 G12) (inner-join G15 G16 G12) (inner-join G16 G15 G12) (inner-join G17 G18 G12) (inner-join G18 G17 G12) (merge-join G3 G2 G19 inner-join,+6,+2) (merge-join G6 G5 G19 inner-join,+10,+6) (merge-join G9 G8 G19 inner-join,+10,+6) (merge-join G11 G10 G19 inner-join,+13,+10) (merge-join G14 G13 G19 inner-join,+13,+10) (merge-join G16 G15 G19 inner-join,+13,+10) (lookup-join G17 G19 abc,keyCols=[10],outCols=(1,2,5,6,9,10,13-16)) (merge-join G18 G17 G19 inner-join,+13,+10) │ └── [presentation: b:1,x:2,c:5,y:6,d:9,z:10,a:13,b:14,c:15,d:16] │ ├── best: (inner-join G3 G2 G4) @@ -2774,7 +2774,7 @@ SELECT ( ) FROM table80901_1 AS tab_42921; ---- -memo (optimized, ~71KB, required=[presentation: ?column?:50]) +memo (optimized, ~74KB, required=[presentation: ?column?:50]) ├── G1: (project G2 G3) │ └── [presentation: ?column?:50] │ ├── best: (project G2 G3) @@ -3547,7 +3547,7 @@ right-join (hash) # Only 2 joins are considered (instead of 8) when the STRAIGHT hint is present in one join. reorderjoins format=hide-all -SELECT * +SELECT * FROM straight_join1 INNER STRAIGHT JOIN straight_join2 ON straight_join1.x = straight_join2.y INNER JOIN straight_join3 ON straight_join1.x = straight_join3.z @@ -3597,7 +3597,7 @@ inner-join (hash) # No joins are considered when the STRAIGHT hint is present in both joins. reorderjoins format=hide-all -SELECT * +SELECT * FROM straight_join1 INNER STRAIGHT JOIN straight_join2 ON straight_join1.x = straight_join2.y INNER STRAIGHT JOIN straight_join3 ON straight_join1.x = straight_join3.z diff --git a/pkg/sql/opt/xform/testdata/rules/select b/pkg/sql/opt/xform/testdata/rules/select index c7a5a06d4e4a..736c6ff1acfd 100644 --- a/pkg/sql/opt/xform/testdata/rules/select +++ b/pkg/sql/opt/xform/testdata/rules/select @@ -281,7 +281,7 @@ CREATE INDEX idx2 ON p (s) WHERE i > 0 memo expect=GeneratePartialIndexScans SELECT * FROM p WHERE i > 0 AND s = 'foo' ---- -memo (optimized, ~19KB, required=[presentation: k:1,i:2,f:3,s:4,b:5]) +memo (optimized, ~20KB, required=[presentation: k:1,i:2,f:3,s:4,b:5]) ├── G1: (select G2 G3) (index-join G4 p,cols=(1-5)) (index-join G5 p,cols=(1-5)) (index-join G6 p,cols=(1-5)) (index-join G7 p,cols=(1-5)) │ └── [presentation: k:1,i:2,f:3,s:4,b:5] │ ├── best: (index-join G4 p,cols=(1-5)) @@ -1054,7 +1054,7 @@ select memo SELECT * FROM b WHERE v >= 1 AND v <= 10 AND k+u = 1 AND k > 5 ---- -memo (optimized, ~12KB, required=[presentation: k:1,u:2,v:3,j:4]) +memo (optimized, ~13KB, required=[presentation: k:1,u:2,v:3,j:4]) ├── G1: (select G2 G3) (select G4 G5) (select G6 G7) │ └── [presentation: k:1,u:2,v:3,j:4] │ ├── best: (select G6 G7) @@ -11687,7 +11687,7 @@ JOIN t61795 AS t2 ON t1.c = t1.b AND t1.b = t2.b WHERE t1.a = 10 OR t2.b != abs(t2.b) ORDER BY t1.b ASC ---- -memo (optimized, ~36KB, required=[presentation: a:1] [ordering: +2]) +memo (optimized, ~38KB, required=[presentation: a:1] [ordering: +2]) ├── G1: (project G2 G3 a b) │ ├── [presentation: a:1] [ordering: +2] │ │ ├── best: (sort G1) diff --git a/pkg/sql/opt/xform/testdata/rules/set b/pkg/sql/opt/xform/testdata/rules/set index e5e1f3d10206..6a90625be6fa 100644 --- a/pkg/sql/opt/xform/testdata/rules/set +++ b/pkg/sql/opt/xform/testdata/rules/set @@ -263,7 +263,7 @@ memo (optimized, ~12KB, required=[presentation: k:1,u:2,v:3,w:4]) memo expect-not=GenerateStreamingSetOp SELECT * FROM kuvw UNION ALL SELECT * FROM kuvw ---- -memo (optimized, ~10KB, required=[presentation: k:13,u:14,v:15,w:16]) +memo (optimized, ~11KB, required=[presentation: k:13,u:14,v:15,w:16]) ├── G1: (union-all G2 G3) │ └── [presentation: k:13,u:14,v:15,w:16] │ ├── best: (union-all G2 G3) diff --git a/pkg/sql/sessiondatapb/local_only_session_data.proto b/pkg/sql/sessiondatapb/local_only_session_data.proto index 42665438f836..c0ee2e661dcc 100644 --- a/pkg/sql/sessiondatapb/local_only_session_data.proto +++ b/pkg/sql/sessiondatapb/local_only_session_data.proto @@ -547,6 +547,12 @@ message LocalOnlySessionData { // plans in which every expression has a bounded cardinality over plans with // one or more expressions with unbounded cardinality. bool optimizer_prefer_bounded_cardinality = 154; + // OptimizerMinRowCount set a lower bound on row count estimates for + // relational expressions during query planning. A value of zero indicates no + // lower bound. Note that if this is set to a value greater than zero, a row + // count of zero can still be estimated for expressions with a cardinality of + // zero, e.g., for a contradictory filter. + double optimizer_min_row_count = 155; /////////////////////////////////////////////////////////////////////////// // WARNING: consider whether a session parameter you're adding needs to // diff --git a/pkg/sql/vars.go b/pkg/sql/vars.go index e4be8a9559ed..bde9cfbd5266 100644 --- a/pkg/sql/vars.go +++ b/pkg/sql/vars.go @@ -3516,6 +3516,30 @@ var varGen = map[string]sessionVar{ }, GlobalDefault: globalFalse, }, + + `optimizer_min_row_count`: { + GetStringVal: makeFloatGetStringValFn(`optimizer_min_row_count`), + Get: func(evalCtx *extendedEvalContext, _ *kv.Txn) (string, error) { + return formatFloatAsPostgresSetting(evalCtx.SessionData().OptimizerMinRowCount), nil + }, + GlobalDefault: func(sv *settings.Values) string { + return "0" + }, + Set: func(_ context.Context, m sessionDataMutator, s string) error { + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return err + } + // Note that we permit fractions above 1.0 to allow for giving + // head-of-the-line batch more memory that is available - this will + // put the budget in debt. + if f < 0 { + return pgerror.New(pgcode.InvalidParameterValue, "optimizer_min_row_count must be non-negative") + } + m.SetOptimizerMinRowCount(f) + return nil + }, + }, } func ReplicationModeFromString(s string) (sessiondatapb.ReplicationMode, error) {