diff --git a/pkg/sql/opt/exec/execbuilder/testdata/select b/pkg/sql/opt/exec/execbuilder/testdata/select index c705d02692c3..0e555cfffb32 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/select +++ b/pkg/sql/opt/exec/execbuilder/testdata/select @@ -521,6 +521,54 @@ scan · · (f float) · statement ok DROP TABLE flt +# ------------------------------------------------------------------------------ +# Verify we create the correct spans for negative numbers with extra +# operations. +# ------------------------------------------------------------------------------ + +statement ok +CREATE TABLE num ( + i int null, + unique index (i), + f float null, + unique index (f), + d decimal null, + unique index (d), + n interval null, + unique index (n) +) + +query TTTTT +EXPLAIN (TYPES) SELECT i FROM num WHERE i = -1:::INT +---- +scan · · (i int) · +· table num@num_i_key · · +· spans /-1-/0 · · + +query TTTTT +EXPLAIN (TYPES) SELECT f FROM num WHERE f = -1:::FLOAT +---- +scan · · (f float) · +· table num@num_f_key · · +· spans /-1-/-1/PrefixEnd · · + +query TTTTT +EXPLAIN (TYPES) SELECT d FROM num WHERE d = -1:::DECIMAL +---- +scan · · (d decimal) · +· table num@num_d_key · · +· spans /-1-/-1/PrefixEnd · · + +query TTTTT +EXPLAIN (TYPES) SELECT n FROM num WHERE n = -'1h':::INTERVAL +---- +scan · · (n interval) · +· table num@num_n_key · · +· spans /-1h-/1d-25h · · + +statement ok +DROP TABLE num + # ------------------------------------------------------------------------------ # ANY, ALL tests. # ------------------------------------------------------------------------------ diff --git a/pkg/sql/opt/idxconstraint/testdata/single-column b/pkg/sql/opt/idxconstraint/testdata/single-column index f98b682892c4..667effbfc76f 100644 --- a/pkg/sql/opt/idxconstraint/testdata/single-column +++ b/pkg/sql/opt/idxconstraint/testdata/single-column @@ -228,6 +228,16 @@ index-constraints vars=(int) index=(@1 desc) ---- [/5 - /5] +index-constraints vars=(int) index=(@1) +@1 = -1 +---- +[/-1 - /-1] + +index-constraints vars=(decimal) index=(@1) +@1 = -2.0 +---- +[/-2.0 - /-2.0] + index-constraints vars=(int) index=(@1 desc) @1 IS DISTINCT FROM 5 ---- diff --git a/pkg/sql/opt/norm/custom_funcs.go b/pkg/sql/opt/norm/custom_funcs.go index 49cf6222e660..55daf2a2ea79 100644 --- a/pkg/sql/opt/norm/custom_funcs.go +++ b/pkg/sql/opt/norm/custom_funcs.go @@ -761,3 +761,32 @@ func (c *CustomFuncs) IsOne(input memo.GroupID) bool { } return false } + +// CanFoldUnaryMinus checks if a constant numeric value can be negated. +func (c *CustomFuncs) CanFoldUnaryMinus(input memo.GroupID) bool { + d := c.f.mem.LookupPrivate(c.f.mem.NormExpr(input).AsConst().Value()).(tree.Datum) + if t, ok := d.(*tree.DInt); ok { + return *t != math.MinInt64 + } + return true +} + +// NegateNumeric applies a unary minus to a numeric value. +func (c *CustomFuncs) NegateNumeric(input memo.GroupID) memo.GroupID { + d := c.f.mem.LookupPrivate(c.f.mem.NormExpr(input).AsConst().Value()).(tree.Datum) + var id memo.PrivateID + switch t := d.(type) { + case *tree.DDecimal: + id = c.f.InternDatum(t.Negate()) + case *tree.DFloat: + id = c.f.InternDatum(t.Negate()) + case *tree.DInt: + id = c.f.InternDatum(t.Negate()) + case *tree.DInterval: + id = c.f.InternDatum(t.Negate()) + default: + panic("unrecognized numeric type") + } + + return c.f.ConstructConst(id) +} diff --git a/pkg/sql/opt/norm/rules/numeric.opt b/pkg/sql/opt/norm/rules/numeric.opt index 046273a9c30b..57c93370ec0c 100644 --- a/pkg/sql/opt/norm/rules/numeric.opt +++ b/pkg/sql/opt/norm/rules/numeric.opt @@ -65,3 +65,11 @@ $left # EliminateUnaryMinus discards a doubled UnaryMinus operator. [EliminateUnaryMinus, Normalize] (UnaryMinus (UnaryMinus $input:*)) => $input + +# FoldUnaryMinus negates a constant value within a UnaryMinus. +[FoldUnaryMinus, Normalize] +(UnaryMinus + $input:(Const) & (CanFoldUnaryMinus $input) +) +=> +(NegateNumeric $input) diff --git a/pkg/sql/opt/norm/testdata/rules/combo b/pkg/sql/opt/norm/testdata/rules/combo index c716887af0d5..20876dd4e8c1 100644 --- a/pkg/sql/opt/norm/testdata/rules/combo +++ b/pkg/sql/opt/norm/testdata/rules/combo @@ -135,7 +135,7 @@ PushFilterIntoJoinLeft + └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] + └── a.k = xy.x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] ================================================================================ -PruneJoinLeftCols +PruneJoinRightCols Cost: 2163.33 ================================================================================ project @@ -176,7 +176,7 @@ PruneJoinLeftCols └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] └── a.k = xy.x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] ================================================================================ -PruneSelectCols +PruneLimitCols Cost: 2143.33 ================================================================================ project @@ -210,7 +210,7 @@ PruneSelectCols └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] └── a.k = xy.x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] ================================================================================ -EliminateProject +EliminateProjectProject Cost: 2143.33 ================================================================================ project @@ -247,7 +247,7 @@ EliminateProject └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] └── a.k = xy.x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] ================================================================================ -PruneJoinRightCols +PruneAggCols Cost: 2133.33 ================================================================================ project @@ -277,7 +277,7 @@ PruneJoinRightCols └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] └── a.k = xy.x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] -------------------------------------------------------------------------------- -GenerateIndexScans (higher cost) +ConstrainScan (higher cost) -------------------------------------------------------------------------------- project ├── columns: s:4(string) @@ -308,13 +308,13 @@ GenerateIndexScans (higher cost) └── filters [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] └── a.k = xy.x [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ])] -------------------------------------------------------------------------------- -ConstrainScan (no changes) +PushFilterIntoLookupJoinNoRemainder (no changes) -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -ConstrainLookupJoinIndexScan (no changes) +NumRuleNames (no changes) -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -GenerateIndexScans (no changes) +ConstrainScan (no changes) -------------------------------------------------------------------------------- ================================================================================ Final best expression @@ -385,7 +385,7 @@ SimplifyFilters + ├── a.s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight)] + └── a.f > 100.0 [type=bool, outer=(3), constraints=(/3: [/100.00000000000001 - ]; tight)] ================================================================================ -PruneSelectCols +PruneLimitCols Cost: 1090.00 ================================================================================ project @@ -408,7 +408,7 @@ PruneSelectCols ├── a.s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight)] └── a.f > 100.0 [type=bool, outer=(3), constraints=(/3: [/100.00000000000001 - ]; tight)] ================================================================================ -GenerateIndexScans +ConstrainScan Cost: 1080.00 ================================================================================ project @@ -428,10 +428,10 @@ GenerateIndexScans ├── a.s = 'foo' [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight)] └── a.f > 100.0 [type=bool, outer=(3), constraints=(/3: [/100.00000000000001 - ]; tight)] -------------------------------------------------------------------------------- -ConstrainScan (no changes) +PushFilterIntoLookupJoinNoRemainder (no changes) -------------------------------------------------------------------------------- ================================================================================ -ConstrainScan +PushFilterIntoLookupJoinNoRemainder Cost: 1.54 ================================================================================ project @@ -648,7 +648,7 @@ EliminateEmptyAnd - └── filters [type=bool] + └── true [type=bool] ================================================================================ -EliminateSelect +EnsureSelectFiltersAnd Cost: 2160.00 ================================================================================ -select @@ -680,7 +680,7 @@ EliminateSelect + └── filters [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ])] + └── xy.y = a.i [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ])] -------------------------------------------------------------------------------- -GenerateIndexScans (higher cost) +ConstrainScan (higher cost) -------------------------------------------------------------------------------- semi-join ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) @@ -704,7 +704,7 @@ GenerateIndexScans (higher cost) └── filters [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ])] └── xy.y = a.i [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ])] -------------------------------------------------------------------------------- -GenerateIndexScans (no changes) +ConstrainScan (no changes) -------------------------------------------------------------------------------- ================================================================================ Final best expression @@ -750,7 +750,7 @@ Initial expression │ └── fd: (3)-->(4-7), (5,6)~~>(3,4,7) └── variable: xy.x [type=int, outer=(1)] ================================================================================ -PruneScanCols +PruneSelectCols Cost: 2100.00 ================================================================================ project @@ -772,7 +772,7 @@ PruneScanCols + │ └── key: (3) └── variable: xy.x [type=int, outer=(1)] ================================================================================ -EliminateProject +EliminateProjectProject Cost: 2100.00 ================================================================================ project @@ -793,7 +793,7 @@ EliminateProject + │ └── key: (3) └── variable: xy.x [type=int, outer=(1)] ================================================================================ -PruneScanCols +PruneSelectCols Cost: 2090.00 ================================================================================ project @@ -867,7 +867,7 @@ HoistProjectSubquery + └── projections [outer=(11)] + └── variable: case [type=bool, outer=(11)] ================================================================================ -EnsureSelectFilters +MergeSelects Cost: 2110.01 ================================================================================ project @@ -1268,7 +1268,7 @@ EliminateGroupByProject └── projections [outer=(11)] └── variable: case [type=bool, outer=(11)] ================================================================================ -EliminateSelect +EnsureSelectFiltersAnd Cost: 2120.00 ================================================================================ project @@ -1340,7 +1340,7 @@ EliminateSelect └── projections [outer=(11)] └── variable: case [type=bool, outer=(11)] ================================================================================ -EliminateSelect +EnsureSelectFiltersAnd Cost: 2110.00 ================================================================================ project @@ -1413,7 +1413,7 @@ EliminateSelect └── projections [outer=(11)] └── variable: case [type=bool, outer=(11)] ================================================================================ -PruneProjectCols +PruneScanCols Cost: 2110.00 ================================================================================ project @@ -1519,10 +1519,10 @@ InlineProjectInProject + └── projections [outer=(1,10)] + └── CASE WHEN bool_or AND (xy.x IS NOT NULL) THEN true WHEN bool_or IS NULL THEN false END [type=bool, outer=(1,10)] -------------------------------------------------------------------------------- -GenerateIndexScans (no changes) +ConstrainScan (no changes) -------------------------------------------------------------------------------- ================================================================================ -GenerateIndexScans +ConstrainScan Cost: 2100.00 ================================================================================ project diff --git a/pkg/sql/opt/norm/testdata/rules/numeric b/pkg/sql/opt/norm/testdata/rules/numeric index 2b13e4c4477a..4767d3208172 100644 --- a/pkg/sql/opt/norm/testdata/rules/numeric +++ b/pkg/sql/opt/norm/testdata/rules/numeric @@ -130,3 +130,116 @@ project │ └── columns: i:2(int) └── projections [outer=(2)] └── variable: a.i [type=int, outer=(2)] + +# -------------------------------------------------- +# FoldUnaryMinus +# -------------------------------------------------- +opt +SELECT -(1:::int) +---- +project + ├── columns: "?column?":1(int!null) + ├── cardinality: [1 - 1] + ├── key: () + ├── fd: ()-->(1) + ├── values + │ ├── cardinality: [1 - 1] + │ ├── key: () + │ └── tuple [type=tuple{}] + └── projections + └── const: -1 [type=int] + +opt +SELECT -(1:::float) +---- +project + ├── columns: "?column?":1(float!null) + ├── cardinality: [1 - 1] + ├── key: () + ├── fd: ()-->(1) + ├── values + │ ├── cardinality: [1 - 1] + │ ├── key: () + │ └── tuple [type=tuple{}] + └── projections + └── const: -1.0 [type=float] + +# TODO(justin): it would be better if this produced an error in the optimizer +# rather than falling back to execution to error. +opt format=show-all +SELECT -((-9223372036854775808)::int) +---- +project + ├── columns: "?column?":1(int) + ├── cardinality: [1 - 1] + ├── stats: [rows=1] + ├── cost: 0.01 + ├── key: () + ├── fd: ()-->(1) + ├── prune: (1) + ├── values + │ ├── cardinality: [1 - 1] + │ ├── stats: [rows=1] + │ ├── cost: 0.01 + │ ├── key: () + │ └── tuple [type=tuple{}] + └── projections + └── unary-minus [type=int] + └── const: -9223372036854775808 [type=int] + +opt format=show-all +SELECT -(1:::decimal) +---- +project + ├── columns: "?column?":1(decimal!null) + ├── cardinality: [1 - 1] + ├── stats: [rows=1] + ├── cost: 0.01 + ├── key: () + ├── fd: ()-->(1) + ├── prune: (1) + ├── values + │ ├── cardinality: [1 - 1] + │ ├── stats: [rows=1] + │ ├── cost: 0.01 + │ ├── key: () + │ └── tuple [type=tuple{}] + └── projections + └── const: -1 [type=decimal] + +opt format=show-all +SELECT -('-1d'::interval); +---- +project + ├── columns: "?column?":1(interval!null) + ├── cardinality: [1 - 1] + ├── stats: [rows=1] + ├── cost: 0.01 + ├── key: () + ├── fd: ()-->(1) + ├── prune: (1) + ├── values + │ ├── cardinality: [1 - 1] + │ ├── stats: [rows=1] + │ ├── cost: 0.01 + │ ├── key: () + │ └── tuple [type=tuple{}] + └── projections + └── const: '1d' [type=interval] + +# TODO(justin): this seems incorrect but it's consistent with the existing +# planner. Revisit this: #26932. +opt +SELECT -('-9223372036854775808d'::interval); +---- +project + ├── columns: "?column?":1(interval!null) + ├── cardinality: [1 - 1] + ├── key: () + ├── fd: ()-->(1) + ├── values + │ ├── cardinality: [1 - 1] + │ ├── key: () + │ └── tuple [type=tuple{}] + └── projections + └── const: '-9223372036854775808d' [type=interval] diff --git a/pkg/sql/opt/optbuilder/testdata/scalar b/pkg/sql/opt/optbuilder/testdata/scalar index 62e7c787a4ce..053ce1a63068 100644 --- a/pkg/sql/opt/optbuilder/testdata/scalar +++ b/pkg/sql/opt/optbuilder/testdata/scalar @@ -736,3 +736,14 @@ build-scalar ARRAY['"foo"'::json] ---- error: arrays of jsonb not allowed + +opt +SELECT -((-9223372036854775808):::int) +---- +project + ├── columns: "?column?":1(int) + ├── values + │ └── tuple [type=tuple{}] + └── projections + └── unary-minus [type=int] + └── const: -9223372036854775808 [type=int] diff --git a/pkg/sql/sem/tree/eval.go b/pkg/sql/sem/tree/eval.go index ea382ae32ee1..633a39c16b35 100644 --- a/pkg/sql/sem/tree/eval.go +++ b/pkg/sql/sem/tree/eval.go @@ -64,6 +64,33 @@ var ( // SecondsInDay is the number of seconds in a Day. const SecondsInDay = 24 * 60 * 60 +// Negate returns the negative of a non-math.MinInt64 DInt. +func (d *DInt) Negate() *DInt { + return NewDInt(-*d) +} + +// Negate returns the negative of a DFloat. +func (d *DFloat) Negate() *DFloat { + return NewDFloat(-*d) +} + +// Negate returns the negative of a DDecimal. +func (d *DDecimal) Negate() *DDecimal { + dec := &d.Decimal + dd := &DDecimal{} + dd.Decimal.Neg(dec) + return dd +} + +// Negate returns the negative of a DInterval. +func (d *DInterval) Negate() *DInterval { + i := d.Duration + i.Nanos = -i.Nanos + i.Days = -i.Days + i.Months = -i.Months + return &DInterval{Duration: i} +} + // UnaryOp is a unary operator. type UnaryOp struct { Typ types.T @@ -135,35 +162,28 @@ var UnaryOps = map[UnaryOperator]unaryOpOverload{ if i == math.MinInt64 { return nil, errIntOutOfRange } - return NewDInt(-i), nil + return i.Negate(), nil }, }, UnaryOp{ Typ: types.Float, ReturnType: types.Float, fn: func(_ *EvalContext, d Datum) (Datum, error) { - return NewDFloat(-*d.(*DFloat)), nil + return d.(*DFloat).Negate(), nil }, }, UnaryOp{ Typ: types.Decimal, ReturnType: types.Decimal, fn: func(_ *EvalContext, d Datum) (Datum, error) { - dec := &d.(*DDecimal).Decimal - dd := &DDecimal{} - dd.Decimal.Neg(dec) - return dd, nil + return d.(*DDecimal).Negate(), nil }, }, UnaryOp{ Typ: types.Interval, ReturnType: types.Interval, fn: func(_ *EvalContext, d Datum) (Datum, error) { - i := d.(*DInterval).Duration - i.Nanos = -i.Nanos - i.Days = -i.Days - i.Months = -i.Months - return &DInterval{Duration: i}, nil + return d.(*DInterval).Negate(), nil }, }, },