diff --git a/pkg/sql/exec_util.go b/pkg/sql/exec_util.go index a8028c2282b1..c11ed80bccfe 100644 --- a/pkg/sql/exec_util.go +++ b/pkg/sql/exec_util.go @@ -4007,6 +4007,14 @@ func (m *sessionDataMutator) SetCatalogDigestStalenessCheckEnabled(b bool) { m.data.CatalogDigestStalenessCheckEnabled = b } +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 c6edb6e13b2c..016335de9e1e 100644 --- a/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/pkg/sql/logictest/testdata/logic_test/information_schema @@ -4007,6 +4007,8 @@ 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 on optimizer_push_offset_into_index_join on diff --git a/pkg/sql/logictest/testdata/logic_test/pg_catalog b/pkg/sql/logictest/testdata/logic_test/pg_catalog index 8499f75e0678..30f8a1020b18 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -3007,6 +3007,8 @@ 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 on NULL NULL NULL string optimizer_push_offset_into_index_join on NULL NULL NULL string @@ -3212,6 +3214,8 @@ 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 on NULL user NULL on on optimizer_push_offset_into_index_join on NULL user NULL on on @@ -3416,6 +3420,8 @@ 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 optimizer_push_offset_into_index_join 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 b884b2f2a6f4..be85fcf7d5bc 100644 --- a/pkg/sql/logictest/testdata/logic_test/show_source +++ b/pkg/sql/logictest/testdata/logic_test/show_source @@ -138,6 +138,8 @@ 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 on optimizer_push_offset_into_index_join on diff --git a/pkg/sql/opt/exec/execbuilder/testdata/explain_redact b/pkg/sql/opt/exec/execbuilder/testdata/explain_redact index d2e029e4aace..19f7c50d1f86 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/explain_redact +++ b/pkg/sql/opt/exec/execbuilder/testdata/explain_redact @@ -678,7 +678,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, ~36KB, required=[presentation: info:25] [distribution: test]) +memo (optimized, ~37KB, required=[presentation: info:25] [distribution: test]) ├── G1: (explain G2 [distribution: test]) │ └── [presentation: info:25] [distribution: test] │ ├── best: (explain G2="[distribution: test]" [distribution: test]) @@ -1117,7 +1117,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:15] [distribution: test] │ ├── best: (explain G2="[distribution: test]" [distribution: test]) @@ -1355,7 +1355,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:17] [distribution: test] │ ├── best: (explain G2="[distribution: test]" [distribution: test]) @@ -1686,7 +1686,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:7] [distribution: test]) +memo (optimized, ~13KB, required=[presentation: info:7] [distribution: test]) ├── G1: (explain G2 [presentation: b:1,c:2] [distribution: test]) │ └── [presentation: info:7] [distribution: test] │ ├── best: (explain G2="[presentation: b:1,c:2] [distribution: test]" [presentation: b:1,c:2] [distribution: test]) @@ -2435,7 +2435,7 @@ project query T EXPLAIN (OPT, MEMO, REDACT) SELECT * FROM bc JOIN f ON b = f + 1 ---- -memo (optimized, ~28KB, required=[presentation: info:14] [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]) diff --git a/pkg/sql/opt/memo/cost.go b/pkg/sql/opt/memo/cost.go index 6dfd70265abc..8dbcda97903a 100644 --- a/pkg/sql/opt/memo/cost.go +++ b/pkg/sql/opt/memo/cost.go @@ -19,8 +19,12 @@ type Cost struct { // group members during testing, by setting their cost so high that any other // member will have a lower cost. var MaxCost = Cost{ - C: math.Inf(+1), - Flags: CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, + C: math.Inf(+1), + Flags: CostFlags{ + FullScanPenalty: true, + HugeCostPenalty: true, + UnboundedCardinality: true, + }, } // Less returns true if this cost is lower than the given cost. @@ -57,6 +61,10 @@ type CostFlags struct { // used when the optimizer is forced to use a particular plan, and will error // if it cannot be used. HugeCostPenalty bool + // UnboundedCardinality is true if the operator or any of its descendants + // have no guaranteed upperbound on the number of rows that they can + // produce. See props.AnyCardinality. + UnboundedCardinality bool } // Less returns true if these flags indicate a lower penalty than the other @@ -71,6 +79,9 @@ func (c CostFlags) Less(other CostFlags) bool { if c.FullScanPenalty != other.FullScanPenalty { return !c.FullScanPenalty } + if c.UnboundedCardinality != other.UnboundedCardinality { + return !c.UnboundedCardinality + } return false } @@ -78,9 +89,10 @@ func (c CostFlags) Less(other CostFlags) bool { func (c *CostFlags) Add(other CostFlags) { c.FullScanPenalty = c.FullScanPenalty || other.FullScanPenalty c.HugeCostPenalty = c.HugeCostPenalty || other.HugeCostPenalty + c.UnboundedCardinality = c.UnboundedCardinality || other.UnboundedCardinality } // Empty returns true if these flags are empty. func (c CostFlags) Empty() bool { - return !c.FullScanPenalty && !c.HugeCostPenalty + return !c.FullScanPenalty && !c.HugeCostPenalty && !c.UnboundedCardinality } diff --git a/pkg/sql/opt/memo/cost_test.go b/pkg/sql/opt/memo/cost_test.go index ed8235731ad2..c44e3247c7ad 100644 --- a/pkg/sql/opt/memo/cost_test.go +++ b/pkg/sql/opt/memo/cost_test.go @@ -35,6 +35,8 @@ func TestCostLess(t *testing.T) { {memo.MaxCost, memo.MaxCost, false}, {memo.MaxCost, memo.Cost{C: 1.0, Flags: memo.CostFlags{FullScanPenalty: true}}, false}, {memo.Cost{C: 1.0, Flags: memo.CostFlags{HugeCostPenalty: true}}, memo.MaxCost, true}, + {memo.Cost{C: 2.0, Flags: memo.CostFlags{}}, memo.Cost{C: 1.0, Flags: memo.CostFlags{UnboundedCardinality: true}}, true}, + {memo.Cost{C: 1.0, Flags: memo.CostFlags{UnboundedCardinality: true}}, memo.Cost{C: 2.0, Flags: memo.CostFlags{}}, false}, } for _, tc := range testCases { if tc.left.Less(tc.right) != tc.expected { @@ -72,6 +74,8 @@ func TestCostFlagsLess(t *testing.T) { {memo.CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, memo.CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, false}, {memo.CostFlags{FullScanPenalty: false}, memo.CostFlags{FullScanPenalty: true}, true}, {memo.CostFlags{HugeCostPenalty: false}, memo.CostFlags{HugeCostPenalty: true}, true}, + {memo.CostFlags{UnboundedCardinality: false}, memo.CostFlags{UnboundedCardinality: true}, true}, + {memo.CostFlags{UnboundedCardinality: true}, memo.CostFlags{UnboundedCardinality: false}, false}, } for _, tc := range testCases { if tc.left.Less(tc.right) != tc.expected { diff --git a/pkg/sql/opt/memo/expr_format.go b/pkg/sql/opt/memo/expr_format.go index 2029e911a3a0..adc2af70d31d 100644 --- a/pkg/sql/opt/memo/expr_format.go +++ b/pkg/sql/opt/memo/expr_format.go @@ -913,6 +913,9 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) { if cost.Flags.HugeCostPenalty { b.WriteString(" huge-cost-penalty") } + if cost.Flags.UnboundedCardinality { + b.WriteString(" unbounded-cardinality") + } tp.Child(b.String()) } } diff --git a/pkg/sql/opt/memo/memo.go b/pkg/sql/opt/memo/memo.go index fa24b707c9e1..9dd7b902f0f4 100644 --- a/pkg/sql/opt/memo/memo.go +++ b/pkg/sql/opt/memo/memo.go @@ -200,6 +200,8 @@ type Memo struct { pushLimitIntoProjectFilteredScan bool unsafeAllowTriggersModifyingCascades bool legacyVarcharTyping bool + preferBoundedCardinality bool + minRowCount float64 internal bool // txnIsoLevel is the isolation level under which the plan was created. This @@ -293,6 +295,8 @@ func (m *Memo) Init(ctx context.Context, evalCtx *eval.Context) { pushLimitIntoProjectFilteredScan: evalCtx.SessionData().OptimizerPushLimitIntoProjectFilteredScan, unsafeAllowTriggersModifyingCascades: evalCtx.SessionData().UnsafeAllowTriggersModifyingCascades, legacyVarcharTyping: evalCtx.SessionData().LegacyVarcharTyping, + preferBoundedCardinality: evalCtx.SessionData().OptimizerPreferBoundedCardinality, + minRowCount: evalCtx.SessionData().OptimizerMinRowCount, internal: evalCtx.SessionData().Internal, txnIsoLevel: evalCtx.TxnIsoLevel, } @@ -463,6 +467,8 @@ func (m *Memo) IsStale( m.pushLimitIntoProjectFilteredScan != evalCtx.SessionData().OptimizerPushLimitIntoProjectFilteredScan || m.unsafeAllowTriggersModifyingCascades != evalCtx.SessionData().UnsafeAllowTriggersModifyingCascades || m.legacyVarcharTyping != evalCtx.SessionData().LegacyVarcharTyping || + m.preferBoundedCardinality != evalCtx.SessionData().OptimizerPreferBoundedCardinality || + m.minRowCount != evalCtx.SessionData().OptimizerMinRowCount || m.internal != evalCtx.SessionData().Internal || 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 34f654726a15..f37248939ab7 100644 --- a/pkg/sql/opt/memo/memo_test.go +++ b/pkg/sql/opt/memo/memo_test.go @@ -538,6 +538,16 @@ func TestMemoIsStale(t *testing.T) { evalCtx.SessionData().LegacyVarcharTyping = false notStale() + evalCtx.SessionData().OptimizerPreferBoundedCardinality = true + stale() + evalCtx.SessionData().OptimizerPreferBoundedCardinality = false + notStale() + + evalCtx.SessionData().OptimizerMinRowCount = 1.0 + stale() + evalCtx.SessionData().OptimizerMinRowCount = 0 + notStale() + evalCtx.SessionData().Internal = true stale() evalCtx.SessionData().Internal = false diff --git a/pkg/sql/opt/memo/statistics_builder.go b/pkg/sql/opt/memo/statistics_builder.go index d050cd579bf1..179085a6da3e 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, } } @@ -836,7 +838,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 } @@ -1085,7 +1087,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 } @@ -1126,7 +1128,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 } @@ -1228,7 +1230,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 } @@ -1289,7 +1291,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 } @@ -1820,7 +1822,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 } @@ -1901,7 +1903,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 } @@ -2002,7 +2004,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 } @@ -2114,7 +2116,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 } @@ -2219,7 +2221,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 } @@ -2324,7 +2326,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 } @@ -2367,7 +2369,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 } @@ -2409,7 +2411,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 } @@ -2462,7 +2464,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 } @@ -2492,7 +2494,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 } @@ -2537,7 +2539,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 } @@ -2605,7 +2607,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 } @@ -2738,7 +2740,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 } @@ -2780,7 +2782,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 } @@ -2818,7 +2820,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 } @@ -2852,7 +2854,7 @@ func (sb *statisticsBuilder) buildVectorSearch( search *VectorSearchExpr, 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 } @@ -2909,7 +2911,7 @@ func (sb *statisticsBuilder) buildVectorPartitionSearch( search *VectorPartitionSearchExpr, 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 } @@ -2944,7 +2946,7 @@ func (sb *statisticsBuilder) colStatVectorPartitionSearch( 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 } @@ -2978,7 +2980,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 d903c2cf200a..76f71161c694 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/coster.go b/pkg/sql/opt/xform/coster.go index 07391cf4724e..f5a59789fbf2 100644 --- a/pkg/sql/opt/xform/coster.go +++ b/pkg/sql/opt/xform/coster.go @@ -625,8 +625,13 @@ func (c *coster) ComputeCost(candidate memo.RelExpr, required *physical.Required // Add a one-time cost for any operator with unbounded cardinality. This // ensures we prefer plans that push limits as far down the tree as possible, // all else being equal. + // + // Also add a cost flag for unbounded cardinality. if candidate.Relational().Cardinality.IsUnbounded() { cost.C += cpuCostFactor + if c.evalCtx.SessionData().OptimizerPreferBoundedCardinality { + cost.Flags.UnboundedCardinality = true + } } if !cost.Less(memo.MaxCost) { diff --git a/pkg/sql/opt/xform/testdata/coster/outside-histogram b/pkg/sql/opt/xform/testdata/coster/outside-histogram new file mode 100644 index 000000000000..698d474b3bb0 --- /dev/null +++ b/pkg/sql/opt/xform/testdata/coster/outside-histogram @@ -0,0 +1,661 @@ +# This file contains tests for queries that filter columns beyond the max/min +# values in their histograms. Each query is tested multiple times with different +# settings to show the effect those settings have on the plan. +# + +exec-ddl +CREATE TABLE t ( + k INT PRIMARY KEY, + i INT, + s STRING, + INDEX (i), + INDEX (s) +) +---- + +exec-ddl +ALTER TABLE t INJECT STATISTICS '[ + { + "columns": ["k"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 1000, + "null_count": 0, + "avg_size": 2, + "histo_col_type": "int", + "histo_buckets": [ + {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "0"}, + {"num_eq": 1, "num_range": 99, "distinct_range": 99, "upper_bound": "100"}, + {"num_eq": 1, "num_range": 199, "distinct_range": 199, "upper_bound": "200"}, + {"num_eq": 1, "num_range": 299, "distinct_range": 299, "upper_bound": "300"}, + {"num_eq": 1, "num_range": 399, "distinct_range": 399, "upper_bound": "400"} + ] + }, + { + "columns": ["i"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 44, + "null_count": 0, + "avg_size": 2, + "histo_col_type": "int", + "histo_buckets": [ + {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "0"}, + {"num_eq": 10, "num_range": 90, "distinct_range": 10, "upper_bound": "100"}, + {"num_eq": 10, "num_range": 190, "distinct_range": 10, "upper_bound": "200"}, + {"num_eq": 20, "num_range": 280, "distinct_range": 10, "upper_bound": "300"}, + {"num_eq": 30, "num_range": 370, "distinct_range": 10, "upper_bound": "400"} + ] + }, + { + "columns": ["s"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 1000, + "distinct_count": 40, + "null_count": 0, + "avg_size": 3, + "histo_col_type": "string", + "histo_buckets": [ + {"num_eq": 0, "num_range": 0, "distinct_range": 0, "upper_bound": "apple"}, + {"num_eq": 100, "num_range": 100, "distinct_range": 10, "upper_bound": "banana"}, + {"num_eq": 100, "num_range": 100, "distinct_range": 10, "upper_bound": "cherry"}, + {"num_eq": 200, "num_range": 100, "distinct_range": 10, "upper_bound": "mango"}, + {"num_eq": 200, "num_range": 100, "distinct_range": 10, "upper_bound": "pineapple"} + ] + } +]' +---- + +# -------------------------------------------------- +# Q1 +# -------------------------------------------------- + +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 + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 110 --- 120 --- 130 --- 140 + │ histogram(2)= + ├── cost: 24.0200012 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + └── scan t@t_i_idx + ├── columns: k:1!null i:2!null + ├── constraint: /2/1 + │ ├── [/500/110 - /500/110] + │ ├── [/500/120 - /500/120] + │ ├── [/500/130 - /500/130] + │ └── [/500/140 - /500/140] + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 110 --- 120 --- 130 --- 140 + │ histogram(2)= + ├── cost: 24.01 + ├── key: (1) + └── fd: ()-->(2) + +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 + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 110 --- 120 --- 130 --- 140 + │ histogram(2)= + ├── cost: 24.0200012 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + └── scan t@t_i_idx + ├── columns: k:1!null i:2!null + ├── constraint: /2/1 + │ ├── [/500/110 - /500/110] + │ ├── [/500/120 - /500/120] + │ ├── [/500/130 - /500/130] + │ └── [/500/140 - /500/140] + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 110 --- 120 --- 130 --- 140 + │ histogram(2)= + ├── cost: 24.01 + ├── 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,optimizer_min_row_count=0) +SELECT * FROM t WHERE k IN (100, 110, 120, 130) AND i > 500 +---- +index-join t + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 100 --- 110 --- 120 --- 130 + │ histogram(2)= + ├── cost: 18.0500006 + ├── key: (1) + ├── fd: (1)-->(2,3) + └── select + ├── columns: k:1!null i:2!null + ├── cardinality: [0 - 4] + ├── stats: [rows=8e-10, distinct(1)=8e-10, null(1)=0] + │ histogram(1)= 0 2e-10 0 2e-10 0 2e-10 0 2e-10 + │ <--- 100 --- 110 --- 120 --- 130 + ├── cost: 18.0400002 + ├── key: (1) + ├── fd: (1)-->(2) + ├── scan t@t_i_idx + │ ├── columns: k:1!null i:2!null + │ ├── constraint: /2/1: [/501/100 - ] + │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0] + │ │ histogram(1)= 0 0 1.98e-08 2e-10 3.98e-08 2e-10 5.98e-08 2e-10 7.98e-08 2e-10 + │ │ <--- 0 ---------- 100 ---------- 200 ---------- 300 ---------- 400 + │ │ histogram(2)= + │ ├── cost: 18.0200002 + │ ├── key: (1) + │ └── fd: (1)-->(2) + └── 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,optimizer_min_row_count=0) +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=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 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)] + +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,optimizer_min_row_count=0) +SELECT * FROM t WHERE k IN (410, 420, 430) AND i > 500 +---- +index-join t + ├── columns: k:1!null i:2!null s:3 + ├── cardinality: [0 - 3] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0] + │ histogram(1)= + │ histogram(2)= + ├── cost: 18.0500006 + ├── key: (1) + ├── fd: (1)-->(2,3) + └── select + ├── columns: k:1!null i:2!null + ├── cardinality: [0 - 3] + ├── stats: [rows=4e-17, distinct(1)=4e-17, null(1)=0] + │ histogram(1)= + ├── cost: 18.0400002 + ├── key: (1) + ├── fd: (1)-->(2) + ├── scan t@t_i_idx + │ ├── columns: k:1!null i:2!null + │ ├── constraint: /2/1: [/501/410 - ] + │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0] + │ │ histogram(1)= 0 0 1.98e-08 2e-10 3.98e-08 2e-10 5.98e-08 2e-10 7.98e-08 2e-10 + │ │ <--- 0 ---------- 100 ---------- 200 ---------- 300 ---------- 400 + │ │ histogram(2)= + │ ├── cost: 18.0200002 + │ ├── key: (1) + │ └── fd: (1)-->(2) + └── 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,optimizer_min_row_count=0) +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=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, 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=2e-07, distinct(1)=2e-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)] + +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,optimizer_min_row_count=0) +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=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0, distinct(2,3)=2e-07, null(2,3)=0, distinct(1-3)=2e-07, null(1-3)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 100 --- 110 --- 120 --- 130 + │ histogram(2)= 0 2e-07 + │ <--- 400 + │ histogram(3)= + ├── cost: 18.0700002 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── cardinality: [0 - 4] + │ ├── stats: [rows=8e-10] + │ ├── cost: 18.0500002 + │ ├── key: (1) + │ ├── fd: (1)-->(2,3) + │ └── select + │ ├── columns: k:1!null s:3!null + │ ├── cardinality: [0 - 4] + │ ├── stats: [rows=8e-10, distinct(1)=8e-10, null(1)=0] + │ │ histogram(1)= 0 2e-10 0 2e-10 0 2e-10 0 2e-10 + │ │ <--- 100 --- 110 --- 120 --- 130 + │ ├── cost: 18.0400002 + │ ├── key: (1) + │ ├── fd: (1)-->(3) + │ ├── scan t@t_s_idx + │ │ ├── columns: k:1!null s:3!null + │ │ ├── constraint: /3/1: (/NULL - /'apple') + │ │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(3)=2e-07, null(3)=0] + │ │ │ histogram(1)= 0 0 1.98e-08 2e-10 3.98e-08 2e-10 5.98e-08 2e-10 7.98e-08 2e-10 + │ │ │ <--- 0 ---------- 100 ---------- 200 ---------- 300 ---------- 400 + │ │ │ histogram(3)= + │ │ ├── cost: 18.0200002 + │ │ ├── key: (1) + │ │ └── fd: (1)-->(3) + │ └── filters + │ └── k:1 IN (100, 110, 120, 130) [outer=(1), constraints=(/1: [/100 - /100] [/110 - /110] [/120 - /120] [/130 - /130]; tight)] + └── filters + └── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] + +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 + ├── columns: k:1!null i:2!null s:3!null + ├── cardinality: [0 - 4] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0, distinct(2,3)=2e-07, null(2,3)=0, distinct(1-3)=2e-07, null(1-3)=0] + │ histogram(1)= 0 5e-08 0 5e-08 0 5e-08 0 5e-08 + │ <--- 100 --- 110 --- 120 --- 130 + │ histogram(2)= 0 2e-07 + │ <--- 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)] + +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,optimizer_min_row_count=0) +SELECT * FROM t WHERE i = 400 AND s > 'z' +---- +select + ├── columns: k:1!null i:2!null s:3!null + ├── stats: [rows=2e-07, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0, distinct(2,3)=2e-07, null(2,3)=0] + │ histogram(2)= 0 2e-07 + │ <--- 400 + │ histogram(3)= + ├── cost: 18.0700014 + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── stats: [rows=2e-07] + │ ├── cost: 18.0400014 + │ ├── 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=2e-07, distinct(3)=2e-07, null(3)=0] + │ │ histogram(3)= + │ ├── cost: 18.0200002 + │ ├── key: (1) + │ └── fd: (1)-->(3) + └── filters + └── i:2 = 400 [outer=(2), constraints=(/2: [/400 - /400]; tight), fd=()-->(2)] + +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 + ├── columns: k:1!null i:2!null s:3!null + ├── stats: [rows=2e-07, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0, distinct(2,3)=2e-07, null(2,3)=0] + │ histogram(2)= 0 2e-07 + │ <--- 400 + │ histogram(3)= + ├── cost: 18.0700014 + ├── cost-flags: unbounded-cardinality + ├── key: (1) + ├── fd: ()-->(2), (1)-->(3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── stats: [rows=2e-07] + │ ├── cost: 18.0400014 + │ ├── cost-flags: unbounded-cardinality + │ ├── 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=2e-07, distinct(3)=2e-07, null(3)=0] + │ │ histogram(3)= + │ ├── cost: 18.0200002 + │ ├── cost-flags: unbounded-cardinality + │ ├── key: (1) + │ └── fd: (1)-->(3) + └── 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,optimizer_min_row_count=0) +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=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0] + │ histogram(1)= 0 2e-07 + │ <--- 100 + │ histogram(2)= + │ histogram(3)= + ├── cost: 9.04000121 + ├── key: () + ├── fd: ()-->(1-3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── cardinality: [0 - 1] + │ ├── stats: [rows=2e-07] + │ ├── cost: 9.02000121 + │ ├── key: () + │ ├── fd: ()-->(1-3) + │ └── scan t@t_i_idx + │ ├── columns: k:1!null i:2!null + │ ├── constraint: /2/1: [/500/100 - /500/100] + │ ├── cardinality: [0 - 1] + │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ │ histogram(1)= 0 2e-07 + │ │ <--- 100 + │ │ histogram(2)= + │ ├── cost: 9.01 + │ ├── key: () + │ └── fd: ()-->(1,2) + └── filters + └── s:3 = 'zzz' [outer=(3), constraints=(/3: [/'zzz' - /'zzz']; tight), fd=()-->(3)] + +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 + ├── columns: k:1!null i:2!null s:3!null + ├── cardinality: [0 - 1] + ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(3)=2e-07, null(3)=0] + │ histogram(1)= 0 2e-07 + │ <--- 100 + │ histogram(2)= + │ histogram(3)= + ├── cost: 9.04000121 + ├── key: () + ├── fd: ()-->(1-3) + ├── index-join t + │ ├── columns: k:1!null i:2 s:3 + │ ├── cardinality: [0 - 1] + │ ├── stats: [rows=2e-07] + │ ├── cost: 9.02000121 + │ ├── key: () + │ ├── fd: ()-->(1-3) + │ └── scan t@t_i_idx + │ ├── columns: k:1!null i:2!null + │ ├── constraint: /2/1: [/500/100 - /500/100] + │ ├── cardinality: [0 - 1] + │ ├── stats: [rows=2e-07, distinct(1)=2e-07, null(1)=0, distinct(2)=2e-07, null(2)=0, distinct(1,2)=2e-07, null(1,2)=0] + │ │ histogram(1)= 0 2e-07 + │ │ <--- 100 + │ │ histogram(2)= + │ ├── cost: 9.01 + │ ├── key: () + │ └── 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 86cdee2de26c..66571409ffb1 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 7b2af3e4120b..a39b5ec26ad5 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, ~27KB, 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 868f13247321..fcd64c021569 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, ~47KB, 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, ~41KB, 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 98ee56fa0fcd..78a78bd881ef 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, ~25KB, 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, ~36KB, 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, ~32KB, 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, ~32KB, 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, ~68KB, 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, ~73KB, required=[presentation: ?column?:50]) +memo (optimized, ~74KB, required=[presentation: ?column?:50]) ├── G1: (project G2 G3) │ └── [presentation: ?column?:50] │ ├── best: (project G2 G3) diff --git a/pkg/sql/opt/xform/testdata/rules/select b/pkg/sql/opt/xform/testdata/rules/select index f796aca2ee4f..614b0f8777c4 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) @@ -11891,7 +11891,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, ~37KB, 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/prepared_stmt.go b/pkg/sql/prepared_stmt.go index 967fb36b487a..681222ca6122 100644 --- a/pkg/sql/prepared_stmt.go +++ b/pkg/sql/prepared_stmt.go @@ -164,6 +164,11 @@ func (p *planCosts) NumCustom() int { // AvgCustom returns the average cost of all the custom plan costs in planCosts. // If there are no custom plan costs, it returns 0. +// +// TODO(mgartner): Figure out how this should incorporate cost flags. Some of +// them, like UnboundedCardinality, are only set if session settings are set. +// When those session settings change, do we need to clear and recompute the +// average cost of custom plans? func (p *planCosts) AvgCustom() memo.Cost { if p.custom.length == 0 { return memo.Cost{C: 0} diff --git a/pkg/sql/sessiondatapb/local_only_session_data.proto b/pkg/sql/sessiondatapb/local_only_session_data.proto index f07b3c86e0da..2a5b84eec1c7 100644 --- a/pkg/sql/sessiondatapb/local_only_session_data.proto +++ b/pkg/sql/sessiondatapb/local_only_session_data.proto @@ -594,6 +594,16 @@ message LocalOnlySessionData { // CatalogDigestStalenessCheckEnabled is used to enable using the catalog // digest information to do fast memo checks. bool catalog_digest_staleness_check_enabled = 153; + // OptimizerPreferBoundedCardinality instructs the optimizer to prefer query + // 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 b4456217ca82..26b58c3dcb50 100644 --- a/pkg/sql/vars.go +++ b/pkg/sql/vars.go @@ -3724,6 +3724,7 @@ var varGen = map[string]sessionVar{ }, GlobalDefault: globalFalse, }, + // CockroachDB extension. `catalog_digest_staleness_check_enabled`: { GetStringVal: makePostgresBoolGetStringValFn(`catalog_digest_staleness_check_enabled`), @@ -3741,6 +3742,47 @@ var varGen = map[string]sessionVar{ GlobalDefault: globalTrue, Hidden: true, }, + + // CockroachDB extension. + `optimizer_prefer_bounded_cardinality`: { + GetStringVal: makePostgresBoolGetStringValFn(`optimizer_prefer_bounded_cardinality`), + Set: func(_ context.Context, m sessionDataMutator, s string) error { + b, err := paramparse.ParseBoolVar("optimizer_prefer_bounded_cardinality", s) + if err != nil { + return err + } + m.SetOptimizerPreferBoundedCardinality(b) + return nil + }, + Get: func(evalCtx *extendedEvalContext, _ *kv.Txn) (string, error) { + return formatBoolAsPostgresSetting(evalCtx.SessionData().OptimizerPreferBoundedCardinality), nil + }, + 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) {