diff --git a/pkg/sql/logictest/testdata/logic_test/geospatial b/pkg/sql/logictest/testdata/logic_test/geospatial index e861f27c636e..57ffa6568170 100644 --- a/pkg/sql/logictest/testdata/logic_test/geospatial +++ b/pkg/sql/logictest/testdata/logic_test/geospatial @@ -6021,3 +6021,159 @@ SELECT st_asmvtgeom( NULL subtest end + +subtest regression_103616 + +# Regression test for #103616 +statement ok +CREATE TABLE t103616 ( + tag string, + geog GEOGRAPHY, + geom GEOMETRY, + INVERTED INDEX geog_idx (geog), + INVERTED INDEX geom_idx (geom) +); + +statement ok +insert into t103616 values ('null', null, null); + +statement ok +insert into t103616 values ('point (0 0)', 'POINT(0 0)'::GEOGRAPHY, 'POINT(0 0)'::GEOMETRY); + +statement ok +insert into t103616 values ('point (10 10)', 'POINT(10 10)'::GEOGRAPHY, 'POINT(10 10)'::GEOMETRY); + +statement ok +insert into t103616 values ('empty 1', ST_GeogFromText('POLYGON EMPTY'), ST_GeomFromText('POLYGON EMPTY')); + +statement ok +insert into t103616 values ('empty 2', ST_GeogFromText('GEOMETRYCOLLECTION EMPTY'), ST_GeomFromText('GEOMETRYCOLLECTION EMPTY')); + +# Null and empty geog values should not appear in the output. +query T rowsort +SELECT tag FROM t103616@geog_idx +WHERE + st_distance(geog, 'POINT(0 0)'::GEOGRAPHY, false) = 0; +---- +point (0 0) + +# Null and empty geog values should not appear in the output. +query T rowsort +SELECT tag FROM t103616@geog_idx +WHERE + st_distance(geog, 'POINT(0 0)'::GEOGRAPHY, false) = 0; +---- +point (0 0) + +# Null and empty geog values should not appear in the output. +query T rowsort +SELECT tag FROM t103616@geog_idx +WHERE + NOT 1.2345678901234566e-43 < st_distance(geog, 'POINT(0 0)'::GEOGRAPHY); +---- +point (0 0) + +# Null and empty geog values should not appear in the output. +query T rowsort +SELECT tag FROM t103616 +WHERE + NOT 1.2345678901234566e-43 > st_distance(geog, 'POINT(0 0)'::GEOGRAPHY); +---- +point (10 10) + +# Null and empty geog values should not appear in the output. +query T rowsort +SELECT tag FROM t103616@geog_idx +WHERE + 1.2345678901234566e-43 > st_distance(geog, 'POINT(0 0)'::GEOGRAPHY); +---- +point (0 0) + +# Null and empty geog values should not appear in the output. +query T rowsort +SELECT tag FROM t103616 +WHERE + 1.2345678901234566e-43 < st_distance(geog, 'POINT(0 0)'::GEOGRAPHY); +---- +point (10 10) + +# Null and empty geog values should not appear in the output. +query T rowsort +SELECT tag FROM t103616 +WHERE + 0 <= st_distance(geog, 'POINT(0 0)'::GEOGRAPHY); +---- +point (0 0) +point (10 10) + +# Only null and empty geog values should appear in the output. +query T rowsort +SELECT tag FROM t103616 +WHERE + (st_distance(geog, 'POINT(0 0)'::GEOGRAPHY, false) = 0) IS NULL; +---- +null +empty 1 +empty 2 + +# Only null and empty geog values should appear in the output. +query T rowsort +SELECT tag FROM t103616 +WHERE + (0 <= st_distance(geog, 'POINT(0 0)'::GEOGRAPHY)) IS NULL; +---- +null +empty 1 +empty 2 + +# Null and empty geom values should appear in the output. +query T rowsort +SELECT tag FROM t103616@geom_idx +WHERE + NOT 1.2345678901234566e-43 < st_maxdistance(geom, 'POINT(0 0)'::GEOMETRY); +---- +point (0 0) + +# Null and empty geom values should not appear in the output. +query T rowsort +SELECT tag FROM t103616 +WHERE + NOT 1.2345678901234566e-43 > st_maxdistance(geom, 'POINT(0 0)'::GEOMETRY); +---- +point (10 10) + +# Null and empty geom values should not appear in the output. +query T rowsort +SELECT tag FROM t103616@geom_idx +WHERE + 1.2345678901234566e-43 > st_maxdistance(geom, 'POINT(0 0)'::GEOMETRY); +---- +point (0 0) + +# Null and empty geom values should not appear in the output. +query T rowsort +SELECT tag FROM t103616 +WHERE + 1.2345678901234566e-43 < st_maxdistance(geom, 'POINT(0 0)'::GEOMETRY); +---- +point (10 10) + +# Null and empty geom values should not appear in the output. +query T rowsort +SELECT tag FROM t103616 +WHERE + 0 <= st_maxdistance(geom, 'POINT(0 0)'::GEOMETRY); +---- +point (0 0) +point (10 10) + +# Only null and empty geog values should appear in the output. +query T rowsort +SELECT tag FROM t103616 +WHERE + (0 <= st_maxdistance(geom, 'POINT(0 0)'::GEOMETRY)) IS NULL; +---- +null +empty 1 +empty 2 + diff --git a/pkg/sql/opt/invertedidx/geo.go b/pkg/sql/opt/invertedidx/geo.go index c2bbcdec60f6..345050126d2a 100644 --- a/pkg/sql/opt/invertedidx/geo.go +++ b/pkg/sql/opt/invertedidx/geo.go @@ -334,6 +334,218 @@ type geoFilterPlanner struct { var _ invertedFilterPlanner = &geoFilterPlanner{} +// STDistanceUseSpheroid returns true if the use_spheroid argument of +// st_distance is not explicitly false. use_spheroid is the third argument of +// st_distance for the geography overload and it is true by default. The +// geometry overload does not have a use_spheroid argument, so if either of the +// first two arguments are geometries, it returns false. +func (g *geoFilterPlanner) STDistanceUseSpheroid(args memo.ScalarListExpr) bool { + if len(args) < 2 { + panic(errors.AssertionFailedf("expected st_distance to have at least two arguments")) + } + if args[0].DataType().Family() == types.GeometryFamily || + args[1].DataType().Family() == types.GeometryFamily { + return false + } + const useSpheroidIdx = 2 + if len(args) <= useSpheroidIdx { + // The use_spheroid argument is true by default, so return true if it + // was not provided. + return true + } + return args[useSpheroidIdx].Op() != opt.FalseOp +} + +// MaybeMakeSTDWithin attempts to derive an ST_DWithin (or ST_DFullyWithin) +// function that is similar to an expression of the following form: +// ST_Distance(a,b) <= x. The ST_Distance function can be on either side of the +// inequality, and the inequality can be one of the following: '<', '<=', '>', +// '>='. This replacement allows early-exit behavior, and may enable use of an +// inverted index scan. If the derived expression would need to be negated with +// a NotExpr, so the derivation fails, and expr, ok=false is returned. +func (g *geoFilterPlanner) MaybeMakeSTDWithin( + expr opt.ScalarExpr, + args memo.ScalarListExpr, + bound opt.ScalarExpr, + fnIsLeftArg bool, + fullyWithin bool, +) (derivedExpr opt.ScalarExpr, ok bool) { + op := expr.Op() + var not bool + var name string + fnName := "st_dwithin" + if fullyWithin { + fnName = "st_dfullywithin" + } + incName := fnName + exName := fnName + "exclusive" + switch op { + case opt.GeOp: + if fnIsLeftArg { + // Matched expression: ST_Distance(a,b) >= x. + not = true + name = exName + } else { + // Matched expression: x >= ST_Distance(a,b). + not = false + name = incName + } + + case opt.GtOp: + if fnIsLeftArg { + // Matched expression: ST_Distance(a,b) > x. + not = true + name = incName + } else { + // Matched expression: x > ST_Distance(a,b). + not = false + name = exName + } + + case opt.LeOp: + if fnIsLeftArg { + // Matched expression: ST_Distance(a,b) <= x. + not = false + name = incName + } else { + // Matched expression: x <= ST_Distance(a,b). + not = true + name = exName + } + + case opt.LtOp: + if fnIsLeftArg { + // Matched expression: ST_Distance(a,b) < x. + not = false + name = exName + } else { + // Matched expression: x < ST_Distance(a,b). + not = true + name = incName + } + } + if not { + // ST_DWithin and ST_DWithinExclusive are equivalent to ST_Distance <= x and + // ST_Distance < x respectively. The comparison operator in the matched + // expression (if ST_Distance is normalized to be on the left) is either '>' + // or '>='. Therefore, we would have to take the opposite of within. This + // would not result in a useful expression for inverted index scan, so return + // ok=false. + return expr, false + } + newArgs := make(memo.ScalarListExpr, len(args)+1) + const distanceIdx, useSpheroidIdx = 2, 3 + copy(newArgs, args[:distanceIdx]) + + // The distance parameter must be type float. + newArgs[distanceIdx] = g.factory.ConstructCast(bound, types.Float) + + // Add the use_spheroid parameter if it exists. + if len(newArgs) > useSpheroidIdx { + newArgs[useSpheroidIdx] = args[useSpheroidIdx-1] + } + + props, overload, ok := memo.FindFunction(&newArgs, name) + if !ok { + panic(errors.AssertionFailedf("could not find overload for %s", name)) + } + within := g.factory.ConstructFunction(newArgs, &memo.FunctionPrivate{ + Name: name, + Typ: types.Bool, + Properties: props, + Overload: overload, + }) + return within, true +} + +// maybeDeriveUsefulInvertedFilterCondition identifies an expression of the +// form: 'st_distance(a, b, bool) = 0', 'st_distance(...) <= x' or +// 'st_maxdistance(...) <= x', and returns a function call to st_dwithin, +// st_dwithinexclusive, st_dfullywithin, st_dfullywithinexclusive or +// st_intersects which is almost equivalent to the original expression, except +// for empty or null geography/geometry inputs (it may evaluate to true in more +// cases). The derived expression may enable use of an inverted index scan. See +// the MaybeMakeSTDWithin or MakeIntersectionFunction method for the specific +// function that is used to replace expressions with different comparison +// operators (e.g. '<' vs '<='). Note that the `st_distance` or `st_maxdistance` +// may be on the left or right of the comparison operation (LT, GT, LE, GE). +func (g *geoFilterPlanner) maybeDeriveUsefulInvertedFilterCondition( + expr opt.ScalarExpr, +) (opt.ScalarExpr, bool) { + var left, right opt.ScalarExpr + var function *memo.FunctionExpr + leftIsFunction := false + rightIsFunction := false + c := g.factory.CustomFuncs() + switch t := expr.(type) { + case *memo.EqExpr: + left = t.Left + right = t.Right + function, leftIsFunction = left.(*memo.FunctionExpr) + if !leftIsFunction { + return expr, false + } + private := &function.FunctionPrivate + if private.Name != "st_distance" { + return expr, false + } + if g.STDistanceUseSpheroid(function.Args) { + return expr, false + } + constant, rightIsConstant := right.(*memo.ConstExpr) + if !rightIsConstant { + return expr, false + } + value := constant.Value + if !c.IsFloatDatum(value) { + return expr, false + } + if !c.DatumsEqual(value, tree.NewDInt(0)) { + return expr, false + } + return c.MakeIntersectionFunction(function.Args), true + case *memo.LtExpr, *memo.GtExpr, *memo.LeExpr, *memo.GeExpr: + left = t.Child(0).(opt.ScalarExpr) + right = t.Child(1).(opt.ScalarExpr) + function, leftIsFunction = left.(*memo.FunctionExpr) + if !leftIsFunction { + function, rightIsFunction = right.(*memo.FunctionExpr) + if !rightIsFunction { + return expr, false + } + } + // Combinations which result in a `NOT st_d*` function would not enable + // inverted index scan, so no need to derive filters for these cases. + if leftIsFunction && (t.Op() == opt.GtOp || t.Op() == opt.GeOp) { + return expr, false + } else if rightIsFunction && (t.Op() == opt.LtOp || t.Op() == opt.LeOp) { + return expr, false + } + // Main logic below to eliminate a code nesting level. + default: + return expr, false + } + + if function == nil { + return expr, false + } + args := function.Args + private := &function.FunctionPrivate + if private.Name != "st_distance" && private.Name != "st_maxdistance" { + return expr, false + } + var boundExpr opt.ScalarExpr + if leftIsFunction { + boundExpr = right + } else { + boundExpr = left + } + if private.Name == "st_distance" { + return g.MaybeMakeSTDWithin(expr, args, boundExpr, leftIsFunction, false /* fullyWithin */) + } + return g.MaybeMakeSTDWithin(expr, args, boundExpr, leftIsFunction, true /* fullyWithin */) +} + // extractInvertedFilterConditionFromLeaf is part of the invertedFilterPlanner // interface. func (g *geoFilterPlanner) extractInvertedFilterConditionFromLeaf( @@ -344,6 +556,9 @@ func (g *geoFilterPlanner) extractInvertedFilterConditionFromLeaf( _ *invertedexpr.PreFiltererStateForInvertedFilterer, ) { var args memo.ScalarListExpr + filterIsDerived := false + originalExpr := expr + expr, filterIsDerived = g.maybeDeriveUsefulInvertedFilterCondition(expr) switch t := expr.(type) { case *memo.FunctionExpr: args = t.Args @@ -382,8 +597,11 @@ func (g *geoFilterPlanner) extractInvertedFilterConditionFromLeaf( ctx, g.factory, expr, args, true /* commuteArgs */, g.tabID, g.index, g.getSpanExpr, ) } - if !invertedExpr.IsTight() { - remainingFilters = expr + // A derived filter may not be semantically equivalent to the original, so we + // need to apply the original filter in that case, the same as when the + // inverted expression is not tight. + if !invertedExpr.IsTight() || filterIsDerived { + remainingFilters = originalExpr } return invertedExpr, remainingFilters, pfState } diff --git a/pkg/sql/opt/norm/comp_funcs.go b/pkg/sql/opt/norm/comp_funcs.go index a654e61eaabe..0d14881ba260 100644 --- a/pkg/sql/opt/norm/comp_funcs.go +++ b/pkg/sql/opt/norm/comp_funcs.go @@ -126,28 +126,6 @@ func findTimeZoneFunction(typ *types.T) (*tree.FunctionProperties, *tree.Overloa panic(errors.AssertionFailedf("could not find overload for timezone")) } -// STDistanceUseSpheroid returns true if the use_spheroid argument of -// st_distance is not explicitly false. use_spheroid is the third argument of -// st_distance for the geography overload and it is true by default. The -// geometry overload does not have a use_spheroid argument, so if either of the -// first two arguments are geometries, it returns false. -func (c *CustomFuncs) STDistanceUseSpheroid(args memo.ScalarListExpr) bool { - if len(args) < 2 { - panic(errors.AssertionFailedf("expected st_distance to have at least two arguments")) - } - if args[0].DataType().Family() == types.GeometryFamily || - args[1].DataType().Family() == types.GeometryFamily { - return false - } - const useSpheroidIdx = 2 - if len(args) <= useSpheroidIdx { - // The use_spheroid argument is true by default, so return true if it - // was not provided. - return true - } - return args[useSpheroidIdx].Op() != opt.FalseOp -} - // MakeIntersectionFunction returns an ST_Intersects function for the given // arguments. func (c *CustomFuncs) MakeIntersectionFunction(args memo.ScalarListExpr) opt.ScalarExpr { @@ -176,138 +154,3 @@ func (c *CustomFuncs) MakeIntersectionFunction(args memo.ScalarListExpr) opt.Sca }, ) } - -// MakeSTDWithinLeft returns an ST_DWithin function that replaces an expression -// of the following form: ST_Distance(a,b) <= x. Note that the ST_Distance -// function is on the left side of the inequality. -func (c *CustomFuncs) MakeSTDWithinLeft( - op opt.Operator, args memo.ScalarListExpr, bound opt.ScalarExpr, -) opt.ScalarExpr { - const fnName = "st_dwithin" - const fnIsLeftArg = true - return c.makeSTDWithin(op, args, bound, fnName, fnIsLeftArg) -} - -// MakeSTDWithinRight returns an ST_DWithin function that replaces an expression -// of the following form: x <= ST_Distance(a,b). Note that the ST_Distance -// function is on the right side of the inequality. -func (c *CustomFuncs) MakeSTDWithinRight( - op opt.Operator, args memo.ScalarListExpr, bound opt.ScalarExpr, -) opt.ScalarExpr { - const fnName = "st_dwithin" - const fnIsLeftArg = false - return c.makeSTDWithin(op, args, bound, fnName, fnIsLeftArg) -} - -// MakeSTDFullyWithinLeft returns an ST_DFullyWithin function that replaces an -// expression of the following form: ST_MaxDistance(a,b) <= x. Note that the -// ST_MaxDistance function is on the left side of the inequality. -func (c *CustomFuncs) MakeSTDFullyWithinLeft( - op opt.Operator, args memo.ScalarListExpr, bound opt.ScalarExpr, -) opt.ScalarExpr { - const fnName = "st_dfullywithin" - const fnIsLeftArg = true - return c.makeSTDWithin(op, args, bound, fnName, fnIsLeftArg) -} - -// MakeSTDFullyWithinRight returns an ST_DFullyWithin function that replaces an -// expression of the following form: x <= ST_MaxDistance(a,b). Note that the -// ST_MaxDistance function is on the right side of the inequality. -func (c *CustomFuncs) MakeSTDFullyWithinRight( - op opt.Operator, args memo.ScalarListExpr, bound opt.ScalarExpr, -) opt.ScalarExpr { - const fnName = "st_dfullywithin" - const fnIsLeftArg = false - return c.makeSTDWithin(op, args, bound, fnName, fnIsLeftArg) -} - -// makeSTDWithin returns an ST_DWithin (or ST_DFullyWithin) function that -// replaces an expression of the following form: ST_Distance(a,b) <= x. The -// ST_Distance function can be on either side of the inequality, and the -// inequality can be one of the following: '<', '<=', '>', '>='. This -// replacement allows early-exit behavior, and may enable use of an inverted -// index scan. -func (c *CustomFuncs) makeSTDWithin( - op opt.Operator, args memo.ScalarListExpr, bound opt.ScalarExpr, fnName string, fnIsLeftArg bool, -) opt.ScalarExpr { - var not bool - var name string - incName := fnName - exName := fnName + "exclusive" - switch op { - case opt.GeOp: - if fnIsLeftArg { - // Matched expression: ST_Distance(a,b) >= x. - not = true - name = exName - } else { - // Matched expression: x >= ST_Distance(a,b). - not = false - name = incName - } - - case opt.GtOp: - if fnIsLeftArg { - // Matched expression: ST_Distance(a,b) > x. - not = true - name = incName - } else { - // Matched expression: x > ST_Distance(a,b). - not = false - name = exName - } - - case opt.LeOp: - if fnIsLeftArg { - // Matched expression: ST_Distance(a,b) <= x. - not = false - name = incName - } else { - // Matched expression: x <= ST_Distance(a,b). - not = true - name = exName - } - - case opt.LtOp: - if fnIsLeftArg { - // Matched expression: ST_Distance(a,b) < x. - not = false - name = exName - } else { - // Matched expression: x < ST_Distance(a,b). - not = true - name = incName - } - } - - newArgs := make(memo.ScalarListExpr, len(args)+1) - const distanceIdx, useSpheroidIdx = 2, 3 - copy(newArgs, args[:distanceIdx]) - - // The distance parameter must be type float. - newArgs[distanceIdx] = c.f.ConstructCast(bound, types.Float) - - // Add the use_spheroid parameter if it exists. - if len(newArgs) > useSpheroidIdx { - newArgs[useSpheroidIdx] = args[useSpheroidIdx-1] - } - - props, overload, ok := memo.FindFunction(&newArgs, name) - if !ok { - panic(errors.AssertionFailedf("could not find overload for %s", name)) - } - within := c.f.ConstructFunction(newArgs, &memo.FunctionPrivate{ - Name: name, - Typ: types.Bool, - Properties: props, - Overload: overload, - }) - if not { - // ST_DWithin and ST_DWithinExclusive are equivalent to ST_Distance <= x and - // ST_Distance < x respectively. The comparison operator in the matched - // expression (if ST_Distance is normalized to be on the left) is either '>' - // or '>='. Therefore, we have to take the opposite of within. - within = c.f.ConstructNot(within) - } - return within -} diff --git a/pkg/sql/opt/norm/rules/comp.opt b/pkg/sql/opt/norm/rules/comp.opt index d123f9379003..257f2eb6b0a8 100644 --- a/pkg/sql/opt/norm/rules/comp.opt +++ b/pkg/sql/opt/norm/rules/comp.opt @@ -258,68 +258,6 @@ (MakeTimeZoneFunction (FirstScalarListExpr $args) $right) ) -# FoldEqZeroSTDistance matches an expression of the form: 'ST_Distance(a,b) = 0' -# and replaces it with 'ST_Intersects(a,b)'. This replacement allows for -# early-exit behavior, and may allow an inverted index scan to be generated. -[FoldEqZeroSTDistance, Normalize] -(Eq - (Function $args:* $private:(FunctionPrivate "st_distance")) & - ^(STDistanceUseSpheroid $args) - $right:(Const - $value:* & (IsFloatDatum $value) & (DatumsEqual $value 0) - ) -) -=> -(MakeIntersectionFunction $args) - -# FoldCmpSTDistanceLeft replaces an expression of the form: -# 'ST_Distance(...) <= x' with a call to ST_DWithin or ST_DWithinExclusive. This -# replacement allows early-exit behavior, and may enable use of an inverted -# index scan. See the MakeSTDWithin method for the specific variation on -# ST_DWithin that is used to replace expressions with different comparison -# operators (e.g. '<' vs '<='). -[FoldCmpSTDistanceLeft, Normalize] -(Ge | Gt | Le | Lt - (Function $args:* $private:(FunctionPrivate "st_distance")) - $right:* -) -=> -(MakeSTDWithinLeft (OpName) $args $right) - -# FoldCmpSTDistanceRight mirrors FoldCmpSTDistanceLeft. -[FoldCmpSTDistanceRight, Normalize] -(Ge | Gt | Le | Lt - $left:* - (Function $args:* $private:(FunctionPrivate "st_distance")) -) -=> -(MakeSTDWithinRight (OpName) $args $left) - -# FoldCmpSTMaxDistanceLeft is a variant of FoldCmpSTDistanceLeft that matches -# ST_MaxDistance instead of ST_Distance. -[FoldCmpSTMaxDistanceLeft, Normalize] -(Ge | Gt | Le | Lt - (Function - $args:* - $private:(FunctionPrivate "st_maxdistance") - ) - $right:* -) -=> -(MakeSTDFullyWithinLeft (OpName) $args $right) - -# FoldCmpSTMaxDistanceRight mirrors FoldCmpSTMaxDistanceLeft. -[FoldCmpSTMaxDistanceRight, Normalize] -(Ge | Gt | Le | Lt - $left:* - (Function - $args:* - $private:(FunctionPrivate "st_maxdistance") - ) -) -=> -(MakeSTDFullyWithinRight (OpName) $args $left) - # FoldEqTrue replaces x = True with x. [FoldEqTrue, Normalize] (Eq $left:* (True)) diff --git a/pkg/sql/opt/norm/testdata/rules/comp b/pkg/sql/opt/norm/testdata/rules/comp index c78fc082dd1f..e203d140db1d 100644 --- a/pkg/sql/opt/norm/testdata/rules/comp +++ b/pkg/sql/opt/norm/testdata/rules/comp @@ -883,357 +883,6 @@ project └── projections └── ts:1 <= '2020-06-01 13:35:55' [as="?column?":6, outer=(1)] -# -------------------------------------------------- -# FoldEqZeroSTDistance -# -------------------------------------------------- - -# Geometry case. -norm expect=FoldEqZeroSTDistance -SELECT * FROM geom_geog WHERE st_distance(geom, 'POINT(0.0 0.0)') = 0 ----- -select - ├── columns: geom:1!null geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_intersects(geom:1, '010100000000000000000000000000000000000000') [outer=(1), immutable, constraints=(/1: (/NULL - ])] - -# Geography case with use_spheroid=false. -norm expect=FoldEqZeroSTDistance -SELECT * FROM geom_geog WHERE st_distance(geog, 'POINT(0.0 0.0)', false) = 0 ----- -select - ├── columns: geom:1 geog:2!null val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_intersects(geog:2, '0101000020E610000000000000000000000000000000000000') [outer=(2), immutable, constraints=(/2: (/NULL - ])] - -# No-op case because the constant is nonzero. -norm expect-not=FoldEqZeroSTDistance -SELECT * FROM geom_geog WHERE st_distance(geom, 'POINT(0.0 0.0)') = 1 ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_distance(geom:1, '010100000000000000000000000000000000000000') = 1.0 [outer=(1), immutable] - -# No-op case because use_spheroid=true implicitly. -norm expect-not=FoldEqZeroSTDistance -SELECT * FROM geom_geog WHERE st_distance(geog, 'POINT(0.0 0.0)') = 0 ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000') = 0.0 [outer=(2), immutable] - -# No-op case because use_spheroid=true. -norm expect-not=FoldEqZeroSTDistance -SELECT * FROM geom_geog WHERE st_distance(geog, 'POINT(0.0 0.0)', true) = 0 ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000', true) = 0.0 [outer=(2), immutable] - -# -------------------------------------------------- -# FoldCmpSTDistanceLeft -# -------------------------------------------------- - -# Geometry case with '<=' operator. -norm expect=FoldCmpSTDistanceLeft -SELECT * FROM geom_geog WHERE st_distance(geom, 'point(0.0 0.0)') <= 5 ----- -select - ├── columns: geom:1!null geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dwithin(geom:1, '010100000000000000000000000000000000000000', 5.0) [outer=(1), immutable, constraints=(/1: (/NULL - ])] - -# Geometry case with '<' operator. -norm expect=FoldCmpSTDistanceLeft -SELECT * FROM geom_geog WHERE st_distance('point(0.0 0.0)', geom) < 5 ----- -select - ├── columns: geom:1!null geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dwithinexclusive('010100000000000000000000000000000000000000', geom:1, 5.0) [outer=(1), immutable, constraints=(/1: (/NULL - ])] - -# Geometry case with '>=' operator. -norm expect=FoldCmpSTDistanceLeft -SELECT * FROM geom_geog WHERE st_distance(geom, 'point(0.0 0.0)') >= 5 ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── NOT st_dwithinexclusive(geom:1, '010100000000000000000000000000000000000000', 5.0) [outer=(1), immutable] - -# Geometry case with '>' operator. -norm expect=FoldCmpSTDistanceLeft -SELECT * FROM geom_geog WHERE st_distance(geom, 'point(0.0 0.0)') > 5 ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── NOT st_dwithin(geom:1, '010100000000000000000000000000000000000000', 5.0) [outer=(1), immutable] - -# Geography case with '<=' operator. -norm expect=FoldCmpSTDistanceLeft -SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') <= 5 ----- -select - ├── columns: geom:1 geog:2!null val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dwithin(geog:2, '0101000020E610000000000000000000000000000000000000', 5.0) [outer=(2), immutable, constraints=(/2: (/NULL - ])] - -# Geography case with '<' operator. -norm expect=FoldCmpSTDistanceLeft -SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') < 5 ----- -select - ├── columns: geom:1 geog:2!null val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dwithinexclusive(geog:2, '0101000020E610000000000000000000000000000000000000', 5.0) [outer=(2), immutable, constraints=(/2: (/NULL - ])] - -# Regression test for #54326. Ensure the distance param is cast to a float. -norm expect=FoldCmpSTDistanceLeft format=(show-scalars,show-types) -SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') < 5::int ----- -select - ├── columns: geom:1(geometry) geog:2(geography!null) val:3(float) - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1(geometry) geog:2(geography) val:3(float) - └── filters - └── function: st_dwithinexclusive [type=bool, outer=(2), immutable, constraints=(/2: (/NULL - ])] - ├── variable: geog:2 [type=geography] - ├── const: '0101000020E610000000000000000000000000000000000000' [type=geography] - └── const: 5.0 [type=float] - -norm expect=FoldCmpSTDistanceLeft format=(show-scalars,show-types) -SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') < val::int ----- -select - ├── columns: geom:1(geometry) geog:2(geography!null) val:3(float) - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1(geometry) geog:2(geography) val:3(float) - └── filters - └── function: st_dwithinexclusive [type=bool, outer=(2,3), immutable, constraints=(/2: (/NULL - ])] - ├── variable: geog:2 [type=geography] - ├── const: '0101000020E610000000000000000000000000000000000000' [type=geography] - └── cast: FLOAT8 [type=float] - └── cast: INT8 [type=int] - └── variable: val:3 [type=float] - -# Regression test for #55675. Handle use_spheroid param. -norm expect=FoldCmpSTDistanceLeft -SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)', true) <= 5 ----- -select - ├── columns: geom:1 geog:2!null val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dwithin(geog:2, '0101000020E610000000000000000000000000000000000000', 5.0, true) [outer=(2), immutable, constraints=(/2: (/NULL - ])] - -# -------------------------------------------------- -# FoldCmpSTDistanceRight -# -------------------------------------------------- - -# Case with '<=' operator. -norm expect=FoldCmpSTDistanceRight -SELECT * FROM geom_geog WHERE val <= st_distance(geom, 'point(0.0 0.0)') ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── NOT st_dwithinexclusive(geom:1, '010100000000000000000000000000000000000000', val:3) [outer=(1,3), immutable] - -# Case with '<' operator. -norm expect=FoldCmpSTDistanceRight -SELECT * FROM geom_geog WHERE val < st_distance(geom, 'point(0.0 0.0)') ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── NOT st_dwithin(geom:1, '010100000000000000000000000000000000000000', val:3) [outer=(1,3), immutable] - -# Case with '>=' operator. -norm expect=FoldCmpSTDistanceRight -SELECT * FROM geom_geog WHERE val >= st_distance(geom, 'point(0.0 0.0)') ----- -select - ├── columns: geom:1!null geog:2 val:3!null - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dwithin(geom:1, '010100000000000000000000000000000000000000', val:3) [outer=(1,3), immutable, constraints=(/1: (/NULL - ]; /3: (/NULL - ])] - -# Case with '>' operator. -norm expect=FoldCmpSTDistanceRight -SELECT * FROM geom_geog WHERE val > st_distance(geom, 'point(0.0 0.0)') ----- -select - ├── columns: geom:1!null geog:2 val:3!null - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dwithinexclusive(geom:1, '010100000000000000000000000000000000000000', val:3) [outer=(1,3), immutable, constraints=(/1: (/NULL - ]; /3: (/NULL - ])] - -# Regression test for #55675. Handle use_spheroid param. -norm expect=FoldCmpSTDistanceRight -SELECT * FROM geom_geog WHERE val > st_distance(geog, 'point(0.0 0.0)', false) ----- -select - ├── columns: geom:1 geog:2!null val:3!null - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dwithinexclusive(geog:2, '0101000020E610000000000000000000000000000000000000', val:3, false) [outer=(2,3), immutable, constraints=(/2: (/NULL - ]; /3: (/NULL - ])] - -# -------------------------------------------------- -# FoldCmpSTMaxDistanceLeft -# -------------------------------------------------- - -# Case with '<=' operator. -norm expect=FoldCmpSTMaxDistanceLeft -SELECT * FROM geom_geog WHERE st_maxdistance(geom, 'point(0.0 0.0)') <= 5 ----- -select - ├── columns: geom:1!null geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dfullywithin(geom:1, '010100000000000000000000000000000000000000', 5.0) [outer=(1), immutable, constraints=(/1: (/NULL - ])] - -# Case with '<' operator. -norm expect=FoldCmpSTMaxDistanceLeft -SELECT * FROM geom_geog WHERE st_maxdistance('point(0.0 0.0)', geom) < 5 ----- -select - ├── columns: geom:1!null geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dfullywithinexclusive('010100000000000000000000000000000000000000', geom:1, 5.0) [outer=(1), immutable, constraints=(/1: (/NULL - ])] - -# Case with '>=' operator. -norm expect=FoldCmpSTMaxDistanceLeft -SELECT * FROM geom_geog WHERE st_maxdistance(geom, 'point(0.0 0.0)') >= 5 ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── NOT st_dfullywithinexclusive(geom:1, '010100000000000000000000000000000000000000', 5.0) [outer=(1), immutable] - -# Case with '>' operator. -norm expect=FoldCmpSTMaxDistanceLeft -SELECT * FROM geom_geog WHERE st_maxdistance(geom, 'point(0.0 0.0)') > 5 ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── NOT st_dfullywithin(geom:1, '010100000000000000000000000000000000000000', 5.0) [outer=(1), immutable] - -# -------------------------------------------------- -# FoldCmpSTMaxDistanceRight -# -------------------------------------------------- - -# Case with '<=' operator. -norm expect=FoldCmpSTMaxDistanceRight -SELECT * FROM geom_geog WHERE val <= st_maxdistance(geom, 'point(0.0 0.0)') ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── NOT st_dfullywithinexclusive(geom:1, '010100000000000000000000000000000000000000', val:3) [outer=(1,3), immutable] - -# Case with '<' operator. -norm expect=FoldCmpSTMaxDistanceRight -SELECT * FROM geom_geog WHERE val < st_maxdistance(geom, 'point(0.0 0.0)') ----- -select - ├── columns: geom:1 geog:2 val:3 - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── NOT st_dfullywithin(geom:1, '010100000000000000000000000000000000000000', val:3) [outer=(1,3), immutable] - -# Case with '>=' operator. -norm expect=FoldCmpSTMaxDistanceRight -SELECT * FROM geom_geog WHERE val >= st_maxdistance(geom, 'point(0.0 0.0)') ----- -select - ├── columns: geom:1!null geog:2 val:3!null - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dfullywithin(geom:1, '010100000000000000000000000000000000000000', val:3) [outer=(1,3), immutable, constraints=(/1: (/NULL - ]; /3: (/NULL - ])] - -# Case with '>' operator. -norm expect=FoldCmpSTMaxDistanceRight -SELECT * FROM geom_geog WHERE val > st_maxdistance(geom, 'point(0.0 0.0)') ----- -select - ├── columns: geom:1!null geog:2 val:3!null - ├── immutable - ├── scan geom_geog - │ └── columns: geom:1 geog:2 val:3 - └── filters - └── st_dfullywithinexclusive(geom:1, '010100000000000000000000000000000000000000', val:3) [outer=(1,3), immutable, constraints=(/1: (/NULL - ]; /3: (/NULL - ])] - # -------------------------------------------------- # FoldEqTrue + FoldEqFalse # -------------------------------------------------- diff --git a/pkg/sql/opt/xform/testdata/coster/spatial-filters b/pkg/sql/opt/xform/testdata/coster/spatial-filters index b845c8e919e9..32aeb5c72f31 100644 --- a/pkg/sql/opt/xform/testdata/coster/spatial-filters +++ b/pkg/sql/opt/xform/testdata/coster/spatial-filters @@ -132,24 +132,24 @@ WHERE st_distance(geog, st_makepoint(1.0, 1.0)::geography) < 200; project ├── columns: id:1!null ├── immutable - ├── stats: [rows=55000] - ├── cost: 535578.67 + ├── stats: [rows=166666.7] + ├── cost: 1036695.34 ├── key: (1) └── select - ├── columns: id:1!null geog:4!null + ├── columns: id:1!null geog:4 ├── immutable - ├── stats: [rows=55000, distinct(4)=50000, null(4)=0] - ├── cost: 535028.65 + ├── stats: [rows=166666.7] + ├── cost: 1035028.65 ├── key: (1) ├── fd: (1)-->(4) ├── scan g │ ├── columns: id:1!null geog:4 - │ ├── stats: [rows=500000, distinct(1)=500000, null(1)=0, distinct(4)=50000, null(4)=5000] + │ ├── stats: [rows=500000, distinct(1)=500000, null(1)=0] │ ├── cost: 530028.62 │ ├── key: (1) │ └── fd: (1)-->(4) └── filters - └── st_dwithinexclusive(geog:4, '0101000020E6100000000000000000F03F000000000000F03F', 200.0) [outer=(4), immutable, constraints=(/4: (/NULL - ])] + └── st_distance(geog:4, '0101000020E6100000000000000000F03F000000000000F03F') < 200.0 [outer=(4), immutable] opt SELECT id FROM g @@ -158,24 +158,24 @@ WHERE st_distance(geog, st_makepoint(a, 1.0)::geography) < 200; project ├── columns: id:1!null ├── immutable - ├── stats: [rows=55000] - ├── cost: 1040578.77 + ├── stats: [rows=166666.7] + ├── cost: 1541695.44 ├── key: (1) └── select - ├── columns: id:1!null a:2 geog:4!null + ├── columns: id:1!null a:2 geog:4 ├── immutable - ├── stats: [rows=55000, distinct(4)=50000, null(4)=0] - ├── cost: 1040028.75 + ├── stats: [rows=166666.7] + ├── cost: 1540028.75 ├── key: (1) ├── fd: (1)-->(2,4) ├── scan g │ ├── columns: id:1!null a:2 geog:4 - │ ├── stats: [rows=500000, distinct(1)=500000, null(1)=0, distinct(4)=50000, null(4)=5000] + │ ├── stats: [rows=500000, distinct(1)=500000, null(1)=0] │ ├── cost: 535028.72 │ ├── key: (1) │ └── fd: (1)-->(2,4) └── filters - └── st_dwithinexclusive(geog:4, st_makepoint(a:2, 1.0)::GEOGRAPHY, 200.0) [outer=(2,4), immutable, constraints=(/4: (/NULL - ])] + └── st_distance(geog:4, st_makepoint(a:2, 1.0)::GEOGRAPHY) < 200.0 [outer=(2,4), immutable] opt SELECT id FROM g diff --git a/pkg/sql/opt/xform/testdata/rules/select b/pkg/sql/opt/xform/testdata/rules/select index f040e9e09ddc..0d5aaf128d3e 100644 --- a/pkg/sql/opt/xform/testdata/rules/select +++ b/pkg/sql/opt/xform/testdata/rules/select @@ -7421,6 +7421,783 @@ index-join t63180 ├── key: (2) └── fd: (2)-->(5) +exec-ddl +CREATE TABLE geom_geog ( + geom GEOMETRY, + geog GEOGRAPHY, + val FLOAT, + INVERTED INDEX(geom), + INVERTED INDEX(geog) +) +---- + +# Tests for pre-existing normalization rules FoldEqZeroSTDistance, +# FoldCmpSTDistanceLeft, FoldCmpSTDistanceRight, FoldCmpSTMaxDistanceLeft and +# FoldCmpSTMaxDistanceRight have been moved here, and converted into tests +# detecting whether GenerateInvertedIndexScans was fired due to derivation +# of a new filter condition, used only internally when determining the +# inverted index scan spans to use. + + +# Tests related to old normalization rule FoldEqZeroSTDistance + +# Geometry case. +opt expect=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geom, 'POINT(0.0 0.0)') = 0 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1 geog:2 val:3 + │ └── inverted-filter + │ ├── columns: rowid:4!null + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ ├── pre-filterer expression + │ │ └── st_intersects('010100000000000000000000000000000000000000', geom:1) + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geom_idx + │ ├── columns: rowid:4!null geom_inverted_key:7!null + │ ├── inverted constraint: /7/4 + │ │ └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ ├── key: (4) + │ └── fd: (4)-->(7) + └── filters + └── st_distance(geom:1, '010100000000000000000000000000000000000000') = 0.0 [outer=(1), immutable] + +# Geography case with use_spheroid=false. +opt expect=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geog, 'POINT(0.0 0.0)', false) = 0 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1 geog:2 val:3 + │ └── inverted-filter + │ ├── columns: rowid:4!null + │ ├── inverted expression: /8 + │ │ ├── tight: false, unique: false + │ │ └── union spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ ├── pre-filterer expression + │ │ └── st_intersects('0101000020E610000000000000000000000000000000000000', geog:2) + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geog_idx + │ ├── columns: rowid:4!null geog_inverted_key:8!null + │ ├── inverted constraint: /8/4 + │ │ └── spans: ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ ├── key: (4) + │ └── fd: (4)-->(8) + └── filters + └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000', false) = 0.0 [outer=(2), immutable] + +# No-op case because the constant is nonzero. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geom, 'POINT(0.0 0.0)') = 1 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── st_distance(geom:1, '010100000000000000000000000000000000000000') = 1.0 [outer=(1), immutable] + +# No-op case because use_spheroid=true implicitly. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geog, 'POINT(0.0 0.0)') = 0 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000') = 0.0 [outer=(2), immutable] + +# No-op case because use_spheroid=true. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geog, 'POINT(0.0 0.0)', true) = 0 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000', true) = 0.0 [outer=(2), immutable] + +# Tests related to old normalization rule FoldCmpSTDistanceLeft + +# Geometry case with '<=' operator. +opt expect=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geom, 'point(0.0 0.0)') <= 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1 geog:2 val:3 + │ └── inverted-filter + │ ├── columns: rowid:4!null + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"] + │ ├── pre-filterer expression + │ │ └── st_dwithin('010100000000000000000000000000000000000000', geom:1, 5.0) + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geom_idx + │ ├── columns: rowid:4!null geom_inverted_key:7!null + │ ├── inverted constraint: /7/4 + │ │ └── spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"] + │ ├── key: (4) + │ └── fd: (4)-->(7) + └── filters + └── st_distance(geom:1, '010100000000000000000000000000000000000000') <= 5.0 [outer=(1), immutable] + +# Geometry case with '<' operator. +opt expect=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance('point(0.0 0.0)', geom) < 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1 geog:2 val:3 + │ └── inverted-filter + │ ├── columns: rowid:4!null + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"] + │ ├── pre-filterer expression + │ │ └── st_dwithinexclusive('010100000000000000000000000000000000000000', geom:1, 5.0) + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geom_idx + │ ├── columns: rowid:4!null geom_inverted_key:7!null + │ ├── inverted constraint: /7/4 + │ │ └── spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"] + │ ├── key: (4) + │ └── fd: (4)-->(7) + └── filters + └── st_distance('010100000000000000000000000000000000000000', geom:1) < 5.0 [outer=(1), immutable] + +# Geometry case with '>=' operator. +# Inverted index scan not expected because derived expression is +# NOT st_dwithinexclusive. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geom, 'point(0.0 0.0)') >= 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── st_distance(geom:1, '010100000000000000000000000000000000000000') >= 5.0 [outer=(1), immutable] + +# Geometry case with '>' operator. +# Inverted index scan not expected because derived expression is NOT st_dwithin. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geom, 'point(0.0 0.0)') > 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── st_distance(geom:1, '010100000000000000000000000000000000000000') > 5.0 [outer=(1), immutable] + +# Geography case with '<=' operator. +opt expect=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') <= 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1 geog:2 val:3 + │ └── inverted-filter + │ ├── columns: rowid:4!null + │ ├── inverted expression: /8 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"] + │ │ └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00") + │ ├── pre-filterer expression + │ │ └── st_dwithin('0101000020E610000000000000000000000000000000000000', geog:2, 5.0) + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geog_idx + │ ├── columns: rowid:4!null geog_inverted_key:8!null + │ ├── inverted constraint: /8/4 + │ │ └── spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"] + │ │ └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00") + │ ├── key: (4) + │ └── fd: (4)-->(8) + └── filters + └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000') <= 5.0 [outer=(2), immutable] + +# Geography case with '<' operator. +opt expect=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') < 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1 geog:2 val:3 + │ └── inverted-filter + │ ├── columns: rowid:4!null + │ ├── inverted expression: /8 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"] + │ │ └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00") + │ ├── pre-filterer expression + │ │ └── st_dwithin('0101000020E610000000000000000000000000000000000000', geog:2, 5.0) + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geog_idx + │ ├── columns: rowid:4!null geog_inverted_key:8!null + │ ├── inverted constraint: /8/4 + │ │ └── spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"] + │ │ └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00") + │ ├── key: (4) + │ └── fd: (4)-->(8) + └── filters + └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000') < 5.0 [outer=(2), immutable] + +# Regression test for #54326. Ensure the distance param is cast to a float. +opt expect=GenerateInvertedIndexScans format=(show-scalars,show-types) +SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') < 5::int +---- +select + ├── columns: geom:1(geometry) geog:2(geography) val:3(float) + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1(geometry) geog:2(geography) val:3(float) + │ └── inverted-filter + │ ├── columns: rowid:4(int!null) + │ ├── inverted expression: /8 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"] + │ │ └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00") + │ ├── pre-filterer expression + │ │ └── function: st_dwithin [type=bool] + │ │ ├── const: '0101000020E610000000000000000000000000000000000000' [type=geography] + │ │ ├── variable: geog:2 [type=geography] + │ │ └── const: 5.0 [type=float] + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geog_idx + │ ├── columns: rowid:4(int!null) geog_inverted_key:8(encodedkey!null) + │ ├── inverted constraint: /8/4 + │ │ └── spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"] + │ │ └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00") + │ ├── key: (4) + │ └── fd: (4)-->(8) + └── filters + └── lt [type=bool, outer=(2), immutable] + ├── function: st_distance [type=float] + │ ├── variable: geog:2 [type=geography] + │ └── const: '0101000020E610000000000000000000000000000000000000' [type=geography] + └── const: 5 [type=int] + +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans format=(show-scalars,show-types) +SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)') < val::int +---- +select + ├── columns: geom:1(geometry) geog:2(geography) val:3(float) + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1(geometry) geog:2(geography) val:3(float) + └── filters + └── lt [type=bool, outer=(2,3), immutable] + ├── function: st_distance [type=float] + │ ├── variable: geog:2 [type=geography] + │ └── const: '0101000020E610000000000000000000000000000000000000' [type=geography] + └── cast: INT8 [type=int] + └── variable: val:3 [type=float] + +# Regression test for #55675. Handle use_spheroid param. +opt expect=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_distance(geog, 'point(0.0 0.0)', true) <= 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1 geog:2 val:3 + │ └── inverted-filter + │ ├── columns: rowid:4!null + │ ├── inverted expression: /8 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"] + │ │ └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00") + │ ├── pre-filterer expression + │ │ └── st_dwithin('0101000020E610000000000000000000000000000000000000', geog:2, 5.0, true) + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geog_idx + │ ├── columns: rowid:4!null geog_inverted_key:8!null + │ ├── inverted constraint: /8/4 + │ │ └── spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd \x00\x00\x00\x00\x00\x00\x01", "B\xfd\"\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\"\x00\x00\x00\x00\x00\x00\x01", "B\xfd$\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd$\x00\x00\x00\x00\x00\x00\x00", "B\xfd$\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd0\x00\x00\x00\x00\x00\x00\x00", "B\xfd0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x00", "B\xfd<\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd<\x00\x00\x00\x00\x00\x00\x01", "B\xfd>\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd>\x00\x00\x00\x00\x00\x00\x01", "B\xfd@\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd@\x00\x00\x00\x00\x00\x00\x01", "B\xfdB\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdD\x00\x00\x00\x00\x00\x00\x00", "B\xfdD\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdF\x00\x00\x00\x00\x00\x00\x01", "B\xfdH\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdH\x00\x00\x00\x00\x00\x00\x01", "B\xfdJ\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdJ\x00\x00\x00\x00\x00\x00\x01", "B\xfdL\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfdL\x00\x00\x00\x00\x00\x00\x00", "B\xfdL\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfdP\x00\x00\x00\x00\x00\x00\x00", "B\xfdP\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8a\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x8c\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x8c\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x8e\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x90\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x90\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x92\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x00", "B\xfd\x94\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\x94\x00\x00\x00\x00\x00\x00\x01", "B\xfd\x96\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb0\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xb4\x00\x00\x00\x00\x00\x00\x00"] + │ │ ├── ["B\xfd\xb4\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb6\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb6\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xb8\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xb8\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xba\x00\x00\x00\x00\x00\x00\x00") + │ │ ├── ["B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00", "B\xfd\xbc\x00\x00\x00\x00\x00\x00\x00"] + │ │ └── ["B\xfd\xbe\x00\x00\x00\x00\x00\x00\x01", "B\xfd\xc0\x00\x00\x00\x00\x00\x00\x00") + │ ├── key: (4) + │ └── fd: (4)-->(8) + └── filters + └── st_distance(geog:2, '0101000020E610000000000000000000000000000000000000', true) <= 5.0 [outer=(2), immutable] + +# Tests related to old normalization rule FoldCmpSTDistanceRight + +# Case with '<=' operator. +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE val <= st_distance(geom, 'point(0.0 0.0)') +---- +select + ├── columns: geom:1 geog:2 val:3!null + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── val:3 <= st_distance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])] + +# Case with '<' operator. +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE val < st_distance(geom, 'point(0.0 0.0)') +---- +select + ├── columns: geom:1 geog:2 val:3!null + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── val:3 < st_distance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])] + +# Case with '>=' operator. +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE val >= st_distance(geom, 'point(0.0 0.0)') +---- +select + ├── columns: geom:1 geog:2 val:3!null + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── val:3 >= st_distance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])] + +# Case with '>' operator. +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE val > st_distance(geom, 'point(0.0 0.0)') +---- +select + ├── columns: geom:1 geog:2 val:3!null + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── val:3 > st_distance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])] + +# Regression test for #55675. Handle use_spheroid param. +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE val > st_distance(geog, 'point(0.0 0.0)', false) +---- +select + ├── columns: geom:1 geog:2 val:3!null + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── val:3 > st_distance(geog:2, '0101000020E610000000000000000000000000000000000000', false) [outer=(2,3), immutable, constraints=(/3: (/NULL - ])] + +# Tests related to old normalization rule FoldCmpSTMaxDistanceLeft + +# Case with '<=' operator. +opt expect=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_maxdistance(geom, 'point(0.0 0.0)') <= 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1 geog:2 val:3 + │ └── inverted-filter + │ ├── columns: rowid:4!null + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"] + │ ├── pre-filterer expression + │ │ └── st_dfullywithin('010100000000000000000000000000000000000000', geom:1, 5.0) + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geom_idx + │ ├── columns: rowid:4!null geom_inverted_key:7!null + │ ├── inverted constraint: /7/4 + │ │ └── spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"] + │ ├── key: (4) + │ └── fd: (4)-->(7) + └── filters + └── st_maxdistance(geom:1, '010100000000000000000000000000000000000000') <= 5.0 [outer=(1), immutable] + +# Case with '<' operator. +opt expect=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_maxdistance('point(0.0 0.0)', geom) < 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── index-join geom_geog + │ ├── columns: geom:1 geog:2 val:3 + │ └── inverted-filter + │ ├── columns: rowid:4!null + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"] + │ ├── pre-filterer expression + │ │ └── st_dfullywithinexclusive('010100000000000000000000000000000000000000', geom:1, 5.0) + │ ├── key: (4) + │ └── scan geom_geog@geom_geog_geom_idx + │ ├── columns: rowid:4!null geom_inverted_key:7!null + │ ├── inverted constraint: /7/4 + │ │ └── spans + │ │ ├── ["B\x89", "B\xfd \x00\x00\x00\x00\x00\x00\x00") + │ │ └── ["B\xfd\xff\xff\xff\xff\xff\xff\xff\xff", "B\xfd\xff\xff\xff\xff\xff\xff\xff\xff"] + │ ├── key: (4) + │ └── fd: (4)-->(7) + └── filters + └── st_maxdistance('010100000000000000000000000000000000000000', geom:1) < 5.0 [outer=(1), immutable] + +# Case with '>=' operator. +# Inverted index scan not expected because derived expression is +# NOT st_dwithinexclusive. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_maxdistance(geom, 'point(0.0 0.0)') >= 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── st_maxdistance(geom:1, '010100000000000000000000000000000000000000') >= 5.0 [outer=(1), immutable] + +# Case with '>' operator. +# Inverted index scan not expected because derived expression is NOT st_within. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE st_maxdistance(geom, 'point(0.0 0.0)') > 5 +---- +select + ├── columns: geom:1 geog:2 val:3 + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── st_maxdistance(geom:1, '010100000000000000000000000000000000000000') > 5.0 [outer=(1), immutable] + +# Tests related to old normalization rule FoldCmpSTMaxDistanceRight + +# Case with '<=' operator. +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE val <= st_maxdistance(geom, 'point(0.0 0.0)') +---- +select + ├── columns: geom:1 geog:2 val:3!null + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── val:3 <= st_maxdistance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])] + +# Case with '<' operator. +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE val < st_maxdistance(geom, 'point(0.0 0.0)') +---- +select + ├── columns: geom:1 geog:2 val:3!null + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── val:3 < st_maxdistance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])] + +# Case with '>=' operator. +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE val >= st_maxdistance(geom, 'point(0.0 0.0)') +---- +select + ├── columns: geom:1 geog:2 val:3!null + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── val:3 >= st_maxdistance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])] + +# Case with '>' operator. +# Inverted index scan not expected because `st_distance` result compared +# with column. +opt expect-not=GenerateInvertedIndexScans +SELECT * FROM geom_geog WHERE val > st_maxdistance(geom, 'point(0.0 0.0)') +---- +select + ├── columns: geom:1 geog:2 val:3!null + ├── immutable + ├── scan geom_geog + │ └── columns: geom:1 geog:2 val:3 + └── filters + └── val:3 > st_maxdistance(geom:1, '010100000000000000000000000000000000000000') [outer=(1,3), immutable, constraints=(/3: (/NULL - ])] + # -------------------------------------------------- # GenerateZigzagJoins # --------------------------------------------------