diff --git a/pkg/sql/opt/norm/general_funcs.go b/pkg/sql/opt/norm/general_funcs.go index 57a637affc79..cf74b8c0c191 100644 --- a/pkg/sql/opt/norm/general_funcs.go +++ b/pkg/sql/opt/norm/general_funcs.go @@ -675,6 +675,11 @@ func (c *CustomFuncs) IsFilterFalse(filters memo.FiltersExpr) bool { return filters.IsFalse() } +// IsFilterEmpty returns true if filters is empty. +func (c *CustomFuncs) IsFilterEmpty(filters memo.FiltersExpr) bool { + return len(filters) == 0 +} + // IsContradiction returns true if the given filter item contains a // contradiction constraint. func (c *CustomFuncs) IsContradiction(item *memo.FiltersItem) bool { @@ -692,6 +697,13 @@ func (c *CustomFuncs) ConcatFilters(left, right memo.FiltersExpr) memo.FiltersEx return newFilters } +// DiffFilters creates new Filters that contains all conditions in left that do +// not exist in right. If right is empty, the original left filters are +// returned. +func (c *CustomFuncs) DiffFilters(left, right memo.FiltersExpr) memo.FiltersExpr { + return left.Difference(right) +} + // RemoveFiltersItem returns a new list that is a copy of the given list, except // that it does not contain the given search item. If the list contains the item // multiple times, then only the first instance is removed. If the list does not diff --git a/pkg/sql/opt/norm/inline_funcs.go b/pkg/sql/opt/norm/inline_funcs.go index 2ea5698e73c5..5ed3d1dcf5bb 100644 --- a/pkg/sql/opt/norm/inline_funcs.go +++ b/pkg/sql/opt/norm/inline_funcs.go @@ -216,172 +216,39 @@ func (c *CustomFuncs) CanInline(scalar opt.ScalarExpr) bool { return false } -// IndexedVirtualColumns returns the set of column IDs in the scanPrivate's -// table that are both virtual computed columns and key columns of a secondary -// index. -// -// TODO(mgartner): Include virtual computed columns that are referenced in -// partial index predicates. -func (c *CustomFuncs) IndexedVirtualColumns(scanPrivate *memo.ScanPrivate) opt.ColSet { - md := c.mem.Metadata() - tab := md.Table(scanPrivate.Table) - tabMeta := md.TableMeta(scanPrivate.Table) - - virtualCols := tabMeta.VirtualComputedColumns() - if virtualCols.Empty() { - // Return early if there are no virtual columns to avoid unnecessarily - // calculating the index key columns. - return opt.ColSet{} - } - - var indexCols opt.ColSet - for i, n := 0, tab.IndexCount(); i < n; i++ { - indexCols.UnionWith(tabMeta.IndexKeyColumnsMapVirtual(i)) - } - - return virtualCols.Intersection(indexCols) +// VirtualColumns returns the set of columns in the scanPrivate's table that are +// virtual computed columns. +func (c *CustomFuncs) VirtualColumns(scanPrivate *memo.ScanPrivate) opt.ColSet { + tabMeta := c.mem.Metadata().TableMeta(scanPrivate.Table) + return tabMeta.VirtualComputedColumns() } -// TryInlineSelectVirtualColumns is used by the InlineSelectVirtualColumns rule -// to find filters on virtual computed columns that can be pushed below the -// Project that produces the virtual column. The filter is pushed-down by -// inlining the virtual column expression. Only the columns in -// indexedVirtualColumns are eligible for inlining. -// -// If successful, a pair of filters is returned; the first containing filters in -// which virtual columns have been inlined, and the second containing filters -// that were unaffected by inlining. If unsuccessful, an empty -// InlinedVirtualColumnFiltersPair is returned, indicating that there is nothing -// to inline. -// -// Two separate filters are returned as a pair so that the -// InlineSelectVirtualColumns rule can push down eligible filters, but not the -// rest. -// -// For example: -// -// CREATE TABLE t ( -// a INT, -// b INT, -// v INT AS (abs(a)), -// w INT AS (abs(b)), -// INDEX (v) -// ) -// -// SELECT * FROM t WHERE v = 5 AND w = 10 +// InlinableVirtualColumnFilters returns a new filters expression containing any +// of the given filters that meet the criteria: // -// The (v = 5) and (w = 10) filters are returned in separate filters. In the -// first, v is inlined because it is indexed, so the filter is transformed to -// (abs(a) = 5). This filter is no longer dependent on the projection of v, so -// it can be pushed below the Project. The w variable is not indexed, so the -// (w = 10) filter is unchanged and remains above the Project. +// 1. The filter has references to any of the columns in virtualColumns. +// 2. The filter is not a correlated subquery. // -// See the InlineSelectVirtualColumns rule for more details. -func (c *CustomFuncs) TryInlineSelectVirtualColumns( - filters memo.FiltersExpr, projections memo.ProjectionsExpr, indexedVirtualColumns opt.ColSet, -) InlinedVirtualColumnFiltersPair { - // Collect the projections for the indexed virtual columns. - var inlinableCols opt.ColSet - var inlinableProjections memo.ProjectionsExpr - for i := range projections { - col := projections[i].Col - - // If the projected expression is volatile, it cannot be inlined. This - // should be impossible (computed column expressions cannot be - // volatile), but adds additional safety in case this function is - // expanded to inline non-virtual columns in the future. - if projections[i].ScalarProps().VolatilitySet.HasVolatile() { - continue - } - - if indexedVirtualColumns.Contains(col) { - // Initialize inlinableProjections lazily. - if inlinableProjections == nil { - inlinableProjections = make(memo.ProjectionsExpr, 0, len(projections)-i) - } - inlinableCols.Add(col) - inlinableProjections = append(inlinableProjections, projections[i]) - } - } - - if len(inlinableProjections) == 0 { - // None of the projections are eligible for inlining in filters. - return InlinedVirtualColumnFiltersPair{} - } - - // For each filter, determine if there are any virtual columns to inline. If - // so, inline the virtual column expressions in the filter. We keep track of - // which filters have inlined projections in inlinedFilterIdxs so that we - // can build a list of non-inlined filters below. - var inlined memo.FiltersExpr - var inlinedFilterIdxs util.FastIntSet +func (c *CustomFuncs) InlinableVirtualColumnFilters( + filters memo.FiltersExpr, virtualColumns opt.ColSet, +) (inlinableFilters memo.FiltersExpr) { for i := range filters { item := &filters[i] // Do not inline a filter if it has a correlated subquery or it does not - // reference an inlinable virtual column. - if item.ScalarProps().HasCorrelatedSubquery || !item.ScalarProps().OuterCols.Intersects(inlinableCols) { + // reference a virtual column. + if item.ScalarProps().HasCorrelatedSubquery || !item.ScalarProps().OuterCols.Intersects(virtualColumns) { continue } - // Initialize inlined lazily. - if inlined == nil { - inlined = make(memo.FiltersExpr, 0, len(filters)-i) - } - - // Inline the virtual column expressions in the filter. - filter := c.f.ConstructFiltersItem( - c.inlineProjections(item.Condition, inlinableProjections).(opt.ScalarExpr), - ) - inlined = append(inlined, filter) - inlinedFilterIdxs.Add(i) - } - - if len(inlined) == 0 { - // No projections were inlined in filters. - return InlinedVirtualColumnFiltersPair{} - } - - // Collect the filters which did not have projections inlined. - notInlined := make(memo.FiltersExpr, 0, len(filters)-inlinedFilterIdxs.Len()) - for i := range filters { - if !inlinedFilterIdxs.Contains(i) { - notInlined = append(notInlined, filters[i]) + // Initialize inlinableFilters lazily. + if inlinableFilters == nil { + inlinableFilters = make(memo.FiltersExpr, 0, len(filters)-i) } - } - return InlinedVirtualColumnFiltersPair{ - inlined: inlined, - notInlined: notInlined, + inlinableFilters = append(inlinableFilters, *item) } -} - -// InlinedVirtualColumnFiltersPair is the result of -// TryInlineSelectVirtualColumns. See that function for more details. -type InlinedVirtualColumnFiltersPair struct { - inlined memo.FiltersExpr - notInlined memo.FiltersExpr -} - -// InlinedFilters returns the filters in the pair where virtual columns were -// inlined. -func (c *CustomFuncs) InlinedFilters(pair InlinedVirtualColumnFiltersPair) memo.FiltersExpr { - return pair.inlined -} - -// NotInlinedFilters returns the filters in the pair where virtual columns were -// not inlined. -func (c *CustomFuncs) NotInlinedFilters(pair InlinedVirtualColumnFiltersPair) memo.FiltersExpr { - return pair.notInlined -} - -// InlineSelectVirtualColumnsSucceeded returns true if the pair is not nil, -// indicating that InlineSelectVirtualColumns successfully inlined virtual -// columns into some of the filters. -func (c *CustomFuncs) InlineSelectVirtualColumnsSucceeded( - pair InlinedVirtualColumnFiltersPair, -) bool { - return len(pair.inlined) > 0 + return inlinableFilters } // InlineSelectProject searches the filter conditions for any variable @@ -412,6 +279,7 @@ func (c *CustomFuncs) InlineProjectProject( newProjections := make(memo.ProjectionsExpr, len(projections)) for i := range projections { item := &projections[i] + newProjections[i] = c.f.ConstructProjectionsItem( c.inlineProjections(item.Element, innerProjections).(opt.ScalarExpr), item.Col, diff --git a/pkg/sql/opt/norm/rules/inline.opt b/pkg/sql/opt/norm/rules/inline.opt index c9124ea0031f..eb82132ca006 100644 --- a/pkg/sql/opt/norm/rules/inline.opt +++ b/pkg/sql/opt/norm/rules/inline.opt @@ -159,76 +159,76 @@ # InlineSelectVirtualColumns pushes Select filters referencing virtual columns # into a Project by inlining the virtual column expressions. This makes the -# Select independent of the Project. Filters with virtual columns are only -# pushed below the Project if those virtual columns are indexed by a secondary -# index. This allows exploration rules to generate plans that use those indexes. -# Non-indexed virtual column filters are not inlined because the virtual column +# Select independent of the Project. Because these filters are pushed below the +# Project, exploration rules that match on the (Select (Scan)) pattern can +# generate plans that use indexes on virtual columns. +# +# Filters on non-virtual projected columns are not inlined because the # expression would be executed twice (once in the filter and once in the -# projection), adding overhead without any chance of a secondary index being -# used in the optimized plan. +# projection), adding overhead without any chance of a secondary index on a +# virtual column being used in the optimized plan. # # Notice that this rule is similar to PushSelectIntoInlinableProject. The key # difference is that PushSelectIntoInlinableProject only inlines simple # expressions that will add negligible overhead when computing twice. # Conversely, InlineSelectVirtualColumns does not discriminate by the type of -# expression. It will inline all virtual columns that are indexed in the hopes -# that inling will lead to a query plan that uses a virtual column index. +# expression. It will inline all virtual columns in the hopes that inlining will +# lead to a query plan that uses a virtual column index. # -# Also, PushSelectIntoInlinableProject will inline filters if and only if each -# filter is inlinable (by its definition), whereas InlineSelectVirtualColumns -# will split the input filters into two groups: one to inline below the Project, -# and one to leave above the Project. +# Also, PushSelectIntoInlinableProject will inline filters if and only if all of +# the filter items are inlinable (by its definition), whereas +# InlineSelectVirtualColumns will split the input filters into two groups: one +# to inline below the Project, and one to leave above the Project. This allows +# filters on virtual columns to be pushed down in more cases. # # For example, consider the table and query: # # CREATE TABLE t ( # a INT, # b INT, -# v INT AS (abs(a)), -# w INT AS (abs(b)), +# v INT AS (abs(a)) VIRTUAL, # INDEX (v) # ) -# SELECT * FROM t WHERE v = 5 AND w = 10 +# SELECT v, w FROM ( +# SELECT v, abs(b) AS w FROM t +# ) WHERE v = 5 AND w = 10 # # The partially normalized expression for the SELECT query before -# InlineSelectVirtualColumns is: +# InlineSelectVirtualColumns is applied is: # -# project -# ├── columns: a:1 b:2 v:3 w:4 -# └── select -# ├── columns: a:1 b:2 v:3 w:4 -# ├── project -# │ ├── columns: v:3 w:4 a:1 b:2 -# │ ├── scan t -# │ │ └── columns: a:1 b:2 -# │ └── projections -# │ ├── abs(a:1) [as=v:3] -# │ └── abs(b:2) [as=w:4] -# └── filters -# ├── v:3 = 5 -# └── w:4 = 10 +# select +# ├── columns: v:3 w:6 +# ├── project +# │ ├── columns: w:6 v:3 +# │ ├── scan t +# │ │ └── columns: a:1 b:2 +# │ └── projections +# │ ├── abs(b:2) [as=w:6] +# │ └── abs(a:1) [as=v:3] +# └── filters +# ├── v:3 = 5 +# └── w:6 = 10 # # InlineSelectVirtualColumns will push only the (v = 5) filter below the Project -# as (abs(a) = 5) because v is indexed. The (w = 10) filter remains above the -# Project. +# as (abs(a) = 5) because v is a virtual column. The (w = 10) filter remains +# above the Project. Notice the (Select (Scan)) pattern that will allow a +# constrained scan over the secondary index to be generated. # -# project -# ├── columns: a:1 b:2 v:3 w:4 -# └── select -# ├── columns: a:1 b:2 v:3 w:4!null -# ├── project -# │ ├── columns: v:3 w:4 a:1 b:2 -# │ ├── select -# │ │ ├── columns: a:1 b:2 -# │ │ ├── scan t -# │ │ │ └── columns: a:1 b:2 -# │ │ └── filters -# │ │ └── abs(a:1) = 5 -# │ └── projections -# │ ├── abs(a:1) [as=v:3] -# │ └── abs(b:2) [as=w:4] -# └── filters -# └── w:4 = 10 +# select +# ├── columns: v:3 w:6 +# ├── project +# │ ├── columns: w:6 v:3 +# │ ├── select +# │ │ ├── columns: a:1 b:2 +# │ │ ├── scan t +# │ │ │ └── columns: a:1 b:2 +# │ │ └── filters +# │ │ └── abs(a:1) = 5 +# │ └── projections +# │ ├── abs(b:2) [as=w:6] +# │ └── abs(a:1) [as=v:3] +# └── filters +# └── w:6 = 10 # # This rule has no explicit priority so that it runs before # PushSelectIntoInlinableProject (which is low priority). It must run before @@ -244,26 +244,26 @@ ) $filters:* & ^(ColsAreEmpty - $indexedVirtualColumns:(IndexedVirtualColumns - $scanPrivate - ) + $virtualColumns:(VirtualColumns $scanPrivate) ) & - (InlineSelectVirtualColumnsSucceeded - $result:(TryInlineSelectVirtualColumns + ^(IsFilterEmpty + $inlinableFilters:(InlinableVirtualColumnFilters $filters - $projections - $indexedVirtualColumns + $virtualColumns ) ) ) => (Select (Project - (Select $scan (InlinedFilters $result)) + (Select + $scan + (InlineSelectProject $inlinableFilters $projections) + ) $projections $passthrough ) - (NotInlinedFilters $result) + (DiffFilters $filters $inlinableFilters) ) # InlineProjectInProject folds an inner Project operator into an outer Project diff --git a/pkg/sql/opt/norm/testdata/rules/inline b/pkg/sql/opt/norm/testdata/rules/inline index e9a88cd7c2d0..100be311846e 100644 --- a/pkg/sql/opt/norm/testdata/rules/inline +++ b/pkg/sql/opt/norm/testdata/rules/inline @@ -21,7 +21,6 @@ CREATE TABLE virt ( s STRING, j JSON, v STRING AS (lower(s)) VIRTUAL, - w STRING AS (upper(s)) VIRTUAL, x JSON AS (j->'x') VIRTUAL, INDEX (v), INVERTED INDEX (x) @@ -723,10 +722,10 @@ norm expect=InlineSelectVirtualColumns SELECT * FROM virt WHERE v = 'foo' ---- project - ├── columns: k:1!null i:2 s:3 j:4 v:5 w:6 x:7 + ├── columns: k:1!null i:2 s:3 j:4 v:5 x:6 ├── immutable ├── key: (1) - ├── fd: (1)-->(2-4), (3)-->(5,6), (4)-->(7) + ├── fd: (1)-->(2-4), (3)-->(5), (4)-->(6) ├── select │ ├── columns: k:1!null i:2 s:3 j:4 │ ├── immutable @@ -737,9 +736,7 @@ project │ │ ├── computed column expressions │ │ │ ├── v:5 │ │ │ │ └── lower(s:3) - │ │ │ ├── w:6 - │ │ │ │ └── upper(s:3) - │ │ │ └── x:7 + │ │ │ └── x:6 │ │ │ └── j:4->'x' │ │ ├── key: (1) │ │ └── fd: (1)-->(2-4) @@ -747,18 +744,17 @@ project │ └── lower(s:3) = 'foo' [outer=(3), immutable] └── projections ├── lower(s:3) [as=v:5, outer=(3), immutable] - ├── upper(s:3) [as=w:6, outer=(3), immutable] - └── j:4->'x' [as=x:7, outer=(4), immutable] + └── j:4->'x' [as=x:6, outer=(4), immutable] # Inline inverted-indexed virtual columns. norm expect=InlineSelectVirtualColumns SELECT * FROM virt WHERE x @> '1' ---- project - ├── columns: k:1!null i:2 s:3 j:4 v:5 w:6 x:7 + ├── columns: k:1!null i:2 s:3 j:4 v:5 x:6 ├── immutable ├── key: (1) - ├── fd: (1)-->(2-4), (3)-->(5,6), (4)-->(7) + ├── fd: (1)-->(2-4), (3)-->(5), (4)-->(6) ├── select │ ├── columns: k:1!null i:2 s:3 j:4 │ ├── immutable @@ -769,9 +765,7 @@ project │ │ ├── computed column expressions │ │ │ ├── v:5 │ │ │ │ └── lower(s:3) - │ │ │ ├── w:6 - │ │ │ │ └── upper(s:3) - │ │ │ └── x:7 + │ │ │ └── x:6 │ │ │ └── j:4->'x' │ │ ├── key: (1) │ │ └── fd: (1)-->(2-4) @@ -779,131 +773,80 @@ project │ └── (j:4->'x') @> '1' [outer=(4), immutable] └── projections ├── lower(s:3) [as=v:5, outer=(3), immutable] - ├── upper(s:3) [as=w:6, outer=(3), immutable] - └── j:4->'x' [as=x:7, outer=(4), immutable] + └── j:4->'x' [as=x:6, outer=(4), immutable] -# Do not inline virtual columns that aren't indexed. -norm expect-not=InlineSelectVirtualColumns -SELECT * FROM virt WHERE w = 'FOO' ----- -select - ├── columns: k:1!null i:2 s:3 j:4 v:5 w:6!null x:7 - ├── immutable - ├── key: (1) - ├── fd: ()-->(6), (1)-->(2-4), (3)-->(5), (4)-->(7) - ├── project - │ ├── columns: v:5 w:6 x:7 k:1!null i:2 s:3 j:4 - │ ├── immutable - │ ├── key: (1) - │ ├── fd: (1)-->(2-4), (3)-->(5,6), (4)-->(7) - │ ├── scan virt - │ │ ├── columns: k:1!null i:2 s:3 j:4 - │ │ ├── computed column expressions - │ │ │ ├── v:5 - │ │ │ │ └── lower(s:3) - │ │ │ ├── w:6 - │ │ │ │ └── upper(s:3) - │ │ │ └── x:7 - │ │ │ └── j:4->'x' - │ │ ├── key: (1) - │ │ └── fd: (1)-->(2-4) - │ └── projections - │ ├── lower(s:3) [as=v:5, outer=(3), immutable] - │ ├── upper(s:3) [as=w:6, outer=(3), immutable] - │ └── j:4->'x' [as=x:7, outer=(4), immutable] - └── filters - └── w:6 = 'FOO' [outer=(6), constraints=(/6: [/'FOO' - /'FOO']; tight), fd=()-->(6)] - -# Split the filters and inline only the indexed virtual columns. +# Split the filters and inline only virtual columns. norm expect=InlineSelectVirtualColumns -SELECT * FROM virt WHERE v = 'foo' AND w = 'FOO' AND i = 10 +SELECT * FROM ( + SELECT i, v, upper(s) AS w FROM virt +) WHERE v = 'foo' AND w = 'FOO' AND i = 10 ---- select - ├── columns: k:1!null i:2!null s:3 j:4 v:5 w:6!null x:7 + ├── columns: i:2!null v:5 w:9!null ├── immutable - ├── key: (1) - ├── fd: ()-->(2,6), (1)-->(3,4), (3)-->(5), (4)-->(7) + ├── fd: ()-->(2,9) ├── project - │ ├── columns: v:5 w:6 x:7 k:1!null i:2!null s:3 j:4 + │ ├── columns: w:9 v:5 i:2!null │ ├── immutable - │ ├── key: (1) - │ ├── fd: ()-->(2), (1)-->(3,4), (3)-->(5,6), (4)-->(7) + │ ├── fd: ()-->(2) │ ├── select - │ │ ├── columns: k:1!null i:2!null s:3 j:4 + │ │ ├── columns: i:2!null s:3 │ │ ├── immutable - │ │ ├── key: (1) - │ │ ├── fd: ()-->(2), (1)-->(3,4) + │ │ ├── fd: ()-->(2) │ │ ├── scan virt - │ │ │ ├── columns: k:1!null i:2 s:3 j:4 - │ │ │ ├── computed column expressions - │ │ │ │ ├── v:5 - │ │ │ │ │ └── lower(s:3) - │ │ │ │ ├── w:6 - │ │ │ │ │ └── upper(s:3) - │ │ │ │ └── x:7 - │ │ │ │ └── j:4->'x' - │ │ │ ├── key: (1) - │ │ │ └── fd: (1)-->(2-4) + │ │ │ ├── columns: i:2 s:3 + │ │ │ └── computed column expressions + │ │ │ ├── v:5 + │ │ │ │ └── lower(s:3) + │ │ │ └── x:6 + │ │ │ └── j:4->'x' │ │ └── filters │ │ ├── lower(s:3) = 'foo' [outer=(3), immutable] │ │ └── i:2 = 10 [outer=(2), constraints=(/2: [/10 - /10]; tight), fd=()-->(2)] │ └── projections - │ ├── lower(s:3) [as=v:5, outer=(3), immutable] - │ ├── upper(s:3) [as=w:6, outer=(3), immutable] - │ └── j:4->'x' [as=x:7, outer=(4), immutable] + │ ├── upper(s:3) [as=w:9, outer=(3), immutable] + │ └── lower(s:3) [as=v:5, outer=(3), immutable] └── filters - └── w:6 = 'FOO' [outer=(6), constraints=(/6: [/'FOO' - /'FOO']; tight), fd=()-->(6)] + └── w:9 = 'FOO' [outer=(9), constraints=(/9: [/'FOO' - /'FOO']; tight), fd=()-->(9)] # Do not inline correlated subqueries. norm expect-not=InlineSelectVirtualColumns SELECT * FROM virt v1 -WHERE w = 'FOO' AND EXISTS ( +WHERE EXISTS ( SELECT * FROM virt v2 WHERE v1.v = v2.s ) ---- semi-join (hash) - ├── columns: k:1!null i:2 s:3 j:4 v:5 w:6!null x:7 + ├── columns: k:1!null i:2 s:3 j:4 v:5 x:6 ├── immutable ├── key: (1) - ├── fd: ()-->(6), (1)-->(2-4), (3)-->(5), (4)-->(7) - ├── select - │ ├── columns: k:1!null i:2 s:3 j:4 v:5 w:6!null x:7 + ├── fd: (1)-->(2-4), (3)-->(5), (4)-->(6) + ├── project + │ ├── columns: v:5 x:6 k:1!null i:2 s:3 j:4 │ ├── immutable │ ├── key: (1) - │ ├── fd: ()-->(6), (1)-->(2-4), (3)-->(5), (4)-->(7) - │ ├── project - │ │ ├── columns: v:5 w:6 x:7 k:1!null i:2 s:3 j:4 - │ │ ├── immutable + │ ├── fd: (1)-->(2-4), (3)-->(5), (4)-->(6) + │ ├── scan virt + │ │ ├── columns: k:1!null i:2 s:3 j:4 + │ │ ├── computed column expressions + │ │ │ ├── v:5 + │ │ │ │ └── lower(s:3) + │ │ │ └── x:6 + │ │ │ └── j:4->'x' │ │ ├── key: (1) - │ │ ├── fd: (1)-->(2-4), (3)-->(5,6), (4)-->(7) - │ │ ├── scan virt - │ │ │ ├── columns: k:1!null i:2 s:3 j:4 - │ │ │ ├── computed column expressions - │ │ │ │ ├── v:5 - │ │ │ │ │ └── lower(s:3) - │ │ │ │ ├── w:6 - │ │ │ │ │ └── upper(s:3) - │ │ │ │ └── x:7 - │ │ │ │ └── j:4->'x' - │ │ │ ├── key: (1) - │ │ │ └── fd: (1)-->(2-4) - │ │ └── projections - │ │ ├── lower(s:3) [as=v:5, outer=(3), immutable] - │ │ ├── upper(s:3) [as=w:6, outer=(3), immutable] - │ │ └── j:4->'x' [as=x:7, outer=(4), immutable] - │ └── filters - │ └── w:6 = 'FOO' [outer=(6), constraints=(/6: [/'FOO' - /'FOO']; tight), fd=()-->(6)] + │ │ └── fd: (1)-->(2-4) + │ └── projections + │ ├── lower(s:3) [as=v:5, outer=(3), immutable] + │ └── j:4->'x' [as=x:6, outer=(4), immutable] ├── scan virt - │ ├── columns: s:12 + │ ├── columns: s:11 │ └── computed column expressions - │ ├── v:14 - │ │ └── lower(s:12) - │ ├── w:15 - │ │ └── upper(s:12) - │ └── x:16 - │ └── j:13->'x' + │ ├── v:13 + │ │ └── lower(s:11) + │ └── x:14 + │ └── j:12->'x' └── filters - └── v:5 = s:12 [outer=(5,12), constraints=(/5: (/NULL - ]; /12: (/NULL - ]), fd=(5)==(12), (12)==(5)] + └── v:5 = s:11 [outer=(5,11), constraints=(/5: (/NULL - ]; /11: (/NULL - ]), fd=(5)==(11), (11)==(5)] # -------------------------------------------------- # InlineProjectInProject diff --git a/pkg/sql/opt/xform/testdata/rules/select b/pkg/sql/opt/xform/testdata/rules/select index 0d5dd6af11a4..22d2dcd6b172 100644 --- a/pkg/sql/opt/xform/testdata/rules/select +++ b/pkg/sql/opt/xform/testdata/rules/select @@ -124,8 +124,10 @@ CREATE TABLE virtual ( c INT AS (b + 10) VIRTUAL, s STRING, lower_s STRING AS (lower(s)) VIRTUAL, + upper_s STRING AS (upper(s)) VIRTUAL, INDEX (a) WHERE c IN (10, 20, 30), - INDEX (lower_s) + INDEX (lower_s), + INDEX (a) WHERE upper_s = 'FOO' ) ---- @@ -367,11 +369,15 @@ project │ ├── computed column expressions │ │ ├── c:4 │ │ │ └── b:3 + 10 - │ │ └── lower_s:6 - │ │ └── lower(s:5) + │ │ ├── lower_s:6 + │ │ │ └── lower(s:5) + │ │ └── upper_s:7 + │ │ └── upper(s:5) │ ├── partial index predicates + │ │ ├── secondary: filters + │ │ │ └── (b:3 + 10) IN (10, 20, 30) [outer=(3), immutable] │ │ └── secondary: filters - │ │ └── (b:3 + 10) IN (10, 20, 30) [outer=(3), immutable] + │ │ └── upper(s:5) = 'FOO' [outer=(5), immutable] │ ├── key: (1) │ └── fd: (1)-->(3) └── filters @@ -1711,17 +1717,36 @@ project │ ├── computed column expressions │ │ ├── c:4 │ │ │ └── b:3 + 10 - │ │ └── lower_s:6 - │ │ └── lower(s:5) + │ │ ├── lower_s:6 + │ │ │ └── lower(s:5) + │ │ └── upper_s:7 + │ │ └── upper(s:5) │ ├── partial index predicates + │ │ ├── secondary: filters + │ │ │ └── (b:3 + 10) IN (10, 20, 30) [outer=(3), immutable] │ │ └── secondary: filters - │ │ └── (b:3 + 10) IN (10, 20, 30) [outer=(3), immutable] + │ │ └── upper(s:5) = 'FOO' [outer=(5), immutable] │ ├── key: (1) │ └── fd: (1)-->(2,3) └── filters ├── (a:2 > 10) AND (a:2 < 20) [outer=(2), constraints=(/2: [/11 - /19]; tight)] └── b:3 = 10 [outer=(3), constraints=(/3: [/10 - /10]; tight), fd=()-->(3)] +# Test that we can constrain a partial index with a virtual column in the +# predicate. +opt expect=GenerateConstrainedScans +SELECT k FROM virtual WHERE a > 1 AND a < 10 AND upper_s = 'FOO' +---- +project + ├── columns: k:1!null + ├── immutable + ├── key: (1) + └── scan virtual@secondary,partial + ├── columns: k:1!null a:2!null + ├── constraint: /2/1: [/2 - /9] + ├── key: (1) + └── fd: (1)-->(2) + # Regression test for #55387. GenerateConstrainedScans should not reduce the # input filters when proving partial index implication. diff --git a/pkg/sql/sem/tree/backup.go b/pkg/sql/sem/tree/backup.go index 521be0067201..2406d6a3c7f2 100644 --- a/pkg/sql/sem/tree/backup.go +++ b/pkg/sql/sem/tree/backup.go @@ -294,7 +294,7 @@ func (o *RestoreOptions) Format(ctx *FmtCtx) { if o.IntoDB != nil { maybeAddSep() ctx.WriteString("into_db=") - o.IntoDB.Format(ctx) + ctx.formatNodeOrHideConstants(o.IntoDB) } if o.SkipMissingFKs { diff --git a/pkg/sql/sem/tree/format_test.go b/pkg/sql/sem/tree/format_test.go index 106ebced0a0b..9779ede0ac6e 100644 --- a/pkg/sql/sem/tree/format_test.go +++ b/pkg/sql/sem/tree/format_test.go @@ -127,6 +127,13 @@ func TestFormatStatement(t *testing.T) { `ALTER TYPE _ DROP VALUE _`}, {`ALTER TYPE a RENAME VALUE 'value1' TO 'value2'`, tree.FmtAnonymize, `ALTER TYPE _ RENAME VALUE _ TO _`}, + + {`RESTORE abc.xzy FROM 'a' WITH into_db='foo', skip_missing_foreign_keys`, + tree.FmtHideConstants | tree.FmtAnonymize, + `RESTORE TABLE _._ FROM _ WITH into_db=_, skip_missing_foreign_keys`}, + {`RESTORE FROM 'a' WITH into_db='foo', skip_missing_foreign_keys`, + tree.FmtHideConstants | tree.FmtAnonymize, + `RESTORE FROM _ WITH into_db=_, skip_missing_foreign_keys`}, } for i, test := range testData {